feat(tasks): background task runtime, generic task_* control tools, bash/subagent producers

One shared ctx.tasks registry (branded <kind>-N ids, owner-fenced
read/kill/wait/list, attachSurface misconfiguration fence, reported-flag
notice dedup, atomic register) + dsh-tool-tasks (task_output/task_list/
task_kill, completion-notice injection, background prompt habit).
Producers opt in via their own enableRunInBackground config: bash
(stream kind; seam slimmed to resolve/run/start returning a BashProcess
handle, bash_output/bash_kill deleted) and subagent (final-output kind;
done settles after run.dispose()). Owner disposal drains tasks through
the new awaited ctx.agents.onCleanup seam in the loop's disposal chain.
Both RFCs moved to implemented/; docs, catalogs, snapshots re-pinned.
This commit is contained in:
Yichen Jiang
2026-07-09 21:22:54 +08:00
parent e7e382f9d1
commit 184e164091
83 changed files with 3909 additions and 1627 deletions

View File

@@ -41,9 +41,7 @@ Registration is effect-based: disposing the plugin fiber unregisters the tool (w
## Long-running work
Follow tool-bash's background pattern: a `run_in_background` flag returns a task id immediately; companion tools poll incrementally and kill; completion notices arrive via `agent.inject()`. Bound buffers and spill full output to disk so nothing is silently lost.
> TODO: each tool reimplements this background pattern by hand today. At some point we need a generic long-running-tool layer that handles task ids, incremental polling, kill, and completion notices uniformly.
Register the running work with the shared task runtime instead of inventing a task protocol: gate a `run_in_background` parameter behind your plugin's own defaulted `enableRunInBackground`-style config, start the work, and hand it to `ctx.tasks.register({ kind, label, owner: exec.agent, cancel, done, readOutput? })` (`@deepseek-ai/dsh-tasks`). The runtime issues the `<kind>-N` id, fences access to the owning session, cancels-and-awaits your task when the owner disposes, and the generic `task_output`/`task_list`/`task_kill` tools plus the completion notice come from `@deepseek-ai/dsh-tool-tasks` — your tool returns `started background task <id>` and is done. Your producer keeps its execution concerns: `done` must settle at quiescence (resources released), and a stream-kind `readOutput` owns its own truncation/spill formatting (bound buffers, spill full output to disk so nothing is silently lost — see tool-bash's `renderProcessRead`). Do NOT wire `exec.signal` to the background work after the id is returned; check `exec.signal?.aborted` once before starting, then leave cancellation to `task_kill` and owner cleanup. **A failed `register()` must not orphan the work**: `register()` is atomic (a throw — the no-control-surface fence, a bad owner — mutates no registry state), so wrap it in try/catch, cancel the just-started work, AWAIT its quiescence, and rethrow — the model never learns an id, so nothing else could ever collect or kill what you started (tool-bash's `proc.kill(); await proc.done` and tool-subagent's `run.cancel(); await done` are the templates).
## Permissions / sandboxing