Merge remote-tracking branch 'origin/master' into worktree/provider-routed-llm-adapters

# Conflicts:
#	docs/config-catalog.md
#	docs/cordis-catalog/events.md
#	docs/cordis-catalog/services.md
#	docs/core-data-structures/core.md
#	docs/event-producer-consumer.md
#	docs/persistence-catalog.md
#	examples/acp-agent/tests/snapshots/advanced-toolchain/session.1.jsonl
#	examples/acp-agent/tests/snapshots/advanced-toolchain/session.2.jsonl
#	examples/acp-agent/tests/snapshots/advanced-toolchain/session.jsonl
#	examples/acp-agent/tests/snapshots/both-mode-turn/session.jsonl
#	examples/acp-agent/tests/snapshots/hook-cc-pretool-ask/session.jsonl
#	examples/acp-agent/tests/snapshots/skill-load/session.jsonl
#	examples/acp-agent/tests/snapshots/text-turn/session.jsonl
#	examples/sandbox-acp-agent/cordis.yml
#	examples/sandbox-acp-agent/tests/snapshots/escalation-approved/session.jsonl
#	examples/sandbox-acp-agent/tests/snapshots/escalation-rejected/session.jsonl
#	examples/sandbox-acp-agent/tests/snapshots/mode-switching/session.jsonl
#	packages/compact/compact-basic/README.md
#	packages/compact/compact-basic/src/index.ts
#	packages/compact/compact-basic/tests/compact-basic.spec.ts
#	packages/core/agent-loop/README.md
#	packages/core/agent-loop/src/loop.ts
#	packages/core/agent-loop/tests/properties.spec.ts
#	packages/core/session/README.md
#	packages/core/session/src/types.ts
#	packages/core/session/tests/derived-cache.spec.ts
#	packages/llm/llm-deepseek/src/index.ts
#	packages/llm/llm-pi-ai/README.md
#	packages/llm/llm-pi-ai/src/adapter.ts
#	packages/llm/llm-pi-ai/src/convert.ts
#	packages/llm/llm-pi-ai/tests/adapter.spec.ts
#	packages/llm/llm/README.md
#	packages/llm/llm/src/call-config.ts
#	packages/llm/llm/src/index.ts
#	packages/ui/acp-agent/src/index.ts
#	packages/ui/acp/tests/harness.ts
#	packages/ui/jsonrpc/README.md
#	packages/ui/jsonrpc/src/server.ts
#	packages/ui/stdio-agent/README.md
#	packages/ui/stdio-agent/src/index.ts
#	python/sdk/README.i18n.yaml
This commit is contained in:
Yichen Jiang
2026-07-14 22:17:50 +08:00
672 changed files with 10295 additions and 14200 deletions

View File

@@ -1,6 +1,6 @@
# @deepseek-ai/dsh-tool-workflow
The model-facing **`workflow` tool**: run a JavaScript orchestration script that fans out subagents, and return the script's final value. Pure schema + lifecycle shaping over [`ctx.workflows`](../workflow/README.md) script parsing, execution, caps, and cancellation live behind the seam, so a hardened engine swaps in without touching what the model sees.
The model-facing **`workflow` tool**: run a JavaScript orchestration script that fans out subagents, and return the script's final value. This package owns schema and lifecycle shaping over [`ctx.workflows`](../workflow/README.md); script parsing, execution, caps, and cancellation live behind the seam, while the consumer retains ownership of the parent-facing schema and result envelope.
## What the model sees
@@ -20,3 +20,35 @@ Decided up front (per the [render-intent RFC](../../../docs/rfc/implemented/arch
|---|---|---|
| `toolName` | `workflow` | The model-facing tool name to register. |
| `maxResultChars` | `50000` | Rendered-result ceiling; longer JSON is truncated with a notice. |
## Model Experience
### System prompt
**What the model sees**: Every parent request in this plugin's registration scope receives the workflow guidance below. A scoped tool restriction can hide the schema without removing this independently registered guidance.
**Token effect**: Small fixed guidance cost per request while the plugin is active.
#### Workflow guidance
```markdown
Use the <toolName> tool ONLY when the user explicitly asks for a workflow or for large multi-agent orchestration: you write a JavaScript script (the tool description documents the exact format) that fans work out across many subagents with phases and structured results. For one or two delegations, prefer plain subagent calls.
```
### Tool schema
**What the model sees**: When visible, the generated default [`workflow` schema](../../../docs/tool-catalog.md#deepseek-aidsh-tool-workflow) carries the complete JavaScript hook and metadata contract; `toolName` can rename the definition, and the model submits script, metadata, and optional args.
**Token effect**: Substantial fixed schema cost on each request where the tool is visible.
### Tool-call history and result
**What the model sees**: The full model-written script, metadata, and args remain in the assistant tool call. Success is exactly `workflow "<name>" completed (<count> agent<optional-s>).`, newline, `Return value:`, newline, and pretty-printed data-dependent JSON; a cap adds `… [truncated: <omitted> more characters]` on a new line. Failures are exactly `Error: workflow run was cancelled`, optionally suffixed ` (<error>)`, `Error: workflow run failed: <error-or-unknown error>`, or defensively `Error: workflow run ended abnormally (<reason>)`; a call without an owning agent becomes `Error: workflow tool requires a calling agent (exec.agent was undefined)`. Intermediate child messages are omitted.
**Token effect**: Call tokens can be large and remain until compaction. Result rendering is capped by `maxResultChars`; child-model tokens are separate from the parent's retained context.
## Known Limitations and Deferred Work
- **The parent turn blocks until the whole workflow settles** — there is no background start/poll surface, and cancellation discards partial output as an error.
- **`args` must be an object and the result is bounded text** — callers wrap top-level arrays/scalars in a field, and JSON beyond `maxResultChars` is truncated rather than stored behind a retrieval handle.
- **Workflow policy is fixed per tool registration** — provider selection, caps, and tool name are deployment config, not model-call arguments.

View File

@@ -1,25 +1,12 @@
/**
* The model-facing `workflow` tool: run a JavaScript orchestration script that
* fans out subagents, and return the script's final value. Pure schema +
* lifecycle shaping — script parsing, execution, caps, and cancellation live
* behind `ctx.workflows` (`@deepseek-ai/dsh-workflow`), so a hardened engine
* swaps in without touching what the model sees.
*
* Collection is SYNCHRONOUS this cut (like `dsh-tool-subagent`): `execute`
* starts a run and awaits `run.result` inside a `try/finally` that always
* disposes the run, so the script and its children are torn down on every
* path. A non-`completed` stop reason maps to an `isError` tool result (by
* throwing) rather than returning partial output as success. Background
* collection is deferred to the cross-tool background redesign.
*
* Render intent (decided up front, per the render-intent RFC): a `generic`
* card whose title carries the workflow's `meta.name`, read directly from the
* call's `meta` parameter — presentation is a pure function of `args`.
*
* Usage policy ships with the tool as a `tool:<toolName>` system-prompt
* section (explicit-ask-only guidance) — tool guidance lives in tool plugins,
* never in the deployment persona.
*
* The model-facing `workflow` tool: run a JavaScript orchestration script that fans out
* subagents, and return the script's final value. Pure schema + lifecycle shaping — script
* parsing, execution, caps, and cancellation live behind `ctx.workflows`
* (`@deepseek-ai/dsh-workflow`), so a hardened engine swaps in without touching what the model
* sees. Execution awaits `run.result` and always disposes the run; non-completed reasons become tool
* errors, and background collection remains deferred. Presentation is an args-only generic card
* titled from `meta.name`. Explicit-ask usage guidance is registered as the tool's own prompt
* section rather than deployment persona prose.
* @module @deepseek-ai/dsh-tool-workflow
*/
@@ -190,10 +177,9 @@ export function apply(ctx: Context, config: Config): void {
...exec.signal ? { signal: exec.signal } : {},
})
// Bridge the tool's abort signal to the run: if the parent step is
// aborted while the script is in flight, cancel the whole run. The
// engine also receives `signal` directly, but an explicit bridge keeps
// the tool's contract local (and covers an engine that ignores it).
// Bridge the tool's abort signal to the run: if the parent step is aborted while the
// script is in flight, cancel the whole run. The signal also enters the engine directly, but
// this local bridge preserves the tool contract even if an implementation ignores it.
const onAbort = (): void => { run.cancel('parent step aborted') }
exec.signal?.addEventListener('abort', onAbort, { once: true })
// `addEventListener` does NOT fire for a signal already aborted before

View File

@@ -224,13 +224,8 @@ describe('dsh-tool-workflow', () => {
describe('composition with the REAL worker-thread engine (the mock above must stay honest)', () => {
it('an abort releases the tool even when the script parks on a promise no hook owns', async () => {
// Regression for the review-found turn wedge: the tool awaits
// run.result BEFORE its disposing finally, the registry and the loop
// await the tool — so if cancellation could not settle result (a script
// parked on `await new Promise(() => {})`), an aborted turn stayed
// wedged forever. The seam now guarantees result settles within the
// grace of cancel(); this drives that guarantee through the real
// registry + real tool + real engine.
// The tool and loop await run.result before cleanup, so cancellation must settle a script
// parked on an unowned promise. Exercise that guarantee through the real registry and worker.
const ctx = new Context()
await ctx.plugin(SystemPrompt)
await ctx.plugin(ToolRegistry)