Skip to content

Releases: devoidfury/hotdog

v0.11.3

Choose a tag to compare

@devoidfury devoidfury released this 07 Oct 03:02

Loads of little fixes, model modality detection improvements, polishing on the webui now that I'm using it more heavily.

  • question tool - a pending question survives a tab refresh. A question blocked when the webui reconnects now re-renders its card after history instead of hanging with no UI to answer. Replay moved from the SessionManager's QUESTION-event buffer to the question bridge itself: on attach or session switch the server peeks the live pending question.

  • webui - wss:// on https pages. The chat socket URL hardcoded ws://, which browsers block as mixed content on an https page.

  • webui - dropped the pulsing glow on the connection indicator

  • evals - added independent evals runner and a first easy series kielbasa-1

  • webui - attachments render inside the user bubble. @-ref uploads echoed as an extra SYSTEM_MESSAGE per file while the bubble itself showed the file-include part's raw JSON. The user-message event now carries the text plus a files[] view, rendered as collapsible boxes in the bubble -- live, session replay, and cold log replay. SystemMessageEvent loses its files field.

  • Removed --no-stream / stream config (breaking). The flag only gated display -- the request path always streamed -- so --no-stream runs silently dropped the answer. Output is now always streamed; --no-stream fails fast as an unknown flag and "stream" in defaults.json surfaces as an unknown key in hotdog rescue.

  • fetch tool returns images. A fetched PNG/JPEG/WebP/GIF now attaches to the tool result as a base64 image_url, exactly like read -- a vision model can look at a chart or screenshot straight from a URL. Gated by modelAcceptsImages before any bytes are read (fail-closed on text-only models), capped at the shared 10MB image limit with a streaming byte cap so endless streams get cancelled, not buffered. Other image/* types (e.g. svg) keep the text path; HEAD still returns headers only.

  • @-ref image attachments get their own size budget. fileAttachment.maxFileSize (100KB) gated text and images alike, so screenshots over 100KB were silently dropped from @-refs even on a vision model -- while read saw them fine under its 10MB cap. New fileAttachment.maxImageSize (default 10MB, matching read) applies to images; text budget unchanged. The webui Attach button shares the split: image/* uploads over the websocket are sized against maxImageSize.

  • llama.cpp model-modality detection. fetchModels now understands plain llama.cpp's /v1/models shape, not just llama-swap: context from data[].meta.n_ctx, vision from the ollama-style capabilities list, aliases expand. When the payload has no modality data it falls back to /props. Vision models on bare llama.cpp now report inputModalities: ["text","image"] without hand-editing provider config; non-llama.cpp providers see no extra requests.

  • --disable-env-scrubbing CLI flag: turns off env scrubbing for agent-spawned subprocesses (bash tool, MCP stdio); children inherit the full environment. Default stays scrubbed.

  • provider health tracking. A dead provider looks idle -- the preferred placement target -- until a task locks onto it and burns its retries on connection-refused. Placement now consults reachability verdicts: a sweep every providerHealthCheckIntervalSecs (default 30, 0 disables the timer) probes each provider with a resolvable url -- /v1/models for fetchModels: true providers, which doubles as a live catalog refresh upserted into the running registry, /health otherwise -- and connection-level task failures (refused/DNS/reset) demote a provider immediately, active even at 0. Reachability is status-agnostic: a 500 or a stalled generation still proves the socket answers. Recovery clears through a successful probe or any task completing a turn, so demotion is never process-permanent. A down provider is skipped during multi-candidate placement unless every candidate is down (fail-open); pins are never rerouted. hotdog info shows the per-provider up/down verdict.

  • fix tool-result blindness on strict chat templates. Since the tool-result part refactor, role: "tool" messages rode the wire as part arrays; llama.cpp/Ollama templates interpolate tool content as a raw string and choke, so some models effectively never saw tool results. Text-only tool content now collapses back to a plain \n-joined string on the wire; arrays with non-text parts (images) stay arrays.

  • marker-mangler - chatML tokens are escaped too. ChatML control tokens (<|im_start|>) in untrusted text are rewritten to a safe form and reversed on output -- chatML markers survive enough model templates to be common breakage.

Full Changelog: v0.11.2...v0.11.3

v0.11.2

Choose a tag to compare

@devoidfury devoidfury released this 29 Sep 05:32
  • Workflows: validate-is-save, file dispatch, and graph params. Manager tool workflow_save is gone. workflow_validate now saves a valid design on the spot (as <name>.workflow.yaml, returning the path) and also accepts file to re-validate an existing graph. workflow_dispatch takes file (path or bare name resolved against the workflows dir) instead of pasted yaml, closing the loop so validation is a required step and designs avoid round-trip through the transcript. Graphs can declare a top-level params block (id -> default, empty = required) and reference values as {{params.x}} in any string; workflow_dispatch(args={...}) and the CLI's repeatable --param key=value fill them, making saved graphs reusable templates. Substitution runs on the parsed YAML tree, so values can't inject graph structure.

  • fix regression in startup --profile tool filtering, causing tools to be loaded based on the default profile regardless

  • task-lanes - fix address issue causing cap+1 lanes on providers

  • fix frontmatter-only (empty body) task agent profile

  • updates to example profiles (meta updates, new manager)

Full Changelog: v0.11.1...v0.11.2

v0.11.1

Choose a tag to compare

@devoidfury devoidfury released this 28 Sep 07:19

Small but important fix to solve an issue causing hangs when using task-lanes (lanesRetryTimer storm)

Full Changelog: v0.11.0...v0.11.1

v0.11.0

Choose a tag to compare

@devoidfury devoidfury released this 27 Sep 13:58

[v0.11.0] - 2026-09-27

This one's a footlong! Biggest new features: optimizations for local models, local infra subagent fan-out, /profile switch, session rewind/forking, tab completions in the webui.

  • Workflows: multi-agent DAG orchestration. A workflow is a YAML graph of worker nodes -- saved as config/workflows/<name>.workflow.yaml or designed by a manager -- where each node declares a profile, a model pin/requirements, inputs from upstream nodes, and a machine-checked accept gate: its declared output files must exist fresh inside the run dir plus a <node>.verdict file whose first line is pass|fail|reject. CLI: hotdog workflow validate|render <file>, run <file> [--id <run-id>] (foreground with live node lines, Ctrl-C cancels gracefully; one dir per run with an append-only run.jsonl state log; --id resumes a previous run, reusing only completed nodes that still verify on the filesystem, which reconcile reports on), plus list, status, reconcile, cancel. Manager tools workflow_validate / workflow_save / workflow_dispatch / workflow_status let an orchestrator profile design, persist, and dispatch graphs without polling -- completion posts to the delegating session on the task-completion path -- and /workflow / /followup <node> <message> list, inspect, cancel, and steer runs. Config workflows: path (default <configDir>/workflows), maxNodes (soft cap 8, hard ceiling 32), maxRuntimeMins (30).

  • Profile-bound model groups. A worker profile can bind fanout itself: add a group field (frontmatter or in-config) naming a modelGroups pool, and every delegate_task / workflow node routed to that profile places across the group without the manager explicitly naming a group.

  • Cross-provider placement fanout (modelGroups, provider noSpread). Spawn model resolution now produces an ordered placement plan instead of a single pick, and TaskManager admits it lane-aware: the first candidate whose provider lane has capacity wins, locked for the task's lifetime. Two delegate_task calls with no model given now run in parallel across the providers carrying the default model's copies (warm llama-swap nodes preferred among those with room; the /running peek re-ranks queued tasks at placement time) instead of serializing behind one lane. modelGroups declares interchangeable pools (mid-level: [qwen3.8-flash-next, llama-3.3-70b]), referenced as worker_model: "group:<name>" or the workflow node group key -- placement degrades to the next free member instead of queueing, while bare-name copies never degrade. Strict pins and provider-qualified explicit values never fan out; noSpread: true on a provider keeps it off implicit fanout (explicit members and pins still land there). A multi-candidate task can return while still queued unplaced (task_status shows the intent, provider null, until a lane is granted), and a first worker on a cold copy pays one model swap by design. spawnTask now refuses an id already held by a queued/running task instead of silently orphaning the live one.

  • Empty-response-after-tools nudge. When a completion arrives with no text and no tool calls while the context tail is a tool result (a common local-model stall that silently abandoned the task), the harness persists a system-notice asking the model to continue and advances the loop instead of stopping.

  • Loop detector (src/extensions/loop-detect/, on by default). Hashes each tool call as (name, canonicalized arguments) and watches the tail for two spin patterns: k consecutive identical calls (default 3) and strict A/B/A/B ping-pong (default 4). Escalation follows the streak: a system-notice nudge injected before the next request, a stronger nudge at 2x, then agent.cancel() with a user-visible message at 3x. Every escalation fires the loop:detected hook ({ agent, toolName, kind, streak, level }). Per-session state, dropped on context replacement (compaction/rewind//clear) and session end; tune via the loopDetect config block.

  • /undo, /rewind [N], /fork [N] [prompt] -- turn-level time travel. A turn is a user message plus everything after it until the next user message (tool chains never split). /undo drops the last turn; /rewind [N] drops N (default 1). Rewinds persist: the session-log extension checkpoints the log with a reset entry followed by the surviving messages (append-only, undone history kept above the marker for audit) -- the previously dead reset replay support in core finally has a writer, which also fixes resumed sessions replaying history that /clear removed. /fork [N] [prompt] branches a new session from N turns back (default 0 = full copy) and switches to it (interactive CLI re-attaches the channel; the source stays attached for /switch back); an optional prompt is enqueued by the UI only after its channel/socket is re-targeted onto the fork (interactive CLI and webui alike) -- core's forkSession never touches the prompt, so the fork's first output can never fire before anyone is listening. Session-mutating commands (clear/undo/rewind/fork) are rejected while a run is active -- replacing context mid-turn corrupted both histories. The webui handles /fork consistently with the interactive CLI: the ws layer registers the forked session's UI metadata, re-targets the requesting tab (sessionCreated + history replay), then enqueues the optional prompt there; forkSession itself also refuses to fork a running session (direct-call guard) and mirrors create()'s current-session ordering around SESSION_CREATE. New core pieces: trimTurns (src/core/context/rewind.ts), context:rewound hook, Agent.rewindContext(), SessionManager.forkSession() injected into agents as the forkSession seam (same pattern as enqueueCallback).

  • tool-call-repair - handle a second kind of problem, when the JSON can end up malformed - interrupting a hung stream could leave an assistant message with truncated tool-call arguments (invalid JSON) in history; a llama.cpp-style backend then throws error 500 every subsequent request ("Failed to parse tool call arguments as JSON ..."), bricking the session. Now removes broken tool calls and retries.

  • --json-schema structured output for one-shot mode. can be a file path or inline JSON object.

  • /profile command added (list + switch). New extension src/extensions/profile-switch/ adds: /profile lists profiles marking current; /profile <name> and /profile:<name> to switch.

  • rescue subcommand - config diagnostics that survive a broken config -- a syntax error in defaults.json (the most common breakage: JSONC comments, trailing commas) would throw inside the early so every subcommand died with a one-line parse error and there was no way in. New core subcommand hotdog rescue [fix]: prints the config-dir resolution chain, diagnoses defaults.json along the chain (explicit -f overrides), and for broken files shows the parse error with an engine-independent line/column caret locator, repairs BOM/line-and-block-comments/trailing-commas, and with fix rewrites the file keeping a .bak (never clobbers an existing backup); non-fixable breakage gets the marked position. When JSON parses it also warns on unknown top-level keys ("did you mean" via shared suggestCandidates), silently-won duplicates, and core+extension schema violations (validateConfig against raw normalized file + registry schemas), plus system_prompt.md/profiles-dir presence.

  • [BRK?] bash-tool brace expansion fix -- BashTool.execute was previously spawned with shell: true, which uses /bin/sh (dash on Debian-ish systems, where was run); dash has no brace expansion, so mkdir -p x/{mcp,openai,sse} created one literal {mcp,openai,sse} directory. Now uses 'bash' explicitly. Slight chance of breakage if you were using this in a slim container with sh but without bash.

  • hotdog info added an Extensions section: every extension discovered in the scan paths, shows (disabled) if enabled: false in config (incl. schema defaults like user-gate's off-by-default), (not loaded) if enabled but excluded (autoload off, list selection, or create() returning null -- e.g. subagents on non-manager profiles).

  • $VAR interpolation in config files. a string that is exactly $VAR or ${VAR} resolves from process.env. Whole-string only -- "http://$HOST" / "cost $5" untouched.

  • webui - tab completion in chat input -- Tab opens the popup, Tab/Shift+Tab/arrows cycle, Enter accepts (Escape first to submit raw), Esc/blur/edit dismiss, mousedown accepts without caret loss, invalidation token so a late reply can't reopen a dismissed menu.

  • tab completion for bare filepaths -- file-attachment completion matcher/handler now also trigger on bare path-looking words (./x, /x, anything containing /) in addition to @-prefixed attachments; values are returned without the @ in that case (prefix preserved, e.g. ./te -> ./test.txt). Plain prose like test.txt won't trigger (no /, no leading .); slash-containing prose like and/or does trip the matcher but the handler yields no matches and thus an empty list (readdir of the nonexistent and dir hits the catch-all), so it should be harmless in practice.

  • security

    • env scrubber - widen to redact *_PWD and add extra list in config
    • fetch-tool - widen blocklist for reserved addrs
    • session-log - validate session id before writing
    • marker-mangler - escape dashed-prefixed markers without terminator at end of string
  • fixes

    • /compact N - no longer leaves orphaned tool messages, which can cause errors on some backends
    • mcp-client - handle write/stdin errors without full hotdog crash
    • avoid orphaning tool messages when interrupted (ctrl+C, or stop button)
    • fix --no-log, was writing log files anyway due to a regression during JS-...
Read more

v0.10.0

Choose a tag to compare

@devoidfury devoidfury released this 19 Sep 14:55
  • [BRK] removed the profile-level role field. The only "role" concepts that remain are the message-format wire encoding (Message.role, role-mapping) and plain prose in user-supplied context files.

    • Profile files with role: frontmatter still parse -- the key is dropped and can never reach prompt assembly (covered by tests asserting a profile role can never leak into the system prompt).
    • Config keys role and taskDefaultRole and the --role flag are gone; the {{ role }} placeholder was removed from the default system prompt template. If you had a role line, fold it into the profile body.
  • [BRK] internals / hooks - pipeline handlers now patch the payload. Whatever a handler returns is written onto the payload, field by field, so every later handler and core read one thing: the payload. This replaces reading "the last handler's whole return value" at each call site, which silently dropped earlier patches — a provider:request handler returning { modelConfig } followed by one returning { messages } used to lose the model. Same rule now covers context ({ messages }), provider:request, provider:response, tool:result, tool:call, input, and command:dispatch. Non-object returns (a bare array, a string) are not patches and are ignored. runHookPipeline() no longer returns a lastResult field; results (every return, in order) and stopped remain. The now-dead type-guard helpers (isGateActionBlock/Modify/Continue/Handled, isInputTransform, isInputHandled) are removed: call sites directly read the payload's action.

  • extensions - new opt-in tier: @experimental in extension_paths loads src/experimental/ (the setting replaces the default, so use ["@extensions", "@experimental"] to keep the built-ins). First entry: file-watch, cooperative-editing awareness for a shared working tree — it tracks the files a session reads and writes, notices when the bytes on disk stop matching what the session believes, rides that onto each request as a harness system-notice, and blocks an overwrite onto a file that changed underneath the session.

  • fix - duplicate message delivery in the core agent loop: a UI host calling run() after SessionManager had already started the loop spawned a second consumer on the same queue, so one queued message could drive two agent.run() calls (the loser threw AlreadyRunning). run()/runUntilCancelled() are now idempotent -- a later call joins the active loop instead of racing it.

  • tools - a batch of core-tool and extension fixes found by auditing src/extensions/:

    • project_info: the non-git file listing was computed relative to the process CWD instead of the requested directory, so listing a directory from another launch dir produced absolute/../ paths.
    • web_search: the results count entry reported the number of output lines instead of the number of results (e.g. 3 results with descriptions read as 9).
    • find: max_results: 0 was accepted and silently returned "No files found" even when matches exist; it now falls back to the default like any other sub-minimum value. The file_type tool schema now documents the empty-file filter ("e") the code already supported.
    • read: files above 1MB are now read with a single streaming pass (the whole-file read is kept for small files), so paginating a multi-GB log no longer materializes the entire file in memory per call.
  • config - removed two dead config keys: coreTools.maxDiffSize and handoffTool.systemPrompt (declared in schema and docs, read by no code). Added the missing layers block to the loop config schema, which had left loop.* settings outside the config resolution pipeline.

  • question tool: the schema no longer marks key as required — it was always auto-generated from the prompt when omitted.

  • hooks - notifyHooks() is now awaitable: handlers start immediately in registration order (async ones run in parallel). Core call sites now await it where later code depends on the effect: the tool pipeline (TOOL_BEFORE_EXECUTE, AGENT_TOOL_CONTEXT, TOOL_AFTER_EXECUTE, TOOL_METRICS), turn boundaries (TURN_START/TURN_END), session create/swap, the bootstrap registration hooks, and SHUTDOWN_CLEANUP. Previously an async AGENT_TOOL_CONTEXT handler could complete its tool-context mount after the TOOL_CALL gate (e.g. the user-gate approval seam) had already run. Signature note: notifyHooks() returns Promise<void> (was void); unawaited call sites keep the old behavior.

Full Changelog: v0.9.2...v0.10.0

v0.9.2

Choose a tag to compare

@devoidfury devoidfury released this 17 Sep 03:47

Quick followup for the last release, since it's a big fix:

  • tool-call-repair - now handles both xml and chatml style tags, instead of just chatml style

Full Changelog: v0.9.1...v0.9.2

v0.9.1

Choose a tag to compare

@devoidfury devoidfury released this 17 Sep 02:47
  • added tool-call-repair extension: recovers malformed tool calls that often occur with local backend. When it detects a dropped tool call, the response is rewritten in place and the agent's normal tool-execution path runs the calls.

    • Config: toolCallRepair.enabled (default true), toolCallRepair.maxRepairsPerTurn (default 2, -1 = unlimited).
    • If you find any broken tool calls going forward, please make an issue on the tracker with the shape of the tool call, so that I can repro and fix it.
  • internals

    • PROVIDER_RESPONSE became a real pipeline: handlers may return { response } to replace the parsed response before the assistant message is built and tools execute.

Full Changelog: v0.9.0...v0.9.1

v0.9.0

Choose a tag to compare

@devoidfury devoidfury released this 15 Sep 10:13
  • [BRK] llm-client / config: the tool-result seam split into two independently-selected seams.

    • WireFormat owns the model-facing markup shape of harness wrappers (tool results, attached-file includes, system notices): modelToolFormat/--model-tool-format became modelWireFormat/--model-wire-format (default still "xml", but the shape -- and the file-include/system-notice marker protection -- now come from the autoloaded wire-format-xml extension).
    • RoleMapping owns where the internal harness role rides on the wire: the provider/model entry wireFormat: "system-first" | "developer" moved to roleMapping with the same two ids, registered by the new autoloaded role-mapping-default.
    • Tool results now travel as structured parts (tool/status/meta/error/hint/output) through context, session logs, hooks, and UI events instead of pre-rendered text. Rendering is up to each UI. The wire serializer is the only render point for the wire format. Fields are mangled before the format sees them.
    • Wire breaks for extension authors: the turn:end hook payload's result is content, same for the TOOL_RESULT output event; ToolResult.outputTag removed; EXTENSION_PROVIDES.TOOL_FORMATS replaced by WIRE_FORMATS + ROLE_MAPPINGS. A seam name configured but not registered by a handler throws Unknown wire format/role mapping "<name>"
    • Config Migration: a stale wireFormat: "developer" config fails on the first request with Unknown wire format "developer" -- rename the key to roleMapping/role-mapping.
  • added visual display protection from zero-width characters / bidi spoofing (which a malicious llm could use to hide a dangerous command in an innocent-looking ask prompt) by escaping these characters prior to rendering.

  • ui-interactive-cli: bracketed pastes whose content is a single line once surrounding newlines are trimmed no longer become a [Paste #N] marker -- the trimmed content is inserted inline (a pasted line with a trailing Enter behaves like typing it).

  • added experimental sysbox layer for bash-tool -- kernel enforced resource limits, syscall-level sandboxing (seccomp launcher C, Landlock fence, capability probes, cgroup DoS limits).

  • removed sysbox layer. Fun experiment but I was not confident enough shipping it. For real isolation run hotdog in a container or VM. More info at docs/postmortems/sysbox.md

  • approvals - user-gate, a tool-call approval layer, above the spawn boundary.

    • Opt-in (userGate.enabled defaults false -- with default: "ask" an on-by-default gate would prompt every user on every call).
    • Grammar is tool or tool.param=glob -- bash.cmd=git, edit.paths=src, fetch.url=https://docs.example.com/* -- and path values reuse Workspace rule matcher (same as workspace.deny), matched against the absolute AND the workspace-relative path.
    • Precedence deny > allow > default. deny never prompts and cannot be overridden mid-run. default can be set to ask (interactive), allow or deny -- allowlist-only, nothing prompts, unrecognized tools and unanalyzable bash deny too. default deny recommended in non-interactive mode, like on CI or with -p.
    • Bash commands are segmented on ;/&&/||/|/newline with cd retargeting later segments, and the analysis bails to an ask on anything that could hide an effect: $(...), backticks, ${}/$VAR, parens, brace expansion, unquoted globs, here-documents, xargs, eval, env, find -exec/-delete, and inline-code interpreters (sh -c, python -c, node -e, awk...).
    • Fails closed -- no seam, non-interactive UI, throwing prompt, aborted run or empty answer all block, with the reason; malformed rule is a startup error and keeps every call blocked until fixed.
    • What it does NOT stop: the command is not executed and the final operation isn't checked; nothing enforced at system level once the command is started.
    • Nothing is written to config from a prompt; a session allow is in-memory only, keyed on the tool plus every extracted value.
  • added /show-prompt command to see the active system prompt, as an interactive slash command and the show-prompt subcommand.

  • cli - token usage line reworked; now also shows the context window size.

  • workspace - now warns and continues when some configured roots are missing; errors only when all roots are missing. Also fix workspace handling when a root is /.

  • extensions - an invalid extension.json now throws at load instead of being silently skipped. Fix nested extension import.

  • sessions - resuming with an invalid session id now throws instead of silently starting a fresh session.

  • sessions - interrupted or missing tool results are healed on load, so a session killed mid tool-call no longer produces API errors on resume.

  • agent - fix per-model context-limit lookup; non-default models were falling back to the base limit.

  • compaction - an unrecognized model now resolves the default context limit instead of erroring and skipping compaction.

  • llm-client - connection errors now report the url being connected to.

  • question tool - more robust Ctrl-C handling; clearer error in one-shot mode when no user is connected to answer.

  • info - fix skills display.

  • internals

    • tool-executor - toolCtx is now built (and AGENT_TOOL_CONTEXT fires) BEFORE the TOOL_CALL gate pipeline, and the gate payload carries toolCtx
    • bash-tool - add truncated="true" result metadata info when output is truncated, same as find/grep/etc
    • hooks - unrecognized hook name now logs a warning instead of being silently ignored.

Full Changelog: v0.8.0...v0.9.0

v0.8.0

Choose a tag to compare

@devoidfury devoidfury released this 06 Sep 20:02

Added utils/jsx to hook into bun's jsx support as a react-like jsx view layer for the UI, and then refactored most of the webui to use that. There's also a bunch of little QoL things and bug fixes in this release.

  • llm-client - retries now honor a server Retry-After header (delta-seconds and HTTP-date forms, capped at 60s) instead of the fixed exponential backoff; absent or malformed headers fall back to the existing ladder

  • llm-client - error responses are now read up to the 200K cap but quoted in the LlmError message up to 2K (with a [truncated] marker), so a broken endpoint returning a full HTML page no longer dumps hundreds of KB into the sink, logs, and retry lines

  • agent - run() is now guarded against re-entrancy: a second overlapping run() throws AgentError.AlreadyRunning instead of silently interleaving loop state (iteration count, abort controller, stream replay buffers)

  • tool-executor - "Tool 'x' is not available for this agent" now suggests near-matching tools from the set the model was offered (case/separator differences first, then prefix/substring near-matches), so a misspelled or case-flipped tool name self-corrects in one retry

  • cli - unknown flags are now fatal (exit 1) with a "Did you mean" suggestion when a registered flag is a near-match, instead of a warning that let the run proceed with the flag silently dropped

  • session - a throwing channel event handler is now logged at debug level instead of being swallowed silently; other handlers still run

  • package - minimum bun bumped to 1.3.1 (the --only-failures flag used by the test scripts landed in 1.3.1)

  • internals

    • webui - rewrote in jsx
    • marker mangler - compiled escape/unescape regexes are now built once per name pair instead of rebuilt per protected prefix on every escape() call
    • cleanup session - remove unused SessionManager.deserialize and related unused serialize/deserialize hooks
    • task-manager - drop unsafe last-set bus fallback
    • task-manager - release the finished task's Agent reference from the registry when a task reaches a terminal state (completed/failed/cancelled); previously dead tasks pinned their full agent context for the manager's lifetime, an unbounded leak on long-lived hosts like the webui
    • tool-utils - remove legacy parseToolArgs (its invalid-JSON {input: ...} fallback was a footgun); subagents now use parseToolInput like every other tool
  • CLI colors now honor the NO_COLOR environment convention and TERM=dumb: color output is disabled regardless of config when either is present

Full Changelog: v0.7.3...v0.8.0

v0.7.3

Choose a tag to compare

@devoidfury devoidfury released this 01 Sep 19:30

This release includes some security fixes for grep/find, expands the default workspace path denylist to include cloud creds, and other improvements.

  • find/grep tool - fix argument injection in the fd path: a model-supplied pattern starting with - was parsed as an fd flag (e.g. --exec=rm made fd run rm on every file found, recursively). Same issue with grep/rg. Dash-leading patterns now route to the find fallback, whose -name/-path consume the pattern as a literal, and the positional slot is additionally guarded with a -- separator.
  • workspace denylist defaults expanded with cloud/credential directories: .aws, .azure, .docker, .gnupg, .kube (alongside .ssh, .config, .git). Projects that legitimately keep a directory of one of these names inside a workspace root can carve it out via workspace.deny (rules are last-match-wins, so !.aws after the positive rule disables it, or configure the exact rule set you want)
  • fetch tool SSRF blocklist now refuses Teredo (2001:0000::/32) alongside the existing 6to4/NAT64 refusal -- a Teredo packet carries an unchecked (XOR-obfuscated) IPv4 destination; adjacent 2001:: allocations such as provider space (2001:4860::/32) and documentation (2001:db8::/32) are unaffected
  • websocket/webui: renaming a session now sets a display title instead of changing the session's profile; the profile keeps driving behavior (role, tools, model) and is still switched via the profile switcher. Titles are in-memory only and are dropped when a session is rebuilt from a cold log or the server restarts
  • ui-session-review-cli - sessions delete subcommand now validates sessionId before confirmation gate
  • handoff-tool: listed handoff files that exist and are under the new handoffTool.autoIncludeFilesUnderBytes config (default 24KB) are now auto-inlined into the handoff message instead of just listed; oversized, binary, missing, and directory entries are listed with a note (size, binary, not found, directory, or path rejected)

Full Changelog: v0.7.2...v0.7.3