kcode-pi 0.1.31 → 0.1.35
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/README.md +10 -10
- package/dist/cli/kcode.js +3 -3
- package/dist/context/project-context.js +3 -3
- package/docs/CHANGELOG.md +28 -2
- package/docs/COMMAND_REFERENCE.md +10 -10
- package/docs/DEVELOPMENT.md +3 -3
- package/docs/EVIDENCE_AND_GATES.md +8 -8
- package/docs/HARNESS_WORKFLOW.md +12 -12
- package/docs/KCODE_DISTRIBUTION.md +7 -7
- package/docs/PRODUCT_PROFILE.md +9 -9
- package/docs/TROUBLESHOOTING.md +8 -8
- package/docs/USER_GUIDE.md +10 -10
- package/extensions/kingdee-harness.ts +61 -48
- package/extensions/kingdee-header.ts +3 -3
- package/extensions/kingdee-subagents.ts +1 -1
- package/extensions/kingdee-tools.ts +171 -43
- package/package.json +6 -2
- package/src/cli/kcode.ts +3 -3
- package/src/context/project-context.ts +3 -3
- package/src/harness/artifacts.ts +6 -7
- package/src/harness/data-source-policy.ts +302 -0
- package/src/harness/delegation.ts +23 -23
- package/src/harness/gates.ts +26 -1
- package/src/harness/messages.ts +70 -11
- package/src/harness/path-policy.ts +1 -0
- package/src/harness/plan-steps.ts +3 -3
- package/src/harness/prompt-policy.ts +196 -0
- package/src/harness/prompt.ts +8 -16
- package/src/harness/repair.ts +2 -2
- package/src/harness/state.ts +58 -1
- package/src/official/kingdee-skills.ts +4 -4
- package/src/product/profile.ts +2 -2
- package/src/rules/checker.ts +27 -27
- package/src/tools/build-debug.ts +5 -5
- package/src/tools/sdk-signature.ts +4 -4
package/docs/USER_GUIDE.md
CHANGED
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
|
|
9
9
|
- Node.js `>=22.19.0`
|
|
10
10
|
- npm
|
|
11
|
-
- Windows
|
|
12
|
-
-
|
|
11
|
+
- Windows 环境使用 Windows Terminal
|
|
12
|
+
- 无需安装 Python。KCode 随包工具使用 Node/TypeScript 实现;只有业务项目或外部脚本明确依赖 Python 时才另行安装。
|
|
13
13
|
|
|
14
14
|
全局安装:
|
|
15
15
|
|
|
@@ -25,7 +25,7 @@ kcode
|
|
|
25
25
|
|
|
26
26
|
## 初始化业务项目
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
在终端进入实际业务项目根目录,不是 KCode 源码目录。
|
|
29
29
|
|
|
30
30
|
初始化项目级配置:
|
|
31
31
|
|
|
@@ -73,7 +73,7 @@ kcode version
|
|
|
73
73
|
kcode start
|
|
74
74
|
```
|
|
75
75
|
|
|
76
|
-
`kcode start`
|
|
76
|
+
`kcode start` 会确保 `.pi/settings.json` 已登记当前安装的 KCode package,然后启动随包 Pi CLI。
|
|
77
77
|
|
|
78
78
|
`start` 后面的参数会原样透传给 Pi CLI:
|
|
79
79
|
|
|
@@ -83,7 +83,7 @@ kcode start --provider openai --model gpt-4o
|
|
|
83
83
|
|
|
84
84
|
## 配置模型
|
|
85
85
|
|
|
86
|
-
首次启动 Pi
|
|
86
|
+
首次启动 Pi 时出现以下信息:
|
|
87
87
|
|
|
88
88
|
```text
|
|
89
89
|
Warning: No models available.
|
|
@@ -107,7 +107,7 @@ DeepSeek
|
|
|
107
107
|
Gemini
|
|
108
108
|
```
|
|
109
109
|
|
|
110
|
-
|
|
110
|
+
支持在 PowerShell 中设置环境变量后再启动:
|
|
111
111
|
|
|
112
112
|
```powershell
|
|
113
113
|
$env:OPENAI_API_KEY="sk-..."
|
|
@@ -124,7 +124,7 @@ KCode 在 Pi 内注册的 `/kd-*` 命令见 [命令参考](COMMAND_REFERENCE.md#
|
|
|
124
124
|
|
|
125
125
|
## 自定义模型供应商
|
|
126
126
|
|
|
127
|
-
|
|
127
|
+
模型服务为企业网关、代理服务、Ollama、LM Studio、vLLM 或其他 OpenAI-compatible endpoint 时,无需修改 KCode。Pi 原生支持通过用户级配置文件添加自定义供应商:
|
|
128
128
|
|
|
129
129
|
```powershell
|
|
130
130
|
notepad $env:USERPROFILE\.pi\agent\models.json
|
|
@@ -171,7 +171,7 @@ $env:CORP_AI_API_KEY="sk-..."
|
|
|
171
171
|
kcode start
|
|
172
172
|
```
|
|
173
173
|
|
|
174
|
-
|
|
174
|
+
支持直接指定:
|
|
175
175
|
|
|
176
176
|
```powershell
|
|
177
177
|
kcode start --provider corp-ai --model corp-coder
|
|
@@ -199,11 +199,11 @@ npm install -g kcode-pi@latest
|
|
|
199
199
|
kcode version
|
|
200
200
|
```
|
|
201
201
|
|
|
202
|
-
|
|
202
|
+
升级后在业务项目根目录执行:
|
|
203
203
|
|
|
204
204
|
```powershell
|
|
205
205
|
kcode init
|
|
206
206
|
kcode doctor --deep
|
|
207
207
|
```
|
|
208
208
|
|
|
209
|
-
|
|
209
|
+
升级失败或仍加载旧版本时,按 [故障排查](TROUBLESHOOTING.md) 处理。
|
|
@@ -22,6 +22,7 @@ import type { KdRisk } from "../src/harness/types.ts";
|
|
|
22
22
|
import { readArtifact } from "../src/harness/artifacts.ts";
|
|
23
23
|
import { flagshipWriteBlockReason, isSourceLikePath, planWriteBlockReason } from "../src/harness/path-policy.ts";
|
|
24
24
|
import { sdkSignatureProductionWriteBlockReason } from "../src/harness/sdk-policy.ts";
|
|
25
|
+
import { dataSourceProductionWriteBlockReason } from "../src/harness/data-source-policy.ts";
|
|
25
26
|
import { tddProductionWriteBlockReason } from "../src/harness/tdd-policy.ts";
|
|
26
27
|
import { windowsPathHint } from "../src/platform/path.ts";
|
|
27
28
|
import { repairPromptForRun, workflowPromptForRun } from "../src/harness/prompt.ts";
|
|
@@ -137,14 +138,15 @@ function codeWriteBlockReason(cwd: string, path: string | undefined): string | u
|
|
|
137
138
|
|
|
138
139
|
const run = readActiveRun(cwd);
|
|
139
140
|
if (!run) {
|
|
140
|
-
return "KCode
|
|
141
|
+
return "KCode 工作流未启动,禁止直接写产品代码。执行指令:使用自然语言需求启动 discuss,或执行 /kd-start <需求> 创建 run;完成 discuss -> spec -> plan -> execute 后再写生产源码。";
|
|
141
142
|
}
|
|
142
143
|
|
|
143
144
|
if (run.phase !== "execute") {
|
|
144
|
-
return `当前 KCode 阶段是 ${run.phase}
|
|
145
|
+
return `当前 KCode 阶段是 ${run.phase},禁止写产品代码。执行指令:完成当前阶段文档和门禁,使用 /kd-advance 推进到 execute;计划不完整时更新 PLAN.md,禁止直接写代码。`;
|
|
145
146
|
}
|
|
146
147
|
|
|
147
148
|
return (
|
|
149
|
+
dataSourceProductionWriteBlockReason(cwd, run, path) ??
|
|
148
150
|
sdkSignatureProductionWriteBlockReason(cwd, run, path) ??
|
|
149
151
|
tddProductionWriteBlockReason(cwd, run, path) ??
|
|
150
152
|
planWriteBlockReason(cwd, run, path, readArtifact(cwd, run, "plan") ?? "")
|
|
@@ -154,7 +156,7 @@ function codeWriteBlockReason(cwd: string, path: string | undefined): string | u
|
|
|
154
156
|
const kdPlanStatusTool = defineTool({
|
|
155
157
|
name: "kd_plan_status",
|
|
156
158
|
label: "KD 状态",
|
|
157
|
-
description: "
|
|
159
|
+
description: "输出当前金蝶 Harness run、当前阶段、必需文档和门禁状态。",
|
|
158
160
|
parameters: Type.Object({}),
|
|
159
161
|
|
|
160
162
|
async execute(_toolCallId, _params, _signal, _onUpdate, ctx) {
|
|
@@ -170,22 +172,22 @@ const kdQuestionTool = defineTool({
|
|
|
170
172
|
name: "kd_question",
|
|
171
173
|
label: "KD 问题",
|
|
172
174
|
description:
|
|
173
|
-
"创建、回答或列出金蝶 Harness
|
|
175
|
+
"创建、回答或列出金蝶 Harness 结构化问题。每次必须只记录一个最阻塞的短问题。",
|
|
174
176
|
parameters: Type.Object({
|
|
175
177
|
action: Type.Optional(Type.String({ description: "操作类型:ask、answer 或 list,默认 ask。" })),
|
|
176
178
|
id: Type.Optional(Type.String({ description: "回答问题时的问题编号,例如 Q-001。" })),
|
|
177
|
-
question: Type.Optional(Type.String({ description: "
|
|
179
|
+
question: Type.Optional(Type.String({ description: "提问内容;必须是一个短问题。" })),
|
|
178
180
|
answer: Type.Optional(Type.String({ description: "用户答案,action=answer 时使用。" })),
|
|
179
|
-
reason: Type.Optional(Type.String({ description: "
|
|
180
|
-
choices: Type.Optional(Type.Array(Type.String(), { description: "
|
|
181
|
-
blocking: Type.Optional(Type.Boolean({ description: "
|
|
181
|
+
reason: Type.Optional(Type.String({ description: "当前阶段阻塞原因。" })),
|
|
182
|
+
choices: Type.Optional(Type.Array(Type.String(), { description: "候选答案;最多 3 个简短选项。" })),
|
|
183
|
+
blocking: Type.Optional(Type.Boolean({ description: "控制问题是否阻塞阶段推进,默认 true。" })),
|
|
182
184
|
}),
|
|
183
185
|
|
|
184
186
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
185
187
|
const run = readActiveRun(ctx.cwd);
|
|
186
188
|
if (!run) {
|
|
187
189
|
return {
|
|
188
|
-
content: [{ type: "text", text: "当前没有 active Kingdee Harness run
|
|
190
|
+
content: [{ type: "text", text: "当前没有 active Kingdee Harness run。使用 /kd-start <需求> 创建。" }],
|
|
189
191
|
details: { error: "no-active-run" },
|
|
190
192
|
};
|
|
191
193
|
}
|
|
@@ -199,7 +201,7 @@ const kdQuestionTool = defineTool({
|
|
|
199
201
|
if (action === "answer") {
|
|
200
202
|
if (!params.id || !params.answer) {
|
|
201
203
|
return {
|
|
202
|
-
content: [{ type: "text", text: "kd_question action=answer
|
|
204
|
+
content: [{ type: "text", text: "kd_question action=answer 必须同时提供 id 和 answer。" }],
|
|
203
205
|
details: { error: "missing-answer-params" },
|
|
204
206
|
};
|
|
205
207
|
}
|
|
@@ -220,14 +222,14 @@ const kdQuestionTool = defineTool({
|
|
|
220
222
|
|
|
221
223
|
if (action !== "ask") {
|
|
222
224
|
return {
|
|
223
|
-
content: [{ type: "text", text: `未知 kd_question action:${params.action}
|
|
225
|
+
content: [{ type: "text", text: `未知 kd_question action:${params.action}。有效值:ask、answer、list。` }],
|
|
224
226
|
details: { error: "unknown-action", action: params.action },
|
|
225
227
|
};
|
|
226
228
|
}
|
|
227
229
|
|
|
228
230
|
if (!params.question?.trim()) {
|
|
229
231
|
return {
|
|
230
|
-
content: [{ type: "text", text: "kd_question action=ask
|
|
232
|
+
content: [{ type: "text", text: "kd_question action=ask 必须提供 question。" }],
|
|
231
233
|
details: { error: "missing-question" },
|
|
232
234
|
};
|
|
233
235
|
}
|
|
@@ -240,11 +242,11 @@ const kdQuestionTool = defineTool({
|
|
|
240
242
|
text: [
|
|
241
243
|
batchProblem,
|
|
242
244
|
"",
|
|
243
|
-
"kd_question
|
|
244
|
-
"
|
|
245
|
+
"kd_question 一次必须只登记一个当前最阻塞的问题。",
|
|
246
|
+
"登记第一个必须确认的问题,例如:",
|
|
245
247
|
"kd_question action=ask question=\"采购入库单 Form ID 是否为 pur_receivebill?\" choices=[\"是\", \"不是\"]",
|
|
246
248
|
"",
|
|
247
|
-
"
|
|
249
|
+
"交互模式弹出选择/输入对话并自动记录;非交互模式必须由用户在对话中回答,再用 kd_question action=answer 记录。",
|
|
248
250
|
].join("\n"),
|
|
249
251
|
},
|
|
250
252
|
],
|
|
@@ -272,7 +274,7 @@ const kdQuestionTool = defineTool({
|
|
|
272
274
|
}
|
|
273
275
|
}
|
|
274
276
|
|
|
275
|
-
const text = `${formatQuestionCard(question)}\n\n
|
|
277
|
+
const text = `${formatQuestionCard(question)}\n\n未记录交互式答案;该问题保持 open,直至用户回答。`;
|
|
276
278
|
return { content: [{ type: "text", text }], details: { question, gate: readActiveRun(ctx.cwd)?.gate, answered: false } };
|
|
277
279
|
},
|
|
278
280
|
});
|
|
@@ -281,7 +283,7 @@ function createKdVerifyResultTool(pi: ExtensionAPI) {
|
|
|
281
283
|
return defineTool({
|
|
282
284
|
name: "kd_verify_result",
|
|
283
285
|
label: "KD 验证结果",
|
|
284
|
-
description: "记录当前 verify 命令结果。失败时自动写失败证据并回到 execute
|
|
286
|
+
description: "记录当前 verify 命令结果。失败时自动写失败证据并回到 execute 修复;成功时记录通过证据并推进门禁。",
|
|
285
287
|
parameters: Type.Object({
|
|
286
288
|
command: Type.String({ description: "实际执行的验证命令。" }),
|
|
287
289
|
exitCode: Type.Number({ description: "验证命令退出码。" }),
|
|
@@ -294,7 +296,7 @@ function createKdVerifyResultTool(pi: ExtensionAPI) {
|
|
|
294
296
|
const run = readActiveRun(ctx.cwd);
|
|
295
297
|
if (!run) {
|
|
296
298
|
return {
|
|
297
|
-
content: [{ type: "text", text: "当前没有 active Kingdee Harness run
|
|
299
|
+
content: [{ type: "text", text: "当前没有 active Kingdee Harness run。使用 /kd-start <需求> 创建。" }],
|
|
298
300
|
details: { error: "no-active-run" },
|
|
299
301
|
};
|
|
300
302
|
}
|
|
@@ -326,7 +328,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
326
328
|
[
|
|
327
329
|
`发现未完成 KCode run:${run.goal ?? run.id}`,
|
|
328
330
|
`阶段:${run.phase},产品:${run.profile?.product ?? run.product ?? "unknown"}`,
|
|
329
|
-
"
|
|
331
|
+
"执行 /kd-resume 接续;执行 /kd-runs 输出其他需求或需求组。",
|
|
330
332
|
].join("\n"),
|
|
331
333
|
"info",
|
|
332
334
|
);
|
|
@@ -342,7 +344,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
342
344
|
if (ctx.hasUI) {
|
|
343
345
|
ctx.ui.notify(`已启动 Kingdee Harness run:${run.id}(${run.profile?.product}/${run.profile?.techStack})`, "info");
|
|
344
346
|
if (run.profile?.product === "unknown") {
|
|
345
|
-
ctx.ui.notify("
|
|
347
|
+
ctx.ui.notify("产品画像未识别。执行指令:执行 /kd-product <flagship|xinghan|cangqiong|enterprise>。", "warning");
|
|
346
348
|
}
|
|
347
349
|
}
|
|
348
350
|
}
|
|
@@ -360,15 +362,26 @@ export default function (pi: ExtensionAPI) {
|
|
|
360
362
|
const path = typeof input.path === "string" ? input.path : undefined;
|
|
361
363
|
const hint = path ? windowsPathHint(path) : undefined;
|
|
362
364
|
if (hint && ["read", "write", "edit"].includes(event.toolName)) {
|
|
363
|
-
const reason = `当前是 Windows 工作区,路径 ${path}
|
|
365
|
+
const reason = `当前是 Windows 工作区,路径 ${path} 不是本项目使用的路径形式。执行指令:改用项目相对路径;必须使用绝对路径时,使用 Windows 路径 ${hint}。`;
|
|
364
366
|
if (ctx.hasUI) ctx.ui.notify(reason, "warning");
|
|
365
367
|
return { block: true, reason };
|
|
366
368
|
}
|
|
367
369
|
|
|
370
|
+
// Redirect read tool to kd_doc_read for office document formats
|
|
371
|
+
if (event.toolName === "read" && path) {
|
|
372
|
+
const ext = path.toLowerCase().split(".").pop();
|
|
373
|
+
if (ext === "pdf" || ext === "docx" || ext === "doc" || ext === "xlsx" || ext === "xls" || ext === "csv") {
|
|
374
|
+
const reason = `read 工具无法解析 .${ext} 二进制格式。执行指令:改用 kd_doc_read 工具读取此文件,参数 path="${path}"。`;
|
|
375
|
+
if (ctx.hasUI) ctx.ui.notify(reason, "info");
|
|
376
|
+
return { block: true, reason };
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
368
380
|
const subagentRole = isSubagentChild() ? subagentRoleFromEnv() : undefined;
|
|
369
381
|
if (subagentRole) {
|
|
370
382
|
const run = readActiveRun(ctx.cwd);
|
|
371
383
|
const sourceWriteBlock =
|
|
384
|
+
dataSourceProductionWriteBlockReason(ctx.cwd, run, path) ??
|
|
372
385
|
sdkSignatureProductionWriteBlockReason(ctx.cwd, run, path) ??
|
|
373
386
|
tddProductionWriteBlockReason(ctx.cwd, run, path) ??
|
|
374
387
|
planWriteBlockReason(ctx.cwd, run, path, run ? (readArtifact(ctx.cwd, run, "plan") ?? "") : "") ??
|
|
@@ -398,14 +411,14 @@ export default function (pi: ExtensionAPI) {
|
|
|
398
411
|
});
|
|
399
412
|
|
|
400
413
|
pi.registerCommand("kd-status", {
|
|
401
|
-
description: "
|
|
414
|
+
description: "输出当前 Kingdee Harness run 和门禁状态",
|
|
402
415
|
handler: async (_args, ctx) => {
|
|
403
416
|
ctx.ui.notify(formatStatus(ctx.cwd, readActiveRun(ctx.cwd)), "info");
|
|
404
417
|
},
|
|
405
418
|
});
|
|
406
419
|
|
|
407
420
|
pi.registerCommand("kd-runs", {
|
|
408
|
-
description: "
|
|
421
|
+
description: "输出当前项目的 Kingdee Harness run 列表",
|
|
409
422
|
handler: async (_args, ctx) => {
|
|
410
423
|
const active = readActiveRun(ctx.cwd);
|
|
411
424
|
ctx.ui.notify(formatRuns(listRuns(ctx.cwd), active?.id), "info");
|
|
@@ -413,11 +426,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
413
426
|
});
|
|
414
427
|
|
|
415
428
|
pi.registerCommand("kd-gate", {
|
|
416
|
-
description: "
|
|
429
|
+
description: "刷新并输出当前 Kingdee Harness 门禁",
|
|
417
430
|
handler: async (_args, ctx) => {
|
|
418
431
|
const run = requireRun(ctx.cwd);
|
|
419
432
|
if (!run) {
|
|
420
|
-
ctx.ui.notify("当前没有 active Kingdee Harness run
|
|
433
|
+
ctx.ui.notify("当前没有 active Kingdee Harness run。使用 /kd-start <需求>。", "error");
|
|
421
434
|
return;
|
|
422
435
|
}
|
|
423
436
|
const refreshed = refreshGate(ctx.cwd, run);
|
|
@@ -426,7 +439,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
426
439
|
});
|
|
427
440
|
|
|
428
441
|
pi.registerCommand("kd-start", {
|
|
429
|
-
description: "
|
|
442
|
+
description: "创建 Kingdee Harness run:/kd-start [--product 产品] [--version 版本] <需求或需求组>",
|
|
430
443
|
handler: async (args, ctx) => {
|
|
431
444
|
const parsed = parseStartArgs(args);
|
|
432
445
|
const goal = parsed.goal;
|
|
@@ -438,7 +451,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
438
451
|
const run = createActiveRun(ctx.cwd, goal, parsed.product, parsed.version);
|
|
439
452
|
ctx.ui.notify(`已启动 Kingdee Harness run:${run.id}(${run.profile?.product}/${run.profile?.techStack})`, "info");
|
|
440
453
|
if (run.profile?.product === "unknown") {
|
|
441
|
-
ctx.ui.notify("
|
|
454
|
+
ctx.ui.notify("产品画像未识别。执行指令:执行 /kd-product <flagship|xinghan|cangqiong|enterprise>。", "warning");
|
|
442
455
|
}
|
|
443
456
|
sendWorkflowPrompt(pi, ctx, run, `继续 KCode Harness run ${run.id}:${goal}`);
|
|
444
457
|
},
|
|
@@ -449,7 +462,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
449
462
|
handler: async (args, ctx) => {
|
|
450
463
|
const run = requireRun(ctx.cwd);
|
|
451
464
|
if (!run) {
|
|
452
|
-
ctx.ui.notify("当前没有 active Kingdee Harness run
|
|
465
|
+
ctx.ui.notify("当前没有 active Kingdee Harness run。使用 /kd-start <需求> 或 /kd-runs。", "error");
|
|
453
466
|
return;
|
|
454
467
|
}
|
|
455
468
|
|
|
@@ -465,7 +478,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
465
478
|
});
|
|
466
479
|
|
|
467
480
|
pi.registerCommand("kd-switch", {
|
|
468
|
-
description: "
|
|
481
|
+
description: "设置当前 active Kingdee Harness run:/kd-switch <run-id>",
|
|
469
482
|
handler: async (args, ctx) => {
|
|
470
483
|
const id = args.trim();
|
|
471
484
|
if (!id) {
|
|
@@ -475,7 +488,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
475
488
|
|
|
476
489
|
const run = switchActiveRun(ctx.cwd, id);
|
|
477
490
|
if (!run) {
|
|
478
|
-
ctx.ui.notify(`未找到 Kingdee Harness run:${id}
|
|
491
|
+
ctx.ui.notify(`未找到 Kingdee Harness run:${id}。使用 /kd-runs 查看列表。`, "error");
|
|
479
492
|
return;
|
|
480
493
|
}
|
|
481
494
|
|
|
@@ -488,12 +501,12 @@ export default function (pi: ExtensionAPI) {
|
|
|
488
501
|
handler: async (_args, ctx) => {
|
|
489
502
|
const run = requireRun(ctx.cwd);
|
|
490
503
|
if (!run) {
|
|
491
|
-
ctx.ui.notify("当前没有 active Kingdee Harness run
|
|
504
|
+
ctx.ui.notify("当前没有 active Kingdee Harness run。使用 /kd-start <需求>。", "error");
|
|
492
505
|
return;
|
|
493
506
|
}
|
|
494
507
|
|
|
495
508
|
const finished = finishActiveRun(ctx.cwd, run);
|
|
496
|
-
ctx.ui.notify(`已完成 Kingdee Harness run:${finished.id}
|
|
509
|
+
ctx.ui.notify(`已完成 Kingdee Harness run:${finished.id}。下一个需求或需求组使用 /kd-start <需求>。`, "info");
|
|
497
510
|
},
|
|
498
511
|
});
|
|
499
512
|
|
|
@@ -502,7 +515,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
502
515
|
handler: async (args, ctx) => {
|
|
503
516
|
const run = requireRun(ctx.cwd);
|
|
504
517
|
if (!run) {
|
|
505
|
-
ctx.ui.notify("当前没有 active Kingdee Harness run
|
|
518
|
+
ctx.ui.notify("当前没有 active Kingdee Harness run。使用 /kd-start <需求>。", "error");
|
|
506
519
|
return;
|
|
507
520
|
}
|
|
508
521
|
|
|
@@ -523,7 +536,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
523
536
|
handler: async (args, ctx) => {
|
|
524
537
|
const run = requireRun(ctx.cwd);
|
|
525
538
|
if (!run) {
|
|
526
|
-
ctx.ui.notify("当前没有 active Kingdee Harness run
|
|
539
|
+
ctx.ui.notify("当前没有 active Kingdee Harness run。使用 /kd-start <需求>。", "error");
|
|
527
540
|
return;
|
|
528
541
|
}
|
|
529
542
|
|
|
@@ -544,7 +557,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
544
557
|
handler: async (args, ctx) => {
|
|
545
558
|
const run = requireRun(ctx.cwd);
|
|
546
559
|
if (!run) {
|
|
547
|
-
ctx.ui.notify("当前没有 active Kingdee Harness run
|
|
560
|
+
ctx.ui.notify("当前没有 active Kingdee Harness run。使用 /kd-start <需求>。", "error");
|
|
548
561
|
return;
|
|
549
562
|
}
|
|
550
563
|
|
|
@@ -552,7 +565,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
552
565
|
let requested: KdPhase | undefined;
|
|
553
566
|
if (requestedText) {
|
|
554
567
|
if (!isKdPhase(requestedText)) {
|
|
555
|
-
ctx.ui.notify(`未知阶段 "${requestedText}"
|
|
568
|
+
ctx.ui.notify(`未知阶段 "${requestedText}"。有效阶段:${PHASE_ORDER.join(", ")}`, "error");
|
|
556
569
|
return;
|
|
557
570
|
}
|
|
558
571
|
requested = requestedText;
|
|
@@ -564,11 +577,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
564
577
|
});
|
|
565
578
|
|
|
566
579
|
pi.registerCommand("kd-artifact", {
|
|
567
|
-
description: "
|
|
580
|
+
description: "创建阶段文档,或使用 --replace 显式覆盖阶段文档:/kd-artifact [阶段] [内容] [--replace]",
|
|
568
581
|
handler: async (args, ctx) => {
|
|
569
582
|
const run = requireRun(ctx.cwd);
|
|
570
583
|
if (!run) {
|
|
571
|
-
ctx.ui.notify("当前没有 active Kingdee Harness run
|
|
584
|
+
ctx.ui.notify("当前没有 active Kingdee Harness run。使用 /kd-start <需求>。", "error");
|
|
572
585
|
return;
|
|
573
586
|
}
|
|
574
587
|
|
|
@@ -579,7 +592,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
579
592
|
}
|
|
580
593
|
|
|
581
594
|
if (parsed.content && readArtifact(ctx.cwd, run, parsed.phase) !== undefined && !parsed.replace) {
|
|
582
|
-
ctx.ui.notify(`拒绝覆盖 ${parsed.phase}
|
|
595
|
+
ctx.ui.notify(`拒绝覆盖 ${parsed.phase} 阶段文档。确认整体替换时追加 --replace;追加内容必须让 KCode 更新具体章节。`, "warning");
|
|
583
596
|
return;
|
|
584
597
|
}
|
|
585
598
|
|
|
@@ -596,7 +609,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
596
609
|
handler: async (args, ctx) => {
|
|
597
610
|
const run = requireRun(ctx.cwd);
|
|
598
611
|
if (!run) {
|
|
599
|
-
ctx.ui.notify("当前没有 active Kingdee Harness run
|
|
612
|
+
ctx.ui.notify("当前没有 active Kingdee Harness run。使用 /kd-start <需求>。", "error");
|
|
600
613
|
return;
|
|
601
614
|
}
|
|
602
615
|
const [id, ...answerParts] = args.trim().split(/\s+/);
|
|
@@ -621,7 +634,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
621
634
|
handler: async (args, ctx) => {
|
|
622
635
|
const run = requireRun(ctx.cwd);
|
|
623
636
|
if (!run) {
|
|
624
|
-
ctx.ui.notify("当前没有 active Kingdee Harness run
|
|
637
|
+
ctx.ui.notify("当前没有 active Kingdee Harness run。使用 /kd-start <需求>。", "error");
|
|
625
638
|
return;
|
|
626
639
|
}
|
|
627
640
|
const [exitCodeText, ...commandParts] = args.trim().split(/\s+/);
|
|
@@ -655,7 +668,7 @@ function formatQuestions(run: NonNullable<ReturnType<typeof readActiveRun>>): st
|
|
|
655
668
|
}
|
|
656
669
|
|
|
657
670
|
function formatRuns(runs: NonNullable<ReturnType<typeof readActiveRun>>[], activeId?: string): string {
|
|
658
|
-
if (runs.length === 0) return "当前项目没有 Kingdee Harness run
|
|
671
|
+
if (runs.length === 0) return "当前项目没有 Kingdee Harness run。使用 /kd-start <需求> 创建。";
|
|
659
672
|
return runs
|
|
660
673
|
.map((run) =>
|
|
661
674
|
[
|
|
@@ -677,7 +690,7 @@ function formatQuestionCard(question: NonNullable<NonNullable<ReturnType<typeof
|
|
|
677
690
|
question.choices?.length ? `选项:${question.choices.join(" | ")}` : undefined,
|
|
678
691
|
question.answer ? `答案:${question.answer}` : undefined,
|
|
679
692
|
question.status === "open"
|
|
680
|
-
?
|
|
693
|
+
? `交互弹窗出现时直接回答;无弹窗时在对话中回复,并使用 kd_question action=answer id=${question.id} answer=<答案> 记录。`
|
|
681
694
|
: undefined,
|
|
682
695
|
];
|
|
683
696
|
return lines.filter(Boolean).join("\n");
|
|
@@ -693,14 +706,14 @@ async function askQuestionInteractively(
|
|
|
693
706
|
const normalizedChoices = choices?.map((choice) => choice.trim()).filter(Boolean) ?? [];
|
|
694
707
|
try {
|
|
695
708
|
if (normalizedChoices.length === 0) {
|
|
696
|
-
return (await ctx.ui.input(question, "
|
|
709
|
+
return (await ctx.ui.input(question, "输入答案"))?.trim() || undefined;
|
|
697
710
|
}
|
|
698
711
|
|
|
699
|
-
const customChoice = "
|
|
712
|
+
const customChoice = "自定义输入";
|
|
700
713
|
const selected = await ctx.ui.select(question, [...normalizedChoices, customChoice]);
|
|
701
714
|
if (!selected) return undefined;
|
|
702
715
|
if (selected !== customChoice) return selected;
|
|
703
|
-
return (await ctx.ui.input(question, "
|
|
716
|
+
return (await ctx.ui.input(question, "输入答案"))?.trim() || undefined;
|
|
704
717
|
} catch {
|
|
705
718
|
return undefined;
|
|
706
719
|
}
|
|
@@ -711,8 +724,8 @@ function questionBatchProblem(question: string, choices?: string[]): string | un
|
|
|
711
724
|
const numberedItems = text.split(/\r?\n/).filter((line) => /^\s*(\d+[\.\)、)]|[-*]\s+)/.test(line)).length;
|
|
712
725
|
if (numberedItems >= 2) return "kd_question 拒绝了批量清单式问题。";
|
|
713
726
|
if ((text.match(/[??]/g) ?? []).length > 1) return "kd_question 拒绝了一次包含多个问题的提问。";
|
|
714
|
-
if (text.length > 220) return "kd_question
|
|
715
|
-
if ((choices?.length ?? 0) > 3) return "kd_question
|
|
727
|
+
if (text.length > 220) return "kd_question 拒绝了过长问题。问题必须聚焦当前最小阻塞项。";
|
|
728
|
+
if ((choices?.length ?? 0) > 3) return "kd_question 拒绝了过多选项。最多 3 个简短选项。";
|
|
716
729
|
if (choices?.some((choice) => choice.length > 40 || /[\r\n??]/.test(choice))) {
|
|
717
730
|
return "kd_question 拒绝了复杂选项。选项必须是短标签,不能嵌套问题。";
|
|
718
731
|
}
|
|
@@ -43,7 +43,7 @@ function visibleWidth(str: string): number {
|
|
|
43
43
|
* and appends SGR reset before the `>`. No padding — pi-tui only requires
|
|
44
44
|
* visibleWidth <= width.
|
|
45
45
|
*/
|
|
46
|
-
function clipLine(text: string, maxWidth: number): string {
|
|
46
|
+
export function clipLine(text: string, maxWidth: number): string {
|
|
47
47
|
if (maxWidth <= 0) return "";
|
|
48
48
|
const vw = visibleWidth(text);
|
|
49
49
|
if (vw <= maxWidth) return text;
|
|
@@ -56,7 +56,7 @@ function clipLine(text: string, maxWidth: number): string {
|
|
|
56
56
|
while (i < text.length && visibleSoFar < targetW) {
|
|
57
57
|
// Preserve ANSI escape sequences
|
|
58
58
|
if (text[i] === "\x1b") {
|
|
59
|
-
const m = text.slice(i).match(
|
|
59
|
+
const m = text.slice(i).match(/^\x1b(?:\[[0-9;]*[A-Za-z]|\][^\x07]*\x07|_[^\x1b]*\x1b\\)/);
|
|
60
60
|
if (m) {
|
|
61
61
|
result += m[0];
|
|
62
62
|
i += m[0].length;
|
|
@@ -186,7 +186,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
186
186
|
description: "恢复 KCode 金蝶标题栏",
|
|
187
187
|
handler: async (_args, ctx) => {
|
|
188
188
|
if (ctx.mode !== "tui") return;
|
|
189
|
-
ctx.ui.notify("
|
|
189
|
+
ctx.ui.notify("重启会话或重新加载扩展以恢复 KCode 标题栏。", "info");
|
|
190
190
|
},
|
|
191
191
|
});
|
|
192
192
|
|
|
@@ -246,7 +246,7 @@ async function runChainedAgents(
|
|
|
246
246
|
|
|
247
247
|
function formatModeResult(mode: "single" | "parallel" | "chain", results: ChildAgentResult[], limit: number): string {
|
|
248
248
|
const succeeded = results.filter((result) => result.exitCode === 0).length;
|
|
249
|
-
const header = `子 agent ${mode} 完成:${succeeded}/${results.length}
|
|
249
|
+
const header = `子 agent ${mode} 完成:${succeeded}/${results.length} 成功`;
|
|
250
250
|
const sections = results.map((result, index) => {
|
|
251
251
|
const output = truncateOutput(result.output || result.stderr || "(子 agent 无输出)", limit);
|
|
252
252
|
return [
|