@tranhoangnguyen0310/pi-flow-external 4.0.0-external.0 → 5.0.1-external.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.
- package/CHANGELOG.md +24 -0
- package/README.md +24 -26
- package/docs/README.md +3 -2
- package/docs/migration-agent-to-external-delegate.md +3 -17
- package/docs/migration-workflow-to-external-workflow.md +48 -0
- package/docs/public-contract.md +3 -3
- package/docs/supervision-and-runtime.md +64 -20
- package/docs/troubleshooting.md +2 -2
- package/docs/workflows.md +36 -4
- package/package.json +2 -1
- package/src/contract/envelope.ts +1 -1
- package/src/contract/workflow.ts +2 -1
- package/src/core/concurrency.ts +2 -2
- package/src/core/spawn.ts +3 -3
- package/src/core/subagent-render.ts +2 -2
- package/src/external-help.ts +17 -13
- package/src/pi-subagent.ts +2 -2
- package/src/profile-creator.ts +2 -2
- package/src/profiles.ts +1 -1
- package/src/prompts.ts +16 -8
- package/src/tool-names.ts +1 -0
- package/src/types.ts +3 -3
- package/src/workflow/script-validation.ts +3 -0
- package/src/workflow/source.ts +2 -1
- package/src/workflow/tool.ts +9 -6
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,30 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to pi-flow external are documented here.
|
|
4
4
|
|
|
5
|
+
## [5.0.1-external.0] - 2026-10-19
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
- Clarified background execution guidance across the coordinator prompt, `external_help` (`usage` and `workflow`), tool parameter descriptions, background launch receipts, and docs:
|
|
9
|
+
- When the parent has independent local work (`read`, `edit`, `bash`) that does not touch files the delegated run may change, pass `background: true` and complete that local work before calling `external_runs wait`.
|
|
10
|
+
- Ending a turn does not stop a background run: when the user asks to leave work running, switch tasks, or check back later, report the `runId` and proceed with the other task or end the turn without calling `external_runs wait`.
|
|
11
|
+
- Background launch receipts for `external_delegate` and `external_workflow` now instruct the model to finish independent local work or report the `runId` and proceed/end its turn before calling `external_runs`, avoiding an immediate-wait reflex.
|
|
12
|
+
|
|
13
|
+
## [5.0.0-external.0] - 2026-10-15
|
|
14
|
+
|
|
15
|
+
### Breaking
|
|
16
|
+
- Renamed the orchestration tool **`workflow` → `external_workflow`**, following PR #86's direct-tool rename precedent. There is no alias or automatic translation. Update `tools.workflow(...)` → `tools.external_workflow(...)`, `describeTool("workflow")` → `describeTool("external_workflow")`, exact-name hooks, tool allow-lists, and receipt checks. A stale call may reach another extension's `workflow` tool rather than fail.
|
|
17
|
+
- New workflow receipts report `tool: "external_workflow"`. Envelope `contractVersion: 1`, `action: "run"`, run `kind: "workflow"`, `wf_...` IDs, saved scripts, and script helpers such as `agent()` remain unchanged. Settings stay at version 5; no conversion or saved-run rewrite is required. Help topic `workflow`, extension option `{ workflow: false }`, and E2E `--workflow` are unchanged.
|
|
18
|
+
- Update Flow's `pi-cc-extensions` `excludeRenderers` entry from `"workflow"` to `"external_workflow"` (alongside `"external_delegate"` and `"external_runs"`), preserve unrelated entries, and restart Pi. See the [workflow tool migration guide](docs/migration-workflow-to-external-workflow.md).
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
- Parent guidance requires a plain-language pre-launch briefing covering purpose, assignments, parallel/sequential order, allowed changes, and deliverable, with the same roadmap in `meta.phases` and actual transitions marked by `phase()`. This follows the user's existing approval policy; it is not a runtime-verified chat-message gate.
|
|
22
|
+
- Workflow cards now display existing planned-phase details, including future stages. Help and examples explain the roadmap, actual progress, and explicit launch inspection for retrieving recorded plans during background runs and after completion. Phases remain optional for existing saved scripts; declared intent is not proof of execution.
|
|
23
|
+
|
|
24
|
+
- Foreground/background guidance now puts the selection rule and ownership up front: foreground also supports parallel work; background is for independent work or an explicit request, has no automatic completion delivery, and requires collection in the owning session. The runtime guide includes outcome-aware, paged collection instead of teaching immediate background-then-wait. Permission descriptions distinguish native tool restrictions from OS sandboxing.
|
|
25
|
+
|
|
26
|
+
### Fixed
|
|
27
|
+
- Phase explanations use the existing secret redaction before display, and supplied phase details must be strings rather than silently rendering malformed values.
|
|
28
|
+
|
|
5
29
|
## [4.0.0-external.0] - 2026-10-13
|
|
6
30
|
|
|
7
31
|
### Breaking
|
package/README.md
CHANGED
|
@@ -24,7 +24,7 @@ You ask Pi to delegate work in plain chat. Every delegation combines three eleme
|
|
|
24
24
|
|
|
25
25
|
> ⚠️ **Security & Defaults:**
|
|
26
26
|
> Roles describe **intent**; `permission` defines **authority**. Saying "read-only" in prompt text does not restrict an agent.
|
|
27
|
-
> Out of the box, the default harness is `agy` and the default permission is `danger` (full host access). Antigravity rejects `readonly` and `edit`. For
|
|
27
|
+
> Out of the box, the default harness is `agy` and the default permission is `danger` (full host access). Antigravity rejects `readonly` and `edit`. For restricted access, request a supported permission tier explicitly. Claude uses native tool restrictions; Codex uses an OS sandbox. See the permission matrix below.
|
|
28
28
|
|
|
29
29
|
---
|
|
30
30
|
|
|
@@ -71,7 +71,7 @@ Pi recognizes the request and delegates:
|
|
|
71
71
|
|
|
72
72
|
- **Role:** `explorer`
|
|
73
73
|
- **Harness:** `claude`
|
|
74
|
-
- **Permission:** `readonly` (
|
|
74
|
+
- **Permission:** `readonly` (native tool restrictions: Claude cannot edit files or run shell commands; not an OS sandbox)
|
|
75
75
|
|
|
76
76
|
*(Programmatic / Codemode equivalent):*
|
|
77
77
|
|
|
@@ -140,34 +140,27 @@ await tools.external_delegate({
|
|
|
140
140
|
});
|
|
141
141
|
```
|
|
142
142
|
|
|
143
|
-
### 2.
|
|
143
|
+
### 2. Foreground or Background?
|
|
144
144
|
|
|
145
|
-
|
|
145
|
+
**Use foreground when you need the result next.** Use background when independent local work can proceed first, or you want to leave the work running and check back in a later turn (ending a turn does not stop it). A long task alone does not need background mode.
|
|
146
146
|
|
|
147
|
-
|
|
147
|
+
Both `external_delegate` and `external_workflow` support these modes:
|
|
148
148
|
|
|
149
|
-
|
|
149
|
+
| | Foreground (default) | Background (`background: true`) |
|
|
150
|
+
|---|---|---|
|
|
151
|
+
| Original call returns | When the work finishes or fails | After registration, with a run ID; work may already have finished |
|
|
152
|
+
| Progress | Live execution card | A static launch receipt; inspect with `/external runs` or ask Pi for status |
|
|
153
|
+
| Next step | Pi checks the returned outcome | Pi keeps the run ID and does independent work before collecting, or reports the ID and switches tasks/ends its turn if left running across turns |
|
|
150
154
|
|
|
151
|
-
|
|
152
|
-
- Or ask Pi: *"How is that background security audit going?"*
|
|
155
|
+
> **You:** "Have Claude audit the API in the background while we review the release checklist. Then bring me the findings."
|
|
153
156
|
|
|
154
|
-
|
|
157
|
+
- **No automatic completion message:** background work does not notify or wake the parent. Pi collects once its independent work is done; if you ask to leave work running or check back later, Pi reports the run ID and proceeds or ends its turn without waiting.
|
|
158
|
+
- **Keep Pi open:** background work belongs to its originating session, not a persistent service. Orderly shutdown requests cancellation with bounded cleanup; restart does not adopt unfinished work.
|
|
159
|
+
- **Stopping is different from waiting:** interrupting the original foreground call cancels it. Interrupting `external_runs wait` only stops waiting; use `/external runs` or `external_runs cancel` to stop background work.
|
|
155
160
|
|
|
156
|
-
|
|
157
|
-
const receipt = await tools.external_delegate({
|
|
158
|
-
description: "Audit security-sensitive endpoints",
|
|
159
|
-
role: "reviewer",
|
|
160
|
-
harness: "claude",
|
|
161
|
-
permission: "readonly",
|
|
162
|
-
prompt: "Audit security-sensitive endpoints.",
|
|
163
|
-
background: true,
|
|
164
|
-
});
|
|
161
|
+
Both modes allow parallel workflow children under the same concurrency limit. Background does not change permissions, enforce new budget limits, or make work inherently faster or cheaper. Prompt instructions such as “do not edit” express intent; the supported `permission` tier defines enforced restrictions.
|
|
165
162
|
|
|
166
|
-
|
|
167
|
-
const runId = receipt.data.run.runId;
|
|
168
|
-
await tools.external_runs({ action: "wait", runIds: [runId], mode: "all" });
|
|
169
|
-
}
|
|
170
|
-
```
|
|
163
|
+
See [launch → independent work → collect](docs/supervision-and-runtime.md#background-example-launch--independent-work--collect) for the operational example, result handling, and cancellation details.
|
|
171
164
|
|
|
172
165
|
### 3. Multi-Agent Workflows (Parallel Delegation)
|
|
173
166
|
|
|
@@ -175,13 +168,17 @@ For tasks that benefit from multiple perspectives at once:
|
|
|
175
168
|
|
|
176
169
|
> **You:** "Run a parallel review: have Claude check for architectural issues while Codex audits test coverage."
|
|
177
170
|
|
|
178
|
-
Pi
|
|
171
|
+
Pi first explains who will do what, what runs in parallel, what may change, and the expected result. It then uses `external_workflow` to run both subagents under a shared concurrency limiter. The live card shows the roadmap and actual child activity; `/external runs` provides inspection and cancellation.
|
|
179
172
|
|
|
180
173
|
*(Workflow script):*
|
|
181
174
|
|
|
182
175
|
```js
|
|
183
|
-
export const meta = {
|
|
176
|
+
export const meta = {
|
|
177
|
+
apiVersion: 1, name: "code-review", description: "Parallel review",
|
|
178
|
+
phases: [{ title: "Review", detail: "Claude checks architecture while Codex checks test coverage; both use readonly access." }],
|
|
179
|
+
};
|
|
184
180
|
|
|
181
|
+
phase("Review");
|
|
185
182
|
const [arch, coverage] = await parallel([
|
|
186
183
|
() => agent("Review architectural integrity read-only", { role: "reviewer", harness: "claude", permission: "readonly" }),
|
|
187
184
|
() => agent("Audit test coverage read-only", { role: "qa", harness: "codex", permission: "readonly" }),
|
|
@@ -227,6 +224,7 @@ The direct delegation tool `Agent` is now `external_delegate`, with no alias. Up
|
|
|
227
224
|
- **[Harness Reference](docs/harness-reference.md):** Deep CLI flags, OpenCode 2 standalone server details, Muse session resume, Grok sandbox notes, and Pi in-process presets.
|
|
228
225
|
- **[Configuration Reference](docs/configuration-reference.md):** Settings v5 JSON schema, sparse inheritance, role overrides, and full command reference.
|
|
229
226
|
- **[Supervision & Runtime](docs/supervision-and-runtime.md):** `external_runs` actions, cursor pagination, batch inspect, and byte budgets.
|
|
230
|
-
- **[Workflows](docs/workflows.md):**
|
|
227
|
+
- **[Workflows](docs/workflows.md):** `external_workflow`, plain-language plans, live phases, error handling (`ChildRunError`), and replay cache.
|
|
228
|
+
- **[Workflow Tool Rename](docs/migration-workflow-to-external-workflow.md):** `workflow` → `external_workflow`, with no alias.
|
|
231
229
|
- **[Public Contract](docs/public-contract.md):** Contract envelope definitions for Pi codemode consumers.
|
|
232
230
|
- **[Troubleshooting](docs/troubleshooting.md):** Common errors, authentication checks, and `pi-cc-extensions` renderer compatibility.
|
package/docs/README.md
CHANGED
|
@@ -18,6 +18,7 @@ Core reference guides for configuring and using the extension with Pi:
|
|
|
18
18
|
| **[Troubleshooting](troubleshooting.md)** | Diagnostic recipes, authentication checks, and `pi-cc-extensions` renderer compatibility. |
|
|
19
19
|
| **[Migration Guide (v4 → v5)](migration-v4-to-v5.md)** | Upgrading from older configurations, backups, command mappings, and safe file purging. |
|
|
20
20
|
| **[Tool Rename Migration (`Agent` → `external_delegate`)](migration-agent-to-external-delegate.md)** | Updating codemode calls, hooks, receipt checks, and renderer exclusions after the direct tool rename. |
|
|
21
|
+
| **[Workflow Tool Rename (`workflow` → `external_workflow`)](migration-workflow-to-external-workflow.md)** | Updating workflow callers, hooks, receipt checks, and renderer exclusions; script helpers are unchanged. |
|
|
21
22
|
|
|
22
23
|
---
|
|
23
24
|
|
|
@@ -26,7 +27,7 @@ Specifications for programmatic subagent delegation and tool outputs:
|
|
|
26
27
|
|
|
27
28
|
| Document | Status | Purpose |
|
|
28
29
|
|---|---|---|
|
|
29
|
-
| **[Public Contract v1](public-contract.md)** | `Normative Contract` | Schema definitions, envelope shapes (`{ ok, data, error }`), and delivery invariants for `external_delegate`, `
|
|
30
|
+
| **[Public Contract v1](public-contract.md)** | `Normative Contract` | Schema definitions, envelope shapes (`{ ok, data, error }`), and delivery invariants for `external_delegate`, `external_workflow`, and `external_runs`. |
|
|
30
31
|
|
|
31
32
|
---
|
|
32
33
|
|
|
@@ -55,6 +56,6 @@ Non-normative proposals, design investigations, and dated implementation records
|
|
|
55
56
|
|
|
56
57
|
## Document Status Legend
|
|
57
58
|
|
|
58
|
-
- **`Normative Guide` / `Normative Contract`:** Authoritative documentation reflecting current
|
|
59
|
+
- **`Normative Guide` / `Normative Contract`:** Authoritative documentation reflecting the current implementation.
|
|
59
60
|
- **`Design Direction`:** Approved future UX/architectural targets; does not claim current implementation satisfies it.
|
|
60
61
|
- **`Historical Record` / `Archive`:** Preserved investigation notes and dated planning documents. Superseded where newer contracts exist.
|
|
@@ -44,26 +44,12 @@ return receipt.data.run;
|
|
|
44
44
|
- **Envelope:** `contractVersion` is still `1`. `action` is still `"delegate"`, and runs still report `kind: "agent"`. `ok`, `data`, `error`, and `warnings` keep their meaning. See [Public Contract v1](public-contract.md).
|
|
45
45
|
- **Settings and saved runs:** settings stay at version 5. No settings conversion or saved-run rewrite is needed.
|
|
46
46
|
- **Workflows:** the workflow `agent()` function keeps its name. It exists only inside workflow scripts and never appears in a tool list.
|
|
47
|
-
- **Other tools:** `workflow`, `external_runs`, and `external_help`
|
|
47
|
+
- **Other tools at the time of this rename:** `workflow`, `external_runs`, and `external_help` kept their names. In the subsequent release, `workflow` became [`external_workflow`](migration-workflow-to-external-workflow.md); use that name for current callers.
|
|
48
48
|
|
|
49
49
|
---
|
|
50
50
|
|
|
51
51
|
## Background Runs Still Need Collecting
|
|
52
52
|
|
|
53
|
-
|
|
53
|
+
Foreground is the default, including parallel workflows. Use background for useful independent work or an explicit user request—not routinely followed by an immediate wait. Background completion does not notify or wake the parent. Retain the ID and collect/check the outcome unless the user explicitly defers; keep the owning session open.
|
|
54
54
|
|
|
55
|
-
|
|
56
|
-
const receipt = await tools.external_delegate({
|
|
57
|
-
description: "Audit endpoints",
|
|
58
|
-
role: "reviewer",
|
|
59
|
-
prompt: "Audit the endpoints read-only.",
|
|
60
|
-
background: true,
|
|
61
|
-
});
|
|
62
|
-
if (!receipt.ok) return receipt.error;
|
|
63
|
-
const runId = receipt.data.run.runId;
|
|
64
|
-
|
|
65
|
-
await tools.external_runs({ action: "wait", runIds: [runId], mode: "all" });
|
|
66
|
-
const final = await tools.external_runs({ action: "inspect", runIds: [runId], view: "final" });
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
`final` holds only a verified successful answer. For a failed run, read the outcome from `wait` and the details with `view: "diagnostics"`.
|
|
55
|
+
Use inline successful output when available; otherwise inspect `final` and follow pagination. Failed runs need outcomes and `diagnostics`, not a successful final answer. See the [launch → independent work → collect example](supervision-and-runtime.md#background-example-launch--independent-work--collect) for the complete sequence and interruption rules.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Migration Guide: `workflow` Tool Renamed to `external_workflow`
|
|
2
|
+
|
|
3
|
+
The orchestration tool formerly named `workflow` is now `external_workflow`, alongside `external_delegate`, `external_runs`, and `external_help`. This follows the direct tool rename in [PR #86](https://github.com/tranhoangnguyen03/pi-flow-external/pull/86).
|
|
4
|
+
|
|
5
|
+
## No Alias
|
|
6
|
+
|
|
7
|
+
Flow no longer registers `workflow`. Old calls are not translated. A stale call fails, or reaches another extension's `workflow` tool if one is registered. Update every caller, then reload or restart Pi.
|
|
8
|
+
|
|
9
|
+
## What to Update
|
|
10
|
+
|
|
11
|
+
| Where | Before | After |
|
|
12
|
+
|---|---|---|
|
|
13
|
+
| Codemode calls | `tools.workflow({...})` | `tools.external_workflow({...})` |
|
|
14
|
+
| Codemode discovery | `describeTool("workflow")` | `describeTool("external_workflow")` |
|
|
15
|
+
| Exact-name hooks (`tool_call`, `tool_result`, `event.toolName`), handlers, and tool allow-lists such as `--tools` | `"workflow"` | `"external_workflow"` |
|
|
16
|
+
| Result discriminator: envelope `tool` field | `tool: "workflow"` | `tool: "external_workflow"` |
|
|
17
|
+
| `pi-cc-extensions` `excludeRenderers` | `"workflow"` | `"external_workflow"`, then restart Pi. Keep `"workflow"` only if another extension needs it. |
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
const receipt = await tools.external_workflow({
|
|
21
|
+
script: `export const meta = {
|
|
22
|
+
apiVersion: 1, name: "review", description: "Review the change without edits",
|
|
23
|
+
phases: [{ title: "Review", detail: "Claude reviews the change with readonly access." }],
|
|
24
|
+
};
|
|
25
|
+
phase("Review");
|
|
26
|
+
return await agent("Review the change without editing files", {
|
|
27
|
+
description: "Review change", role: "reviewer", harness: "claude", permission: "readonly",
|
|
28
|
+
});`,
|
|
29
|
+
});
|
|
30
|
+
if (!receipt.ok) return receipt.error;
|
|
31
|
+
return receipt.data.run;
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## What Does Not Change
|
|
35
|
+
|
|
36
|
+
- Inputs: `script`, `name`, `scriptPath`, `args`, `background`, and `resumeFromRunId` are unchanged.
|
|
37
|
+
- Script API: `meta.apiVersion: 1`, `agent()`, `parallel()`, `pipeline()`, `phase()`, and `log()` keep their names. Existing saved scripts do not need rewriting.
|
|
38
|
+
- Envelope: `contractVersion: 1`, `action: "run"`, run `kind: "workflow"`, and `wf_...` run IDs are unchanged.
|
|
39
|
+
- Settings stay at version 5. No settings conversion or saved-run rewrite is needed. The extension option `{ workflow: false }`, help topic `workflow`, workflow directories, and E2E `--workflow` flag are unchanged.
|
|
40
|
+
- `external_delegate`, `external_runs`, and `external_help` keep their names. No bridge-compatibility fix is claimed by this rename.
|
|
41
|
+
|
|
42
|
+
## Explain Work Before Launch
|
|
43
|
+
|
|
44
|
+
Parent guidance now requires a short plain-language briefing: purpose, assignments, parallel/sequential order, allowed changes, and deliverable. Put the roadmap in the existing optional `meta.phases` and use `phase()` at actual transitions. Phase details now display on the live card.
|
|
45
|
+
|
|
46
|
+
This is an instruction to the agent, not a runtime check that a chat message was sent. Planned phases describe intent; child states and activity describe observed execution. Existing saved scripts without phases remain valid. Follow the user's existing approval policy rather than treating every briefing as a new approval gate.
|
|
47
|
+
|
|
48
|
+
Background work still needs collecting through `external_runs`; its launch receipt is not a live monitor. Inspect `view: "launch"` to retrieve the recorded purpose and phase plan during or after execution. That explicit view also contains sensitive script/argument evidence. See [Workflows](workflows.md) for details.
|
package/docs/public-contract.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
# Public contract v1: external_delegate,
|
|
1
|
+
# Public contract v1: external_delegate, external_workflow and external_runs
|
|
2
2
|
|
|
3
|
-
`external_delegate`, `
|
|
3
|
+
`external_delegate`, `external_workflow` and `external_runs` declare `outputSchema` and return matching `structuredContent`. Human `content` and renderer `details` stay separate. Native Pi codemode receives the structured result, including failures. **Check `ok`**, not just whether the call threw.
|
|
4
4
|
|
|
5
5
|
```js
|
|
6
6
|
const receipt = await tools.external_delegate({ role: "worker", description: "Check build", prompt: "Run the build and report." });
|
|
@@ -23,7 +23,7 @@ Every result carries:
|
|
|
23
23
|
| Tool | `action` | Success `data` | Failure `data` |
|
|
24
24
|
|---|---|---|---|
|
|
25
25
|
| `external_delegate` | `delegate` | `{run}` | `{run}`, or `{run: null}` before registration |
|
|
26
|
-
| `
|
|
26
|
+
| `external_workflow` | `run` | `{run}` | `{run}`, or `{run: null}` before registration |
|
|
27
27
|
| `external_runs` | the requested action | the action's payload (below) | `null` |
|
|
28
28
|
|
|
29
29
|
A failure that has a run keeps that run's evidence. Read-only supervision never returns partial data on failure.
|
|
@@ -31,35 +31,78 @@ Workspace /path/to/project
|
|
|
31
31
|
|
|
32
32
|
---
|
|
33
33
|
|
|
34
|
-
## Background
|
|
34
|
+
## Foreground, Background & Lifecycle
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
**Foreground is the default:** omit `background` or set it to `false`. The original `external_delegate` or `external_workflow` call waits for settlement and returns the result or failure. Its live card carries progress while the parent waits.
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
**Background returns a handle:** set `background: true` when the parent has independent local work to do first, or when the user asks to run in the background, switch tasks, or check back in a later turn. The receipt confirms registration, not completion; a fast run may already be settled. The launch receipt is not a live monitor. Use `/external runs` to browse, or `external_runs` to inspect, wait, or cancel.
|
|
39
39
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
40
|
+
Use foreground when the result is needed next—even for a long workflow or parallel children. Never launch background then immediately wait; do your independent work first, or if leaving the run active across turns, report the run ID and proceed or end your turn without waiting. Both modes support `parallel()` under the same concurrency limiter; neither changes permission boundaries or budget enforcement. Prompt instructions express intent, not an enforced restriction.
|
|
41
|
+
|
|
42
|
+
### Background Example: Launch → Independent Work → Collect
|
|
43
|
+
|
|
44
|
+
Before launch, tell the user: “Claude will audit the API with readonly permission. While it runs, I’ll read the release checklist, then compare the findings.” For a workflow, also explain assignments and ordering as described in [Workflows](workflows.md#explain-the-plan-before-launch).
|
|
45
|
+
|
|
46
|
+
This codemode example assumes both paths exist; replace them with your project's absolute paths. It uses `external_delegate`, but collection is the same for `external_workflow` and its `wf_...` ID.
|
|
47
|
+
|
|
48
|
+
```js
|
|
49
|
+
const launched = await tools.external_delegate({
|
|
50
|
+
description: "Audit API routes",
|
|
51
|
+
role: "reviewer", harness: "claude", permission: "readonly",
|
|
52
|
+
prompt: "Audit /absolute/project/api for security defects. Do not edit files.",
|
|
47
53
|
background: true,
|
|
48
54
|
});
|
|
55
|
+
if (!launched.ok) throw new Error(launched.error.message);
|
|
56
|
+
let run = launched.data.run; // Retain this handle and its runId.
|
|
57
|
+
|
|
58
|
+
// Useful independent work, not an immediate wait:
|
|
59
|
+
const checklist = await tools.read({ path: "/absolute/project/RELEASE.md" });
|
|
60
|
+
|
|
61
|
+
if (!run.state.outcome) {
|
|
62
|
+
const waited = await tools.external_runs({
|
|
63
|
+
action: "wait", runIds: [run.runId], mode: "all",
|
|
64
|
+
});
|
|
65
|
+
if (!waited.ok) throw new Error(waited.error.message);
|
|
66
|
+
const settled = waited.data.completed.find(item => item.runId === run.runId);
|
|
67
|
+
if (!settled) return { pending: waited.data.pending, checklist };
|
|
68
|
+
run = settled;
|
|
69
|
+
}
|
|
49
70
|
|
|
50
|
-
if (
|
|
51
|
-
|
|
71
|
+
if (run.state.outcome === "succeeded" && run.output.delivery === "inline") {
|
|
72
|
+
return { run, result: run.output.value, checklist }; // null/false/0 are valid.
|
|
52
73
|
}
|
|
53
74
|
|
|
54
|
-
const
|
|
75
|
+
const view = run.state.outcome === "succeeded" ? "final" : "diagnostics";
|
|
76
|
+
let cursor;
|
|
77
|
+
let text = "";
|
|
78
|
+
let encoding;
|
|
79
|
+
do {
|
|
80
|
+
const inspected = await tools.external_runs({
|
|
81
|
+
action: "inspect", runIds: [run.runId], view, ...(cursor ? { cursor } : {}),
|
|
82
|
+
});
|
|
83
|
+
if (!inspected.ok) throw new Error(inspected.error.message);
|
|
84
|
+
if (view === "final" && !inspected.data.finalAvailable) {
|
|
85
|
+
throw new Error("No verified final answer is available");
|
|
86
|
+
}
|
|
87
|
+
text += inspected.data.page.text;
|
|
88
|
+
encoding = inspected.data.page.encoding;
|
|
89
|
+
cursor = inspected.data.page.nextCursor;
|
|
90
|
+
} while (cursor);
|
|
91
|
+
return { run, view, encoding, text, checklist };
|
|
55
92
|
```
|
|
56
93
|
|
|
57
|
-
|
|
94
|
+
- **Check the run, not just `ok`.** `wait.ok` means supervision worked, even if a child failed. Read each `completed[i].state.outcome` and all `pending` IDs. With multiple targets, a failed workflow can return early even in `mode: "all"`; pending work continues. Keep collecting it unless the user defers, or cancel it explicitly.
|
|
95
|
+
- **Avoid redundant calls.** If settlement is already known, inspect directly rather than wait again. Successful inline output needs no second fetch. A successful workflow can still have `children.failed` when the script handled child errors; check the returned reports.
|
|
96
|
+
- **Read every page.** Otherwise use `final` for verified success or `diagnostics`/`output` for failure. `final` is empty without verified success. Follow `page.nextCursor`, concatenate text, and only then parse if `encoding` is `json`. Workflow final JSON wraps the value in `{ runId, result, finalAvailable }`; agent finals are text.
|
|
97
|
+
- **Keep ownership.** The parent retains IDs and collects/checks results after its independent work finishes, unless the user asks to leave work running or check back later—in which case it reports the IDs and switches tasks or ends the turn without waiting. A script's local variables do not survive a separate codemode call; pass the retained handle or ID explicitly. If independent work or collection fails, report any still-running IDs rather than abandoning them silently. Never retry a child automatically.
|
|
98
|
+
|
|
99
|
+
### Interruption, Cancellation & Session Ownership
|
|
58
100
|
|
|
59
|
-
|
|
60
|
-
- **
|
|
61
|
-
- **
|
|
62
|
-
- **
|
|
101
|
+
- **Foreground interruption:** interrupting the original blocking call cancels that work.
|
|
102
|
+
- **Wait interruption:** interrupting `external_runs wait` only stops waiting; it does not cancel the selected runs. Use `external_runs cancel` to stop background work. Cancellation requests are not proof of completed cleanup; check settlement.
|
|
103
|
+
- **Workflow cancellation:** cancelling the root stops its active children. Cancelling one child throws a catchable child error; a script that handles it may continue.
|
|
104
|
+
- **No automatic delivery:** ending a turn does not stop background work; it continues in the owning session. Background completion does not notify, wake, or resume the parent.
|
|
105
|
+
- **Not a persistent service:** keep the owning Pi session open. Orderly shutdown requests cancellation and waits for bounded cleanup. Crashes or force-kills leave unfinished evidence `interrupted_or_uncertain`; restarting Pi never adopts live orphan work.
|
|
63
106
|
|
|
64
107
|
---
|
|
65
108
|
|
|
@@ -110,10 +153,11 @@ Block until one or more targets reach terminal state:
|
|
|
110
153
|
const waitResult = await external_runs({
|
|
111
154
|
action: "wait",
|
|
112
155
|
runIds: [runId1, runId2],
|
|
113
|
-
mode: "all", //
|
|
156
|
+
mode: "all", // A failed workflow can return early; always check data.pending.
|
|
114
157
|
limitBytes: 32768,
|
|
115
158
|
});
|
|
116
159
|
```
|
|
160
|
+
- **Wait Modes:** `any` returns after a selected target settles; `all` waits for all, except an unsuccessful workflow returns early. Check `completed` outcomes and `pending` IDs; `ok: true` does not mean every run succeeded.
|
|
117
161
|
- **Live Progress Heartbeat:** While waiting, the tool emits bounded progress updates (watched targets, completed/pending counts, recent activity) through Pi's update channel.
|
|
118
162
|
- **Shared Budget:** The `limitBytes` cap is spent across settled results in requested order. If a result exceeds remaining space, it returns `resultTruncated: true` with references to inspect the rest.
|
|
119
163
|
|
|
@@ -131,7 +175,7 @@ await external_runs({
|
|
|
131
175
|
|
|
132
176
|
## Operational Evidence & Run Storage
|
|
133
177
|
|
|
134
|
-
|
|
178
|
+
Normal runs write private, best-effort evidence to disk; unavailable storage does not discard a settled in-memory result:
|
|
135
179
|
|
|
136
180
|
```text
|
|
137
181
|
~/.pi/agent/pi-flow-external/runs/<run-id>/
|
package/docs/troubleshooting.md
CHANGED
|
@@ -59,14 +59,14 @@ Solutions to common issues, authentication questions, and environment quirks whe
|
|
|
59
59
|
|
|
60
60
|
## Third-Party UI Compatibility: `pi-cc` / `ccstyle`
|
|
61
61
|
|
|
62
|
-
If using [`pi-cc-extensions`](https://www.npmjs.com/package/pi-cc-extensions), its default rendering overrides can cause `
|
|
62
|
+
If using [`pi-cc-extensions`](https://www.npmjs.com/package/pi-cc-extensions), its default rendering overrides can cause `external_workflow` and `external_runs` wait progress cards to show a generic `Pending…` message.
|
|
63
63
|
|
|
64
64
|
### Resolution
|
|
65
65
|
Add `pi-flow-external` tool names to `excludeRenderers` in `~/.pi/agent/pi-cc-extensions.json`:
|
|
66
66
|
|
|
67
67
|
```json
|
|
68
68
|
{
|
|
69
|
-
"excludeRenderers": ["external_delegate", "
|
|
69
|
+
"excludeRenderers": ["external_delegate", "external_workflow", "external_runs"]
|
|
70
70
|
}
|
|
71
71
|
```
|
|
72
72
|
|
package/docs/workflows.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Workflows
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The `external_workflow` tool provides trusted JavaScript orchestration across external AI harnesses and in-process Pi models, enabling parallel fan-out, multi-stage pipelines, and structured failure handling. The former `workflow` tool name is not an alias; see the [rename migration guide](migration-workflow-to-external-workflow.md).
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -13,8 +13,13 @@ export const meta = {
|
|
|
13
13
|
apiVersion: 1,
|
|
14
14
|
name: "code-review",
|
|
15
15
|
description: "Parallel architecture and QA review",
|
|
16
|
+
phases: [
|
|
17
|
+
{ title: "Review", detail: "Claude checks architecture while Codex checks tests; both use readonly access." },
|
|
18
|
+
{ title: "Collect", detail: "Return both reports for the parent to compare." },
|
|
19
|
+
],
|
|
16
20
|
};
|
|
17
21
|
|
|
22
|
+
phase("Review");
|
|
18
23
|
// Orchestrate subagents:
|
|
19
24
|
const [arch, qa] = await parallel([
|
|
20
25
|
() => agent("Review architecture read-only", {
|
|
@@ -29,11 +34,38 @@ const [arch, qa] = await parallel([
|
|
|
29
34
|
}),
|
|
30
35
|
]);
|
|
31
36
|
|
|
37
|
+
phase("Collect");
|
|
32
38
|
return { arch, qa };
|
|
33
39
|
```
|
|
34
40
|
|
|
35
41
|
---
|
|
36
42
|
|
|
43
|
+
## Explain the Plan Before Launch
|
|
44
|
+
|
|
45
|
+
Before calling `external_workflow`, the parent agent must tell the user, in plain language:
|
|
46
|
+
|
|
47
|
+
- The purpose and which agent/harness handles each assignment.
|
|
48
|
+
- What runs in parallel and what waits for earlier results.
|
|
49
|
+
- What may change and what deliverable the user will receive.
|
|
50
|
+
|
|
51
|
+
For example: “Claude will review the architecture while Codex checks test coverage, both with readonly access. Then I’ll compare their reports and return one prioritized list.” Follow the user's existing approval policy; a briefing is not an extra approval gate. Explain material changes to the plan.
|
|
52
|
+
|
|
53
|
+
Put the same roadmap in `meta.phases`, an optional ordered array of `{ title, detail? }`, and call `phase(title)` when each stage actually begins. Short phase details explain the work on the live card; future stages appear as planned. Existing saved scripts without phases remain valid.
|
|
54
|
+
|
|
55
|
+
**A plan is intent, not proof of execution.** The briefing is an agent instruction, not a runtime-verified chat message. Runtime validation checks phase metadata, not whether the script follows its prose. Child states and activity are the source of truth for observed execution. Do not invent completion estimates or call a quiet agent healthy merely because a spinner moves.
|
|
56
|
+
|
|
57
|
+
Choose foreground by default when you need the result next, including parallel workflows. Use background when you have independent local work to do first, or when the user asks to leave the workflow running across turns; it does not make children more parallel. While a foreground call runs, the live card carries progress; the parent cannot reliably send chat updates from inside that call. A background receipt is not a live monitor, and completion does not wake the parent. Keep the owning session open and collect results after independent work finishes, or report the run ID and switch tasks/end the turn when the user wants to check back later. See [execution and collection](supervision-and-runtime.md#foreground-background--lifecycle) for the complete lifecycle and interruption rules.
|
|
58
|
+
|
|
59
|
+
The recorded purpose and phase details remain accessible in explicit launch inspection, during execution and after completion:
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
await tools.external_runs({ action: "inspect", runIds: [runId], view: "launch" });
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Launch evidence also includes the script and arguments and may contain sensitive information. Follow `nextCursor` and concatenate pages when the response is paged.
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
37
69
|
## Global Functions & Environment
|
|
38
70
|
|
|
39
71
|
The workflow sandbox exposes these helpers:
|
|
@@ -41,7 +73,7 @@ The workflow sandbox exposes these helpers:
|
|
|
41
73
|
- **`agent(prompt, options)`:** Delegates one subagent task.
|
|
42
74
|
- `options.role`: Role name (`explorer`, `reviewer`, `implementer`, etc.).
|
|
43
75
|
- `options.harness`: Optional harness override (`claude`, `codex`, `agy`, `grok`, `muse`, `opencode`, or named `pi-*`).
|
|
44
|
-
- `options.permission`:
|
|
76
|
+
- `options.permission`: Permission tier (`readonly`, `edit`, or `danger`).
|
|
45
77
|
- `options.context`: Context sharing (`{ mode: "recent", turns: 5 }` or `{ mode: "full" }`).
|
|
46
78
|
- `options.description`: User-facing task label in UI.
|
|
47
79
|
- `options.schema`: Optional JSON / TypeBox schema for structured return values (supported on Pi subagents and native CLI schemas).
|
|
@@ -50,7 +82,7 @@ The workflow sandbox exposes these helpers:
|
|
|
50
82
|
- **`pipeline(items, ...stages)`:** Feeds items sequentially through transformation stages.
|
|
51
83
|
- **`phase(title)`:** Marks milestone boundaries in live progress presentation.
|
|
52
84
|
- **`log(message)`:** Appends diagnostic notes to the workflow journal.
|
|
53
|
-
- **`args`:** Input JSON arguments passed via `
|
|
85
|
+
- **`args`:** Input JSON arguments passed via `external_workflow({ args: { ... } })`.
|
|
54
86
|
- **`cwd`:** Current working directory of the Pi session.
|
|
55
87
|
|
|
56
88
|
---
|
|
@@ -96,7 +128,7 @@ When a workflow begins, it freezes an immutable snapshot of all settings, role i
|
|
|
96
128
|
When executing a saved workflow file from disk, you can pass `resumeFromRunId` to replay:
|
|
97
129
|
|
|
98
130
|
```ts
|
|
99
|
-
await
|
|
131
|
+
await tools.external_workflow({
|
|
100
132
|
scriptPath: "/path/to/workflow.js",
|
|
101
133
|
resumeFromRunId: "wf_previous_run_id",
|
|
102
134
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tranhoangnguyen0310/pi-flow-external",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.0.1-external.0",
|
|
4
4
|
"description": "External Claude Code, Codex CLI, Antigravity, Grok Build CLI, Muse Code, and OpenCode delegation for pi.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./index.ts",
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
"docs/troubleshooting.md",
|
|
22
22
|
"docs/migration-v4-to-v5.md",
|
|
23
23
|
"docs/migration-agent-to-external-delegate.md",
|
|
24
|
+
"docs/migration-workflow-to-external-workflow.md",
|
|
24
25
|
"docs/public-contract.md"
|
|
25
26
|
],
|
|
26
27
|
"keywords": [
|
package/src/contract/envelope.ts
CHANGED
|
@@ -25,7 +25,7 @@ export function contractEnvelope<S extends TSchema, F extends TSchema>(tool: str
|
|
|
25
25
|
return Type.Union([successVariant(tool, action, success), failureVariant(tool, action, failure)]);
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
/** Builds one envelope as plain JSON. `redact` re-redacts the whole envelope (
|
|
28
|
+
/** Builds one envelope as plain JSON. `redact` re-redacts the whole envelope (external_delegate and external_workflow receipts). */
|
|
29
29
|
export function envelope({ tool, action, data, warnings = [], error, redact = false }: {
|
|
30
30
|
tool: string;
|
|
31
31
|
action: string;
|
package/src/contract/workflow.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { Type } from "typebox";
|
|
2
|
+
import { EXTERNAL_WORKFLOW_TOOL } from "../tool-names.ts";
|
|
2
3
|
import type { RegisteredRunEntry } from "../core/run-registry.ts";
|
|
3
4
|
import { contractEnvelope, INLINE_RESULT_BYTES, type PublicError } from "./envelope.ts";
|
|
4
5
|
import { liveWorkflowRun, workflowRunSchema, type EvidenceIntegrity, type PublicRun } from "./run.ts";
|
|
5
6
|
|
|
6
7
|
const workflowData = Type.Object({ run: Type.Union([Type.Null(), workflowRunSchema]) });
|
|
7
8
|
/** `ok` describes the workflow operation: a background launch is accepted, a foreground one settled successfully. */
|
|
8
|
-
export const workflowOutputSchema = contractEnvelope(
|
|
9
|
+
export const workflowOutputSchema = contractEnvelope(EXTERNAL_WORKFLOW_TOOL, Type.Literal("run"), workflowData, workflowData);
|
|
9
10
|
|
|
10
11
|
export const HANDLED_CHILD_FAILURES = "handled_child_failures";
|
|
11
12
|
|
package/src/core/concurrency.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* A shared in-flight concurrency limiter (semaphore + FIFO wait-queue).
|
|
3
3
|
*
|
|
4
|
-
* One instance is shared across the `
|
|
4
|
+
* One instance is shared across the `external_delegate` tool and the `external_workflow` tool so the
|
|
5
5
|
* global cap bounds the total number of concurrent subagents across both entry
|
|
6
6
|
* points — not per-tool.
|
|
7
7
|
*
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* - `tryAcquire()` — synchronous, non-blocking. Returns a release fn if a slot
|
|
10
10
|
* is free, else `null`. Kept for direct semaphore-style callers/tests.
|
|
11
11
|
* - `acquire(signal?)` — async. Resolves with a release fn when a slot frees.
|
|
12
|
-
* Used by both the `
|
|
12
|
+
* Used by both the `external_delegate` tool and workflow `agent()` calls so excess
|
|
13
13
|
* subagents queue and drain under the shared cap. Honors an AbortSignal.
|
|
14
14
|
*
|
|
15
15
|
* Correctness / no over-subscription: `active` is incremented only on the fast
|
package/src/core/spawn.ts
CHANGED
|
@@ -45,18 +45,18 @@ import { runRecordsDirectory } from "./retention.ts";
|
|
|
45
45
|
import { createTimeoutSignal, markSubagentTimedOut } from "./timeout.ts";
|
|
46
46
|
import type { PermissionResolution } from "./permissions.ts";
|
|
47
47
|
import { effectivePiResourcePreset, isValidThinkingLevel, VALID_THINKING_LEVELS } from "../harnesses.ts";
|
|
48
|
-
import { EXTERNAL_DELEGATE_TOOL } from "../tool-names.ts";
|
|
48
|
+
import { EXTERNAL_DELEGATE_TOOL, EXTERNAL_WORKFLOW_TOOL } from "../tool-names.ts";
|
|
49
49
|
|
|
50
50
|
/**
|
|
51
51
|
* The delegation tools a spawned child must never receive, so subagents cannot
|
|
52
52
|
* recursively fan out. Owned by the spawn core and used as the default exclude
|
|
53
53
|
* list so no caller can accidentally under-specify the nesting block.
|
|
54
54
|
*/
|
|
55
|
-
export const CHILD_EXCLUDED_TOOLS: readonly string[] = [EXTERNAL_DELEGATE_TOOL,
|
|
55
|
+
export const CHILD_EXCLUDED_TOOLS: readonly string[] = [EXTERNAL_DELEGATE_TOOL, EXTERNAL_WORKFLOW_TOOL];
|
|
56
56
|
|
|
57
57
|
/**
|
|
58
58
|
* Parameters for a single subagent run. This is the shared spawn primitive used
|
|
59
|
-
* by both the `
|
|
59
|
+
* by both the `external_delegate` tool and the `external_workflow` tool's `agent()` global.
|
|
60
60
|
* Concurrency accounting lives in the callers, not here; callers acquire a slot
|
|
61
61
|
* before invoking spawnSubagent, so the runtime timeout below excludes queue time.
|
|
62
62
|
*/
|
|
@@ -17,8 +17,8 @@ export const RICH_SUBAGENT_ACTIVE_LIMIT = 4;
|
|
|
17
17
|
* initializes (`initTheme()`); a headless/RPC render, an uninitialized test
|
|
18
18
|
* harness, or any other caller without that startup step must still produce
|
|
19
19
|
* a readable row instead of crashing the tool card. This is the single
|
|
20
|
-
* shared "render a result body" primitive for `
|
|
21
|
-
* `
|
|
20
|
+
* shared "render a result body" primitive for `external_delegate` (subagent-render.ts),
|
|
21
|
+
* `external_workflow` (its own final-output block), and `external_runs`'s output/
|
|
22
22
|
* final views — the last mile of the shared-rendering fix for issue #52's
|
|
23
23
|
* "still plain Text for output rather than rendered Markdown" gap.
|
|
24
24
|
*/
|
package/src/external-help.ts
CHANGED
|
@@ -90,33 +90,37 @@ function validateHarnessFilter(harness: string | undefined, configuredPiHarnesse
|
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
function workflowHelp(workflowsEnabled: boolean, workflows: ReturnType<typeof listSavedWorkflows>): string {
|
|
93
|
-
const availability = workflowsEnabled ? "The
|
|
93
|
+
const availability = workflowsEnabled ? "The external_workflow tool is enabled." : "The external_workflow tool is disabled for this extension instance.";
|
|
94
94
|
return `${availability} It runs trusted JavaScript, not a sandbox.
|
|
95
95
|
|
|
96
96
|
Provide exactly one workflow source: name, scriptPath, or script. Every script starts with the literal current declaration export const meta = { apiVersion: 1, name, description }, calls agent() at least once, awaits every started call, and returns JSON-serializable data. Missing/unsupported API versions fail before any child launches. Project .pi/workflows are visible only when the project is trusted.
|
|
97
97
|
|
|
98
|
+
Before calling external_workflow, explain the purpose, assignments, parallel/sequential order, allowed changes, and deliverable in plain language. Add the same roadmap as meta.phases: [{ title: "Review", detail: "Who checks what and whether files may change" }, ...], and call phase("Review") when that stage actually starts. Planned phases and details remain visible on the live card; they describe intent, while child states/activity show observed execution. Explain material plan changes and follow the user's existing approval policy. The briefing is an agent instruction, not a runtime-verified chat message; phases remain optional for existing saved scripts. Use external_runs inspect view launch to retrieve the recorded purpose and plan (also includes sensitive script/args); background receipts are not live monitors.
|
|
99
|
+
|
|
98
100
|
APIs: agent(prompt, { description, label, role, harness, subagent_type, permission, max_budget_usd, resume, context, schema, phase }); parallel(thunks); pipeline(items, ...stages); phase(title); log(message). Globals: args and cwd. Agent options other than the profile selector are optional. "description" is the common task-name option shared with the direct external_delegate tool; "label" is a compatible alias — set only one, or both to the same value. Choose role/harness or legacy exact subagent_type, never both. Use unique descriptions/labels and clear task prompts. An agent() succeeds with its value or throws ChildRunError { runId, outcome, message, outputRef, diagnosticsRef }; catch optional failures explicitly. Uncaught failures terminate the workflow and drain siblings. Helpers never convert failures to null. Context options: {mode:"none"} (default), {mode:"recent",turns:N} (positive integer, includes current user turn), or {mode:"full"} (available context after compaction). All children share invocation-time context/settings; earlier child results must still be passed explicitly. Context excludes thinking/system instructions and pending calls; images and snapshots over 1 MiB fail without truncation. Context sharing cannot be combined with resume. Use schema for results that control branching or aggregation. Imports, filesystem globals, Date APIs, and Math.random() are unavailable.
|
|
99
101
|
|
|
100
|
-
|
|
102
|
+
Execution mode: foreground is blocking by default when you need the result next, including parallel workflows. Pass background:true when you have independent local work (read, edit, bash) that does not touch files the workflow may change, or on an explicit background request; do that independent work first and never launch background then immediately wait. Ending a turn does not stop a background run: if the user asks to leave the workflow running, switch tasks, or check back later, report the runId and work on the other task or end your turn without waiting; otherwise retain the ID and collect it after your independent work finishes. Both modes support parallel([() => agent(...), ...]) under the same maxConcurrentSubagents cap; sequential awaits stay serial. Background changes neither permissions nor budget enforcement. Its stable handle means accepted, not completed; the receipt is not a live monitor. No completion notification wakes this session. Keep the session open: orderly shutdown requests cancellation with bounded cleanup; crashes leave uncertain evidence and restart does not adopt work. Topic usage contains the worked collection guidance.
|
|
103
|
+
|
|
104
|
+
Supervision: external_runs selects targets with runIds. list takes optional workflowRunId, cursor, workflowCursor, and limit. inspect with one id reads summary/output/diagnostics/final/launch and pages with cursor and limitBytes; several ids (up to 20) batch summary only. wait takes up to 100 runIds in mode any or all; check ok for the operation, then each completed run's outcome and pending IDs. It never cancels pending work and can return early on a failed workflow even in all mode. If settlement is already known, skip wait. Use inline successful output when available; otherwise inspect final and follow nextCursor. Failures need diagnostics/output, not final. Follow nextWorkflowCursor for workflow listings too. Foreground-call interruption cancels that call; interrupting wait only stops waiting. Use cancel with one id and an optional reason to stop background work. Cancelling a workflow stops active children; cancelling one child is a catchable workflow error. There is no live steering.
|
|
101
105
|
|
|
102
106
|
Replay: resumeFromRunId works only with persisted scriptPath and starts an explicit new attempt. It reuses the longest unchanged prefix of successful child calls; the first changed/failed/cancelled/timed-out call and its suffix run again. Script recomposition is cheap, but child reruns can cost money or repeat side effects. There is no automatic repaired-script replay.
|
|
103
107
|
|
|
104
|
-
Background is
|
|
108
|
+
Background is an external_workflow tool call parameter ({ script, background: true }), never a meta field — meta.background is silently ignored and does not run the workflow in the background.
|
|
105
109
|
|
|
106
|
-
|
|
107
|
-
export const meta = {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
export const meta = { apiVersion: 1, name: "review", description: "Map and review a repository" };
|
|
110
|
+
Example (blocking by default; pass background:true on the external_workflow call when doing independent local work first or on a background request):
|
|
111
|
+
export const meta = {
|
|
112
|
+
apiVersion: 1, name: "review", description: "Map and review a repository",
|
|
113
|
+
phases: [
|
|
114
|
+
{ title: "Check", detail: "The default harness maps the repository while Codex reviews it; neither is assigned edits." },
|
|
115
|
+
{ title: "Collect", detail: "Return both reports for the parent to compare." },
|
|
116
|
+
],
|
|
117
|
+
};
|
|
118
|
+
phase("Check");
|
|
116
119
|
const [map, review] = await parallel([
|
|
117
120
|
() => agent("Map /absolute/repo read-only.", { label: "map", role: "explorer" }),
|
|
118
121
|
() => agent("Review /absolute/repo read-only.", { label: "review", role: "reviewer", harness: "codex" }),
|
|
119
122
|
]);
|
|
123
|
+
phase("Collect");
|
|
120
124
|
return { map, review };
|
|
121
125
|
|
|
122
126
|
${formatSavedWorkflows(workflows)}`;
|
package/src/pi-subagent.ts
CHANGED
|
@@ -76,7 +76,7 @@ export const agentToolParameters = Type.Object({
|
|
|
76
76
|
description: "The task briefing to send to the subagent.",
|
|
77
77
|
}),
|
|
78
78
|
background: Type.Optional(Type.Boolean({
|
|
79
|
-
description: "
|
|
79
|
+
description: "Defaults to false: wait for settlement. True returns a stable session-owned run handle after registration; work may already be settled. Pass true when you have independent local work to do first (do it, then call external_runs wait), or when the user asks to leave work running across turns (report the runId and do not call external_runs wait this turn). No completion notification; keep the owning session open.",
|
|
80
80
|
})),
|
|
81
81
|
role: Type.Optional(Type.String({
|
|
82
82
|
minLength: 1,
|
|
@@ -623,7 +623,7 @@ function createAgentTool(
|
|
|
623
623
|
});
|
|
624
624
|
}
|
|
625
625
|
return receipt(textResult(
|
|
626
|
-
`Subagent "${params.description}" (${subagentType}) queued as ${runRecord.runId}.
|
|
626
|
+
`Subagent "${params.description}" (${subagentType}) queued as ${runRecord.runId}. Do independent local work first, or report the runId and proceed/end your turn without waiting; use external_runs when ready to inspect, wait, or cancel.`,
|
|
627
627
|
{
|
|
628
628
|
description: params.description,
|
|
629
629
|
subagentType,
|
package/src/profile-creator.ts
CHANGED
|
@@ -36,13 +36,13 @@ A role is shared authoring only: one markdown file under pi-flow-external/roles/
|
|
|
36
36
|
|
|
37
37
|
Describe read-only or editing intent in the instructions. Do not ask for a permission tier: a role does not grant authority. The caller passes permission, or the global defaultPermission applies. Do not ask about backend, model, or thinking: shared roles never pin those, and backend-specific execution settings belong in /external config role set <role> --harness <name>; instruction replacements use /external config role edit <role> --harness <name>. When ready, summarize once and call ${ROLE_TOOL_NAME}.
|
|
38
38
|
|
|
39
|
-
Do not write files yourself and do not run external_delegate or
|
|
39
|
+
Do not write files yourself and do not run external_delegate or external_workflow. The tool will show what will be created for review, request confirmation, and write it offline without a backend smoke test. A role is not an authenticated connection; readiness smoke testing belongs to harness registration.`;
|
|
40
40
|
|
|
41
41
|
export const HARNESS_INTERVIEW_PROMPT = `Help me register one named Pi harness configuration through an AI-assisted interview.
|
|
42
42
|
|
|
43
43
|
Ask for a "pi-<label>" name, a provider/model id (validated live against the model registry), an optional thinking level (off/minimal/low/medium/high/xhigh; default off), and a resource preset. The preset is a simple choice of minimal or skills. minimal is the default and loads no skills. skills loads installed skills; project skills load only when the project is trusted. The six canonical roles (explorer, planner, implementer, reviewer, qa, worker) become automatically available on this harness the moment it is registered — no per-role file needed. When ready, summarize once and call ${HARNESS_TOOL_NAME}.
|
|
44
44
|
|
|
45
|
-
Do not write files yourself and do not run external_delegate or
|
|
45
|
+
Do not write files yourself and do not run external_delegate or external_workflow in this branch. The tool will show what will be registered for review, request confirmation, stage it, run a real in-process pi smoke test, and either install it or roll it back.`;
|
|
46
46
|
|
|
47
47
|
const roleParameters = Type.Object({
|
|
48
48
|
name: Type.String({ description: "Lowercase shared role name, such as security-reviewer. No backend prefix; reviewer replaces the built-in reviewer." }),
|
package/src/profiles.ts
CHANGED
|
@@ -264,7 +264,7 @@ export function loadExternalCatalog(agentDir = getAgentDir(), options: { legacyI
|
|
|
264
264
|
const { frontmatter } = parseFrontmatter<Record<string, unknown>>(content);
|
|
265
265
|
const obsolete = ["permission", "capabilitySet"].filter((key) => Object.prototype.hasOwnProperty.call(frontmatter, key));
|
|
266
266
|
if (obsolete.length) {
|
|
267
|
-
throw new Error(`obsolete metadata ${obsolete.join(", ")} does not grant authority. Remove it. A role describes intent; pass permission on the external_delegate or
|
|
267
|
+
throw new Error(`obsolete metadata ${obsolete.join(", ")} does not grant authority. Remove it. A role describes intent; pass permission on the external_delegate or external_workflow call, or set defaultPermission. Pi skills follow the harness preset.`);
|
|
268
268
|
}
|
|
269
269
|
if (Object.keys(frontmatter).some((key) => key !== "description")) throw new Error("shared roles support description only; use an exact override for execution settings");
|
|
270
270
|
} else {
|
package/src/prompts.ts
CHANGED
|
@@ -92,13 +92,15 @@ export function buildCoordinatorPrompt(
|
|
|
92
92
|
|
|
93
93
|
${formatExternalRoleCatalog(profiles, defaultHarness, configuredHarnessNames)}
|
|
94
94
|
|
|
95
|
-
Own the result and verify it yourself. Do the work directly unless a separate harness, a fresh context, or a bounded parallel review earns the overhead. A role is the job. A harness (${EXTERNAL_HARNESSES.join(", ")}, or a registered pi-* name) is the environment. Prefer the configured default harness unless the user asks for another or the task needs a capability the default lacks. Select both through external_delegate or
|
|
95
|
+
Own the result and verify it yourself. Do the work directly unless a separate harness, a fresh context, or a bounded parallel review earns the overhead. A role is the job. A harness (${EXTERNAL_HARNESSES.join(", ")}, or a registered pi-* name) is the environment. Prefer the configured default harness unless the user asks for another or the task needs a capability the default lacks. Select both through external_delegate or external_workflow. Follow the user's own approval policy before widening a delegation. This extension has no fixed multi-agent approval threshold.
|
|
96
|
+
|
|
97
|
+
Before external_workflow, explain the purpose, who does what, which tasks run in parallel or in sequence, allowed changes, and the expected deliverable in plain language. Put the same roadmap in meta.phases with short titles and details, and use phase() to mark actual transitions. Explain material plan changes; follow the user's existing approval policy. Planned phases describe intent, not proof of execution: actual child states and activity are authoritative.
|
|
96
98
|
|
|
97
99
|
Give every child an objective, the context it needs, the changes it may make, a deliverable, and the check you will run. Use absolute paths and say whether the task is read-only or may edit. Permission is the call's tier, otherwise defaultPermission (danger unless changed). Omit it when the child should keep that default, including a reviewer that may use a shell and still must not edit. A danger tier still has to stay inside the authorization the user gave you. agy accepts only danger and rejects readonly and edit.
|
|
98
100
|
|
|
99
101
|
Choose a fresh child or resume on purpose. Omit context for independent work. context {mode:"recent", turns:N} shares the last N user turns, including the current turn; {mode:"full"} shares the available post-compaction conversation. Snapshots drop thinking, system instructions, and pending calls, reject images and payloads over 1 MiB, and are sent to that harness. resume continues the same CLI child and cannot be combined with sharing. A named pi-* harness has no persisted session and no enforced budget. Prefer resume for the same task and a new child for an independent review.
|
|
100
102
|
|
|
101
|
-
|
|
103
|
+
Default to foreground (blocking) when you need the result next, including parallel workflows. When you have independent local work (read, edit, bash) that does not touch files the delegated run may change, or the user asks for background execution, pass background:true, do that other work first, and only call external_runs wait once you actually need the result; never background then immediately wait. Ending a turn does not stop a background run: if the user asks to leave it running, switch tasks, or check back later, report the runId and end your turn or work on the other task without waiting. Otherwise retain the runId and collect it in this turn after your independent work finishes. Foreground has a live card; a background receipt is not a live monitor and completion never notifies or wakes this session. Use external_runs wait only when needed; check each run's outcome and pending IDs. Read inline successful output or inspect view final; inspect failures with diagnostics. Interrupting foreground cancels that call; interrupting wait only stops waiting. Explicit cancel stops background work. Keep the owning session open: orderly shutdown requests cancellation; restart does not adopt work. Prefer one external_workflow with parallel() for coordinated parallel tasks; all children share maxConcurrentSubagents. Sequential awaits stay serial. Report milestones. Retry a failed run, or switch its model or harness, only when the user authorized that step. agy alone may make one disclosed infrastructure retry. Nested agents may start in another workspace.
|
|
102
104
|
|
|
103
105
|
Catalog availability reflects enabled harnesses and built-in/authored roles, not CLI installation or authentication. Disabled harnesses reject calls without fallback; manage them through /external config. Harness and role lifecycle commands live under /external config harness and /external config role. Role selection never falls back. external_help topic usage is the playbook; roles, permissions, and workflow are the references.`;
|
|
104
106
|
}
|
|
@@ -132,7 +134,7 @@ export const USAGE_INDEPENDENT_REVIEW: UsageAgentExample = {
|
|
|
132
134
|
context: { mode: "none" },
|
|
133
135
|
};
|
|
134
136
|
|
|
135
|
-
/** Returns after registration. The parent
|
|
137
|
+
/** Returns after registration. The parent does independent local work first or defers collection across turns before reading through external_runs. */
|
|
136
138
|
export const USAGE_BACKGROUND_AGENT: UsageAgentExample = {
|
|
137
139
|
description: "Background map",
|
|
138
140
|
prompt: "Map /absolute/repo read-only and return a short file list. Do not edit.",
|
|
@@ -187,10 +189,12 @@ export function usageWorkflowScript(): string {
|
|
|
187
189
|
}
|
|
188
190
|
}`);
|
|
189
191
|
return [
|
|
190
|
-
'export const meta = { apiVersion: 1, name: "parallel-review", description: "Review in parallel and keep a sibling failure" };',
|
|
192
|
+
'export const meta = { apiVersion: 1, name: "parallel-review", description: "Review in parallel and keep a sibling failure", phases: [{ title: "Review", detail: "Claude maps the repository while Codex reviews concurrency; neither is assigned to edit files." }, { title: "Collect", detail: "Return both findings, including any failed check." }] };',
|
|
193
|
+
'phase("Review");',
|
|
191
194
|
"const settled = await parallel([",
|
|
192
195
|
branches.join(",\n"),
|
|
193
196
|
"]);",
|
|
197
|
+
'phase("Collect");',
|
|
194
198
|
"return { settled };",
|
|
195
199
|
"",
|
|
196
200
|
].join("\n");
|
|
@@ -203,7 +207,7 @@ function jsonBlock(value: unknown): string {
|
|
|
203
207
|
/** Worked playbook served by external_help topic "usage". Examples are the exported constants. */
|
|
204
208
|
export function formatUsagePlaybook(): string {
|
|
205
209
|
const script = usageWorkflowScript();
|
|
206
|
-
return `Practical delegation playbook. A role is the work. A harness is the environment: ${EXTERNAL_HARNESSES.join(", ")}, or a registered pi-* name. external_delegate and
|
|
210
|
+
return `Practical delegation playbook. A role is the work. A harness is the environment: ${EXTERNAL_HARNESSES.join(", ")}, or a registered pi-* name. external_delegate and external_workflow select every one of those harnesses the same way. Prefer the configured default harness. Name another when the user asks for it or the task needs a capability the default lacks. Do the task directly unless a separate environment, an independent context, or bounded parallel work is worth the coordination. You still own the outcome: read the result and check the deliverable. Follow the user's approval policy before widening a delegation. There is no fixed team-size threshold here.
|
|
207
211
|
|
|
208
212
|
A named pi-* harness runs in-process on the SDK's built-in tools. Preset minimal leaves skills unloaded. Preset skills loads installed skills, and project skills only when the project is trusted. Those presets select skill loading. They are not this extension's tools and they are not web search. Resume does not continue a pi-* child, and a budget there is recorded without enforcement.
|
|
209
213
|
|
|
@@ -211,7 +215,9 @@ Tell each child the objective, the context it needs, what it may change, the del
|
|
|
211
215
|
|
|
212
216
|
Choose fresh context or resume deliberately. Omit context, or pass {"mode":"none"}, for independent work such as a review. {"mode":"recent","turns":N} and {"mode":"full"} share parent conversation and cannot be combined with resume. resume continues one prior CLI run that recorded a session id, on the same backend: ${EXTERNAL_HARNESSES.join(", ")}. A named pi-* harness has no persisted session, so resume does not continue it. Prefer resume for the same task and a new child when you want an independent judgment.
|
|
213
217
|
|
|
214
|
-
|
|
218
|
+
Choose foreground by default when you need the result next, even for a long or parallel workflow: the original call waits for settlement and its live card carries progress. Pass background:true when you have independent local work (read, edit, bash) that does not touch files the delegated run may change, or when the user asks for background execution. Do that independent work first and never follow a background launch with an immediate external_runs wait. Ending a turn does not stop a background run: if the user asks to leave the workflow running, attend to another task, or check back later, report the runId and proceed with the other task or end your turn without waiting. Otherwise keep the ID and collect/check the outcome after your independent work finishes. The returned handle means accepted, not completed; the run may already be settled. No completion notification wakes the session. Keep Pi open: orderly shutdown requests cancellation with bounded cleanup; crashes leave uncertain evidence and restart does not adopt work.
|
|
219
|
+
|
|
220
|
+
Unless the user deferred collection, after independent work use external_runs wait if settlement is not already known. When the user later asks about a deferred run, inspect its runId first and wait only if it is still running. Check ok for the operation, then each completed run's state.outcome and pending IDs; a failed workflow can return wait early even in mode all. Use successful output when delivery is inline (null/false/0 are valid); otherwise inspect view final, following cursor pages. For failures inspect diagnostics or output; final is empty without verified success. A successful workflow may still have children.failed when its script caught child errors; read the reports. Interrupting the original foreground call cancels its work. Interrupting wait only stops waiting; cancel explicitly to stop background work. Cancelling a workflow stops active children; cancelling one child throws a catchable child error. Both modes share permissions, budget enforcement, and concurrency limits. A failed or aborted run stays failed. Retry it, or switch model or harness, only when the user authorized that step. agy may make one disclosed infrastructure retry; that exception is not yours to extend.
|
|
215
221
|
|
|
216
222
|
One external_delegate call. Permission stays omitted; the prompt carries the no-edit intent:
|
|
217
223
|
${jsonBlock(USAGE_ONE_AGENT)}
|
|
@@ -219,11 +225,13 @@ ${jsonBlock(USAGE_ONE_AGENT)}
|
|
|
219
225
|
Independent review. Fresh context, a reviewer role, the default tier, no resume:
|
|
220
226
|
${jsonBlock(USAGE_INDEPENDENT_REVIEW)}
|
|
221
227
|
|
|
222
|
-
Background
|
|
228
|
+
Background launch. Save data.run.runId from a successful receipt, then do useful independent work before collection:
|
|
229
|
+
|
|
223
230
|
${jsonBlock(USAGE_BACKGROUND_AGENT)}
|
|
231
|
+
Later, wait if needed and check the outcome. If succeeded, use inline output; otherwise the following is the final-inspection shape (substitute the retained ID and follow nextCursor). For a failed run choose diagnostics instead:
|
|
224
232
|
${jsonBlock(USAGE_RUNS_FINAL)}
|
|
225
233
|
|
|
226
|
-
Parallel workflow. Catch the child error inside each branch so one failure does not drain its sibling. Pass this string as the
|
|
234
|
+
Parallel workflow. First explain the plan in plain language: purpose, assignments, order, allowed changes, and deliverable. Put the same roadmap in meta.phases and mark transitions with phase(). The live card carries progress while a blocking call runs; background receipts are not live monitors, so collect and inspect background work with external_runs. Catch the child error inside each branch so one failure does not drain its sibling. Pass this string as the external_workflow tool's script:
|
|
227
235
|
${script}
|
|
228
236
|
Resume, only for the same task on a CLI harness that stored a session id. Do not combine it with context:
|
|
229
237
|
${jsonBlock(USAGE_RESUME_AGENT)}
|
package/src/tool-names.ts
CHANGED
package/src/types.ts
CHANGED
|
@@ -61,18 +61,18 @@ export interface SubagentExtensionOptions {
|
|
|
61
61
|
* Maximum number of subagents allowed to run concurrently across the whole
|
|
62
62
|
* agent run (a global in-flight cap, not a per-level fan-out width). A slot is
|
|
63
63
|
* taken when a subagent launches and released when it completes, fails, or is
|
|
64
|
-
* aborted. The cap is shared by the `
|
|
64
|
+
* aborted. The cap is shared by the `external_delegate` tool and the `external_workflow` tool.
|
|
65
65
|
*/
|
|
66
66
|
maxConcurrentSubagents?: number;
|
|
67
67
|
/**
|
|
68
68
|
* Maximum wall-clock runtime for each launched subagent, in milliseconds.
|
|
69
69
|
* Defaults to a generous global guardrail. Set to 0 to disable. The limit is
|
|
70
|
-
* shared by direct `
|
|
70
|
+
* shared by direct `external_delegate` calls and workflow `agent()` calls, and can also
|
|
71
71
|
* be overridden with `--subagent-timeout-ms`.
|
|
72
72
|
*/
|
|
73
73
|
subagentTimeoutMs?: number;
|
|
74
74
|
/**
|
|
75
|
-
* Register the dynamic `
|
|
75
|
+
* Register the dynamic `external_workflow` tool alongside `external_delegate`. Defaults to true:
|
|
76
76
|
* one product, two entry points. Set to false for a subagents-only surface.
|
|
77
77
|
*/
|
|
78
78
|
workflow?: boolean;
|
|
@@ -231,6 +231,9 @@ function validateMeta(meta: unknown): asserts meta is WorkflowMeta {
|
|
|
231
231
|
if (!phase || typeof phase !== "object" || typeof (phase as WorkflowMetaPhase).title !== "string") {
|
|
232
232
|
throw new Error("each meta phase must have a title string");
|
|
233
233
|
}
|
|
234
|
+
if (phase.detail !== undefined && typeof phase.detail !== "string") {
|
|
235
|
+
throw new Error("meta phase detail must be a string");
|
|
236
|
+
}
|
|
234
237
|
}
|
|
235
238
|
}
|
|
236
239
|
}
|
package/src/workflow/source.ts
CHANGED
|
@@ -20,13 +20,14 @@ import type { WorkflowCachedAgentResult, WorkflowMetaPhase } from "./types.ts";
|
|
|
20
20
|
|
|
21
21
|
export const workflowToolParameters = Type.Object({
|
|
22
22
|
background: Type.Optional(Type.Boolean({
|
|
23
|
-
description: "
|
|
23
|
+
description: "Defaults to false: wait for settlement. True returns a stable session-owned workflow handle after registration; work may already be settled. Pass true when you have independent local work to do first (do it, then call external_runs wait), or when the user asks to leave work running across turns (report the runId and do not call external_runs wait this turn). No completion notification; keep the owning session open.",
|
|
24
24
|
})),
|
|
25
25
|
script: Type.Optional(
|
|
26
26
|
Type.String({
|
|
27
27
|
description: [
|
|
28
28
|
"Raw JavaScript workflow script (no Markdown fences) for an ad-hoc workflow.",
|
|
29
29
|
"First statement: export const meta = { apiVersion: 1, name: 'short_name', description: 'non-empty' }.",
|
|
30
|
+
"Explain the plan in plain language before launch. Add meta.phases: [{ title, detail }] for the roadmap and call phase(title) at actual transitions.",
|
|
30
31
|
"Use agent(prompt, opts), parallel(thunks), pipeline(items, ...stages), phase(title), log(message), args, cwd. Must call agent() at least once and return a JSON-serializable value. Results are canonicalized to JSON; non-plain objects are rejected.",
|
|
31
32
|
"Provide exactly one of `script`, `name`, or `scriptPath`.",
|
|
32
33
|
].join(" "),
|
package/src/workflow/tool.ts
CHANGED
|
@@ -20,8 +20,9 @@ import { runRecordsDirectory } from "../core/retention.ts";
|
|
|
20
20
|
import { captureParentContext } from "../core/parent-context.ts";
|
|
21
21
|
import { RunRegistry, type RegisteredRunHandle } from "../core/run-registry.ts";
|
|
22
22
|
import { ExpectedFlowError, type FlowErrorCode } from "../core/errors.ts";
|
|
23
|
-
import { withContract } from "../contract/envelope.ts";
|
|
23
|
+
import { redactedText, withContract } from "../contract/envelope.ts";
|
|
24
24
|
import { workflowOutputSchema, workflowReceipt } from "../contract/workflow.ts";
|
|
25
|
+
import { EXTERNAL_WORKFLOW_TOOL } from "../tool-names.ts";
|
|
25
26
|
import { loadWorkflowJournal } from "./journal.ts";
|
|
26
27
|
import { OUTPUT_PREVIEW_CHARS } from "../core/progress.ts";
|
|
27
28
|
import { loadExternalCatalog, resolveExternalProfile, selectorHarness, unknownExternalProfileMessage } from "../profiles.ts";
|
|
@@ -135,14 +136,14 @@ export function createWorkflowTool(
|
|
|
135
136
|
options: CreateWorkflowToolOptions,
|
|
136
137
|
): ToolDefinition<typeof workflowToolParameters, WorkflowToolDetails> {
|
|
137
138
|
return defineTool({
|
|
138
|
-
name:
|
|
139
|
-
label:
|
|
140
|
-
description: "Run a saved, persisted, or ad-hoc trusted JavaScript workflow that orchestrates external roles.",
|
|
139
|
+
name: EXTERNAL_WORKFLOW_TOOL,
|
|
140
|
+
label: EXTERNAL_WORKFLOW_TOOL,
|
|
141
|
+
description: "Run a saved, persisted, or ad-hoc trusted JavaScript workflow that orchestrates external roles. Before launch, explain purpose, assignments, parallel/sequential order, allowed changes, and deliverable in plain language.",
|
|
141
142
|
promptSnippet: WORKFLOW_PROMPT_SNIPPET,
|
|
142
143
|
parameters: workflowToolParameters,
|
|
143
144
|
outputSchema: workflowOutputSchema,
|
|
144
145
|
async execute(toolCallId, params, signal, onUpdate, ctx) {
|
|
145
|
-
return await withContract<WorkflowToolDetails>({ tool:
|
|
146
|
+
return await withContract<WorkflowToolDetails>({ tool: EXTERNAL_WORKFLOW_TOOL, action: "run", failureData: { run: null }, redact: true }, async () => {
|
|
146
147
|
const parentMessages = captureParentContext(ctx.sessionManager);
|
|
147
148
|
const sessionId = ctx.sessionManager?.getSessionId?.() ?? `unpersisted:${toolCallId}`;
|
|
148
149
|
const sessionVersion = options.registry.sessionVersion(sessionId);
|
|
@@ -648,7 +649,7 @@ export function createWorkflowTool(
|
|
|
648
649
|
}
|
|
649
650
|
return {
|
|
650
651
|
...workflowResult(
|
|
651
|
-
`Workflow "${metaName}" queued as ${identity.runId}.
|
|
652
|
+
`Workflow "${metaName}" queued as ${identity.runId}. Do independent local work first, or report the runId and proceed/end your turn without waiting; use external_runs when ready to inspect, wait, or cancel.`,
|
|
652
653
|
{ ...cloneSnapshot(snapshot), backgroundReceipt: true },
|
|
653
654
|
),
|
|
654
655
|
...workflowReceipt(options.registry.get(identity.runId)!, { inspectable: true }),
|
|
@@ -783,6 +784,8 @@ function renderPhaseTree(container: Container, details: WorkflowToolDetails, the
|
|
|
783
784
|
0,
|
|
784
785
|
),
|
|
785
786
|
);
|
|
787
|
+
const detail = details.plannedPhases?.find((planned) => planned.title === phase)?.detail;
|
|
788
|
+
if (detail) container.addChild(new Text(` ${theme.fg("muted", redactedText(detail))}`, 0, 0));
|
|
786
789
|
const shown = selectAgentsForRender(agents);
|
|
787
790
|
for (const agent of shown) {
|
|
788
791
|
container.addChild(renderSubagentNode(agent, theme, frame, runningCount, " ", Date.now(), expanded, false));
|