| Age | Commit message (Collapse) | Author |
|
Adds an agent-callable `key_usage` tool that reports current usage for
configured API keys so the agent can pick a key with headroom, warn before
hitting a rate limit, and diagnose exhausted-key failures.
Per key it reports: provider, active/exhausted status (with last error +
when it was exhausted), remaining rate-limit headroom and reset timestamp per
window (5-hour, weekly, and monthly where the provider exposes it), and
whether the figures are live or served from cache (with the cache's
last-fetched-from-source time). Supports anthropic and opencode-go keys
(live with cache fallback for anthropic; live scrape for opencode-go).
Optional `key_id` reports one key; omitted reports all.
Hard permission gate `perm_key_usage` (default off): when disabled the tool
is completely removed from the toolset/context. Registered in both the
parent permission-gated path and the child whitelist path, advertised in the
system prompt (TOOL_DESCRIPTIONS), grantable to subagents via the summon
enum, and exposed as a frontend tool-permission checkbox.
To report data freshness, claude.ts gains `getAccountUsageWithSource` +
`ClaudeUsageResult` (live vs cache + cachedAt from usage_cache.cached_at);
the existing `getAccountUsage` now delegates to it, preserving behavior.
Tests: core key-usage tool suite (windows, %-conversion, freshness, exhausted
status, unsupported/unavailable, filtering) + agent-manager perm-gate test.
|
|
# Conflicts:
# packages/api/src/agent-manager.ts
# packages/api/tests/agent-manager.test.ts
# packages/frontend/src/lib/components/ToolPermissions.svelte
# packages/frontend/src/lib/settings.svelte.ts
|
|
Address findings from a second independent (Gemini) review covering the tool
and the packaging:
- Robustness (was: crash): non-string params from a model hallucination (e.g.
include_ext: ["ts","go"]) threw 'x.trim is not a function' and killed the
tool call. Add an asString() coercion for all string params (query, path,
include_ext, exclude_pattern, only); non-strings now no-op or return the
graceful 'query is required' error.
- Output bound: cap each rendered snippet line at 500 chars (MAX_LINE_CHARS,
mirrors read-file.ts) so a matched minified/generated line can't bloat the
payload. (Total output is already bounded by the universal truncator.)
- packaging/PKGBUILD: make the cs clone rerun-safe (rm -rf before clone) so
makepkg -e / repeat runs don't abort on 'destination path already exists';
add conflicts=('cs') to the code-search package for a clean pacman error vs.
the unrelated AUR 'cs' that also owns /usr/bin/cs (no provides — different
program).
Not changed (verified): path containment, the -- flag-injection guard, and the
deterministic pinned Docker build were all confirmed solid by the review.
Tests: +2 (wrong-type params don't crash; long-line truncation). Full suite
605 pass, biome + tsc green.
|
|
Add Language Server Protocol integration modeled on opencode's, wired for
this codebase's plain-TypeScript tool/agent architecture.
Core (@dispatch/core):
- lsp/client.ts: LSP/JSON-RPC client over stdio (vscode-jsonrpc) with the
initialize handshake, didOpen/didChange sync, push + pull diagnostics
(textDocument/diagnostic, workspace/diagnostic), and a generic request()
passthrough for hover/definition/references/documentSymbol.
- lsp/server.ts: resolves dispatch.toml [lsp] entries into spawn specs.
Config-driven only — no builtin registry, no auto-download.
- lsp/manager.ts: process-wide LspManager owning client lifecycles, keyed
by root+serverID, lazy spawn + reuse + graceful shutdown.
- lsp/language.ts: extension->languageId map incl. .luau -> "luau".
- lsp/diagnostic.ts: error-only <diagnostics> block formatting (1-based).
- tools/lsp.ts: on-demand 'lsp' tool (1-based coords -> 0-based wire).
- write-file.ts: optional onAfterWrite hook for diagnostics-on-write.
- config schema: validate [lsp] block; DispatchConfig.lsp + LspServerConfig.
API (@dispatch/api):
- AgentManager owns one LspManager; per-working-directory server cache
cleared on config reload; diagnostics appended to write_file results;
'lsp' tool gated by new perm_lsp setting; shutdownAll on destroy().
Config:
- dispatch.toml: documented, commented [lsp.luau-lsp] Roblox example.
Tests: fake-lsp-server fixture + client/manager/server/diagnostic/schema/
tool/write-hook suites, plus an opt-in real-binary luau-lsp smoke test
(auto-skipped when luau-lsp is absent). 652 pass; biome + 3 typechecks green.
|
|
Address bugs found by an end-to-end test of the tool:
- HIGH: prose/text files (.md/.html/etc.) came back as bare headers with no
snippet. cs's default 'auto' snippet mode emits a single 'content' string
(no 'lines[]') for prose, which the renderer skipped. Force
--snippet-mode=lines by default so every file type returns a lines[] window
that renders. Also add a defensive 'content'-shape fallback in formatResults
(+ widen the CsResult type) so a content result is never shown blank.
- HIGH: the 'context' parameter was a no-op — cs ignores -C except in grep
snippet mode. When context is supplied, switch to --snippet-mode=grep so -C
actually widens the per-match window (verified 2 -> 26 lines); default
(no context) keeps the richer lines window for code.
- LOW: a 'path' pointing at a file (not a dir) silently returned 'No matches
found' (cs --dir <file> => null). Now stat the path and return an
explanatory error (file vs nonexistent), pointing at read_file for a file.
- MEDIUM/doc: clarify snippet_length (prose-mostly) and context descriptions.
Tests: +5 (prose rendering live + stubbed content-shape; context widening;
path-is-file; path-nonexistent). Full suite 603 pass, biome + tsc green.
Note: the EACCES spill failure seen in testing is pre-existing platform
infra (truncate.ts SPILL_ROOT, shared by all tools), not part of this tool.
|
|
Address findings from an independent code review of the search_code tool:
- Critical: cs failures (non-zero exit, or SIGTERM from the spawn timeout)
were swallowed and reported to the model as 'No matches found', discarding
stderr. Now capture exit code + signal from 'close' and return a real
Error: (timeout message for SIGTERM, exit-code + stderr otherwise). cs
exits 0 on a genuine no-match, so that path still reports correctly.
- High: a query beginning with '-' (e.g. '-foo') was parsed by cs as a
(usually invalid) flag. Insert a '--' separator before the query so it is
always treated as the positional search term.
- Low: relative-path display fallback now matches the workdir only at a path
boundary, so a sibling dir sharing the prefix (e.g. /app vs /app-secrets)
isn't rendered as a '../app-secrets/...' path.
Adds tests for the non-zero-exit (stderr surfaced, not 'No matches') and
dash-leading-query cases. All tests (598), biome, and tsc pass.
|
|
Add a dedicated, permission-gated search_code tool that wraps boyter/cs
(code spelunker) — a fast, relevance-ranked, structure-aware code search
engine — giving agents a better default than grep/find for exploratory
'where is X / how does Y work' searches (ranked results, snippets, ~5x
smaller payloads).
- packages/core/src/tools/search-code.ts: createSearchCodeTool factory;
-f json invocation, workdir path containment, graceful missing-binary
handling (DISPATCH_CS_BIN override), readable per-file formatted output.
- Wire-up: export from core; register in agent-manager (both child-whitelist
and parent perm paths) behind new perm_search_code; add to summon catalog
+ tools enum; frontend ToolPermissions + settings.
- Docker: build a patched, statically-linked cs (pinned v3.1.0 commit) in a
golang builder stage and bundle at /usr/local/bin/cs.
- docker/cs/luau-declarations.patch: additive Luau declaration table so
--only-declarations / definition ranking works for Roblox .luau files
(upstream has Lua but not Luau). Applied during the Docker build.
- Tests: new search-code.test.ts (stubbed JSON formatting + live-cs
integration, skipped when cs absent); agent-manager/routes mocks +
perm-gating assertions; loader pass-through.
All tests (596), biome, and tsc (core/api/frontend) pass. cs-builder Docker
stage verified to build and produce a working patched binary.
|
|
# Conflicts:
# packages/api/tests/agent-manager.test.ts
|
|
'arrives on its own')
Matches actual behavior: a peer's reply wakes this tab with a new message in a
later turn. Updated the send_to_tab description (both canReadTab branches), the
delivery-result text (both branches), and the system-prompt one-liner; updated
the test assertion accordingly.
|
|
Granting only the user-agent (top-level) permission without the
subagent-summon permission left the agent unable to summon user agents:
the whole summon tool was gated behind perm_summon, so perm_user_agent
alone produced no summon tool.
Register summon when EITHER perm_summon OR perm_user_agent is granted.
createSummonTool now takes an independent subagentEnabled flag (mirrors
perm_summon) alongside userAgentEnabled (mirrors perm_user_agent):
- subagent-only -> ordinary subagents, no top_level
- user-agent-only -> spawns ONLY top-level user agents (top_level
forced, background/top_level params dropped, user-agent catalog only)
- both -> unchanged full behavior
retrieve stays bundled with perm_summon (user agents are fire-and-forget).
Adds core summon tests (user-agent-only mode + legacy-default regression)
and an agent-manager summon/user_agent permission-split suite.
|
|
The send_to_tab guidance previously told the agent it could call read_tab to
check for a reply, but the tab-messaging permissions are split — a tab can
hold send_to_tab WITHOUT read_tab (the exact case in testing). Advertising a
tool the agent wasn't granted is wrong.
Thread a canReadTab flag from AgentManager.buildTabCommToolEntries into
createSendToTabTool (true iff this tab is also granted read_tab). The tool
description and the delivery-result text now only reference read_tab when
canReadTab is true; otherwise they say a reply arrives on its own and to end
the turn. Drop the read_tab phrasing from the static TOOL_DESCRIPTIONS
one-liner (can't be conditional per-tab there).
Also uppercase ONLY in the recipient reply-contract footer for emphasis.
Tests: cover both canReadTab branches for description + result text; assert
ONLY is uppercased.
|
|
replies
Two behavioral problems observed once the tools were usable:
1. The SENDER busy-waited for a reply (ran 'sleep 20' / polled) instead of
ending its turn. Tool description, the delivery result text, and the
system-prompt one-liner now say plainly: do not sleep/poll/run commands
to wait; a reply arrives on its own in a later turn (or via read_tab in a
future turn); keep working if there's other work, else end your turn.
2. The RECIPIENT replied to its OWN user in plain text instead of routing the
answer back through send_to_tab. The provenance wrapper now states the
message is from another AGENT (not your user), and that to reply you must
use send_to_tab addressed to the sender's handle — and only if asked, since
it may just be context. A plain text answer reaches only your own user.
Tests updated for the new wording.
|
|
Replace the imperative id-based CRUD todo tool (add/update/list/get/remove)
with opencode's declarative whole-list design: a single `todos` param that
replaces the entire list each call. No model-visible ids, no delta reasoning,
no "task not found" spirals.
- core: TaskItem { id, content, status }; statuses pending|in_progress|
completed|cancelled. TaskList.setTasks/getTasks/onChange. New rich
TODO_DESCRIPTION adapted from opencode's todowrite.txt.
- api: TASK_MANAGEMENT_GUIDANCE system-prompt section (from anthropic.txt);
updated TOOL_DESCRIPTIONS.todo. Reload fix: TabStatusSnapshot now carries
per-tab tasks so getAllStatuses rehydrates the panel on reconnect.
- frontend: mirror types; hydrate tasks from snapshot in both restore paths;
upgrade sidebar Tasks panel to render content + all four statuses + progress.
- tests: new core task-list.test.ts (15); updated api TaskList mocks +
getAllStatuses task-snapshot coverage.
bun run check clean; 569 tests pass; all packages typecheck.
|
|
Add send_to_tab / read_tab tools so an agent can message or read another
tab by a git-style short handle (shortest unique prefix of the tab UUID,
min 4 chars), shown in the tab bar.
- core/db/tabs: resolveTabPrefix + shortestUniquePrefix (open tabs only,
LIKE-sanitized prefix matching)
- new tools read-tab.ts / send-to-tab.ts (+ tests) decoupled from the DB
TabRow via a minimal ResolvedTabRef projection
- agent-manager: unified deliverMessage routing (busy -> queue, idle ->
new turn) shared by POST /chat and send_to_tab; agent->agent auto-wake
budget (MAX_AGENT_AUTO_WAKES) to bound ping-pong loops
- summon/loader: send_to_tab + read_tab as grantable tools
- frontend: shortHandleFor + handle badge in TabBar; perm toggles
- notes: tab-comm / user-agents / todo-redesign plans
- chore: biome format fixes (debug-logger, summon.test)
Refs notes/plan-tab-comm.md
|
|
- agent parameter is now required on summon tool
- new top_level param spawns independent fire-and-forget user agent tabs
- gated by perm_user_agent permission (UI checkbox added)
- agent definition type validation (subagent vs user-agent slug mismatch)
- context-aware error messages when agent slug not found
- read_file_slice added to summon tool's allowed tools enum
- updated and expanded summon tests
|
|
Move all loose root-level .md files (plans, reports, gemini reviews, incident
notes) into a single notes/ directory, and update the doc-reference breadcrumbs in
code comments/test labels to the notes/ path.
Add notes/queue-interrupt-reconcile-edge-cases.md: documents why the
queue/interrupt/turn-sealed reconcile path keeps surfacing edge cases (a catalog of
the four review-pass bugs, the no-loss/no-duplicate invariants, the recommended
membership-based reconcile refactor, and interleaving-test guidance).
|
|
preserve slider selection on agent config refresh
|
|
AgentBuilder default, SubAgent mode display
- Filter summon tool catalog to is_subagent-flagged agents only
- Return fresh subagent list in error when slug not found
- Add subagent hint to system prompt when summon tool available
- Default is_subagent checkbox to true in AgentBuilder
- Fix tab-created event to include agentSlug and agentModels
- Add SubAgent read-only mode to ModelSelector with model slider
|
|
feat(summon): agent definition support; docs: cc/ research findings
- registry.ts: add normalizeForAnthropic() to strip , additionalProperties, default, nullable from zodToJsonSchema output so Anthropic doesn't silently reject tool definitions
- agent.ts: add toolChoice=auto for Claude OAuth to prevent Opus thinking forever without calling tools
- summon.ts: add agentSlug parameter, build agents catalog in description, add toAvailableAgents helper
- agent-manager.ts: wire agent definition loading into spawnChildAgent, agent model fallback
- loader.ts: export loadAgent, expandAgentToolNames, getAgentDirPaths; add getAgentDirPaths for permission gate
- agent.ts: auto-allow read-only tools in agent definition directories
- packaging/PKGBUILD: exclude ARM64 prebuilds from x86_64 package
- cc/: research findings on Claude Opus tool calling issues
- tests: loader tests, summon tool tests
|
|
reasoning round-trip + max-thinking budget audit
Migrates the LLM stack from [email protected] + @ai-sdk/[email protected] +
@ai-sdk/[email protected] to [email protected] + @ai-sdk/[email protected]
+ @ai-sdk/[email protected]. Full design in plan-v6-upgrade.md;
two rounds of Gemini code review captured in report.md.
Motivation: the recurring 'reasoning-signature without reasoning' error
on Claude Opus 4.7 was a v4 SDK artefact — @ai-sdk/[email protected] emitted
Anthropic signature_delta as a separate stream chunk that orphaned when
the model produced a signed-but-empty thinking block, and our chunk
store had no signature field so the round-trip back to Anthropic was
rejected on the next turn. In v6, signatures arrive inside
providerMetadata on the reasoning-end event, and the orphan-signature
class of bug is gone at the SDK level.
Core changes:
• ThinkingChunk gains optional metadata?: Record<string, unknown>
(the v6 providerMetadata blob). A non-undefined metadata 'seals'
the chunk: subsequent reasoning-delta opens a new chunk rather
than extending the sealed one.
• AgentEvent gains { type: 'reasoning-end'; metadata? } (replaces
the v4 reasoning-signature variant).
• toModelMessages (replaces toCoreMessages):
- returns ModelMessage[] (was CoreMessage[])
- thinking → { type: 'reasoning', text, providerOptions: metadata }
- tool-batch entries → { type: 'tool-call', input } (was 'args')
- tool results → { output: { type: 'text', value } } ToolResultOutput
• Claude OAuth uses createAnthropic({ authToken }) natively — no more
custom-fetch x-api-key → Bearer swap.
• rewriteBodyForOpus47 deleted — Opus 4.7 adaptive thinking is native
via providerOptions.anthropic.thinking = { type: 'adaptive' }.
• V1 middleware → V3 (specificationVersion: 'v3').
• v4-era normalizeMessages openai-compatible middleware deleted; the
v6 openai-compatible provider extracts reasoning_content natively
from { type: 'reasoning' } content parts.
• applyAnthropicStructuralNormalisations (mirrors opencode
provider/transform.ts:53-148): drops empty text/reasoning parts,
scrubs non-[a-zA-Z0-9_-] toolCallIds, splits [tool-call, non-tool]
assistant turns (Anthropic rejects tool_use followed by text).
• applyOpenAICompatibleReasoningNormalisation (mirrors opencode
transform.ts:217-249): lifts reasoning text into
providerOptions.openaiCompatible.reasoning_content (always, even
empty). Solves DeepSeek 'The reasoning_content in the thinking
mode must be passed back' — the v6 SDK skips emitting
reasoning_content when text is empty (dist/index.mjs:245), but
DeepSeek requires the field present once thinking was used.
• Tools: tool({ inputSchema: jsonSchema(zodToJsonSchema(...)) })
(was parameters: ZodSchema). AI SDK tools have no execute
callback — the agent runs tools manually for permission prompts
and shell-output streaming. New dep: zod-to-json-schema@^3.25.2.
• fullStream event loop rewritten for v6 event shape: text-delta
(text not textDelta), reasoning-start/delta/end, tool-input-*,
tool-call (input not args), tool-result, tool-error (new), abort
(new), start-step/finish-step, finish.
Max-thinking audit (matches opencode transform.ts:642-671 budgets):
• Claude enabled-thinking max budget 16000 → 31999 (Anthropic ceiling)
• Claude enabled-thinking high budget 10000 → 16000
• maxOutputTokens 'budget + 8000' → fixed 32000 (matches opencode's
OUTPUT_TOKEN_MAX; model self-allocates thinking vs response within)
• Opus 4.7 adaptive thinking gains display: 'summarized' and sibling
effort field (without these, thinking content is hidden by Anthropic
and the model barely thinks).
Frontend mirrors:
• types.ts — ThinkingChunk.metadata?, AgentEvent reasoning-end
• tabs.svelte.ts — routes reasoning-end through applyChunkEvent
• ChatMessage.svelte — hides empty thinking chunks; hides the entire
assistant bubble when no chunk has renderable content
Gemini-review-driven fixes:
• tool-error and abort stream events now surface as error chunks
(were silently ignored)
• toolCallId scrubbing pass (opencode transform.ts:96-122 parity)
• Empty-reasoning-cull explicit test coverage for both Anthropic
structural normalisation and DeepSeek path
Test counts (223 tests across 3 packages, all green):
• tests/chunks/append.test.ts: 44 (was 38) — reasoning-end sealing,
orphan walk-back, multi-block interleaving
• tests/agent/agent.test.ts: 24 (was 5) — exhaustive v6 event
mappings, structural normalisations, signature/reasoning_content
round-trip, tool-error/abort branches, DeepSeek scenario, empty
reasoning edge case
• tests/llm/provider.test.ts: 9 (was 22) — dropped 13 obsolete v4
middleware tests; new minimal tests confirm no middleware wrapping
on default openai-compat path and that createAnthropic gets
authToken vs apiKey correctly for OAuth vs api-key flows
• tests/tools/registry.test.ts: 10 (was 4) — v6 tool() contract
(inputSchema, no execute, JSON Schema for nested zod)
• packages/api/tests/agent-manager.test.ts: 12 (was 7) — mock Agent
emits v6 reasoning events; reasoning-end broadcast + ordering
• packages/frontend/tests/chat-store.test.ts: 35 (was 32) —
reasoning-end flow through Svelte $state store
typecheck clean (tsc --noEmit on core + api, svelte-check on frontend),
biome clean across 124 files.
|
|
|
|
batching, error/system chunks
|
|
symlink-safe path resolution
|
|
copy truncation
- Agent rate-limit fallback now iterates through agent's configured models[] in strict order
- Frontend sends agentModels with each /chat request; backend uses buildFallbackSequence()
- Emits notice event on fallback so chat shows which key failed and what's being tried next
- Child agents inherit parent's agentModels for fallback
- Added statusCode propagation from AI SDK errors for programmatic 429 detection
- Copy button truncates all tool results at 300 chars (was 200 for 4 specific tools)
- run_shell, summon, youtube_transcribe: background mode support
- summon: blocking mode by default with getResult callback
|
|
- Add is_subagent checkbox to agent editor; subagents are hidden from Chat Settings
- Add is_subagent field to AgentDefinition type, TOML serialization, and API route
- Filter subagents from ModelSelector agent list
- Fix all biome lint/format errors across codebase (useLiteralKeys, noNonNullAssertion, noExplicitAny, formatting, import sorting)
- Fix svelte-check errors (type narrowing in SkillsBrowser, ToolPermissions, SidebarPanel)
- Fix a11y warnings in App.svelte (label-control associations)
- Fix test mocks missing BackgroundShellStore, BackgroundTranscriptStore, createWebSearchTool, createYoutubeTranscribeTool
- Update stale 409 test to match current message-queuing behavior
- Exclude packaging/ and release/ dirs from biome to avoid linting stale build artifacts
|
|
retrieve
- youtube_transcribe now polls until transcript is ready (waits estimated_seconds - 2s, min 2s)
- Times out after 10 minutes of polling
- When user interrupts, polling continues in background with youtube_transcribe_<uuid> job ID
- BackgroundTranscriptStore holds polling jobs, retrieve tool resolves them
- ToolCallDisplay shows 'interrupted' badge (blue) when result contains [USER INTERRUPT]
- Applies to all interruptible tools: run_shell, youtube_transcribe, retrieve
|
|
fixes
- Add web_search tool (Firecrawl POST to /v1/search with query, limit, lang, country, scrapeOptions)
- Add youtube_transcribe tool (GET to transcriber service, handles completed/queued/failed statuses)
- Both tools registered for parent agents (always) and child agents (permission-gated)
- Added to summon enum, TOOL_DESCRIPTIONS, and core exports
- Shell interrupt: run_shell now races against user queue interrupt
- When interrupted, command continues in background with run_shell_<uuid> job ID
- BackgroundShellStore holds running processes, auto-cleans 10min after completion
- retrieve tool extended to handle both agent IDs and shell job IDs
- Tool error detection: results starting with 'Error:' now marked isError in UI
- Fix TS error: cast unavailMatch[1] regex capture group to string
- Docker: network_mode host for Tailscale/LAN access to external services
- Bun.serve idleTimeout set to 60s (was default 10s)
- KeyUsage: clearer message when OpenCode usage data unavailable
- Firecrawl: only send scrapeOptions when scrape=true (avoid 400 on instances without scrape support)
|
|
- Add message queue allowing users to send messages while agent is running
- Queue messages are injected into tool results as [USER INTERRUPT]
- Retrieve tool interrupted via Promise.race when user message arrives
- Queued messages show with 'queued' badge and cancel button
- Consumed messages repositioned and chat splits at interrupt point
- New assistant message block created after interrupt for clean flow
- Add POST /chat/cancel endpoint for cancelling queued messages
- Fix CORS to allow any origin (Tailscale/LAN access)
- Fix crypto.randomUUID fallback for non-secure contexts (HTTP)
- Fix frontend API URL derivation from page hostname
- Auto-create DB tab if missing on processMessage (foreign key fix)
- Add error logging to processMessage catch block
- Fix working directory input sync on agent switch
- Fix agent mode button to re-apply agent settings
|
|
handling
- Agent Builder: full CRUD with card grid, drag-and-drop model reorder, edit/delete
- Auto-save on edit with 600ms debounce, AbortController for concurrency, fieldset disabled until name entered
- Agent definitions stored as TOML with cwd field, loaded from global/project dirs
- Working directory: per-tab CWD override in Chat Settings, agent default CWD, auto-create on first message
- CWD validation: check-dir endpoint with ~ expansion, real-time validity indicator
- Subagent CWD validated against parent's effective CWD using path.relative
- Unavailable tool calls: caught gracefully, shown as tool call with error badge, model retries
- UI: tab bar border radius, sidebar border removed, chat input ghost style, scroll-to-bottom rectangle
- Skills dir collapse uses CSS rotation, Model Choice renamed to Chat Settings, System Prompt view removed
- Reusable SkillsBrowser/ToolPermissions with external mode for Agent Builder
- ModelSelector: Agent/Manual toggle, agent list, Agent Settings link
- Page router, skills recursive scanning, bin/up gopass removed, docker volume mounts
|
|
double-execution bug fix
- Add summon/retrieve tools for spawning child agents in new tabs
- summon: non-blocking, returns agent_id immediately
- retrieve: blocking, waits for child to finish, returns result
- Child tools are intersected with parent permissions (no privilege escalation)
- Working directory validated to stay within workspace
- Abort controller stops orphaned agents on tab close
- Rename task_list tool to todo with comprehensive usage guidance in system prompt
- Rename PermissionLog.svelte to ToolPermissions.svelte
- Add 'Summon agents' toggle to tool permissions UI
- Redesign TaskListPanel with DaisyUI checkboxes (indeterminate for in-progress)
- Remove 'blocked' status from task system
- Add tab-created WebSocket event for child agent tab visibility
- Add HMR cleanup for WebSocket connections (close stale connections on hot reload)
- Fix ensureAssistantMessage to not throw on closed tabs
- Fix double tool execution: remove execute from AI SDK tool() in registry.ts
(agent.ts already executes tools manually via executeToolWithStreaming)
- Fix all pre-existing test failures (missing mocks, stale API signatures)
- Add debug info to copy button (tab ID, injected skills, all tab IDs)
- Add tab ID and tools to conversation copy output
|
|
- Config system: TOML-based dispatch.toml with hot-reload via chokidar
- Model/key resolution: tag-based model selection, key fallback chains
- Skills system: directory loader with TOML frontmatter, agent mappings
- Task list tool: add/update/list/get operations with WebSocket events
- API routes: GET /config, /skills, /skills/:name, /models, /models/resolve
- Frontend: sidebar with model status, task list, config viewer, skills browser, permission log
- Sliding sidebar animation using CSS transitions (not Svelte transitions)
|
|
Permission engine:
- Rule-based engine: wildcard matching, last-match-wins, reject cascade
- PermissionService with pending/approved state, PermissionChecker interface
- dispatch.yaml config loader with per-permission pattern rules
Shell tool:
- run_shell tool with child_process spawn, timeout, streaming output
- Tree-sitter static analysis (web-tree-sitter + tree-sitter-bash WASM)
- BashArity command normalization for 'always allow' patterns
- FILE_COMMANDS set: rm, cp, mv, mkdir, ls, find, grep, cat, etc.
Agent loop refactored:
- Removed maxSteps, manual step loop with tool execution
- Permission checks on shell commands (external_directory only)
- Permission checks on file tools outside workspace boundary
- Symlink bypass fix (realpathSync), .. false positive fix
- Shell output streaming via Promise.race + setImmediate polling
API layer:
- PermissionManager wraps PermissionService, broadcasts via WebSocket
- WebSocket handles permission-reply messages from frontend
- Config loaded from dispatch.yaml, converted to ruleset
Frontend:
- Permission prompt modal (native dialog, focus trap, ARIA)
- Always-allow confirmation flow with pattern preview
- Shell output display (live streaming + final parsed result)
- Permission log panel (fixed bottom-right overlay)
- Exit code badge (green 0, red non-zero)
134 tests, typecheck clean on all 3 packages
|
|
- Bun monorepo with @dispatch/core, @dispatch/api, @dispatch/frontend
- Agent runtime with Vercel AI SDK, streaming via WebSocket
- Tools: read_file, write_file, list_files (scoped to working directory)
- Hono API server with POST /chat, GET /status, GET /health, WS /ws
- Svelte 5 + DaisyUI frontend with chat UI, theme switcher, copy button
- OpenCode Go (Zen) as LLM provider, deepseek-v4-flash-free model
- Docker setup (dev + prod) with bin/ scripts and gopass secrets
- Biome v2 linting/formatting, Vitest tests (44 passing)
- Debug info attached to error messages for diagnostics
|