@marcoscale98/pi-extensible-workflows 5.14.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 +536 -0
- package/README.md +64 -0
- package/dist/src/agent-execution.d.ts +338 -0
- package/dist/src/agent-execution.js +1633 -0
- package/dist/src/background-widget.d.ts +74 -0
- package/dist/src/background-widget.js +1340 -0
- package/dist/src/budget.d.ts +38 -0
- package/dist/src/budget.js +188 -0
- package/dist/src/changelog.d.ts +6 -0
- package/dist/src/changelog.js +112 -0
- package/dist/src/decoders.d.ts +96 -0
- package/dist/src/decoders.js +629 -0
- package/dist/src/execution.d.ts +19 -0
- package/dist/src/execution.js +749 -0
- package/dist/src/herdr.d.ts +53 -0
- package/dist/src/herdr.js +270 -0
- package/dist/src/host-delivery.d.ts +95 -0
- package/dist/src/host-delivery.js +376 -0
- package/dist/src/host-navigator.d.ts +83 -0
- package/dist/src/host-navigator.js +1087 -0
- package/dist/src/host-phases.d.ts +89 -0
- package/dist/src/host-phases.js +320 -0
- package/dist/src/host-recovery.d.ts +141 -0
- package/dist/src/host-recovery.js +356 -0
- package/dist/src/host-runtime.d.ts +78 -0
- package/dist/src/host-runtime.js +378 -0
- package/dist/src/host-view.d.ts +116 -0
- package/dist/src/host-view.js +868 -0
- package/dist/src/host.d.ts +40 -0
- package/dist/src/host.js +1641 -0
- package/dist/src/index.d.ts +29 -0
- package/dist/src/index.js +13725 -0
- package/dist/src/index.js.map +6 -0
- package/dist/src/io.d.ts +14 -0
- package/dist/src/io.js +82 -0
- package/dist/src/package-identity.d.ts +4 -0
- package/dist/src/package-identity.js +6 -0
- package/dist/src/paths.d.ts +14 -0
- package/dist/src/paths.js +51 -0
- package/dist/src/persistence.d.ts +5 -0
- package/dist/src/persistence.js +5 -0
- package/dist/src/pi-runtime-adapter.d.ts +27 -0
- package/dist/src/pi-runtime-adapter.js +243 -0
- package/dist/src/pi-runtime-runner.d.ts +42 -0
- package/dist/src/pi-runtime-runner.js +505 -0
- package/dist/src/registry.d.ts +48 -0
- package/dist/src/registry.js +379 -0
- package/dist/src/retention.d.ts +17 -0
- package/dist/src/retention.js +195 -0
- package/dist/src/runtime/agent-runner.d.ts +143 -0
- package/dist/src/runtime/agent-runner.js +12 -0
- package/dist/src/runtime/index.d.ts +2 -0
- package/dist/src/runtime/index.js +2 -0
- package/dist/src/runtime/namespace.d.ts +14 -0
- package/dist/src/runtime/namespace.js +1 -0
- package/dist/src/runtime/workflow-result.d.ts +2 -0
- package/dist/src/runtime/workflow-result.js +6 -0
- package/dist/src/session-handoff.d.ts +2 -0
- package/dist/src/session-handoff.js +82 -0
- package/dist/src/session-lease.d.ts +12 -0
- package/dist/src/session-lease.js +180 -0
- package/dist/src/store.d.ts +95 -0
- package/dist/src/store.js +951 -0
- package/dist/src/subagent-manager-handle.d.ts +4 -0
- package/dist/src/subagent-manager-handle.js +6 -0
- package/dist/src/tool-timing.d.ts +12 -0
- package/dist/src/tool-timing.js +33 -0
- package/dist/src/trajectory-contracts.d.ts +8 -0
- package/dist/src/trajectory-contracts.js +9 -0
- package/dist/src/trajectory-host-handle.d.ts +12 -0
- package/dist/src/trajectory-host-handle.js +6 -0
- package/dist/src/trajectory.d.ts +128 -0
- package/dist/src/trajectory.js +594 -0
- package/dist/src/types.d.ts +960 -0
- package/dist/src/types.js +60 -0
- package/dist/src/utils.d.ts +57 -0
- package/dist/src/utils.js +225 -0
- package/dist/src/validation.d.ts +29 -0
- package/dist/src/validation.js +1100 -0
- package/dist/src/workflow-artifacts.d.ts +14 -0
- package/dist/src/workflow-artifacts.js +40 -0
- package/dist/starter/index.d.ts +1 -0
- package/dist/starter/index.js +103 -0
- package/dist/starter/index.js.map +6 -0
- package/dist/starter/review-loop.d.ts +72 -0
- package/dist/starter/review-loop.js +43 -0
- package/dist/starter/roles/developer.md +6 -0
- package/dist/starter/roles/oracle.md +18 -0
- package/dist/starter/roles/researcher.md +20 -0
- package/dist/starter/roles/reviewer.md +19 -0
- package/dist/starter/roles/scout.md +18 -0
- package/dist/subagents/index.d.ts +8 -0
- package/dist/subagents/index.js +8021 -0
- package/dist/subagents/index.js.map +6 -0
- package/dist/subagents/src/contracts.d.ts +173 -0
- package/dist/subagents/src/contracts.js +68 -0
- package/dist/subagents/src/decode.d.ts +15 -0
- package/dist/subagents/src/decode.js +182 -0
- package/dist/subagents/src/index.d.ts +12 -0
- package/dist/subagents/src/index.js +180 -0
- package/dist/subagents/src/manager.d.ts +3 -0
- package/dist/subagents/src/manager.js +1636 -0
- package/dist/subagents/src/navigator.d.ts +5 -0
- package/dist/subagents/src/navigator.js +627 -0
- package/dist/subagents/src/view.d.ts +70 -0
- package/dist/subagents/src/view.js +387 -0
- package/dist/subagents/src/worktree.d.ts +34 -0
- package/dist/subagents/src/worktree.js +68 -0
- package/dist/trajectory/assets/favicon.png +0 -0
- package/dist/trajectory/assets/index.html +772 -0
- package/dist/trajectory/assets/marked.min.js +6 -0
- package/dist/trajectory/assets/morphdom.min.js +1 -0
- package/dist/trajectory/index.d.ts +5 -0
- package/dist/trajectory/index.js +3459 -0
- package/dist/trajectory/index.js.map +6 -0
- package/dist/trajectory/src/assets/favicon.png +0 -0
- package/dist/trajectory/src/assets/index.html +772 -0
- package/dist/trajectory/src/assets/marked.min.js +6 -0
- package/dist/trajectory/src/assets/morphdom.min.js +1 -0
- package/dist/trajectory/src/export.d.ts +25 -0
- package/dist/trajectory/src/export.js +80 -0
- package/dist/trajectory/src/index.d.ts +26 -0
- package/dist/trajectory/src/index.js +732 -0
- package/dist/trajectory/src/server.d.ts +7 -0
- package/dist/trajectory/src/server.js +2534 -0
- package/dist/trajectory/src/server.js.map +6 -0
- package/examples/advanced-patterns/auto-model-router.mjs +78 -0
- package/examples/workflow-extension-template/README.md +37 -0
- package/examples/workflow-extension-template/extension.test.mjs +59 -0
- package/examples/workflow-extension-template/index.js +51 -0
- package/examples/workflow-extension-template/roles/reviewer.md +4 -0
- package/package.json +105 -0
- package/skills/pi-extensible-workflows/SKILL.md +160 -0
- package/src/agent-execution.ts +1533 -0
- package/src/background-widget.ts +1496 -0
- package/src/budget.ts +109 -0
- package/src/changelog.ts +114 -0
- package/src/decoders.ts +567 -0
- package/src/execution.ts +652 -0
- package/src/herdr.ts +240 -0
- package/src/host-delivery.ts +346 -0
- package/src/host-navigator.ts +871 -0
- package/src/host-phases.ts +264 -0
- package/src/host-recovery.ts +333 -0
- package/src/host-runtime.ts +373 -0
- package/src/host-view.ts +828 -0
- package/src/host.ts +1502 -0
- package/src/index.ts +29 -0
- package/src/io.ts +73 -0
- package/src/package-identity.ts +7 -0
- package/src/paths.ts +49 -0
- package/src/persistence.ts +11 -0
- package/src/pi-runtime-adapter.ts +247 -0
- package/src/pi-runtime-runner.ts +436 -0
- package/src/registry.ts +321 -0
- package/src/retention.ts +178 -0
- package/src/runtime/agent-runner.ts +158 -0
- package/src/runtime/index.ts +2 -0
- package/src/runtime/namespace.ts +16 -0
- package/src/runtime/workflow-result.ts +8 -0
- package/src/session-handoff.ts +78 -0
- package/src/session-lease.ts +125 -0
- package/src/store.ts +832 -0
- package/src/subagent-manager-handle.ts +8 -0
- package/src/tool-timing.ts +47 -0
- package/src/trajectory-contracts.ts +13 -0
- package/src/trajectory-host-handle.ts +16 -0
- package/src/trajectory.ts +543 -0
- package/src/types.ts +374 -0
- package/src/utils.ts +192 -0
- package/src/validation.ts +910 -0
- package/src/workflow-artifacts.ts +35 -0
- package/starter/index.ts +35 -0
- package/starter/prompts/council.md +16 -0
- package/starter/prompts/deep-research.md +16 -0
- package/starter/prompts/oracle.md +14 -0
- package/starter/prompts/parallel-review.md +22 -0
- package/starter/prompts/parallel-scout.md +16 -0
- package/starter/prompts/review-loop.md +14 -0
- package/starter/prompts/review.md +14 -0
- package/starter/prompts/scout.md +14 -0
- package/starter/review-loop.ts +73 -0
- package/starter/roles/developer.md +6 -0
- package/starter/roles/oracle.md +18 -0
- package/starter/roles/researcher.md +20 -0
- package/starter/roles/reviewer.md +19 -0
- package/starter/roles/scout.md +18 -0
- package/subagents/README.md +145 -0
- package/subagents/index.ts +8 -0
- package/subagents/src/contracts.ts +176 -0
- package/subagents/src/decode.ts +158 -0
- package/subagents/src/index.ts +194 -0
- package/subagents/src/manager.ts +1560 -0
- package/subagents/src/navigator.ts +488 -0
- package/subagents/src/view.ts +376 -0
- package/subagents/src/worktree.ts +99 -0
- package/trajectory/index.ts +5 -0
- package/trajectory/src/assets/favicon.png +0 -0
- package/trajectory/src/assets/index.html +772 -0
- package/trajectory/src/assets/marked.min.js +6 -0
- package/trajectory/src/assets/morphdom.min.js +1 -0
- package/trajectory/src/export.ts +85 -0
- package/trajectory/src/index.ts +604 -0
- package/trajectory/src/server.ts +447 -0
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { Type } from "@earendil-works/pi-ai";
|
|
2
|
+
import type { ExtensionContext, ToolDefinition } from "@earendil-works/pi-coding-agent";
|
|
3
|
+
import { WorkflowError } from "../../src/types.js";
|
|
4
|
+
import type { AgentActivity, AgentAccounting, AgentAttemptSummary, AgentExecutionOptions, AgentExecutionResult, AgentExecutionRoot, AgentProgress, AgentToolCallProgress, AgentTransport, LiveSessionHandoff, PreparedAgentSession, WorkflowAgentSession, WorkflowAgentSessionReference } from "../../src/index.js";
|
|
5
|
+
import { validateAgentOptions } from "../../src/validation.js";
|
|
6
|
+
import type { Static } from "typebox";
|
|
7
|
+
import { Value } from "typebox/value";
|
|
8
|
+
import type { SubagentWorktreeAdapter } from "./worktree.js";
|
|
9
|
+
|
|
10
|
+
export const SUBAGENTS_TOOL_NAMES = [
|
|
11
|
+
"subagents_run",
|
|
12
|
+
"subagents_inspect",
|
|
13
|
+
"subagents_steer",
|
|
14
|
+
"subagents_stop",
|
|
15
|
+
"subagents_retry",
|
|
16
|
+
] as const;
|
|
17
|
+
export const SUBAGENT_ATTEMPT_DETAILS_LIMIT = 1;
|
|
18
|
+
export const SUBAGENT_MAX_RETRIES = 255;
|
|
19
|
+
export const SUBAGENT_SYSTEM_PROMPT_LIMIT = 64 * 1024;
|
|
20
|
+
|
|
21
|
+
const SUBAGENTS_MODE = Type.Union([
|
|
22
|
+
Type.Literal("background"),
|
|
23
|
+
Type.Literal("foreground"),
|
|
24
|
+
], { description: "Launch mode; background is the default and foreground waits for the terminal envelope" });
|
|
25
|
+
|
|
26
|
+
export const SUBAGENTS_RUN_PARAMETERS = Type.Object({
|
|
27
|
+
prompt: Type.String({ description: "Task for the subagent" }),
|
|
28
|
+
mode: Type.Optional(SUBAGENTS_MODE),
|
|
29
|
+
label: Type.Optional(Type.String({ description: "Optional display label for the subagent" })),
|
|
30
|
+
model: Type.Optional(Type.String({ description: "Optional model as provider/model:thinking or alias[:thinking]" })),
|
|
31
|
+
tools: Type.Optional(Type.Array(Type.String(), { description: "Optional ordered tool selectors; candidates start enabled and !* restricts the set" })),
|
|
32
|
+
skills: Type.Optional(Type.Array(Type.String(), { description: "Optional ordered skill selectors; candidates start enabled and !* restricts the set" })),
|
|
33
|
+
extensions: Type.Optional(Type.Array(Type.String(), { description: "Optional ordered extension selectors; candidates start enabled and !* restricts the set" })),
|
|
34
|
+
contextFiles: Type.Optional(Type.Array(Type.String(), { description: "Optional context-file scopes: global, project, cwd" })),
|
|
35
|
+
role: Type.Optional(Type.String({ description: "Workflow role name" })),
|
|
36
|
+
worktree: Type.Optional(Type.String({ description: "Optional named worktree; requires a clean working tree" })),
|
|
37
|
+
outputSchema: Type.Optional(Type.Record(Type.String(), Type.Unknown(), { description: "Optional JSON schema for the result" })),
|
|
38
|
+
retries: Type.Optional(Type.Integer({ minimum: 0, maximum: SUBAGENT_MAX_RETRIES, description: "Optional retry count; at most 255 retries" })),
|
|
39
|
+
timeoutMs: Type.Optional(Type.Union([Type.Integer({ minimum: 1, description: "Optional execution timeout in milliseconds" }), Type.Null()])),
|
|
40
|
+
}, { additionalProperties: false });
|
|
41
|
+
|
|
42
|
+
export const SUBAGENTS_INSPECT_PARAMETERS = Type.Object({
|
|
43
|
+
id: Type.Optional(Type.String({ description: "Subagent ID; omit to list ordered run summaries" })),
|
|
44
|
+
}, { additionalProperties: false });
|
|
45
|
+
|
|
46
|
+
export const SUBAGENTS_ID_PARAMETERS = Type.Object({
|
|
47
|
+
id: Type.String({ description: "Subagent ID" }),
|
|
48
|
+
}, { additionalProperties: false });
|
|
49
|
+
|
|
50
|
+
export const SUBAGENTS_STEER_PARAMETERS = Type.Object({
|
|
51
|
+
id: Type.String({ description: "Subagent ID" }),
|
|
52
|
+
message: Type.String({ description: "Message to send to the running subagent" }),
|
|
53
|
+
}, { additionalProperties: false });
|
|
54
|
+
|
|
55
|
+
export const SUBAGENTS_STOP_PARAMETERS = Type.Object({
|
|
56
|
+
id: Type.String({ description: "Subagent ID" }),
|
|
57
|
+
}, { additionalProperties: false });
|
|
58
|
+
|
|
59
|
+
export const SUBAGENTS_RETRY_PARAMETERS = Type.Object({
|
|
60
|
+
id: Type.String({ description: "Failed or stopped subagent ID to retry" }),
|
|
61
|
+
}, { additionalProperties: false });
|
|
62
|
+
|
|
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;
|
|
70
|
+
|
|
71
|
+
export type SubagentRunRequest = Static<typeof SUBAGENTS_RUN_PARAMETERS>;
|
|
72
|
+
export function normalizeSubagentRunRequest(value: unknown): SubagentRunRequest {
|
|
73
|
+
if (!Value.Check(SUBAGENTS_RUN_PARAMETERS, value)) throw new WorkflowError("INVALID_METADATA", "Invalid subagents_run parameters");
|
|
74
|
+
if (typeof value.worktree === "string" && !value.worktree.trim()) throw new WorkflowError("INVALID_METADATA", "worktree name must be a non-empty string");
|
|
75
|
+
const snapshot = structuredClone(value);
|
|
76
|
+
snapshot.mode ??= "background";
|
|
77
|
+
if (snapshot.worktree !== undefined) snapshot.worktree = snapshot.worktree.trim();
|
|
78
|
+
validateAgentOptions(snapshot);
|
|
79
|
+
return snapshot;
|
|
80
|
+
}
|
|
81
|
+
export type SubagentInspectRequest = Static<typeof SUBAGENTS_INSPECT_PARAMETERS>;
|
|
82
|
+
export type SubagentIdRequest = Static<typeof SUBAGENTS_ID_PARAMETERS>;
|
|
83
|
+
export type SubagentSteerRequest = Static<typeof SUBAGENTS_STEER_PARAMETERS>;
|
|
84
|
+
export type SubagentProgress = {
|
|
85
|
+
readonly accounting: AgentAccounting;
|
|
86
|
+
readonly toolCalls: readonly AgentToolCallProgress[];
|
|
87
|
+
readonly state?: Omit<NonNullable<AgentProgress["state"]>, "systemPrompt">;
|
|
88
|
+
readonly activity?: AgentActivity;
|
|
89
|
+
readonly lastEventAt?: number;
|
|
90
|
+
};
|
|
91
|
+
export interface SubagentStatus {
|
|
92
|
+
readonly id: string;
|
|
93
|
+
readonly sessionId?: string;
|
|
94
|
+
readonly state: "running" | "completed" | "failed" | "stopped";
|
|
95
|
+
readonly startedAt?: number;
|
|
96
|
+
readonly finishedAt?: number;
|
|
97
|
+
readonly worktree?: { readonly path: string; readonly branch: string };
|
|
98
|
+
readonly error?: { readonly code: string; readonly message: string };
|
|
99
|
+
readonly progress?: SubagentProgress;
|
|
100
|
+
readonly attempts?: number;
|
|
101
|
+
readonly attemptDetails?: readonly AgentAttemptSummary[];
|
|
102
|
+
}
|
|
103
|
+
export interface SubagentNotification {
|
|
104
|
+
readonly id: string;
|
|
105
|
+
readonly label?: string;
|
|
106
|
+
readonly role?: string;
|
|
107
|
+
readonly state: "completed" | "failed";
|
|
108
|
+
readonly error?: { readonly code: string; readonly message: string };
|
|
109
|
+
}
|
|
110
|
+
export interface SubagentManagerContext {
|
|
111
|
+
readonly toolCallId: string;
|
|
112
|
+
readonly signal: AbortSignal | undefined;
|
|
113
|
+
readonly onUpdate: ((value: unknown) => void) | undefined;
|
|
114
|
+
readonly extensionContext: ExtensionContext;
|
|
115
|
+
readonly waitForForeground?: boolean;
|
|
116
|
+
readonly includeAttemptMetadata?: boolean;
|
|
117
|
+
}
|
|
118
|
+
export interface SubagentAttemptActionData {
|
|
119
|
+
readonly attempt: AgentAttemptSummary;
|
|
120
|
+
readonly session?: WorkflowAgentSessionReference;
|
|
121
|
+
readonly liveSession?: WorkflowAgentSession;
|
|
122
|
+
readonly prepared?: Readonly<PreparedAgentSession>;
|
|
123
|
+
readonly handoff?: LiveSessionHandoff;
|
|
124
|
+
readonly signal: AbortSignal;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export interface SubagentOwnerMarker {
|
|
128
|
+
readonly pid: number;
|
|
129
|
+
readonly processStart: number;
|
|
130
|
+
readonly sessionId: string;
|
|
131
|
+
readonly token: string;
|
|
132
|
+
readonly acquiredAt: number;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export interface SubagentLiveness {
|
|
136
|
+
readonly pid?: number;
|
|
137
|
+
readonly processStart?: number;
|
|
138
|
+
readonly sessionId?: string;
|
|
139
|
+
readonly token?: string;
|
|
140
|
+
readonly isLive?: (owner: Readonly<SubagentOwnerMarker>) => boolean | Promise<boolean>;
|
|
141
|
+
}
|
|
142
|
+
export interface SubagentExecutor {
|
|
143
|
+
execute(task: string, options: AgentExecutionOptions, signal?: AbortSignal, setSteer?: (handler: (message: string) => void | Promise<void>) => void): Promise<AgentExecutionResult>;
|
|
144
|
+
}
|
|
145
|
+
export interface SubagentManagerDependencies {
|
|
146
|
+
readonly getActiveTools?: () => readonly string[];
|
|
147
|
+
readonly agentDir?: string;
|
|
148
|
+
readonly storageDir?: string;
|
|
149
|
+
readonly transport?: AgentTransport;
|
|
150
|
+
readonly createExecutor?: (root: AgentExecutionRoot, transport: AgentTransport) => SubagentExecutor;
|
|
151
|
+
readonly worktreeAdapter?: SubagentWorktreeAdapter;
|
|
152
|
+
readonly liveness?: SubagentLiveness;
|
|
153
|
+
readonly notify?: (notification: Readonly<SubagentNotification>) => void | Promise<void>;
|
|
154
|
+
readonly onStatus?: (status: Readonly<SubagentStatus>, request: Readonly<SubagentRunRequest>) => void;
|
|
155
|
+
readonly onResourceWarning?: (message: string) => void;
|
|
156
|
+
}
|
|
157
|
+
export interface SubagentManager {
|
|
158
|
+
run(request: Readonly<SubagentRunRequest>, context: Readonly<SubagentManagerContext>): Promise<unknown>;
|
|
159
|
+
inspect(request: Readonly<SubagentInspectRequest>, context: Readonly<SubagentManagerContext>): Promise<unknown>;
|
|
160
|
+
steer(request: Readonly<SubagentSteerRequest>, context: Readonly<SubagentManagerContext>): Promise<unknown>;
|
|
161
|
+
stop(request: Readonly<SubagentIdRequest>, context: Readonly<SubagentManagerContext>): Promise<unknown>;
|
|
162
|
+
retry(request: Readonly<SubagentIdRequest>, context: Readonly<SubagentManagerContext>): Promise<unknown>;
|
|
163
|
+
getAttemptActionData?(id: string): Readonly<SubagentAttemptActionData> | undefined;
|
|
164
|
+
dispose?(): Promise<void>;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export interface SubagentsExtensionOptions {
|
|
168
|
+
readonly manager?: SubagentManager;
|
|
169
|
+
readonly clipboard?: (value: string) => Promise<void>;
|
|
170
|
+
readonly managerDependencies?: SubagentManagerDependencies;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export interface SubagentsExtension {
|
|
174
|
+
readonly manager: SubagentManager;
|
|
175
|
+
readonly tools: readonly ToolDefinition[];
|
|
176
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { finiteNumber, jsonValue, parseThinking } from "../../src/utils.js";
|
|
2
|
+
import type { AgentAttemptSummary } from "../../src/types.js";
|
|
3
|
+
import { SUBAGENT_ATTEMPT_DETAILS_LIMIT, type SubagentProgress, type SubagentStatus } from "./contracts.js";
|
|
4
|
+
|
|
5
|
+
function objectValue(value: unknown): Record<string, unknown> | undefined { return typeof value === "object" && value !== null && !Array.isArray(value) ? value as Record<string, unknown> : undefined; }
|
|
6
|
+
function resourceSummaryValue(value: unknown): NonNullable<AgentAttemptSummary["setup"]["resourceSelectors"]> | undefined {
|
|
7
|
+
const record = objectValue(value);
|
|
8
|
+
const selectors = objectValue(record?.selectors);
|
|
9
|
+
const skills = stringArrayValue(record?.skills);
|
|
10
|
+
const extensions = stringArrayValue(record?.extensions);
|
|
11
|
+
const tools = stringArrayValue(record?.tools);
|
|
12
|
+
const selectorSkills = stringArrayValue(selectors?.skills);
|
|
13
|
+
const selectorExtensions = stringArrayValue(selectors?.extensions);
|
|
14
|
+
const selectorTools = stringArrayValue(selectors?.tools);
|
|
15
|
+
const unmatchedSkills = stringArrayValue(record?.unmatchedSkills);
|
|
16
|
+
const unmatchedExtensions = stringArrayValue(record?.unmatchedExtensions);
|
|
17
|
+
const unmatchedTools = stringArrayValue(record?.unmatchedTools);
|
|
18
|
+
if (!record || !selectors || !skills || !extensions || !tools || !selectorSkills || !selectorExtensions || !selectorTools || !unmatchedSkills || !unmatchedExtensions || !unmatchedTools) return undefined;
|
|
19
|
+
return { selectors: { skills: selectorSkills, extensions: selectorExtensions, tools: selectorTools }, skills, extensions, tools, unmatchedSkills, unmatchedExtensions, unmatchedTools };
|
|
20
|
+
}
|
|
21
|
+
function nonnegativeInteger(value: unknown): value is number { return typeof value === "number" && Number.isSafeInteger(value) && value >= 0; }
|
|
22
|
+
function stringArrayValue(value: unknown): readonly string[] | undefined { return Array.isArray(value) && value.length <= 256 && value.every((item) => typeof item === "string") ? value : undefined; }
|
|
23
|
+
export function accountingValue(value: unknown): SubagentProgress["accounting"] | undefined {
|
|
24
|
+
const record = objectValue(value);
|
|
25
|
+
if (!record) return undefined;
|
|
26
|
+
const input = record.input;
|
|
27
|
+
const output = record.output;
|
|
28
|
+
const cacheRead = record.cacheRead;
|
|
29
|
+
const cacheWrite = record.cacheWrite;
|
|
30
|
+
const cost = record.cost;
|
|
31
|
+
if (!finiteNumber(input) || !finiteNumber(output) || !finiteNumber(cacheRead) || !finiteNumber(cacheWrite) || !finiteNumber(cost)) return undefined;
|
|
32
|
+
return { input, output, cacheRead, cacheWrite, cost };
|
|
33
|
+
}
|
|
34
|
+
export function legacyAccountingValue(record: Record<string, unknown>): SubagentProgress["accounting"] | undefined {
|
|
35
|
+
const accounting = accountingValue(record.accounting);
|
|
36
|
+
if (record.accounting !== undefined && !accounting) return undefined;
|
|
37
|
+
const usage = record.usage;
|
|
38
|
+
if (usage === undefined) return accounting;
|
|
39
|
+
const usageRecord = objectValue(usage);
|
|
40
|
+
const tokens = objectValue(usageRecord?.tokens);
|
|
41
|
+
if (!usageRecord || !tokens || !finiteNumber(tokens.input) || !finiteNumber(tokens.output) || !finiteNumber(tokens.cacheRead) || !finiteNumber(tokens.cacheWrite) || !finiteNumber(tokens.total) || !finiteNumber(usageRecord.cost)) return undefined;
|
|
42
|
+
return accounting ?? { input: tokens.input, output: tokens.output, cacheRead: tokens.cacheRead, cacheWrite: tokens.cacheWrite, cost: usageRecord.cost };
|
|
43
|
+
}
|
|
44
|
+
export function activityValue(value: unknown): NonNullable<SubagentProgress["activity"]> | undefined {
|
|
45
|
+
const record = objectValue(value);
|
|
46
|
+
return record && (record.kind === "reasoning" || record.kind === "tool" || record.kind === "text") && typeof record.text === "string" ? { kind: record.kind, text: record.text } : undefined;
|
|
47
|
+
}
|
|
48
|
+
function toolCallsValue(value: unknown): SubagentProgress["toolCalls"] | undefined {
|
|
49
|
+
if (!Array.isArray(value) || value.length > 256) return undefined;
|
|
50
|
+
const calls: SubagentProgress["toolCalls"][number][] = [];
|
|
51
|
+
for (const item of value) {
|
|
52
|
+
const record = objectValue(item);
|
|
53
|
+
if (!record || typeof record.id !== "string" || typeof record.name !== "string" || record.state !== "running" && record.state !== "completed" && record.state !== "failed") return undefined;
|
|
54
|
+
calls.push({ id: record.id, name: record.name, state: record.state });
|
|
55
|
+
}
|
|
56
|
+
return calls;
|
|
57
|
+
}
|
|
58
|
+
function sessionReferenceValue(value: unknown): AgentAttemptSummary["session"] | undefined {
|
|
59
|
+
const record = objectValue(value);
|
|
60
|
+
const transport = record?.transport;
|
|
61
|
+
const sessionId = record?.sessionId;
|
|
62
|
+
if (!record || typeof transport !== "string" || !transport.trim() || typeof sessionId !== "string" || !sessionId.trim()) return undefined;
|
|
63
|
+
return { transport, sessionId, ...(record.locator === undefined || !jsonValue(record.locator) ? {} : { locator: record.locator }) };
|
|
64
|
+
}
|
|
65
|
+
export function subagentErrorValue(value: unknown): { readonly code: string; readonly message: string } | undefined {
|
|
66
|
+
const record = objectValue(value);
|
|
67
|
+
return record && typeof record.code === "string" && typeof record.message === "string" ? { code: record.code, message: record.message } : undefined;
|
|
68
|
+
}
|
|
69
|
+
export function worktreeValue(value: unknown): { readonly path: string; readonly branch: string } | undefined {
|
|
70
|
+
const record = objectValue(value);
|
|
71
|
+
return record && typeof record.path === "string" && record.path.trim() && typeof record.branch === "string" && record.branch.trim() ? { path: record.path, branch: record.branch } : undefined;
|
|
72
|
+
}
|
|
73
|
+
function progressValue(value: unknown): SubagentProgress | undefined {
|
|
74
|
+
const record = objectValue(value);
|
|
75
|
+
if (!record) return undefined;
|
|
76
|
+
const accounting = accountingValue(record.accounting);
|
|
77
|
+
const toolCalls = toolCallsValue(record.toolCalls);
|
|
78
|
+
if (!accounting || !toolCalls) return undefined;
|
|
79
|
+
const activity = record.activity === undefined ? undefined : activityValue(record.activity);
|
|
80
|
+
if (record.activity !== undefined && activity === undefined) return undefined;
|
|
81
|
+
const rawState = record.state;
|
|
82
|
+
let state: SubagentProgress["state"];
|
|
83
|
+
if (rawState !== undefined) {
|
|
84
|
+
const stateRecord = objectValue(rawState);
|
|
85
|
+
const model = objectValue(stateRecord?.model);
|
|
86
|
+
const tools = stringArrayValue(stateRecord?.tools);
|
|
87
|
+
if (!stateRecord || !model || !tools || typeof model.provider !== "string" || typeof model.model !== "string") return undefined;
|
|
88
|
+
const thinking = parseThinking(model.thinking);
|
|
89
|
+
if (model.thinking !== undefined && thinking === undefined) return undefined;
|
|
90
|
+
state = { model: { provider: model.provider, model: model.model, ...(thinking === undefined ? {} : { thinking }) }, tools };
|
|
91
|
+
}
|
|
92
|
+
const lastEventAt = record.lastEventAt;
|
|
93
|
+
if (lastEventAt !== undefined && !nonnegativeInteger(lastEventAt)) return undefined;
|
|
94
|
+
return { accounting, toolCalls, ...(state === undefined ? {} : { state }), ...(activity === undefined ? {} : { activity }), ...(lastEventAt === undefined ? {} : { lastEventAt }) };
|
|
95
|
+
}
|
|
96
|
+
function legacyProgressValue(record: Record<string, unknown>): SubagentProgress | undefined {
|
|
97
|
+
const accounting = legacyAccountingValue(record);
|
|
98
|
+
const toolCalls = record.toolCalls === undefined ? [] : toolCallsValue(record.toolCalls);
|
|
99
|
+
if (!accounting || !toolCalls) return undefined;
|
|
100
|
+
const activity = record.activity === undefined ? undefined : activityValue(record.activity);
|
|
101
|
+
const lastEventAt = record.lastEventAt;
|
|
102
|
+
if (record.activity !== undefined && activity === undefined || lastEventAt !== undefined && !nonnegativeInteger(lastEventAt)) return undefined;
|
|
103
|
+
return { accounting, toolCalls, ...(activity === undefined ? {} : { activity }), ...(lastEventAt === undefined ? {} : { lastEventAt }) };
|
|
104
|
+
}
|
|
105
|
+
export function attemptValue(value: unknown): AgentAttemptSummary | undefined {
|
|
106
|
+
const record = objectValue(value);
|
|
107
|
+
const setup = objectValue(record?.setup);
|
|
108
|
+
const model = objectValue(setup?.model);
|
|
109
|
+
const hookNames = stringArrayValue(setup?.hookNames);
|
|
110
|
+
const tools = stringArrayValue(setup?.tools);
|
|
111
|
+
const resources = setup?.resourceSelectors === undefined ? undefined : resourceSummaryValue(setup.resourceSelectors);
|
|
112
|
+
const accounting = accountingValue(record?.accounting);
|
|
113
|
+
const session = record?.session === undefined ? undefined : sessionReferenceValue(record.session);
|
|
114
|
+
const error = record?.error === undefined ? undefined : subagentErrorValue(record.error);
|
|
115
|
+
const thinking = parseThinking(model?.thinking);
|
|
116
|
+
if (!record || !nonnegativeInteger(record.attempt) || record.attempt < 1 || typeof record.transport !== "string" || !record.transport.trim() || !setup || typeof setup.cwd !== "string" || !setup.cwd.trim() || !model || typeof model.provider !== "string" || !model.provider.trim() || typeof model.model !== "string" || !model.model.trim() || model.thinking !== undefined && thinking === undefined || !hookNames || !tools || setup.resourceSelectors !== undefined && resources === undefined || !accounting || record.session !== undefined && session === undefined || record.error !== undefined && error === undefined) return undefined;
|
|
117
|
+
return {
|
|
118
|
+
attempt: record.attempt,
|
|
119
|
+
transport: record.transport,
|
|
120
|
+
setup: { hookNames, model: { provider: model.provider, model: model.model, ...(thinking === undefined ? {} : { thinking }) }, tools, cwd: setup.cwd, ...(resources === undefined ? {} : { resourceSelectors: resources }) },
|
|
121
|
+
accounting,
|
|
122
|
+
...(session === undefined ? {} : { session }),
|
|
123
|
+
...(error === undefined ? {} : { error }),
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
export function statusValue(value: unknown): SubagentStatus | undefined {
|
|
127
|
+
const record = objectValue(value);
|
|
128
|
+
const id = record?.id;
|
|
129
|
+
const state = record?.state;
|
|
130
|
+
if (!record || typeof id !== "string" || !id || state !== "running" && state !== "completed" && state !== "failed" && state !== "stopped") return undefined;
|
|
131
|
+
const startedAt = record.startedAt;
|
|
132
|
+
const finishedAt = record.finishedAt;
|
|
133
|
+
if (startedAt !== undefined && !nonnegativeInteger(startedAt) || finishedAt !== undefined && (!nonnegativeInteger(finishedAt) || typeof startedAt === "number" && finishedAt < startedAt)) return undefined;
|
|
134
|
+
const sessionId = record.sessionId;
|
|
135
|
+
if (sessionId !== undefined && (typeof sessionId !== "string" || !sessionId.trim())) return undefined;
|
|
136
|
+
const worktree = record.worktree === undefined ? undefined : worktreeValue(record.worktree);
|
|
137
|
+
if (record.worktree !== undefined && worktree === undefined) return undefined;
|
|
138
|
+
const error = record.error === undefined ? undefined : subagentErrorValue(record.error);
|
|
139
|
+
if (record.error !== undefined && error === undefined) return undefined;
|
|
140
|
+
const attempts = record.attempts;
|
|
141
|
+
if (attempts !== undefined && (!nonnegativeInteger(attempts) || attempts < 1)) return undefined;
|
|
142
|
+
const attemptDetails = record.attemptDetails === undefined ? undefined : Array.isArray(record.attemptDetails) ? record.attemptDetails.slice(-SUBAGENT_ATTEMPT_DETAILS_LIMIT).map(attemptValue) : undefined;
|
|
143
|
+
if (record.attemptDetails !== undefined && (!attemptDetails || attemptDetails.some((attempt): attempt is undefined => attempt === undefined))) return undefined;
|
|
144
|
+
const progress = record.progress === undefined ? legacyProgressValue(record) : progressValue(record.progress);
|
|
145
|
+
if (record.progress !== undefined && progress === undefined) return undefined;
|
|
146
|
+
return {
|
|
147
|
+
id,
|
|
148
|
+
...(sessionId === undefined ? {} : { sessionId }),
|
|
149
|
+
state,
|
|
150
|
+
...(startedAt === undefined ? {} : { startedAt }),
|
|
151
|
+
...(finishedAt === undefined ? {} : { finishedAt }),
|
|
152
|
+
...(worktree === undefined ? {} : { worktree }),
|
|
153
|
+
...(error === undefined ? {} : { error }),
|
|
154
|
+
...(attempts === undefined ? {} : { attempts }),
|
|
155
|
+
...(attemptDetails === undefined ? {} : { attemptDetails: attemptDetails.filter((attempt): attempt is AgentAttemptSummary => attempt !== undefined) }),
|
|
156
|
+
...(progress === undefined ? {} : { progress }),
|
|
157
|
+
};
|
|
158
|
+
}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { defineTool, getAgentDir, type AgentToolResult, type ExtensionAPI, type ExtensionContext, type ToolDefinition } from "@earendil-works/pi-coding-agent";
|
|
3
|
+
import { Value } from "typebox/value";
|
|
4
|
+
import { WorkflowError, loadingRegistry } from "../../src/index.js";
|
|
5
|
+
import { clearSubagentManager, setSubagentManager } from "../../src/subagent-manager-handle.js";
|
|
6
|
+
import type { SubagentIdRequest, SubagentInspectRequest, SubagentManager, SubagentManagerContext, SubagentNotification, SubagentsExtension, SubagentsExtensionOptions, SubagentRunRequest, SubagentStatus, SubagentSteerRequest } from "./contracts.js";
|
|
7
|
+
import { createSubagentManager } from "./manager.js";
|
|
8
|
+
import { registerSubagentNavigator } from "./navigator.js";
|
|
9
|
+
import { createSubagentBackgroundWidget, renderSubagentCall, renderSubagentControlCall, renderSubagentControlResult, renderSubagentInspectCall, renderSubagentInspectResult, renderSubagentResult } from "./view.js";
|
|
10
|
+
import {
|
|
11
|
+
normalizeSubagentRunRequest,
|
|
12
|
+
SUBAGENTS_ID_PARAMETERS,
|
|
13
|
+
SUBAGENTS_INSPECT_PARAMETERS,
|
|
14
|
+
SUBAGENTS_RETRY_PARAMETERS,
|
|
15
|
+
SUBAGENTS_RUN_PARAMETERS,
|
|
16
|
+
SUBAGENTS_STEER_PARAMETERS,
|
|
17
|
+
SUBAGENTS_STOP_PARAMETERS,
|
|
18
|
+
} from "./contracts.js";
|
|
19
|
+
|
|
20
|
+
export * from "./contracts.js";
|
|
21
|
+
export { attemptValue, statusValue, subagentErrorValue } from "./decode.js";
|
|
22
|
+
export { createSubagentManager, createUnavailableSubagentManager } from "./manager.js";
|
|
23
|
+
export { createRunStoreWorktreeAdapter, defaultWorktreeHome } from "./worktree.js";
|
|
24
|
+
export type { SubagentWorktreeAdapter, SubagentWorktreeContext, SubagentWorktreeHandle, SubagentWorktreeRunStore } from "./worktree.js";
|
|
25
|
+
|
|
26
|
+
type SubagentsExtensionAPI = Pick<ExtensionAPI, "registerTool"> & Partial<Pick<ExtensionAPI, "getActiveTools" | "on" | "sendMessage" | "registerCommand">>;
|
|
27
|
+
|
|
28
|
+
function validateSubagentRunRequest(value: unknown): SubagentRunRequest {
|
|
29
|
+
return normalizeSubagentRunRequest(value);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function validateSubagentIdRequest(value: unknown, operation: string): SubagentIdRequest {
|
|
33
|
+
if (!Value.Check(SUBAGENTS_ID_PARAMETERS, value)) throw new WorkflowError("INVALID_METADATA", `Invalid ${operation} parameters`);
|
|
34
|
+
return value;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function validateSubagentInspectRequest(value: unknown): SubagentInspectRequest {
|
|
38
|
+
if (!Value.Check(SUBAGENTS_INSPECT_PARAMETERS, value)) throw new WorkflowError("INVALID_METADATA", "Invalid subagents_inspect parameters");
|
|
39
|
+
return value;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function validateSubagentSteerRequest(value: unknown): SubagentSteerRequest {
|
|
43
|
+
if (!Value.Check(SUBAGENTS_STEER_PARAMETERS, value)) throw new WorkflowError("INVALID_METADATA", "Invalid subagents_steer parameters");
|
|
44
|
+
return value;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function serialize(value: unknown): string {
|
|
48
|
+
if (typeof value === "string") return value;
|
|
49
|
+
try {
|
|
50
|
+
const serialized = JSON.stringify(value);
|
|
51
|
+
return typeof serialized === "string" ? serialized : String(value);
|
|
52
|
+
} catch {
|
|
53
|
+
return String(value);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function toolResult(value: unknown): AgentToolResult<unknown> {
|
|
58
|
+
return { content: [{ type: "text", text: serialize(value) }], details: value };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function managerContext(toolCallId: string, signal: AbortSignal | undefined, onUpdate: ((value: AgentToolResult<unknown>) => void) | undefined, context: ExtensionContext): SubagentManagerContext {
|
|
62
|
+
return {
|
|
63
|
+
toolCallId,
|
|
64
|
+
signal,
|
|
65
|
+
onUpdate: onUpdate === undefined ? undefined : (value) => { onUpdate(toolResult(value)); },
|
|
66
|
+
extensionContext: context,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
export function createSubagentTools(manager: SubagentManager): readonly ToolDefinition[] {
|
|
70
|
+
return [
|
|
71
|
+
defineTool({
|
|
72
|
+
name: "subagents_run",
|
|
73
|
+
label: "Subagents Run",
|
|
74
|
+
description: "Start a durable subagent run. Background mode returns its ID immediately; foreground mode waits and returns the terminal value or error in this tool call. Use the ID with subagents_inspect, subagents_steer, subagents_stop, or subagents_retry.",
|
|
75
|
+
promptSnippet: "Run durable subagents in background by default; inspect, steer, stop, or retry them with the other subagents tools.",
|
|
76
|
+
promptGuidelines: [
|
|
77
|
+
"Runs are background by default; choose mode: \"foreground\" only when the terminal envelope is needed in this turn.",
|
|
78
|
+
"Do not poll a running ID; use subagents_inspect({ id }) when you need its current status or terminal result.",
|
|
79
|
+
],
|
|
80
|
+
parameters: SUBAGENTS_RUN_PARAMETERS,
|
|
81
|
+
async execute(toolCallId, params, signal, onUpdate, context) {
|
|
82
|
+
return toolResult(await manager.run(validateSubagentRunRequest(params), managerContext(toolCallId, signal, onUpdate, context)));
|
|
83
|
+
},
|
|
84
|
+
renderCall(args, _theme, context) { return renderSubagentCall(args, context); },
|
|
85
|
+
renderResult(result, options, theme, context) { return renderSubagentResult(result, options, theme, context); },
|
|
86
|
+
}),
|
|
87
|
+
defineTool({
|
|
88
|
+
name: "subagents_inspect",
|
|
89
|
+
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.",
|
|
91
|
+
parameters: SUBAGENTS_INSPECT_PARAMETERS,
|
|
92
|
+
async execute(toolCallId, params, signal, onUpdate, context) {
|
|
93
|
+
return toolResult(await manager.inspect(validateSubagentInspectRequest(params), managerContext(toolCallId, signal, onUpdate, context)));
|
|
94
|
+
},
|
|
95
|
+
renderCall(args, theme) { return renderSubagentInspectCall(args, theme); },
|
|
96
|
+
renderResult(result, options, theme, context) { return renderSubagentInspectResult(result, options, theme, context.args); },
|
|
97
|
+
}),
|
|
98
|
+
defineTool({
|
|
99
|
+
name: "subagents_steer",
|
|
100
|
+
label: "Subagents Steer",
|
|
101
|
+
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
|
+
parameters: SUBAGENTS_STEER_PARAMETERS,
|
|
103
|
+
async execute(toolCallId, params, signal, onUpdate, context) {
|
|
104
|
+
return toolResult(await manager.steer(validateSubagentSteerRequest(params), managerContext(toolCallId, signal, onUpdate, context)));
|
|
105
|
+
},
|
|
106
|
+
renderCall(args, theme) { return renderSubagentControlCall("subagents_steer", args, theme); },
|
|
107
|
+
renderResult(result, _options, theme) { return renderSubagentControlResult(result, theme); },
|
|
108
|
+
}),
|
|
109
|
+
defineTool({
|
|
110
|
+
name: "subagents_stop",
|
|
111
|
+
label: "Subagents Stop",
|
|
112
|
+
description: "Stop one running subagent, abort its active session, persist state \"stopped\", and clean its worktree without affecting sibling runs.",
|
|
113
|
+
parameters: SUBAGENTS_STOP_PARAMETERS,
|
|
114
|
+
async execute(toolCallId, params, signal, onUpdate, context) {
|
|
115
|
+
return toolResult(await manager.stop(validateSubagentIdRequest(params, "subagents_stop"), managerContext(toolCallId, signal, onUpdate, context)));
|
|
116
|
+
},
|
|
117
|
+
renderCall(args, theme) { return renderSubagentControlCall("subagents_stop", args, theme); },
|
|
118
|
+
renderResult(result, _options, theme) { return renderSubagentControlResult(result, theme); },
|
|
119
|
+
}),
|
|
120
|
+
defineTool({
|
|
121
|
+
name: "subagents_retry",
|
|
122
|
+
label: "Subagents Retry",
|
|
123
|
+
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
|
+
parameters: SUBAGENTS_RETRY_PARAMETERS,
|
|
125
|
+
async execute(toolCallId, params, signal, onUpdate, context) {
|
|
126
|
+
return toolResult(await manager.retry(validateSubagentIdRequest(params, "subagents_retry"), managerContext(toolCallId, signal, onUpdate, context)));
|
|
127
|
+
},
|
|
128
|
+
renderCall(args, theme) { return renderSubagentControlCall("subagents_retry", args, theme); },
|
|
129
|
+
renderResult(result, options, theme, context) { return renderSubagentResult(result, options, theme, context); },
|
|
130
|
+
}),
|
|
131
|
+
];
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function notificationContent(notification: SubagentNotification): string {
|
|
135
|
+
const label = notification.label?.trim();
|
|
136
|
+
const role = notification.role?.trim() || "none";
|
|
137
|
+
const identity = label ? `${label} role=${role} (${notification.id})` : `${notification.id} role=${role}`;
|
|
138
|
+
if (notification.state === "completed") return `Subagent ${identity} completed. Inspect it with subagents_inspect({ id: "${notification.id}" }).`;
|
|
139
|
+
return `Subagent ${identity} failed: ${notification.error?.message ?? "unknown error"}. Inspect it with subagents_inspect({ id: "${notification.id}" }).`;
|
|
140
|
+
}
|
|
141
|
+
function managerDependencies(options: SubagentsExtensionOptions, activeTools: (() => readonly string[]) | undefined, notify: ((notification: SubagentNotification) => void | Promise<void>) | undefined, onStatus: ((status: Readonly<SubagentStatus>, request: Readonly<SubagentRunRequest>) => void) | undefined, onResourceWarning?: (message: string) => void): SubagentsExtensionOptions["managerDependencies"] {
|
|
142
|
+
const dependencies = options.managerDependencies;
|
|
143
|
+
const next = {
|
|
144
|
+
...(dependencies ?? {}),
|
|
145
|
+
...(activeTools !== undefined && dependencies?.getActiveTools === undefined ? { getActiveTools: activeTools } : {}),
|
|
146
|
+
...(notify !== undefined && dependencies?.notify === undefined ? { notify } : {}),
|
|
147
|
+
...(onStatus !== undefined && dependencies?.onStatus === undefined ? { onStatus } : {}),
|
|
148
|
+
...(onResourceWarning !== undefined && dependencies?.onResourceWarning === undefined ? { onResourceWarning } : {}),
|
|
149
|
+
};
|
|
150
|
+
return Object.keys(next).length === 0 ? dependencies : next;
|
|
151
|
+
}
|
|
152
|
+
function storageDirectory(options: SubagentsExtensionOptions): string {
|
|
153
|
+
const dependencies = options.managerDependencies;
|
|
154
|
+
return dependencies?.storageDir ?? join(dependencies?.agentDir ?? getAgentDir(), "subagents");
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function createSubagentsExtension(options: SubagentsExtensionOptions = {}, activeTools?: () => readonly string[], notify?: (notification: SubagentNotification) => void | Promise<void>, onStatus?: (status: Readonly<SubagentStatus>, request: Readonly<SubagentRunRequest>) => void, onResourceWarning?: (message: string) => void): SubagentsExtension {
|
|
158
|
+
const manager = options.manager ?? createSubagentManager(managerDependencies(options, activeTools, notify, onStatus, onResourceWarning));
|
|
159
|
+
const extension = { manager, tools: createSubagentTools(manager) };
|
|
160
|
+
return extension;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export function registerSubagentsExtension(pi: SubagentsExtensionAPI, options: SubagentsExtensionOptions = {}): SubagentsExtension {
|
|
164
|
+
const getActiveTools = pi.getActiveTools;
|
|
165
|
+
const activeTools = getActiveTools === undefined ? undefined : () => getActiveTools.call(pi);
|
|
166
|
+
const sendMessage = pi.sendMessage;
|
|
167
|
+
const notify = sendMessage === undefined ? undefined : (notification: SubagentNotification): void => {
|
|
168
|
+
sendMessage.call(pi, { customType: "subagents", content: notificationContent(notification), display: true, details: notification }, { deliverAs: "steer", triggerTurn: true });
|
|
169
|
+
};
|
|
170
|
+
const onResourceWarning = sendMessage === undefined ? undefined : (message: string): void => {
|
|
171
|
+
sendMessage.call(pi, { customType: "subagents", content: `Warning: ${message}`, display: true }, { deliverAs: "steer" });
|
|
172
|
+
};
|
|
173
|
+
const widget = createSubagentBackgroundWidget();
|
|
174
|
+
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);
|
|
175
|
+
for (const tool of extension.tools) pi.registerTool(tool);
|
|
176
|
+
if (pi.registerCommand !== undefined) registerSubagentNavigator(pi.registerCommand.bind(pi), extension.manager, storageDirectory(options), options.clipboard);
|
|
177
|
+
if (pi.on !== undefined) {
|
|
178
|
+
setSubagentManager(extension.manager);
|
|
179
|
+
pi.on("session_start", (_event, context) => { widget.start(context); });
|
|
180
|
+
pi.on("session_shutdown", async () => {
|
|
181
|
+
try {
|
|
182
|
+
widget.dispose();
|
|
183
|
+
await extension.manager.dispose?.();
|
|
184
|
+
} finally {
|
|
185
|
+
clearSubagentManager(extension.manager);
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
return extension;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export default function extension(pi: ExtensionAPI, options: SubagentsExtensionOptions = {}): void {
|
|
193
|
+
registerSubagentsExtension(pi, options);
|
|
194
|
+
}
|