@agwab/pi-workflow 0.1.1 → 0.2.0

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 (70) hide show
  1. package/README.md +20 -15
  2. package/agents/researcher.md +17 -7
  3. package/dist/artifact-graph-runtime.js +1 -0
  4. package/dist/compiler.d.ts +2 -0
  5. package/dist/compiler.js +29 -4
  6. package/dist/dynamic-generated-task-runtime.js +4 -3
  7. package/dist/dynamic-runtime-bundle.js +3 -2
  8. package/dist/engine.d.ts +2 -0
  9. package/dist/engine.js +3 -2
  10. package/dist/extension.js +240 -16
  11. package/dist/store.js +1 -0
  12. package/dist/subagent-backend.js +82 -27
  13. package/dist/tool-metadata.d.ts +1 -0
  14. package/dist/tool-metadata.js +13 -1
  15. package/dist/types.d.ts +3 -0
  16. package/dist/workflow-artifact-extension.js +3 -2
  17. package/dist/workflow-artifact-tool.js +84 -4
  18. package/dist/workflow-progress-health.d.ts +37 -0
  19. package/dist/workflow-progress-health.js +296 -0
  20. package/dist/workflow-runtime.d.ts +6 -0
  21. package/dist/workflow-runtime.js +33 -10
  22. package/dist/workflow-view.d.ts +2 -0
  23. package/dist/workflow-view.js +97 -18
  24. package/dist/workflow-web-source-extension.d.ts +43 -0
  25. package/dist/workflow-web-source-extension.js +1194 -0
  26. package/dist/workflow-web-source.d.ts +171 -0
  27. package/dist/workflow-web-source.js +915 -0
  28. package/docs/usage.md +32 -18
  29. package/node_modules/@agwab/pi-subagent/package.json +1 -1
  30. package/node_modules/@agwab/pi-subagent/src/api.ts +245 -132
  31. package/node_modules/@agwab/pi-subagent/src/artifacts/result.ts +243 -163
  32. package/node_modules/@agwab/pi-subagent/src/core/constants.ts +117 -90
  33. package/node_modules/@agwab/pi-subagent/src/core/validation.ts +728 -475
  34. package/node_modules/@agwab/pi-subagent/src/orchestrate/run.ts +305 -209
  35. package/node_modules/@agwab/pi-subagent/src/runners/headless-model.ts +750 -439
  36. package/node_modules/@agwab/pi-subagent/src/runners/tmux.ts +422 -268
  37. package/package.json +7 -7
  38. package/skills/workflow-guide/scaffolds/object-tool-fallback/schemas/fetch-control.schema.json +1 -1
  39. package/skills/workflow-guide/scaffolds/object-tool-fallback/spec.json +4 -3
  40. package/src/artifact-graph-runtime.ts +1 -0
  41. package/src/compiler.ts +43 -3
  42. package/src/dynamic-generated-task-runtime.ts +4 -2
  43. package/src/dynamic-runtime-bundle.ts +3 -2
  44. package/src/engine.ts +7 -16
  45. package/src/extension.ts +299 -22
  46. package/src/store.ts +1 -0
  47. package/src/subagent-backend.ts +121 -37
  48. package/src/tool-metadata.ts +22 -1
  49. package/src/types.ts +4 -0
  50. package/src/workflow-artifact-extension.ts +3 -2
  51. package/src/workflow-artifact-tool.ts +96 -4
  52. package/src/workflow-progress-health.ts +461 -0
  53. package/src/workflow-runtime.ts +50 -13
  54. package/src/workflow-view.ts +186 -41
  55. package/src/workflow-web-source-extension.ts +1411 -0
  56. package/src/workflow-web-source.ts +1294 -0
  57. package/workflows/README.md +1 -1
  58. package/workflows/deep-research/helpers/claim-evidence-gate.mjs +552 -44
  59. package/workflows/deep-research/helpers/final-audit-packet.mjs +396 -0
  60. package/workflows/deep-research/helpers/normalize-input-packet.mjs +545 -0
  61. package/workflows/deep-research/helpers/render-executive.mjs +1199 -192
  62. package/workflows/deep-research/helpers/sanitize-verification-candidates.mjs +624 -0
  63. package/workflows/deep-research/schemas/deep-research-executive-render-control.schema.json +37 -8
  64. package/workflows/deep-research/schemas/deep-research-final-synthesis-control.schema.json +110 -0
  65. package/workflows/deep-research/schemas/deep-research-normalize-claims-control.schema.json +45 -4
  66. package/workflows/deep-research/schemas/deep-research-verify-claims-control.schema.json +0 -2
  67. package/workflows/deep-research/spec.json +71 -26
  68. package/workflows/deep-review/helpers/render-review-report.mjs +502 -0
  69. package/workflows/deep-review/schemas/deep-review-render-control.schema.json +50 -0
  70. package/workflows/deep-review/spec.json +22 -1
package/docs/usage.md CHANGED
@@ -187,20 +187,22 @@ A run prints a `workflow_*` id. Use that id for follow-up commands:
187
187
 
188
188
  The runtime task is not optional. `/workflow run <workflow>` and `/workflow dynamic` without task text fail before launch.
189
189
 
190
- ### Run-scoped `fetch_content` cache
190
+ ### Run-scoped web-source cache
191
191
 
192
- Workflow tasks that use `fetch_content` share a run-scoped file cache by default. The cache is stored under the workflow run directory, for example:
192
+ Prefer normalized workflow web tools in new workflows:
193
193
 
194
- ```text
195
- .pi/workflows/<run-id>/source-cache/fetch-content/
196
- ```
194
+ - `workflow_web_search` returns compact candidate cards.
195
+ - `workflow_web_fetch_source` caches one or more URLs and returns compact source cards with `sourceRef` values; pass `urls: [...]` or `sources: [{ url, title }]` to batch several fetches in one tool call.
196
+ - `workflow_web_source_read` reads narrow exact/fuzzy/term-matched evidence snippets by `sourceRef`; pass `queries: [...]` or `reads: [...]` to batch several snippets from the same source in one tool call, or `claim` + distinctive `terms` when the exact quote is unknown. Term/claim reads return candidate metadata (`matchedTerms`, `missingTerms`, `coverageRatio`) rather than a proof verdict.
197
197
 
198
- The cache is only reused within the same workflow run, including resume/retry of that run. It is not reused across separate runs by default. Cache events are appended to that cache directory's `events.jsonl` with `hit`, `miss`, `write`, and `skip` records for telemetry and audit. To disable the cache for a run, set:
198
+ The normalized cache is stored under the workflow run directory:
199
199
 
200
- ```bash
201
- PI_WORKFLOW_FETCH_CONTENT_CACHE=0
200
+ ```text
201
+ .pi/workflows/<run-id>/web-source-cache/
202
202
  ```
203
203
 
204
+ Do not instruct agents to read that directory directly; source cards intentionally expose only opaque refs and short previews. The cache also writes an append-only index ledger plus same-URL fetch locks/negative-cache files so duplicate lookup and deterministic terminal failures can recover across parallel worker processes. Custom extension `fetch_content` providers are treated as trusted fetchers and are disabled under the default private-host policy; use the default safe fetch path or opt into trusted private-host behavior only for controlled providers. Legacy workflow tasks that still use `fetch_content` keep the older run-scoped file cache under `.pi/workflows/<run-id>/source-cache/fetch-content/`. Set `PI_WORKFLOW_FETCH_CONTENT_CACHE=0` to disable that legacy fetch cache for a run.
205
+
204
206
  Benchmark note: cache-enabled runs are a distinct cohort from older uncached runs. Do not compare wall-clock numbers directly unless the task set, model, and cache policy are controlled and recorded.
205
207
 
206
208
  ## Bundled workflows
@@ -209,7 +211,7 @@ Benchmark note: cache-enabled runs are a distinct cohort from older uncached run
209
211
 
210
212
  | Workflow | Required agents | Mode | Use when |
211
213
  |---|---|---|---|
212
- | `deep-research` | `researcher` | plan + foreach questions + normalize + foreach verifier + audit support + full audit reduce + deterministic executive render | Use when you need a grounded answer or summary based on source material. |
214
+ | `deep-research` | `researcher` | plan + foreach questions + normalize-input packet support + normalize + foreach verifier + audit support + final-audit packet support + compact final synthesis reduce + deterministic ledger-backed executive render | Use when you need a grounded answer or summary based on source material. |
213
215
  | `deep-review` | `scout` | triage + foreach review lenses + dedup support + foreach devil's advocate + verdict-partition support + reduce | Use when you want code or design reviewed carefully from multiple angles. |
214
216
  | `spec-review` | `scout` | extract spec + map implementation + inspect tests -> reduce candidates -> foreach verifier -> reduce report | Use when you want to check whether requirements, an API spec, or a contract are reflected in the implementation and tests. |
215
217
  | `impact-review` | `scout` | scope/implementation/validation maps -> impact lenses -> consistency/regression/ship-readiness joins -> final synthesis | Use before merging or releasing a change to check affected areas, risks, missing tests, and missing docs. |
@@ -534,7 +536,7 @@ Authoring checklist:
534
536
  Workflow `tools` are still the child-worker allowlist. Entries can be strings:
535
537
 
536
538
  ```json
537
- { "tools": ["read", "grep", "fetch_content"] }
539
+ { "tools": ["read", "grep", "workflow_web_search", "workflow_web_fetch_source", "workflow_web_source_read"] }
538
540
  ```
539
541
 
540
542
  or object specs for custom/local providers:
@@ -548,7 +550,7 @@ or object specs for custom/local providers:
548
550
  "extensions": ["packages/pi-scrapling-access"],
549
551
  "classification": "read-only",
550
552
  "optional": true,
551
- "fallbackTools": ["fetch_content"]
553
+ "fallbackTools": ["workflow_web_fetch_source"]
552
554
  }
553
555
  ]
554
556
  }
@@ -569,10 +571,22 @@ Scope order is agent frontmatter fallback < `defaults.tools` < stage `tools`: th
569
571
 
570
572
  ## Web tools
571
573
 
572
- Workflows that use `web_search`, `fetch_content`, `get_search_content`, or
573
- `code_search` use the bundled `pi-web-access` dependency packaged with
574
- pi-workflow. The worker launcher injects that bundled extension automatically
575
- for those tools. Object-form custom tool `extensions` are merged with this
576
- built-in mapping and deduplicated for the subagent launch. Web calls can still
577
- fail when network access, provider credentials, browser state, or quota are
578
- unavailable; research workflows should report those limits instead of guessing.
574
+ New workflows should use `workflow_web_search`, `workflow_web_fetch_source`, and
575
+ `workflow_web_source_read`. These tools route through a workflow web-source
576
+ adapter, return compact model-visible cards/snippets, and preserve full source
577
+ text in a run-scoped cache when safe. Fetch accepts `urls: [...]` and
578
+ `sources: [{ url, title }]` so agents can cache several source cards in one call.
579
+ Source-read accepts `queries: [...]` and `reads: [...]` so agents can retrieve
580
+ several snippets from one `sourceRef` in a single call, and accepts `claim` +
581
+ distinctive `terms` for deterministic quote
582
+ candidate harvesting when the exact quote is unknown. Term/claim matches are
583
+ candidate evidence and include matched/missing term metadata; they are not a
584
+ verdict by themselves. The bundled `pi-web-access` adapter remains
585
+ available as the default compatibility provider for this release scope.
586
+
587
+ Legacy workflows that use `web_search`, `fetch_content`, `get_search_content`, or
588
+ `code_search` still use the bundled `pi-web-access` dependency packaged with
589
+ pi-workflow. Object-form custom tool `extensions` are merged with built-in
590
+ mappings and deduplicated for the subagent launch. Web calls can still fail when
591
+ network access, provider credentials, browser state, or quota are unavailable;
592
+ research workflows should report those limits instead of guessing.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agwab/pi-subagent",
3
- "version": "0.3.5",
3
+ "version": "0.3.6",
4
4
  "description": "Minimal subagent runtime for Pi.",
5
5
  "type": "module",
6
6
  "private": false,
@@ -1,26 +1,45 @@
1
1
  import { resolve } from "node:path";
2
2
  import { loadAgentByName, type AgentDefinition } from "./agents.ts";
3
3
  import type { ResultEnvelope } from "./artifacts/index.ts";
4
- import type { ExecutionMode, ResolveInput, ResolvedBackend } from "./core/constants.ts";
4
+ import type {
5
+ ExecutionMode,
6
+ ResolveInput,
7
+ ResolvedBackend,
8
+ } from "./core/constants.ts";
5
9
  import { resolveBackend } from "./core/resolver.ts";
6
10
  import { validateResolveInput } from "./core/validation.ts";
7
- import { startAsyncParallelSubagentRuns, startAsyncSubagentRun } from "./orchestrate/async.ts";
8
- import { interruptRun, type InterruptRunOptions, type InterruptRunResult } from "./orchestrate/interrupt.ts";
9
- import { reconcileSubagentRun as reconcileRun, type ReconcileSubagentRunOptions as ReconcileRunOptions, type ReconcileSubagentRunResult } from "./orchestrate/reconcile.ts";
10
- import { runParallelSubagentTasks, runSubagentTask, type ParallelRunResult } from "./orchestrate/run.ts";
11
11
  import {
12
- getRunLogs,
13
- getRunStatus,
14
- waitForRun,
15
- type RunLogsSnapshot,
16
- type RunStatusRef,
17
- type RunStatusSnapshot,
18
- type WaitForRunOptions,
19
- type WaitForRunResult,
12
+ startAsyncParallelSubagentRuns,
13
+ startAsyncSubagentRun,
14
+ } from "./orchestrate/async.ts";
15
+ import {
16
+ interruptRun,
17
+ type InterruptRunOptions,
18
+ type InterruptRunResult,
19
+ } from "./orchestrate/interrupt.ts";
20
+ import {
21
+ reconcileSubagentRun as reconcileRun,
22
+ type ReconcileSubagentRunOptions as ReconcileRunOptions,
23
+ type ReconcileSubagentRunResult,
24
+ } from "./orchestrate/reconcile.ts";
25
+ import {
26
+ runParallelSubagentTasks,
27
+ runSubagentTask,
28
+ type ParallelRunResult,
29
+ } from "./orchestrate/run.ts";
30
+ import {
31
+ getRunLogs,
32
+ getRunStatus,
33
+ waitForRun,
34
+ type RunLogsSnapshot,
35
+ type RunStatusRef,
36
+ type RunStatusSnapshot,
37
+ type WaitForRunOptions,
38
+ type WaitForRunResult,
20
39
  } from "./orchestrate/status.ts";
21
40
 
22
41
  export interface RunSubagentOptions extends ResolveInput {
23
- signal?: AbortSignal;
42
+ signal?: AbortSignal;
24
43
  }
25
44
 
26
45
  export type RunSubagentResult = ResultEnvelope | ParallelRunResult;
@@ -32,159 +51,253 @@ export type InterruptSubagentOptions = InterruptRunOptions;
32
51
  export type ReconcileSubagentOptions = ReconcileRunOptions;
33
52
 
34
53
  export class SubagentValidationError extends Error {
35
- readonly failureKind = "validation" as const;
36
- readonly backend?: ResolvedBackend;
37
-
38
- constructor(message: string, backend?: ResolvedBackend) {
39
- super(message);
40
- this.name = "SubagentValidationError";
41
- this.backend = backend;
42
- }
54
+ readonly failureKind = "validation" as const;
55
+ readonly backend?: ResolvedBackend;
56
+
57
+ constructor(message: string, backend?: ResolvedBackend) {
58
+ super(message);
59
+ this.name = "SubagentValidationError";
60
+ this.backend = backend;
61
+ }
43
62
  }
44
63
 
45
- const AGENT_REQUEST_KEYS = ["agent", "task", "roleContext", "agentScope", "confirmProjectAgents"] as const;
64
+ const AGENT_REQUEST_KEYS = [
65
+ "agent",
66
+ "task",
67
+ "roleContext",
68
+ "agentScope",
69
+ "confirmProjectAgents",
70
+ ] as const;
46
71
 
47
72
  interface AgentRequest {
48
- agent: string;
49
- cwd?: string;
50
- agentScope?: ResolveInput["agentScope"];
51
- confirmProjectAgents?: boolean;
73
+ agent: string;
74
+ cwd?: string;
75
+ agentScope?: ResolveInput["agentScope"];
76
+ confirmProjectAgents?: boolean;
52
77
  }
53
78
 
54
79
  function executionMode(input: ResolveInput): ExecutionMode {
55
- if (input.mode !== undefined) return input.mode;
56
- if (input.tasks !== undefined) return "parallel";
57
- return "single";
80
+ if (input.mode !== undefined) return input.mode;
81
+ if (input.tasks !== undefined) return "parallel";
82
+ return "single";
58
83
  }
59
84
 
60
85
  function hasRunnableSingleInput(input: ResolveInput): boolean {
61
- return AGENT_REQUEST_KEYS.some((key) => input[key] !== undefined);
86
+ return AGENT_REQUEST_KEYS.some((key) => input[key] !== undefined);
62
87
  }
63
88
 
64
89
  function agentRequests(input: ResolveInput): AgentRequest[] {
65
- if (input.tasks !== undefined) {
66
- return input.tasks
67
- .filter((task): task is typeof task & { agent: string } => typeof task.agent === "string" && task.agent.length > 0)
68
- .map((task) => ({
69
- agent: task.agent,
70
- cwd: task.cwd,
71
- agentScope: task.agentScope ?? input.agentScope,
72
- confirmProjectAgents: task.confirmProjectAgents ?? input.confirmProjectAgents ?? false,
73
- }));
74
- }
75
- return typeof input.agent === "string" && input.agent.length > 0
76
- ? [{ agent: input.agent, cwd: input.cwd, agentScope: input.agentScope, confirmProjectAgents: input.confirmProjectAgents ?? false }]
77
- : [];
90
+ if (input.tasks !== undefined) {
91
+ return input.tasks
92
+ .filter(
93
+ (task): task is typeof task & { agent: string } =>
94
+ typeof task.agent === "string" && task.agent.length > 0,
95
+ )
96
+ .map((task) => ({
97
+ agent: task.agent,
98
+ cwd: task.cwd,
99
+ agentScope: task.agentScope ?? input.agentScope,
100
+ confirmProjectAgents:
101
+ task.confirmProjectAgents ?? input.confirmProjectAgents ?? false,
102
+ }));
103
+ }
104
+ return typeof input.agent === "string" && input.agent.length > 0
105
+ ? [
106
+ {
107
+ agent: input.agent,
108
+ cwd: input.cwd,
109
+ agentScope: input.agentScope,
110
+ confirmProjectAgents: input.confirmProjectAgents ?? false,
111
+ },
112
+ ]
113
+ : [];
78
114
  }
79
115
 
80
- async function assertProjectAgentApproval(input: ResolveInput, cwd: string): Promise<void> {
81
- const projectAgents: AgentDefinition[] = [];
82
- for (const request of agentRequests(input)) {
83
- if (request.confirmProjectAgents === false || request.agentScope === "global") continue;
84
- const requestCwd = resolve(cwd, request.cwd ?? ".");
85
- const agent = await loadAgentByName(request.agent, requestCwd, request.agentScope);
86
- if (agent?.source === "project" && !projectAgents.some((candidate) => candidate.sourcePath === agent.sourcePath)) {
87
- projectAgents.push(agent);
88
- }
89
- }
90
- if (projectAgents.length === 0) return;
91
-
92
- const names = projectAgents.map((agent) => agent.displayName).join(", ");
93
- const sources = projectAgents.map((agent) => agent.sourcePath).join("\n");
94
- throw new SubagentValidationError(
95
- `Project-local subagent definitions require explicit opt-in from code API callers. Set confirmProjectAgents:false only for trusted repositories. Agents: ${names}\nSources:\n${sources}`,
96
- );
116
+ async function assertProjectAgentApproval(
117
+ input: ResolveInput,
118
+ cwd: string,
119
+ ): Promise<void> {
120
+ const projectAgents: AgentDefinition[] = [];
121
+ for (const request of agentRequests(input)) {
122
+ if (
123
+ request.confirmProjectAgents === false ||
124
+ request.agentScope === "global"
125
+ )
126
+ continue;
127
+ const requestCwd = resolve(cwd, request.cwd ?? ".");
128
+ const agent = await loadAgentByName(
129
+ request.agent,
130
+ requestCwd,
131
+ request.agentScope,
132
+ );
133
+ if (
134
+ agent?.source === "project" &&
135
+ !projectAgents.some(
136
+ (candidate) => candidate.sourcePath === agent.sourcePath,
137
+ )
138
+ ) {
139
+ projectAgents.push(agent);
140
+ }
141
+ }
142
+ if (projectAgents.length === 0) return;
143
+
144
+ const names = projectAgents.map((agent) => agent.displayName).join(", ");
145
+ const sources = projectAgents.map((agent) => agent.sourcePath).join("\n");
146
+ throw new SubagentValidationError(
147
+ `Project-local subagent definitions require explicit opt-in from code API callers. Set confirmProjectAgents:false only for trusted repositories. Agents: ${names}\nSources:\n${sources}`,
148
+ );
97
149
  }
98
150
 
99
- function validateRunnableInput(input: ResolveInput, backend: ResolvedBackend): void {
100
- const mode = executionMode(input);
101
- if (mode === "parallel") {
102
- if (input.tasks === undefined || input.tasks.length === 0) {
103
- throw new SubagentValidationError("parallel mode requires a non-empty tasks array.", backend);
104
- }
105
- return;
106
- }
107
-
108
- if (!hasRunnableSingleInput(input)) {
109
- throw new SubagentValidationError(`${backend} execution requires agent/task input.`, backend);
110
- }
111
- if (input.task === undefined) {
112
- throw new SubagentValidationError(`${backend} agent/task execution requires a non-empty task.`, backend);
113
- }
151
+ function validateRunnableInput(
152
+ input: ResolveInput,
153
+ backend: ResolvedBackend,
154
+ ): void {
155
+ const mode = executionMode(input);
156
+ if (mode === "parallel") {
157
+ if (input.tasks === undefined || input.tasks.length === 0) {
158
+ throw new SubagentValidationError(
159
+ "parallel mode requires a non-empty tasks array.",
160
+ backend,
161
+ );
162
+ }
163
+ return;
164
+ }
165
+
166
+ if (!hasRunnableSingleInput(input)) {
167
+ throw new SubagentValidationError(
168
+ `${backend} execution requires agent/task input.`,
169
+ backend,
170
+ );
171
+ }
172
+ if (input.task === undefined) {
173
+ throw new SubagentValidationError(
174
+ `${backend} agent/task execution requires a non-empty task.`,
175
+ backend,
176
+ );
177
+ }
114
178
  }
115
179
 
116
- function validateRunOptions(options: RunSubagentOptions): { input: ResolveInput; cwd: string; backend: ResolvedBackend; signal?: AbortSignal } {
117
- const { signal, ...rawInput } = options;
118
- const validation = validateResolveInput(rawInput);
119
- if (!validation.ok) {
120
- throw new SubagentValidationError(validation.failure.error, validation.failure.backend);
121
- }
122
-
123
- const resolved = resolveBackend(validation.input);
124
- if (resolved.status === "failed") {
125
- throw new SubagentValidationError(resolved.error, resolved.backend);
126
- }
127
-
128
- validateRunnableInput(validation.input, resolved.backend);
129
- const cwd = resolve(validation.input.cwd ?? process.cwd());
130
- return { input: validation.input, cwd, backend: resolved.backend, signal };
180
+ function validateRunOptions(options: RunSubagentOptions): {
181
+ input: ResolveInput;
182
+ cwd: string;
183
+ backend: ResolvedBackend;
184
+ signal?: AbortSignal;
185
+ } {
186
+ const { signal, ...rawInput } = options;
187
+ const validation = validateResolveInput(rawInput);
188
+ if (!validation.ok) {
189
+ throw new SubagentValidationError(
190
+ validation.failure.error,
191
+ validation.failure.backend,
192
+ );
193
+ }
194
+
195
+ const resolved = resolveBackend(validation.input);
196
+ if (resolved.status === "failed") {
197
+ throw new SubagentValidationError(resolved.error, resolved.backend);
198
+ }
199
+
200
+ validateRunnableInput(validation.input, resolved.backend);
201
+ const cwd = resolve(validation.input.cwd ?? process.cwd());
202
+ return { input: validation.input, cwd, backend: resolved.backend, signal };
131
203
  }
132
204
 
133
- export async function runSubagent(options: RunSubagentOptions): Promise<RunSubagentResult> {
134
- const { input, cwd, backend, signal } = validateRunOptions(options);
135
- await assertProjectAgentApproval(input, cwd);
136
-
137
- try {
138
- const mode = executionMode(input);
139
- const asyncRequested = input.async === true || input.onComplete === "detach" || input.onComplete === "notify";
140
- if (mode === "parallel") {
141
- return asyncRequested ? await startAsyncParallelSubagentRuns(input, cwd, signal) : await runParallelSubagentTasks(input, cwd, signal);
142
- }
143
-
144
- if (asyncRequested) {
145
- return await startAsyncSubagentRun({ input, cwd, backend, signal });
146
- }
147
-
148
- return await runSubagentTask({ input, cwd, signal });
149
- } catch (error) {
150
- if (typeof error === "object" && error !== null && (error as { failureKind?: unknown }).failureKind === "validation") {
151
- throw new SubagentValidationError(error instanceof Error ? error.message : String(error));
152
- }
153
- throw error;
154
- }
205
+ export async function runSubagent(
206
+ options: RunSubagentOptions,
207
+ ): Promise<RunSubagentResult> {
208
+ const { input, cwd, backend, signal } = validateRunOptions(options);
209
+ await assertProjectAgentApproval(input, cwd);
210
+
211
+ try {
212
+ const mode = executionMode(input);
213
+ const asyncRequested =
214
+ input.async === true ||
215
+ input.onComplete === "detach" ||
216
+ input.onComplete === "notify";
217
+ if (mode === "parallel") {
218
+ return asyncRequested
219
+ ? await startAsyncParallelSubagentRuns(input, cwd, signal)
220
+ : await runParallelSubagentTasks(input, cwd, signal);
221
+ }
222
+
223
+ if (asyncRequested) {
224
+ return await startAsyncSubagentRun({ input, cwd, backend, signal });
225
+ }
226
+
227
+ return await runSubagentTask({ input, cwd, signal });
228
+ } catch (error) {
229
+ if (
230
+ typeof error === "object" &&
231
+ error !== null &&
232
+ (error as { failureKind?: unknown }).failureKind === "validation"
233
+ ) {
234
+ throw new SubagentValidationError(
235
+ error instanceof Error ? error.message : String(error),
236
+ );
237
+ }
238
+ throw error;
239
+ }
155
240
  }
156
241
 
157
- export async function getSubagentStatus(options: GetSubagentStatusOptions): Promise<RunStatusSnapshot | null> {
158
- return await getRunStatus(options);
242
+ export async function getSubagentStatus(
243
+ options: GetSubagentStatusOptions,
244
+ ): Promise<RunStatusSnapshot | null> {
245
+ return await getRunStatus(options);
159
246
  }
160
247
 
161
- export async function getSubagentLogs(options: GetSubagentLogsOptions): Promise<RunLogsSnapshot | null> {
162
- return await getRunLogs(options);
248
+ export async function getSubagentLogs(
249
+ options: GetSubagentLogsOptions,
250
+ ): Promise<RunLogsSnapshot | null> {
251
+ return await getRunLogs(options);
163
252
  }
164
253
 
165
- export async function waitForSubagent(options: WaitForSubagentOptions): Promise<WaitForRunResult> {
166
- return await waitForRun(options);
254
+ export async function waitForSubagent(
255
+ options: WaitForSubagentOptions,
256
+ ): Promise<WaitForRunResult> {
257
+ return await waitForRun(options);
167
258
  }
168
259
 
169
- export async function interruptSubagent(options: InterruptSubagentOptions): Promise<InterruptRunResult> {
170
- return await interruptRun(options);
260
+ export async function interruptSubagent(
261
+ options: InterruptSubagentOptions,
262
+ ): Promise<InterruptRunResult> {
263
+ return await interruptRun(options);
171
264
  }
172
265
 
173
- export async function reconcileSubagentRun(options: ReconcileSubagentOptions): Promise<ReconcileSubagentRunResult> {
174
- return await reconcileRun(options);
266
+ export async function reconcileSubagentRun(
267
+ options: ReconcileSubagentOptions,
268
+ ): Promise<ReconcileSubagentRunResult> {
269
+ return await reconcileRun(options);
175
270
  }
176
271
 
177
272
  export type {
178
- ArtifactRef,
179
- CompletionMetadata,
180
- ResultEnvelope,
181
- ResultSandbox,
182
- ResultTmuxMetadata,
183
- ResultWorkspace,
184
- WorktreeCleanupStatus,
273
+ ArtifactRef,
274
+ CompletionMetadata,
275
+ ResultEnvelope,
276
+ ResultMetadata,
277
+ ResultSandbox,
278
+ ResultSessionDisposition,
279
+ ResultSessionMetadata,
280
+ ResultSessionReason,
281
+ ResultTmuxMetadata,
282
+ ResultWorkspace,
283
+ WorktreeCleanupStatus,
185
284
  } from "./artifacts/index.ts";
186
- export type { AsyncDependency, Backend, ExecutionMode, FailureKind, ResolvedBackend, Status } from "./core/constants.ts";
285
+ export type {
286
+ AsyncDependency,
287
+ Backend,
288
+ ExecutionMode,
289
+ FailureKind,
290
+ ResolvedBackend,
291
+ Status,
292
+ } from "./core/constants.ts";
187
293
  export type { InterruptRunResult } from "./orchestrate/interrupt.ts";
188
294
  export type { ReconcileSubagentRunResult } from "./orchestrate/reconcile.ts";
189
295
  export type { ParallelRunResult } from "./orchestrate/run.ts";
190
- export type { RunLogRef, RunLogsSnapshot, RunStatusRef, RunStatusSnapshot, RunTaskStatusSnapshot, WaitForRunResult } from "./orchestrate/status.ts";
296
+ export type {
297
+ RunLogRef,
298
+ RunLogsSnapshot,
299
+ RunStatusRef,
300
+ RunStatusSnapshot,
301
+ RunTaskStatusSnapshot,
302
+ WaitForRunResult,
303
+ } from "./orchestrate/status.ts";