@x1a0f3n9/dsh-tool-workflow 0.1.5-rc.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 DeepSeek
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,6 @@
1
+ # Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
2
+ # side as of the last confirmed-consistent state. Both languages carry equal authority;
3
+ # after editing either side, bring the other along and re-record with:
4
+ # pnpm run verify-translation-pairing --write packages/workflow/tool-workflow/README.md
5
+ README.md: 8c93fa2658703f2252b3300a4c55cec4ae5fa0cf
6
+ README.zh.md: 354502a49f0157af2fb3e6c2109e303184400a44
package/README.md ADDED
@@ -0,0 +1,174 @@
1
+ ---
2
+ description: "The model-facing workflow tool: run a JavaScript orchestration script that fans out subagents, for users and maintainers choosing or configuring model-driven orchestration."
3
+ kind: "package-reference"
4
+ ---
5
+
6
+ # @x1a0f3n9/dsh-tool-workflow
7
+
8
+ English | [中文](README.zh.md)
9
+
10
+ ## Summary
11
+
12
+ `dsh-tool-workflow` lets a model run a JavaScript orchestration script that delegates work to many subagents and returns the script's final JSON value. Use it only when the user explicitly requests a workflow or large multi-agent orchestration; use plain subagent calls for one or two delegations. The parent turn waits until every delegated task settles, and cancellation or abnormal completion returns an error rather than partial success. Deployments can rename the tool and cap rendered result text through `toolName` and `maxResultChars`.
13
+
14
+ ## Table of Contents
15
+
16
+ - [Use this package](#use-this-package)
17
+ - [Understand the implementation](#understand-the-implementation)
18
+ - [Further Exploration](#further-exploration)
19
+ - [Model Experience](#model-experience)
20
+ - [Known Limitations and Deferred Work](#known-limitations-and-deferred-work)
21
+ - [Dev Note](#dev-note)
22
+
23
+ -----
24
+
25
+ <a id="use-this-package"></a>
26
+ ## Use this package
27
+
28
+ The `workflow` tool runs a model-authored orchestration script that fans work out across many subagents and returns the script's final JSON value. Use it only when the user explicitly asks for a workflow or for large multi-agent orchestration — an audit over many files, a migration, multi-angle research; for one or two delegations, prefer plain subagent calls.
29
+
30
+ ### Calling the tool
31
+
32
+ The model submits three parameters: `meta` (required identity data: `name`, `description`, and optional `whenToUse` and `phases`), `script` (required plain JavaScript body — no `export const meta` statement; the tool description carries the complete authoring contract), and `args` (optional JSON object exposed to the script as the `args` global; wrap a bare list in a field so the wire schema stays honest).
33
+
34
+ Success returns the canonical envelope `{ runId, agentsStarted, result }`, rendered to the model as `workflow "<name>" completed (<count> agent<optional-s>).` followed by `Return value:` and the pretty-printed JSON. A workflow that cannot start — a script parse or meta validation failure — returns an error the model can correct from. Cancellation and execution failures return `Error: workflow run was cancelled` or `Error: workflow run failed: <error>`; partial output is never reported as success.
35
+
36
+ ### What to expect during a run
37
+
38
+ While the script runs, the parent turn waits: the tool starts the run, awaits its result, and always disposes it, so the script and its children reach quiescence on every path — including cancellation, which is bridged from the parent step's abort signal. The model sees one final outcome, never intermediate child messages; the children's own work stays out of the parent conversation.
39
+
40
+ ### Config
41
+
42
+ | Field | Default | Meaning |
43
+ |---|---|---|
44
+ | `toolName` | `workflow` | The model-facing tool name to register. |
45
+ | `maxResultChars` | `50000` | Rendered-result ceiling; longer JSON is truncated with a notice. |
46
+
47
+ The generated [configuration catalog](../../../docs/config-catalog.md#deepseek-aidsh-tool-workflow) is the exhaustive source for every accepted field.
48
+
49
+ -----
50
+
51
+ <a id="understand-the-implementation"></a>
52
+ ## Understand the implementation
53
+
54
+ <details>
55
+ <summary>Implementation internals — click to expand</summary>
56
+
57
+ This section explains how the consumer is split from the engine and how the run lifecycle and records work; observable behavior is fully covered in [Use this package](#use-this-package).
58
+
59
+ ### Design concept
60
+
61
+ The consumer owns the model-facing schema, the `tool:<toolName>` system-prompt guidance, and the result envelope; script parsing, execution, caps, and cancellation live behind `ctx.workflowEngine`, so a hardened engine swaps in without changing what the model sees. Usage guidance ships with the tool plugin as a prompt section, never in the deployment persona.
62
+
63
+ ### Run lifecycle
64
+
65
+ `execute` starts the run and awaits `run.result` inside a `try/finally` that always disposes the run. `exec.signal` is bridged to `run.cancel()`, including the already-aborted-before-start case. A non-`completed` stop reason maps to an `isError` result reporting the reason; completion renders `{ runId, agentsStarted, result }`, with the Native renderer truncating only that projection at `maxResultChars`.
66
+
67
+ ### Durable session records
68
+
69
+ For a root transport execution (`exec.parent` absent), the tool projects the run into the calling Agent's Session with four log-only events: run-start after `start()` returns, member starts and endings filtered by `run.id`, then run-end only after the result is available and disposal reaches quiescence. Nested transport calls execute normally but write no record. The first failed Session append disables later recording for that run with one warning, leaving either no record or a legal continuous prefix without changing the tool result or cleanup. The package invariant rejects duplicate starts, unpaired members, terminal events with open members, and updates after run-end on both cold load and live append, while accepting missing terminal suffixes.
70
+
71
+ ### Render intent
72
+
73
+ Decided up front per the [render-intent Agent Note](../../../.agents/notes/implemented/architecture/2026-07-02-tool-render-intent-union.md): a `generic` card titled `workflow: <meta.name>`, read directly from `args.meta.name` — presentation is a pure function of args — with the script text carried as `rawInput`. The result keeps the generic card.
74
+
75
+ ### Source map
76
+
77
+ | File | Role |
78
+ |---|---|
79
+ | [`src/index.ts`](src/index.ts) | Plugin entry: tool registration, run lifecycle, recorder wiring |
80
+ | [`src/types.ts`](src/types.ts) | The four log-only record event payloads and their `SessionEventMap` declaration |
81
+ | [`src/invariant.ts`](src/invariant.ts) | Invariant companion: durable workflow-record protocol validation |
82
+
83
+ </details>
84
+
85
+ -----
86
+
87
+ <a id="further-exploration"></a>
88
+ ## Further Exploration
89
+
90
+ Read these pages when the tool-level contract is not enough. They move from the shared workflow model to the engine and the comparable delegation tool.
91
+
92
+ - [Workflow subsystem](../../../docs/subsystems/workflow.md) — the seam contract, start request, and event payloads.
93
+ - [Workflow seam](../workflow/README.md) — the run and result vocabulary behind the tool.
94
+ - [Worker-thread engine](../workflow-worker-thread/README.md) — the engine that executes the scripts.
95
+ - [subagent tool](../../subagent/tool-subagent/README.md) — the plain-delegation alternative for one or two children.
96
+ - [Group map](../README.md) — the workflow capability family and its packages.
97
+ - [Dynamic workflows Agent Note](../../../.agents/notes/implemented/feature/2026-07-05-dynamic-workflows.md) — the seam design and its decisions.
98
+
99
+ -----
100
+
101
+ <a id="model-experience"></a>
102
+ ## Model Experience
103
+
104
+ ### System prompt
105
+
106
+ #### What the model sees
107
+
108
+ 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.
109
+
110
+ ##### Workflow guidance
111
+
112
+ ```markdown
113
+ 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.
114
+ ```
115
+
116
+ #### Token effect
117
+
118
+ Small fixed guidance cost per request while the plugin is active.
119
+
120
+ #### KV Cache effect
121
+
122
+ Prefix-stable while the plugin scope and guidance text are unchanged. Activation or disposal may invalidate reuse from this prompt section.
123
+
124
+ ### Tool schema
125
+
126
+ #### What the model sees
127
+
128
+ 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.
129
+
130
+ #### Token effect
131
+
132
+ Substantial fixed schema cost on each request where the tool is visible.
133
+
134
+ #### KV Cache effect
135
+
136
+ Prefix-stable while `toolName`, definition, and visibility are unchanged. Renaming, plugin lifecycle, or scoped restrictions may invalidate reuse from this schema.
137
+
138
+ ### Tool-call history and result
139
+
140
+ #### What the model sees
141
+
142
+ 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.
143
+
144
+ #### Token effect
145
+
146
+ 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.
147
+
148
+ #### KV Cache effect
149
+
150
+ Append-only; newly visible content follows the reusable request prefix and does not invalidate existing KV-cache entries.
151
+
152
+ ## Known Limitations and Deferred Work
153
+
154
+ <a id="known-limitations-and-deferred-work"></a>
155
+
156
+
157
+ These limits define what the tool does not yet support. They are current constraints, not a task backlog.
158
+
159
+ - **The parent turn blocks until the whole workflow settles** — there is no background start/poll API, and cancellation discards partial output as an error.
160
+ - **`args` must be an object and Native result text is bounded** — callers wrap top-level arrays and scalars in a field; the canonical workflow result stays complete, while JSON beyond `maxResultChars` is truncated in the model-facing projection rather than stored behind a retrieval handle.
161
+ - **Workflow policy is fixed per tool registration** — provider selection, caps, and tool name are deployment config, not model-call arguments.
162
+ - **Durable records are top-level and observational** — nested PTC mode dispatches are not recorded, and a recording failure intentionally degrades to an incomplete prefix rather than changing execution.
163
+
164
+ <a id="dev-note"></a>
165
+ ### Dev Note
166
+
167
+ <details>
168
+ <summary>Working context for maintainers — click to expand</summary>
169
+
170
+ This Dev Note is working context for maintainers: open directions that are not decided. It is explicitly non-authoritative — shipped behavior, limits, and accepted rationale live in the sections above, the package code, and the linked Agent Notes.
171
+
172
+ Open directions: a background start/poll route so the parent turn does not block; storing truncated JSON behind a retrieval handle instead of clipping the projection; recording nested dispatches beyond the top level.
173
+
174
+ </details>
package/README.zh.md ADDED
@@ -0,0 +1,174 @@
1
+ ---
2
+ description: "面向模型的工作流工具:运行扇出 subagent 的 JavaScript 编排脚本,供选择或配置模型驱动编排的用户与维护者阅读。"
3
+ kind: "package-reference"
4
+ ---
5
+
6
+ # @x1a0f3n9/dsh-tool-workflow
7
+
8
+ [English](README.md) | 中文
9
+
10
+ ## 概述
11
+
12
+ `dsh-tool-workflow` 让模型运行 JavaScript 编排脚本,把工作委派给多个 subagent,并返回脚本的最终 JSON 值。仅当用户明确要求工作流或大型多 agent(智能体)编排时使用;一两项委派应使用普通 subagent 调用。父级轮次会等待所有委派任务结束;取消或异常完成会返回错误,而不是部分成功。部署方可以通过 `toolName` 重命名工具,并通过 `maxResultChars` 限制渲染结果文本。
13
+
14
+ ## 目录
15
+
16
+ - [使用本包](#use-this-package)
17
+ - [理解实现](#understand-the-implementation)
18
+ - [进一步探索](#further-exploration)
19
+ - [模型体验](#model-experience)
20
+ - [已知限制与延期工作](#known-limitations-and-deferred-work)
21
+ - [开发备注](#dev-note)
22
+
23
+ -----
24
+
25
+ <a id="use-this-package"></a>
26
+ ## 使用本包
27
+
28
+ `workflow` 工具运行由模型编写的编排脚本,把工作扇出到多个 subagent,并返回脚本的最终 JSON 值。仅当用户明确要求工作流或大型多 agent 编排时使用——例如跨多个文件的审计、一次迁移、多角度研究;一两项委派时优先使用普通 subagent 调用。
29
+
30
+ ### 调用工具
31
+
32
+ 模型提交三个参数:`meta`(必需的身份数据:`name`、`description`,以及可选的 `whenToUse` 与 `phases`)、`script`(必需的纯 JavaScript 脚本体——不含 `export const meta` 语句;工具描述携带完整的编写约定)与 `args`(可选 JSON 对象,作为全局变量 `args` 向脚本公开;裸列表应包装到字段中,使协议 schema 如实表达形态)。
33
+
34
+ 成功返回规范包络 `{ runId, agentsStarted, result }`,向模型渲染为 `workflow "<name>" completed (<count> agent<optional-s>).`,后接 `Return value:` 与美化打印的 JSON。无法启动的工作流——脚本解析或 meta 校验失败——返回模型可以修正的错误。取消与执行失败返回 `Error: workflow run was cancelled` 或 `Error: workflow run failed: <error>`;部分输出绝不会被报告为成功。
35
+
36
+ ### 运行期间的预期
37
+
38
+ 脚本运行期间,父级轮次会等待:工具启动运行、等待其结果,并始终对该运行执行 dispose(资源释放),因此脚本及其子 agent 在每条路径上完全停稳——包括从父级步骤中止信号桥接而来的取消。模型只看到最终结果,永远不会看到中间子 agent 消息;子 agent 自己的工作不会进入父级对话。
39
+
40
+ ### 配置
41
+
42
+ | 字段 | 默认值 | 含义 |
43
+ |---|---|---|
44
+ | `toolName` | `workflow` | 要注册的面向模型工具名称。 |
45
+ | `maxResultChars` | `50000` | 渲染结果上限;更长的 JSON 会被截断并附上提示。 |
46
+
47
+ 生成的[配置目录](../../../docs/config-catalog.zh.md#deepseek-aidsh-tool-workflow)是每个受支持字段的穷尽式真源。
48
+
49
+ -----
50
+
51
+ <a id="understand-the-implementation"></a>
52
+ ## 理解实现
53
+
54
+ <details>
55
+ <summary>实现细节——点击展开</summary>
56
+
57
+ 本节解释消费方如何与引擎拆分、运行生命周期与记录如何工作;可观察行为已在[使用本包](#use-this-package)中完整说明。
58
+
59
+ ### 设计理念
60
+
61
+ 消费方拥有模型侧 schema、`tool:<toolName>` 系统提示词指导与结果包络;脚本解析、执行、上限与取消位于 `ctx.workflowEngine` 之后,因此更坚固的引擎可以无缝替换,而不改变模型看到的内容。使用指导以提示词段的形式随工具插件交付,绝不放入部署 persona。
62
+
63
+ ### 运行生命周期
64
+
65
+ `execute` 启动运行,并在 `try/finally` 内等待 `run.result`;该结构总会对运行执行 dispose。`exec.signal` 会桥接到 `run.cancel()`,包括启动前已经中止的情况。非 `completed` 结束原因会映射为报告原因的 `isError` 结果;完成时渲染 `{ runId, agentsStarted, result }`,Native 渲染器只会在 `maxResultChars` 处截断该投影。
66
+
67
+ ### 持久会话记录
68
+
69
+ 对于根 transport 执行(`exec.parent` 缺省),工具会用四个 log-only 事件把运行投影到调用方 agent 的会话:`start()` 返回后写 run-start,只记录 `run.id` 匹配的成员开始与结束,并且只在结果可用且 dispose 完全停稳后写 run-end。嵌套 transport 调用照常执行,但不写任何记录。会话追加操作首次失败后,本运行会停止后续记录并只告警一次,留下空记录或合法连续前缀,同时不改变工具结果和清理。包 invariant 会在冷加载与实时追加时拒绝重复 start、未配对成员、仍有开放成员的终点与 run-end 后更新,同时允许缺失终态后缀的连续前缀。
70
+
71
+ ### 渲染意图
72
+
73
+ 按[渲染意图 Agent Note](../../../.agents/notes/implemented/architecture/2026-07-02-tool-render-intent-union.zh.md)预先确定:使用 `generic` 卡片,标题为 `workflow: <meta.name>`,直接从 `args.meta.name` 读取——呈现是参数的纯函数——脚本文本作为 `rawInput` 携带。结果继续使用 generic 卡片。
74
+
75
+ ### 源码地图
76
+
77
+ | 文件 | 职责 |
78
+ |---|---|
79
+ | [`src/index.ts`](src/index.ts) | 插件入口:工具注册、运行生命周期、记录器接线 |
80
+ | [`src/types.ts`](src/types.ts) | 四个 log-only 记录事件 payload 及其 `SessionEventMap` 声明 |
81
+ | [`src/invariant.ts`](src/invariant.ts) | 不变式配套入口:持久工作流记录协议校验 |
82
+
83
+ </details>
84
+
85
+ -----
86
+
87
+ <a id="further-exploration"></a>
88
+ ## 进一步探索
89
+
90
+ 当工具级约定不够用时阅读以下页面。这些页面依次介绍共享工作流模型、引擎,以及可供比较的委派工具。
91
+
92
+ - [工作流子系统](../../../docs/subsystems/workflow.zh.md)——seam 约定、启动请求与事件载荷。
93
+ - [工作流 seam](../workflow/README.zh.md)——工具背后的运行与结果词汇。
94
+ - [worker-thread 引擎](../workflow-worker-thread/README.zh.md)——执行脚本的引擎。
95
+ - [subagent 工具](../../subagent/tool-subagent/README.zh.md)——一两项委派时的普通委派替代方案。
96
+ - [组地图](../README.zh.md)——工作流能力家族及其包。
97
+ - [动态工作流 Agent Note](../../../.agents/notes/implemented/feature/2026-07-05-dynamic-workflows.zh.md)——seam 设计及其决策。
98
+
99
+ -----
100
+
101
+ <a id="model-experience"></a>
102
+ ## 模型体验
103
+
104
+ ### 系统提示词
105
+
106
+ #### 模型看到什么
107
+
108
+ 在该插件的注册作用域内,每个父级请求都会收到下方的工作流指导。作用域工具限制可以隐藏 schema,而不移除这段独立注册的指导。
109
+
110
+ ##### 工作流指导
111
+
112
+ ```markdown
113
+ 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.
114
+ ```
115
+
116
+ #### Token 影响
117
+
118
+ 插件启用期间,每个请求都会产生少量固定的指导 token 开销。
119
+
120
+ #### KV Cache 影响
121
+
122
+ 只要插件作用域与指导文本不变,前缀就保持稳定。启用或 dispose 可能会使从该提示词段起的缓存复用失效。
123
+
124
+ ### 工具 schema
125
+
126
+ #### 模型看到什么
127
+
128
+ 工具可见时,已生成的默认 [`workflow` schema](../../../docs/tool-catalog.zh.md#deepseek-aidsh-tool-workflow) 包含完整的 JavaScript 钩子与元数据约定;`toolName` 可以重命名该定义,模型会提交脚本、元数据与可选 args。
129
+
130
+ #### Token 影响
131
+
132
+ 工具可见时,每个请求都会产生较大的固定 schema token 开销。
133
+
134
+ #### KV Cache 影响
135
+
136
+ 只要 `toolName`、定义与可见性不变,前缀就保持稳定。重命名、插件生命周期或作用域限制可能会使从该 schema 起的缓存复用失效。
137
+
138
+ ### 工具调用历史与结果
139
+
140
+ #### 模型看到什么
141
+
142
+ 由模型编写的完整脚本、元数据与 args 会保留在 assistant 工具调用中。成功结果精确为 `workflow "<name>" completed (<count> agent<optional-s>).`、换行、`Return value:`、换行,以及美化打印且依赖数据的 JSON;达到上限时,会在新行添加 `… [truncated: <omitted> more characters]`。失败结果精确为 `Error: workflow run was cancelled`(可以追加后缀 ` (<error>)`)、`Error: workflow run failed: <error-or-unknown error>` 或防御性的 `Error: workflow run ended abnormally (<reason>)`;没有所属 agent 的调用变为 `Error: workflow tool requires a calling agent (exec.agent was undefined)`。中间子 agent 消息会被省略。
143
+
144
+ #### Token 影响
145
+
146
+ 调用 token 可能很多,并会保留到压缩(compaction)为止。结果渲染受 `maxResultChars` 限制;子模型 token 与父级保留的上下文相互独立。
147
+
148
+ #### KV Cache 影响
149
+
150
+ 仅追加;新增可见内容位于可复用请求前缀之后,不会使现有 KV Cache 条目失效。
151
+
152
+ ## 已知限制与延期工作
153
+
154
+ <a id="known-limitations-and-deferred-work"></a>
155
+
156
+
157
+ 这些限制说明该工具尚未支持什么。它们是当前约束,不是任务积压。
158
+
159
+ - **父级轮次会阻塞到整个工作流结算**——没有后台启动/轮询接口,取消会丢弃局部输出并返回错误。
160
+ - **`args` 必须是对象,Native 结果文本有界**——调用方把顶层数组/标量包装到字段中;规范工作流结果保持完整,超过 `maxResultChars` 的 JSON 会在面向模型的投影中截断,而不是存储在检索句柄背后。
161
+ - **每次工具注册的工作流策略固定**——提供方选择、上限与工具名称属于部署配置,不是模型调用参数。
162
+ - **持久记录只覆盖顶层且只供观察**——嵌套 PTC mode dispatch 不记录;记录故障会刻意退化为不完整前缀,而不改变执行。
163
+
164
+ <a id="dev-note"></a>
165
+ ### 开发备注
166
+
167
+ <details>
168
+ <summary>维护者的工作上下文——点击展开</summary>
169
+
170
+ 本开发备注是维护者的工作上下文:尚未决定的开放方向。它明确不具权威性——已交付的行为、限制与既定理由以上文、包代码与相关 Agent Note 为准。
171
+
172
+ 开放方向:让父级轮次不再阻塞的后台启动/轮询路径;把截断的 JSON 存储在检索句柄背后,而不是剪裁投影;记录超出顶层的嵌套 dispatch。
173
+
174
+ </details>
package/lib/index.js ADDED
@@ -0,0 +1,276 @@
1
+ import z from "@deepseek-ai/schemastery";
2
+ import { defineTool } from "@x1a0f3n9/dsh-tools";
3
+ //#region lib/types/index.js
4
+ /**
5
+ * The model-facing `workflow` tool: run a JavaScript orchestration script that fans out
6
+ * subagents, and return the script's final value. It owns the model-facing schema and run lifecycle; script
7
+ * parsing, execution, caps, and cancellation live behind `ctx.workflowEngine`
8
+ * (`@x1a0f3n9/dsh-workflow`), so a hardened engine swaps in without touching what the model
9
+ * sees. Execution awaits `run.result` and always disposes the run; non-completed reasons become tool
10
+ * errors, and background collection remains deferred. Presentation is an args-only generic card
11
+ * titled from `meta.name`. Explicit-ask usage guidance is registered as the tool's own prompt
12
+ * section rather than deployment persona prose.
13
+ * @module @x1a0f3n9/dsh-tool-workflow
14
+ */
15
+ const name = "tool-workflow";
16
+ const inject = [
17
+ "tools",
18
+ "workflowEngine",
19
+ "systemPrompt"
20
+ ];
21
+ const Config = z.object({
22
+ toolName: z.string().default("workflow"),
23
+ maxResultChars: z.natural().min(1).default(5e4)
24
+ });
25
+ /** Render a contained recording failure without trusting the thrown value. */
26
+ function renderRecordingError(error) {
27
+ try {
28
+ return String(error);
29
+ } catch {
30
+ return "[unrenderable thrown value]";
31
+ }
32
+ }
33
+ /**
34
+ * Project active top-level workflow runs into their parent Sessions without
35
+ * letting recording failure affect tool execution.
36
+ */
37
+ function createWorkflowRecorder(ctx) {
38
+ const active = /* @__PURE__ */ new Map();
39
+ const append = (session, type, data) => {
40
+ const appendRecord = session.append.bind(session);
41
+ try {
42
+ appendRecord(type, data);
43
+ return true;
44
+ } catch (error) {
45
+ ctx.logger.warn(`tool-workflow: disabled durable record after ${type} append failed: ${renderRecordingError(error)}`);
46
+ return false;
47
+ }
48
+ };
49
+ ctx.on("workflow/agent-start", (info, agent) => {
50
+ const session = active.get(info.id);
51
+ if (session === void 0) return;
52
+ if (!append(session, "tool-workflow/agent-start", {
53
+ runId: info.id,
54
+ seq: agent.seq,
55
+ label: agent.label,
56
+ ...agent.phase === void 0 ? {} : { phase: agent.phase },
57
+ childId: agent.childId
58
+ })) active.delete(info.id);
59
+ });
60
+ ctx.on("workflow/agent-end", (info, agent) => {
61
+ const session = active.get(info.id);
62
+ if (session === void 0) return;
63
+ if (!append(session, "tool-workflow/agent-end", {
64
+ runId: info.id,
65
+ seq: agent.seq,
66
+ outcome: agent.outcome
67
+ })) active.delete(info.id);
68
+ });
69
+ return {
70
+ start(session, run) {
71
+ if (append(session, "tool-workflow/run-start", {
72
+ runId: run.id,
73
+ name: run.meta.name
74
+ })) active.set(run.id, session);
75
+ },
76
+ finish(runId, stopReason) {
77
+ const session = active.get(runId);
78
+ if (session !== void 0) append(session, "tool-workflow/run-end", {
79
+ runId,
80
+ stopReason
81
+ });
82
+ active.delete(runId);
83
+ },
84
+ abandon: (runId) => {
85
+ active.delete(runId);
86
+ }
87
+ };
88
+ }
89
+ /**
90
+ * The script-authoring contract, embedded in the tool description. This IS the
91
+ * model-facing spec: the meta block, the hooks and their exact semantics, and
92
+ * the supported schema subset.
93
+ */
94
+ const DESCRIPTION = `Run a JavaScript workflow script that orchestrates subagents at scale. Use this for work that fans out across many independent pieces — an audit over many files, a migration, multi-angle research, adversarial verification of findings — where you write the orchestration as a script instead of delegating turn by turn.
95
+
96
+ The workflow's identity rides the \`meta\` parameter as JSON: required \`name\` (short kebab-case) and \`description\` strings, optional \`whenToUse\` string and \`phases\` array (\`{title, detail?, provider?, model?}\`). The \`script\` parameter is the plain JavaScript body ONLY (NOT TypeScript, and NO \`export const meta\` statement — meta is a parameter, not code), running with top-level await; end with \`return <value>\` — the value must be JSON-serializable and is this tool's result.
97
+
98
+ Script-body hooks:
99
+ - \`agent(prompt, opts?): Promise<any>\` — run one subagent to completion. Without \`opts.schema\` it resolves to the child's final text; with \`opts.schema\` (an object-rooted JSON Schema using ONLY type/properties/required/additionalProperties/items/enum/const/oneOf — no pattern/format/numeric bounds) it resolves to the validated object. Resolves \`null\` when the child fails (filter with \`.filter(Boolean)\`). Other opts: \`label\` (display), \`phase\` (progress group), and independent \`provider\`/\`model\` LLM target overrides (either may be provided alone). Anything else (\`effort\`/\`isolation\`/\`agentType\`) is rejected loudly.
100
+ - \`pipeline(items, ...stages): Promise<any[]>\` — run each item through the stages independently with NO barrier between stages (prefer this for multi-stage work). Each stage receives \`(prev, item, index)\`. An ordinary stage throw drops that ITEM to \`null\` and skips its remaining stages.
101
+ - \`parallel(thunks): Promise<any[]>\` — run zero-argument functions concurrently and await ALL of them (a barrier; use only when a stage genuinely needs every prior result together). A throwing thunk resolves to \`null\`.
102
+ - \`phase(title)\` — start a progress phase; \`log(message)\` — narrate progress; \`args\` — the tool call's \`args\` input, verbatim.
103
+
104
+ Misused hooks (bad arguments, unknown options, unsupported schemas, tripped caps) throw errors that ALWAYS kill the script — they never dissolve into a per-item \`null\`.
105
+
106
+ Constraints: concurrency and total-agent caps apply; no filesystem, network, timers, or Node.js APIs are provided — the agents do the work, the script only coordinates them. The run executes in the foreground: this call returns when the whole script finishes.`;
107
+ /** The pending-state card: a generic card titled by the workflow's meta name. */
108
+ function presentWorkflowCall(args) {
109
+ return {
110
+ card: "generic",
111
+ title: `workflow: ${args.meta.name}`,
112
+ rawInput: args.script
113
+ };
114
+ }
115
+ /** The completed-state card: keep the pending title; render the result content as-is. */
116
+ function presentWorkflowResult(args, result) {
117
+ return { card: "generic" };
118
+ }
119
+ /** A non-`completed` stop reason means the script did not finish cleanly. */
120
+ function stopReasonError(result) {
121
+ switch (result.stopReason) {
122
+ case "completed": return;
123
+ case "cancelled": return `workflow run was cancelled${result.error !== void 0 ? ` (${result.error})` : ""}`;
124
+ case "error": return `workflow run failed: ${result.error ?? "unknown error"}`;
125
+ /* v8 ignore start -- defensive: WorkflowStopReason is a closed union, exhaustive by construction; a future variant fails here loudly */
126
+ default: return `workflow run ended abnormally (${String(result.stopReason)})`;
127
+ }
128
+ }
129
+ /** Render the run's outcome text: the meta name, agent count, and the JSON value (capped). */
130
+ function renderResult(name, agentsStarted, value, maxChars) {
131
+ const rendered = JSON.stringify(value, null, 2);
132
+ const clipped = rendered.length > maxChars ? `${rendered.slice(0, maxChars)}\n… [truncated: ${rendered.length - maxChars} more characters]` : rendered;
133
+ return `workflow "${name}" completed (${agentsStarted} agent${agentsStarted === 1 ? "" : "s"}).\nReturn value:\n${clipped}`;
134
+ }
135
+ function apply(ctx, config) {
136
+ const { toolName, maxResultChars } = config;
137
+ const recorder = createWorkflowRecorder(ctx);
138
+ ctx.systemPrompt.section({
139
+ name: `tool:${toolName}`,
140
+ order: ctx.systemPrompt.getSectionOrder("TOOL_WORKFLOW"),
141
+ text: `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.`
142
+ });
143
+ ctx.tools.register(defineTool({
144
+ name: toolName,
145
+ description: DESCRIPTION,
146
+ parameters: {
147
+ script: {
148
+ type: "string",
149
+ required: true,
150
+ description: "The plain-JS workflow script body (top-level await allowed; NO `export const meta` statement; end with `return <json-value>`)."
151
+ },
152
+ meta: {
153
+ type: "object",
154
+ additionalProperties: true,
155
+ required: true,
156
+ description: "The workflow identity block (plain JSON — never code).",
157
+ properties: {
158
+ name: {
159
+ type: "string",
160
+ required: true,
161
+ description: "Short kebab-case workflow name."
162
+ },
163
+ description: {
164
+ type: "string",
165
+ required: true,
166
+ description: "One-line description of what the workflow does."
167
+ },
168
+ whenToUse: {
169
+ type: "string",
170
+ description: "Optional guidance on when this workflow applies."
171
+ },
172
+ phases: {
173
+ type: "array",
174
+ description: "Optional phase declarations matched by phase() calls.",
175
+ items: {
176
+ type: "object",
177
+ additionalProperties: true,
178
+ properties: {
179
+ title: {
180
+ type: "string",
181
+ required: true,
182
+ description: "The phase title phase() calls match by exact string."
183
+ },
184
+ detail: {
185
+ type: "string",
186
+ description: "Optional one-line description of the phase."
187
+ },
188
+ provider: {
189
+ type: "string",
190
+ description: "Optional provider override this phase is expected to use."
191
+ },
192
+ model: {
193
+ type: "string",
194
+ description: "Optional model override this phase is expected to use."
195
+ }
196
+ }
197
+ }
198
+ }
199
+ }
200
+ },
201
+ args: {
202
+ type: "object",
203
+ additionalProperties: true,
204
+ description: "Optional JSON input exposed to the script as the `args` global (wrap a bare list as a field, e.g. {\"files\": [...]})."
205
+ }
206
+ },
207
+ output: {
208
+ schema: {
209
+ type: "object",
210
+ additionalProperties: false,
211
+ properties: {
212
+ runId: {
213
+ type: "string",
214
+ required: true
215
+ },
216
+ agentsStarted: {
217
+ type: "integer",
218
+ required: true
219
+ },
220
+ result: {
221
+ type: "json",
222
+ required: true
223
+ }
224
+ }
225
+ },
226
+ render: (args, value) => [{
227
+ type: "text",
228
+ text: renderResult(args.meta.name, value.agentsStarted, value.result, maxResultChars)
229
+ }]
230
+ },
231
+ async execute(args, exec) {
232
+ const parent = exec.agent;
233
+ if (!parent) throw new Error("workflow tool requires a calling agent (exec.agent was undefined)");
234
+ const run = ctx.workflowEngine.start({
235
+ script: args.script,
236
+ meta: args.meta,
237
+ ...args.args !== void 0 ? { args: args.args } : {},
238
+ parent,
239
+ signal: exec.signal
240
+ });
241
+ const recordsRun = exec.parent === void 0;
242
+ if (recordsRun) recorder.start(parent.session, run);
243
+ const onAbort = () => {
244
+ run.cancel("parent step aborted");
245
+ };
246
+ exec.signal.addEventListener("abort", onAbort, { once: true });
247
+ let result;
248
+ try {
249
+ result = await run.result;
250
+ const error = stopReasonError(result);
251
+ if (error !== void 0) throw new Error(error);
252
+ return {
253
+ runId: run.id,
254
+ agentsStarted: result.agentsStarted,
255
+ result: result.value
256
+ };
257
+ } finally {
258
+ exec.signal.removeEventListener("abort", onAbort);
259
+ try {
260
+ await run.dispose();
261
+ if (recordsRun) {
262
+ /* v8 ignore next -- WorkflowRun.result never rejects by contract, so result is assigned before finally. */
263
+ if (result === void 0) throw new Error("workflow run settled without a result");
264
+ recorder.finish(run.id, result.stopReason);
265
+ }
266
+ } finally {
267
+ if (recordsRun) recorder.abandon(run.id);
268
+ }
269
+ }
270
+ },
271
+ presentCall: (args) => presentWorkflowCall(args),
272
+ presentResult: (args, result) => presentWorkflowResult(args, result)
273
+ }));
274
+ }
275
+ //#endregion
276
+ export { Config, apply, inject, name };