@ghyper9023/pi-dev-workflow 0.4.3 → 0.5.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/.doc/AGENT-FRONTMATTER-REFERENCE.md +198 -0
- package/.version/RELEASE-v0.5.0.md +82 -0
- package/README.md +2 -0
- package/agents/git-agent.md +7 -0
- package/agents/grill/dev-doc-grill-agent.md +7 -0
- package/agents/grill/dev-fix-grill-agent.md +7 -0
- package/agents/grill/dev-grill-agent.md +7 -0
- package/agents/grill/dev-perf-grill-agent.md +7 -0
- package/agents/grill/dev-prd-agent.md +7 -0
- package/agents/grill/dev-refactor-grill-agent.md +7 -0
- package/agents/grill/dev-test-grill-agent.md +7 -0
- package/agents/review-agent.md +7 -0
- package/agents/workflow/docWriter-agent.md +13 -2
- package/agents/workflow/planner-agent.md +16 -3
- package/agents/workflow/reviewer-agent.md +21 -4
- package/agents/workflow/trimmer-agent.md +12 -1
- package/agents/workflow/worker-agent.md +17 -2
- package/extensions/sub-agents.ts +192 -30
- package/extensions/ui-helpers.ts +20 -20
- package/extensions/workflow-engine.ts +347 -16
- package/package.json +1 -1
- package/tests/test-workflow-engine-bugs.mjs +317 -0
- package/.pi-dev-output/pi-grill/answers/answer-mpds3by7-20260520-1606.md +0 -14
- package/.pi-dev-output/pi-grill/answers/answer-mpfe77f1-20260521-1913.md +0 -58
- package/.pi-dev-output/pi-grill/answers/answer-mpfh37wu-20260521-2034.md +0 -13
- package/.pi-dev-output/pi-grill/answers/answer-mpfi5q4c-20260521-2104.md +0 -13
- package/.pi-dev-output/pi-grill/answers/answer-mpfizccb-20260521-2127.md +0 -13
- package/.pi-dev-output/pi-grill/answers/answer-mpfjk78k-20260521-2143.md +0 -13
- package/.pi-dev-output/pi-grill/answers/answer-mpfttme1-20260522-0230.md +0 -13
- package/.pi-dev-output/pi-grill/questions/questions-mpfdz1tz-20260521-1907.json +0 -94
- package/.pi-dev-output/pi-plans/20260520-153000-fix-workflow-engine-bugs.md +0 -150
- package/.pi-dev-output/pi-plans/20260521-113000-fix-loopcount-timeout.md +0 -215
- package/.pi-dev-output/pi-plans/20260521-1730-grill-input-wrap-back-fix.md +0 -240
- package/.pi-dev-output/pi-plans/20260521-230000-fix-timeout-display-loopcount-gitdiff.md +0 -253
- package/.pi-dev-output/pi-plans/20260521-230500-esc-double-press-confirm-workflow.md +0 -137
- package/.pi-dev-output/pi-plans/20260521-235000-fix-gitdiff-loopcount.md +0 -258
- package/.pi-dev-output/pi-plans/20260522-113000-grill-left-arrow-fix.md +0 -274
- package/.pi-dev-output/pi-review/html/20260521-2305-review-workflow-index.html +0 -196
- package/.pi-dev-output/pi-review/md/review-20260520-100000.md +0 -91
- package/.pi-dev-output/pi-review/md/review-20260521-140000.md +0 -191
- package/.pi-dev-output/pi-review/md/review-20260521-190000.md +0 -189
- package/.pi-dev-output/pi-review/md/review-20260521-204500.md +0 -241
- package/.pi-dev-output/pi-review/md/review-20260521-214500.md +0 -270
- package/.pi-dev-output/pi-review/md/review-20260521-215158.md +0 -214
- package/.pi-dev-output/pi-review/md/review-20260521-234500.md +0 -201
- package/.pi-dev-output/pi-review/md/review-20260521-235500.md +0 -422
- package/.pi-dev-output/pi-review/md/review-20260522-000000.md +0 -212
- package/.pi-dev-output/pi-review/md/review-20260522-003000.md +0 -377
- package/.pi-dev-output/pi-review/md/review-20260522-003500.md +0 -296
- package/.pi-dev-output/pi-review/md/review-20260522-105000.md +0 -166
- package/.pi-dev-output/pi-workflow/checkpoint-20260520-153000-fix-workflow-engine-bugs.json +0 -108
- package/.pi-dev-output/pi-workflow/checkpoint-20260521-113000-fix-loopcount-timeout.json +0 -402
- package/.pi-dev-output/pi-workflow/checkpoint-20260521-1730-grill-input-wrap-back-fix.json +0 -447
- package/.pi-dev-output/pi-workflow/checkpoint-20260521-230000-fix-timeout-display-loopcount-gitdiff.json +0 -708
- package/.pi-dev-output/pi-workflow/checkpoint-20260521-230500-esc-double-press-confirm-workflow.json +0 -365
- package/.pi-dev-output/pi-workflow/checkpoint-20260521-235000-fix-gitdiff-loopcount.json +0 -395
- package/.pi-dev-output/pi-workflow/checkpoint-20260522-113000-grill-left-arrow-fix.json +0 -473
- package/.pi-dev-output/pi-workflow/checkpoint-archive-mpfhyxc5.json +0 -30
- package/.pi-dev-output/pi-workflow/checkpoint-archive-mpfi2unc.json +0 -49
- package/.pi-dev-output/pi-workflow/checkpoint-archive-mpfi382e.json +0 -59
- package/.pi-dev-output/pi-workflow/checkpoint-archive-mpfi5r22.json +0 -76
package/extensions/sub-agents.ts
CHANGED
|
@@ -34,7 +34,11 @@ const DEFAULT_TIMEOUT_MS = 180_000;
|
|
|
34
34
|
const PROGRESS_INTERVAL_MS = 1_500;
|
|
35
35
|
|
|
36
36
|
/** Hard limit on accumulated output per subagent (prevents OOM on runaway). */
|
|
37
|
-
|
|
37
|
+
// Keep up to 10MB of output — large enough to hold complete session event streams
|
|
38
|
+
// including cargo test compilation output (commonly 100-400 KB).
|
|
39
|
+
// We preserve the END of the buffer (last N bytes) so extractFinalOutput can
|
|
40
|
+
// find the final AI response even when the total output is large.
|
|
41
|
+
const MAX_BUFFER_BYTES = 10_000_000;
|
|
38
42
|
|
|
39
43
|
/**
|
|
40
44
|
* Locations to auto-load APPEND_SYSTEM.md / append.system.md from (checked in order).
|
|
@@ -111,6 +115,15 @@ function osHomedir(): string {
|
|
|
111
115
|
}
|
|
112
116
|
}
|
|
113
117
|
|
|
118
|
+
/** Parse boolean-like frontmatter values: true/yes/1, false/no/0, or undefined. */
|
|
119
|
+
function parseBool(val: string | undefined): boolean | undefined {
|
|
120
|
+
if (val === undefined) return undefined;
|
|
121
|
+
const lc = val.toLowerCase().trim();
|
|
122
|
+
if (lc === "true" || lc === "yes" || lc === "1") return true;
|
|
123
|
+
if (lc === "false" || lc === "no" || lc === "0") return false;
|
|
124
|
+
return undefined;
|
|
125
|
+
}
|
|
126
|
+
|
|
114
127
|
// ── Process lifecycle management ─────────────────────────────
|
|
115
128
|
//
|
|
116
129
|
// Fix #1: Prevent orphan sub-agents when pi exits or crashes.
|
|
@@ -172,6 +185,22 @@ export interface AgentDef {
|
|
|
172
185
|
systemPrompt: string;
|
|
173
186
|
/** Custom timeout in ms for this agent type. */
|
|
174
187
|
timeoutMs?: number;
|
|
188
|
+
|
|
189
|
+
// ── Subprocess CLI args (from frontmatter) ──
|
|
190
|
+
/** Thinking level: "off" | "low" | "medium" | "high" | "xhigh" */
|
|
191
|
+
thinkingLevel?: string;
|
|
192
|
+
/** Persist session (default: false = --no-session) */
|
|
193
|
+
session?: boolean;
|
|
194
|
+
/** Session directory override */
|
|
195
|
+
sessionDir?: string;
|
|
196
|
+
/** Skip context files AGENTS.md/CLAUDE.md (default: true = -nc) */
|
|
197
|
+
noContext?: boolean;
|
|
198
|
+
/** Disable extension loading (default: true = -ne) */
|
|
199
|
+
noExtensions?: boolean;
|
|
200
|
+
/** Output mode: "json" | "text" (default: "json") */
|
|
201
|
+
mode?: string;
|
|
202
|
+
/** Extra raw CLI args to append */
|
|
203
|
+
extraArgs?: string[];
|
|
175
204
|
}
|
|
176
205
|
|
|
177
206
|
export interface SubagentResult {
|
|
@@ -182,6 +211,33 @@ export interface SubagentResult {
|
|
|
182
211
|
durationMs: number;
|
|
183
212
|
}
|
|
184
213
|
|
|
214
|
+
/**
|
|
215
|
+
* Caller-provided overrides for subprocess CLI args.
|
|
216
|
+
* Each field, when set, takes precedence over the AgentDef (frontmatter) value.
|
|
217
|
+
*/
|
|
218
|
+
export interface SubagentArgs {
|
|
219
|
+
/** Override thinking level */
|
|
220
|
+
thinkingLevel?: string;
|
|
221
|
+
/** Override session persistence */
|
|
222
|
+
session?: boolean;
|
|
223
|
+
/** Override session directory */
|
|
224
|
+
sessionDir?: string;
|
|
225
|
+
/** Override -nc (no context files) */
|
|
226
|
+
noContext?: boolean;
|
|
227
|
+
/** Override -ne (no extensions) */
|
|
228
|
+
noExtensions?: boolean;
|
|
229
|
+
/** Override --mode */
|
|
230
|
+
mode?: string;
|
|
231
|
+
/** Override --tools whitelist */
|
|
232
|
+
tools?: string[];
|
|
233
|
+
/** Extra --append-system-prompt (appended after agent's own) */
|
|
234
|
+
appendSystemPrompt?: string;
|
|
235
|
+
/** Extra raw CLI args (appended last, highest priority) */
|
|
236
|
+
extraArgs?: string[];
|
|
237
|
+
/** Workflow UUID to include in session name for cross-workflow traceability */
|
|
238
|
+
workflowId?: string;
|
|
239
|
+
}
|
|
240
|
+
|
|
185
241
|
function inferTimeout(name: string): number {
|
|
186
242
|
const lc = name.toLowerCase();
|
|
187
243
|
if (lc.includes("review") || lc.includes("审查")) return REVIEW_TIMEOUT_MS;
|
|
@@ -201,7 +257,7 @@ function loadAgent(filePath: string): AgentDef | null {
|
|
|
201
257
|
|
|
202
258
|
const fields: Record<string, string> = {};
|
|
203
259
|
for (const line of frontRaw.split("\n")) {
|
|
204
|
-
const m = line.match(/^(\w+):\s*(.*)$/);
|
|
260
|
+
const m = line.match(/^([\w-]+):\s*(.*)$/);
|
|
205
261
|
if (m) fields[m[1]] = m[2];
|
|
206
262
|
}
|
|
207
263
|
|
|
@@ -214,6 +270,14 @@ function loadAgent(filePath: string): AgentDef | null {
|
|
|
214
270
|
tools: tools && tools.length > 0 ? tools : undefined,
|
|
215
271
|
systemPrompt: body,
|
|
216
272
|
timeoutMs: inferTimeout(fields.name),
|
|
273
|
+
// ── Subprocess args from frontmatter ──
|
|
274
|
+
thinkingLevel: fields.thinking || undefined,
|
|
275
|
+
session: parseBool(fields.session),
|
|
276
|
+
sessionDir: fields["session-dir"] || undefined,
|
|
277
|
+
noContext: parseBool(fields["no-context"]),
|
|
278
|
+
noExtensions: parseBool(fields["no-extensions"]),
|
|
279
|
+
mode: fields.mode || undefined,
|
|
280
|
+
extraArgs: fields["extra-args"] ? fields["extra-args"].split(/\s+/).filter(Boolean) : undefined,
|
|
217
281
|
};
|
|
218
282
|
} catch {
|
|
219
283
|
return null;
|
|
@@ -270,8 +334,9 @@ export function discoverAgents(): AgentDef[] {
|
|
|
270
334
|
// Fix #2: Simplified settle logic - no listener manipulation races.
|
|
271
335
|
// Buffer is capped at MAX_BUFFER_BYTES to prevent OOM on runaway output.
|
|
272
336
|
// Fix #3: onProgress callback streams sub-agent output in real-time.
|
|
273
|
-
// Fix #4:
|
|
274
|
-
//
|
|
337
|
+
// Fix #4: Subprocess CLI args are resolved from caller > AgentDef frontmatter > defaults.
|
|
338
|
+
// Defaults: -nc, -ne, --no-session, --mode json, --thinking off.
|
|
339
|
+
// Auto-load append.system.md for project-level instructions.
|
|
275
340
|
|
|
276
341
|
function getPiInvocation(args: string[]): { command: string; args: string[] } {
|
|
277
342
|
const currentScript = process.argv[1];
|
|
@@ -294,33 +359,65 @@ export async function spawnSubagent(
|
|
|
294
359
|
signal?: AbortSignal,
|
|
295
360
|
timeoutMs?: number,
|
|
296
361
|
onProgress?: (msg: string) => void,
|
|
362
|
+
argsOverride?: SubagentArgs,
|
|
297
363
|
): Promise<SubagentResult> {
|
|
298
364
|
const effectiveTimeout = timeoutMs ?? agent.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
299
365
|
const systemPrompt = agent.systemPrompt.trim();
|
|
300
366
|
|
|
367
|
+
// ── Resolve arg overrides (caller > AgentDef frontmatter > hardcoded default) ──
|
|
368
|
+
const override = (argsOverride as SubagentArgs) ?? {};
|
|
369
|
+
|
|
370
|
+
const finalThinking = override.thinkingLevel ?? agent.thinkingLevel ?? "off";
|
|
371
|
+
const finalSession = override.session ?? agent.session ?? false;
|
|
372
|
+
const finalSessionDir = override.sessionDir ?? agent.sessionDir;
|
|
373
|
+
const finalNoContext = override.noContext ?? agent.noContext ?? true;
|
|
374
|
+
const finalNoExtensions = override.noExtensions ?? agent.noExtensions ?? true;
|
|
375
|
+
const finalMode = override.mode ?? agent.mode ?? "json";
|
|
376
|
+
const finalTools = override.tools ?? agent.tools;
|
|
377
|
+
const finalExtraArgs = override.extraArgs ?? agent.extraArgs;
|
|
378
|
+
|
|
301
379
|
// Build pi arguments for the subagent
|
|
302
|
-
const args: string[] = [
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
"-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
380
|
+
const args: string[] = ["-p"]; // non-interactive
|
|
381
|
+
|
|
382
|
+
if (!finalSession) {
|
|
383
|
+
args.push("--no-session");
|
|
384
|
+
} else {
|
|
385
|
+
// Session enabled — auto-name with timestamp + agent name
|
|
386
|
+
// Use full file path with .jsonl extension (pi --session <path> creates/opens file)
|
|
387
|
+
const sessionDir = finalSessionDir || ".pi-dev-output/pi-subagent-sessions";
|
|
388
|
+
const safeAgentName = agent.name.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
389
|
+
const ts = new Date().toISOString().replace(/[:.]/g, "-");
|
|
390
|
+
const wfSuffix = override.workflowId ? `_${override.workflowId}` : "";
|
|
391
|
+
const sessionName = `${ts}_${safeAgentName}${wfSuffix}`;
|
|
392
|
+
// Ensure session directory exists before spawning subagent
|
|
393
|
+
try {
|
|
394
|
+
fs.mkdirSync(sessionDir, { recursive: true });
|
|
395
|
+
} catch { /* ignore if already exists */ }
|
|
396
|
+
const sessionPath = path.join(sessionDir, `${sessionName}.jsonl`);
|
|
397
|
+
args.push("--session", sessionPath);
|
|
398
|
+
}
|
|
309
399
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
400
|
+
if (finalNoContext) args.push("-nc");
|
|
401
|
+
if (finalNoExtensions) args.push("-ne");
|
|
402
|
+
|
|
403
|
+
args.push("--mode", finalMode);
|
|
404
|
+
args.push("--thinking", finalThinking);
|
|
313
405
|
|
|
314
406
|
// Only grant the tools the agent needs
|
|
315
|
-
if (
|
|
316
|
-
args.push("--tools",
|
|
407
|
+
if (finalTools && finalTools.length > 0) {
|
|
408
|
+
args.push("--tools", finalTools.join(","));
|
|
317
409
|
}
|
|
318
410
|
|
|
319
|
-
// Append agent system prompt
|
|
411
|
+
// Append agent system prompt (from AgentDef markdown body)
|
|
320
412
|
if (systemPrompt) {
|
|
321
413
|
args.push("--append-system-prompt", systemPrompt);
|
|
322
414
|
}
|
|
323
415
|
|
|
416
|
+
// Caller override system prompt (appended after agent's own)
|
|
417
|
+
if (override.appendSystemPrompt) {
|
|
418
|
+
args.push("--append-system-prompt", override.appendSystemPrompt);
|
|
419
|
+
}
|
|
420
|
+
|
|
324
421
|
// Auto-load append.system.md (global or project-level)
|
|
325
422
|
// Fix #3: Sub-agents now follow append.system.md instructions
|
|
326
423
|
try {
|
|
@@ -332,6 +429,11 @@ export async function spawnSubagent(
|
|
|
332
429
|
// fail silently
|
|
333
430
|
}
|
|
334
431
|
|
|
432
|
+
// Extra raw args appended last (highest priority)
|
|
433
|
+
if (finalExtraArgs && finalExtraArgs.length > 0) {
|
|
434
|
+
args.push(...finalExtraArgs);
|
|
435
|
+
}
|
|
436
|
+
|
|
335
437
|
// The task itself
|
|
336
438
|
args.push(task);
|
|
337
439
|
|
|
@@ -379,13 +481,14 @@ export async function spawnSubagent(
|
|
|
379
481
|
|
|
380
482
|
// ── Stream stdout data + immediate progress callback ──
|
|
381
483
|
// Fix #3: Flush progress on every data chunk
|
|
484
|
+
// Fix #4: Preserve the END of the buffer (last N bytes) instead of the beginning,
|
|
485
|
+
// because extractFinalOutput needs the final AI response which is at the end.
|
|
382
486
|
proc.stdout.on("data", (data: Buffer) => {
|
|
383
487
|
const chunk = data.toString();
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
}
|
|
488
|
+
stdout += chunk;
|
|
489
|
+
if (stdout.length > MAX_BUFFER_BYTES) {
|
|
490
|
+
// Keep only the last MAX_BUFFER_BYTES bytes
|
|
491
|
+
stdout = stdout.slice(stdout.length - MAX_BUFFER_BYTES);
|
|
389
492
|
}
|
|
390
493
|
// Immediate progress on new data
|
|
391
494
|
if (!settled && onProgress) {
|
|
@@ -399,11 +502,9 @@ export async function spawnSubagent(
|
|
|
399
502
|
|
|
400
503
|
proc.stderr.on("data", (data: Buffer) => {
|
|
401
504
|
const chunk = data.toString();
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
stderr = stderr.slice(0, MAX_BUFFER_BYTES);
|
|
406
|
-
}
|
|
505
|
+
stderr += chunk;
|
|
506
|
+
if (stderr.length > MAX_BUFFER_BYTES) {
|
|
507
|
+
stderr = stderr.slice(stderr.length - MAX_BUFFER_BYTES);
|
|
407
508
|
}
|
|
408
509
|
});
|
|
409
510
|
|
|
@@ -475,11 +576,13 @@ export function extractFinalOutput(jsonOutput: string): string {
|
|
|
475
576
|
textEndSeen = true;
|
|
476
577
|
}
|
|
477
578
|
|
|
478
|
-
// Format 2:
|
|
479
|
-
//
|
|
579
|
+
// Format 2: pi agent event — message_end / message_stop / message_complete
|
|
580
|
+
// Only process assistant messages; toolResult and user messages contain
|
|
581
|
+
// raw tool output (e.g. ls -la) that would pollute the extracted summary.
|
|
480
582
|
if ((event.type === "message_stop" || event.type === "message_end" ||
|
|
481
583
|
event.type === "message_complete") &&
|
|
482
|
-
event.message?.content
|
|
584
|
+
event.message?.content &&
|
|
585
|
+
event.message?.role === "assistant") {
|
|
483
586
|
const parts = Array.isArray(event.message.content)
|
|
484
587
|
? event.message.content
|
|
485
588
|
: [event.message.content];
|
|
@@ -510,6 +613,46 @@ export function extractFinalOutput(jsonOutput: string): string {
|
|
|
510
613
|
if (event.type === "complete" && event.text) {
|
|
511
614
|
result = event.text;
|
|
512
615
|
}
|
|
616
|
+
|
|
617
|
+
// Format 5b: turn_end — extract text from the turn's assistant message.
|
|
618
|
+
// This provides an additional extraction point when message_end is lost
|
|
619
|
+
// due to buffer truncation (large tool results like cargo test output
|
|
620
|
+
// can push the final assistant message beyond the buffer limit).
|
|
621
|
+
if (event.type === "turn_end" &&
|
|
622
|
+
event.message?.content &&
|
|
623
|
+
event.message?.role === "assistant") {
|
|
624
|
+
const parts = Array.isArray(event.message.content)
|
|
625
|
+
? event.message.content
|
|
626
|
+
: [event.message.content];
|
|
627
|
+
for (const part of parts) {
|
|
628
|
+
if (typeof part === "string") result = part;
|
|
629
|
+
else if (part?.type === "text") result = part.text;
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
// Format 6: agent_end — iterate messages backwards to find the last
|
|
634
|
+
// assistant message with text content. This catches edge cases where
|
|
635
|
+
// the final assistant response has no type:text block (e.g. only
|
|
636
|
+
// thinking/toolCall), which left result polluted by a prior tool result.
|
|
637
|
+
if (event.type === "agent_end" && Array.isArray(event.messages)) {
|
|
638
|
+
for (let i = event.messages.length - 1; i >= 0; i--) {
|
|
639
|
+
const msg = event.messages[i];
|
|
640
|
+
if (msg?.role === "assistant" && Array.isArray(msg.content)) {
|
|
641
|
+
for (let j = msg.content.length - 1; j >= 0; j--) {
|
|
642
|
+
const block = msg.content[j];
|
|
643
|
+
if (typeof block === "string") {
|
|
644
|
+
result = block;
|
|
645
|
+
break;
|
|
646
|
+
}
|
|
647
|
+
if (block?.type === "text" && block.text) {
|
|
648
|
+
result = block.text;
|
|
649
|
+
break;
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
if (result) break;
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
}
|
|
513
656
|
} catch {
|
|
514
657
|
// If a line isn't JSON, it might be raw text output - collect it
|
|
515
658
|
if (!result) {
|
|
@@ -599,6 +742,14 @@ export default function (pi: ExtensionAPI) {
|
|
|
599
742
|
parameters: Type.Object({
|
|
600
743
|
agent: Type.String({ description: "Name of the agent to invoke" }),
|
|
601
744
|
task: Type.String({ description: "Task description to delegate" }),
|
|
745
|
+
// Optional overrides (pass-through to SubagentArgs)
|
|
746
|
+
thinkingLevel: Type.Optional(Type.String({ description: "Override thinking level: off | low | medium | high | xhigh" })),
|
|
747
|
+
session: Type.Optional(Type.Boolean({ description: "Override session persistence" })),
|
|
748
|
+
noContext: Type.Optional(Type.Boolean({ description: "Override -nc (skip AGENTS.md/CLAUDE.md)" })),
|
|
749
|
+
noExtensions: Type.Optional(Type.Boolean({ description: "Override -ne (disable extensions)" })),
|
|
750
|
+
mode: Type.Optional(Type.String({ description: "Override output mode: json | text" })),
|
|
751
|
+
tools: Type.Optional(Type.String({ description: "Override tool whitelist (comma-separated)" })),
|
|
752
|
+
extraArgs: Type.Optional(Type.String({ description: "Extra raw CLI args (space-separated)" })),
|
|
602
753
|
}),
|
|
603
754
|
async execute(toolCallId, params, signal, onUpdate, ctx) {
|
|
604
755
|
const agent = agents.find((a) => a.name === params.agent);
|
|
@@ -616,6 +767,16 @@ export default function (pi: ExtensionAPI) {
|
|
|
616
767
|
// Fix #3: Report progress via onUpdate
|
|
617
768
|
onUpdate?.({ state: "running", message: `🤖 正在委派 ${agent.name} 处理...` });
|
|
618
769
|
|
|
770
|
+
// Build SubagentArgs from optional tool params
|
|
771
|
+
const toolArgs: SubagentArgs = {};
|
|
772
|
+
if (params.thinkingLevel !== undefined) toolArgs.thinkingLevel = params.thinkingLevel as string;
|
|
773
|
+
if (params.session !== undefined) toolArgs.session = params.session as boolean;
|
|
774
|
+
if (params.noContext !== undefined) toolArgs.noContext = params.noContext as boolean;
|
|
775
|
+
if (params.noExtensions !== undefined) toolArgs.noExtensions = params.noExtensions as boolean;
|
|
776
|
+
if (params.mode !== undefined) toolArgs.mode = params.mode as string;
|
|
777
|
+
if (params.tools !== undefined) toolArgs.tools = (params.tools as string).split(",").map(t => t.trim()).filter(Boolean);
|
|
778
|
+
if (params.extraArgs !== undefined) toolArgs.extraArgs = (params.extraArgs as string).split(/\s+/).filter(Boolean);
|
|
779
|
+
|
|
619
780
|
const result = await spawnSubagent(
|
|
620
781
|
agent,
|
|
621
782
|
params.task,
|
|
@@ -625,6 +786,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
625
786
|
(progress) => {
|
|
626
787
|
onUpdate?.({ state: "running", message: progress });
|
|
627
788
|
},
|
|
789
|
+
Object.keys(toolArgs).length > 0 ? toolArgs : undefined,
|
|
628
790
|
);
|
|
629
791
|
const output = extractFinalOutput(result.output);
|
|
630
792
|
const dur = (result.durationMs / 1000).toFixed(1);
|
package/extensions/ui-helpers.ts
CHANGED
|
@@ -309,6 +309,8 @@ export function uiInput(
|
|
|
309
309
|
export interface WorkflowSubStepWidgetState {
|
|
310
310
|
agent: string;
|
|
311
311
|
status: "pending" | "running" | "done" | "failed";
|
|
312
|
+
/** Thinking level for this agent ("off" | "low" | "medium" | "high" | "xhigh") */
|
|
313
|
+
thinkingLevel?: string;
|
|
312
314
|
/** Recent tool activity (e.g. "edit:src/main.rs", "read:config.json") */
|
|
313
315
|
tools?: string[];
|
|
314
316
|
/** Output file paths */
|
|
@@ -357,6 +359,8 @@ export interface WorkflowWidgetState {
|
|
|
357
359
|
updatedAt: string;
|
|
358
360
|
/** Human-readable task summary shown in widget header, e.g. "[feat - 在 auth 中实现登录]" */
|
|
359
361
|
taskSummary?: string;
|
|
362
|
+
/** Workflow UUID for cross-file traceability */
|
|
363
|
+
workflowId?: string;
|
|
360
364
|
}
|
|
361
365
|
|
|
362
366
|
// ── Widget component builder ─────────────────────────────────
|
|
@@ -440,6 +444,9 @@ function buildWidgetLines(state: WorkflowWidgetState, theme: Theme, expanded: bo
|
|
|
440
444
|
? theme.fg("error", "✗")
|
|
441
445
|
: theme.fg("warning", "■");
|
|
442
446
|
lines.push(`${glyph} 工作流 · ${dim(theme, modeLabel)} · ${dim(theme, formatDurationFull(elapsed))}`);
|
|
447
|
+
if (state.workflowId) {
|
|
448
|
+
lines.push(` ${dim(theme, `UUID: ${state.workflowId}`)}`);
|
|
449
|
+
}
|
|
443
450
|
|
|
444
451
|
// ── Step list ──
|
|
445
452
|
for (let i = 0; i < state.steps.length; i++) {
|
|
@@ -466,20 +473,6 @@ function buildWidgetLines(state: WorkflowWidgetState, theme: Theme, expanded: bo
|
|
|
466
473
|
icon = dim(theme, "◦");
|
|
467
474
|
}
|
|
468
475
|
|
|
469
|
-
// ── Duration ──
|
|
470
|
-
let displayDurMs: number | undefined = s.durationMs;
|
|
471
|
-
if (isRunning && s.startedAt) {
|
|
472
|
-
displayDurMs = Date.now() - s.startedAt;
|
|
473
|
-
}
|
|
474
|
-
const durStr =
|
|
475
|
-
displayDurMs != null
|
|
476
|
-
? dim(theme, ` (${formatDurationFull(displayDurMs)}`)
|
|
477
|
-
: isRunning
|
|
478
|
-
? dim(theme, ` (0s`)
|
|
479
|
-
: "";
|
|
480
|
-
const timeout = s.timeoutMs ? dim(theme, `/超时时间${formatTimeout(s.timeoutMs)}`) : "";
|
|
481
|
-
const durClose = displayDurMs != null || isRunning ? dim(theme, ")") : "";
|
|
482
|
-
|
|
483
476
|
// ── Loop count (第 N 次循环) for loop-group steps ──
|
|
484
477
|
let loopStr = "";
|
|
485
478
|
if (s.loopCount != null && s.loopCount > 0) {
|
|
@@ -520,8 +513,8 @@ function buildWidgetLines(state: WorkflowWidgetState, theme: Theme, expanded: bo
|
|
|
520
513
|
stepIndent = " ";
|
|
521
514
|
}
|
|
522
515
|
|
|
523
|
-
// ── Step line ──
|
|
524
|
-
lines.push(`${stepIndent}${icon} ${labelStyle}${loopStr}
|
|
516
|
+
// ── Step line (注意:计时/超时信息只在子代理行展示,不在父步骤行展示) ──
|
|
517
|
+
lines.push(`${stepIndent}${icon} ${labelStyle}${loopStr}`);
|
|
525
518
|
|
|
526
519
|
// ── Sub-steps (agents with |__ tree) ──
|
|
527
520
|
if (s.subSteps && s.subSteps.length > 0) {
|
|
@@ -550,11 +543,14 @@ function buildWidgetLines(state: WorkflowWidgetState, theme: Theme, expanded: bo
|
|
|
550
543
|
let subDurStr = "";
|
|
551
544
|
let subTimeoutStr = "";
|
|
552
545
|
let subDurClose = "";
|
|
546
|
+
// ⭐ 修复:已完成/失败的子代理使用记录的 durationMs,运行中使用 live 计时
|
|
553
547
|
let elapsedMs: number | undefined;
|
|
554
|
-
if (sub.
|
|
555
|
-
|
|
556
|
-
} else if (sub.durationMs != null) {
|
|
548
|
+
if (isSubDone || sub.status === "failed") {
|
|
549
|
+
// 已完成/失败 → 使用最终记录的 durationMs(代理完成时已冻结)
|
|
557
550
|
elapsedMs = sub.durationMs;
|
|
551
|
+
} else if (isSubRunning && sub.startedAt) {
|
|
552
|
+
// 运行中 → 实时计算从 startedAt 到现在的时长
|
|
553
|
+
elapsedMs = Date.now() - sub.startedAt;
|
|
558
554
|
}
|
|
559
555
|
if (elapsedMs != null) {
|
|
560
556
|
subDurStr = dim(theme, ` (${formatDurationFull(elapsedMs)}`);
|
|
@@ -570,7 +566,8 @@ function buildWidgetLines(state: WorkflowWidgetState, theme: Theme, expanded: bo
|
|
|
570
566
|
subDurClose = dim(theme, ")");
|
|
571
567
|
}
|
|
572
568
|
const agentConnector = dim(theme, "|__");
|
|
573
|
-
|
|
569
|
+
const thinkingTag = sub.thinkingLevel ? dim(theme, `thinking-${sub.thinkingLevel} `) : "";
|
|
570
|
+
lines.push(`${agentIndent}${agentConnector} ${subIcon} ${sub.agent} · ${thinkingTag}${subDurStr}${subTimeoutStr}${subDurClose}`);
|
|
574
571
|
|
|
575
572
|
// ── Children (tools, outputs, or "正在排队") ──
|
|
576
573
|
const childItems: string[] = [];
|
|
@@ -1011,6 +1008,7 @@ export function sendWorkflowResult(
|
|
|
1011
1008
|
`[${taskSummary}]`,
|
|
1012
1009
|
"",
|
|
1013
1010
|
`${resultIcon} **工作流${statusText}** (${totalDur})`,
|
|
1011
|
+
state.workflowId ? `工作流 UUID: \`${state.workflowId}\`` : "",
|
|
1014
1012
|
"",
|
|
1015
1013
|
stepSummaryParts.join("\n"),
|
|
1016
1014
|
"",
|
|
@@ -1058,6 +1056,7 @@ export function buildWidgetState(
|
|
|
1058
1056
|
status: WorkflowWidgetState["status"],
|
|
1059
1057
|
extra?: { toolCount?: number; tokenCount?: number },
|
|
1060
1058
|
taskSummary?: string,
|
|
1059
|
+
workflowId?: string,
|
|
1061
1060
|
): WorkflowWidgetState {
|
|
1062
1061
|
return {
|
|
1063
1062
|
mode,
|
|
@@ -1069,6 +1068,7 @@ export function buildWidgetState(
|
|
|
1069
1068
|
toolCount: extra?.toolCount,
|
|
1070
1069
|
tokenCount: extra?.tokenCount,
|
|
1071
1070
|
taskSummary,
|
|
1071
|
+
workflowId,
|
|
1072
1072
|
};
|
|
1073
1073
|
}
|
|
1074
1074
|
|