@ghyper9023/pi-dev-workflow 0.4.2 → 0.5.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/.doc/AGENT-FRONTMATTER-REFERENCE.md +198 -0
- package/.version/RELEASE-v0.4.2.md +31 -0
- package/.version/RELEASE-v0.4.3.md +42 -0
- package/.version/RELEASE-v0.5.0.md +82 -0
- package/README.md +23 -3
- 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/grill-me-agent.ts +58 -8
- package/extensions/sub-agents.ts +133 -16
- package/extensions/ui-helpers.ts +27 -27
- package/extensions/workflow-engine.ts +323 -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/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-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-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-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
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: trimmer
|
|
3
3
|
description: 代码精简 agent — 精简冗余代码,缩短行数,优化可读性
|
|
4
|
-
|
|
4
|
+
thinking: medium
|
|
5
|
+
session: true
|
|
6
|
+
session-dir: .pi-dev-output/pi-subagent-sessions/trimmer/
|
|
7
|
+
no-context: false
|
|
8
|
+
no-extensions: false
|
|
9
|
+
mode: json
|
|
10
|
+
extra-args:
|
|
5
11
|
---
|
|
6
12
|
|
|
7
13
|
你是一个代码精简和可读性优化专家。你的任务是精简代码,缩短不必要冗长的行,消除重复逻辑,在不改变行为的前提下提升可读性。
|
|
@@ -24,6 +30,11 @@ tools: read, bash, write, find, ls, grep
|
|
|
24
30
|
- 语法正确
|
|
25
31
|
- 可读性更好或至少不变
|
|
26
32
|
|
|
33
|
+
## 额外可用工具
|
|
34
|
+
|
|
35
|
+
- `MCP`:可直接调用已注册的 MCP 工具获取外部信息或执行操作
|
|
36
|
+
- `SKILL`:可直接使用项目中可用的 SKILL 文件获取领域知识和最佳实践
|
|
37
|
+
|
|
27
38
|
## 约束
|
|
28
39
|
|
|
29
40
|
- **绝不改变业务逻辑**。这是最高优先级。
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: worker
|
|
3
3
|
description: 代码实施 agent — 根据计划逐步实现代码改动
|
|
4
|
-
|
|
4
|
+
thinking: medium
|
|
5
|
+
session: true
|
|
6
|
+
session-dir: .pi-dev-output/pi-subagent-sessions/worker/
|
|
7
|
+
no-context: false
|
|
8
|
+
no-extensions: false
|
|
9
|
+
mode: json
|
|
10
|
+
extra-args:
|
|
5
11
|
---
|
|
6
12
|
|
|
7
13
|
你是一个资深软件工程师。你的任务是严格按照实施计划实现代码。
|
|
@@ -16,8 +22,14 @@ tools: read, bash, write, find, ls, grep
|
|
|
16
22
|
- 每个步骤完成后,若计划中有验证命令则运行确认
|
|
17
23
|
4. **自我检查**:完成所有步骤后,确保:
|
|
18
24
|
- 没有遗漏任何计划中的改动
|
|
25
|
+
- 对修改的代码自我 review 一遍
|
|
19
26
|
- 代码语法正确(可运行 `node -c` 或 `tsc --noEmit` 等检查)
|
|
20
|
-
-
|
|
27
|
+
- 确认没有因为偷懒而破坏现有功能或忽略某项计划
|
|
28
|
+
|
|
29
|
+
## 额外可用工具
|
|
30
|
+
|
|
31
|
+
- `MCP`:可直接调用已注册的 MCP 工具获取外部信息或执行操作
|
|
32
|
+
- `SKILL`:可直接使用项目中可用的 SKILL 文件获取领域知识和最佳实践
|
|
21
33
|
|
|
22
34
|
## 约束
|
|
23
35
|
|
|
@@ -26,4 +38,7 @@ tools: read, bash, write, find, ls, grep
|
|
|
26
38
|
- **最小改动原则**:只修改计划中列出的文件,只做计划中描述的改动
|
|
27
39
|
- **不要删除未计划删除的文件**
|
|
28
40
|
- **不要修改未计划修改的现有逻辑**(除非计划中明确要求)
|
|
41
|
+
- **计划实施完成后,首先自我 review,然后使用语法检查、`test` 命令等确认代码无误**
|
|
42
|
+
- **不修改除本项目/本目录以外的任何文件或内容**
|
|
29
43
|
- 若发现计划有误或不可行,请在输出中说明原因和建议的修正方案,不要擅自变更计划
|
|
44
|
+
- **实施完成后,在回复中列出你修改的所有文件及变更类型(新增/修改/删除),供后续审查者参考**
|
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
matchesKey,
|
|
25
25
|
Key,
|
|
26
26
|
truncateToWidth,
|
|
27
|
+
wrapTextWithAnsi,
|
|
27
28
|
type SelectItem,
|
|
28
29
|
} from "@earendil-works/pi-tui";
|
|
29
30
|
import { uiSelect, uiConfirm, uiInput } from "./ui-helpers";
|
|
@@ -592,7 +593,23 @@ export async function runGrillPhase(
|
|
|
592
593
|
};
|
|
593
594
|
}
|
|
594
595
|
|
|
595
|
-
/**
|
|
596
|
+
/**
|
|
597
|
+
* Show a single grill question as TUI SelectList with option items and custom input entry.
|
|
598
|
+
*
|
|
599
|
+
* Navigation:
|
|
600
|
+
* - ↑↓ 选择, Enter 确认, Esc 取消全部评审
|
|
601
|
+
* - Ctrl+Shift+← 返回上一题(仅当 backable=true 且 currentIndex > 1 时生效)
|
|
602
|
+
* - 选择 "✏️ 自定义输入" 进入文本输入模式
|
|
603
|
+
*
|
|
604
|
+
* @param ctx - Extension command context for TUI rendering
|
|
605
|
+
* @param q - The grill question to display
|
|
606
|
+
* @param currentIndex - 1-based index of current question
|
|
607
|
+
* @param totalCount - Total number of questions
|
|
608
|
+
* @param titlePrefix - Prefix for the question title bar
|
|
609
|
+
* @param backable - Whether navigating back to previous question is allowed
|
|
610
|
+
* @param previousAnswer - Previous answer to pre-fill as "上次选择" marker
|
|
611
|
+
* @returns Selected option text, "__BACK__" for back navigation, or null for cancel
|
|
612
|
+
*/
|
|
596
613
|
async function showQuestionTUI(
|
|
597
614
|
ctx: ExtensionCommandContext,
|
|
598
615
|
q: GrillQuestion,
|
|
@@ -602,18 +619,19 @@ async function showQuestionTUI(
|
|
|
602
619
|
backable = false,
|
|
603
620
|
previousAnswer?: string,
|
|
604
621
|
): Promise<string | null> {
|
|
605
|
-
|
|
622
|
+
// 根据终端宽度计算截断宽度
|
|
623
|
+
const termWidth = process.stdout.columns || 120;
|
|
624
|
+
const maxOptWidth = Math.min(termWidth - 12, 100);
|
|
606
625
|
const selectItems: SelectItem[] = q.options.map((opt, i) => {
|
|
607
626
|
const prefix = `(${String.fromCharCode(97 + i)}) `;
|
|
608
627
|
const label = opt === previousAnswer
|
|
609
628
|
? `${prefix}${opt} - 上次选择`
|
|
610
629
|
: `${prefix}${opt}`;
|
|
611
|
-
const truncated = truncateToWidth(label,
|
|
630
|
+
const truncated = truncateToWidth(label, maxOptWidth, "...");
|
|
612
631
|
return {
|
|
613
632
|
value: `opt-${i}`,
|
|
614
633
|
label: truncated,
|
|
615
|
-
//
|
|
616
|
-
description: truncated !== label ? opt : undefined,
|
|
634
|
+
// 完整文本由下方的预览面板展示(支持换行),description 列无法换行故移除
|
|
617
635
|
};
|
|
618
636
|
});
|
|
619
637
|
|
|
@@ -650,14 +668,46 @@ async function showQuestionTUI(
|
|
|
650
668
|
description: (s) => theme.fg("muted", s),
|
|
651
669
|
scrollInfo: (s) => theme.fg("dim", s),
|
|
652
670
|
noMatch: (s) => theme.fg("warning", s),
|
|
671
|
+
}, {
|
|
672
|
+
minPrimaryColumnWidth: 30,
|
|
673
|
+
maxPrimaryColumnWidth: maxOptWidth + 2,
|
|
674
|
+
truncatePrimary: ({ text, maxWidth }) => truncateToWidth(text, maxWidth, "..."),
|
|
653
675
|
});
|
|
654
676
|
selectList.onSelect = (item) => done(item.value);
|
|
655
677
|
selectList.onCancel = () => done(null);
|
|
656
678
|
container.addChild(selectList);
|
|
657
679
|
|
|
680
|
+
// 完整选项预览面板(支持换行,展示当前选中选项的完整文本)
|
|
681
|
+
const previewWidth = Math.max(30, termWidth - 8);
|
|
682
|
+
const previewText = new Text("", 0, 0);
|
|
683
|
+
container.addChild(new Spacer(1));
|
|
684
|
+
container.addChild(previewText);
|
|
685
|
+
|
|
686
|
+
// 初始化预览为第一个选项
|
|
687
|
+
if (q.options.length > 0) {
|
|
688
|
+
const initialWrapped = wrapTextWithAnsi(q.options[0], previewWidth);
|
|
689
|
+
previewText.setText(
|
|
690
|
+
initialWrapped.map(l => theme.fg("dim", ` ${l}`)).join("\n")
|
|
691
|
+
);
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
selectList.onSelectionChange = (item) => {
|
|
695
|
+
if (item.value.startsWith("opt-")) {
|
|
696
|
+
const idx = parseInt(item.value.replace("opt-", ""), 10);
|
|
697
|
+
const fullText = q.options[idx];
|
|
698
|
+
const wrapped = wrapTextWithAnsi(fullText, previewWidth);
|
|
699
|
+
previewText.setText(
|
|
700
|
+
wrapped.map(l => theme.fg("dim", ` ${l}`)).join("\n")
|
|
701
|
+
);
|
|
702
|
+
} else {
|
|
703
|
+
previewText.setText("");
|
|
704
|
+
}
|
|
705
|
+
tui.requestRender();
|
|
706
|
+
};
|
|
707
|
+
|
|
658
708
|
container.addChild(new Spacer(1));
|
|
659
709
|
const hint = backable && currentIndex > 1
|
|
660
|
-
? " ↑↓ 导航 • Enter 选择 •
|
|
710
|
+
? " ↑↓ 导航 • Enter 选择 • Ctrl+Shift+← 返回上一题 • Esc 取消全部评审"
|
|
661
711
|
: " ↑↓ 导航 • Enter 选择 • Esc 取消全部评审";
|
|
662
712
|
container.addChild(
|
|
663
713
|
new Text(theme.fg("dim", hint), 0, 0),
|
|
@@ -668,8 +718,8 @@ async function showQuestionTUI(
|
|
|
668
718
|
render: (w) => container.render(w),
|
|
669
719
|
invalidate: () => container.invalidate(),
|
|
670
720
|
handleInput: (data) => {
|
|
671
|
-
//
|
|
672
|
-
if (backable && currentIndex > 1 && matchesKey(data, Key.left)) {
|
|
721
|
+
// Ctrl+Shift+← → 返回上一题(SelectList 不处理该键,需自行拦截)
|
|
722
|
+
if (backable && currentIndex > 1 && matchesKey(data, Key.ctrlShift("left"))) {
|
|
673
723
|
done("__BACK__");
|
|
674
724
|
return;
|
|
675
725
|
}
|
package/extensions/sub-agents.ts
CHANGED
|
@@ -111,6 +111,15 @@ function osHomedir(): string {
|
|
|
111
111
|
}
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
+
/** Parse boolean-like frontmatter values: true/yes/1, false/no/0, or undefined. */
|
|
115
|
+
function parseBool(val: string | undefined): boolean | undefined {
|
|
116
|
+
if (val === undefined) return undefined;
|
|
117
|
+
const lc = val.toLowerCase().trim();
|
|
118
|
+
if (lc === "true" || lc === "yes" || lc === "1") return true;
|
|
119
|
+
if (lc === "false" || lc === "no" || lc === "0") return false;
|
|
120
|
+
return undefined;
|
|
121
|
+
}
|
|
122
|
+
|
|
114
123
|
// ── Process lifecycle management ─────────────────────────────
|
|
115
124
|
//
|
|
116
125
|
// Fix #1: Prevent orphan sub-agents when pi exits or crashes.
|
|
@@ -172,6 +181,22 @@ export interface AgentDef {
|
|
|
172
181
|
systemPrompt: string;
|
|
173
182
|
/** Custom timeout in ms for this agent type. */
|
|
174
183
|
timeoutMs?: number;
|
|
184
|
+
|
|
185
|
+
// ── Subprocess CLI args (from frontmatter) ──
|
|
186
|
+
/** Thinking level: "off" | "low" | "medium" | "high" | "xhigh" */
|
|
187
|
+
thinkingLevel?: string;
|
|
188
|
+
/** Persist session (default: false = --no-session) */
|
|
189
|
+
session?: boolean;
|
|
190
|
+
/** Session directory override */
|
|
191
|
+
sessionDir?: string;
|
|
192
|
+
/** Skip context files AGENTS.md/CLAUDE.md (default: true = -nc) */
|
|
193
|
+
noContext?: boolean;
|
|
194
|
+
/** Disable extension loading (default: true = -ne) */
|
|
195
|
+
noExtensions?: boolean;
|
|
196
|
+
/** Output mode: "json" | "text" (default: "json") */
|
|
197
|
+
mode?: string;
|
|
198
|
+
/** Extra raw CLI args to append */
|
|
199
|
+
extraArgs?: string[];
|
|
175
200
|
}
|
|
176
201
|
|
|
177
202
|
export interface SubagentResult {
|
|
@@ -182,6 +207,33 @@ export interface SubagentResult {
|
|
|
182
207
|
durationMs: number;
|
|
183
208
|
}
|
|
184
209
|
|
|
210
|
+
/**
|
|
211
|
+
* Caller-provided overrides for subprocess CLI args.
|
|
212
|
+
* Each field, when set, takes precedence over the AgentDef (frontmatter) value.
|
|
213
|
+
*/
|
|
214
|
+
export interface SubagentArgs {
|
|
215
|
+
/** Override thinking level */
|
|
216
|
+
thinkingLevel?: string;
|
|
217
|
+
/** Override session persistence */
|
|
218
|
+
session?: boolean;
|
|
219
|
+
/** Override session directory */
|
|
220
|
+
sessionDir?: string;
|
|
221
|
+
/** Override -nc (no context files) */
|
|
222
|
+
noContext?: boolean;
|
|
223
|
+
/** Override -ne (no extensions) */
|
|
224
|
+
noExtensions?: boolean;
|
|
225
|
+
/** Override --mode */
|
|
226
|
+
mode?: string;
|
|
227
|
+
/** Override --tools whitelist */
|
|
228
|
+
tools?: string[];
|
|
229
|
+
/** Extra --append-system-prompt (appended after agent's own) */
|
|
230
|
+
appendSystemPrompt?: string;
|
|
231
|
+
/** Extra raw CLI args (appended last, highest priority) */
|
|
232
|
+
extraArgs?: string[];
|
|
233
|
+
/** Workflow UUID to include in session name for cross-workflow traceability */
|
|
234
|
+
workflowId?: string;
|
|
235
|
+
}
|
|
236
|
+
|
|
185
237
|
function inferTimeout(name: string): number {
|
|
186
238
|
const lc = name.toLowerCase();
|
|
187
239
|
if (lc.includes("review") || lc.includes("审查")) return REVIEW_TIMEOUT_MS;
|
|
@@ -201,7 +253,7 @@ function loadAgent(filePath: string): AgentDef | null {
|
|
|
201
253
|
|
|
202
254
|
const fields: Record<string, string> = {};
|
|
203
255
|
for (const line of frontRaw.split("\n")) {
|
|
204
|
-
const m = line.match(/^(\w+):\s*(.*)$/);
|
|
256
|
+
const m = line.match(/^([\w-]+):\s*(.*)$/);
|
|
205
257
|
if (m) fields[m[1]] = m[2];
|
|
206
258
|
}
|
|
207
259
|
|
|
@@ -214,6 +266,14 @@ function loadAgent(filePath: string): AgentDef | null {
|
|
|
214
266
|
tools: tools && tools.length > 0 ? tools : undefined,
|
|
215
267
|
systemPrompt: body,
|
|
216
268
|
timeoutMs: inferTimeout(fields.name),
|
|
269
|
+
// ── Subprocess args from frontmatter ──
|
|
270
|
+
thinkingLevel: fields.thinking || undefined,
|
|
271
|
+
session: parseBool(fields.session),
|
|
272
|
+
sessionDir: fields["session-dir"] || undefined,
|
|
273
|
+
noContext: parseBool(fields["no-context"]),
|
|
274
|
+
noExtensions: parseBool(fields["no-extensions"]),
|
|
275
|
+
mode: fields.mode || undefined,
|
|
276
|
+
extraArgs: fields["extra-args"] ? fields["extra-args"].split(/\s+/).filter(Boolean) : undefined,
|
|
217
277
|
};
|
|
218
278
|
} catch {
|
|
219
279
|
return null;
|
|
@@ -270,8 +330,9 @@ export function discoverAgents(): AgentDef[] {
|
|
|
270
330
|
// Fix #2: Simplified settle logic - no listener manipulation races.
|
|
271
331
|
// Buffer is capped at MAX_BUFFER_BYTES to prevent OOM on runaway output.
|
|
272
332
|
// Fix #3: onProgress callback streams sub-agent output in real-time.
|
|
273
|
-
// Fix #4:
|
|
274
|
-
//
|
|
333
|
+
// Fix #4: Subprocess CLI args are resolved from caller > AgentDef frontmatter > defaults.
|
|
334
|
+
// Defaults: -nc, -ne, --no-session, --mode json, --thinking off.
|
|
335
|
+
// Auto-load append.system.md for project-level instructions.
|
|
275
336
|
|
|
276
337
|
function getPiInvocation(args: string[]): { command: string; args: string[] } {
|
|
277
338
|
const currentScript = process.argv[1];
|
|
@@ -294,33 +355,65 @@ export async function spawnSubagent(
|
|
|
294
355
|
signal?: AbortSignal,
|
|
295
356
|
timeoutMs?: number,
|
|
296
357
|
onProgress?: (msg: string) => void,
|
|
358
|
+
argsOverride?: SubagentArgs,
|
|
297
359
|
): Promise<SubagentResult> {
|
|
298
360
|
const effectiveTimeout = timeoutMs ?? agent.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
299
361
|
const systemPrompt = agent.systemPrompt.trim();
|
|
300
362
|
|
|
363
|
+
// ── Resolve arg overrides (caller > AgentDef frontmatter > hardcoded default) ──
|
|
364
|
+
const override = (argsOverride as SubagentArgs) ?? {};
|
|
365
|
+
|
|
366
|
+
const finalThinking = override.thinkingLevel ?? agent.thinkingLevel ?? "off";
|
|
367
|
+
const finalSession = override.session ?? agent.session ?? false;
|
|
368
|
+
const finalSessionDir = override.sessionDir ?? agent.sessionDir;
|
|
369
|
+
const finalNoContext = override.noContext ?? agent.noContext ?? true;
|
|
370
|
+
const finalNoExtensions = override.noExtensions ?? agent.noExtensions ?? true;
|
|
371
|
+
const finalMode = override.mode ?? agent.mode ?? "json";
|
|
372
|
+
const finalTools = override.tools ?? agent.tools;
|
|
373
|
+
const finalExtraArgs = override.extraArgs ?? agent.extraArgs;
|
|
374
|
+
|
|
301
375
|
// Build pi arguments for the subagent
|
|
302
|
-
const args: string[] = [
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
"-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
376
|
+
const args: string[] = ["-p"]; // non-interactive
|
|
377
|
+
|
|
378
|
+
if (!finalSession) {
|
|
379
|
+
args.push("--no-session");
|
|
380
|
+
} else {
|
|
381
|
+
// Session enabled — auto-name with timestamp + agent name
|
|
382
|
+
// Use full file path with .jsonl extension (pi --session <path> creates/opens file)
|
|
383
|
+
const sessionDir = finalSessionDir || ".pi-dev-output/pi-subagent-sessions";
|
|
384
|
+
const safeAgentName = agent.name.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
385
|
+
const ts = new Date().toISOString().replace(/[:.]/g, "-");
|
|
386
|
+
const wfSuffix = override.workflowId ? `_${override.workflowId}` : "";
|
|
387
|
+
const sessionName = `${ts}_${safeAgentName}${wfSuffix}`;
|
|
388
|
+
// Ensure session directory exists before spawning subagent
|
|
389
|
+
try {
|
|
390
|
+
fs.mkdirSync(sessionDir, { recursive: true });
|
|
391
|
+
} catch { /* ignore if already exists */ }
|
|
392
|
+
const sessionPath = path.join(sessionDir, `${sessionName}.jsonl`);
|
|
393
|
+
args.push("--session", sessionPath);
|
|
394
|
+
}
|
|
309
395
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
396
|
+
if (finalNoContext) args.push("-nc");
|
|
397
|
+
if (finalNoExtensions) args.push("-ne");
|
|
398
|
+
|
|
399
|
+
args.push("--mode", finalMode);
|
|
400
|
+
args.push("--thinking", finalThinking);
|
|
313
401
|
|
|
314
402
|
// Only grant the tools the agent needs
|
|
315
|
-
if (
|
|
316
|
-
args.push("--tools",
|
|
403
|
+
if (finalTools && finalTools.length > 0) {
|
|
404
|
+
args.push("--tools", finalTools.join(","));
|
|
317
405
|
}
|
|
318
406
|
|
|
319
|
-
// Append agent system prompt
|
|
407
|
+
// Append agent system prompt (from AgentDef markdown body)
|
|
320
408
|
if (systemPrompt) {
|
|
321
409
|
args.push("--append-system-prompt", systemPrompt);
|
|
322
410
|
}
|
|
323
411
|
|
|
412
|
+
// Caller override system prompt (appended after agent's own)
|
|
413
|
+
if (override.appendSystemPrompt) {
|
|
414
|
+
args.push("--append-system-prompt", override.appendSystemPrompt);
|
|
415
|
+
}
|
|
416
|
+
|
|
324
417
|
// Auto-load append.system.md (global or project-level)
|
|
325
418
|
// Fix #3: Sub-agents now follow append.system.md instructions
|
|
326
419
|
try {
|
|
@@ -332,6 +425,11 @@ export async function spawnSubagent(
|
|
|
332
425
|
// fail silently
|
|
333
426
|
}
|
|
334
427
|
|
|
428
|
+
// Extra raw args appended last (highest priority)
|
|
429
|
+
if (finalExtraArgs && finalExtraArgs.length > 0) {
|
|
430
|
+
args.push(...finalExtraArgs);
|
|
431
|
+
}
|
|
432
|
+
|
|
335
433
|
// The task itself
|
|
336
434
|
args.push(task);
|
|
337
435
|
|
|
@@ -599,6 +697,14 @@ export default function (pi: ExtensionAPI) {
|
|
|
599
697
|
parameters: Type.Object({
|
|
600
698
|
agent: Type.String({ description: "Name of the agent to invoke" }),
|
|
601
699
|
task: Type.String({ description: "Task description to delegate" }),
|
|
700
|
+
// Optional overrides (pass-through to SubagentArgs)
|
|
701
|
+
thinkingLevel: Type.Optional(Type.String({ description: "Override thinking level: off | low | medium | high | xhigh" })),
|
|
702
|
+
session: Type.Optional(Type.Boolean({ description: "Override session persistence" })),
|
|
703
|
+
noContext: Type.Optional(Type.Boolean({ description: "Override -nc (skip AGENTS.md/CLAUDE.md)" })),
|
|
704
|
+
noExtensions: Type.Optional(Type.Boolean({ description: "Override -ne (disable extensions)" })),
|
|
705
|
+
mode: Type.Optional(Type.String({ description: "Override output mode: json | text" })),
|
|
706
|
+
tools: Type.Optional(Type.String({ description: "Override tool whitelist (comma-separated)" })),
|
|
707
|
+
extraArgs: Type.Optional(Type.String({ description: "Extra raw CLI args (space-separated)" })),
|
|
602
708
|
}),
|
|
603
709
|
async execute(toolCallId, params, signal, onUpdate, ctx) {
|
|
604
710
|
const agent = agents.find((a) => a.name === params.agent);
|
|
@@ -616,6 +722,16 @@ export default function (pi: ExtensionAPI) {
|
|
|
616
722
|
// Fix #3: Report progress via onUpdate
|
|
617
723
|
onUpdate?.({ state: "running", message: `🤖 正在委派 ${agent.name} 处理...` });
|
|
618
724
|
|
|
725
|
+
// Build SubagentArgs from optional tool params
|
|
726
|
+
const toolArgs: SubagentArgs = {};
|
|
727
|
+
if (params.thinkingLevel !== undefined) toolArgs.thinkingLevel = params.thinkingLevel as string;
|
|
728
|
+
if (params.session !== undefined) toolArgs.session = params.session as boolean;
|
|
729
|
+
if (params.noContext !== undefined) toolArgs.noContext = params.noContext as boolean;
|
|
730
|
+
if (params.noExtensions !== undefined) toolArgs.noExtensions = params.noExtensions as boolean;
|
|
731
|
+
if (params.mode !== undefined) toolArgs.mode = params.mode as string;
|
|
732
|
+
if (params.tools !== undefined) toolArgs.tools = (params.tools as string).split(",").map(t => t.trim()).filter(Boolean);
|
|
733
|
+
if (params.extraArgs !== undefined) toolArgs.extraArgs = (params.extraArgs as string).split(/\s+/).filter(Boolean);
|
|
734
|
+
|
|
619
735
|
const result = await spawnSubagent(
|
|
620
736
|
agent,
|
|
621
737
|
params.task,
|
|
@@ -625,6 +741,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
625
741
|
(progress) => {
|
|
626
742
|
onUpdate?.({ state: "running", message: progress });
|
|
627
743
|
},
|
|
744
|
+
Object.keys(toolArgs).length > 0 ? toolArgs : undefined,
|
|
628
745
|
);
|
|
629
746
|
const output = extractFinalOutput(result.output);
|
|
630
747
|
const dur = (result.durationMs / 1000).toFixed(1);
|
package/extensions/ui-helpers.ts
CHANGED
|
@@ -205,9 +205,14 @@ export function uiConfirm(
|
|
|
205
205
|
// ── Input (replaces ctx.ui.input) ────────────────────────────
|
|
206
206
|
|
|
207
207
|
/**
|
|
208
|
-
* Show an input dialog with proper wrapping.
|
|
208
|
+
* Show an input dialog with proper wrapping and live preview.
|
|
209
|
+
*
|
|
210
|
+
* Features:
|
|
211
|
+
* - 实时换行预览:输入框上方显示完整的换行预览(跟随输入实时更新)
|
|
212
|
+
* - 方向键 ←/→ 可正常移动光标编辑已有内容
|
|
213
|
+
*
|
|
209
214
|
* Returns the entered string, or BACK_MARKER on back, or undefined on cancel.
|
|
210
|
-
* When backable=true, supports
|
|
215
|
+
* When backable=true, supports Ctrl+Shift+← for back and Ctrl+Shift+→ for submit+next.
|
|
211
216
|
*/
|
|
212
217
|
export function uiInput(
|
|
213
218
|
ctx: ExtensionCommandContext,
|
|
@@ -260,11 +265,6 @@ export function uiInput(
|
|
|
260
265
|
render: (w) => container.render(w),
|
|
261
266
|
invalidate: () => container.invalidate(),
|
|
262
267
|
handleInput: (data) => {
|
|
263
|
-
// 左方向键 → 返回(优先于 Input 的光标左移)
|
|
264
|
-
if (backable && matchesKey(data, Key.left)) {
|
|
265
|
-
done(BACK_MARKER);
|
|
266
|
-
return;
|
|
267
|
-
}
|
|
268
268
|
|
|
269
269
|
// Intercept back/next keys before passing to Input
|
|
270
270
|
if (backable) {
|
|
@@ -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
|
|