@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.
Files changed (123) hide show
  1. package/CHANGELOG.md +77 -0
  2. package/README.md +2 -2
  3. package/dist/src/agent-execution.d.ts +43 -10
  4. package/dist/src/agent-execution.js +365 -127
  5. package/dist/src/background-widget.d.ts +0 -1
  6. package/dist/src/background-widget.js +4 -9
  7. package/dist/src/decoders.d.ts +2 -3
  8. package/dist/src/decoders.js +33 -37
  9. package/dist/src/execution.d.ts +2 -0
  10. package/dist/src/execution.js +5 -5
  11. package/dist/src/host-delivery.js +4 -8
  12. package/dist/src/host-navigator.js +5 -2
  13. package/dist/src/host-phases.d.ts +1 -0
  14. package/dist/src/host-recovery.d.ts +6 -6
  15. package/dist/src/host-recovery.js +14 -22
  16. package/dist/src/host-runtime.js +56 -5
  17. package/dist/src/host-view.d.ts +1 -1
  18. package/dist/src/host-view.js +17 -15
  19. package/dist/src/host.js +62 -87
  20. package/dist/src/index.d.ts +1 -2
  21. package/dist/src/index.js +1523 -1630
  22. package/dist/src/index.js.map +3 -3
  23. package/dist/src/paths.d.ts +7 -6
  24. package/dist/src/paths.js +24 -15
  25. package/dist/src/persistence.d.ts +1 -0
  26. package/dist/src/persistence.js +1 -0
  27. package/dist/src/pi-runtime-runner.js +4 -1
  28. package/dist/src/registry.d.ts +9 -7
  29. package/dist/src/registry.js +28 -70
  30. package/dist/src/runtime/agent-runner.d.ts +2 -0
  31. package/dist/src/session-lease.d.ts +1 -1
  32. package/dist/src/session-lease.js +45 -11
  33. package/dist/src/settings.d.ts +6 -3
  34. package/dist/src/settings.js +36 -47
  35. package/dist/src/store.d.ts +3 -1
  36. package/dist/src/store.js +17 -1
  37. package/dist/src/trajectory.d.ts +0 -1
  38. package/dist/src/trajectory.js +2 -3
  39. package/dist/src/types.d.ts +100 -44
  40. package/dist/src/types.js +4 -7
  41. package/dist/src/utils.d.ts +3 -2
  42. package/dist/src/utils.js +20 -10
  43. package/dist/src/validation.d.ts +8 -2
  44. package/dist/src/validation.js +122 -86
  45. package/dist/starter/index.js +8 -9
  46. package/dist/starter/index.js.map +1 -1
  47. package/dist/starter/review-loop.js +8 -7
  48. package/dist/subagents/index.js +654 -814
  49. package/dist/subagents/index.js.map +3 -3
  50. package/dist/subagents/src/contracts.d.ts +4 -3
  51. package/dist/subagents/src/contracts.js +2 -2
  52. package/dist/subagents/src/index.d.ts +1 -1
  53. package/dist/subagents/src/index.js +1 -2
  54. package/dist/subagents/src/manager.js +49 -70
  55. package/dist/subagents/src/navigator.js +3 -14
  56. package/dist/subagents/src/view.d.ts +0 -1
  57. package/dist/subagents/src/view.js +2 -16
  58. package/dist/subagents/src/worktree.js +1 -1
  59. package/dist/trajectory/assets/index.html +8 -8
  60. package/dist/trajectory/index.js +487 -891
  61. package/dist/trajectory/index.js.map +3 -3
  62. package/dist/trajectory/src/assets/index.html +8 -8
  63. package/dist/trajectory/src/index.js +126 -86
  64. package/dist/trajectory/src/server.js +264 -25
  65. package/dist/trajectory/src/server.js.map +3 -3
  66. package/dist/trajectory/src/startup-mutex.d.ts +17 -0
  67. package/dist/trajectory/src/startup-mutex.js +172 -0
  68. package/examples/workflow-extension-template/README.md +4 -7
  69. package/examples/workflow-extension-template/extension.test.mjs +4 -8
  70. package/examples/workflow-extension-template/index.js +0 -3
  71. package/package.json +5 -5
  72. package/skills/pi-extensible-workflows/SKILL.md +10 -8
  73. package/src/agent-execution.ts +339 -127
  74. package/src/background-widget.ts +4 -10
  75. package/src/decoders.ts +31 -37
  76. package/src/execution.ts +5 -5
  77. package/src/host-delivery.ts +4 -8
  78. package/src/host-navigator.ts +4 -2
  79. package/src/host-phases.ts +1 -1
  80. package/src/host-recovery.ts +20 -26
  81. package/src/host-runtime.ts +44 -6
  82. package/src/host-view.ts +18 -11
  83. package/src/host.ts +62 -113
  84. package/src/index.ts +1 -2
  85. package/src/paths.ts +18 -12
  86. package/src/persistence.ts +1 -0
  87. package/src/pi-runtime-runner.ts +3 -1
  88. package/src/registry.ts +27 -63
  89. package/src/runtime/agent-runner.ts +2 -0
  90. package/src/session-lease.ts +34 -6
  91. package/src/settings.ts +35 -47
  92. package/src/store.ts +18 -2
  93. package/src/trajectory.ts +2 -4
  94. package/src/types.ts +71 -30
  95. package/src/utils.ts +18 -9
  96. package/src/validation.ts +102 -79
  97. package/starter/index.ts +0 -1
  98. package/starter/review-loop.ts +8 -7
  99. package/starter/roles/developer.md +0 -1
  100. package/starter/roles/oracle.md +0 -1
  101. package/starter/roles/researcher.md +0 -1
  102. package/starter/roles/reviewer.md +0 -1
  103. package/starter/roles/scout.md +0 -1
  104. package/subagents/README.md +8 -14
  105. package/subagents/src/contracts.ts +3 -4
  106. package/subagents/src/index.ts +2 -3
  107. package/subagents/src/manager.ts +45 -125
  108. package/subagents/src/navigator.ts +4 -13
  109. package/subagents/src/view.ts +4 -14
  110. package/subagents/src/worktree.ts +1 -1
  111. package/trajectory/src/assets/index.html +8 -8
  112. package/trajectory/src/index.ts +106 -82
  113. package/trajectory/src/server.ts +44 -8
  114. package/trajectory/src/startup-mutex.ts +127 -0
  115. package/dist/src/roles.d.ts +0 -70
  116. package/dist/src/roles.js +0 -326
  117. package/dist/starter/roles/developer.md +0 -6
  118. package/dist/starter/roles/oracle.md +0 -18
  119. package/dist/starter/roles/researcher.md +0 -20
  120. package/dist/starter/roles/reviewer.md +0 -19
  121. package/dist/starter/roles/scout.md +0 -18
  122. package/examples/workflow-extension-template/roles/reviewer.md +0 -4
  123. package/src/roles.ts +0 -327
@@ -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 previous review findings and complete the task.
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
- { task, review: review.findings },
30
+ { review: review.findings },
30
31
  );
31
- devResult = await agent(devPrompt, { role: "developer" });
32
+ devResult = await developer.send(devPrompt);
32
33
  review = await agent(
33
34
  prompt(
34
- `Review the implementation against the task. Set pass=true only when the task is complete and correct. The developer may have addressed a previous review run of yours. So its summary is related to the last round of review if present.
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
- <last_review>{review}</last_review>
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 };
@@ -1,5 +1,4 @@
1
1
  ---
2
- model: developer-model
3
2
  description: Developer focused agent
4
3
  ---
5
4
 
@@ -1,5 +1,4 @@
1
1
  ---
2
- model: oracle-model
3
2
  tools: ["!*", "read", "grep", "find", "ls", "bash"]
4
3
  description: Oracle. Use for a second opinion on a plan, design, or decision before committing to it
5
4
  ---
@@ -1,5 +1,4 @@
1
1
  ---
2
- model: researcher-model
3
2
  tools: ["!edit", "!write", "!bash"]
4
3
  description: Researcher. Use for deep research across the codebase, docs, and the web when web tools are available
5
4
  ---
@@ -1,5 +1,4 @@
1
1
  ---
2
- model: reviewer-model
3
2
  tools: ["!*", "read", "grep", "find", "ls"]
4
3
  description: Reviewer. Use when we need to review decisions or code changes
5
4
  ---
@@ -1,5 +1,4 @@
1
1
  ---
2
- model: scout-model
3
2
  tools: ["!*", "read", "grep", "find", "ls"]
4
3
  description: Scout. Use for fast read-only codebase recon before planning or implementing
5
4
  ---
@@ -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 roles, model aliases, settings including extension-defined namespaces, and agent options: `label`, `model`, `tools`, `skills`, `extensions`, `contextFiles`, `worktree`, `outputSchema`, `retries` (0 through 255), and `timeoutMs`.
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, role, 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. Requests without a role show `Role: (none)`. 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.
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
- Every tool schema is a closed object. Unknown properties are rejected. The model-visible standalone surface contains exactly these five names:
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[], role?: string, worktree?: string, outputSchema?: object, retries?: integer 0..255, timeoutMs?: positive integer \| null }` |
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. A `role` string selects an existing workflow role. Model, tools, skills, extensions, and contextFiles are top-level `AgentOptions` overrides applied after the role file. Concrete models are `provider/model:thinking`. Capability selectors use ordered minimatch rules and are applied after global, trusted-project, and role selectors. Use `tools: ["*"]` to re-enable all tools after a role restriction.
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 external settings and role definitions 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.
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 a workflow `agent(...)`: model, thinking level, tool, skill, and extension selectors, named roles or inline role overrides, worktrees, structured output, retries, and timeout. The extension also reuses workflow model aliases, settings, resource selector policy, and role discovery:
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 role/settings reuse.
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: false });
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
  }
@@ -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 role = notification.role?.trim() || "none";
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
  }
@@ -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
- addAccounting,
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 standaloneExtensionSettingsPath(cwd: string, agentDir: string, trustedProject: boolean, external: StandaloneExternalConfiguration): string {
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
- validateStandaloneExtensionSources(context, external, registry, agentDir);
211
- const extensionSettings = trustedProject && external.projectExtensionSettings !== undefined ? external.projectExtensionSettings : external.globalExtensionSettings;
212
- const extensionSettingsPath = standaloneExtensionSettingsPath(extensionContext.cwd, agentDir, trustedProject, external);
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
- agentDefinitions: external.agentDefinitions,
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 role = request.role;
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) || !jsonValue(value) || typeof value.projectTrusted !== "boolean") return undefined;
402
- const globalExtensionSettings = value.globalExtensionSettings === undefined ? undefined : decodeWorkflowExtensions(value.globalExtensionSettings);
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 ?? standaloneExternalConfiguration(context, this.dependencies);
972
- if (external.projectTrusted && !context.extensionContext.isProjectTrusted()) throw new WorkflowError("RESUME_INCOMPATIBLE", "Cannot retry standalone configuration from an untrusted project");
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, external);
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 role = roleNameOf(run.request.role) ?? "none";
1537
- const label = run.request.label?.trim() || (role === "none" ? "subagent" : role);
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
- function requestRole(request: SubagentRunRequest | undefined): string {
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
- const role = requestRole(entry.request);
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, request } = inspection.entry;
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 role = requestRole(request);
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),