@zaalipro/dsh-workflows 0.1.0-rc.12

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.
Files changed (229) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE.md +224 -0
  3. package/README.i18n.yaml +4 -0
  4. package/README.md +111 -0
  5. package/README.zh.md +111 -0
  6. package/cordis.patch.yml +10 -0
  7. package/docs/architecture.i18n.yaml +4 -0
  8. package/docs/architecture.md +177 -0
  9. package/docs/architecture.zh.md +177 -0
  10. package/docs/testing.i18n.yaml +4 -0
  11. package/docs/testing.md +125 -0
  12. package/docs/testing.zh.md +125 -0
  13. package/docs/user-guide.i18n.yaml +4 -0
  14. package/docs/user-guide.md +351 -0
  15. package/docs/user-guide.zh.md +351 -0
  16. package/lib/client-types/WorkflowMemberInspector.d.ts +28 -0
  17. package/lib/client-types/WorkflowMemberInspector.d.ts.map +1 -0
  18. package/lib/client-types/WorkflowMemberInspector.js +181 -0
  19. package/lib/client-types/WorkflowMemberInspector.js.map +1 -0
  20. package/lib/client-types/WorkflowRunPanel.d.ts +43 -0
  21. package/lib/client-types/WorkflowRunPanel.d.ts.map +1 -0
  22. package/lib/client-types/WorkflowRunPanel.js +130 -0
  23. package/lib/client-types/WorkflowRunPanel.js.map +1 -0
  24. package/lib/client-types/WorkflowsDashboard.d.ts +49 -0
  25. package/lib/client-types/WorkflowsDashboard.d.ts.map +1 -0
  26. package/lib/client-types/WorkflowsDashboard.js +942 -0
  27. package/lib/client-types/WorkflowsDashboard.js.map +1 -0
  28. package/lib/client-types/adapter.d.ts +60 -0
  29. package/lib/client-types/adapter.d.ts.map +1 -0
  30. package/lib/client-types/adapter.js +117 -0
  31. package/lib/client-types/adapter.js.map +1 -0
  32. package/lib/client-types/chat-renderer.d.ts +10 -0
  33. package/lib/client-types/chat-renderer.d.ts.map +1 -0
  34. package/lib/client-types/chat-renderer.js +11 -0
  35. package/lib/client-types/chat-renderer.js.map +1 -0
  36. package/lib/client-types/contract.d.ts +210 -0
  37. package/lib/client-types/contract.d.ts.map +1 -0
  38. package/lib/client-types/contract.js +37 -0
  39. package/lib/client-types/contract.js.map +1 -0
  40. package/lib/client-types/controller.d.ts +83 -0
  41. package/lib/client-types/controller.d.ts.map +1 -0
  42. package/lib/client-types/controller.js +705 -0
  43. package/lib/client-types/controller.js.map +1 -0
  44. package/lib/client-types/index.d.ts +19 -0
  45. package/lib/client-types/index.d.ts.map +1 -0
  46. package/lib/client-types/index.js +731 -0
  47. package/lib/client-types/index.js.map +1 -0
  48. package/lib/client-types/locales.d.ts +257 -0
  49. package/lib/client-types/locales.d.ts.map +1 -0
  50. package/lib/client-types/locales.js +270 -0
  51. package/lib/client-types/locales.js.map +1 -0
  52. package/lib/client-types/slot-components.d.ts +38 -0
  53. package/lib/client-types/slot-components.d.ts.map +1 -0
  54. package/lib/client-types/slot-components.js +22 -0
  55. package/lib/client-types/slot-components.js.map +1 -0
  56. package/lib/client-types/store.d.ts +69 -0
  57. package/lib/client-types/store.d.ts.map +1 -0
  58. package/lib/client-types/store.js +135 -0
  59. package/lib/client-types/store.js.map +1 -0
  60. package/lib/client-types/workflow-definition.d.ts +52 -0
  61. package/lib/client-types/workflow-definition.d.ts.map +1 -0
  62. package/lib/client-types/workflow-definition.js +135 -0
  63. package/lib/client-types/workflow-definition.js.map +1 -0
  64. package/lib/client.js +11065 -0
  65. package/lib/client.js.map +1 -0
  66. package/lib/compat-engine/index.js +1926 -0
  67. package/lib/compat-engine/index.js.map +1 -0
  68. package/lib/compat-engine/worker.cjs +1500 -0
  69. package/lib/compat-engine/worker.cjs.map +1 -0
  70. package/lib/typert.host.d.ts +3 -0
  71. package/lib/typert.host.js +1709 -0
  72. package/lib/typert.remote-client.d.ts +57 -0
  73. package/lib/typert.remote-client.js +1662 -0
  74. package/lib/types/commands/aliases.d.ts +4 -0
  75. package/lib/types/commands/aliases.d.ts.map +1 -0
  76. package/lib/types/commands/aliases.js +25 -0
  77. package/lib/types/commands/aliases.js.map +1 -0
  78. package/lib/types/commands/index.d.ts +66 -0
  79. package/lib/types/commands/index.d.ts.map +1 -0
  80. package/lib/types/commands/index.js +550 -0
  81. package/lib/types/commands/index.js.map +1 -0
  82. package/lib/types/commands/parser.d.ts +17 -0
  83. package/lib/types/commands/parser.d.ts.map +1 -0
  84. package/lib/types/commands/parser.js +45 -0
  85. package/lib/types/commands/parser.js.map +1 -0
  86. package/lib/types/config.d.ts +71 -0
  87. package/lib/types/config.d.ts.map +1 -0
  88. package/lib/types/config.js +109 -0
  89. package/lib/types/config.js.map +1 -0
  90. package/lib/types/index.d.ts +41 -0
  91. package/lib/types/index.d.ts.map +1 -0
  92. package/lib/types/index.js +483 -0
  93. package/lib/types/index.js.map +1 -0
  94. package/lib/types/invariant.d.ts +21 -0
  95. package/lib/types/invariant.d.ts.map +1 -0
  96. package/lib/types/invariant.js +85 -0
  97. package/lib/types/invariant.js.map +1 -0
  98. package/lib/types/registry/definition.d.ts +24 -0
  99. package/lib/types/registry/definition.d.ts.map +1 -0
  100. package/lib/types/registry/definition.js +189 -0
  101. package/lib/types/registry/definition.js.map +1 -0
  102. package/lib/types/registry/index.d.ts +100 -0
  103. package/lib/types/registry/index.d.ts.map +1 -0
  104. package/lib/types/registry/index.js +899 -0
  105. package/lib/types/registry/index.js.map +1 -0
  106. package/lib/types/registry/names.d.ts +19 -0
  107. package/lib/types/registry/names.d.ts.map +1 -0
  108. package/lib/types/registry/names.js +69 -0
  109. package/lib/types/registry/names.js.map +1 -0
  110. package/lib/types/registry/remote.d.ts +15 -0
  111. package/lib/types/registry/remote.d.ts.map +1 -0
  112. package/lib/types/registry/remote.js +128 -0
  113. package/lib/types/registry/remote.js.map +1 -0
  114. package/lib/types/registry/roots.d.ts +44 -0
  115. package/lib/types/registry/roots.d.ts.map +1 -0
  116. package/lib/types/registry/roots.js +118 -0
  117. package/lib/types/registry/roots.js.map +1 -0
  118. package/lib/types/registry/types.d.ts +62 -0
  119. package/lib/types/registry/types.d.ts.map +1 -0
  120. package/lib/types/registry/types.js +2 -0
  121. package/lib/types/registry/types.js.map +1 -0
  122. package/lib/types/registry/watchers.d.ts +70 -0
  123. package/lib/types/registry/watchers.d.ts.map +1 -0
  124. package/lib/types/registry/watchers.js +152 -0
  125. package/lib/types/registry/watchers.js.map +1 -0
  126. package/lib/types/remote-events.d.ts +42 -0
  127. package/lib/types/remote-events.d.ts.map +1 -0
  128. package/lib/types/remote-events.js +21 -0
  129. package/lib/types/remote-events.js.map +1 -0
  130. package/lib/types/run-recorder.d.ts +44 -0
  131. package/lib/types/run-recorder.d.ts.map +1 -0
  132. package/lib/types/run-recorder.js +356 -0
  133. package/lib/types/run-recorder.js.map +1 -0
  134. package/lib/types/supervisor/canned-validate.d.ts +25 -0
  135. package/lib/types/supervisor/canned-validate.d.ts.map +1 -0
  136. package/lib/types/supervisor/canned-validate.js +122 -0
  137. package/lib/types/supervisor/canned-validate.js.map +1 -0
  138. package/lib/types/supervisor/completion-notice.d.ts +49 -0
  139. package/lib/types/supervisor/completion-notice.d.ts.map +1 -0
  140. package/lib/types/supervisor/completion-notice.js +281 -0
  141. package/lib/types/supervisor/completion-notice.js.map +1 -0
  142. package/lib/types/supervisor/cursors.d.ts +68 -0
  143. package/lib/types/supervisor/cursors.d.ts.map +1 -0
  144. package/lib/types/supervisor/cursors.js +158 -0
  145. package/lib/types/supervisor/cursors.js.map +1 -0
  146. package/lib/types/supervisor/engine-compat.d.ts +48 -0
  147. package/lib/types/supervisor/engine-compat.d.ts.map +1 -0
  148. package/lib/types/supervisor/engine-compat.js +116 -0
  149. package/lib/types/supervisor/engine-compat.js.map +1 -0
  150. package/lib/types/supervisor/index.d.ts +183 -0
  151. package/lib/types/supervisor/index.d.ts.map +1 -0
  152. package/lib/types/supervisor/index.js +2040 -0
  153. package/lib/types/supervisor/index.js.map +1 -0
  154. package/lib/types/supervisor/parallel-compat.d.ts +36 -0
  155. package/lib/types/supervisor/parallel-compat.d.ts.map +1 -0
  156. package/lib/types/supervisor/parallel-compat.js +180 -0
  157. package/lib/types/supervisor/parallel-compat.js.map +1 -0
  158. package/lib/types/supervisor/remote.d.ts +25 -0
  159. package/lib/types/supervisor/remote.d.ts.map +1 -0
  160. package/lib/types/supervisor/remote.js +478 -0
  161. package/lib/types/supervisor/remote.js.map +1 -0
  162. package/lib/types/supervisor/storage/bounded-file.d.ts +41 -0
  163. package/lib/types/supervisor/storage/bounded-file.d.ts.map +1 -0
  164. package/lib/types/supervisor/storage/bounded-file.js +186 -0
  165. package/lib/types/supervisor/storage/bounded-file.js.map +1 -0
  166. package/lib/types/supervisor/storage/details-codec.d.ts +14 -0
  167. package/lib/types/supervisor/storage/details-codec.d.ts.map +1 -0
  168. package/lib/types/supervisor/storage/details-codec.js +359 -0
  169. package/lib/types/supervisor/storage/details-codec.js.map +1 -0
  170. package/lib/types/supervisor/storage/index.d.ts +26 -0
  171. package/lib/types/supervisor/storage/index.d.ts.map +1 -0
  172. package/lib/types/supervisor/storage/index.js +100 -0
  173. package/lib/types/supervisor/storage/index.js.map +1 -0
  174. package/lib/types/supervisor/storage/lease.d.ts +34 -0
  175. package/lib/types/supervisor/storage/lease.d.ts.map +1 -0
  176. package/lib/types/supervisor/storage/lease.js +289 -0
  177. package/lib/types/supervisor/storage/lease.js.map +1 -0
  178. package/lib/types/supervisor/storage/manifest-codec.d.ts +10 -0
  179. package/lib/types/supervisor/storage/manifest-codec.d.ts.map +1 -0
  180. package/lib/types/supervisor/storage/manifest-codec.js +335 -0
  181. package/lib/types/supervisor/storage/manifest-codec.js.map +1 -0
  182. package/lib/types/supervisor/storage/manifest-store.d.ts +99 -0
  183. package/lib/types/supervisor/storage/manifest-store.d.ts.map +1 -0
  184. package/lib/types/supervisor/storage/manifest-store.js +1606 -0
  185. package/lib/types/supervisor/storage/manifest-store.js.map +1 -0
  186. package/lib/types/supervisor/storage/manifest-types.d.ts +269 -0
  187. package/lib/types/supervisor/storage/manifest-types.d.ts.map +1 -0
  188. package/lib/types/supervisor/storage/manifest-types.js +2 -0
  189. package/lib/types/supervisor/storage/manifest-types.js.map +1 -0
  190. package/lib/types/supervisor/storage/private-root.d.ts +143 -0
  191. package/lib/types/supervisor/storage/private-root.d.ts.map +1 -0
  192. package/lib/types/supervisor/storage/private-root.js +860 -0
  193. package/lib/types/supervisor/storage/private-root.js.map +1 -0
  194. package/lib/types/supervisor/storage/recovery.d.ts +14 -0
  195. package/lib/types/supervisor/storage/recovery.d.ts.map +1 -0
  196. package/lib/types/supervisor/storage/recovery.js +126 -0
  197. package/lib/types/supervisor/storage/recovery.js.map +1 -0
  198. package/lib/types/supervisor/storage/run-files.d.ts +67 -0
  199. package/lib/types/supervisor/storage/run-files.d.ts.map +1 -0
  200. package/lib/types/supervisor/storage/run-files.js +346 -0
  201. package/lib/types/supervisor/storage/run-files.js.map +1 -0
  202. package/lib/types/supervisor/types.d.ts +295 -0
  203. package/lib/types/supervisor/types.d.ts.map +1 -0
  204. package/lib/types/supervisor/types.js +3 -0
  205. package/lib/types/supervisor/types.js.map +1 -0
  206. package/lib/types/supervisor/value-view.d.ts +19 -0
  207. package/lib/types/supervisor/value-view.d.ts.map +1 -0
  208. package/lib/types/supervisor/value-view.js +183 -0
  209. package/lib/types/supervisor/value-view.js.map +1 -0
  210. package/lib/types/tool/index.d.ts +86 -0
  211. package/lib/types/tool/index.d.ts.map +1 -0
  212. package/lib/types/tool/index.js +810 -0
  213. package/lib/types/tool/index.js.map +1 -0
  214. package/lib/types/tool/schema.d.ts +158 -0
  215. package/lib/types/tool/schema.d.ts.map +1 -0
  216. package/lib/types/tool/schema.js +137 -0
  217. package/lib/types/tool/schema.js.map +1 -0
  218. package/lib/types/types.d.ts +9 -0
  219. package/lib/types/types.d.ts.map +1 -0
  220. package/lib/types/types.js +3 -0
  221. package/lib/types/types.js.map +1 -0
  222. package/lib/types/user-questions.d.ts +9 -0
  223. package/lib/types/user-questions.d.ts.map +1 -0
  224. package/lib/types/user-questions.js +91 -0
  225. package/lib/types/user-questions.js.map +1 -0
  226. package/package.json +291 -0
  227. package/skills/create-workflow/SKILL.md +166 -0
  228. package/vendor/workflow-engine/LICENSE +21 -0
  229. package/vendor/workflow-engine/README.md +1 -0
@@ -0,0 +1,810 @@
1
+ import { parseWorkflowDefinition, validateDefinitionEnvelope } from '../registry/definition.js';
2
+ import { repairObjectLiteralSemicolons } from '../supervisor/canned-validate.js';
3
+ import { VALIDATION_NOTE } from '../supervisor/index.js';
4
+ import { FsError } from '@deepseek-ai/dsh-fs';
5
+ import { assertNever } from '@deepseek-ai/dsh-util-values';
6
+ import { defineTool } from '@deepseek-ai/dsh-tools';
7
+ import { constants as fsConstants } from 'node:fs';
8
+ import { lstat as localLstat, open as localOpen } from 'node:fs/promises';
9
+ import { isAbsolute as isLocalAbsolute, resolve as resolveLocalPath } from 'node:path';
10
+ import { pathToFileURL } from 'node:url';
11
+ import { parseWorkflowToolRequest, WORKFLOW_TOOL_PARAMETERS, WORKFLOW_TOOL_SCHEMA } from './schema.js';
12
+ export * from './schema.js';
13
+ export { VALIDATION_NOTE };
14
+ /**
15
+ * Opaque contribution identities for a Host that exposes atomic replacement.
16
+ * Until the Host exports these exact objects, identity matching uses this
17
+ * package freeze plus an explicit official marker; a same-name custom tool is
18
+ * never treated as official.
19
+ */
20
+ export const WORKFLOW_TOOL_DEFINITION = Object.freeze({
21
+ name: 'workflow',
22
+ description: 'official DeepSeek Harness workflow tool',
23
+ parameters: WORKFLOW_TOOL_SCHEMA,
24
+ });
25
+ export const WORKFLOW_PROMPT_SECTION = Object.freeze({
26
+ name: 'tool:workflow',
27
+ order: 115,
28
+ text: 'official DeepSeek Harness workflow guidance',
29
+ });
30
+ const OFFICIAL_MARKER = Symbol.for('deepseek-harness.workflow.official-contribution');
31
+ const MAX_RESULT_CHARS = 50_000;
32
+ const MISSING_AGENT = 'workflow tool requires a calling agent (exec.agent was undefined)';
33
+ const MISSING_SHADOW = 'workflow package requires either verified atomic replacement seams or Agent-scoped tools.register and systemPrompt.section';
34
+ const MISSING_FS = 'workflow script_path requires either Host fs.readBytesNoFollow or the published RC2 local filesystem capability';
35
+ /*
36
+ * Stock 0.1.6-alpha.1 predates the opaque contribution marker and atomic
37
+ * replacement seam. Its public registry deliberately supports an Agent-local
38
+ * definition shadowing a global definition with the same name, but it exposes
39
+ * no package provenance for that global. Match the complete, distinctive
40
+ * public stock schema plus stable description boundaries before using that
41
+ * public fallback. A same-name custom tool does not pass this check. A future
42
+ * verified atomic seam takes the identity/marker compare-and-swap route below.
43
+ */
44
+ const STOCK_DESCRIPTION_START = 'Run a JavaScript workflow script that orchestrates subagents at scale.';
45
+ const STOCK_DESCRIPTION_END = 'The run executes in the foreground: this call returns when the whole script finishes.';
46
+ function ownKeysExactly(value, expected) {
47
+ if (typeof value !== 'object' || value === null || Array.isArray(value))
48
+ return false;
49
+ const actual = Object.keys(value).sort();
50
+ const wanted = [...expected].sort();
51
+ return actual.length === wanted.length && actual.every((key, index) => key === wanted[index]);
52
+ }
53
+ function stringSetExactly(value, expected) {
54
+ if (!Array.isArray(value) || value.some(item => typeof item !== 'string'))
55
+ return false;
56
+ const actual = [...value].sort();
57
+ const wanted = [...expected].sort();
58
+ return actual.length === wanted.length && actual.every((key, index) => key === wanted[index]);
59
+ }
60
+ /** Public-fingerprint check used only for stock's Agent-scoped shadow seam. */
61
+ export function isStockOfficialWorkflowTool(value) {
62
+ if (typeof value !== 'object' || value === null)
63
+ return false;
64
+ const tool = value;
65
+ if (tool.name !== 'workflow' || typeof tool.description !== 'string'
66
+ || !tool.description.startsWith(STOCK_DESCRIPTION_START)
67
+ || !tool.description.endsWith(STOCK_DESCRIPTION_END)
68
+ || typeof tool.execute !== 'function')
69
+ return false;
70
+ const parameters = tool.parameters;
71
+ if (!ownKeysExactly(parameters, ['type', 'properties', 'required'])
72
+ || parameters.type !== 'object'
73
+ || !stringSetExactly(parameters.required, ['script', 'meta'])
74
+ || !ownKeysExactly(parameters.properties, ['script', 'meta', 'args']))
75
+ return false;
76
+ const script = parameters.properties.script;
77
+ const meta = parameters.properties.meta;
78
+ const args = parameters.properties.args;
79
+ if (script?.type !== 'string' || meta?.type !== 'object' || meta?.additionalProperties !== true
80
+ || args?.type !== 'object' || args?.additionalProperties !== true
81
+ || !ownKeysExactly(meta.properties, ['name', 'description', 'whenToUse', 'phases'])
82
+ || !stringSetExactly(meta.required, ['name', 'description'])
83
+ || meta.properties.name?.type !== 'string'
84
+ || meta.properties.description?.type !== 'string'
85
+ || meta.properties.whenToUse?.type !== 'string')
86
+ return false;
87
+ const phase = meta.properties.phases;
88
+ const phaseItem = phase?.items;
89
+ if (phase?.type !== 'array' || phaseItem?.type !== 'object'
90
+ || phaseItem.additionalProperties !== true
91
+ || !ownKeysExactly(phaseItem.properties, ['title', 'detail', 'provider', 'model'])
92
+ || !stringSetExactly(phaseItem.required, ['title'])
93
+ || Object.values(phaseItem.properties).some((field) => field?.type !== 'string'))
94
+ return false;
95
+ const output = tool.output;
96
+ const outputSchema = output?.schema;
97
+ return typeof output?.render === 'function'
98
+ && ownKeysExactly(outputSchema, ['type', 'additionalProperties', 'properties', 'required'])
99
+ && outputSchema.type === 'object'
100
+ && outputSchema.additionalProperties === false
101
+ && ownKeysExactly(outputSchema.properties, ['runId', 'agentsStarted', 'result'])
102
+ && stringSetExactly(outputSchema.required, ['runId', 'agentsStarted', 'result'])
103
+ && outputSchema.properties.runId?.type === 'string'
104
+ && outputSchema.properties.agentsStarted?.type === 'integer';
105
+ }
106
+ 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.
107
+
108
+ Supply EXACTLY ONE source: \`name\` (a saved workflow in .dsh/workflows), \`script\` (an inline plain-JS body, plus the required \`meta\` object), or \`script_path\` (a .workflow.json envelope or a script file on disk, plus \`meta\` for a bare file).
109
+
110
+ The workflow's identity rides \`meta\` as JSON: required \`name\` (short kebab-case) and \`description\` strings, optional \`whenToUse\` string and \`phases\` array (\`{title, detail?, provider?, model?}\`). The script body is plain JavaScript ONLY (NOT TypeScript, and NO \`export const meta\` statement — meta is data beside the body), running with top-level await.
111
+
112
+ Script-body hooks:
113
+ - \`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/minItems/maxItems/enum/const/oneOf — no pattern, format, minimum/maximum, or other keywords) it resolves to the validated object. \`minItems\` and \`maxItems\` are inclusive array-length bounds: each must be a non-negative safe integer (not \`-0\`), may appear only on a \`type: "array"\` node, must satisfy \`minItems <= maxItems\`, and is forbidden beside \`oneOf\`. The package validates these declarations before child launch, removes only those two keywords from the copy sent to stock RC2, and post-validates the structured child result against the authored bounds. Resolves \`null\` when the child fails or returns a schema-invalid value (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 is rejected loudly.
114
+ - \`parallel(items): Promise<any[]>\` — run zero-argument functions OR job maps \`{prompt, label?, phase?, schema?, provider?, model?}\` concurrently and await ALL (a barrier). Failed slots resolve to \`null\`.
115
+ - \`pipeline(items, ...stages): Promise<any[]>\` — run each item through the stages independently with NO barrier between stages. Each stage receives \`(prev, item, index)\`.
116
+ - \`phase(title)\` — start a progress phase; \`log(message)\` — narrate progress; \`args\` — the tool call's \`args\` input, verbatim.
117
+ - \`complete(value)\` — end the run successfully with a JSON value (first call wins). Stock workers have no native \`complete\`; this package injects one. \`return value\` also settles the run.
118
+ - \`await_user(kind, message)\` — park the run for a human answer; resume continues past it. \`pause(kind, message)\` — park a run for a condition resume cannot change; resume re-fires it. Kinds: user, back_off, no_progress, verification, infra.
119
+ - \`budget(): { total, spent, reserved, remaining }\` — this run's logical agent budget. \`write_scratch_file(name, content)\` / \`read_scratch_file(name)\` — per-run scratch storage (single-component names).
120
+
121
+ 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\`.
122
+
123
+ Launch is BACKGROUND for a saved definition (\`name\` / \`script_path\`): the call returns immediately with \`{ displayName, runId, script_path, status: "started" }\`. Inline \`script\` authoring DEFAULTS to \`validate_only: true\` (canned stubs, no children, milliseconds) and SAVES on a successful smoke. \`save_scope\` selects \`project\` (default, \`.dsh/workflows\`) or \`user\` (\`$DSH_HOME/workflows\`); it is valid only for this inline validate-only save. Pass \`validate_only: false\` only to launch that inline script live. Object literals are plain JavaScript (commas, never semicolons — \`{ a: 1, b: 2 }\` not \`{ a: 1; b: 2 }\`). To resume a paused, needs-input, or budget-limited run, supply only \`resume_from_run_id\` and optionally a higher \`agent_budget\` for a budget-limited run.`;
124
+ const PROMPT_TEXT = 'Use the workflow 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. Inline script + meta defaults to validate_only and saves to save_scope project (default) or user — do not pass validate_only: false while authoring, and never put semicolons between object fields. For one or two delegations, prefer plain subagent calls.';
125
+ function officialContribution(value) {
126
+ if (typeof value !== 'object' || value === null)
127
+ return false;
128
+ const record = value;
129
+ return record[OFFICIAL_MARKER] === true || record.__officialWorkflowContribution === true;
130
+ }
131
+ /** Verified identity/marker or stock fingerprint; custom same-name tools are not official. */
132
+ export function isOfficialWorkflowTool(definition) {
133
+ return definition === WORKFLOW_TOOL_DEFINITION
134
+ || officialContribution(definition)
135
+ || isStockOfficialWorkflowTool(definition);
136
+ }
137
+ function isOfficialWorkflowPrompt(section) {
138
+ return section === WORKFLOW_PROMPT_SECTION || officialContribution(section);
139
+ }
140
+ function visibleWorkflowTool(agent) {
141
+ const tools = agent?.ctx?.tools;
142
+ if (typeof tools?.get === 'function') {
143
+ try {
144
+ return tools.get('workflow', agent) ?? tools.get('workflow');
145
+ }
146
+ catch { /* inspect other faces */ }
147
+ }
148
+ if (typeof tools?.list === 'function') {
149
+ try {
150
+ const list = tools.list(agent);
151
+ return Array.isArray(list) ? list.find((item) => item?.name === 'workflow') : undefined;
152
+ }
153
+ catch { /* absent on minimal fixtures */ }
154
+ }
155
+ return tools?.workflow;
156
+ }
157
+ function visibleWorkflowPrompt(agent) {
158
+ const prompt = agent?.ctx?.systemPrompt;
159
+ if (typeof prompt?.get === 'function') {
160
+ try {
161
+ return prompt.get('tool:workflow');
162
+ }
163
+ catch { /* absent */ }
164
+ }
165
+ if (typeof prompt?.section === 'function') {
166
+ try {
167
+ return prompt.section('tool:workflow');
168
+ }
169
+ catch { /* absent */ }
170
+ }
171
+ return prompt?.sections?.get?.('tool:workflow') ?? prompt?.workflowSection;
172
+ }
173
+ function presentWorkflowCall(args) {
174
+ const title = args.name ?? (args.meta && typeof args.meta.name === 'string' ? args.meta.name : 'workflow');
175
+ return {
176
+ card: 'generic',
177
+ title: `workflow: ${title}`,
178
+ ...(args.script !== undefined ? { rawInput: args.script } : {}),
179
+ };
180
+ }
181
+ function presentWorkflowResult(_args, _result) {
182
+ return { card: 'generic' };
183
+ }
184
+ /** Render the launch/validate outcome for the tool result. */
185
+ export function renderLaunch(value, maxChars = MAX_RESULT_CHARS) {
186
+ switch (value.status) {
187
+ case 'validated': {
188
+ const rendered = JSON.stringify(value.result ?? null, null, 2);
189
+ const clipped = rendered.length > maxChars ? `${rendered.slice(0, maxChars)}\n… [truncated]` : rendered;
190
+ const saved = typeof value.saved_path === 'string' && value.saved_path.length > 0
191
+ ? `Saved ${value.saved_path}. Launch with /workflow <name> (do not start children unless the user asks).\n`
192
+ : 'No new workflow definition was saved by this validation.\n';
193
+ return `workflow smoke check passed.\n${VALIDATION_NOTE}\n${saved}Result:\n${clipped}`;
194
+ }
195
+ case 'started':
196
+ case 'resumed':
197
+ return JSON.stringify(value);
198
+ default:
199
+ return assertNever(value, 'workflow tool output');
200
+ }
201
+ }
202
+ function replacementPrompt(official) {
203
+ return {
204
+ ...official,
205
+ name: 'tool:workflow',
206
+ order: typeof official?.order === 'number' ? official.order : 115,
207
+ text: PROMPT_TEXT,
208
+ };
209
+ }
210
+ /** Shadow one Agent via atomic CAS or rollback-safe scoped registrations. */
211
+ export function installWorkflowShadow(agent, servicesOrTool) {
212
+ const ctx = agent?.ctx;
213
+ const tools = ctx?.tools;
214
+ const systemPrompt = ctx?.systemPrompt;
215
+ const isServices = typeof servicesOrTool === 'object' && servicesOrTool?.registry !== undefined;
216
+ const suppliedReplacement = !isServices && typeof servicesOrTool === 'object' && servicesOrTool?.name === 'workflow';
217
+ const visible = visibleWorkflowTool(agent);
218
+ const candidateVisible = visible ?? (!suppliedReplacement ? servicesOrTool : undefined);
219
+ if (!isOfficialWorkflowTool(candidateVisible))
220
+ return () => undefined;
221
+ const services = isServices ? servicesOrTool : ctx?.workflowToolServices;
222
+ if (services === undefined && !suppliedReplacement)
223
+ return () => undefined;
224
+ const replacement = suppliedReplacement ? servicesOrTool : createWorkflowTool({
225
+ ...services,
226
+ fs: services.fs ?? ctx?.fs,
227
+ });
228
+ // Replace only exact visible official objects and restore both contributions
229
+ // atomically. This remains the stronger preferred seam when available.
230
+ if (typeof tools?.replace === 'function' && typeof systemPrompt?.replaceSection === 'function') {
231
+ const promptVisible = visibleWorkflowPrompt(agent);
232
+ if (promptVisible === undefined || !isOfficialWorkflowPrompt(promptVisible))
233
+ return () => undefined;
234
+ let restoreTool;
235
+ try {
236
+ restoreTool = tools.replace('workflow', candidateVisible, replacement);
237
+ if (typeof restoreTool !== 'function')
238
+ throw new Error('workflow tool replacement did not return a disposer');
239
+ const restorePrompt = systemPrompt.replaceSection('tool:workflow', promptVisible, replacementPrompt(promptVisible));
240
+ if (typeof restorePrompt !== 'function')
241
+ throw new Error('workflow prompt replacement did not return a disposer');
242
+ let disposed = false;
243
+ return () => {
244
+ if (disposed)
245
+ return;
246
+ disposed = true;
247
+ try {
248
+ restorePrompt();
249
+ }
250
+ finally {
251
+ restoreTool?.();
252
+ }
253
+ };
254
+ }
255
+ catch (error) {
256
+ try {
257
+ restoreTool?.();
258
+ }
259
+ catch { /* preserve original failure */ }
260
+ throw error;
261
+ }
262
+ }
263
+ // Stock 0.1.6-alpha.1: the ToolRuntime and SystemPrompt public APIs scope a
264
+ // registration made through agent.ctx to that exact Agent. Such a local
265
+ // contribution shadows the stock global one and its prompt section. Never
266
+ // use this route for a marker-only contribution: without CAS that would
267
+ // weaken the exact-identity guarantee.
268
+ if (!isStockOfficialWorkflowTool(candidateVisible))
269
+ throw new Error(MISSING_SHADOW);
270
+ if (typeof tools?.register !== 'function' || typeof systemPrompt?.section !== 'function') {
271
+ throw new Error(MISSING_SHADOW);
272
+ }
273
+ let restoreTool;
274
+ try {
275
+ restoreTool = tools.register(replacement);
276
+ if (typeof restoreTool !== 'function')
277
+ throw new Error('workflow Agent-scoped tool registration did not return a disposer');
278
+ const restorePrompt = systemPrompt.section({ name: 'tool:workflow', order: 115, text: PROMPT_TEXT });
279
+ if (typeof restorePrompt !== 'function')
280
+ throw new Error('workflow Agent-scoped prompt registration did not return a disposer');
281
+ let disposed = false;
282
+ return () => {
283
+ if (disposed)
284
+ return;
285
+ disposed = true;
286
+ try {
287
+ restorePrompt();
288
+ }
289
+ finally {
290
+ restoreTool?.();
291
+ }
292
+ };
293
+ }
294
+ catch (error) {
295
+ try {
296
+ restoreTool?.();
297
+ }
298
+ catch { /* preserve original failure */ }
299
+ throw error;
300
+ }
301
+ }
302
+ /** Reconcile exact-Agent shadows on Agent/tool lifecycle changes. */
303
+ export function applyToolShadow(ctx, config = {}) {
304
+ if (config.enabled === false)
305
+ return;
306
+ const services = config.services ?? {
307
+ registry: ctx.workflows,
308
+ supervisor: ctx.workflowSupervisor,
309
+ recorder: ctx.workflowRunRecorder,
310
+ fs: ctx.fs,
311
+ };
312
+ if (services.registry === undefined || services.supervisor === undefined)
313
+ throw new Error('workflow tool shadow services are unavailable');
314
+ const resolved = { ...services, fs: services.fs ?? ctx.fs };
315
+ const states = new Map();
316
+ // Stock ToolRuntime emits one unscoped tools/change for every scoped
317
+ // registration and removal. Suppress the whole package-owned mutation,
318
+ // rather than only the Agent being reconciled: an A mutation must not queue
319
+ // B, whose mutation would otherwise queue A again on a multi-Agent Host.
320
+ let internalMutations = 0;
321
+ const mutate = (operation) => {
322
+ internalMutations += 1;
323
+ try {
324
+ operation();
325
+ }
326
+ finally {
327
+ internalMutations -= 1;
328
+ }
329
+ };
330
+ const reconcile = (agent) => {
331
+ const state = states.get(agent);
332
+ if (state === undefined || state.disposed)
333
+ return;
334
+ mutate(() => {
335
+ try {
336
+ state.restore?.();
337
+ }
338
+ catch { /* restoration is best-effort during churn */ }
339
+ state.restore = undefined;
340
+ const visible = visibleWorkflowTool(agent);
341
+ try {
342
+ if (isOfficialWorkflowTool(visible))
343
+ state.restore = installWorkflowShadow(agent, resolved);
344
+ }
345
+ catch (error) {
346
+ ctx.logger?.warn?.(`workflow tool shadow failed: ${error instanceof Error ? error.message : String(error)}`);
347
+ }
348
+ });
349
+ };
350
+ const schedule = (agent) => {
351
+ const state = states.get(agent);
352
+ if (state === undefined || state.queued || state.disposed)
353
+ return;
354
+ state.queued = true;
355
+ queueMicrotask(() => { state.queued = false; if (!state.disposed)
356
+ reconcile(agent); });
357
+ };
358
+ const add = (agent) => {
359
+ if (agent === undefined || states.has(agent))
360
+ return;
361
+ states.set(agent, { queued: false, disposed: false });
362
+ reconcile(agent);
363
+ };
364
+ const remove = (agent) => {
365
+ const state = states.get(agent);
366
+ if (state === undefined)
367
+ return;
368
+ state.disposed = true;
369
+ mutate(() => { try {
370
+ state.restore?.();
371
+ }
372
+ catch { /* contained */ } });
373
+ state.restore = undefined;
374
+ states.delete(agent);
375
+ };
376
+ for (const agent of (Array.isArray(ctx.agents?.list?.()) ? ctx.agents.list() : []))
377
+ add(agent);
378
+ const cleanups = [];
379
+ for (const [event, handler] of [['agent/created', (event) => add(event?.agent ?? event)], ['agent/disposed', (event) => remove(event?.agent ?? event)]]) {
380
+ const dispose = ctx.on?.(event, handler);
381
+ if (typeof dispose === 'function')
382
+ cleanups.push(dispose);
383
+ }
384
+ const toolsChanged = () => {
385
+ if (internalMutations > 0)
386
+ return;
387
+ for (const agent of states.keys())
388
+ schedule(agent);
389
+ };
390
+ const changed = ctx.on?.('tools/change', toolsChanged);
391
+ if (typeof changed === 'function')
392
+ cleanups.push(changed);
393
+ let disposed = false;
394
+ const dispose = () => {
395
+ if (disposed)
396
+ return;
397
+ disposed = true;
398
+ for (const agent of [...states.keys()])
399
+ remove(agent);
400
+ for (const cleanup of cleanups.splice(0)) {
401
+ try {
402
+ cleanup();
403
+ }
404
+ catch { /* contained */ }
405
+ }
406
+ };
407
+ if (typeof ctx.effect === 'function')
408
+ ctx.effect(() => dispose, 'dsh-workflows: tool shadow');
409
+ return dispose;
410
+ }
411
+ function boundedLimit(value) {
412
+ const limit = value ?? 1_048_576;
413
+ if (!Number.isSafeInteger(limit) || limit < 1 || limit > 1_048_576) {
414
+ throw new RangeError('workflow definitionMaxBytes must be a safe integer from 1 through 1048576');
415
+ }
416
+ return limit;
417
+ }
418
+ function fsErrorCode(error) {
419
+ if (error instanceof FsError)
420
+ return error.code;
421
+ if (typeof error === 'object' && error !== null && 'code' in error) {
422
+ const code = error.code;
423
+ return typeof code === 'string' ? code : undefined;
424
+ }
425
+ }
426
+ function translateScriptPathError(path, error, maxBytes) {
427
+ const code = fsErrorCode(error);
428
+ if (code === 'FS_NOT_FOUND' || code === 'ENOENT') {
429
+ throw new Error(`workflow script_path "${path}" was not found`, { cause: error });
430
+ }
431
+ if (code === 'FS_NOT_REGULAR_FILE') {
432
+ throw new Error(`workflow script_path "${path}" must be a regular file and must not be a symbolic link`, { cause: error });
433
+ }
434
+ if (code === 'FS_TOO_LARGE') {
435
+ throw new Error(`workflow script_path "${path}" exceeds the ${maxBytes}-byte limit`, { cause: error });
436
+ }
437
+ throw error;
438
+ }
439
+ function rc2PathInfo(value) {
440
+ if (typeof value !== 'object' || value === null)
441
+ return undefined;
442
+ const info = value;
443
+ if (!['file', 'directory', 'symlink', 'other'].includes(String(info.type)))
444
+ return undefined;
445
+ if (info.size !== undefined && (!Number.isSafeInteger(info.size) || info.size < 0))
446
+ return undefined;
447
+ return info;
448
+ }
449
+ function rc2Target(value) {
450
+ if (typeof value !== 'object' || value === null)
451
+ return undefined;
452
+ const target = value;
453
+ if (typeof target.targetKey !== 'string' || typeof target.displayPath !== 'string')
454
+ return undefined;
455
+ return target;
456
+ }
457
+ function sameLocalIdentity(left, right) {
458
+ return left.dev === right.dev && left.ino === right.ino;
459
+ }
460
+ function sameLocalSnapshot(left, right) {
461
+ return sameLocalIdentity(left, right)
462
+ && left.size === right.size
463
+ && left.mtimeNs === right.mtimeNs
464
+ && left.ctimeNs === right.ctimeNs;
465
+ }
466
+ function rc2Abort(signal) {
467
+ if (signal?.aborted)
468
+ throw new FsError('workflow script_path read aborted', 'FS_ABORTED');
469
+ }
470
+ function localReadError(path, error) {
471
+ const code = fsErrorCode(error);
472
+ if (code === 'ENOENT' || code === 'ENOTDIR') {
473
+ throw new FsError(`cannot read "${path}": not found`, 'FS_NOT_FOUND', { cause: error });
474
+ }
475
+ if (code === 'ELOOP' || code === 'EISDIR') {
476
+ throw new FsError(`cannot read "${path}": not a regular file or is a symbolic link`, 'FS_NOT_REGULAR_FILE', { cause: error });
477
+ }
478
+ throw error;
479
+ }
480
+ /**
481
+ * Published 0.1.6-alpha.1 has resolve/lstat/processPath/readBytes but not the
482
+ * later path-shaped readBytesNoFollow method. For that exact local service
483
+ * shape, authorize and normalize through the Host, then open the Host's
484
+ * lexical display path with O_NOFOLLOW and read a bounded snapshot from the
485
+ * retained descriptor. Unknown/remote providers fail closed rather than
486
+ * treating an execution-world process path as a path on this Node host.
487
+ */
488
+ async function readPublishedRc2LocalBytes(fs, path, cwd, signal, maxBytes) {
489
+ /* c8 ignore next -- stock RC2 compatibility reads require POSIX O_NOFOLLOW. */
490
+ if (process.platform === 'win32' || typeof fsConstants.O_NOFOLLOW !== 'number'
491
+ || fsConstants.O_NOFOLLOW === 0)
492
+ throw new Error(MISSING_FS);
493
+ if (typeof fs.resolve !== 'function' || typeof fs.lstat !== 'function'
494
+ || typeof fs.processPath !== 'function' || typeof fs.fileUrl !== 'function'
495
+ || typeof fs.readBytes !== 'function')
496
+ throw new Error(MISSING_FS);
497
+ rc2Abort(signal);
498
+ const options = cwd === undefined ? {} : { cwd };
499
+ const first = rc2PathInfo(await fs.lstat(path, options, signal));
500
+ rc2Abort(signal);
501
+ if (first === undefined)
502
+ throw new FsError(`cannot read "${path}": not found`, 'FS_NOT_FOUND');
503
+ if (first.type !== 'file') {
504
+ throw new FsError(`cannot read "${path}": not a regular file or is a symbolic link`, 'FS_NOT_REGULAR_FILE');
505
+ }
506
+ if (first.size !== undefined && first.size > maxBytes) {
507
+ throw new FsError(`cannot read "${path}": exceeds ${maxBytes} bytes`, 'FS_TOO_LARGE');
508
+ }
509
+ const target = rc2Target(await fs.resolve(path, { ...options, ...(signal === undefined ? {} : { signal }) }));
510
+ rc2Abort(signal);
511
+ if (target === undefined || !isLocalAbsolute(target.displayPath))
512
+ throw new Error(MISSING_FS);
513
+ // These are the published local/sandbox-local RC2 invariants. In
514
+ // particular, targetKey/processPath is a canonical local path while
515
+ // displayPath preserves the lexical Session-world path that must be opened
516
+ // no-follow. Requiring all of them prevents accidental use on remote fs
517
+ // providers whose execution-world path is not on this Node host.
518
+ const expectedDisplay = resolveLocalPath(cwd ?? process.cwd(), path);
519
+ let processPath;
520
+ let fileUrl;
521
+ try {
522
+ processPath = fs.processPath(target);
523
+ fileUrl = fs.fileUrl(target);
524
+ }
525
+ catch {
526
+ throw new Error(MISSING_FS);
527
+ }
528
+ if (target.displayPath !== expectedDisplay || target.targetKey !== processPath
529
+ || !isLocalAbsolute(processPath) || fileUrl !== pathToFileURL(processPath).href) {
530
+ throw new Error(MISSING_FS);
531
+ }
532
+ let handle;
533
+ try {
534
+ rc2Abort(signal);
535
+ handle = await localOpen(target.displayPath, fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW);
536
+ rc2Abort(signal);
537
+ const before = await handle.stat({ bigint: true });
538
+ if (!before.isFile()) {
539
+ throw new FsError(`cannot read "${path}": not a regular file`, 'FS_NOT_REGULAR_FILE');
540
+ }
541
+ if (before.size > BigInt(maxBytes)) {
542
+ throw new FsError(`cannot read "${path}": exceeds ${maxBytes} bytes`, 'FS_TOO_LARGE');
543
+ }
544
+ // Confirm that the descriptor opened from the lexical no-follow path is
545
+ // the same local identity authorized by the Host target.
546
+ const canonical = await localLstat(processPath, { bigint: true });
547
+ if (!canonical.isFile() || !sameLocalIdentity(before, canonical)) {
548
+ throw new FsError(`cannot read "${path}": path identity changed during authorization`, 'FS_IO_ERROR');
549
+ }
550
+ // Allocate only maxBytes + the one byte needed to distinguish an exact-cap
551
+ // file from a growing/oversized file. FileHandle.read pins the opened file;
552
+ // it never re-opens the user-controlled path.
553
+ const buffer = new Uint8Array(maxBytes + 1);
554
+ let offset = 0;
555
+ while (offset < buffer.byteLength) {
556
+ rc2Abort(signal);
557
+ const result = await handle.read(buffer, offset, buffer.byteLength - offset, null);
558
+ if (result.bytesRead === 0)
559
+ break;
560
+ offset += result.bytesRead;
561
+ }
562
+ rc2Abort(signal);
563
+ if (offset > maxBytes)
564
+ throw new FsError(`cannot read "${path}": exceeds ${maxBytes} bytes`, 'FS_TOO_LARGE');
565
+ const after = await handle.stat({ bigint: true });
566
+ const finalPath = await localLstat(target.displayPath, { bigint: true });
567
+ const finalHost = rc2PathInfo(await fs.lstat(path, options, signal));
568
+ rc2Abort(signal);
569
+ if (!sameLocalSnapshot(before, after) || !finalPath.isFile()
570
+ || !sameLocalIdentity(before, finalPath)
571
+ || finalHost === undefined || finalHost.type !== 'file'
572
+ || finalHost.version !== first.version) {
573
+ throw new FsError(`cannot read "${path}": file changed while it was being read`, 'FS_IO_ERROR');
574
+ }
575
+ return buffer.slice(0, offset);
576
+ }
577
+ catch (error) {
578
+ localReadError(path, error);
579
+ }
580
+ finally {
581
+ await handle?.close().catch(() => undefined);
582
+ }
583
+ /* c8 ignore next -- the catch above always rethrows through localReadError. */
584
+ throw new Error(MISSING_FS);
585
+ }
586
+ async function readSourceBytes(ctx, path, options, maxBytes) {
587
+ const cwd = options.agent?.session?.header?.cwd;
588
+ if (!isAbsolutePath(path) && (typeof cwd !== 'string' || cwd.trim().length === 0 || !isAbsolutePath(cwd))) {
589
+ throw new Error('workflow script_path must be absolute when the calling Session has no absolute cwd');
590
+ }
591
+ const fs = ctx.fs;
592
+ if (fs === undefined)
593
+ throw new Error(MISSING_FS);
594
+ let bytes;
595
+ try {
596
+ const cleanCwd = typeof cwd === 'string' && cwd.trim().length > 0 ? cwd : undefined;
597
+ bytes = typeof fs.readBytesNoFollow === 'function'
598
+ ? await fs.readBytesNoFollow(path, cleanCwd === undefined ? {} : { cwd: cleanCwd }, options.signal, maxBytes)
599
+ : await readPublishedRc2LocalBytes(fs, path, cleanCwd, options.signal, maxBytes);
600
+ }
601
+ catch (error) {
602
+ translateScriptPathError(path, error, maxBytes);
603
+ }
604
+ if (!(bytes instanceof Uint8Array) || bytes.byteLength > maxBytes) {
605
+ throw new Error(`workflow script_path "${path}" exceeds the ${maxBytes}-byte limit`);
606
+ }
607
+ return bytes;
608
+ }
609
+ function isAbsolutePath(path) {
610
+ return path.startsWith('/') || /^[A-Za-z]:[\\/]/u.test(path);
611
+ }
612
+ function requireScriptBytes(script, maxBytes, label) {
613
+ if (new TextEncoder().encode(script).byteLength > maxBytes) {
614
+ throw new Error(`workflow ${label} exceeds the ${maxBytes}-byte limit`);
615
+ }
616
+ }
617
+ /** Resolve exactly one validated model source without following its final path component. */
618
+ export async function resolveWorkflowSource(ctx, request, options = {}) {
619
+ if (request.kind === 'resume')
620
+ throw new Error('resume request has no source');
621
+ const maxBytes = boundedLimit(options.definitionMaxBytes);
622
+ if (request.source.kind === 'name') {
623
+ const definition = await ctx.workflows.get(request.source.name, {
624
+ cwd: options.agent?.session?.header?.cwd,
625
+ signal: options.signal,
626
+ });
627
+ if (definition === undefined)
628
+ throw new Error(`no saved workflow named "${request.source.name}"`);
629
+ return {
630
+ script: definition.script,
631
+ meta: {
632
+ name: definition.name,
633
+ description: definition.description,
634
+ ...(definition.whenToUse === undefined ? {} : { whenToUse: definition.whenToUse }),
635
+ ...(definition.phases === undefined ? {} : { phases: [...definition.phases] }),
636
+ },
637
+ args: request.args,
638
+ filename: definition.path,
639
+ };
640
+ }
641
+ if (request.source.kind === 'script') {
642
+ requireScriptBytes(request.source.script, maxBytes, 'script');
643
+ const clean = validateDefinitionEnvelope({ meta: request.source.meta, script: request.source.script }, '<inline workflow>');
644
+ return { script: clean.script, meta: clean.meta, args: request.args, filename: '<inline workflow>' };
645
+ }
646
+ const path = request.source.path;
647
+ const bytes = await readSourceBytes(ctx, path, options, maxBytes);
648
+ if (path.endsWith('.workflow.json')) {
649
+ const filename = path.replaceAll('\\', '/').split('/').at(-1);
650
+ const name = filename.slice(0, -'.workflow.json'.length);
651
+ const definition = parseWorkflowDefinition(bytes, path, 'project', name, maxBytes);
652
+ return {
653
+ script: definition.script,
654
+ meta: {
655
+ name: definition.name,
656
+ description: definition.description,
657
+ ...(definition.whenToUse === undefined ? {} : { whenToUse: definition.whenToUse }),
658
+ ...(definition.phases === undefined ? {} : { phases: [...definition.phases] }),
659
+ },
660
+ args: request.args,
661
+ filename: path,
662
+ };
663
+ }
664
+ let script;
665
+ try {
666
+ script = new TextDecoder('utf-8', { fatal: true }).decode(bytes);
667
+ }
668
+ catch (error) {
669
+ throw new Error(`workflow script_path "${path}" is not valid UTF-8`, { cause: error });
670
+ }
671
+ if (request.source.meta === undefined)
672
+ throw new Error('workflow bare script_path requires the meta object');
673
+ const clean = validateDefinitionEnvelope({ meta: request.source.meta, script }, path);
674
+ return { script: clean.script, meta: clean.meta, args: request.args, filename: path };
675
+ }
676
+ async function saveInlineDefinition(registry, spec) {
677
+ if (typeof registry?.save !== 'function') {
678
+ throw new Error('workflow inline authoring requires registry.save');
679
+ }
680
+ const cwd = typeof spec.cwd === 'string' && spec.cwd.trim().length > 0 ? spec.cwd : undefined;
681
+ if (spec.scope === 'project' && cwd === undefined) {
682
+ throw new Error('workflow inline project save requires a session cwd; use save_scope "user" for a cwd-independent save');
683
+ }
684
+ spec.signal?.throwIfAborted();
685
+ const saved = await registry.save({ meta: spec.meta, script: spec.script }, {
686
+ scope: spec.scope,
687
+ ...(cwd === undefined ? {} : { cwd }),
688
+ ...(spec.signal === undefined ? {} : { signal: spec.signal }),
689
+ });
690
+ spec.signal?.throwIfAborted();
691
+ if (typeof saved !== 'object' || saved === null
692
+ || typeof saved.path !== 'string' || saved.path.trim().length === 0) {
693
+ throw new Error('workflow registry.save returned no valid saved path');
694
+ }
695
+ return saved.path;
696
+ }
697
+ /** Create the exact model-facing background workflow operation. */
698
+ export function createWorkflowTool(services) {
699
+ const maxResultChars = services.maxResultChars ?? MAX_RESULT_CHARS;
700
+ return defineTool({
701
+ name: 'workflow',
702
+ description: DESCRIPTION,
703
+ parameters: WORKFLOW_TOOL_PARAMETERS,
704
+ output: {
705
+ schema: {
706
+ oneOf: [
707
+ {
708
+ type: 'object',
709
+ additionalProperties: false,
710
+ properties: {
711
+ status: { type: 'string', const: 'started', required: true },
712
+ displayName: { type: 'string', required: true },
713
+ runId: { type: 'string', required: true },
714
+ script_path: { type: 'string' },
715
+ },
716
+ },
717
+ {
718
+ type: 'object',
719
+ additionalProperties: false,
720
+ properties: {
721
+ status: { type: 'string', const: 'resumed', required: true },
722
+ displayName: { type: 'string', required: true },
723
+ runId: { type: 'string', required: true },
724
+ },
725
+ },
726
+ {
727
+ type: 'object',
728
+ additionalProperties: false,
729
+ properties: {
730
+ status: { type: 'string', const: 'validated', required: true },
731
+ ok: { type: 'boolean', const: true, required: true },
732
+ result: { type: 'json' },
733
+ saved_path: { type: 'string' },
734
+ },
735
+ },
736
+ ],
737
+ },
738
+ render: (_args, value) => [{ type: 'text', text: renderLaunch(value, maxResultChars) }],
739
+ },
740
+ async execute(raw, exec) {
741
+ if (exec?.agent === undefined)
742
+ throw new Error(MISSING_AGENT);
743
+ const request = parseWorkflowToolRequest(raw);
744
+ if (request.kind === 'resume') {
745
+ const run = await services.supervisor.resumeById(request.runId, exec.agent, request.agentBudget, exec.signal);
746
+ return { status: 'resumed', displayName: run.displayName, runId: run.runId };
747
+ }
748
+ const source = await resolveWorkflowSource({
749
+ workflows: services.registry,
750
+ fs: services.fs,
751
+ }, request, {
752
+ agent: exec.agent,
753
+ signal: exec.signal,
754
+ definitionMaxBytes: services.definitionMaxBytes,
755
+ });
756
+ const script = request.source.kind === 'script'
757
+ ? repairObjectLiteralSemicolons(source.script)
758
+ : source.script;
759
+ if (request.validateOnly) {
760
+ const validation = await services.supervisor.validate({
761
+ script,
762
+ meta: source.meta,
763
+ args: source.args,
764
+ parent: exec.agent,
765
+ filename: source.filename,
766
+ agentBudget: request.agentBudget,
767
+ signal: exec.signal,
768
+ });
769
+ if (!validation.ok)
770
+ throw new Error(validation.error);
771
+ const savedPath = request.source.kind === 'script'
772
+ ? await saveInlineDefinition(services.registry, {
773
+ meta: source.meta,
774
+ script,
775
+ cwd: exec.agent?.session?.header?.cwd,
776
+ signal: exec.signal,
777
+ scope: request.saveScope ?? 'project',
778
+ })
779
+ : undefined;
780
+ return {
781
+ status: 'validated',
782
+ ok: true,
783
+ ...(validation.value === undefined ? {} : { result: validation.value }),
784
+ ...(savedPath === undefined ? {} : { saved_path: savedPath }),
785
+ };
786
+ }
787
+ const start = () => services.supervisor.start({
788
+ script,
789
+ meta: source.meta,
790
+ args: source.args,
791
+ parent: exec.agent,
792
+ agentBudget: request.agentBudget,
793
+ signal: exec.signal,
794
+ });
795
+ const nested = exec.parent !== undefined;
796
+ const launched = services.recorder === undefined || nested
797
+ ? await start()
798
+ : await services.recorder.launch(exec.agent.session, start);
799
+ return {
800
+ status: 'started',
801
+ displayName: launched.displayName,
802
+ runId: launched.runId,
803
+ ...(launched.scriptPath === undefined ? {} : { script_path: launched.scriptPath }),
804
+ };
805
+ },
806
+ presentCall: args => presentWorkflowCall(args),
807
+ presentResult: (args, result) => presentWorkflowResult(args, result),
808
+ });
809
+ }
810
+ //# sourceMappingURL=index.js.map