@marcoscale98/pi-extensible-workflows 5.17.0-fork.1 → 5.19.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 (80) hide show
  1. package/CHANGELOG.md +68 -0
  2. package/dist/src/agent-execution.js +81 -39
  3. package/dist/src/background-widget.d.ts +5 -0
  4. package/dist/src/background-widget.js +1 -1
  5. package/dist/src/decoders.js +26 -0
  6. package/dist/src/execution.d.ts +4 -2
  7. package/dist/src/execution.js +43 -2
  8. package/dist/src/host-delivery.js +1 -0
  9. package/dist/src/host-recovery.d.ts +5 -2
  10. package/dist/src/host-recovery.js +3 -3
  11. package/dist/src/host-view.d.ts +5 -0
  12. package/dist/src/host-view.js +29 -5
  13. package/dist/src/host.d.ts +2 -2
  14. package/dist/src/host.js +190 -37
  15. package/dist/src/index.js +495 -93
  16. package/dist/src/index.js.map +3 -3
  17. package/dist/src/paths.d.ts +2 -0
  18. package/dist/src/paths.js +2 -0
  19. package/dist/src/registry.js +1 -1
  20. package/dist/src/roles.js +4 -3
  21. package/dist/src/script-tools.d.ts +27 -0
  22. package/dist/src/script-tools.js +87 -0
  23. package/dist/src/settings.d.ts +13 -0
  24. package/dist/src/settings.js +27 -3
  25. package/dist/src/trajectory.d.ts +12 -2
  26. package/dist/src/trajectory.js +30 -8
  27. package/dist/src/types.d.ts +30 -2
  28. package/dist/src/types.js +1 -1
  29. package/dist/src/utils.d.ts +19 -0
  30. package/dist/src/utils.js +30 -0
  31. package/dist/src/validation.d.ts +6 -1
  32. package/dist/src/validation.js +21 -1
  33. package/dist/starter/index.js +0 -2
  34. package/dist/starter/index.js.map +1 -1
  35. package/dist/starter/review-loop.d.ts +0 -13
  36. package/dist/starter/review-loop.js +0 -2
  37. package/dist/subagents/index.js +267 -159
  38. package/dist/subagents/index.js.map +2 -2
  39. package/dist/subagents/src/contracts.d.ts +45 -29
  40. package/dist/subagents/src/contracts.js +18 -7
  41. package/dist/subagents/src/index.d.ts +1 -1
  42. package/dist/subagents/src/index.js +13 -6
  43. package/dist/subagents/src/manager.js +3 -3
  44. package/dist/subagents/src/view.d.ts +1 -0
  45. package/dist/subagents/src/view.js +13 -52
  46. package/dist/trajectory/assets/index.html +27 -5
  47. package/dist/trajectory/index.js +76 -28
  48. package/dist/trajectory/index.js.map +2 -2
  49. package/dist/trajectory/src/assets/index.html +27 -5
  50. package/dist/trajectory/src/index.js +8 -18
  51. package/dist/trajectory/src/server.js +4 -2
  52. package/dist/trajectory/src/server.js.map +2 -2
  53. package/package.json +6 -6
  54. package/skills/pi-extensible-workflows/SKILL.md +2 -0
  55. package/src/agent-execution.ts +77 -41
  56. package/src/background-widget.ts +1 -1
  57. package/src/decoders.ts +20 -0
  58. package/src/execution.ts +41 -4
  59. package/src/host-delivery.ts +1 -0
  60. package/src/host-recovery.ts +8 -7
  61. package/src/host-view.ts +23 -5
  62. package/src/host.ts +173 -35
  63. package/src/paths.ts +3 -0
  64. package/src/registry.ts +1 -1
  65. package/src/roles.ts +4 -3
  66. package/src/script-tools.ts +76 -0
  67. package/src/settings.ts +22 -4
  68. package/src/trajectory.ts +30 -11
  69. package/src/types.ts +11 -4
  70. package/src/utils.ts +28 -0
  71. package/src/validation.ts +19 -1
  72. package/starter/review-loop.ts +0 -2
  73. package/subagents/README.md +2 -2
  74. package/subagents/src/contracts.ts +18 -7
  75. package/subagents/src/index.ts +16 -6
  76. package/subagents/src/manager.ts +3 -3
  77. package/subagents/src/view.ts +13 -50
  78. package/trajectory/src/assets/index.html +27 -5
  79. package/trajectory/src/index.ts +6 -9
  80. package/trajectory/src/server.ts +2 -2
package/src/settings.ts CHANGED
@@ -3,7 +3,7 @@ import { mkdirSync, readFileSync } from "node:fs";
3
3
  import { homedir } from "node:os";
4
4
  import { dirname, join, resolve } from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
- import { getAgentDir } from "@earendil-works/pi-coding-agent";
6
+ import { getAgentDir, type ToolAnnotations } from "@earendil-works/pi-coding-agent";
7
7
  import type { AgentResourcePolicy, AgentResourceSelectors, AgentResourceSelectorSet, ContextFileScope, JsonValue, WorkflowExtensionSettings, WorkflowRetentionSettings, WorkflowSettings, WorkflowSettingsOverrides, WorkflowSettingsResolution, WorkflowSettingsSources } from "./types.js";
8
8
  import { isContextFileScope } from "./types.js";
9
9
  import { annotateModelAliasError, deepFreeze, errorText, fail, isNodeError, jsonValue, modelCapability, object, positiveInteger, resourcePatternHasMagic, unknownModel, validateModelAliases, validateResourcePattern, validWorkflowExtensionNamespace } from "./utils.js";
@@ -14,7 +14,8 @@ const ROLE_DIRECTORY = "pi-extensible-workflows";
14
14
  export const DEFAULT_SETTINGS: Readonly<WorkflowSettings> = Object.freeze({ concurrency: 8, backgroundWidget: true });
15
15
  export function workflowSettingsPath(agentDir = getAgentDir()): string { return join(agentDir, ROLE_DIRECTORY, "settings.json"); }
16
16
  function normalizedResourcePath(value: string, settingsPath: string): string {
17
- if (value === "*") return value;
17
+ // Built-in extensions are named `builtin:<name>`, not by a path.
18
+ if (value === "*" || value.startsWith("builtin:")) return value;
18
19
  let expanded = value === "~" ? homedir() : value.startsWith("~/") || value.startsWith("~\\") ? join(homedir(), value.slice(2)) : value;
19
20
  if (expanded.startsWith("file://")) expanded = fileURLToPath(expanded);
20
21
  const resolved = resolve(dirname(settingsPath), expanded);
@@ -115,7 +116,7 @@ function parseSettings(path: string, partial: boolean): Readonly<WorkflowSetting
115
116
  fail("CONFIG_ERROR", `Invalid workflow settings JSON at ${path}: ${errorText(error)}`);
116
117
  }
117
118
  if (!object(parsed)) fail("INVALID_SETTINGS", `Workflow settings at ${path} must be an object`);
118
- const allowed = new Set(["concurrency", "modelAliases", "skills", "extensions", "extensionSettings", "tools", "retention", "worktreePostCreateCommand", ...(partial ? [] : ["backgroundWidget"]) ]);
119
+ const allowed = new Set(["concurrency", "modelAliases", "skills", "extensions", "extensionSettings", "tools", "retention", "worktreePostCreateCommand", ...(partial ? [] : ["backgroundWidget", "codemodeTools"]) ]);
119
120
  const unknown = Object.keys(parsed).find((key) => !allowed.has(key));
120
121
  if (Object.prototype.hasOwnProperty.call(parsed, "disabledAgentResources")) fail("INVALID_SETTINGS", `disabledAgentResources is no longer supported; use skills, extensions, and tools selectors (settings: ${path})`);
121
122
  if (unknown) fail("INVALID_SETTINGS", `Unknown workflow setting at ${path}: ${unknown}`);
@@ -123,6 +124,8 @@ function parseSettings(path: string, partial: boolean): Readonly<WorkflowSetting
123
124
  if (concurrency !== undefined && (!positiveInteger(concurrency) || concurrency > 16)) fail("INVALID_SETTINGS", `${path}.concurrency must be an integer from 1 to 16`);
124
125
  const backgroundWidget = parsed.backgroundWidget === undefined ? (partial ? undefined : DEFAULT_SETTINGS.backgroundWidget) : parsed.backgroundWidget;
125
126
  if (backgroundWidget !== undefined && typeof backgroundWidget !== "boolean") fail("INVALID_SETTINGS", `${path}.backgroundWidget must be a boolean`);
127
+ const codemodeTools = parsed.codemodeTools;
128
+ if (codemodeTools !== undefined && codemodeTools !== "all" && codemodeTools !== "read-only" && codemodeTools !== "none") fail("INVALID_SETTINGS", `${path}.codemodeTools must be "all", "read-only", or "none"`);
126
129
  const modelAliases = parsed.modelAliases === undefined ? undefined : validateModelAliases(parsed.modelAliases, path);
127
130
  const skills = validateSelectorList(parsed.skills, path, "skills");
128
131
  const tools = validateSelectorList(parsed.tools, path, "tools");
@@ -131,13 +134,28 @@ function parseSettings(path: string, partial: boolean): Readonly<WorkflowSetting
131
134
  const retention = validateRetention(parsed.retention, path);
132
135
  const worktreePostCreateCommand = validateWorktreePostCreateCommand(parsed.worktreePostCreateCommand, path);
133
136
  return Object.freeze({
134
- ...(concurrency === undefined ? {} : { concurrency }), ...(backgroundWidget === undefined ? {} : { backgroundWidget }), ...(modelAliases === undefined ? {} : { modelAliases }),
137
+ ...(concurrency === undefined ? {} : { concurrency }), ...(backgroundWidget === undefined ? {} : { backgroundWidget }), ...(codemodeTools === undefined ? {} : { codemodeTools }), ...(modelAliases === undefined ? {} : { modelAliases }),
135
138
  ...(skills === undefined ? {} : { skills }), ...(extensions === undefined ? {} : { extensions }),
136
139
  ...(extensionSettings === undefined ? {} : { extensionSettings }), ...(tools === undefined ? {} : { tools }), ...(retention === undefined ? {} : { retention }),
137
140
  ...(worktreePostCreateCommand === undefined ? {} : { worktreePostCreateCommand }),
138
141
  });
139
142
  }
140
143
  export function loadSettings(path = workflowSettingsPath()): Readonly<WorkflowSettings> { return parseSettings(path, false); }
144
+ const READ_ONLY_WORKFLOW_TOOLS: ReadonlySet<string> = new Set(["workflow_status", "workflow_catalog", "subagents_inspect"]);
145
+ /**
146
+ * Exposure and annotations of a workflow or subagent tool. Under the global `codemodeTools` setting,
147
+ * `model-only` keeps the model's direct access and stops codemode scripts from calling the tool.
148
+ * Tools are registered once at load, so the setting is global and applies after a reload.
149
+ * Read-only tools carry `readOnlyHint` so permission extensions need not confirm them.
150
+ */
151
+ export function workflowToolExposure(name: string, codemodeTools: WorkflowSettings["codemodeTools"]): { exposure?: "model-only"; annotations?: ToolAnnotations } {
152
+ const readOnly = READ_ONLY_WORKFLOW_TOOLS.has(name);
153
+ return { ...(codemodeTools === "none" || (codemodeTools === "read-only" && !readOnly) ? { exposure: "model-only" as const } : {}), ...(readOnly ? { annotations: { readOnlyHint: true } } : {}) };
154
+ }
155
+ /** The `codemodeTools` setting at load time. Invalid settings keep the default; launches report them. */
156
+ export function loadCodemodeToolsSetting(agentDir?: string): WorkflowSettings["codemodeTools"] {
157
+ try { return loadSettings(workflowSettingsPath(agentDir)).codemodeTools; } catch { return undefined; }
158
+ }
141
159
  export function loadSettingsOverrides(path: string): Readonly<WorkflowSettingsOverrides> { return parseSettings(path, true); }
142
160
  export function resolveWorkflowSettings(cwd: string, projectTrusted: boolean, globalSettingsPath = workflowSettingsPath()): WorkflowSettingsResolution {
143
161
  const projectSettingsPath = workflowProjectSettingsPath(cwd);
package/src/trajectory.ts CHANGED
@@ -5,12 +5,13 @@ import { basename, dirname, join, resolve } from "node:path";
5
5
  import { createBashToolDefinition, createEditToolDefinition, createFindToolDefinition, createGrepToolDefinition, createLsToolDefinition, createReadToolDefinition, createWriteToolDefinition, DefaultPackageManager, getAgentDir, SettingsManager, DEFAULT_MAX_BYTES } from "@earendil-works/pi-coding-agent";
6
6
  import { listRunIds, RunStore, type AwaitingCheckpoint, type CompletedOperation, type EffectiveSystemPrompt, type PersistedRun } from "./persistence.js";
7
7
  import { navigatorAttentionSortByState } from "./host-view.js";
8
- import type { AgentAttemptSummary, AgentRecord, JsonValue, LaunchSnapshot, WorkflowAgentSessionReference } from "./types.js";
8
+ import type { AgentAttemptSummary, AgentRecord, JsonValue, LaunchSnapshot, WorkflowAgentSessionReference, WorkflowErrorCode, WorkflowScriptCall } from "./types.js";
9
9
  import { normalizeSubagentRunRequest, type SubagentProgress, type SubagentRunRequest, type SubagentStatus } from "../subagents/src/contracts.js";
10
10
  import { statusValue, subagentErrorValue } from "../subagents/src/decode.js";
11
11
  import { isNodeError, jsonValue, object, resourcePatternHasMagic, selectResourcesByLayers } from "./utils.js";
12
12
  import type { TrajectoryAction, TrajectoryTarget } from "./trajectory-contracts.js";
13
13
  import { canonicalPath, sameFilesystemPath } from "./paths.js";
14
+ import { TOOL_TIMING_ENTRY_TYPE } from "./tool-timing.js";
14
15
 
15
16
  export const TRAJECTORY_MAX_TRANSCRIPT_BYTES = 2 * 1024 * 1024;
16
17
  const TRAJECTORY_MAX_NON_TIMING_ENTRIES = 400;
@@ -22,7 +23,8 @@ export type TrajectoryAgentOutput =
22
23
  | { readonly status: "truncated"; readonly kind: "result" | "failure"; readonly bytes: number; readonly path?: string }
23
24
  | { readonly status: "unavailable" };
24
25
  export type TrajectoryAgent = AgentRecord & { readonly output: TrajectoryAgentOutput };
25
- export type TrajectoryRunRecord = Omit<PersistedRun, "agents"> & { readonly agents: readonly TrajectoryAgent[] };
26
+ export type TrajectoryScriptCall = WorkflowScriptCall & { readonly output: TrajectoryAgentOutput | { readonly status: "completed" } | { readonly status: "failed" | "cancelled"; readonly code: WorkflowErrorCode } };
27
+ export type TrajectoryRunRecord = Omit<PersistedRun, "agents" | "scriptCalls"> & { readonly agents: readonly TrajectoryAgent[]; readonly scriptCalls?: readonly TrajectoryScriptCall[] };
26
28
  export type TrajectoryRun = {
27
29
  run: TrajectoryRunRecord;
28
30
  snapshot: Readonly<LaunchSnapshot>;
@@ -124,7 +126,7 @@ function transcriptToolCallId(value: unknown): string | undefined {
124
126
  for (const part of message.content) if (object(part) && typeof part.id === "string") return part.id;
125
127
  return undefined;
126
128
  }
127
- function isTimingTranscriptEntry(value: unknown): boolean { return object(value) && value.type === "custom" && value.customType === "pi-workflows:tool-timing"; }
129
+ export function isTimingTranscriptEntry(value: unknown): boolean { return object(value) && value.type === "custom" && value.customType === TOOL_TIMING_ENTRY_TYPE; }
128
130
  function timingToolCallId(value: unknown): string | undefined {
129
131
  if (!object(value) || !isTimingTranscriptEntry(value) || !object(value.data)) return undefined;
130
132
  return typeof value.data.toolCallId === "string" ? value.data.toolCallId : undefined;
@@ -243,7 +245,8 @@ export function withPiToolDescriptionsForTools(tools: readonly string[], cwd: st
243
245
  function canonicalExtensionSelector(selector: string, base: string): string {
244
246
  const negated = selector.startsWith("!");
245
247
  const body = negated ? selector.slice(1) : selector;
246
- if (body === "*" || body === "**" || body.startsWith("**/")) return selector;
248
+ // Built-in extensions are named `builtin:<name>`, not by a path.
249
+ if (body === "*" || body === "**" || body.startsWith("**/") || body.startsWith("builtin:")) return selector;
247
250
  const resolved = resolve(base, body);
248
251
  if (resourcePatternHasMagic(body)) return `${negated ? "!" : ""}${resolved}`;
249
252
  return `${negated ? "!" : ""}${canonicalPath(resolved)}`;
@@ -319,11 +322,17 @@ function failedAgentOutput(agent: PersistedRun["agents"][number]): TrajectoryAge
319
322
  const status = agent.state === "cancelled" ? "cancelled" : "failed";
320
323
  return { status, code: error?.code ?? (status === "cancelled" ? "CANCELLED" : "AGENT_FAILED"), message: error?.message ?? `Agent ${agent.state}` };
321
324
  }
325
+ function operationOutput(operation: CompletedOperation): TrajectoryAgentOutput {
326
+ const bytes = serializedJsonBytes(operation.value);
327
+ return bytes > DEFAULT_MAX_BYTES ? { status: "truncated", kind: "result", bytes } : { status: "available", value: operation.value, bytes };
328
+ }
329
+ function outputForCall(call: WorkflowScriptCall, operation: CompletedOperation | undefined): TrajectoryScriptCall["output"] {
330
+ if (operation !== undefined) return call.kind === "tool" ? { status: "completed" } : operationOutput(operation);
331
+ if (call.error) return { status: call.error.code === "CANCELLED" ? "cancelled" : "failed", code: call.error.code, ...(call.kind === "shell" ? { message: call.error.message ?? "Shell failed" } : {}) };
332
+ return call.finishedAt === undefined ? { status: "pending" } : { status: "unavailable" };
333
+ }
322
334
  function outputForAgent(agent: PersistedRun["agents"][number], operation: CompletedOperation | undefined): TrajectoryAgentOutput {
323
- if (operation !== undefined) {
324
- const bytes = serializedJsonBytes(operation.value);
325
- return bytes > DEFAULT_MAX_BYTES ? { status: "truncated", kind: "result", bytes } : { status: "available", value: operation.value, bytes };
326
- }
335
+ if (operation !== undefined) return operationOutput(operation);
327
336
  if (agent.state === "failed" || agent.state === "cancelled") return failedAgentOutput(agent);
328
337
  if (agent.state === "completed") return { status: "unavailable" };
329
338
  return { status: "pending" };
@@ -331,17 +340,27 @@ function outputForAgent(agent: PersistedRun["agents"][number], operation: Comple
331
340
  export function applyTrajectoryAgentOutputs(run: PersistedRun, operations: readonly CompletedOperation[]): TrajectoryRunRecord {
332
341
  const byPath = new Map(operations.map((operation) => [operation.path, operation]));
333
342
  const agents: readonly TrajectoryAgent[] = run.agents.map((agent) => ({ ...agent, output: outputForAgent(agent, agent.resultPath === undefined ? undefined : byPath.get(agent.resultPath)) }));
334
- return { ...run, agents };
343
+ const scriptCalls = run.scriptCalls?.map((call): TrajectoryScriptCall => {
344
+ const recorded = { ...call, output: outputForCall(call, byPath.get(call.path)) };
345
+ if (call.kind === "tool") {
346
+ delete recorded.input;
347
+ delete recorded.inputBytes;
348
+ if (call.error) recorded.error = { code: call.error.code };
349
+ }
350
+ return recorded;
351
+ });
352
+ const rest: Omit<PersistedRun, "scriptCalls"> = run;
353
+ return { ...rest, agents, ...(scriptCalls === undefined ? {} : { scriptCalls }) };
335
354
  }
336
355
  function overlayTrajectoryRun(run: TrajectoryRunRecord, overlay: (run: PersistedRun) => PersistedRun): TrajectoryRunRecord {
337
- const overlaid = overlay(run);
356
+ const overlaid: Omit<PersistedRun, "scriptCalls"> = overlay(run);
338
357
  const outputs = new Map(run.agents.map((agent) => [agent.id, agent.output]));
339
358
  const agents: readonly TrajectoryAgent[] = overlaid.agents.map((agent) => {
340
359
  const previous = outputs.get(agent.id);
341
360
  const output = previous?.status === "available" || previous?.status === "truncated" ? previous : outputForAgent(agent, undefined);
342
361
  return { ...agent, output };
343
362
  });
344
- return { ...overlaid, agents };
363
+ return { ...overlaid, agents, ...(run.scriptCalls === undefined ? {} : { scriptCalls: run.scriptCalls }) };
345
364
  }
346
365
  async function loadTrajectoryRun(store: RunStore, includeTranscripts = true): Promise<TrajectoryRun> {
347
366
  const value = await store.load();
package/src/types.ts CHANGED
@@ -29,7 +29,7 @@ export const WORKFLOW_BLOCKED_EVENT = "workflow:blocked";
29
29
  export const ERROR_CODES = [
30
30
  "CONFIG_ERROR", "INVALID_SETTINGS", "INVALID_SYNTAX", "INVALID_METADATA", "DUPLICATE_NAME", "INVALID_SCHEMA", "UNKNOWN_MODEL", "UNKNOWN_TOOL", "UNKNOWN_AGENT_TYPE",
31
31
  "RUN_OWNED", "RUN_NOT_FOUND", "REGISTRY_FROZEN", "GLOBAL_COLLISION", "MISSING_WORKFLOW", "RPC_LIMIT_EXCEEDED", "SHELL_FAILED", "AGENT_TIMEOUT", "AGENT_FAILED", "AGENT_RESULT_COLLECTED", "RESULT_INVALID",
32
- "CANCELLED", "WORKER_UNRESPONSIVE", "WORKTREE_FAILED", "RESUME_INCOMPATIBLE", "BUDGET_EXHAUSTED", "INTERNAL_ERROR",
32
+ "TOOL_FAILED", "CANCELLED", "WORKER_UNRESPONSIVE", "WORKTREE_FAILED", "RESUME_INCOMPATIBLE", "BUDGET_EXHAUSTED", "INTERNAL_ERROR",
33
33
  ] as const;
34
34
 
35
35
  export const THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"] as const;
@@ -97,8 +97,9 @@ export interface WorkflowExtensionSettingsValidatorContext { source: WorkflowExt
97
97
  export type WorkflowExtensionSettingsValidator = (settings: Readonly<WorkflowExtensionSettings>, context: Readonly<WorkflowExtensionSettingsValidatorContext>) => void;
98
98
  export interface WorkflowSessionStartEvent { type: "session_start"; reason: "startup" | "reload" | "new" | "resume" | "fork"; previousSessionFile?: string; settings: Readonly<WorkflowExtensionSettings> }
99
99
  export interface WorkflowRetentionSettings { olderThanDays?: number; maxTerminalRuns?: number }
100
- export interface WorkflowSettings { concurrency: number; backgroundWidget?: boolean; modelAliases?: Readonly<Record<string, string>>; skills?: readonly string[]; extensions?: readonly string[]; extensionSettings?: Readonly<WorkflowExtensionSettings>; tools?: readonly string[]; retention?: Readonly<WorkflowRetentionSettings>; worktreePostCreateCommand?: readonly string[] }
101
- export type WorkflowSettingsOverrides = Partial<Omit<WorkflowSettings, "backgroundWidget">>;
100
+ export type CodemodeToolsSetting = "all" | "read-only" | "none";
101
+ export interface WorkflowSettings { concurrency: number; backgroundWidget?: boolean; codemodeTools?: CodemodeToolsSetting; modelAliases?: Readonly<Record<string, string>>; skills?: readonly string[]; extensions?: readonly string[]; extensionSettings?: Readonly<WorkflowExtensionSettings>; tools?: readonly string[]; retention?: Readonly<WorkflowRetentionSettings>; worktreePostCreateCommand?: readonly string[] }
102
+ export type WorkflowSettingsOverrides = Partial<Omit<WorkflowSettings, "backgroundWidget" | "codemodeTools">>;
102
103
  export interface WorkflowSettingsSources { concurrency: string; modelAliases: string; skills?: string; extensions?: string; tools?: string; extensionSettings?: string; retention?: string; worktreePostCreateCommand?: string }
103
104
  export interface WorkflowSettingsResolution { globalSettingsPath: string; projectSettingsPath: string; projectTrusted: boolean; global: Readonly<WorkflowSettings>; project: Readonly<WorkflowSettingsOverrides>; effective: Readonly<WorkflowSettings>; sources: Readonly<WorkflowSettingsSources> }
104
105
  export interface AgentResourceSelectors { skills?: readonly string[]; extensions?: readonly string[]; tools?: readonly string[] }
@@ -178,6 +179,8 @@ export interface WorkflowRunEvent { type: string; message: string; timestamp?: n
178
179
  export interface WorkflowRetryProvenance { sourceRunId: string; lineageRootRunId: string; completedPaths: readonly string[]; incompletePaths: readonly string[]; namedWorktrees: readonly string[] }
179
180
  export interface WorkflowPhaseRecord { phase: string; afterAgent: number }
180
181
  export interface WorkflowPhaseShellActivity { phaseIndex: number; active: number; startedAt: number }
182
+ /** A script call recorded for inspection. Tool payloads stay in the replay journal, not this record. Oversized shell inputs are JSON prefixes, with `inputBytes` their full size. */
183
+ export interface WorkflowScriptCall { kind: "shell" | "tool"; name: string; path: string; input?: JsonValue; inputBytes?: number; phase?: string; startedAt: number; finishedAt?: number; error?: { code: WorkflowErrorCode; message?: string } }
181
184
  export interface RunRecord {
182
185
  id: string;
183
186
  workflowName: string;
@@ -194,6 +197,8 @@ export interface RunRecord {
194
197
  activeShells?: number;
195
198
  activeShellStartedAt?: number;
196
199
  activeShellsByPhase?: readonly WorkflowPhaseShellActivity[];
200
+ activeTools?: readonly { name: string; startedAt: number }[]; // Script tool calls in flight: a live overlay for rendering, never persisted.
201
+ scriptCalls?: readonly WorkflowScriptCall[];
197
202
  error?: WorkflowErrorShape;
198
203
  failedAt?: string;
199
204
  budget?: WorkflowBudget;
@@ -356,7 +361,9 @@ export interface FunctionIdentity { path: string; structuralPath: readonly strin
356
361
  export type AgentContinuity = "fresh" | "continued";
357
362
  export interface AgentIdentity { structuralPath: readonly string[]; callSite: string; occurrence: number; parentBreadcrumb?: string; worktreeOwner?: string; handle?: string; turn?: number }
358
363
  export interface ShellIdentity { structuralPath: readonly string[]; callSite: string; occurrence: number; worktreeOwner?: string }
359
- export interface WorkflowBridge { agent?: (prompt: string, options: Readonly<Record<string, JsonValue>>, signal: AbortSignal, identity: AgentIdentity) => Promise<JsonValue>; shell?: (command: string, options: ShellOptions, signal: AbortSignal, identity: ShellIdentity) => Promise<ShellResult>; checkpoint?: (input: Readonly<Record<string, JsonValue>>, signal: AbortSignal) => boolean | Promise<boolean>; function?: (name: string, input: Readonly<Record<string, JsonValue>>, signal: AbortSignal, identity: FunctionIdentity) => Promise<JsonValue>; worktree?: (owner: string, signal: AbortSignal) => Promise<Readonly<WorkflowWorktreeReference>>; functions?: Readonly<Record<string, { name: string }>>; phase?: (name: string) => void | Promise<void>; log?: (message: string) => void | Promise<void> }
364
+ /** A script `tools.<id>(args)` call: the n-th call of that tool within one structural scope. */
365
+ export interface ToolIdentity { structuralPath: readonly string[]; occurrence: number }
366
+ export interface WorkflowBridge { agent?: (prompt: string, options: Readonly<Record<string, JsonValue>>, signal: AbortSignal, identity: AgentIdentity) => Promise<JsonValue>; shell?: (command: string, options: ShellOptions, signal: AbortSignal, identity: ShellIdentity) => Promise<ShellResult>; checkpoint?: (input: Readonly<Record<string, JsonValue>>, signal: AbortSignal) => boolean | Promise<boolean>; function?: (name: string, input: Readonly<Record<string, JsonValue>>, signal: AbortSignal, identity: FunctionIdentity) => Promise<JsonValue>; worktree?: (owner: string, signal: AbortSignal) => Promise<Readonly<WorkflowWorktreeReference>>; functions?: Readonly<Record<string, { name: string }>>; phase?: (name: string) => void | Promise<void>; log?: (message: string) => void | Promise<void>; tool?: (identifier: string, args: Readonly<Record<string, JsonValue>>, signal: AbortSignal, identity: ToolIdentity) => Promise<JsonValue> }
360
367
  export interface WorkflowExecution { result: Promise<JsonValue>; cancel: () => void }
361
368
  export interface StaticWorkflowScope { kind: "parallel" | "pipeline"; name: string | null; key: string | null }
362
369
  export type StaticWorkflowExecution = "parallel" | "sequential";
package/src/utils.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { createHash } from "node:crypto";
1
2
  import { ERROR_CODES, LAUNCH_SNAPSHOT_IDENTITY_VERSION, THINKING_LEVELS, WorkflowError, type JsonSchema, type JsonValue, type ModelSpec, type ThinkingLevel, type WorkflowErrorCode, type WorkflowExtensionSettings } from "./types.js";
2
3
  import { Minimatch } from "minimatch";
3
4
  export class SerialLane {
@@ -18,6 +19,15 @@ export function sanitizeDisplayText(value: string): string { return value.replac
18
19
  export { object as isObject };
19
20
  function isStringKey(key: PropertyKey): key is string { return typeof key === "string"; }
20
21
  function stringKeyValue(value: object, key: string): unknown { return object(value) ? value[key] : undefined; }
22
+ function sortedJson(value: JsonValue): JsonValue {
23
+ if (Array.isArray(value)) return value.map(sortedJson);
24
+ if (value === null || typeof value !== "object") return value;
25
+ return Object.fromEntries(Object.keys(value).sort().map((key) => [key, sortedJson(value[key] as JsonValue)]));
26
+ }
27
+ /** A short digest of a JSON value that ignores object key order. */
28
+ export function jsonDigest(value: JsonValue): string {
29
+ return createHash("sha256").update(JSON.stringify(sortedJson(value))).digest("hex").slice(0, 16);
30
+ }
21
31
  export function jsonValue(value: unknown, seen = new Set<object>()): value is JsonValue {
22
32
  if (value === null || typeof value === "boolean" || typeof value === "string") return true;
23
33
  if (typeof value === "number") return Number.isFinite(value);
@@ -153,6 +163,24 @@ export function resolveModelReference(value: string, aliases: Readonly<Record<st
153
163
  };
154
164
  return resolveReference(value, []);
155
165
  }
166
+ type ToolSource = { getActiveTools(): string[]; getAllTools?(): readonly { name: string; exposure?: string }[] };
167
+ /**
168
+ * Tools a session can reach, and so the ceiling for its agents: the declared tools plus the ones
169
+ * only codemode scripts call or tool_search loads, such as MCP tools with the default exposure.
170
+ * Agent sessions allow tools by name, so leaving these out would make them unreachable there.
171
+ */
172
+ export function reachableTools(pi: ToolSource): string[] {
173
+ const undeclared = pi.getAllTools?.().filter(({ exposure }) => exposure === "codemode" || exposure === "deferred").map(({ name }) => name) ?? [];
174
+ return [...new Set([...pi.getActiveTools(), ...undeclared])];
175
+ }
176
+ /** Provider of the virtual models that expose workflow model aliases in `/model`. */
177
+ export const VIRTUAL_MODEL_PROVIDER = "workflow";
178
+ /** Child sessions do not load the workflow extension, so they receive the alias target instead of its virtual model. */
179
+ export function physicalModel(spec: ModelSpec, aliases: Readonly<Record<string, string>> = {}, knownModels?: ReadonlySet<string>, settingsPath?: string): ModelSpec {
180
+ if (spec.provider !== VIRTUAL_MODEL_PROVIDER || !modelAliasName(spec.model, aliases)) return spec;
181
+ const target = resolveModelReference(spec.model, aliases, knownModels, settingsPath);
182
+ return spec.thinking === undefined ? target : { ...target, thinking: spec.thinking };
183
+ }
156
184
  export function modelCapability(value: string | ModelSpec, aliases?: Readonly<Record<string, string>>, knownModels?: ReadonlySet<string>, settingsPath?: string): string {
157
185
  const parsed = typeof value === "string" ? resolveModelReference(value, aliases, knownModels, settingsPath) : value;
158
186
  return `${parsed.provider}/${parsed.model}`;
package/src/validation.ts CHANGED
@@ -12,7 +12,7 @@ export { loadAgentDefinitions, loadProjectAgentDefinitions, parseRoleMarkdown, w
12
12
  export type { WorkflowRoleDirectoryInput } from "./roles.js";
13
13
 
14
14
  import { validateContextFileScopes, validateSelectorList } from "./settings.js";
15
- export { DEFAULT_SETTINGS, loadSettings, loadSettingsOverrides, resolveAgentResourcePolicy, resolveWorkflowSettings, saveModelAliases, validateContextFileScopes, validateModelAliasAvailability, validateSelectorList, validateWorkflowExtensionSettings, workflowProjectSettingsPath, workflowSettingsPath } from "./settings.js";
15
+ export { DEFAULT_SETTINGS, loadCodemodeToolsSetting, loadSettings, loadSettingsOverrides, resolveAgentResourcePolicy, resolveWorkflowSettings, saveModelAliases, validateContextFileScopes, validateModelAliasAvailability, validateSelectorList, validateWorkflowExtensionSettings, workflowProjectSettingsPath, workflowSettingsPath, workflowToolExposure } from "./settings.js";
16
16
 
17
17
  export function validateCheckpoint(value: unknown): CheckpointInput {
18
18
  if (!object(value) || Object.keys(value).some((key) => !["name", "prompt", "context"].includes(key)) || typeof value.name !== "string" || value.name.trim() === "" || typeof value.prompt !== "string" || !jsonValue(value.context)) fail("INVALID_METADATA", "checkpoint requires only name, prompt, and JSON context");
@@ -219,6 +219,24 @@ function callHasTrailingComma(source: string, call: WorkflowCall): boolean {
219
219
  return current?.type.label === ")" && previous?.type.label === ",";
220
220
  }
221
221
 
222
+ /**
223
+ * The `tools.<id>` members a workflow script names statically. Any other use of `tools`, such as a
224
+ * local declaration that shadows the global or passing it around, makes the set unknowable: undefined.
225
+ */
226
+ export function scriptToolReferences(script: string): string[] | undefined {
227
+ const body = workflowBody(script);
228
+ if (!body.trim()) return [];
229
+ const names = new Set<string>();
230
+ const opaque = (node: acorn.AnyNode, parent?: acorn.AnyNode): boolean => {
231
+ if (node.type === "Identifier" && node.name === "tools") {
232
+ if (parent?.type === "MemberExpression" && parent.object === node && !parent.computed && parent.property.type === "Identifier") names.add(parent.property.name);
233
+ else if (!(parent?.type === "Property" && parent.key === node && !parent.computed && !parent.shorthand || parent?.type === "MemberExpression" && parent.property === node && !parent.computed)) return true;
234
+ }
235
+ return astChildren(node).some((child) => opaque(child, node));
236
+ };
237
+ return opaque(parseWorkflow(body)) ? undefined : [...names];
238
+ }
239
+
222
240
  export function instrumentWorkflow(script: string): string {
223
241
  const body = workflowBody(script);
224
242
  if (!body.trim()) return body;
@@ -69,5 +69,3 @@ export const reviewLoop = defineWorkflowFunction({
69
69
  ),
70
70
  });
71
71
 
72
- export const reviewLoopInputSchema = reviewLoop.input;
73
- export const reviewLoopOutputSchema = reviewLoop.output;
@@ -53,14 +53,14 @@ Every tool schema is a closed object. Unknown properties are rejected. The model
53
53
  | Tool | Input schema |
54
54
  | --- | --- |
55
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 }` |
56
- | `subagents_inspect` | `{ id?: string }` |
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
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.
62
62
 
63
- `subagents_inspect({})` returns all accessible run summaries ordered by start time. `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`.
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
 
65
65
  ## Launching and concurrency
66
66
 
@@ -41,6 +41,7 @@ export const SUBAGENTS_RUN_PARAMETERS = Type.Object({
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" })),
44
+ scope: Type.Optional(Type.Union([Type.Literal("session"), Type.Literal("all")], { description: "List scope when id is omitted: session (default) lists runs from the current Pi session; all lists every stored run" })),
44
45
  }, { additionalProperties: false });
45
46
 
46
47
  export const SUBAGENTS_ID_PARAMETERS = Type.Object({
@@ -60,13 +61,23 @@ export const SUBAGENTS_RETRY_PARAMETERS = Type.Object({
60
61
  id: Type.String({ description: "Failed or stopped subagent ID to retry" }),
61
62
  }, { additionalProperties: false });
62
63
 
63
- export const SUBAGENTS_TOOL_SCHEMAS = {
64
- subagents_run: SUBAGENTS_RUN_PARAMETERS,
65
- subagents_inspect: SUBAGENTS_INSPECT_PARAMETERS,
66
- subagents_steer: SUBAGENTS_STEER_PARAMETERS,
67
- subagents_stop: SUBAGENTS_STOP_PARAMETERS,
68
- subagents_retry: SUBAGENTS_RETRY_PARAMETERS,
69
- } as const;
64
+ // Structured results codemode scripts receive instead of the JSON text; nested records stay loose so they cannot drift from persistence.
65
+ export const SUBAGENT_STATUS_OUTPUT = Type.Object({
66
+ id: Type.String(),
67
+ state: Type.String(),
68
+ sessionId: Type.Optional(Type.String()),
69
+ startedAt: Type.Optional(Type.Number()),
70
+ finishedAt: Type.Optional(Type.Number()),
71
+ attempts: Type.Optional(Type.Number()),
72
+ attemptDetails: Type.Optional(Type.Array(Type.Unknown())),
73
+ worktree: Type.Optional(Type.Object({ path: Type.String(), branch: Type.String() })),
74
+ error: Type.Optional(Type.Object({ code: Type.String(), message: Type.String() })),
75
+ progress: Type.Optional(Type.Unknown()),
76
+ value: Type.Optional(Type.Unknown()),
77
+ });
78
+ // One run when id is given, otherwise the ordered run summaries; the summaries stay short in the codemode description.
79
+ export const SUBAGENTS_INSPECT_OUTPUT = Type.Union([SUBAGENT_STATUS_OUTPUT, Type.Array(Type.Object({ id: Type.String(), state: Type.String() }))]);
80
+ export const SUBAGENTS_STEER_OUTPUT = Type.Object({ id: Type.String(), accepted: Type.Boolean() });
70
81
 
71
82
  export type SubagentRunRequest = Static<typeof SUBAGENTS_RUN_PARAMETERS>;
72
83
  export function normalizeSubagentRunRequest(value: unknown): SubagentRunRequest {
@@ -1,7 +1,7 @@
1
1
  import { join } from "node:path";
2
2
  import { defineTool, getAgentDir, type AgentToolResult, type ExtensionAPI, type ExtensionContext, type ToolDefinition } from "@earendil-works/pi-coding-agent";
3
3
  import { Value } from "typebox/value";
4
- import { WorkflowError, loadingRegistry } from "../../src/index.js";
4
+ import { WorkflowError, loadCodemodeToolsSetting, loadingRegistry, reachableTools, workflowToolExposure, type JsonValue } from "../../src/index.js";
5
5
  import { clearSubagentManager, setSubagentManager } from "../../src/subagent-manager-handle.js";
6
6
  import type { SubagentIdRequest, SubagentInspectRequest, SubagentManager, SubagentManagerContext, SubagentNotification, SubagentsExtension, SubagentsExtensionOptions, SubagentRunRequest, SubagentStatus, SubagentSteerRequest } from "./contracts.js";
7
7
  import { createSubagentManager } from "./manager.js";
@@ -9,10 +9,13 @@ import { registerSubagentNavigator } from "./navigator.js";
9
9
  import { createSubagentBackgroundWidget, renderSubagentCall, renderSubagentControlCall, renderSubagentControlResult, renderSubagentInspectCall, renderSubagentInspectResult, renderSubagentResult } from "./view.js";
10
10
  import {
11
11
  normalizeSubagentRunRequest,
12
+ SUBAGENT_STATUS_OUTPUT,
12
13
  SUBAGENTS_ID_PARAMETERS,
14
+ SUBAGENTS_INSPECT_OUTPUT,
13
15
  SUBAGENTS_INSPECT_PARAMETERS,
14
16
  SUBAGENTS_RETRY_PARAMETERS,
15
17
  SUBAGENTS_RUN_PARAMETERS,
18
+ SUBAGENTS_STEER_OUTPUT,
16
19
  SUBAGENTS_STEER_PARAMETERS,
17
20
  SUBAGENTS_STOP_PARAMETERS,
18
21
  } from "./contracts.js";
@@ -23,7 +26,7 @@ export { createSubagentManager, createUnavailableSubagentManager } from "./manag
23
26
  export { createRunStoreWorktreeAdapter, defaultWorktreeHome } from "./worktree.js";
24
27
  export type { SubagentWorktreeAdapter, SubagentWorktreeContext, SubagentWorktreeHandle, SubagentWorktreeRunStore } from "./worktree.js";
25
28
 
26
- type SubagentsExtensionAPI = Pick<ExtensionAPI, "registerTool"> & Partial<Pick<ExtensionAPI, "getActiveTools" | "on" | "sendMessage" | "registerCommand" | "appendEntry" | "registerEntryRenderer">>;
29
+ type SubagentsExtensionAPI = Pick<ExtensionAPI, "registerTool"> & Partial<Pick<ExtensionAPI, "getActiveTools" | "getAllTools" | "on" | "sendMessage" | "registerCommand" | "appendEntry" | "registerEntryRenderer">>;
27
30
 
28
31
  function validateSubagentRunRequest(value: unknown): SubagentRunRequest {
29
32
  return normalizeSubagentRunRequest(value);
@@ -55,7 +58,7 @@ function serialize(value: unknown): string {
55
58
  }
56
59
 
57
60
  function toolResult(value: unknown): AgentToolResult<unknown> {
58
- return { content: [{ type: "text", text: serialize(value) }], details: value };
61
+ return { content: [{ type: "text", text: serialize(value) }], details: value, structuredContent: value as JsonValue };
59
62
  }
60
63
 
61
64
  function managerContext(toolCallId: string, signal: AbortSignal | undefined, onUpdate: ((value: AgentToolResult<unknown>) => void) | undefined, context: ExtensionContext): SubagentManagerContext {
@@ -78,6 +81,7 @@ export function createSubagentTools(manager: SubagentManager): readonly ToolDefi
78
81
  "Do not poll a running ID; use subagents_inspect({ id }) when you need its current status or terminal result.",
79
82
  ],
80
83
  parameters: SUBAGENTS_RUN_PARAMETERS,
84
+ outputSchema: SUBAGENT_STATUS_OUTPUT,
81
85
  async execute(toolCallId, params, signal, onUpdate, context) {
82
86
  return toolResult(await manager.run(validateSubagentRunRequest(params), managerContext(toolCallId, signal, onUpdate, context)));
83
87
  },
@@ -87,8 +91,9 @@ export function createSubagentTools(manager: SubagentManager): readonly ToolDefi
87
91
  defineTool({
88
92
  name: "subagents_inspect",
89
93
  label: "Subagents Inspect",
90
- description: "Inspect durable subagent runs. Omit id for ordered run summaries, or provide id for detailed status, progress, activity, accounting, tool calls, timestamps, worktree metadata, and a terminal value or error when available.",
94
+ description: "Inspect durable subagent runs. Omit id for ordered run summaries of the current session (scope all for every session), or provide id for detailed status, progress, activity, accounting, tool calls, timestamps, worktree metadata, and a terminal value or error when available.",
91
95
  parameters: SUBAGENTS_INSPECT_PARAMETERS,
96
+ outputSchema: SUBAGENTS_INSPECT_OUTPUT,
92
97
  async execute(toolCallId, params, signal, onUpdate, context) {
93
98
  return toolResult(await manager.inspect(validateSubagentInspectRequest(params), managerContext(toolCallId, signal, onUpdate, context)));
94
99
  },
@@ -100,6 +105,7 @@ export function createSubagentTools(manager: SubagentManager): readonly ToolDefi
100
105
  label: "Subagents Steer",
101
106
  description: "Send a message to a running subagent. The message is queued safely if its steering handler is not ready; settled runs cannot be steered.",
102
107
  parameters: SUBAGENTS_STEER_PARAMETERS,
108
+ outputSchema: SUBAGENTS_STEER_OUTPUT,
103
109
  async execute(toolCallId, params, signal, onUpdate, context) {
104
110
  return toolResult(await manager.steer(validateSubagentSteerRequest(params), managerContext(toolCallId, signal, onUpdate, context)));
105
111
  },
@@ -111,6 +117,7 @@ export function createSubagentTools(manager: SubagentManager): readonly ToolDefi
111
117
  label: "Subagents Stop",
112
118
  description: "Stop one running subagent, abort its active session, persist state \"stopped\", and clean its worktree without affecting sibling runs.",
113
119
  parameters: SUBAGENTS_STOP_PARAMETERS,
120
+ outputSchema: SUBAGENT_STATUS_OUTPUT,
114
121
  async execute(toolCallId, params, signal, onUpdate, context) {
115
122
  return toolResult(await manager.stop(validateSubagentIdRequest(params, "subagents_stop"), managerContext(toolCallId, signal, onUpdate, context)));
116
123
  },
@@ -122,6 +129,7 @@ export function createSubagentTools(manager: SubagentManager): readonly ToolDefi
122
129
  label: "Subagents Retry",
123
130
  description: "Start a fresh run from a failed or stopped subagent's persisted request. The new run gets a new ID and preserves its original background or foreground mode.",
124
131
  parameters: SUBAGENTS_RETRY_PARAMETERS,
132
+ outputSchema: SUBAGENT_STATUS_OUTPUT,
125
133
  async execute(toolCallId, params, signal, onUpdate, context) {
126
134
  return toolResult(await manager.retry(validateSubagentIdRequest(params, "subagents_retry"), managerContext(toolCallId, signal, onUpdate, context)));
127
135
  },
@@ -162,7 +170,8 @@ export function createSubagentsExtension(options: SubagentsExtensionOptions = {}
162
170
 
163
171
  export function registerSubagentsExtension(pi: SubagentsExtensionAPI, options: SubagentsExtensionOptions = {}): SubagentsExtension {
164
172
  const getActiveTools = pi.getActiveTools;
165
- const activeTools = getActiveTools === undefined ? undefined : () => getActiveTools.call(pi);
173
+ const getAllTools = pi.getAllTools;
174
+ const activeTools = getActiveTools === undefined ? undefined : () => reachableTools({ getActiveTools: () => getActiveTools.call(pi), ...(getAllTools === undefined ? {} : { getAllTools: () => getAllTools.call(pi) }) });
166
175
  const sendMessage = pi.sendMessage;
167
176
  const notify = sendMessage === undefined ? undefined : (notification: SubagentNotification): void => {
168
177
  sendMessage.call(pi, { customType: "subagents", content: notificationContent(notification), display: true, details: notification }, { deliverAs: "steer", triggerTurn: true });
@@ -173,7 +182,8 @@ export function registerSubagentsExtension(pi: SubagentsExtensionAPI, options: S
173
182
  const appendEntry = pi.appendEntry;
174
183
  const widget = createSubagentBackgroundWidget({ ...(appendEntry === undefined ? {} : { appendEntry: (customType, data) => { appendEntry.call(pi, customType, data); } }), ...(pi.registerEntryRenderer === undefined ? {} : { registerEntryRenderer: pi.registerEntryRenderer.bind(pi) }) });
175
184
  const extension = createSubagentsExtension(options, activeTools, notify, (status, request) => { widget.update(status, request); const registry = loadingRegistry(); if (typeof registry.observeSubagentStatus === "function") registry.observeSubagentStatus(status, request); }, onResourceWarning);
176
- for (const tool of extension.tools) pi.registerTool(tool);
185
+ const codemodeTools = loadCodemodeToolsSetting(options.managerDependencies?.agentDir);
186
+ for (const tool of extension.tools) pi.registerTool({ ...tool, ...workflowToolExposure(tool.name, codemodeTools) });
177
187
  if (pi.registerCommand !== undefined) registerSubagentNavigator(pi.registerCommand.bind(pi), extension.manager, storageDirectory(options), options.clipboard);
178
188
  if (pi.on !== undefined) {
179
189
  setSubagentManager(extension.manager);
@@ -1113,9 +1113,9 @@ class PersistentSubagentManager implements SubagentManager {
1113
1113
  }
1114
1114
  return inspection;
1115
1115
  }
1116
- return this.inspectList();
1116
+ return this.inspectList(request.scope === "all" ? undefined : context.extensionContext.sessionManager.getSessionId());
1117
1117
  }
1118
- private async inspectList(): Promise<unknown> {
1118
+ private async inspectList(sessionId: string | undefined): Promise<unknown> {
1119
1119
  const root = storageDirectory(this.dependencies);
1120
1120
  await secureDirectory(root);
1121
1121
  const entries = await readdir(root, { withFileTypes: true });
@@ -1127,7 +1127,7 @@ class PersistentSubagentManager implements SubagentManager {
1127
1127
  try {
1128
1128
  const active = this.activeRuns.get(entry.name);
1129
1129
  const status = publicStatus(active ? persistedStatus(active) : this.terminalSummaries.get(entry.name) ?? await loadPersistedStatus(root, entry.name, false));
1130
- if (status.startedAt === undefined) continue;
1130
+ if (status.startedAt === undefined || (sessionId !== undefined && status.sessionId !== sessionId)) continue;
1131
1131
  statuses.push({ status, startedAt: status.startedAt });
1132
1132
  } catch (error) {
1133
1133
  if (error instanceof WorkflowError && (error.code === "RUN_NOT_FOUND" || error instanceof InvalidPersistedSubagentStatusError)) continue;