| Age | Commit message (Collapse) | Author |
|
|
|
- Add watchDirConfig() for per-directory config watching
- Register watchers for subdirectories with their own dispatch.toml
- Fix permission ordering: move "*" wildcard to front so findLast
reaches specific rules first (was silently breaking all specific
bash permission rules)
- Add comprehensive tests for watcher functionality
- Update mocks in test files
|
|
Load an optional global config at ~/.config/dispatch/dispatch.toml
(override via DISPATCH_GLOBAL_CONFIG) and deep-merge it underneath every
project/working-directory dispatch.toml, so machine-wide settings — most
notably globally available LSP servers — work in any repo without per-repo
config. Local always wins on conflicts.
- loader: add getGlobalConfigPath(), loadGlobalConfig(), mergeConfigs();
loadConfig(dir) now loads+merges global. [lsp] and [[keys]] merge by id;
[permissions] merge per-group with global patterns emitted first so local
rules win at evaluation time (findLast). A malformed global config is
downgraded to empty rather than breaking every repo.
- watcher: watch BOTH global and local dispatch.toml so hot-reload re-merges
on either change (dedupes when paths coincide).
- export new loader fns from config/index and core index.
- types/agent-manager: doc updates reflecting merged LSP resolution.
- dispatch.toml: document global-default merge behavior; activate biome and
typescript-language-server LSP entries.
- tests: merge precedence, lsp/keys merge-by-id, permissions merge,
filesystem integration, malformed-global resilience; isolate global path
in existing loader tests.
|
|
# Conflicts:
# packages/frontend/src/lib/components/ChatInput.svelte
|
|
Summarize a conversation's older "head" into a structured anchored Markdown
summary while preserving the most recent turns verbatim, shrinking context size
while keeping the information needed to continue coherently. Triggered by a
"Compact conversation" button in Chat Settings (not an agent tool).
Approach informed by OpenCode's session/compaction.ts:
- Ported SUMMARY_TEMPLATE (Goal / Constraints / Progress / Key Decisions /
Next Steps / Critical Context / Relevant Files) and the anchored-summary
buildPrompt (re-summarizes a prior summary when present).
- Ported the TOOL_OUTPUT_MAX_CHARS (2000) cap on tool results in the summary
request.
- Simplified tail selection to a fixed recent-turn count (DEFAULT_TAIL_TURNS=2)
instead of OpenCode's token-budget splitTurn.
core:
- New src/compaction/ module (pure, DB-free): template, prompt builder,
head/tail selection, transcript renderer with tool-output capping, prior
summary extraction. Generic over ChatMessage so callers keep turnId/seq.
- db/chunks.ts: rekeyChunks(from,to) relocates a tab's full history to a
backup tab (reversible — nothing is deleted).
- AgentEvent: compaction-started / -complete / -error variants.
api:
- AgentManager.compactTab(tempTabId, sourceTabId): side-effect-free
resolveConnection() for the compactor model (configured compaction_model_*,
else the source tab's own key+model), one-shot tool-less summary generation
via a transient Agent, then relocate full history to a fresh backup tab and
re-seed the canonical source id with [summary turn + preserved tail]. Source
tab is locked (messages queue) during the run; queue drains afterward.
- Routes: POST /tabs/:id/compact, GET/PUT /tabs/settings/compaction-model.
frontend:
- "Compact conversation" button in ModelSelector (Chat Settings), between
Working Directory and the agent toggle; idle-gated.
- Compaction-model key+model selector in Settings, beside the title model.
- Transient placeholder tab shows a large, non-faded "Please wait, compacting
conversation…" screen; closing it cancels. Source input locked while running.
- Handle compaction-* events: reload compacted source, insert backup tab,
refocus source, discard placeholder.
tests: core compaction unit tests, rekeyChunks DB test, AgentManager.compactTab
orchestration tests, and compaction route tests. All green (713 tests), biome
clean, all typechecks pass, frontend builds.
|
|
|
|
Add multimodal image/PDF input to the chat box via clipboard paste, gated by a
graceful per-model capability check.
UX: a pasted image/PDF inserts an inline token (【image:…】 / 【pdf:…】) into the
draft, so attachments have ORDER relative to typed text and can be referenced
positionally. The token is the only handle — deleting it (atomic Backspace/
Delete, or selection overlap) detaches the file; an input-reconciliation safety
net detaches any attachment whose token is no longer intact. No preview strip.
Capability check: resolveModelCapabilities reads models.dev modalities.input
(new GET /models/capabilities, mirrors /context-limit). The input blocks Send
(no tokens spent) only on a definitive 'no'; unknown capability (catalog offline
/ unmapped provider) stays permissive. Attachments require a fresh turn — Send is
blocked while generating and /chat rejects content mid-turn (409).
Attachments are EPHEMERAL: forwarded to the model for the turn via ordered AI SDK
ImagePart/FilePart content, but never persisted (history keeps the text with
[image]/[pdf] markers). Text-only turns serialize byte-identically to before.
Limits (Anthropic-aligned, enforced at paste + re-validated server-side):
PNG/JPEG/WebP/GIF/PDF; image ≤5MB, PDF ≤32MB, ≤20 attachments, ≤32MB total.
core: UserContentPart types, models/attachments validator, capability resolver,
agent.run+toModelMessages thread ordered content. api: /chat content validation +
passthrough. frontend: attachment-tokens helper, ChatInput paste/token/gating,
per-tab staged attachments, App.svelte capability fetch. +44 tests.
|
|
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
|
|
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.
|
|
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.
|
|
|
|
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.
|
|
- TabBar: HTML5 drag-and-drop to reorder user tabs (subagent tabs untouched);
double-click a tab title to rename (Enter/blur confirm, Escape cancel).
- Store: add reorderTabs/renameTab/setDraft; per-tab in-memory `draft` and
`manualTitle` fields. Manual rename suppresses first-message auto-title.
- ChatInput: bind to the active tab's draft so switching tabs saves/restores
unsent text instead of clobbering it.
- Backend: updateTabPositions() + PATCH /tabs/reorder persist tab order to the
existing `position` column; tabs without a stored position fall to the end
then get explicit positions on first reorder.
- Tests: store reorder/rename/auto-title-guard/draft coverage; core
updateTabPositions coverage (FakeDatabase extended with transaction support).
|
|
|
|
Add a 'Context Window' sidebar view showing the live context occupancy
(latest request's input+output) against the model's maximum context
window, resolved dynamically from the models.dev catalog.
- core: models.dev catalog module (resolveContextLimit) with disk cache,
TTL, stale-fallback + offline penalty memo; null for unknown models.
- api: GET /models/context-limit?provider=&modelId=.
- frontend: ContextWindowPanel + computeContextUsage helper; App resolves
+ caches the active model's max (anthropic/opencode-anthropic only);
percent shown to 2 decimals; degrades to bare token count when max
unknown.
- tests: core catalog (13), api route (3), frontend helper (6).
|
|
Persist usage as invisible type:"usage" chunk rows (side channel):
- core: add "usage" ChunkType + UsageData; exclude usage rows from
getChunksForTab/getTotalChunkCount; add getUsageStatsForTab aggregate
(exported from barrel); defensive skip in groupRowsToMessages.
- api: agent-manager accumulates per-attempt usageRows and flushes them in
the same atomic appendChunks call as the turn's content (discarded on a
superseded fallback attempt). GET /tabs enriches rows with usageStats.
- frontend: hydrateFromBackend seeds cacheStats from usageStats (reload only;
no re-seed on statuses reconnect, so no double-count with live events).
Tests: core DB-backed usage persistence/aggregate; api usage-row-per-event +
fallback discard; routes GET /tabs usageStats; frontend hydrate seed +
no-double-count + live-accumulation-after-seed. 495 -> 509 passing.
|
|
Adds a transport-agnostic NotificationDispatcher and a fire-and-forget
ntfy.sh transport (no SDK; just fetch). Configuration is persisted as a
single global JSON blob under the 'ntfy_config' settings key.
Event taxonomy (per-event toggles):
- turn-completed — assistant turn finished cleanly
- turn-error — final turn error (after all fallbacks)
- permission-required — new permission prompt was created
- agent-spawned — top-level user-agent tab spawned via 'summon'
Design:
- Single internal notify(event) interface so a future transport (email,
webhook) plugs in without changing call sites.
- attachToAgentManager + attachToPermissionManager subscribe to the
existing event streams via narrow listener interfaces (no @dispatch/api
dependency back into core).
- 5s in-memory dedupe window on dedupeKey suppresses permission re-emits.
- 10s per-request abort timeout so a hung ntfy server can't pin a worker.
- All sends are fire-and-forget: void Promise.resolve(...).catch(warn).
Tests (39 new):
- ntfy transport: URL/headers/body/auth/click, header sanitization,
per-event-type defaults, error paths.
- config: defaults, normalization tolerance, round-trip, redaction.
- dispatcher: master switch, per-event toggle, dedupe, agent/permission
hookups, top-level-only filtering for agent-spawned, dispose.
|
|
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
|
|
The debug-logger.ts module existed but was completely orphaned — none of
its functions had any callsites, so DISPATCH_DEBUG_LLM=1 did nothing.
Wires it in across the stack:
- llm/debug-logger.ts: add wrapFetchWithLogging() that tees SSE bodies via
TransformStream + response.clone() so we capture every chunk without
draining the body the AI SDK consumes. Redacts authorization / x-api-key
/ cookie headers in logs. Also exports nextDebugSeq() so requests and
log files share an id.
- llm/provider.ts: all 3 factories (Claude OAuth, plain-API-key Anthropic,
OpenAI-compatible) now pass fetch: wrapFetchWithLogging(globalThis.fetch).
For Claude OAuth the wrap goes on the inner base fetch so logged bodies
reflect the post-transform shape + Claude-Code session headers. Added
tabId to ProviderConfig for log labelling.
- agent/agent.ts: threads tabId through createProvider and emits
logAgentLoop / logStepLifecycle / logStreamEvent at every meaningful
point in the run loop — step start/end, tool count, every fullStream
event. All are no-ops when DISPATCH_DEBUG_LLM is unset.
- core/index.ts: re-exports the debug helpers.
- tests/llm/provider.test.ts: switch one full-object equality assertion
to property assertions so the test survives the new fetch: wrapper.
Plumbing the env var into the container required three more fixes:
- bin/up: re-export DISPATCH_DEBUG_LLM* so docker compose forwards them
(compose only forwards vars referenced in the environment: block).
Also pre-creates /tmp/dispatch/llm-debug and chowns it on first run so
the container's UID-1000 bun process can write into it without EACCES.
- docker-compose.yml: declare the debug vars on api.environment and
bind-mount /tmp/dispatch/llm-debug:/tmp/dispatch/llm-debug so logs are
inspectable from the host without docker exec.
- docker/entrypoint.dev.sh: explicitly forward DISPATCH_DEBUG_* through
the 'su -' login-shell barrier — su - resets the environment to TERM/
PATH/HOME/SHELL/USER/LOGNAME only, silently stripping everything else.
This is why the vars appeared via 'docker exec env' (which spawns a
new process inheriting the container env) but were absent from the
actual bun process's /proc/<pid>/environ.
bin/build: drop stray sudo for consistency with bin/up and bin/down.
|
|
- 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
|
|
Replace the message-as-container model with a flat, append-only chunk log.
- chunks table (id, tab_id, seq, turn_id, step, role, type, data_json): one
row per chunk; tool_call (assistant) and tool_result (tool) are SEPARATE
rows linked by callId. Message/turn are derived groupings, not stored.
- chunks/transform.ts: DB-free explode (Chunk[] -> rows) / group (rows ->
messages), shared by backend and the browser frontend.
- Cache fix: toModelMessages segments each turn at tool-batch boundaries into
stable [assistant, tool] pairs per step, so earlier steps serialize
byte-identically across requests (kills the prompt-cache churn).
- agent-manager persists a turn's chunks on seal (once), discarding a failed
fallback attempt's partial chunks; rebuilds agent history from the log.
- GET /messages windows the log by chunk seq then groups; loadMoreMessages
merges a turn split across the window boundary by turnId.
- One-shot migration drops the legacy messages table and clears tabs;
settings/credentials/keys/usage preserved.
Full suite green (317 tests); biome, tsc, and svelte-check clean.
|
|
backend pagination
Frontend keeps only a bounded window of chunks in memory (configurable via
settings slider, default 100). Older messages are evicted when at the bottom
and re-fetched from the backend on scroll-up.
- Backend: paginated GET /tabs/:id/messages with ?limit=N&before=seq
- Store: evictMessages trims oldest messages until total chunks ≤ limit
- Store: loadMoreMessages fetches next page and prepends with dedup
- ChatPanel: smart scroll hooks trigger eviction on return-to-bottom
- ChatPanel: onNearTop loads older history with scroll-position maintenance
- Settings: chunk limit slider in Memory section
- Fix: oldestLoadedSeq recalculated after eviction (pagination cursor stays valid)
- Fix: seq preserved on ChatMessage for cursor tracking
- Fix: scrolledUpTabs cleaned up on tab switch (no memory leak)
- Fix: evictMessages reads appSettings.chunkLimit directly (live updates)
|
|
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
|
|
batching, error/system chunks
|
|
symlink-safe path resolution
|
|
- 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)
|
|
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
|
|
- Add tabs, messages, and settings tables to SQLite database
- Backend: refactor AgentManager to manage per-tab Agent instances via Map<tabId, TabAgent>
- Backend: WebSocket events tagged with tabId for multiplexing
- Backend: tab CRUD routes (create, list, update, archive, messages)
- Backend: persist user and assistant messages to DB during chat
- Frontend: new tabStore replaces single chatStore with multi-tab reactive state
- Frontend: TabBar component using DaisyUI tabs-lift style with status dots
- Frontend: Settings sidebar panel for title generation model selection
- Frontend: wire ChatPanel, ChatInput, Header to use tabStore
- Fix HMR listener accumulation via wsClient.clearCallbacks()
- Delete old single-chat chatStore (chat.svelte.ts)
|
|
- Remove ModelResolver, model definitions, model tags, fallback order, agent templates
- Remove all [[models]], [agents], and fallback from dispatch.toml and config schema
- ModelRegistry is now a pure key-state manager
- dispatch.toml reduced to keys + permissions only
- Docker: fix entrypoint for existing UID, skip bun install in frontend container
- Docker: scoped build cache prune in bin/clean
|
|
- Add SQLite database at ~/.local/share/dispatch/dispatch.db with tables: credentials, api_keys, wake_schedule, usage_cache
- Store Claude OAuth credentials in DB with import button in Model Status UI
- Store OpenCode/Copilot API keys in DB with paste-to-import modal
- Store OpenCode cookie and workspace IDs in DB
- Migrate wake schedule from .wake-schedule.json to DB
- Migrate usage cache from in-memory Map + localStorage to DB
- Remove all env var and file fallbacks — DB is the single source of truth
- Add seed scripts: bin/import-credentials.ts, bin/seed-opencode-keys.ts
- Docker: container runs as host UID/GID with matching home directory
- Clean up dispatch.toml: remove env fields, update comments
- Progress bar time markers for usage cycle tracking
|
|
effort, and dynamic model listing
|
|
- 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
|