pi-subagents 0.42.1 → 0.44.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 +50 -0
- package/README.md +3 -5
- package/package.json +1 -1
- package/skills/pi-subagents/SKILL.md +2 -2
- package/skills/pi-subagents/references/constraints-and-recipes.md +35 -32
- package/skills/pi-subagents/references/execution-controls.md +30 -44
- package/skills/pi-subagents/references/management-authoring-rpc.md +20 -3
- package/skills/pi-subagents/references/prompting-and-roles.md +16 -49
- package/src/agents/agent-refinements.ts +624 -0
- package/src/agents/agents.ts +0 -2
- package/src/agents/proactive-skills.ts +1 -1
- package/src/api/delegation.ts +1 -2
- package/src/extension/config.ts +28 -7
- package/src/extension/control-notices.ts +2 -2
- package/src/extension/fanout-child.ts +46 -22
- package/src/extension/index.ts +32 -13
- package/src/extension/public-execution.ts +71 -0
- package/src/extension/rpc.ts +7 -8
- package/src/extension/schemas.ts +14 -14
- package/src/extension/tool-description.ts +14 -14
- package/src/missions/actions.ts +47 -11
- package/src/missions/goal-driver.ts +162 -0
- package/src/missions/lifecycle.ts +44 -12
- package/src/missions/store.ts +68 -3
- package/src/missions/types.ts +25 -3
- package/src/missions/workflow-state.ts +250 -0
- package/src/profiles/profiles.ts +1 -3
- package/src/runs/background/async-execution.ts +5 -1
- package/src/runs/background/async-job-tracker.ts +2 -17
- package/src/runs/background/async-status.ts +2 -0
- package/src/runs/background/control-channel.ts +50 -6
- package/src/runs/background/retained-children.ts +68 -0
- package/src/runs/background/run-id-resolver.ts +71 -0
- package/src/runs/background/run-status.ts +3 -2
- package/src/runs/background/scheduled-runs.ts +17 -13
- package/src/runs/background/steering.ts +7 -5
- package/src/runs/background/subagent-runner.ts +50 -23
- package/src/runs/foreground/async-steering-action.ts +36 -10
- package/src/runs/foreground/chain-clarify.ts +7 -3
- package/src/runs/foreground/execution.ts +29 -19
- package/src/runs/foreground/subagent-executor.ts +282 -77
- package/src/runs/shared/acceptance.ts +154 -9
- package/src/runs/shared/subagent-prompt-runtime.ts +97 -25
- package/src/shared/settings.ts +10 -6
- package/src/shared/types.ts +22 -2
- package/src/slash/delegation-adapters.ts +0 -13
- package/src/slash/prompt-template-bridge.ts +14 -12
- package/src/slash/prompt-workflows.ts +10 -4
- package/src/slash/slash-bridge.ts +8 -6
- package/src/slash/slash-commands.ts +24 -9
- package/src/slash/slash-live-state.ts +4 -2
- package/src/tui/fleet-status.ts +8 -4
- package/src/tui/fleet.ts +15 -5
- package/src/tui/render.ts +10 -29
- package/src/workflows/scripted-workflow.ts +91 -6
- package/agents/context-builder.md +0 -46
- package/agents/planner.md +0 -56
- package/prompts/parallel-context-build.md +0 -55
- package/prompts/parallel-handoff-plan.md +0 -61
|
@@ -213,13 +213,19 @@ function workflowParams(workflow: PromptWorkflow, args: string[], runtime: Retur
|
|
|
213
213
|
return {
|
|
214
214
|
agent: runtime.agentOverride ?? workflow.agent,
|
|
215
215
|
task,
|
|
216
|
-
clarify: false,
|
|
217
216
|
agentScope: "both",
|
|
218
217
|
...(context ? { context } : {}),
|
|
219
218
|
...(workflow.model ? { model: workflow.model } : {}),
|
|
220
219
|
...(workflow.skill !== undefined ? { skill: workflow.skill } : {}),
|
|
221
220
|
...(workflow.cwd ? { cwd: workflow.cwd } : {}),
|
|
222
|
-
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function promptWorkflowExecutionParams(workflows: PromptWorkflow[], args: string[], runtime: ReturnType<typeof parseRuntimeOptions>): SubagentParamsLike {
|
|
225
|
+
return {
|
|
226
|
+
workflowScript: promptWorkflowScript(workflows, args, runtime),
|
|
227
|
+
agentScope: "both",
|
|
228
|
+
async: runtime.bg ? true : false,
|
|
223
229
|
};
|
|
224
230
|
}
|
|
225
231
|
|
|
@@ -281,10 +287,10 @@ export function registerPromptWorkflowCommands(input: {
|
|
|
281
287
|
if (!step) throw new Error(`Unknown prompt workflow in chain '${workflow.name}': ${stepName}`);
|
|
282
288
|
return step;
|
|
283
289
|
});
|
|
284
|
-
await run(
|
|
290
|
+
await run(promptWorkflowExecutionParams(chain, runtime.args, runtime), ctx);
|
|
285
291
|
return;
|
|
286
292
|
}
|
|
287
|
-
await run(
|
|
293
|
+
await run(promptWorkflowExecutionParams([workflow], runtime.args, runtime), ctx);
|
|
288
294
|
} catch (error) {
|
|
289
295
|
ctx.ui.notify(error instanceof Error ? error.message : String(error), "error");
|
|
290
296
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { AgentToolResult } from "@earendil-works/pi-agent-core";
|
|
2
2
|
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
3
|
+
import { normalizePublicSubagentExecution } from "../extension/public-execution.ts";
|
|
3
4
|
import type { SubagentParamsLike } from "../runs/foreground/subagent-executor.ts";
|
|
4
5
|
import {
|
|
5
6
|
SLASH_SUBAGENT_CANCEL_EVENT,
|
|
@@ -77,21 +78,22 @@ export function registerSlashSubagentBridge(options: SlashBridgeOptions): {
|
|
|
77
78
|
if (!data || typeof data !== "object") return;
|
|
78
79
|
const request = data as Partial<SlashSubagentRequest>;
|
|
79
80
|
if (typeof request.requestId !== "string" || !request.params) return;
|
|
80
|
-
const { requestId
|
|
81
|
-
|
|
82
|
-
|
|
81
|
+
const { requestId } = request as SlashSubagentRequest;
|
|
82
|
+
const normalized = normalizePublicSubagentExecution((request as SlashSubagentRequest).params);
|
|
83
|
+
if (!normalized.ok) {
|
|
83
84
|
options.events.emit(SLASH_SUBAGENT_RESPONSE_EVENT, {
|
|
84
85
|
requestId,
|
|
85
86
|
result: {
|
|
86
|
-
content: [{ type: "text", text:
|
|
87
|
+
content: [{ type: "text", text: normalized.error }],
|
|
87
88
|
isError: true,
|
|
88
|
-
details: { mode:
|
|
89
|
+
details: { mode: normalized.mode, results: [] },
|
|
89
90
|
},
|
|
90
91
|
isError: true,
|
|
91
|
-
errorText:
|
|
92
|
+
errorText: normalized.error,
|
|
92
93
|
} satisfies SlashSubagentResponse);
|
|
93
94
|
return;
|
|
94
95
|
}
|
|
96
|
+
const params = normalized.params;
|
|
95
97
|
|
|
96
98
|
const ctx = request.ctx ?? options.getContext();
|
|
97
99
|
if (!ctx) {
|
|
@@ -621,6 +621,9 @@ function launchSlashSubagent(
|
|
|
621
621
|
void runSlashSubagent(pi, ctx, params);
|
|
622
622
|
}
|
|
623
623
|
|
|
624
|
+
function slashRunWorkflowScript(key: string, child: Record<string, unknown>): string {
|
|
625
|
+
return `return runs.run(${JSON.stringify(key)}, ${JSON.stringify(child)})`;
|
|
626
|
+
}
|
|
624
627
|
|
|
625
628
|
export function registerSlashCommands(
|
|
626
629
|
pi: ExtensionAPI,
|
|
@@ -653,7 +656,7 @@ export function registerSlashCommands(
|
|
|
653
656
|
});
|
|
654
657
|
|
|
655
658
|
pi.registerCommand("run", {
|
|
656
|
-
description: "Run
|
|
659
|
+
description: "Run one subagent through workflowScript: /run agent[output=file] [task] [--bg] [--fork]",
|
|
657
660
|
getArgumentCompletions: makeAgentCompletions(state),
|
|
658
661
|
handler: async (args, ctx) => {
|
|
659
662
|
const { args: cleanedArgs, bg, fork } = extractExecutionFlags(args);
|
|
@@ -671,14 +674,13 @@ export function registerSlashCommands(
|
|
|
671
674
|
if (inline.reads && Array.isArray(inline.reads) && inline.reads.length > 0) {
|
|
672
675
|
finalTask = `[Read from: ${inline.reads.join(", ")}]\n\n${finalTask}`;
|
|
673
676
|
}
|
|
674
|
-
const
|
|
675
|
-
if (inline.output !== undefined)
|
|
676
|
-
if (inline.outputMode !== undefined)
|
|
677
|
-
if (inline.skill !== undefined)
|
|
678
|
-
if (inline.model)
|
|
679
|
-
if (
|
|
680
|
-
|
|
681
|
-
launchSlashSubagent(pi, ctx, params);
|
|
677
|
+
const child: Record<string, unknown> = { agent: agentName, task: finalTask, agentScope: "both" };
|
|
678
|
+
if (inline.output !== undefined) child.output = inline.output;
|
|
679
|
+
if (inline.outputMode !== undefined) child.outputMode = inline.outputMode;
|
|
680
|
+
if (inline.skill !== undefined) child.skill = inline.skill;
|
|
681
|
+
if (inline.model) child.model = inline.model;
|
|
682
|
+
if (fork) child.context = "fork";
|
|
683
|
+
launchSlashSubagent(pi, ctx, { workflowScript: slashRunWorkflowScript("run", child), async: bg ? true : false });
|
|
682
684
|
},
|
|
683
685
|
});
|
|
684
686
|
|
|
@@ -696,6 +698,19 @@ export function registerSlashCommands(
|
|
|
696
698
|
},
|
|
697
699
|
});
|
|
698
700
|
|
|
701
|
+
pi.registerCommand("subagents-refine", {
|
|
702
|
+
description: "Generate a bounded project-local refinement overlay for one subagent",
|
|
703
|
+
getArgumentCompletions: makeAgentCompletions(state),
|
|
704
|
+
handler: async (args, ctx) => {
|
|
705
|
+
const parts = args.trim().split(/\s+/).filter(Boolean);
|
|
706
|
+
if (parts.length !== 1) {
|
|
707
|
+
ctx.ui.notify("Usage: /subagents-refine <agent>", "error");
|
|
708
|
+
return;
|
|
709
|
+
}
|
|
710
|
+
await runSlashSubagent(pi, ctx, { action: "refine", agent: parts[0] });
|
|
711
|
+
},
|
|
712
|
+
});
|
|
713
|
+
|
|
699
714
|
pi.registerCommand("subagents-fleet", {
|
|
700
715
|
description: "Open the live subagent fleet inspector",
|
|
701
716
|
handler: async (_args, ctx) => showFleet(ctx),
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { AgentToolResult } from "@earendil-works/pi-agent-core";
|
|
2
2
|
import type { Message } from "@earendil-works/pi-ai";
|
|
3
3
|
import type { SubagentParamsLike } from "../runs/foreground/subagent-executor.ts";
|
|
4
|
+
import { previewSimpleWorkflowRun } from "../workflows/scripted-workflow.ts";
|
|
4
5
|
import type { SlashSubagentResponse, SlashSubagentUpdate } from "./slash-bridge.ts";
|
|
5
6
|
import { type Details, type SingleResult, type Usage, SLASH_RESULT_TYPE } from "../shared/types.ts";
|
|
6
7
|
|
|
@@ -156,8 +157,9 @@ function buildChainInitialResult(params: SubagentParamsLike): AgentToolResult<De
|
|
|
156
157
|
}
|
|
157
158
|
|
|
158
159
|
function buildSingleInitialResult(params: SubagentParamsLike): AgentToolResult<Details> {
|
|
159
|
-
const
|
|
160
|
-
const
|
|
160
|
+
const preview = previewSimpleWorkflowRun(params.workflowScript) ?? {};
|
|
161
|
+
const agent = params.agent ?? preview.agent ?? "subagent";
|
|
162
|
+
const task = params.task ?? preview.task ?? "";
|
|
161
163
|
return {
|
|
162
164
|
content: [{ type: "text", text: task }],
|
|
163
165
|
details: {
|
package/src/tui/fleet-status.ts
CHANGED
|
@@ -100,21 +100,25 @@ function nestedFleetRows(children: NestedRunSummary[] | undefined): FleetNestedR
|
|
|
100
100
|
const steps = (child.mode === "parallel" || child.mode === "chain") ? child.steps ?? [] : [];
|
|
101
101
|
if (steps.length > 0) {
|
|
102
102
|
for (const step of steps) {
|
|
103
|
+
const modelThinking = formatModelThinking(step.model, step.thinking) || undefined;
|
|
104
|
+
const activity = nestedActivity(step);
|
|
103
105
|
rows.push({
|
|
104
106
|
name: step.agent,
|
|
105
107
|
state: step.status,
|
|
106
|
-
...(
|
|
107
|
-
...(
|
|
108
|
+
...(modelThinking ? { modelThinking } : {}),
|
|
109
|
+
...(activity ? { activity } : {}),
|
|
108
110
|
...(step.startedAt !== undefined ? { startedAt: step.startedAt } : {}),
|
|
109
111
|
});
|
|
110
112
|
}
|
|
111
113
|
continue;
|
|
112
114
|
}
|
|
115
|
+
const modelThinking = formatModelThinking(child.model, child.thinking) || undefined;
|
|
116
|
+
const activity = nestedActivity(child);
|
|
113
117
|
rows.push({
|
|
114
118
|
name: nestedRunLabel(child),
|
|
115
119
|
state: child.state,
|
|
116
|
-
...(
|
|
117
|
-
...(
|
|
120
|
+
...(modelThinking ? { modelThinking } : {}),
|
|
121
|
+
...(activity ? { activity } : {}),
|
|
118
122
|
...(child.startedAt !== undefined ? { startedAt: child.startedAt } : {}),
|
|
119
123
|
});
|
|
120
124
|
}
|
package/src/tui/fleet.ts
CHANGED
|
@@ -10,6 +10,7 @@ import { readStatus } from "../shared/utils.ts";
|
|
|
10
10
|
import { formatAsyncRunTranscript } from "../runs/background/fleet-view.ts";
|
|
11
11
|
import { listAsyncRuns, type AsyncRunSummary } from "../runs/background/async-status.ts";
|
|
12
12
|
import { steerAsyncRun } from "../runs/foreground/async-steering-action.ts";
|
|
13
|
+
import type { SteerDeliveryMode } from "../runs/background/control-channel.ts";
|
|
13
14
|
import { stopAsyncRun } from "../runs/foreground/async-stop-action.ts";
|
|
14
15
|
import { contextModeBadge, contextModeLabel } from "../runs/shared/context-mode.ts";
|
|
15
16
|
import { FLEET_STATUS_WIDGET_KEY } from "./fleet-status.ts";
|
|
@@ -45,7 +46,7 @@ export interface FleetActionResult {
|
|
|
45
46
|
}
|
|
46
47
|
|
|
47
48
|
export interface FleetActionHandlers {
|
|
48
|
-
steer(input: { runId: string; asyncDir: string; index?: number; message: string }): Promise<FleetActionResult>;
|
|
49
|
+
steer(input: { runId: string; asyncDir: string; index?: number; message: string; mode: SteerDeliveryMode }): Promise<FleetActionResult>;
|
|
49
50
|
stop(input: { runId: string; asyncDir: string; index?: number }): Promise<FleetActionResult> | FleetActionResult;
|
|
50
51
|
inspect?(input: { runId: string; asyncDir: string; index?: number }): Promise<FleetActionResult>;
|
|
51
52
|
}
|
|
@@ -484,6 +485,7 @@ export class SubagentFleetComponent implements Component {
|
|
|
484
485
|
private expandedTools = false;
|
|
485
486
|
private actionNotice: FleetActionResult | undefined;
|
|
486
487
|
private steerDraft: string | undefined;
|
|
488
|
+
private steerMode: SteerDeliveryMode = "steer";
|
|
487
489
|
private stopConfirming = false;
|
|
488
490
|
private actionBusy = false;
|
|
489
491
|
private transcriptCache: FleetTranscriptCache | undefined;
|
|
@@ -538,6 +540,7 @@ export class SubagentFleetComponent implements Component {
|
|
|
538
540
|
|
|
539
541
|
private resetActionInput(): void {
|
|
540
542
|
this.steerDraft = undefined;
|
|
543
|
+
this.steerMode = "steer";
|
|
541
544
|
this.stopConfirming = false;
|
|
542
545
|
}
|
|
543
546
|
|
|
@@ -553,8 +556,8 @@ export class SubagentFleetComponent implements Component {
|
|
|
553
556
|
const lines: string[] = [];
|
|
554
557
|
if (this.actionBusy) lines.push(this.theme.fg("accent", "Action pending..."));
|
|
555
558
|
if (this.steerDraft !== undefined) {
|
|
556
|
-
lines.push(this.theme.fg("accent", `Steer message: ${this.steerDraft}${this.theme.fg("dim", "▌")}`));
|
|
557
|
-
lines.push(this.theme.fg("dim", "Enter sends · Esc cancels · Backspace edits"));
|
|
559
|
+
lines.push(this.theme.fg("accent", `Steer message (${this.steerMode}): ${this.steerDraft}${this.theme.fg("dim", "▌")}`));
|
|
560
|
+
lines.push(this.theme.fg("dim", "Enter sends · Tab changes mode · Esc cancels · Backspace edits"));
|
|
558
561
|
} else if (this.stopConfirming) {
|
|
559
562
|
const selected = this.snapshot.items[this.selected];
|
|
560
563
|
lines.push(this.theme.fg("warning", `Confirm stop for async run ${selected?.runId ?? "selected run"}?`));
|
|
@@ -618,7 +621,13 @@ export class SubagentFleetComponent implements Component {
|
|
|
618
621
|
this.setActionNotice({ text: "reason" in target ? target.reason : "Fleet controls are unavailable in this context.", isError: true });
|
|
619
622
|
return;
|
|
620
623
|
}
|
|
621
|
-
this.runAction(() => this.options.actions!.steer({ runId: target.item.runId, asyncDir: target.item.run.asyncDir, ...(target.item.index !== undefined ? { index: target.item.index } : {}), message }));
|
|
624
|
+
this.runAction(() => this.options.actions!.steer({ runId: target.item.runId, asyncDir: target.item.run.asyncDir, ...(target.item.index !== undefined ? { index: target.item.index } : {}), message, mode: this.steerMode }));
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
627
|
+
if (matchesKey(data, "tab") || data === "\t") {
|
|
628
|
+
const modes: SteerDeliveryMode[] = ["steer", "follow_up", "auto"];
|
|
629
|
+
this.steerMode = modes[(modes.indexOf(this.steerMode) + 1) % modes.length]!;
|
|
630
|
+
this.tui.requestRender();
|
|
622
631
|
return;
|
|
623
632
|
}
|
|
624
633
|
if (matchesKey(data, "backspace") || data === "\x7f") {
|
|
@@ -836,11 +845,12 @@ export async function openSubagentFleet(ctx: ExtensionContext, state: SubagentSt
|
|
|
836
845
|
state.fleetInspectorOpen = true;
|
|
837
846
|
if (typeof ctx.ui.setWidget === "function") ctx.ui.setWidget(FLEET_STATUS_WIDGET_KEY, undefined);
|
|
838
847
|
const actions = options.actions ?? {
|
|
839
|
-
steer: async (input: { runId: string; asyncDir: string; index?: number; message: string }) => firstToolResultText(await steerAsyncRun({
|
|
848
|
+
steer: async (input: { runId: string; asyncDir: string; index?: number; message: string; mode: SteerDeliveryMode }) => firstToolResultText(await steerAsyncRun({
|
|
840
849
|
state,
|
|
841
850
|
runId: input.runId,
|
|
842
851
|
...(input.index !== undefined ? { index: input.index } : {}),
|
|
843
852
|
message: input.message,
|
|
853
|
+
mode: input.mode,
|
|
844
854
|
location: { asyncDir: input.asyncDir, resolvedId: input.runId } as Parameters<typeof steerAsyncRun>[0]["location"],
|
|
845
855
|
}), `Failed to steer async run ${input.runId}.`),
|
|
846
856
|
stop: (input: { runId: string; asyncDir: string; index?: number }) => firstToolResultText(stopAsyncRun(state, input.runId, undefined, { asyncDir: input.asyncDir, resolvedId: input.runId }), `Failed to stop async run ${input.runId}.`),
|
package/src/tui/render.ts
CHANGED
|
@@ -53,6 +53,7 @@ const ansiStylePattern = /\x1b\[[0-9;]*m/y;
|
|
|
53
53
|
* Uses Intl.Segmenter for proper Unicode/emoji handling (not char-by-char).
|
|
54
54
|
*/
|
|
55
55
|
export function truncLine(text: string, maxWidth: number): string {
|
|
56
|
+
if (maxWidth <= 0) return "";
|
|
56
57
|
if (visibleWidth(text) <= maxWidth) return text;
|
|
57
58
|
|
|
58
59
|
const targetWidth = maxWidth - 1;
|
|
@@ -83,7 +84,7 @@ export function truncLine(text: string, maxWidth: number): string {
|
|
|
83
84
|
const textPortion = text.slice(i, end);
|
|
84
85
|
for (const seg of segmenter.segment(textPortion)) {
|
|
85
86
|
const grapheme = seg.segment;
|
|
86
|
-
const graphemeWidth = visibleWidth(grapheme);
|
|
87
|
+
const graphemeWidth = grapheme === "\x1b" ? 0 : visibleWidth(grapheme);
|
|
87
88
|
|
|
88
89
|
if (currentWidth + graphemeWidth > targetWidth) {
|
|
89
90
|
return result + activeStyles.join("") + "…";
|
|
@@ -111,6 +112,7 @@ function wrapPlainText(text: string, maxWidth: number): string[] {
|
|
|
111
112
|
for (const seg of segmenter.segment(rawLine)) {
|
|
112
113
|
const grapheme = seg.segment;
|
|
113
114
|
const graphemeWidth = visibleWidth(grapheme);
|
|
115
|
+
if (graphemeWidth > maxWidth) continue;
|
|
114
116
|
if (currentWidth > 0 && currentWidth + graphemeWidth > maxWidth) {
|
|
115
117
|
lines.push(current);
|
|
116
118
|
current = grapheme;
|
|
@@ -144,24 +146,11 @@ function runningGlyph(seed?: number): string {
|
|
|
144
146
|
return RUNNING_FRAMES[Math.abs(seed) % RUNNING_FRAMES.length]!;
|
|
145
147
|
}
|
|
146
148
|
|
|
147
|
-
const WIDGET_ANIMATION_FRAME_MS = 500;
|
|
148
|
-
let widgetRequestRender: (() => void) | undefined;
|
|
149
|
-
|
|
150
149
|
function animatedSeed(seed: number | undefined, frame: number | undefined): number | undefined {
|
|
151
150
|
if (frame === undefined) return seed;
|
|
152
151
|
return (seed ?? 0) + frame;
|
|
153
152
|
}
|
|
154
153
|
|
|
155
|
-
function widgetAnimationFrame(): number {
|
|
156
|
-
return Math.floor(Date.now() / WIDGET_ANIMATION_FRAME_MS);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
export function requestWidgetRender(): boolean {
|
|
160
|
-
if (!widgetRequestRender) return false;
|
|
161
|
-
widgetRequestRender();
|
|
162
|
-
return true;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
154
|
function progressRunningSeed(progress: ProgressSeedSource | undefined): number | undefined {
|
|
166
155
|
if (!progress) return undefined;
|
|
167
156
|
return runningSeed(
|
|
@@ -1347,26 +1336,19 @@ function fitAdaptiveWidgetLines(jobs: AsyncJobState[], lines: string[], theme: T
|
|
|
1347
1336
|
return rendered.lines;
|
|
1348
1337
|
}
|
|
1349
1338
|
|
|
1350
|
-
function buildWidgetComponent(jobs: AsyncJobState[], expanded: boolean): (
|
|
1351
|
-
return (
|
|
1352
|
-
const requestRender = () => tui.requestRender();
|
|
1353
|
-
widgetRequestRender = requestRender;
|
|
1339
|
+
function buildWidgetComponent(jobs: AsyncJobState[], expanded: boolean): (_tui: unknown, theme: Theme) => Component {
|
|
1340
|
+
return (_tui, theme) => {
|
|
1354
1341
|
const container = new Container();
|
|
1355
1342
|
container.render = (renderWidth: number): string[] => {
|
|
1356
1343
|
const width = Math.max(0, renderWidth - 2);
|
|
1357
|
-
const frame = widgetAnimationFrame();
|
|
1358
1344
|
const lines = expanded
|
|
1359
|
-
? buildWidgetLines(jobs, theme, width, true
|
|
1345
|
+
? buildWidgetLines(jobs, theme, width, true)
|
|
1360
1346
|
: jobs.length === 1
|
|
1361
|
-
? compactSingleWidgetLines(jobs[0]!, theme, width
|
|
1362
|
-
: buildWidgetLines(jobs, theme, width, false
|
|
1363
|
-
return fitAdaptiveWidgetLines(jobs, lines, theme, width, expanded
|
|
1364
|
-
};
|
|
1365
|
-
const component = container as Component & { dispose?(): void };
|
|
1366
|
-
component.dispose = () => {
|
|
1367
|
-
if (widgetRequestRender === requestRender) widgetRequestRender = undefined;
|
|
1347
|
+
? compactSingleWidgetLines(jobs[0]!, theme, width)
|
|
1348
|
+
: buildWidgetLines(jobs, theme, width, false);
|
|
1349
|
+
return fitAdaptiveWidgetLines(jobs, lines, theme, width, expanded).map((line) => paddedWidgetLine(line, renderWidth));
|
|
1368
1350
|
};
|
|
1369
|
-
return
|
|
1351
|
+
return container;
|
|
1370
1352
|
};
|
|
1371
1353
|
}
|
|
1372
1354
|
|
|
@@ -1446,7 +1428,6 @@ export function buildWidgetLines(jobs: AsyncJobState[], theme: Theme, width = ge
|
|
|
1446
1428
|
export function renderWidget(ctx: ExtensionContext, jobs: AsyncJobState[]): void {
|
|
1447
1429
|
if (jobs.length === 0) {
|
|
1448
1430
|
resetWidgetLayoutSession();
|
|
1449
|
-
widgetRequestRender = undefined;
|
|
1450
1431
|
if (ctx.hasUI) ctx.ui.setWidget(WIDGET_KEY, undefined);
|
|
1451
1432
|
return;
|
|
1452
1433
|
}
|
|
@@ -37,11 +37,17 @@ const runFingerprints = new Map();
|
|
|
37
37
|
function validateRunCall(key, params, label, fingerprints) {
|
|
38
38
|
if (typeof key !== "string" || !runKeyPattern.test(key)) throw new Error(label + " has an invalid key.");
|
|
39
39
|
if (!params || typeof params !== "object" || Array.isArray(params)) throw new Error(label + " requires a params object.");
|
|
40
|
-
if (Object.prototype.hasOwnProperty.call(params, "action") || Object.prototype.hasOwnProperty.call(params, "workflowScript") || Object.prototype.hasOwnProperty.call(params, "tasks") || Object.prototype.hasOwnProperty.call(params, "chain") || Object.prototype.hasOwnProperty.call(params, "concurrency") || Object.prototype.hasOwnProperty.call(params, "chainDir")) {
|
|
40
|
+
if (Object.prototype.hasOwnProperty.call(params, "action") || Object.prototype.hasOwnProperty.call(params, "workflowScript") || Object.prototype.hasOwnProperty.call(params, "tasks") || Object.prototype.hasOwnProperty.call(params, "chain") || Object.prototype.hasOwnProperty.call(params, "parallel") || Object.prototype.hasOwnProperty.call(params, "concurrency") || Object.prototype.hasOwnProperty.call(params, "chainDir")) {
|
|
41
41
|
const hint = label === "runs.run" ? "; use runs.all(...) and JavaScript control flow for orchestration." : ".";
|
|
42
42
|
throw new Error(label + " accepts one child via { agent, task } and execution controls only" + hint);
|
|
43
43
|
}
|
|
44
44
|
if (params.worktree !== undefined && typeof params.worktree !== "boolean") throw new Error(label + " worktree must be true or false.");
|
|
45
|
+
if (params.gate !== undefined && (typeof params.gate !== "string" || !params.gate.trim())) throw new Error(label + " gate must be a non-empty command string.");
|
|
46
|
+
if (params.gate !== undefined && params.acceptance !== undefined) throw new Error(label + " gate cannot be combined with acceptance; use one gate command or acceptance.verify.");
|
|
47
|
+
if (params.gate !== undefined && params.resume !== undefined) throw new Error(label + " gate is not supported with retained resume.");
|
|
48
|
+
if (params.resume !== undefined && (typeof params.resume !== "string" || !params.resume.trim())) throw new Error(label + " resume must be a non-empty retained run id.");
|
|
49
|
+
if (params.resume !== undefined && params.agent !== undefined) throw new Error(label + " resume and agent are mutually exclusive.");
|
|
50
|
+
if (params.resume !== undefined && (typeof params.task !== "string" || !params.task.trim())) throw new Error(label + " resume requires a non-empty task follow-up.");
|
|
45
51
|
assertJsonValue(params, label + " params");
|
|
46
52
|
const fingerprint = stableRunJson(params);
|
|
47
53
|
const existing = fingerprints.get(key);
|
|
@@ -77,6 +83,20 @@ const runs = Object.freeze({
|
|
|
77
83
|
},
|
|
78
84
|
});
|
|
79
85
|
|
|
86
|
+
function validateStateKey(key) {
|
|
87
|
+
if (typeof key !== "string" || !runKeyPattern.test(key)) throw new Error("state key must be 1-128 characters using letters, numbers, '.', '_' or '-', and start with a letter or number.");
|
|
88
|
+
return key;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const state = Object.freeze({
|
|
92
|
+
get(key) { return hostCall("state.get", { key: validateStateKey(key) }); },
|
|
93
|
+
set(key, value) {
|
|
94
|
+
const validKey = validateStateKey(key);
|
|
95
|
+
assertJsonValue(value, "state.set('" + validKey + "') value");
|
|
96
|
+
return hostCall("state.set", { key: validKey, value });
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
|
|
80
100
|
let contextObjectPrototype;
|
|
81
101
|
|
|
82
102
|
const capturedConsole = Object.freeze(Object.fromEntries(
|
|
@@ -120,6 +140,7 @@ parentPort.on("message", async (message) => {
|
|
|
120
140
|
if (message.type !== "start") return;
|
|
121
141
|
try {
|
|
122
142
|
const sandbox = { runs, emit(value) { assertJsonValue(value); parentPort.postMessage({ type: "emit", value }); }, console: capturedConsole };
|
|
143
|
+
if (message.stateEnabled) sandbox.state = state;
|
|
123
144
|
const context = vm.createContext(sandbox, { codeGeneration: { strings: false, wasm: false } });
|
|
124
145
|
contextObjectPrototype = vm.runInContext("Object.prototype", context);
|
|
125
146
|
const compiled = new vm.Script("(async () => {\n" + message.script + "\n})()", { filename: "workflow-script.js" });
|
|
@@ -179,6 +200,10 @@ export interface RunWorkflowScriptOptions {
|
|
|
179
200
|
signal?: AbortSignal;
|
|
180
201
|
launch: (key: string, params: Record<string, unknown>, signal: AbortSignal) => Promise<WorkflowScriptChildResult>;
|
|
181
202
|
status: (keyOrRunId: string, signal: AbortSignal) => Promise<WorkflowScriptChildResult>;
|
|
203
|
+
state?: {
|
|
204
|
+
get: (key: string) => unknown | Promise<unknown>;
|
|
205
|
+
set: (key: string, value: unknown) => void | Promise<void>;
|
|
206
|
+
};
|
|
182
207
|
onTrace?: (trace: WorkflowScriptTraceEntry[]) => void;
|
|
183
208
|
onEmit?: (emits: unknown[]) => void;
|
|
184
209
|
}
|
|
@@ -239,15 +264,39 @@ export function formatWorkflowJsonPreview(value: unknown, maxLength: number): st
|
|
|
239
264
|
}
|
|
240
265
|
}
|
|
241
266
|
|
|
267
|
+
export interface SimpleWorkflowRunPreview {
|
|
268
|
+
agent?: string;
|
|
269
|
+
task?: string;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/** Display-only preview for the exact simple `return runs.run(key, {...})` form. */
|
|
273
|
+
export function previewSimpleWorkflowRun(script: string | undefined): SimpleWorkflowRunPreview | undefined {
|
|
274
|
+
const body = script?.match(/^\s*return\s+(?:await\s+)?runs\.run\s*\(\s*(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|`[^`$\\]*`)\s*,\s*\{([\s\S]*)\}\s*\)\s*;?\s*$/)?.[1];
|
|
275
|
+
if (body === undefined) return undefined;
|
|
276
|
+
const readProperty = (name: "agent" | "task"): string | undefined => {
|
|
277
|
+
const match = body.match(new RegExp(`(?:^|,)\\s*(?:${name}|["']${name}["'])\\s*:\\s*("(?:\\\\.|[^"\\\\])*"|'(?:\\\\.|[^'\\\\])*'|\u0060[^\u0060$\\\\]*\u0060)`));
|
|
278
|
+
if (!match?.[1]) return undefined;
|
|
279
|
+
const literal = match[1];
|
|
280
|
+
if (literal.startsWith('"')) {
|
|
281
|
+
try { return JSON.parse(literal) as string; } catch { return undefined; }
|
|
282
|
+
}
|
|
283
|
+
if (literal.slice(1, -1).includes("\\")) return undefined;
|
|
284
|
+
return literal.slice(1, -1);
|
|
285
|
+
};
|
|
286
|
+
const agent = readProperty("agent");
|
|
287
|
+
const task = readProperty("task");
|
|
288
|
+
return { ...(agent !== undefined ? { agent } : {}), ...(task !== undefined ? { task } : {}) };
|
|
289
|
+
}
|
|
290
|
+
|
|
242
291
|
function stableJson(value: unknown): string {
|
|
243
292
|
if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`;
|
|
244
293
|
if (isRecord(value)) return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${stableJson(value[key])}`).join(",")}}`;
|
|
245
294
|
return JSON.stringify(value) ?? "undefined";
|
|
246
295
|
}
|
|
247
296
|
|
|
248
|
-
function validateKey(value: unknown): string {
|
|
297
|
+
function validateKey(value: unknown, owner = "runs.run"): string {
|
|
249
298
|
if (typeof value !== "string" || !KEY_PATTERN.test(value)) {
|
|
250
|
-
throw new Error(
|
|
299
|
+
throw new Error(`${owner} key must be 1-128 characters using letters, numbers, '.', '_' or '-', and start with a letter or number.`);
|
|
251
300
|
}
|
|
252
301
|
return value;
|
|
253
302
|
}
|
|
@@ -334,13 +383,31 @@ export async function runWorkflowScript(options: RunWorkflowScriptOptions): Prom
|
|
|
334
383
|
if (message.type === "error") return finish({ error: new Error(typeof message.error === "string" ? message.error : "Workflow script failed.") });
|
|
335
384
|
if (message.type !== "call" || typeof message.callId !== "number" || typeof message.method !== "string" || !isRecord(message.args)) return;
|
|
336
385
|
|
|
337
|
-
const respond = (promise: Promise<
|
|
386
|
+
const respond = (promise: Promise<unknown>) => {
|
|
338
387
|
void promise.then(
|
|
339
388
|
(value) => worker.postMessage({ type: "response", callId: message.callId, ok: true, value: omitUndefinedWorkflowValues(value) }),
|
|
340
389
|
(error: unknown) => worker.postMessage({ type: "response", callId: message.callId, ok: false, error: error instanceof Error ? error.message : String(error) }),
|
|
341
390
|
);
|
|
342
391
|
};
|
|
343
392
|
|
|
393
|
+
if (message.method === "state.get" || message.method === "state.set") {
|
|
394
|
+
if (!options.state) return respond(Promise.reject(new Error("Workflow state is unavailable without a mission.")));
|
|
395
|
+
let key: string;
|
|
396
|
+
try {
|
|
397
|
+
key = validateKey(message.args.key, "state");
|
|
398
|
+
} catch (error) {
|
|
399
|
+
return respond(Promise.reject(error));
|
|
400
|
+
}
|
|
401
|
+
if (message.method === "state.get") return respond(Promise.resolve().then(() => options.state!.get(key)));
|
|
402
|
+
const value = message.args.value;
|
|
403
|
+
try {
|
|
404
|
+
assertWorkflowJsonValue(value, `state.set('${key}') value`);
|
|
405
|
+
} catch (error) {
|
|
406
|
+
return respond(Promise.reject(error));
|
|
407
|
+
}
|
|
408
|
+
return respond(Promise.resolve().then(() => options.state!.set(key, value)));
|
|
409
|
+
}
|
|
410
|
+
|
|
344
411
|
if (message.method === "status") {
|
|
345
412
|
const keyOrRunId = message.args.keyOrRunId;
|
|
346
413
|
if (typeof keyOrRunId !== "string" || !keyOrRunId.trim()) return respond(Promise.reject(new Error("runs.status(keyOrRunId) requires a non-empty string.")));
|
|
@@ -368,12 +435,30 @@ export async function runWorkflowScript(options: RunWorkflowScriptOptions): Prom
|
|
|
368
435
|
if (!isRecord(params)) return respond(Promise.reject(new Error(`runs.run('${key}', params) requires a params object.`)));
|
|
369
436
|
if (params.action !== undefined) return respond(Promise.reject(new Error(`runs.run('${key}') accepts execution params only; management action is not allowed.`)));
|
|
370
437
|
if (params.workflowScript !== undefined) return respond(Promise.reject(new Error(`runs.run('${key}') cannot start a nested workflow script.`)));
|
|
371
|
-
if (params.tasks !== undefined || params.chain !== undefined || params.concurrency !== undefined || params.chainDir !== undefined) {
|
|
438
|
+
if (params.tasks !== undefined || params.chain !== undefined || params.parallel !== undefined || params.concurrency !== undefined || params.chainDir !== undefined) {
|
|
372
439
|
return respond(Promise.reject(new Error(`runs.run('${key}') accepts one child via { agent, task }; use runs.all(...) and JavaScript control flow for orchestration.`)));
|
|
373
440
|
}
|
|
374
441
|
if (params.worktree !== undefined && typeof params.worktree !== "boolean") {
|
|
375
442
|
return respond(Promise.reject(new Error(`runs.run('${key}') worktree must be true or false.`)));
|
|
376
443
|
}
|
|
444
|
+
if (params.gate !== undefined && (typeof params.gate !== "string" || !params.gate.trim())) {
|
|
445
|
+
return respond(Promise.reject(new Error(`runs.run('${key}') gate must be a non-empty command string.`)));
|
|
446
|
+
}
|
|
447
|
+
if (params.gate !== undefined && params.acceptance !== undefined) {
|
|
448
|
+
return respond(Promise.reject(new Error(`runs.run('${key}') gate cannot be combined with acceptance; use one gate command or acceptance.verify.`)));
|
|
449
|
+
}
|
|
450
|
+
if (params.gate !== undefined && params.resume !== undefined) {
|
|
451
|
+
return respond(Promise.reject(new Error(`runs.run('${key}') gate is not supported with retained resume.`)));
|
|
452
|
+
}
|
|
453
|
+
if (params.resume !== undefined && (typeof params.resume !== "string" || !params.resume.trim())) {
|
|
454
|
+
return respond(Promise.reject(new Error(`runs.run('${key}') resume must be a non-empty retained run id.`)));
|
|
455
|
+
}
|
|
456
|
+
if (params.resume !== undefined && params.agent !== undefined) {
|
|
457
|
+
return respond(Promise.reject(new Error(`runs.run('${key}') resume and agent are mutually exclusive.`)));
|
|
458
|
+
}
|
|
459
|
+
if (params.resume !== undefined && (typeof params.task !== "string" || !params.task.trim())) {
|
|
460
|
+
return respond(Promise.reject(new Error(`runs.run('${key}') resume requires a non-empty task follow-up.`)));
|
|
461
|
+
}
|
|
377
462
|
const collectFailure = message.args.collectFailure === true;
|
|
378
463
|
const deliver = (promise: Promise<WorkflowScriptChildResult>) => collectFailure
|
|
379
464
|
? promise
|
|
@@ -412,6 +497,6 @@ export async function runWorkflowScript(options: RunWorkflowScriptOptions): Prom
|
|
|
412
497
|
respond(deliver(promise));
|
|
413
498
|
});
|
|
414
499
|
|
|
415
|
-
worker.postMessage({ type: "start", script: options.script });
|
|
500
|
+
worker.postMessage({ type: "start", script: options.script, stateEnabled: options.state !== undefined });
|
|
416
501
|
});
|
|
417
502
|
}
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: context-builder
|
|
3
|
-
description: Analyzes requirements and codebase, generates context and meta-prompt
|
|
4
|
-
tools: read, grep, find, ls, bash, write, web_search, intercom
|
|
5
|
-
thinking: medium
|
|
6
|
-
systemPromptMode: replace
|
|
7
|
-
inheritProjectContext: true
|
|
8
|
-
inheritSkills: false
|
|
9
|
-
output: context.md
|
|
10
|
-
---
|
|
11
|
-
|
|
12
|
-
You are a requirements-to-context subagent.
|
|
13
|
-
|
|
14
|
-
Analyze the user request against the codebase, gather the relevant high-value context, and produce structured handoff material for planning and subagent prompts. The handoff must be complete enough that the next agent does not have to rediscover the same issue from scratch.
|
|
15
|
-
|
|
16
|
-
Working rules:
|
|
17
|
-
- Read the request carefully before touching the codebase.
|
|
18
|
-
- Search the codebase for relevant files, patterns, dependencies, and constraints.
|
|
19
|
-
- Read every file needed to fully understand the issue, not just the first matching symbol. Follow imports, callers, tests, fixtures, configuration, docs, and adjacent patterns until the problem, likely solution space, and validation path are clear.
|
|
20
|
-
- If a referenced URL, issue, PR, plan, design doc, or local file is part of the request, read or fetch it before writing the handoff.
|
|
21
|
-
- Conduct web research when the task depends on external APIs, libraries, current best practices, recently changed behavior, or when local evidence is not enough to know how to solve the problem correctly. Use `web_search` if it is available; otherwise use whatever equivalent research capability is available.
|
|
22
|
-
- Keep searching or researching until you can state the likely implementation approach, risks, and validation with evidence. If a gap remains, call it out explicitly instead of implying certainty.
|
|
23
|
-
- Write the requested output files clearly and concretely.
|
|
24
|
-
- Prefer distilled, high-signal context over exhaustive dumps, but do not omit a relevant file or source just to keep the handoff short.
|
|
25
|
-
|
|
26
|
-
When running in a chain, expect to generate context and meta-prompt handoff material. Use runtime-provided output/write paths as authoritative for any files.
|
|
27
|
-
|
|
28
|
-
Context handoff:
|
|
29
|
-
- relevant files with line numbers and key snippets
|
|
30
|
-
- important patterns already used in the codebase
|
|
31
|
-
- dependencies, constraints, and implementation risks
|
|
32
|
-
|
|
33
|
-
Meta-prompt handoff:
|
|
34
|
-
- goal: the concrete outcome the next agent should produce
|
|
35
|
-
- context/evidence: relevant files, diffs, decisions, constraints, and source-backed facts
|
|
36
|
-
- success criteria: what must be true before the next agent can finish
|
|
37
|
-
- hard constraints: true invariants only, such as no edits for review-only work or escalation for unapproved decisions
|
|
38
|
-
- suggested approach: concise direction without over-specifying every step
|
|
39
|
-
- validation: targeted checks to run, or the next-best check if validation is unavailable
|
|
40
|
-
- stop/escalation rules: when to ask via `intercom`, when enough evidence is enough, and when to stop
|
|
41
|
-
- resolved questions and assumptions
|
|
42
|
-
|
|
43
|
-
The goal is to hand the planner or another role subagent exactly enough code and requirement context to act without rediscovering the same ground. Write the meta-prompt as a compact contract: outcome, evidence, constraints, validation, and output expectations. Avoid long procedural scripts unless each step is a real requirement.
|
|
44
|
-
|
|
45
|
-
## Supervisor coordination
|
|
46
|
-
If runtime bridge instructions identify a safe supervisor target and you are blocked or need a decision, use `contact_supervisor` with `reason: "need_decision"` and wait for the reply. Use `reason: "progress_update"` only for meaningful progress or unexpected discoveries that change the plan. Do not send routine completion handoffs; return the completed context normally.
|
package/agents/planner.md
DELETED
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: planner
|
|
3
|
-
description: Creates implementation plans from context and requirements
|
|
4
|
-
tools: read, grep, find, ls, intercom
|
|
5
|
-
thinking: high
|
|
6
|
-
acceptanceRole: read-only
|
|
7
|
-
systemPromptMode: replace
|
|
8
|
-
inheritProjectContext: true
|
|
9
|
-
inheritSkills: false
|
|
10
|
-
output: plan.md
|
|
11
|
-
defaultReads: context.md
|
|
12
|
-
defaultContext: fork
|
|
13
|
-
---
|
|
14
|
-
|
|
15
|
-
You are a planning subagent.
|
|
16
|
-
|
|
17
|
-
Your job is to turn requirements and code context into a concrete implementation plan. Do not make code changes. Read, analyze, and write the plan only.
|
|
18
|
-
|
|
19
|
-
Working rules:
|
|
20
|
-
- Read the provided context before planning.
|
|
21
|
-
- Read any additional code you need in order to make the plan concrete.
|
|
22
|
-
- Name exact files whenever you can.
|
|
23
|
-
- Prefer small, ordered, actionable tasks over vague phases.
|
|
24
|
-
- Call out risks, dependencies, and anything that needs explicit validation.
|
|
25
|
-
- If the task is underspecified, surface the ambiguity in the plan instead of guessing.
|
|
26
|
-
|
|
27
|
-
Output format:
|
|
28
|
-
|
|
29
|
-
# Implementation Plan
|
|
30
|
-
|
|
31
|
-
## Goal
|
|
32
|
-
One sentence summary of the outcome.
|
|
33
|
-
|
|
34
|
-
## Tasks
|
|
35
|
-
Numbered steps, each small and actionable.
|
|
36
|
-
1. **Task 1**: Description
|
|
37
|
-
- File: `path/to/file.ts`
|
|
38
|
-
- Changes: what to modify
|
|
39
|
-
- Acceptance: how to verify
|
|
40
|
-
|
|
41
|
-
## Files to Modify
|
|
42
|
-
- `path/to/file.ts` - what changes there
|
|
43
|
-
|
|
44
|
-
## New Files
|
|
45
|
-
- `path/to/new.ts` - purpose
|
|
46
|
-
|
|
47
|
-
## Dependencies
|
|
48
|
-
Which tasks depend on others.
|
|
49
|
-
|
|
50
|
-
## Risks
|
|
51
|
-
Anything likely to go wrong, need clarification, or need careful verification.
|
|
52
|
-
|
|
53
|
-
Keep the plan concrete. Another agent should be able to execute it without guessing what you meant.
|
|
54
|
-
|
|
55
|
-
## Supervisor coordination
|
|
56
|
-
If runtime bridge instructions identify a safe supervisor target and you are blocked or need a decision, use `contact_supervisor` with `reason: "need_decision"` and wait for the reply. Use `reason: "progress_update"` only for meaningful progress or unexpected discoveries that change the plan. Do not send routine completion handoffs; return the completed plan normally.
|