@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.
- package/CHANGELOG.md +68 -0
- package/dist/src/agent-execution.js +81 -39
- package/dist/src/background-widget.d.ts +5 -0
- package/dist/src/background-widget.js +1 -1
- package/dist/src/decoders.js +26 -0
- package/dist/src/execution.d.ts +4 -2
- package/dist/src/execution.js +43 -2
- package/dist/src/host-delivery.js +1 -0
- package/dist/src/host-recovery.d.ts +5 -2
- package/dist/src/host-recovery.js +3 -3
- package/dist/src/host-view.d.ts +5 -0
- package/dist/src/host-view.js +29 -5
- package/dist/src/host.d.ts +2 -2
- package/dist/src/host.js +190 -37
- package/dist/src/index.js +495 -93
- package/dist/src/index.js.map +3 -3
- package/dist/src/paths.d.ts +2 -0
- package/dist/src/paths.js +2 -0
- package/dist/src/registry.js +1 -1
- package/dist/src/roles.js +4 -3
- package/dist/src/script-tools.d.ts +27 -0
- package/dist/src/script-tools.js +87 -0
- package/dist/src/settings.d.ts +13 -0
- package/dist/src/settings.js +27 -3
- package/dist/src/trajectory.d.ts +12 -2
- package/dist/src/trajectory.js +30 -8
- package/dist/src/types.d.ts +30 -2
- package/dist/src/types.js +1 -1
- package/dist/src/utils.d.ts +19 -0
- package/dist/src/utils.js +30 -0
- package/dist/src/validation.d.ts +6 -1
- package/dist/src/validation.js +21 -1
- package/dist/starter/index.js +0 -2
- package/dist/starter/index.js.map +1 -1
- package/dist/starter/review-loop.d.ts +0 -13
- package/dist/starter/review-loop.js +0 -2
- package/dist/subagents/index.js +267 -159
- package/dist/subagents/index.js.map +2 -2
- package/dist/subagents/src/contracts.d.ts +45 -29
- package/dist/subagents/src/contracts.js +18 -7
- package/dist/subagents/src/index.d.ts +1 -1
- package/dist/subagents/src/index.js +13 -6
- package/dist/subagents/src/manager.js +3 -3
- package/dist/subagents/src/view.d.ts +1 -0
- package/dist/subagents/src/view.js +13 -52
- package/dist/trajectory/assets/index.html +27 -5
- package/dist/trajectory/index.js +76 -28
- package/dist/trajectory/index.js.map +2 -2
- package/dist/trajectory/src/assets/index.html +27 -5
- package/dist/trajectory/src/index.js +8 -18
- package/dist/trajectory/src/server.js +4 -2
- package/dist/trajectory/src/server.js.map +2 -2
- package/package.json +6 -6
- package/skills/pi-extensible-workflows/SKILL.md +2 -0
- package/src/agent-execution.ts +77 -41
- package/src/background-widget.ts +1 -1
- package/src/decoders.ts +20 -0
- package/src/execution.ts +41 -4
- package/src/host-delivery.ts +1 -0
- package/src/host-recovery.ts +8 -7
- package/src/host-view.ts +23 -5
- package/src/host.ts +173 -35
- package/src/paths.ts +3 -0
- package/src/registry.ts +1 -1
- package/src/roles.ts +4 -3
- package/src/script-tools.ts +76 -0
- package/src/settings.ts +22 -4
- package/src/trajectory.ts +30 -11
- package/src/types.ts +11 -4
- package/src/utils.ts +28 -0
- package/src/validation.ts +19 -1
- package/starter/review-loop.ts +0 -2
- package/subagents/README.md +2 -2
- package/subagents/src/contracts.ts +18 -7
- package/subagents/src/index.ts +16 -6
- package/subagents/src/manager.ts +3 -3
- package/subagents/src/view.ts +13 -50
- package/trajectory/src/assets/index.html +27 -5
- package/trajectory/src/index.ts +6 -9
- 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
|
-
|
|
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
|
|
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 ===
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
101
|
-
export
|
|
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
|
-
|
|
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;
|
package/starter/review-loop.ts
CHANGED
package/subagents/README.md
CHANGED
|
@@ -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
|
|
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
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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 {
|
package/subagents/src/index.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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);
|
package/subagents/src/manager.ts
CHANGED
|
@@ -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;
|