@pi-agents/orchid 0.1.0-beta.0
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 +41 -0
- package/LICENSE +21 -0
- package/README.md +246 -0
- package/agents/AGENTS-MANIFEST.md +42 -0
- package/agents/brain.md +42 -0
- package/agents/context-builder.md +46 -0
- package/agents/delegate.md +12 -0
- package/agents/dev-1.md +42 -0
- package/agents/oracle.md +73 -0
- package/agents/planner.md +55 -0
- package/agents/researcher.md +52 -0
- package/agents/reviewer.md +79 -0
- package/agents/scout.md +50 -0
- package/agents/tester.md +45 -0
- package/agents/worker.md +55 -0
- package/extensions/ralph.ts +1 -0
- package/extensions/reviewer-extension.ts +125 -0
- package/extensions/task-orchestrator.ts +28 -0
- package/package.json +63 -0
- package/prompts/gather-context-and-clarify.md +13 -0
- package/prompts/parallel-cleanup.md +59 -0
- package/prompts/parallel-context-build.md +53 -0
- package/prompts/parallel-handoff-plan.md +59 -0
- package/prompts/parallel-research.md +50 -0
- package/prompts/parallel-review.md +54 -0
- package/prompts/review-loop.md +41 -0
- package/skills/orchid/SKILL.md +214 -0
- package/skills/orchid/orchid-cleanup/SKILL.md +122 -0
- package/skills/orchid/orchid-converge/SKILL.md +124 -0
- package/skills/orchid/orchid-decompose/SKILL.md +201 -0
- package/skills/orchid/orchid-doctor/SKILL.md +162 -0
- package/skills/orchid/orchid-investigate/SKILL.md +102 -0
- package/skills/orchid/orchid-launch/SKILL.md +147 -0
- package/skills/ralph/SKILL.md +73 -0
- package/skills/subagents/pi-subagents/SKILL.md +813 -0
- package/src/index.ts +7 -0
- package/src/orchestrator/abort.ts +534 -0
- package/src/orchestrator/agent-bridge-extension.ts +1020 -0
- package/src/orchestrator/agent-host.ts +954 -0
- package/src/orchestrator/cleanup.ts +776 -0
- package/src/orchestrator/config-loader.ts +1412 -0
- package/src/orchestrator/config-schema.ts +690 -0
- package/src/orchestrator/config.ts +81 -0
- package/src/orchestrator/context-window.ts +66 -0
- package/src/orchestrator/diagnostic-reports.ts +475 -0
- package/src/orchestrator/diagnostics.ts +394 -0
- package/src/orchestrator/discovery.ts +1833 -0
- package/src/orchestrator/engine-worker.ts +415 -0
- package/src/orchestrator/engine.ts +5940 -0
- package/src/orchestrator/execution.ts +3104 -0
- package/src/orchestrator/extension.ts +5934 -0
- package/src/orchestrator/formatting.ts +785 -0
- package/src/orchestrator/git.ts +88 -0
- package/src/orchestrator/index.ts +28 -0
- package/src/orchestrator/lane-runner.ts +1787 -0
- package/src/orchestrator/mailbox.ts +780 -0
- package/src/orchestrator/merge.ts +3414 -0
- package/src/orchestrator/messages.ts +1062 -0
- package/src/orchestrator/migrations.ts +278 -0
- package/src/orchestrator/naming.ts +117 -0
- package/src/orchestrator/path-resolver.ts +275 -0
- package/src/orchestrator/persistence.ts +2625 -0
- package/src/orchestrator/process-registry.ts +452 -0
- package/src/orchestrator/quality-gate.ts +1085 -0
- package/src/orchestrator/resume.ts +3488 -0
- package/src/orchestrator/sessions.ts +57 -0
- package/src/orchestrator/settings-loader.ts +136 -0
- package/src/orchestrator/settings-tui.ts +2208 -0
- package/src/orchestrator/sidecar-telemetry.ts +267 -0
- package/src/orchestrator/supervisor.ts +4548 -0
- package/src/orchestrator/task-executor-core.ts +675 -0
- package/src/orchestrator/tmux-compat.ts +37 -0
- package/src/orchestrator/tool-allowlist-constants.ts +37 -0
- package/src/orchestrator/types.ts +4465 -0
- package/src/orchestrator/verification.ts +547 -0
- package/src/orchestrator/waves.ts +1564 -0
- package/src/orchestrator/workspace.ts +707 -0
- package/src/orchestrator/worktree.ts +2725 -0
- package/src/ralph/index.ts +825 -0
- package/src/subagents/agents/agent-management.ts +648 -0
- package/src/subagents/agents/agent-scope.ts +6 -0
- package/src/subagents/agents/agent-selection.ts +23 -0
- package/src/subagents/agents/agent-serializer.ts +86 -0
- package/src/subagents/agents/agents.ts +832 -0
- package/src/subagents/agents/chain-serializer.ts +137 -0
- package/src/subagents/agents/frontmatter.ts +29 -0
- package/src/subagents/agents/identity.ts +30 -0
- package/src/subagents/agents/skills.ts +632 -0
- package/src/subagents/extension/config.ts +16 -0
- package/src/subagents/extension/control-notices.ts +92 -0
- package/src/subagents/extension/doctor.ts +199 -0
- package/src/subagents/extension/fanout-child.ts +170 -0
- package/src/subagents/extension/index.ts +573 -0
- package/src/subagents/extension/schemas.ts +168 -0
- package/src/subagents/intercom/intercom-bridge.ts +379 -0
- package/src/subagents/intercom/result-intercom.ts +377 -0
- package/src/subagents/runs/background/async-execution.ts +712 -0
- package/src/subagents/runs/background/async-job-tracker.ts +310 -0
- package/src/subagents/runs/background/async-resume.ts +345 -0
- package/src/subagents/runs/background/async-status.ts +325 -0
- package/src/subagents/runs/background/completion-dedupe.ts +63 -0
- package/src/subagents/runs/background/notify.ts +108 -0
- package/src/subagents/runs/background/parallel-groups.ts +45 -0
- package/src/subagents/runs/background/result-watcher.ts +307 -0
- package/src/subagents/runs/background/run-id-resolver.ts +83 -0
- package/src/subagents/runs/background/run-status.ts +269 -0
- package/src/subagents/runs/background/stale-run-reconciler.ts +336 -0
- package/src/subagents/runs/background/subagent-runner.ts +1808 -0
- package/src/subagents/runs/background/top-level-async.ts +13 -0
- package/src/subagents/runs/foreground/chain-clarify.ts +1333 -0
- package/src/subagents/runs/foreground/chain-execution.ts +938 -0
- package/src/subagents/runs/foreground/execution.ts +918 -0
- package/src/subagents/runs/foreground/subagent-executor.ts +2527 -0
- package/src/subagents/runs/shared/completion-guard.ts +147 -0
- package/src/subagents/runs/shared/long-running-guard.ts +175 -0
- package/src/subagents/runs/shared/mcp-direct-tool-allowlist.ts +365 -0
- package/src/subagents/runs/shared/model-fallback.ts +103 -0
- package/src/subagents/runs/shared/nested-events.ts +819 -0
- package/src/subagents/runs/shared/nested-path.ts +52 -0
- package/src/subagents/runs/shared/nested-render.ts +115 -0
- package/src/subagents/runs/shared/parallel-utils.ts +109 -0
- package/src/subagents/runs/shared/pi-args.ts +220 -0
- package/src/subagents/runs/shared/pi-spawn.ts +115 -0
- package/src/subagents/runs/shared/run-history.ts +60 -0
- package/src/subagents/runs/shared/single-output.ts +164 -0
- package/src/subagents/runs/shared/subagent-control.ts +226 -0
- package/src/subagents/runs/shared/subagent-prompt-runtime.ts +170 -0
- package/src/subagents/runs/shared/worktree.ts +577 -0
- package/src/subagents/shared/artifacts.ts +98 -0
- package/src/subagents/shared/atomic-json.ts +16 -0
- package/src/subagents/shared/file-coalescer.ts +40 -0
- package/src/subagents/shared/fork-context.ts +76 -0
- package/src/subagents/shared/formatters.ts +133 -0
- package/src/subagents/shared/jsonl-writer.ts +81 -0
- package/src/subagents/shared/model-info.ts +78 -0
- package/src/subagents/shared/post-exit-stdio-guard.ts +85 -0
- package/src/subagents/shared/session-identity.ts +10 -0
- package/src/subagents/shared/session-tokens.ts +44 -0
- package/src/subagents/shared/settings.ts +397 -0
- package/src/subagents/shared/status-format.ts +49 -0
- package/src/subagents/shared/types.ts +822 -0
- package/src/subagents/shared/utils.ts +450 -0
- package/src/subagents/slash/prompt-template-bridge.ts +397 -0
- package/src/subagents/slash/slash-bridge.ts +174 -0
- package/src/subagents/slash/slash-commands.ts +528 -0
- package/src/subagents/slash/slash-live-state.ts +292 -0
- package/src/subagents/tui/render-helpers.ts +80 -0
- package/src/subagents/tui/render.ts +1358 -0
- package/templates/agents/local/supervisor.md +33 -0
- package/templates/agents/local/task-merger.md +27 -0
- package/templates/agents/local/task-reviewer.md +30 -0
- package/templates/agents/local/task-worker.md +34 -0
- package/templates/agents/supervisor-routing.md +92 -0
- package/templates/agents/supervisor.md +229 -0
- package/templates/agents/task-merger.md +214 -0
- package/templates/agents/task-reviewer.md +260 -0
- package/templates/agents/task-worker-segment.md +44 -0
- package/templates/agents/task-worker.md +557 -0
- package/templates/tasks/CONTEXT.md +30 -0
- package/templates/tasks/EXAMPLE-001-hello-world/PROMPT.md +98 -0
- package/templates/tasks/EXAMPLE-001-hello-world/STATUS.md +73 -0
- package/templates/tasks/EXAMPLE-002-parallel-smoke/PROMPT.md +97 -0
- package/templates/tasks/EXAMPLE-002-parallel-smoke/STATUS.md +73 -0
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
export const PROMPT_TEMPLATE_SUBAGENT_REQUEST_EVENT = "prompt-template:subagent:request";
|
|
2
|
+
export const PROMPT_TEMPLATE_SUBAGENT_STARTED_EVENT = "prompt-template:subagent:started";
|
|
3
|
+
export const PROMPT_TEMPLATE_SUBAGENT_RESPONSE_EVENT = "prompt-template:subagent:response";
|
|
4
|
+
export const PROMPT_TEMPLATE_SUBAGENT_UPDATE_EVENT = "prompt-template:subagent:update";
|
|
5
|
+
export const PROMPT_TEMPLATE_SUBAGENT_CANCEL_EVENT = "prompt-template:subagent:cancel";
|
|
6
|
+
|
|
7
|
+
interface PromptTemplateDelegationTask {
|
|
8
|
+
agent: string;
|
|
9
|
+
task: string;
|
|
10
|
+
model?: string;
|
|
11
|
+
cwd?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface PromptTemplateDelegationParallelResult {
|
|
15
|
+
agent: string;
|
|
16
|
+
messages: unknown[];
|
|
17
|
+
isError: boolean;
|
|
18
|
+
errorText?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface PromptTemplateDelegationRequest {
|
|
22
|
+
requestId: string;
|
|
23
|
+
agent: string;
|
|
24
|
+
task: string;
|
|
25
|
+
tasks?: PromptTemplateDelegationTask[];
|
|
26
|
+
context: "fresh" | "fork";
|
|
27
|
+
model: string;
|
|
28
|
+
cwd: string;
|
|
29
|
+
worktree?: boolean;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface PromptTemplateDelegationResponse extends PromptTemplateDelegationRequest {
|
|
33
|
+
messages: unknown[];
|
|
34
|
+
parallelResults?: PromptTemplateDelegationParallelResult[];
|
|
35
|
+
contentText?: string;
|
|
36
|
+
isError: boolean;
|
|
37
|
+
errorText?: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
interface PromptTemplateDelegationTaskProgress {
|
|
41
|
+
index?: number;
|
|
42
|
+
agent: string;
|
|
43
|
+
status?: string;
|
|
44
|
+
currentTool?: string;
|
|
45
|
+
currentToolArgs?: string;
|
|
46
|
+
recentOutput?: string;
|
|
47
|
+
recentOutputLines?: string[];
|
|
48
|
+
recentTools?: Array<{ tool: string; args: string }>;
|
|
49
|
+
model?: string;
|
|
50
|
+
toolCount?: number;
|
|
51
|
+
durationMs?: number;
|
|
52
|
+
tokens?: number;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
interface PromptTemplateDelegationUpdate {
|
|
56
|
+
requestId: string;
|
|
57
|
+
currentTool?: string;
|
|
58
|
+
currentToolArgs?: string;
|
|
59
|
+
recentOutput?: string;
|
|
60
|
+
recentOutputLines?: string[];
|
|
61
|
+
recentTools?: Array<{ tool: string; args: string }>;
|
|
62
|
+
model?: string;
|
|
63
|
+
toolCount?: number;
|
|
64
|
+
durationMs?: number;
|
|
65
|
+
tokens?: number;
|
|
66
|
+
taskProgress?: PromptTemplateDelegationTaskProgress[];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface PromptTemplateBridgeEvents {
|
|
70
|
+
on(event: string, handler: (data: unknown) => void): (() => void) | void;
|
|
71
|
+
emit(event: string, data: unknown): void;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
interface PromptTemplateBridgeResult {
|
|
75
|
+
isError?: boolean;
|
|
76
|
+
content?: unknown;
|
|
77
|
+
details?: {
|
|
78
|
+
results?: Array<{
|
|
79
|
+
agent?: string;
|
|
80
|
+
messages?: unknown[];
|
|
81
|
+
finalOutput?: string;
|
|
82
|
+
exitCode?: number;
|
|
83
|
+
error?: string;
|
|
84
|
+
model?: string;
|
|
85
|
+
}>;
|
|
86
|
+
progress?: Array<{
|
|
87
|
+
index?: number;
|
|
88
|
+
agent?: string;
|
|
89
|
+
status?: string;
|
|
90
|
+
currentTool?: string;
|
|
91
|
+
currentToolArgs?: string;
|
|
92
|
+
recentOutput?: string[];
|
|
93
|
+
recentTools?: Array<{ tool?: string; args?: string }>;
|
|
94
|
+
toolCount?: number;
|
|
95
|
+
durationMs?: number;
|
|
96
|
+
tokens?: number;
|
|
97
|
+
}>;
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
interface PromptTemplateBridgeOptions<Ctx extends { cwd?: string }> {
|
|
102
|
+
events: PromptTemplateBridgeEvents;
|
|
103
|
+
getContext: () => Ctx | null;
|
|
104
|
+
execute: (
|
|
105
|
+
requestId: string,
|
|
106
|
+
request: PromptTemplateDelegationRequest,
|
|
107
|
+
signal: AbortSignal,
|
|
108
|
+
ctx: Ctx,
|
|
109
|
+
onUpdate: (result: PromptTemplateBridgeResult) => void,
|
|
110
|
+
) => Promise<PromptTemplateBridgeResult>;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function parseDelegationTasks(tasks: unknown): PromptTemplateDelegationTask[] {
|
|
114
|
+
if (!Array.isArray(tasks)) return [];
|
|
115
|
+
const parsed: PromptTemplateDelegationTask[] = [];
|
|
116
|
+
for (const item of tasks) {
|
|
117
|
+
if (!item || typeof item !== "object") return [];
|
|
118
|
+
const value = item as Partial<PromptTemplateDelegationTask>;
|
|
119
|
+
if (typeof value.agent !== "string" || !value.agent.trim()) return [];
|
|
120
|
+
if (typeof value.task !== "string" || !value.task.trim()) return [];
|
|
121
|
+
const model = typeof value.model === "string" && value.model.trim().length > 0 ? value.model : undefined;
|
|
122
|
+
const cwd = typeof value.cwd === "string" && value.cwd.trim().length > 0 ? value.cwd : undefined;
|
|
123
|
+
parsed.push({
|
|
124
|
+
agent: value.agent,
|
|
125
|
+
task: value.task,
|
|
126
|
+
...(model ? { model } : {}),
|
|
127
|
+
...(cwd ? { cwd } : {}),
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
return parsed;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function parsePromptTemplateRequest(data: unknown): PromptTemplateDelegationRequest | undefined {
|
|
134
|
+
if (!data || typeof data !== "object") return undefined;
|
|
135
|
+
const value = data as Partial<PromptTemplateDelegationRequest> & { tasks?: unknown };
|
|
136
|
+
if (typeof value.requestId !== "string" || !value.requestId) return undefined;
|
|
137
|
+
if (typeof value.model !== "string" || !value.model) return undefined;
|
|
138
|
+
if (typeof value.cwd !== "string" || !value.cwd) return undefined;
|
|
139
|
+
if (value.context !== "fresh" && value.context !== "fork") return undefined;
|
|
140
|
+
const tasks = parseDelegationTasks(value.tasks);
|
|
141
|
+
const worktree = value.worktree === true ? true : undefined;
|
|
142
|
+
const hasSingle =
|
|
143
|
+
typeof value.agent === "string" &&
|
|
144
|
+
value.agent.length > 0 &&
|
|
145
|
+
typeof value.task === "string" &&
|
|
146
|
+
value.task.length > 0;
|
|
147
|
+
if (!hasSingle && tasks.length === 0) return undefined;
|
|
148
|
+
|
|
149
|
+
const fallbackTask = tasks[0];
|
|
150
|
+
return {
|
|
151
|
+
requestId: value.requestId,
|
|
152
|
+
agent: hasSingle ? value.agent : fallbackTask!.agent,
|
|
153
|
+
task: hasSingle ? value.task : fallbackTask!.task,
|
|
154
|
+
...(tasks.length > 0 ? { tasks } : {}),
|
|
155
|
+
context: value.context,
|
|
156
|
+
model: value.model,
|
|
157
|
+
cwd: value.cwd,
|
|
158
|
+
...(worktree ? { worktree } : {}),
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function firstTextContent(content: unknown): string | undefined {
|
|
163
|
+
if (!Array.isArray(content)) return undefined;
|
|
164
|
+
for (const part of content) {
|
|
165
|
+
if (!part || typeof part !== "object") continue;
|
|
166
|
+
if ((part as { type?: string }).type !== "text") continue;
|
|
167
|
+
const text = (part as { text?: unknown }).text;
|
|
168
|
+
if (typeof text === "string" && text.trim()) return text.trim();
|
|
169
|
+
}
|
|
170
|
+
return undefined;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function filterRecentOutput(lines: string[] | undefined): string[] | undefined {
|
|
174
|
+
if (!lines || lines.length === 0) return undefined;
|
|
175
|
+
const filtered = lines.filter((line) => typeof line === "string" && line.trim() && line.trim() !== "(running...)");
|
|
176
|
+
if (filtered.length === 0) return undefined;
|
|
177
|
+
return filtered;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function sanitizeRecentTools(
|
|
181
|
+
tools: Array<{ tool?: string; args?: string }> | undefined,
|
|
182
|
+
): Array<{ tool: string; args: string }> | undefined {
|
|
183
|
+
if (!tools || tools.length === 0) return undefined;
|
|
184
|
+
const sanitized = tools.flatMap((entry) => {
|
|
185
|
+
if (typeof entry.tool !== "string" || entry.tool.trim().length === 0) return [];
|
|
186
|
+
return [{
|
|
187
|
+
tool: entry.tool,
|
|
188
|
+
args: typeof entry.args === "string" ? entry.args : String(entry.args ?? ""),
|
|
189
|
+
}];
|
|
190
|
+
});
|
|
191
|
+
return sanitized.length > 0 ? sanitized : undefined;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function resolveProgressModel(
|
|
195
|
+
update: PromptTemplateBridgeResult,
|
|
196
|
+
entry: { index?: number; agent?: string },
|
|
197
|
+
): string | undefined {
|
|
198
|
+
const results = update.details?.results;
|
|
199
|
+
if (!results || results.length === 0) return undefined;
|
|
200
|
+
if (typeof entry.index === "number" && entry.index >= 0) {
|
|
201
|
+
const byIndex = results[entry.index];
|
|
202
|
+
if (typeof byIndex?.model === "string") return byIndex.model;
|
|
203
|
+
}
|
|
204
|
+
if (entry.agent) {
|
|
205
|
+
const byAgent = results.find((result) => result.agent === entry.agent && typeof result.model === "string");
|
|
206
|
+
if (byAgent?.model) return byAgent.model;
|
|
207
|
+
}
|
|
208
|
+
const firstWithModel = results.find((result) => typeof result.model === "string");
|
|
209
|
+
return firstWithModel?.model;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function buildDelegationMessages(result: { messages?: unknown[]; finalOutput?: string }, fallbackText?: string): unknown[] {
|
|
213
|
+
if (Array.isArray(result.messages) && result.messages.length > 0) return result.messages;
|
|
214
|
+
const text = typeof result.finalOutput === "string" && result.finalOutput.trim().length > 0
|
|
215
|
+
? result.finalOutput.trim()
|
|
216
|
+
: fallbackText;
|
|
217
|
+
if (!text) return [];
|
|
218
|
+
return [{ role: "assistant", content: [{ type: "text", text }] }];
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function toDelegationUpdate(requestId: string, update: PromptTemplateBridgeResult): PromptTemplateDelegationUpdate | undefined {
|
|
222
|
+
const progress = update.details?.progress?.[0];
|
|
223
|
+
const taskProgress = update.details?.progress?.map((entry) => {
|
|
224
|
+
const lastOutput = entry.recentOutput?.[entry.recentOutput.length - 1];
|
|
225
|
+
const safeLastOutput =
|
|
226
|
+
typeof lastOutput === "string" && lastOutput.trim() && lastOutput !== "(running...)"
|
|
227
|
+
? lastOutput
|
|
228
|
+
: undefined;
|
|
229
|
+
return {
|
|
230
|
+
index: entry.index,
|
|
231
|
+
agent: entry.agent ?? "delegate",
|
|
232
|
+
status: entry.status,
|
|
233
|
+
currentTool: entry.currentTool,
|
|
234
|
+
currentToolArgs: entry.currentToolArgs,
|
|
235
|
+
recentOutput: safeLastOutput,
|
|
236
|
+
recentOutputLines: filterRecentOutput(entry.recentOutput),
|
|
237
|
+
recentTools: sanitizeRecentTools(entry.recentTools),
|
|
238
|
+
model: resolveProgressModel(update, entry),
|
|
239
|
+
toolCount: entry.toolCount,
|
|
240
|
+
durationMs: entry.durationMs,
|
|
241
|
+
tokens: entry.tokens,
|
|
242
|
+
};
|
|
243
|
+
});
|
|
244
|
+
if (!progress && (!taskProgress || taskProgress.length === 0)) return undefined;
|
|
245
|
+
const lastOutput = progress?.recentOutput?.[progress.recentOutput.length - 1];
|
|
246
|
+
const safeLastOutput =
|
|
247
|
+
typeof lastOutput === "string" && lastOutput.trim() && lastOutput !== "(running...)"
|
|
248
|
+
? lastOutput
|
|
249
|
+
: undefined;
|
|
250
|
+
return {
|
|
251
|
+
requestId,
|
|
252
|
+
currentTool: progress?.currentTool,
|
|
253
|
+
currentToolArgs: progress?.currentToolArgs,
|
|
254
|
+
recentOutput: safeLastOutput,
|
|
255
|
+
recentOutputLines: filterRecentOutput(progress?.recentOutput),
|
|
256
|
+
recentTools: sanitizeRecentTools(progress?.recentTools),
|
|
257
|
+
model: progress ? resolveProgressModel(update, progress) : undefined,
|
|
258
|
+
toolCount: progress?.toolCount,
|
|
259
|
+
durationMs: progress?.durationMs,
|
|
260
|
+
tokens: progress?.tokens,
|
|
261
|
+
taskProgress,
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
export function registerPromptTemplateDelegationBridge<Ctx extends { cwd?: string }>(
|
|
266
|
+
options: PromptTemplateBridgeOptions<Ctx>,
|
|
267
|
+
): {
|
|
268
|
+
cancelAll: () => void;
|
|
269
|
+
dispose: () => void;
|
|
270
|
+
} {
|
|
271
|
+
const controllers = new Map<string, AbortController>();
|
|
272
|
+
const pendingCancels = new Set<string>();
|
|
273
|
+
const subscriptions: Array<() => void> = [];
|
|
274
|
+
|
|
275
|
+
const subscribe = (event: string, handler: (data: unknown) => void): void => {
|
|
276
|
+
const unsubscribe = options.events.on(event, handler);
|
|
277
|
+
if (typeof unsubscribe === "function") subscriptions.push(unsubscribe);
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
subscribe(PROMPT_TEMPLATE_SUBAGENT_CANCEL_EVENT, (data) => {
|
|
281
|
+
if (!data || typeof data !== "object") return;
|
|
282
|
+
const requestId = (data as { requestId?: unknown }).requestId;
|
|
283
|
+
if (typeof requestId !== "string") return;
|
|
284
|
+
const controller = controllers.get(requestId);
|
|
285
|
+
if (controller) {
|
|
286
|
+
controller.abort();
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
pendingCancels.add(requestId);
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
subscribe(PROMPT_TEMPLATE_SUBAGENT_REQUEST_EVENT, async (data) => {
|
|
293
|
+
const request = parsePromptTemplateRequest(data);
|
|
294
|
+
if (!request) return;
|
|
295
|
+
|
|
296
|
+
const ctx = options.getContext();
|
|
297
|
+
if (!ctx) {
|
|
298
|
+
const response: PromptTemplateDelegationResponse = {
|
|
299
|
+
...request,
|
|
300
|
+
messages: [],
|
|
301
|
+
isError: true,
|
|
302
|
+
errorText: "No active extension context for delegated subagent execution.",
|
|
303
|
+
};
|
|
304
|
+
options.events.emit(PROMPT_TEMPLATE_SUBAGENT_RESPONSE_EVENT, response);
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const controller = new AbortController();
|
|
309
|
+
controllers.set(request.requestId, controller);
|
|
310
|
+
|
|
311
|
+
if (pendingCancels.delete(request.requestId)) {
|
|
312
|
+
controller.abort();
|
|
313
|
+
const response: PromptTemplateDelegationResponse = {
|
|
314
|
+
...request,
|
|
315
|
+
messages: [],
|
|
316
|
+
isError: true,
|
|
317
|
+
errorText: "Delegated prompt cancelled.",
|
|
318
|
+
};
|
|
319
|
+
options.events.emit(PROMPT_TEMPLATE_SUBAGENT_RESPONSE_EVENT, response);
|
|
320
|
+
controllers.delete(request.requestId);
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
options.events.emit(PROMPT_TEMPLATE_SUBAGENT_STARTED_EVENT, { requestId: request.requestId });
|
|
325
|
+
|
|
326
|
+
try {
|
|
327
|
+
const result = await options.execute(
|
|
328
|
+
request.requestId,
|
|
329
|
+
request,
|
|
330
|
+
controller.signal,
|
|
331
|
+
ctx,
|
|
332
|
+
(update) => {
|
|
333
|
+
const payload = toDelegationUpdate(request.requestId, update);
|
|
334
|
+
if (!payload) return;
|
|
335
|
+
options.events.emit(PROMPT_TEMPLATE_SUBAGENT_UPDATE_EVENT, payload);
|
|
336
|
+
},
|
|
337
|
+
);
|
|
338
|
+
const contentText = firstTextContent(result.content);
|
|
339
|
+
const messages = buildDelegationMessages(result.details?.results?.[0] ?? {}, contentText);
|
|
340
|
+
const parallelResults = request.tasks
|
|
341
|
+
? request.tasks.map<PromptTemplateDelegationParallelResult>((task, index) => {
|
|
342
|
+
const step = result.details?.results?.[index];
|
|
343
|
+
if (!step) {
|
|
344
|
+
return {
|
|
345
|
+
agent: task.agent,
|
|
346
|
+
messages: [],
|
|
347
|
+
isError: true,
|
|
348
|
+
errorText: "Missing result for delegated parallel task.",
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
const exitCode = typeof step.exitCode === "number" ? step.exitCode : undefined;
|
|
352
|
+
const errorText = step.error;
|
|
353
|
+
return {
|
|
354
|
+
agent: step.agent ?? task.agent,
|
|
355
|
+
messages: buildDelegationMessages(step),
|
|
356
|
+
isError: (exitCode !== undefined && exitCode !== 0) || !!errorText,
|
|
357
|
+
errorText: errorText || undefined,
|
|
358
|
+
};
|
|
359
|
+
})
|
|
360
|
+
: undefined;
|
|
361
|
+
const response: PromptTemplateDelegationResponse = {
|
|
362
|
+
...request,
|
|
363
|
+
messages,
|
|
364
|
+
...(parallelResults ? { parallelResults } : {}),
|
|
365
|
+
...(contentText ? { contentText } : {}),
|
|
366
|
+
isError: result.isError === true,
|
|
367
|
+
errorText: result.isError ? contentText : undefined,
|
|
368
|
+
};
|
|
369
|
+
options.events.emit(PROMPT_TEMPLATE_SUBAGENT_RESPONSE_EVENT, response);
|
|
370
|
+
} catch (error) {
|
|
371
|
+
const response: PromptTemplateDelegationResponse = {
|
|
372
|
+
...request,
|
|
373
|
+
messages: [],
|
|
374
|
+
isError: true,
|
|
375
|
+
errorText: error instanceof Error ? error.message : String(error),
|
|
376
|
+
};
|
|
377
|
+
options.events.emit(PROMPT_TEMPLATE_SUBAGENT_RESPONSE_EVENT, response);
|
|
378
|
+
} finally {
|
|
379
|
+
controllers.delete(request.requestId);
|
|
380
|
+
}
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
return {
|
|
384
|
+
cancelAll: () => {
|
|
385
|
+
for (const controller of controllers.values()) {
|
|
386
|
+
controller.abort();
|
|
387
|
+
}
|
|
388
|
+
controllers.clear();
|
|
389
|
+
pendingCancels.clear();
|
|
390
|
+
},
|
|
391
|
+
dispose: () => {
|
|
392
|
+
for (const unsubscribe of subscriptions) unsubscribe();
|
|
393
|
+
subscriptions.length = 0;
|
|
394
|
+
pendingCancels.clear();
|
|
395
|
+
},
|
|
396
|
+
};
|
|
397
|
+
}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import type { AgentToolResult } from "@earendil-works/pi-agent-core";
|
|
2
|
+
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
3
|
+
import type { SubagentParamsLike } from "../runs/foreground/subagent-executor.ts";
|
|
4
|
+
import {
|
|
5
|
+
SLASH_SUBAGENT_CANCEL_EVENT,
|
|
6
|
+
SLASH_SUBAGENT_REQUEST_EVENT,
|
|
7
|
+
SLASH_SUBAGENT_RESPONSE_EVENT,
|
|
8
|
+
SLASH_SUBAGENT_STARTED_EVENT,
|
|
9
|
+
SLASH_SUBAGENT_UPDATE_EVENT,
|
|
10
|
+
type Details,
|
|
11
|
+
} from "../shared/types.ts";
|
|
12
|
+
|
|
13
|
+
interface SlashSubagentRequest {
|
|
14
|
+
requestId: string;
|
|
15
|
+
params: SubagentParamsLike;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface SlashSubagentResponse {
|
|
19
|
+
requestId: string;
|
|
20
|
+
result: AgentToolResult<Details>;
|
|
21
|
+
isError: boolean;
|
|
22
|
+
errorText?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface SlashSubagentUpdate {
|
|
26
|
+
requestId: string;
|
|
27
|
+
progress?: Details["progress"];
|
|
28
|
+
currentTool?: string;
|
|
29
|
+
toolCount?: number;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface EventBus {
|
|
33
|
+
on(event: string, handler: (data: unknown) => void): (() => void) | void;
|
|
34
|
+
emit(event: string, data: unknown): void;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
interface SlashBridgeOptions {
|
|
38
|
+
events: EventBus;
|
|
39
|
+
getContext: () => ExtensionContext | null;
|
|
40
|
+
execute: (
|
|
41
|
+
id: string,
|
|
42
|
+
params: SubagentParamsLike,
|
|
43
|
+
signal: AbortSignal,
|
|
44
|
+
onUpdate: ((r: AgentToolResult<Details>) => void) | undefined,
|
|
45
|
+
ctx: ExtensionContext,
|
|
46
|
+
) => Promise<AgentToolResult<Details>>;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function registerSlashSubagentBridge(options: SlashBridgeOptions): {
|
|
50
|
+
cancelAll: () => void;
|
|
51
|
+
dispose: () => void;
|
|
52
|
+
} {
|
|
53
|
+
const controllers = new Map<string, AbortController>();
|
|
54
|
+
const pendingCancels = new Set<string>();
|
|
55
|
+
const subscriptions: Array<() => void> = [];
|
|
56
|
+
|
|
57
|
+
const subscribe = (event: string, handler: (data: unknown) => void): void => {
|
|
58
|
+
const unsubscribe = options.events.on(event, handler);
|
|
59
|
+
if (typeof unsubscribe === "function") subscriptions.push(unsubscribe);
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
subscribe(SLASH_SUBAGENT_CANCEL_EVENT, (data) => {
|
|
63
|
+
if (!data || typeof data !== "object") return;
|
|
64
|
+
const requestId = (data as { requestId?: unknown }).requestId;
|
|
65
|
+
if (typeof requestId !== "string") return;
|
|
66
|
+
const controller = controllers.get(requestId);
|
|
67
|
+
if (controller) {
|
|
68
|
+
controller.abort();
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
pendingCancels.add(requestId);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
subscribe(SLASH_SUBAGENT_REQUEST_EVENT, async (data) => {
|
|
75
|
+
if (!data || typeof data !== "object") return;
|
|
76
|
+
const request = data as Partial<SlashSubagentRequest>;
|
|
77
|
+
if (typeof request.requestId !== "string" || !request.params) return;
|
|
78
|
+
const { requestId, params } = request as SlashSubagentRequest;
|
|
79
|
+
|
|
80
|
+
const ctx = options.getContext();
|
|
81
|
+
if (!ctx) {
|
|
82
|
+
const response: SlashSubagentResponse = {
|
|
83
|
+
requestId,
|
|
84
|
+
result: {
|
|
85
|
+
content: [{ type: "text", text: "No active extension context for slash subagent execution." }],
|
|
86
|
+
details: { mode: "single" as const, results: [] },
|
|
87
|
+
},
|
|
88
|
+
isError: true,
|
|
89
|
+
errorText: "No active extension context.",
|
|
90
|
+
};
|
|
91
|
+
options.events.emit(SLASH_SUBAGENT_RESPONSE_EVENT, response);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const controller = new AbortController();
|
|
96
|
+
controllers.set(requestId, controller);
|
|
97
|
+
|
|
98
|
+
if (pendingCancels.delete(requestId)) {
|
|
99
|
+
controller.abort();
|
|
100
|
+
const response: SlashSubagentResponse = {
|
|
101
|
+
requestId,
|
|
102
|
+
result: {
|
|
103
|
+
content: [{ type: "text", text: "Cancelled." }],
|
|
104
|
+
details: { mode: "single" as const, results: [] },
|
|
105
|
+
},
|
|
106
|
+
isError: true,
|
|
107
|
+
errorText: "Cancelled before start.",
|
|
108
|
+
};
|
|
109
|
+
options.events.emit(SLASH_SUBAGENT_RESPONSE_EVENT, response);
|
|
110
|
+
controllers.delete(requestId);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
options.events.emit(SLASH_SUBAGENT_STARTED_EVENT, { requestId });
|
|
115
|
+
|
|
116
|
+
try {
|
|
117
|
+
const result = await options.execute(
|
|
118
|
+
requestId,
|
|
119
|
+
params,
|
|
120
|
+
controller.signal,
|
|
121
|
+
(update) => {
|
|
122
|
+
const progress = update.details?.progress;
|
|
123
|
+
const first = progress?.[0];
|
|
124
|
+
const payload: SlashSubagentUpdate = {
|
|
125
|
+
requestId,
|
|
126
|
+
progress,
|
|
127
|
+
currentTool: first?.currentTool,
|
|
128
|
+
toolCount: first?.toolCount,
|
|
129
|
+
};
|
|
130
|
+
options.events.emit(SLASH_SUBAGENT_UPDATE_EVENT, payload);
|
|
131
|
+
},
|
|
132
|
+
ctx,
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
const response: SlashSubagentResponse = {
|
|
136
|
+
requestId,
|
|
137
|
+
result,
|
|
138
|
+
isError: (result as { isError?: boolean }).isError === true,
|
|
139
|
+
errorText: (result as { isError?: boolean }).isError
|
|
140
|
+
? result.content.find((c) => c.type === "text")?.text
|
|
141
|
+
: undefined,
|
|
142
|
+
};
|
|
143
|
+
options.events.emit(SLASH_SUBAGENT_RESPONSE_EVENT, response);
|
|
144
|
+
} catch (error) {
|
|
145
|
+
const response: SlashSubagentResponse = {
|
|
146
|
+
requestId,
|
|
147
|
+
result: {
|
|
148
|
+
content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }],
|
|
149
|
+
details: { mode: "single" as const, results: [] },
|
|
150
|
+
},
|
|
151
|
+
isError: true,
|
|
152
|
+
errorText: error instanceof Error ? error.message : String(error),
|
|
153
|
+
};
|
|
154
|
+
options.events.emit(SLASH_SUBAGENT_RESPONSE_EVENT, response);
|
|
155
|
+
} finally {
|
|
156
|
+
controllers.delete(requestId);
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
cancelAll: () => {
|
|
162
|
+
for (const controller of controllers.values()) {
|
|
163
|
+
controller.abort();
|
|
164
|
+
}
|
|
165
|
+
controllers.clear();
|
|
166
|
+
pendingCancels.clear();
|
|
167
|
+
},
|
|
168
|
+
dispose: () => {
|
|
169
|
+
for (const unsubscribe of subscriptions) unsubscribe();
|
|
170
|
+
subscriptions.length = 0;
|
|
171
|
+
pendingCancels.clear();
|
|
172
|
+
},
|
|
173
|
+
};
|
|
174
|
+
}
|