@marcoscale98/pi-extensible-workflows 5.19.1-fork.1 → 6.2.1-fork.1
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 +77 -0
- package/README.md +2 -2
- package/dist/src/agent-execution.d.ts +43 -10
- package/dist/src/agent-execution.js +365 -127
- package/dist/src/background-widget.d.ts +0 -1
- package/dist/src/background-widget.js +4 -9
- package/dist/src/decoders.d.ts +2 -3
- package/dist/src/decoders.js +33 -37
- package/dist/src/execution.d.ts +2 -0
- package/dist/src/execution.js +5 -5
- package/dist/src/host-delivery.js +4 -8
- package/dist/src/host-navigator.js +5 -2
- package/dist/src/host-phases.d.ts +1 -0
- package/dist/src/host-recovery.d.ts +6 -6
- package/dist/src/host-recovery.js +14 -22
- package/dist/src/host-runtime.js +56 -5
- package/dist/src/host-view.d.ts +1 -1
- package/dist/src/host-view.js +17 -15
- package/dist/src/host.js +62 -87
- package/dist/src/index.d.ts +1 -2
- package/dist/src/index.js +1523 -1630
- package/dist/src/index.js.map +3 -3
- package/dist/src/paths.d.ts +7 -6
- package/dist/src/paths.js +24 -15
- package/dist/src/persistence.d.ts +1 -0
- package/dist/src/persistence.js +1 -0
- package/dist/src/pi-runtime-runner.js +4 -1
- package/dist/src/registry.d.ts +9 -7
- package/dist/src/registry.js +28 -70
- package/dist/src/runtime/agent-runner.d.ts +2 -0
- package/dist/src/session-lease.d.ts +1 -1
- package/dist/src/session-lease.js +45 -11
- package/dist/src/settings.d.ts +6 -3
- package/dist/src/settings.js +36 -47
- package/dist/src/store.d.ts +3 -1
- package/dist/src/store.js +17 -1
- package/dist/src/trajectory.d.ts +0 -1
- package/dist/src/trajectory.js +2 -3
- package/dist/src/types.d.ts +100 -44
- package/dist/src/types.js +4 -7
- package/dist/src/utils.d.ts +3 -2
- package/dist/src/utils.js +20 -10
- package/dist/src/validation.d.ts +8 -2
- package/dist/src/validation.js +122 -86
- package/dist/starter/index.js +8 -9
- package/dist/starter/index.js.map +1 -1
- package/dist/starter/review-loop.js +8 -7
- package/dist/subagents/index.js +654 -814
- package/dist/subagents/index.js.map +3 -3
- package/dist/subagents/src/contracts.d.ts +4 -3
- package/dist/subagents/src/contracts.js +2 -2
- package/dist/subagents/src/index.d.ts +1 -1
- package/dist/subagents/src/index.js +1 -2
- package/dist/subagents/src/manager.js +49 -70
- package/dist/subagents/src/navigator.js +3 -14
- package/dist/subagents/src/view.d.ts +0 -1
- package/dist/subagents/src/view.js +2 -16
- package/dist/subagents/src/worktree.js +1 -1
- package/dist/trajectory/assets/index.html +8 -8
- package/dist/trajectory/index.js +487 -891
- package/dist/trajectory/index.js.map +3 -3
- package/dist/trajectory/src/assets/index.html +8 -8
- package/dist/trajectory/src/index.js +126 -86
- package/dist/trajectory/src/server.js +264 -25
- package/dist/trajectory/src/server.js.map +3 -3
- package/dist/trajectory/src/startup-mutex.d.ts +17 -0
- package/dist/trajectory/src/startup-mutex.js +172 -0
- package/examples/workflow-extension-template/README.md +4 -7
- package/examples/workflow-extension-template/extension.test.mjs +4 -8
- package/examples/workflow-extension-template/index.js +0 -3
- package/package.json +5 -5
- package/skills/pi-extensible-workflows/SKILL.md +10 -8
- package/src/agent-execution.ts +339 -127
- package/src/background-widget.ts +4 -10
- package/src/decoders.ts +31 -37
- package/src/execution.ts +5 -5
- package/src/host-delivery.ts +4 -8
- package/src/host-navigator.ts +4 -2
- package/src/host-phases.ts +1 -1
- package/src/host-recovery.ts +20 -26
- package/src/host-runtime.ts +44 -6
- package/src/host-view.ts +18 -11
- package/src/host.ts +62 -113
- package/src/index.ts +1 -2
- package/src/paths.ts +18 -12
- package/src/persistence.ts +1 -0
- package/src/pi-runtime-runner.ts +3 -1
- package/src/registry.ts +27 -63
- package/src/runtime/agent-runner.ts +2 -0
- package/src/session-lease.ts +34 -6
- package/src/settings.ts +35 -47
- package/src/store.ts +18 -2
- package/src/trajectory.ts +2 -4
- package/src/types.ts +71 -30
- package/src/utils.ts +18 -9
- package/src/validation.ts +102 -79
- package/starter/index.ts +0 -1
- package/starter/review-loop.ts +8 -7
- package/starter/roles/developer.md +0 -1
- package/starter/roles/oracle.md +0 -1
- package/starter/roles/researcher.md +0 -1
- package/starter/roles/reviewer.md +0 -1
- package/starter/roles/scout.md +0 -1
- package/subagents/README.md +8 -14
- package/subagents/src/contracts.ts +3 -4
- package/subagents/src/index.ts +2 -3
- package/subagents/src/manager.ts +45 -125
- package/subagents/src/navigator.ts +4 -13
- package/subagents/src/view.ts +4 -14
- package/subagents/src/worktree.ts +1 -1
- package/trajectory/src/assets/index.html +8 -8
- package/trajectory/src/index.ts +106 -82
- package/trajectory/src/server.ts +44 -8
- package/trajectory/src/startup-mutex.ts +127 -0
- package/dist/src/roles.d.ts +0 -70
- package/dist/src/roles.js +0 -326
- package/dist/starter/roles/developer.md +0 -6
- package/dist/starter/roles/oracle.md +0 -18
- package/dist/starter/roles/researcher.md +0 -20
- package/dist/starter/roles/reviewer.md +0 -19
- package/dist/starter/roles/scout.md +0 -18
- package/examples/workflow-extension-template/roles/reviewer.md +0 -4
- package/src/roles.ts +0 -327
package/starter/review-loop.ts
CHANGED
|
@@ -17,27 +17,28 @@ export const reviewLoop = defineWorkflowFunction({
|
|
|
17
17
|
);
|
|
18
18
|
let devResult: JsonValue = null;
|
|
19
19
|
let review: Static<typeof reviewSchema> = { pass: false, findings: [] };
|
|
20
|
+
// One developer keeps its transcript across iterations; each reviewer starts fresh.
|
|
21
|
+
const developer = agent.create({ name: "developer", label: "Developer", role: "developer" });
|
|
20
22
|
|
|
21
23
|
for (let iterations = 1; iterations <= maxIterations; iterations += 1) {
|
|
22
24
|
const devPrompt =
|
|
23
25
|
iterations === 1
|
|
24
26
|
? prompt("Implement this task:\n\n{task}", { task })
|
|
25
27
|
: prompt(
|
|
26
|
-
`Address the
|
|
27
|
-
<original_task>{task}</original_task>
|
|
28
|
+
`Address the latest review findings and complete the original task.
|
|
28
29
|
<last_review>{review}</last_review>`,
|
|
29
|
-
{
|
|
30
|
+
{ review: review.findings },
|
|
30
31
|
);
|
|
31
|
-
devResult = await
|
|
32
|
+
devResult = await developer.send(devPrompt);
|
|
32
33
|
review = await agent(
|
|
33
34
|
prompt(
|
|
34
|
-
`Review the implementation against the task.
|
|
35
|
+
`Review the actual current implementation against the entire original task. Inspect the code and run relevant checks yourself. Do not rely on the developer summary or on previous findings; they may be incomplete or wrong. Set pass=true only when the whole task is complete and correct.
|
|
35
36
|
<original_task>{task}</original_task>
|
|
36
|
-
<
|
|
37
|
+
<previous_review_findings>{review}</previous_review_findings>
|
|
37
38
|
<dev_summary>{devResult}</dev_summary>`,
|
|
38
39
|
{ task, devResult, review: review.findings },
|
|
39
40
|
),
|
|
40
|
-
{ role: "reviewer", outputSchema: reviewSchema },
|
|
41
|
+
{ label: `Reviewer ${String(iterations)}`, role: "reviewer", outputSchema: reviewSchema },
|
|
41
42
|
);
|
|
42
43
|
|
|
43
44
|
if (review.pass) return { pass: true, iterations, devResult, review };
|
package/starter/roles/oracle.md
CHANGED
package/starter/roles/scout.md
CHANGED
package/subagents/README.md
CHANGED
|
@@ -18,7 +18,7 @@ Use the built-in subagent tools for focused, independent tasks. Use `pi-extensib
|
|
|
18
18
|
|
|
19
19
|
- Five focused tools: run, inspect, steer, stop, and retry.
|
|
20
20
|
- Background fan-out with one durable ID per run, or foreground execution with an inline terminal result.
|
|
21
|
-
- Reuses workflow
|
|
21
|
+
- Reuses generic workflow preparation hooks, model aliases, settings including extension-defined namespaces, and agent options: `label`, `model`, `tools`, `skills`, `extensions`, `contextFiles`, `worktree`, `outputSchema`, `retries` (0 through 255), and `timeoutMs`.
|
|
22
22
|
- Repeatable inspection of progress, token accounting, tool calls, results, failures, and worktrees.
|
|
23
23
|
|
|
24
24
|
Built-in subagent tools are available when the core extension is installed. Use the core package's Pi extension manifest to enable them.
|
|
@@ -44,21 +44,21 @@ subagents_run({ prompt: "Summarize README.md.", mode: "foreground" })
|
|
|
44
44
|
|
|
45
45
|
## Interactive inspection
|
|
46
46
|
|
|
47
|
-
In Pi's TUI, `/subagents` opens the `/workflow` dashboard layout for the durable standalone runs of the current session: the run list sits beside the selected run's details at 80 columns or wider, and narrower terminals drill down from the list to the details. The details show the run ID and the same activity, stall warning, state, model,
|
|
47
|
+
In Pi's TUI, `/subagents` opens the `/workflow` dashboard layout for the durable standalone runs of the current session: the run list sits beside the selected run's details at 80 columns or wider, and narrower terminals drill down from the list to the details. The details show the run ID and the same activity, stall warning, state, model, tools, attempts, duration, cumulative token accounting, cost, and error fields as a `/workflow` agent; a running run shows its live activity, which `subagents_inspect` leaves out. While a run is active the view refreshes the active runs and the selection every second; runs launched while it is open appear after an action or when it reopens. `a` or `enter` opens registered standalone agent actions, editor controls for the prompt, the live system prompt, and the result, `Steer` and `Stop` while running, `Retry` for failed or stopped runs, and the `/workflow` run actions that apply: `Delete`, `Delete all completed`, `Delete all failed`, `Copy run path`, and `Copy agent ID`. Stop and every deletion ask for confirmation, and a run whose worktree still awaits cleanup cannot be deleted. Outside the TUI, `/subagents` opens a picker with the same run summary and bulk deletions as the `/workflow` picker. Background runs appear below the editor in the workflow widget's frame, one row per run with its model, tokens, cost, elapsed time, and quiet or stalled warning, and each leaves a receipt in the transcript when it settles. Inspection does not launch a new LLM call; lifecycle actions may steer, stop, retry, or delete a run.
|
|
48
48
|
|
|
49
49
|
## Tools
|
|
50
50
|
|
|
51
|
-
|
|
51
|
+
`subagents_run` accepts additional JSON agent options for loaded preparation hooks; unknown options are ignored when no plugin handles them. The other control-tool schemas are closed objects. The model-visible standalone surface contains exactly these five names:
|
|
52
52
|
|
|
53
53
|
| Tool | Input schema |
|
|
54
54
|
| --- | --- |
|
|
55
|
-
| `subagents_run` | `{ prompt: string, mode?: "background" \| "foreground", label?: string, model?: string, tools?: string[], skills?: string[], extensions?: string[], contextFiles?: string[],
|
|
55
|
+
| `subagents_run` | `{ prompt: string, mode?: "background" \| "foreground", label?: string, model?: string, tools?: string[], skills?: string[], extensions?: string[], contextFiles?: string[], worktree?: string, outputSchema?: object, retries?: integer 0..255, timeoutMs?: positive integer \| null }` |
|
|
56
56
|
| `subagents_inspect` | `{ id?: string, scope?: "session" \| "all" }` |
|
|
57
57
|
| `subagents_steer` | `{ id: string, message: string }` |
|
|
58
58
|
| `subagents_stop` | `{ id: string }` |
|
|
59
59
|
| `subagents_retry` | `{ id: string }` |
|
|
60
60
|
|
|
61
|
-
`prompt` is the only required `subagents_run` property. `mode` defaults to "background". Resource candidates start enabled, so plain positive top-level selector lists are additive and top-level `[]` adds no matches. Use `["!*", "read", "grep"]` to restrict a selector to those resources, or `["!*"]` to select none.
|
|
61
|
+
`prompt` is the only required `subagents_run` property. `mode` defaults to "background". Resource candidates start enabled, so plain positive top-level selector lists are additive and top-level `[]` adds no matches. Use `["!*", "read", "grep"]` to restrict a selector to those resources, or `["!*"]` to select none. Unknown JSON options are forwarded to enabled plugins. `role` has no core meaning. Concrete models are `provider/model:thinking`. Capability selectors cannot exceed root and parent ceilings. `excludeTools` takes exact tool names removed after preparation hooks, as Pi's `excludeTools` does; patterns and `workflow_result` are rejected with `INVALID_METADATA`, names also remove same-named custom tools, are frozen with the configuration that retries reuse, and names matching no root or custom tool only warn.
|
|
62
62
|
|
|
63
63
|
`subagents_inspect({})` returns the current Pi session's run summaries ordered by start time; `subagents_inspect({ scope: "all" })` lists every stored run. `subagents_inspect({ id })` returns the detailed lifecycle record, including state, start and finish timestamps, and the live snapshot under `progress`: `state`, cumulative `accounting`, `toolCalls`, `activity`, and `lastEventAt`. The snapshot state never includes the effective system prompt, and inspection has no `usage` field; token totals are derived from accounting. Materialized worktree path and branch are included when available. For completed runs it also includes `value`; for failed runs it includes `error`. A running run has no terminal value yet. Unknown IDs fail with `RUN_NOT_FOUND`.
|
|
64
64
|
|
|
@@ -82,7 +82,7 @@ Each call owns an independent run, so calls can execute concurrently and settle
|
|
|
82
82
|
|
|
83
83
|
## IDs, inspection, and terminal values
|
|
84
84
|
|
|
85
|
-
Run records are stored below the agent directory's private `subagents/<id>/` directory, normally `~/.pi/agent/subagents/<id>/`. The record includes the normalized request, its frozen
|
|
85
|
+
Run records are stored below the agent directory's private `subagents/<id>/` directory, normally `~/.pi/agent/subagents/<id>/`. The record includes the normalized request, its frozen concrete prepared configuration for deterministic retries, and status. A shared storage owner marker uses the process ID, process start, session ID, and token; every running record carries the same manager identity so a live manager does not reconcile another manager's active run.
|
|
86
86
|
|
|
87
87
|
Inspection is repeatable. Use the list form for ordered summaries and the ID form for one detailed status plus its terminal value or failure information. Progress is retained in memory and is persisted when the executor marks a progress update for persistence. The result and failure files remain available after manager restart.
|
|
88
88
|
|
|
@@ -102,13 +102,7 @@ Set `worktree` on `subagents_run` to create a named isolated Git worktree for th
|
|
|
102
102
|
|
|
103
103
|
## Agent options, roles, and settings
|
|
104
104
|
|
|
105
|
-
`subagents_run` accepts the same execution options as
|
|
106
|
-
|
|
107
|
-
- global roles: `<agentDir>/pi-extensible-workflows/roles/<name>.md`, normally `~/.pi/agent/pi-extensible-workflows/roles/`;
|
|
108
|
-
- trusted project roles: `<cwd>/.pi/pi-extensible-workflows/roles/<name>.md`;
|
|
109
|
-
- global and trusted project settings: the normal workflow settings path under the agent directory and `<cwd>/.pi/pi-extensible-workflows/settings.json`.
|
|
110
|
-
|
|
111
|
-
The current Pi model, thinking level, active tools, project trust, session ID, role definitions, aliases, and resource policy are captured from the extension context for each run. Internal workflow and subagent control tools are not exposed to the child agent.
|
|
105
|
+
`subagents_run` accepts the same execution options as workflow agents, plus JSON extension options. Enable the optional `@piewf/pi-ext-roles` extension in the parent Pi session when role behavior is required; core does not discover role files or read shared role settings. [Generic preparation hooks](https://vekexasia.github.io/pi-extensible-workflows/extensions.html#preparation) receive authorized inventories, effective cwd/agentDir/trust, root defaults, immutable original options, and cancellation. Prepared configurations are persisted before execution and reused on retry without resolving external policies again. Lost capabilities or trust fail closed. Internal workflow and subagent control tools remain excluded from the child agent.
|
|
112
106
|
|
|
113
107
|
## Programmatic host integration
|
|
114
108
|
|
|
@@ -142,4 +136,4 @@ The v5.4 release removes the seven-tool surface's redundant `subagents_list`, `s
|
|
|
142
136
|
| `subagents_stop({ id })` | unchanged |
|
|
143
137
|
| `subagents_retry({ id })` | unchanged; the original launch mode is preserved |
|
|
144
138
|
|
|
145
|
-
The standalone manager deliberately keeps durable run IDs, repeatable inspection, bounded steering, named worktree cleanup, and workflow
|
|
139
|
+
The standalone manager deliberately keeps durable run IDs, repeatable inspection, bounded steering, named worktree cleanup, and generic workflow preparation and settings reuse.
|
|
@@ -29,15 +29,15 @@ export const SUBAGENTS_RUN_PARAMETERS = Type.Object({
|
|
|
29
29
|
label: Type.Optional(Type.String({ description: "Optional display label for the subagent" })),
|
|
30
30
|
model: Type.Optional(Type.String({ description: "Optional model as provider/model:thinking or alias[:thinking]" })),
|
|
31
31
|
tools: Type.Optional(Type.Array(Type.String(), { description: "Optional ordered tool selectors; candidates start enabled and !* restricts the set" })),
|
|
32
|
+
excludeTools: Type.Optional(Type.Array(Type.String(), { description: "Optional exact tool names removed after preparation; no patterns, cannot remove workflow_result" })),
|
|
32
33
|
skills: Type.Optional(Type.Array(Type.String(), { description: "Optional ordered skill selectors; candidates start enabled and !* restricts the set" })),
|
|
33
34
|
extensions: Type.Optional(Type.Array(Type.String(), { description: "Optional ordered extension selectors; candidates start enabled and !* restricts the set" })),
|
|
34
35
|
contextFiles: Type.Optional(Type.Array(Type.String(), { description: "Optional context-file scopes: global, project, cwd" })),
|
|
35
|
-
role: Type.Optional(Type.String({ description: "Workflow role name" })),
|
|
36
36
|
worktree: Type.Optional(Type.String({ description: "Optional named worktree; requires a clean working tree" })),
|
|
37
37
|
outputSchema: Type.Optional(Type.Record(Type.String(), Type.Unknown(), { description: "Optional JSON schema for the result" })),
|
|
38
38
|
retries: Type.Optional(Type.Integer({ minimum: 0, maximum: SUBAGENT_MAX_RETRIES, description: "Optional retry count; at most 255 retries" })),
|
|
39
39
|
timeoutMs: Type.Optional(Type.Union([Type.Integer({ minimum: 1, description: "Optional execution timeout in milliseconds" }), Type.Null()])),
|
|
40
|
-
}, { additionalProperties:
|
|
40
|
+
}, { additionalProperties: true });
|
|
41
41
|
|
|
42
42
|
export const SUBAGENTS_INSPECT_PARAMETERS = Type.Object({
|
|
43
43
|
id: Type.Optional(Type.String({ description: "Subagent ID; omit to list ordered run summaries" })),
|
|
@@ -79,7 +79,7 @@ export const SUBAGENT_STATUS_OUTPUT = Type.Object({
|
|
|
79
79
|
export const SUBAGENTS_INSPECT_OUTPUT = Type.Union([SUBAGENT_STATUS_OUTPUT, Type.Array(Type.Object({ id: Type.String(), state: Type.String() }))]);
|
|
80
80
|
export const SUBAGENTS_STEER_OUTPUT = Type.Object({ id: Type.String(), accepted: Type.Boolean() });
|
|
81
81
|
|
|
82
|
-
export type SubagentRunRequest = Static<typeof SUBAGENTS_RUN_PARAMETERS
|
|
82
|
+
export type SubagentRunRequest = Static<typeof SUBAGENTS_RUN_PARAMETERS> & { readonly [key: string]: unknown };
|
|
83
83
|
export function normalizeSubagentRunRequest(value: unknown): SubagentRunRequest {
|
|
84
84
|
if (!Value.Check(SUBAGENTS_RUN_PARAMETERS, value)) throw new WorkflowError("INVALID_METADATA", "Invalid subagents_run parameters");
|
|
85
85
|
if (typeof value.worktree === "string" && !value.worktree.trim()) throw new WorkflowError("INVALID_METADATA", "worktree name must be a non-empty string");
|
|
@@ -114,7 +114,6 @@ export interface SubagentStatus {
|
|
|
114
114
|
export interface SubagentNotification {
|
|
115
115
|
readonly id: string;
|
|
116
116
|
readonly label?: string;
|
|
117
|
-
readonly role?: string;
|
|
118
117
|
readonly state: "completed" | "failed";
|
|
119
118
|
readonly error?: { readonly code: string; readonly message: string };
|
|
120
119
|
}
|
package/subagents/src/index.ts
CHANGED
|
@@ -26,7 +26,7 @@ export { createSubagentManager, createUnavailableSubagentManager } from "./manag
|
|
|
26
26
|
export { createRunStoreWorktreeAdapter, defaultWorktreeHome } from "./worktree.js";
|
|
27
27
|
export type { SubagentWorktreeAdapter, SubagentWorktreeContext, SubagentWorktreeHandle, SubagentWorktreeRunStore } from "./worktree.js";
|
|
28
28
|
|
|
29
|
-
type SubagentsExtensionAPI = Pick<ExtensionAPI, "registerTool"> & Partial<Pick<ExtensionAPI, "getActiveTools" | "getAllTools" | "on" | "sendMessage" | "registerCommand" | "appendEntry" | "registerEntryRenderer">>;
|
|
29
|
+
type SubagentsExtensionAPI = Pick<ExtensionAPI, "registerTool"> & Partial<Pick<ExtensionAPI, "events" | "getActiveTools" | "getAllTools" | "on" | "sendMessage" | "registerCommand" | "appendEntry" | "registerEntryRenderer">>;
|
|
30
30
|
|
|
31
31
|
function validateSubagentRunRequest(value: unknown): SubagentRunRequest {
|
|
32
32
|
return normalizeSubagentRunRequest(value);
|
|
@@ -141,8 +141,7 @@ export function createSubagentTools(manager: SubagentManager): readonly ToolDefi
|
|
|
141
141
|
|
|
142
142
|
function notificationContent(notification: SubagentNotification): string {
|
|
143
143
|
const label = notification.label?.trim();
|
|
144
|
-
const
|
|
145
|
-
const identity = label ? `${label} role=${role} (${notification.id})` : `${notification.id} role=${role}`;
|
|
144
|
+
const identity = label ? `${label} (${notification.id})` : notification.id;
|
|
146
145
|
if (notification.state === "completed") return `Subagent ${identity} completed. Inspect it with subagents_inspect({ id: "${notification.id}" }).`;
|
|
147
146
|
return `Subagent ${identity} failed: ${notification.error?.message ?? "unknown error"}. Inspect it with subagents_inspect({ id: "${notification.id}" }).`;
|
|
148
147
|
}
|
package/subagents/src/manager.ts
CHANGED
|
@@ -2,67 +2,11 @@ import { randomUUID } from "node:crypto";
|
|
|
2
2
|
import { chmod, link, mkdir, open, readFile, readdir, rename, rm, stat } from "node:fs/promises";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
deepFreeze,
|
|
8
|
-
errorCode,
|
|
9
|
-
errorText,
|
|
10
|
-
finiteNumber,
|
|
11
|
-
isNodeError,
|
|
12
|
-
isThinkingLevel,
|
|
13
|
-
jsonValue,
|
|
14
|
-
object,
|
|
15
|
-
loadingRegistry,
|
|
16
|
-
localAgentTransport,
|
|
17
|
-
resolveAgentResourcePolicy,
|
|
18
|
-
resolveWorkflowSettings,
|
|
19
|
-
roleNameOf,
|
|
20
|
-
sanitizeDisplayText,
|
|
21
|
-
sumAccounting,
|
|
22
|
-
structuralPath,
|
|
23
|
-
validateModelAliasAvailability,
|
|
24
|
-
WorkflowAgentExecutor,
|
|
25
|
-
WorkflowError,
|
|
26
|
-
workflowProjectSettingsPath,
|
|
27
|
-
workflowSettingsPath,
|
|
28
|
-
type AgentAccounting,
|
|
29
|
-
type AgentAttempt,
|
|
30
|
-
type AgentAttemptSummary,
|
|
31
|
-
type AgentDefinition,
|
|
32
|
-
type AgentExecutionOptions,
|
|
33
|
-
type AgentExecutionRoot,
|
|
34
|
-
type AgentProgress,
|
|
35
|
-
type AgentToolCallProgress,
|
|
36
|
-
type JsonSchema,
|
|
37
|
-
type JsonValue,
|
|
38
|
-
type ModelSpec,
|
|
39
|
-
type WorkflowAgentSessionState,
|
|
40
|
-
type WorkflowExtensionSettings,
|
|
41
|
-
type WorkflowRunContext,
|
|
42
|
-
SerialLane,
|
|
43
|
-
} from "../../src/index.js";
|
|
44
|
-
import { decodeAgentDefinition, decodeWorkflowExtensions } from "../../src/decoders.js";
|
|
45
|
-
import { loadAgentDefinitions } from "../../src/roles.js";
|
|
5
|
+
import { addAccounting, errorCode, errorText, finiteNumber, isNodeError, isThinkingLevel, jsonValue, object, loadingRegistry, localAgentTransport, resolveAgentResourcePolicy, resolveWorkflowSettings, sanitizeDisplayText, sumAccounting, structuralPath, validateModelAliasAvailability, WorkflowAgentExecutor, WorkflowError, workflowSettingsPath, type AgentAccounting, type AgentAttempt, type AgentAttemptSummary, type PreparedAgentConfiguration, type AgentExecutionOptions, type AgentExecutionRoot, type AgentProgress, type AgentToolCallProgress, type JsonSchema, type JsonValue, type ModelSpec, type WorkflowAgentSessionState, type WorkflowRunContext, SerialLane } from "../../src/index.js";
|
|
6
|
+
import { decodePreparedAgentConfiguration } from "../../src/decoders.js";
|
|
46
7
|
import { atomicJson, json as readJson, processAlive } from "../../src/persistence.js";
|
|
47
8
|
import { accountingValue, activityValue, legacyAccountingValue, worktreeValue } from "./decode.js";
|
|
48
|
-
import {
|
|
49
|
-
SUBAGENT_ATTEMPT_DETAILS_LIMIT,
|
|
50
|
-
SUBAGENT_MAX_RETRIES,
|
|
51
|
-
SUBAGENT_SYSTEM_PROMPT_LIMIT,
|
|
52
|
-
SUBAGENTS_TOOL_NAMES,
|
|
53
|
-
normalizeSubagentRunRequest,
|
|
54
|
-
type SubagentInspectRequest,
|
|
55
|
-
type SubagentLiveness,
|
|
56
|
-
type SubagentAttemptActionData,
|
|
57
|
-
type SubagentManager,
|
|
58
|
-
type SubagentManagerContext,
|
|
59
|
-
type SubagentManagerDependencies,
|
|
60
|
-
type SubagentNotification,
|
|
61
|
-
type SubagentOwnerMarker,
|
|
62
|
-
type SubagentProgress,
|
|
63
|
-
type SubagentRunRequest,
|
|
64
|
-
type SubagentStatus,
|
|
65
|
-
} from "./contracts.js";
|
|
9
|
+
import { SUBAGENT_ATTEMPT_DETAILS_LIMIT, SUBAGENT_MAX_RETRIES, SUBAGENT_SYSTEM_PROMPT_LIMIT, SUBAGENTS_TOOL_NAMES, normalizeSubagentRunRequest, type SubagentInspectRequest, type SubagentLiveness, type SubagentAttemptActionData, type SubagentManager, type SubagentManagerContext, type SubagentManagerDependencies, type SubagentNotification, type SubagentOwnerMarker, type SubagentProgress, type SubagentRunRequest, type SubagentStatus } from "./contracts.js";
|
|
66
10
|
import { createRunStoreWorktreeAdapter, defaultWorktreeHome, type SubagentWorktreeContext, type SubagentWorktreeHandle } from "./worktree.js";
|
|
67
11
|
|
|
68
12
|
const WORKFLOW_NAME = "subagents";
|
|
@@ -77,12 +21,7 @@ const MAX_PERSISTED_ATTEMPT_ARRAY_ITEMS = SUBAGENT_MAX_RETRIES + 1;
|
|
|
77
21
|
const MAX_PERSISTED_ATTEMPT_STRING_CHARS = 4096;
|
|
78
22
|
const MAX_PERSISTED_ATTEMPT_LOCATOR_CHARS = 16 * 1024;
|
|
79
23
|
const SUBAGENT_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
80
|
-
type StandaloneExternalConfiguration = Readonly<
|
|
81
|
-
projectTrusted: boolean;
|
|
82
|
-
globalExtensionSettings?: Readonly<WorkflowExtensionSettings>;
|
|
83
|
-
projectExtensionSettings?: Readonly<WorkflowExtensionSettings>;
|
|
84
|
-
agentDefinitions: Readonly<Record<string, AgentDefinition>>;
|
|
85
|
-
}>;
|
|
24
|
+
type StandaloneExternalConfiguration = Readonly<PreparedAgentConfiguration>;
|
|
86
25
|
class InvalidPersistedSubagentStatusError extends WorkflowError {
|
|
87
26
|
constructor() { super("INTERNAL_ERROR", "Persisted subagent status is invalid"); }
|
|
88
27
|
}
|
|
@@ -169,22 +108,11 @@ function rootModel(context: Readonly<SubagentManagerContext>): ModelSpec {
|
|
|
169
108
|
const thinking = context.extensionContext.thinkingLevel;
|
|
170
109
|
return { provider: model.provider, model: model.id, ...(thinking === undefined ? {} : { thinking }) };
|
|
171
110
|
}
|
|
172
|
-
function
|
|
173
|
-
return trustedProject && external.projectExtensionSettings !== undefined ? workflowProjectSettingsPath(cwd) : workflowSettingsPath(agentDir);
|
|
174
|
-
}
|
|
175
|
-
function validateStandaloneExtensionSources(context: Readonly<SubagentManagerContext>, external: StandaloneExternalConfiguration, registry: ReturnType<typeof loadingRegistry>, agentDir: string): void {
|
|
176
|
-
const cwd = context.extensionContext.cwd;
|
|
177
|
-
const globalSettingsPath = workflowSettingsPath(agentDir);
|
|
178
|
-
const trustedProject = context.extensionContext.isProjectTrusted();
|
|
179
|
-
if (external.globalExtensionSettings !== undefined) registry.validateExtensionSettings(external.globalExtensionSettings, { source: "global", cwd, projectTrusted: trustedProject, settingsPath: globalSettingsPath });
|
|
180
|
-
if (trustedProject && external.projectExtensionSettings !== undefined) registry.validateExtensionSettings(external.projectExtensionSettings, { source: "project", cwd, projectTrusted: true, settingsPath: workflowProjectSettingsPath(cwd) });
|
|
181
|
-
}
|
|
182
|
-
function executionRoot(context: Readonly<SubagentManagerContext>, dependencies: Readonly<SubagentManagerDependencies>, signal: AbortSignal, runId: string, worktree: SubagentWorktreeHandle | undefined, external: StandaloneExternalConfiguration): AgentExecutionRoot {
|
|
111
|
+
function executionRoot(context: Readonly<SubagentManagerContext>, dependencies: Readonly<SubagentManagerDependencies>, signal: AbortSignal, runId: string, worktree: SubagentWorktreeHandle | undefined): AgentExecutionRoot {
|
|
183
112
|
const extensionContext = context.extensionContext;
|
|
184
113
|
const model = rootModel(context);
|
|
185
114
|
const agentDir = dependencies.agentDir ?? getAgentDir();
|
|
186
115
|
const currentTrustedProject = extensionContext.isProjectTrusted();
|
|
187
|
-
if (external.projectTrusted && !currentTrustedProject) throw new WorkflowError("RESUME_INCOMPATIBLE", "Cannot retry standalone configuration from an untrusted project");
|
|
188
116
|
const trustedProject = currentTrustedProject;
|
|
189
117
|
const settingsPath = workflowSettingsPath(agentDir);
|
|
190
118
|
const settings = resolveWorkflowSettings(extensionContext.cwd, trustedProject, settingsPath);
|
|
@@ -207,11 +135,13 @@ function executionRoot(context: Readonly<SubagentManagerContext>, dependencies:
|
|
|
207
135
|
signal,
|
|
208
136
|
};
|
|
209
137
|
const registry = loadingRegistry();
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
const
|
|
138
|
+
if (settings.global.extensionSettings !== undefined) registry.validateExtensionSettings(settings.global.extensionSettings, { source: "global", cwd: extensionContext.cwd, projectTrusted: trustedProject, settingsPath: settings.globalSettingsPath });
|
|
139
|
+
if (trustedProject && settings.project.extensionSettings !== undefined) registry.validateExtensionSettings(settings.project.extensionSettings, { source: "project", cwd: extensionContext.cwd, projectTrusted: true, settingsPath: settings.projectSettingsPath });
|
|
140
|
+
const extensionSettings = settings.effective.extensionSettings;
|
|
141
|
+
const extensionSettingsPath = settings.sources.extensionSettings ?? settingsPath;
|
|
213
142
|
return {
|
|
214
143
|
cwd: extensionContext.cwd,
|
|
144
|
+
projectTrusted: trustedProject,
|
|
215
145
|
model,
|
|
216
146
|
tools,
|
|
217
147
|
resourceSelectors: resourcePolicy.effective,
|
|
@@ -221,9 +151,10 @@ function executionRoot(context: Readonly<SubagentManagerContext>, dependencies:
|
|
|
221
151
|
availableModels,
|
|
222
152
|
knownModels,
|
|
223
153
|
...(Object.keys(staticAliases).length ? { modelAliases: staticAliases } : {}),
|
|
154
|
+
dynamicModelAliasNames: [],
|
|
224
155
|
settingsPath: settings.sources.modelAliases,
|
|
225
156
|
extensionSettingsPath,
|
|
226
|
-
|
|
157
|
+
agentPreparationHooks: registry.agentPreparationHooks(),
|
|
227
158
|
agentSetupHooks: registry.agentSetupHooks(),
|
|
228
159
|
validateExtensionSettings: registry.validateExtensionSettings,
|
|
229
160
|
agentResourcePolicy: () => structuredClone(resourcePolicy),
|
|
@@ -240,12 +171,11 @@ async function addDynamicAliases(context: Readonly<SubagentManagerContext>, sign
|
|
|
240
171
|
const trustedProject = resourcePolicy?.projectTrusted ?? context.extensionContext.isProjectTrusted();
|
|
241
172
|
const dynamicAliases = await registry.resolveModelAliases({ cwd: context.extensionContext.cwd, projectTrusted: trustedProject, rootModel: root.model, knownModels: root.knownModels ?? new Set(), availableModels: root.availableModels ?? new Set(), signal }, new Set(Object.keys(staticAliases)));
|
|
242
173
|
validateModelAliasAvailability(dynamicAliases, Object.keys(dynamicAliases), root.availableModels ?? new Set(), root.knownModels ?? new Set(), root.settingsPath);
|
|
243
|
-
return { ...root, modelAliases: { ...dynamicAliases, ...staticAliases } };
|
|
174
|
+
return { ...root, modelAliases: { ...dynamicAliases, ...staticAliases }, dynamicModelAliasNames: Object.keys(dynamicAliases) };
|
|
244
175
|
}
|
|
245
176
|
|
|
246
177
|
function executionOptions(request: Readonly<SubagentRunRequest>, onAttempt: NonNullable<AgentExecutionOptions["onAttempt"]>, onProgress: NonNullable<AgentExecutionOptions["onProgress"]>): AgentExecutionOptions {
|
|
247
|
-
const
|
|
248
|
-
const label = request.label ?? roleNameOf(role) ?? "subagent";
|
|
178
|
+
const label = request.label ?? "subagent";
|
|
249
179
|
return {
|
|
250
180
|
label,
|
|
251
181
|
workflowName: WORKFLOW_NAME,
|
|
@@ -254,11 +184,11 @@ function executionOptions(request: Readonly<SubagentRunRequest>, onAttempt: NonN
|
|
|
254
184
|
...(request.skills === undefined ? {} : { skills: request.skills }),
|
|
255
185
|
...(request.extensions === undefined ? {} : { extensions: request.extensions }),
|
|
256
186
|
...(request.contextFiles === undefined ? {} : { contextFiles: request.contextFiles as NonNullable<AgentExecutionOptions["contextFiles"]> }),
|
|
257
|
-
...(role === undefined ? {} : { role }),
|
|
258
187
|
...(request.worktree === undefined ? {} : { worktreeOwner: structuralPath("worktree", "named", request.worktree) }),
|
|
259
188
|
...(request.outputSchema === undefined ? {} : { schema: request.outputSchema as JsonSchema }),
|
|
260
189
|
...(request.retries === undefined ? {} : { retries: request.retries }),
|
|
261
190
|
...(request.timeoutMs === undefined ? {} : { timeoutMs: request.timeoutMs }),
|
|
191
|
+
agentOptions: Object.fromEntries(Object.entries(request).filter(([key]) => !["prompt", "mode", "worktree"].includes(key))) as Record<string, JsonValue>,
|
|
262
192
|
onAttempt,
|
|
263
193
|
onProgress,
|
|
264
194
|
};
|
|
@@ -398,32 +328,8 @@ function checkedRequest(request: unknown): SubagentRunRequest {
|
|
|
398
328
|
return normalizeSubagentRunRequest(request);
|
|
399
329
|
}
|
|
400
330
|
function decodePersistedStandaloneExternalConfiguration(value: unknown): StandaloneExternalConfiguration | undefined {
|
|
401
|
-
if (!object(value) ||
|
|
402
|
-
|
|
403
|
-
const projectExtensionSettings = value.projectExtensionSettings === undefined ? undefined : decodeWorkflowExtensions(value.projectExtensionSettings);
|
|
404
|
-
if (value.globalExtensionSettings !== undefined && globalExtensionSettings === undefined || value.projectExtensionSettings !== undefined && projectExtensionSettings === undefined) return undefined;
|
|
405
|
-
if (!object(value.agentDefinitions)) return undefined;
|
|
406
|
-
const agentDefinitions = Object.create(null) as Record<string, AgentDefinition>;
|
|
407
|
-
for (const [name, definition] of Object.entries(value.agentDefinitions)) {
|
|
408
|
-
const decoded = decodeAgentDefinition(definition);
|
|
409
|
-
if (decoded === undefined) return undefined;
|
|
410
|
-
agentDefinitions[name] = decoded;
|
|
411
|
-
}
|
|
412
|
-
return deepFreeze({ projectTrusted: value.projectTrusted, ...(globalExtensionSettings === undefined ? {} : { globalExtensionSettings }), ...(projectExtensionSettings === undefined ? {} : { projectExtensionSettings }), agentDefinitions });
|
|
413
|
-
}
|
|
414
|
-
function standaloneExternalConfiguration(context: Readonly<SubagentManagerContext>, dependencies: Readonly<SubagentManagerDependencies>): StandaloneExternalConfiguration {
|
|
415
|
-
const cwd = context.extensionContext.cwd;
|
|
416
|
-
const trustedProject = context.extensionContext.isProjectTrusted();
|
|
417
|
-
const agentDir = dependencies.agentDir ?? getAgentDir();
|
|
418
|
-
const resolution = resolveWorkflowSettings(cwd, trustedProject, workflowSettingsPath(agentDir));
|
|
419
|
-
const globalExtensionSettings = resolution.global.extensionSettings;
|
|
420
|
-
const projectExtensionSettings = resolution.project.extensionSettings;
|
|
421
|
-
return deepFreeze({
|
|
422
|
-
projectTrusted: trustedProject,
|
|
423
|
-
...(globalExtensionSettings === undefined ? {} : { globalExtensionSettings: structuredClone(globalExtensionSettings) }),
|
|
424
|
-
...(projectExtensionSettings === undefined ? {} : { projectExtensionSettings: structuredClone(projectExtensionSettings) }),
|
|
425
|
-
agentDefinitions: structuredClone(loadAgentDefinitions(cwd, agentDir, trustedProject)),
|
|
426
|
-
});
|
|
331
|
+
if (!object(value) || value.version !== 1) return undefined;
|
|
332
|
+
return decodePreparedAgentConfiguration(value.configuration);
|
|
427
333
|
}
|
|
428
334
|
|
|
429
335
|
function checkedId(request: Readonly<{ id: string }>): string {
|
|
@@ -719,7 +625,7 @@ function emitUpdate(run: LiveRun): void {
|
|
|
719
625
|
try { run.observe?.(publicStatus(live, true, true)); } catch { /* A widget failure is display-only. */ }
|
|
720
626
|
}
|
|
721
627
|
|
|
722
|
-
async function createRunStorage(root: string, id: string, request: Readonly<SubagentRunRequest>, status: PersistedSubagentStatus, external: StandaloneExternalConfiguration): Promise<string> {
|
|
628
|
+
async function createRunStorage(root: string, id: string, request: Readonly<SubagentRunRequest>, status: PersistedSubagentStatus, external: StandaloneExternalConfiguration | undefined): Promise<string> {
|
|
723
629
|
await secureDirectory(root);
|
|
724
630
|
const directory = runDirectory(root, id);
|
|
725
631
|
let created = false;
|
|
@@ -728,7 +634,7 @@ async function createRunStorage(root: string, id: string, request: Readonly<Suba
|
|
|
728
634
|
created = true;
|
|
729
635
|
await chmod(directory, 0o700);
|
|
730
636
|
await atomicJson(requestPath(directory), request);
|
|
731
|
-
await atomicJson(configurationPath(directory), external);
|
|
637
|
+
if (external !== undefined) await atomicJson(configurationPath(directory), { version: 1, configuration: external });
|
|
732
638
|
await atomicJson(statusPath(directory), status);
|
|
733
639
|
return directory;
|
|
734
640
|
} catch (error) {
|
|
@@ -918,6 +824,7 @@ function unavailable(operation: string): { ok: false; error: { code: "SUBAGENTS_
|
|
|
918
824
|
|
|
919
825
|
class PersistentSubagentManager implements SubagentManager {
|
|
920
826
|
private readonly activeRuns = new Map<string, LiveRun>();
|
|
827
|
+
private readonly pendingStarts = new Set<Promise<void>>();
|
|
921
828
|
private activeRunCount = 0;
|
|
922
829
|
private readonly terminalSummaries = new Map<string, TerminalSummary>();
|
|
923
830
|
private readonly reconciliationErrors = new Map<string, WorkflowError>();
|
|
@@ -968,8 +875,8 @@ class PersistentSubagentManager implements SubagentManager {
|
|
|
968
875
|
await this.ensureInitialized();
|
|
969
876
|
if (this.disposed) throw new WorkflowError("CANCELLED", "Subagent manager is disposed");
|
|
970
877
|
if (context.signal?.aborted) throw new WorkflowError("CANCELLED", "Subagent cancelled");
|
|
971
|
-
const external = frozenExternal
|
|
972
|
-
if (external
|
|
878
|
+
const external = frozenExternal;
|
|
879
|
+
if (external?.projectTrusted && !context.extensionContext.isProjectTrusted()) throw new WorkflowError("RESUME_INCOMPATIBLE", "Cannot retry standalone configuration from an untrusted project");
|
|
973
880
|
const id = randomUUID();
|
|
974
881
|
const startedAt = Date.now();
|
|
975
882
|
const sessionId = context.extensionContext.sessionManager.getSessionId();
|
|
@@ -982,13 +889,23 @@ class PersistentSubagentManager implements SubagentManager {
|
|
|
982
889
|
this.activeRunCount += 1;
|
|
983
890
|
const controller = new AbortController();
|
|
984
891
|
const initialStatus: PersistedSubagentStatus = { id, sessionId, state: "running", startedAt, owner: { ...owner } };
|
|
892
|
+
let finishStart!: () => void;
|
|
893
|
+
const pendingStart = new Promise<void>((resolve) => { finishStart = resolve; });
|
|
894
|
+
this.pendingStarts.add(pendingStart);
|
|
985
895
|
let directory: string;
|
|
986
896
|
try {
|
|
987
897
|
directory = await createRunStorage(storageDirectory(this.dependencies), id, snapshot, initialStatus, external);
|
|
898
|
+
if (this.disposePromise !== undefined || context.signal?.aborted) {
|
|
899
|
+
await atomicJson(statusPath(directory), reconciledStatus(initialStatus, "stopped", Date.now()));
|
|
900
|
+
throw new WorkflowError("CANCELLED", "Subagent cancelled");
|
|
901
|
+
}
|
|
988
902
|
} catch (error) {
|
|
989
903
|
this.activeRunCount -= 1;
|
|
990
904
|
if (error instanceof WorkflowError) throw error;
|
|
991
905
|
throw internalStorageError(error, `Unable to start subagent ${id}`);
|
|
906
|
+
} finally {
|
|
907
|
+
this.pendingStarts.delete(pendingStart);
|
|
908
|
+
finishStart();
|
|
992
909
|
}
|
|
993
910
|
const current: { run?: LiveRun } = {};
|
|
994
911
|
let resolveTerminal!: (result: ForegroundResult) => void;
|
|
@@ -1019,12 +936,9 @@ class PersistentSubagentManager implements SubagentManager {
|
|
|
1019
936
|
throw new WorkflowError("CANCELLED", "Subagent cancelled");
|
|
1020
937
|
}
|
|
1021
938
|
}
|
|
1022
|
-
const baseRoot = executionRoot(context, this.dependencies, controller.signal, id, worktree
|
|
1023
|
-
const root = loadingRegistry().modelAliases().length === 0 ? baseRoot : await addDynamicAliases(context, controller.signal, baseRoot);
|
|
939
|
+
const baseRoot = executionRoot(context, this.dependencies, controller.signal, id, worktree);
|
|
940
|
+
const root = external !== undefined || loadingRegistry().modelAliases().length === 0 ? baseRoot : await addDynamicAliases(context, controller.signal, baseRoot);
|
|
1024
941
|
const transport = this.dependencies.transport ?? localAgentTransport;
|
|
1025
|
-
const injectedExecutor = this.dependencies.createExecutor?.(root, transport);
|
|
1026
|
-
executorOwnership.default = injectedExecutor === undefined;
|
|
1027
|
-
if (current.run) current.run.executorOwnsSession = executorOwnership.default;
|
|
1028
942
|
const setSteer = (handler: SteerHandler): void => {
|
|
1029
943
|
const run = current.run;
|
|
1030
944
|
if (run) this.registerSteerHandler(run, handler);
|
|
@@ -1066,6 +980,13 @@ class PersistentSubagentManager implements SubagentManager {
|
|
|
1066
980
|
if (run.state !== "running") return;
|
|
1067
981
|
emitUpdate(run);
|
|
1068
982
|
}, (progress) => this.recordProgress(current.run, progress));
|
|
983
|
+
if (external !== undefined) options.configuration = structuredClone(external);
|
|
984
|
+
options.onConfiguration = (configuration) => atomicJson(configurationPath(directory), { version: 1, configuration });
|
|
985
|
+
const cwd = worktree?.cwd ?? root.cwd;
|
|
986
|
+
options.configuration = await new WorkflowAgentExecutor(root, transport).prepare(options, cwd, controller.signal);
|
|
987
|
+
const injectedExecutor = this.dependencies.createExecutor?.({ ...root, extensionSettings: options.configuration.settings }, transport);
|
|
988
|
+
executorOwnership.default = injectedExecutor === undefined;
|
|
989
|
+
if (current.run) current.run.executorOwnsSession = executorOwnership.default;
|
|
1069
990
|
if (injectedExecutor) return injectedExecutor.execute(snapshot.prompt, options, controller.signal, setSteer);
|
|
1070
991
|
return new WorkflowAgentExecutor(root, transport).execute(snapshot.prompt, options, controller.signal, [], setSteer);
|
|
1071
992
|
});
|
|
@@ -1176,11 +1097,11 @@ class PersistentSubagentManager implements SubagentManager {
|
|
|
1176
1097
|
|
|
1177
1098
|
async dispose(): Promise<void> {
|
|
1178
1099
|
if (this.disposePromise) return this.disposePromise;
|
|
1100
|
+
this.disposed = true;
|
|
1179
1101
|
this.disposePromise = (async () => {
|
|
1180
1102
|
await this.initialization;
|
|
1181
|
-
this.disposed = true;
|
|
1182
1103
|
const runs = [...this.activeRuns.values()];
|
|
1183
|
-
await Promise.allSettled(runs.map((run) => this.stopRun(run, true)));
|
|
1104
|
+
await Promise.allSettled([...this.pendingStarts, ...runs.map((run) => this.stopRun(run, true))]);
|
|
1184
1105
|
await Promise.allSettled([...this.notificationPromises]);
|
|
1185
1106
|
const owner = this.storageOwner;
|
|
1186
1107
|
this.storageOwner = undefined;
|
|
@@ -1533,9 +1454,8 @@ class PersistentSubagentManager implements SubagentManager {
|
|
|
1533
1454
|
const notify = this.dependencies.notify;
|
|
1534
1455
|
if (!notify || run.notificationSent || run.disposed) return;
|
|
1535
1456
|
run.notificationSent = true;
|
|
1536
|
-
const
|
|
1537
|
-
const
|
|
1538
|
-
const notification: SubagentNotification = { id: run.id, label, role, state: run.state as "completed" | "failed", ...(run.error === undefined ? {} : { error: run.error }) };
|
|
1457
|
+
const label = run.request.label?.trim() || "subagent";
|
|
1458
|
+
const notification: SubagentNotification = { id: run.id, label, state: run.state as "completed" | "failed", ...(run.error === undefined ? {} : { error: run.error }) };
|
|
1539
1459
|
const pending = Promise.resolve().then(() => notify(notification));
|
|
1540
1460
|
this.notificationPromises.add(pending);
|
|
1541
1461
|
void pending.then(() => { this.notificationPromises.delete(pending); }, () => { this.notificationPromises.delete(pending); });
|
|
@@ -71,16 +71,10 @@ async function inspectEntry(manager: SubagentManager, storageDirectory: string,
|
|
|
71
71
|
};
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
|
|
75
|
-
const role: unknown = request?.role;
|
|
76
|
-
if (typeof role === "string" && role.trim()) return role.trim();
|
|
77
|
-
const override = objectValue(role);
|
|
78
|
-
return typeof override?.name === "string" && override.name.trim() ? override.name.trim() : "none";
|
|
79
|
-
}
|
|
74
|
+
|
|
80
75
|
function shortId(id: string): string { return id.length > 12 ? id.slice(0, 8) : id; }
|
|
81
76
|
function entryName(entry: NavigatorEntry): string {
|
|
82
|
-
|
|
83
|
-
return boundedText(entry.request?.label?.trim() || (role === "none" ? shortId(entry.status.id) : role), 256);
|
|
77
|
+
return boundedText(entry.request?.label?.trim() || shortId(entry.status.id), 256);
|
|
84
78
|
}
|
|
85
79
|
/** Names for a list of runs; a repeated name carries its short ID, as in the `/workflow` picker, so every row stays unique. */
|
|
86
80
|
function listNames(entries: readonly NavigatorEntry[]): string[] {
|
|
@@ -113,7 +107,7 @@ function usageAccounting(status: SubagentStatus): SubagentProgress["accounting"]
|
|
|
113
107
|
return status.progress?.accounting;
|
|
114
108
|
}
|
|
115
109
|
function detailPresentation(inspection: Inspection): AgentDetailPresentation {
|
|
116
|
-
const { status
|
|
110
|
+
const { status } = inspection.entry;
|
|
117
111
|
const attempt = latestAttempt(status);
|
|
118
112
|
const state = status.progress?.state;
|
|
119
113
|
const activity = status.progress?.activity;
|
|
@@ -122,14 +116,12 @@ function detailPresentation(inspection: Inspection): AgentDetailPresentation {
|
|
|
122
116
|
const lastEventAt = status.progress?.lastEventAt;
|
|
123
117
|
const attempts = status.attempts ?? attempt?.attempt;
|
|
124
118
|
const accounting = usageAccounting(status);
|
|
125
|
-
const role = requestRole(request);
|
|
126
119
|
const error = status.error ?? attempt?.error;
|
|
127
120
|
return {
|
|
128
121
|
state: status.state,
|
|
129
122
|
...(activity === undefined ? {} : { activity: { kind: activity.kind, text: boundedText(activity.text) } }),
|
|
130
123
|
...(lastEventAt === undefined ? {} : { lastEventAt }),
|
|
131
124
|
...(model === undefined ? {} : { model: { provider: boundedText(model.provider, 256), model: boundedText(model.model, 256), ...(model.thinking === undefined ? {} : { thinking: model.thinking }) } }),
|
|
132
|
-
...(role === "none" ? {} : { role: boundedText(role, 256) }),
|
|
133
125
|
...(tools === undefined ? {} : { tools: tools.slice(0, 256).map((tool) => boundedText(tool, 256)) }),
|
|
134
126
|
...(attempts === undefined ? {} : { attempts }),
|
|
135
127
|
...(status.startedAt === undefined ? {} : { startedAt: status.startedAt }),
|
|
@@ -186,8 +178,7 @@ function standaloneActionContext(manager: SubagentManager, inspection: Inspectio
|
|
|
186
178
|
const session = attempt.session;
|
|
187
179
|
const live = liveData?.liveSession && session && liveData.liveSession.reference.transport === session.transport && liveData.liveSession.reference.sessionId === session.sessionId ? liveData.liveSession : undefined;
|
|
188
180
|
const label = request?.label?.trim();
|
|
189
|
-
const
|
|
190
|
-
const name = label || (role === "none" ? "subagent" : role);
|
|
181
|
+
const name = label || "subagent";
|
|
191
182
|
const ui = {
|
|
192
183
|
notify: (message: string, level: "info" | "warning" | "error" = "info") => { context.ui.notify(message, level); },
|
|
193
184
|
confirm: (title: string, message: string) => context.ui.confirm(title, message),
|