@tea-agent/loop-agent 0.38.3 → 0.38.4
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/AGENTS.md +4 -1
- package/CHANGELOG.md +153 -96
- package/README.md +9 -5
- package/dist/build-stamp.json +3 -3
- package/dist/cli/command-definitions.js +16 -5
- package/dist/cli/program.js +16 -2
- package/dist/cli/update/notifier.js +2 -2
- package/dist/cli.js +2 -1
- package/dist/commands/dag-rerun.js +55 -1
- package/dist/commands/init-model-catalog.js +464 -0
- package/dist/commands/init-upgrade.js +265 -97
- package/dist/commands/init.js +428 -27
- package/dist/commands/inspect-next.js +8 -0
- package/dist/executors/dag-pi-executor.js +18 -9
- package/dist/executors/pi-playwright-cli-tool.js +51 -12
- package/dist/executors/shell-executor.js +209 -13
- package/dist/shared/dag-prompt-override.js +28 -0
- package/dist/shared/operator/capabilities.js +32 -0
- package/dist/shared/playwright-cli-command-policy.js +15 -0
- package/dist/shared/update/console-notifier.js +100 -0
- package/dist/{cli → shared}/update/npm-client.js +41 -8
- package/dist/{cli → shared}/update/state.js +41 -13
- package/dist/task/source-prepare/parse-intent.js +24 -4
- package/dist/worker/cli.js +31 -1
- package/dist/worker/console/chat/routes.js +1 -0
- package/dist/worker/console/dag-execution-receipt.js +33 -0
- package/dist/worker/console/operator-actions.js +116 -0
- package/dist/worker/console/operator-user-error.js +2 -2
- package/dist/worker/console/routes.js +43 -2
- package/dist/worker/console/security.js +63 -9
- package/dist/worker/console/server.js +36 -5
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-C4kVYweA.js → abnfDiagram-N423BO3Z-gauhD9kD.js} +1 -1
- package/dist/worker/console/static/assets/{arc-Bj2M8iO5.js → arc-pm3c2Rsl.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-CLlXe4-6.js → architectureDiagram-T3A2C74G-D_JAg3wH.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-psEp5xH0.js → blockDiagram-VBNYF7ZC-Dk7-ex52.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-DeKcOCGJ.js → c4Diagram-5PPSVZJV-Uval9oRI.js} +1 -1
- package/dist/worker/console/static/assets/channel-CVrnUdZl.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-K0C744p1.js → chunk-2GRJ4B5K-BSYjHs-v.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-xbNw4J9-.js → chunk-2Q5K7J3B-KGUuBXI8.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-DjLaSDUO.js → chunk-5RXB4S5H-JqAtxD9N.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-CtiPlKel.js → chunk-5VM5RSS4-D6EJOVQK.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-DZtolir7.js → chunk-6Q2QTUOP-B1JriSjm.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-CPcdNeew.js → chunk-GF5L2VYU-D62b96Jd.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-NgHc6V37.js → chunk-JWPE2WC7-BsNKEC_r.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-DQ_T-hg8.js → chunk-KBJHAD2P-B56v6-ZP.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-DgLsxcVP.js → chunk-RYQCIY6F-D2KPnd8K.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-UrVoM9z1.js → chunk-XXDRQBXY-kzKSCaaL.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-aX91WTya.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-aX91WTya.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-CLkYvTb3.js → cose-bilkent-JH36ORCC-C6N6KNJd.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BwT_xKrE.js → cynefin-VYW2F7L2-DiyymT2D.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-Bi9tuzif.js → cynefinDiagram-MW4NZA55-BeKAXvf3.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-BCIqkWBV.js → dagre-VZM6K2ZE-TkIdXfPF.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-Dip6l9_Z.js → diagram-7IWD3JNH-BFmSG3ks.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-B-xkh_wK.js → diagram-B4RE2ZJO-DqpJT2Fs.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-DZO0kTt_.js → diagram-LBJQPF4R-r5ATN43t.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-D6ZplNbZ.js → diagram-Q27KOJAE-DEcSCMuR.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-BPekbhoS.js → diagram-UB23O5K3-VOvYRLVc.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-BtmaJpK5.js → ebnfDiagram-BXEA7PRR-D7f4Cb1u.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-6DS0Xc44.js → erDiagram-JOGREHBK-ziUf3U0G.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-CihnROSm.js → flowDiagram-UKHOOZJN-BoRdQ-1y.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-CZC4zOE2.js → ganttDiagram-PKOTCBZU-B1-GncNm.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-Dq1fhzGN.js → gitGraphDiagram-DS77QQ5N-CTW-ph8W.js} +1 -1
- package/dist/worker/console/static/assets/{index-Ya5FE7cD.css → index-C8rOIAJr.css} +1 -1
- package/dist/worker/console/static/assets/{index-DTZOKgAn.js → index-DEbyrEfW.js} +71 -71
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-DlSl4BGx.js → infoDiagram-6WML65LV-BFA337tZ.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-snnzFl_U.js → ishikawaDiagram-WSZJBQD7-BtEujgRR.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-BDE7qpMx.js → journeyDiagram-NVQOT4AX-C8ZwD2Jp.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-CD2Ci04G.js → kanban-definition-27J2QSJJ-Ck-z8B11.js} +1 -1
- package/dist/worker/console/static/assets/{linear-DdnapKIH.js → linear-CAyXjQjN.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-BVjAT9b8.js → mermaid.core-CcWKn6tS.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-D0yJk-zA.js → mindmap-definition-FAOFIHXS-C2NuDO7E.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-BVo9tFP-.js → pegDiagram-VL7TDLO6-BFub3PDz.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-DnOV-mZ_.js → pieDiagram-7S7Q4E2Y-BJx5THZ5.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-CzUJo58i.js → quadrantDiagram-CIZ2JOQS-DhalkDWV.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-Bvikrolh.js → railroadDiagram-AXF67PYL-CVcBM36o.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-DtaSVCap.js → requirementDiagram-LRYGKXZP-D_vy4n0w.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-C2c9A0wW.js → sankeyDiagram-W5VNT64P-DGYdJL1D.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-DmXJcU7r.js → sequenceDiagram-SI44F4Z6-C1xJFg3k.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-B4GUFW92.js → sizeCapture-X5ZJPWSS-D_Fc43df.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-Dsad2MXf.js → stateDiagram-OKZ733FA-BhGbIjbu.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DUt1lyFL.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-DGq48Fbi.js → swimlanes-SLNWSIFB-DVkNcO-V.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-BM6SGr7j.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-CSSi6OFf.js → timeline-definition-Z64GVDOM-BQU9ufV0.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-BqyzPrWv.js → vennDiagram-T6HMQDX7-BCQhRmYB.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-DlznVb6S.js → wardleyDiagram-T6FBY63Y-DWerOyHO.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-CfBcgJ_K.js → xychartDiagram-ELKLHX3M-DxLeebQU.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/operator-chat/mutation-gate.js +1 -1
- package/dist/worker/loop-agent/loop-agent-client.js +7 -3
- package/dist/worker/observe/node-input.js +2 -0
- package/dist/worker/observe/node-process.js +377 -0
- package/dist/worker/observe/routes.js +36 -3
- package/dist/worker/observe/shell-handler-keys.js +34 -0
- package/dist/worker/observe/static/api.js +66 -0
- package/dist/worker/observe/static/constants.js +8 -1
- package/dist/worker/observe/static/format.js +23 -0
- package/dist/worker/observe/static/markdown-render.js +20 -1
- package/dist/worker/observe/static/operator-chrome.js +4 -0
- package/dist/worker/observe/static/state.js +11 -0
- package/dist/worker/observe/static/styles.css +73 -0
- package/dist/worker/observe/static/views/dag-inspector.js +561 -8
- package/dist/worker/observe/static/views/dag.js +8 -1
- package/dist/worker/observe/static/views/failures.js +5 -2
- package/dist/worker/observe/static/views/session-timeline.js +60 -4
- package/dist/workflows/dag/backend-test-case-coverage-analysis.js +20 -2
- package/dist/workflows/dag/failure-category.js +8 -0
- package/dist/workflows/dag/frontend-implementation-contract.js +5 -1
- package/dist/workflows/dag/frontend-repair.js +14 -0
- package/dist/workflows/dag/frontend-risk.js +15 -2
- package/dist/workflows/dag/frontend-test-case-checklist.js +26 -2
- package/dist/workflows/dag/frontend-test-case-manifest.js +5 -0
- package/dist/workflows/dag/frontend-test-result-contract.js +151 -11
- package/dist/workflows/dag/frontend-verification-trace.js +29 -4
- package/dist/workflows/dag/init-hybrid.js +125 -24
- package/dist/workflows/dag/node-execution.js +20 -2
- package/dist/workflows/dag/rerun-plan.js +112 -0
- package/dist/workflows/dag/rerun-run.js +58 -6
- package/dist/workflows/dag/retry-policy.js +91 -0
- package/dist/workflows/dag/types.js +9 -1
- package/docs/init-surface.manifest.json +5 -0
- package/docs/operations/README.md +1 -1
- package/docs/templates/init-managed-agents.md +13 -3
- package/harness.json +2 -2
- package/package.json +5 -1
- package/scripts/next-info.mjs +356 -0
- package/scripts/next-publish-gate.mjs +238 -0
- package/scripts/release-source-binding.mjs +251 -0
- package/skills/fe-test-ui-scout/SKILL.md +65 -0
- package/skills/fe-test-ui-scout/references/ledger-schema.md +62 -0
- package/skills/fe-test-ui-scout/references/recon-protocol.md +54 -0
- package/skills/loop-agent/references/command-reference.md +11 -4
- package/skills/playwright-cli/SKILL.md +22 -0
- package/skills/playwright-cli-case-generator/SKILL.md +20 -1
- package/dist/worker/console/static/assets/channel-CPF4N7pf.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-C2DwA_Y1.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-C2DwA_Y1.js +0 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-B8FB_cNk.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-CrZYaWfx.js +0 -8
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { createInterface } from "node:readline/promises";
|
|
2
|
+
import semver from "semver";
|
|
3
|
+
import { acquireUpdateLock, readUpdateState, updateLockPath, updateStatePath, writeUpdateState, } from "./state.js";
|
|
4
|
+
export async function runConsoleDailyUpdateCheck(options) {
|
|
5
|
+
void options.writeStdout;
|
|
6
|
+
void options.packageRoot;
|
|
7
|
+
void options.cwd;
|
|
8
|
+
const writeStderr = options.writeStderr ?? ((chunk) => process.stderr.write(chunk));
|
|
9
|
+
const now = options.now ?? (() => new Date());
|
|
10
|
+
try {
|
|
11
|
+
if (!canPromptInteractively(options))
|
|
12
|
+
return;
|
|
13
|
+
const statePath = updateStatePath(options.homeDir);
|
|
14
|
+
const today = formatLocalDate(now());
|
|
15
|
+
const existing = await readUpdateState(statePath);
|
|
16
|
+
if (existing.lastPromptedLocalDate === today)
|
|
17
|
+
return;
|
|
18
|
+
const release = await acquireUpdateLock(updateLockPath(options.homeDir), {
|
|
19
|
+
now,
|
|
20
|
+
});
|
|
21
|
+
if (!release)
|
|
22
|
+
return;
|
|
23
|
+
try {
|
|
24
|
+
const state = await readUpdateState(statePath);
|
|
25
|
+
if (state.lastPromptedLocalDate === today)
|
|
26
|
+
return;
|
|
27
|
+
const check = await options.client.latestVersionCheck();
|
|
28
|
+
if (!check.ok)
|
|
29
|
+
return;
|
|
30
|
+
if (!semver.gt(check.version, options.currentVersion))
|
|
31
|
+
return;
|
|
32
|
+
const parsedLatest = semver.parse(check.version);
|
|
33
|
+
if (!parsedLatest || parsedLatest.prerelease.length > 0)
|
|
34
|
+
return;
|
|
35
|
+
await markPromptedToday(statePath, state, today);
|
|
36
|
+
const answer = await ask(options, `loop-agent 有新版本:${options.currentVersion} → ${check.version}\n现在更新到 ${check.version}?[y/N] `);
|
|
37
|
+
if (!isYes(answer))
|
|
38
|
+
return;
|
|
39
|
+
const proof = await options.client.canAutoInstall();
|
|
40
|
+
if (!proof.ok) {
|
|
41
|
+
writeStderr(`当前 loop-agent 不是由此 npm 的正式全局安装管理,无法自动升级。\n请运行:npm install -g @tea-agent/loop-agent@${check.version}\n`);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
const install = await options.client.installExact(check.version);
|
|
45
|
+
if (!install.ok) {
|
|
46
|
+
writeStderr(`loop-agent 更新失败:${install.reason}\n`);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const verified = await options.client.verifyInstalled(check.version);
|
|
50
|
+
if (!verified.ok) {
|
|
51
|
+
writeStderr(`loop-agent 已尝试安装,但验证失败:${verified.reason}\n`);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
writeStderr(`loop-agent 已更新到 ${check.version};新版本将在下一次命令调用时完全生效。\n`);
|
|
55
|
+
}
|
|
56
|
+
finally {
|
|
57
|
+
await release().catch(() => undefined);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
writeStderr(`loop-agent 更新检查已跳过:${error instanceof Error ? error.message : String(error)}\n`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
function canPromptInteractively(options) {
|
|
65
|
+
if (!options.stdinIsTTY || !options.stderrIsTTY)
|
|
66
|
+
return false;
|
|
67
|
+
if (options.env.CI)
|
|
68
|
+
return false;
|
|
69
|
+
if (options.env.LOOP_AGENT_DISABLE_UPDATE_CHECK === "1")
|
|
70
|
+
return false;
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
function formatLocalDate(date) {
|
|
74
|
+
const year = date.getFullYear();
|
|
75
|
+
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
76
|
+
const day = String(date.getDate()).padStart(2, "0");
|
|
77
|
+
return `${year}-${month}-${day}`;
|
|
78
|
+
}
|
|
79
|
+
async function markPromptedToday(statePath, state, today) {
|
|
80
|
+
await writeUpdateState(statePath, {
|
|
81
|
+
...state,
|
|
82
|
+
lastPromptedLocalDate: today,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
async function ask(options, prompt) {
|
|
86
|
+
if (options.readLine) {
|
|
87
|
+
options.writeStderr?.(prompt);
|
|
88
|
+
return options.readLine(prompt);
|
|
89
|
+
}
|
|
90
|
+
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
91
|
+
try {
|
|
92
|
+
return await rl.question(prompt);
|
|
93
|
+
}
|
|
94
|
+
finally {
|
|
95
|
+
rl.close();
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
function isYes(answer) {
|
|
99
|
+
return /^(y|yes)$/i.test(answer.trim());
|
|
100
|
+
}
|
|
@@ -3,25 +3,48 @@ import { lstat, readFile, realpath } from "node:fs/promises";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import semver from "semver";
|
|
5
5
|
const PACKAGE_NAME = "@tea-agent/loop-agent";
|
|
6
|
+
export const DEFAULT_NPM_QUERY_TIMEOUT_MS = 15_000;
|
|
6
7
|
export function createNpmUpdateClient(options) {
|
|
7
8
|
const npmCommand = options.npmCommand ?? "npm";
|
|
8
9
|
const loopAgentCommand = options.loopAgentCommand ?? "loop-agent";
|
|
9
10
|
const runner = options.runner ?? runCommand;
|
|
10
|
-
const timeoutMs = options.timeoutMs ??
|
|
11
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_NPM_QUERY_TIMEOUT_MS;
|
|
12
|
+
const cwd = options.cwd;
|
|
11
13
|
const env = options.env;
|
|
14
|
+
const latestVersionCheck = async () => {
|
|
15
|
+
let result;
|
|
16
|
+
try {
|
|
17
|
+
result = await runner(npmCommand, ["view", PACKAGE_NAME, "dist-tags.latest", "--json"], { timeoutMs, cwd, env });
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return { ok: false, reason: "spawn-failure" };
|
|
21
|
+
}
|
|
22
|
+
if (result.timedOut)
|
|
23
|
+
return { ok: false, reason: "timeout" };
|
|
24
|
+
if (result.exitCode === null) {
|
|
25
|
+
return { ok: false, reason: "spawn-failure" };
|
|
26
|
+
}
|
|
27
|
+
if (result.exitCode !== 0) {
|
|
28
|
+
return { ok: false, reason: "command-failed" };
|
|
29
|
+
}
|
|
30
|
+
const version = parseNpmViewLatest(result.stdout);
|
|
31
|
+
return version
|
|
32
|
+
? { ok: true, version }
|
|
33
|
+
: { ok: false, reason: "invalid-response" };
|
|
34
|
+
};
|
|
12
35
|
return {
|
|
13
36
|
async latestVersion() {
|
|
14
|
-
const result = await
|
|
15
|
-
|
|
16
|
-
return undefined;
|
|
17
|
-
return parseNpmViewLatest(result.stdout);
|
|
37
|
+
const result = await latestVersionCheck();
|
|
38
|
+
return result.ok ? result.version : undefined;
|
|
18
39
|
},
|
|
40
|
+
latestVersionCheck,
|
|
19
41
|
async canAutoInstall() {
|
|
20
42
|
return proveGlobalNpmInstall({
|
|
21
43
|
currentVersion: options.currentVersion,
|
|
22
44
|
packageRoot: options.packageRoot,
|
|
23
45
|
npmCommand,
|
|
24
46
|
runner,
|
|
47
|
+
cwd,
|
|
25
48
|
env,
|
|
26
49
|
timeoutMs,
|
|
27
50
|
});
|
|
@@ -29,17 +52,25 @@ export function createNpmUpdateClient(options) {
|
|
|
29
52
|
async installExact(version) {
|
|
30
53
|
if (!isStrictRelease(version))
|
|
31
54
|
return { ok: false, reason: "invalid-version" };
|
|
32
|
-
const result = await runner(npmCommand, buildExactInstallArgs(version), {
|
|
55
|
+
const result = await runner(npmCommand, buildExactInstallArgs(version), {
|
|
56
|
+
timeoutMs: Math.max(timeoutMs, 120000),
|
|
57
|
+
cwd,
|
|
58
|
+
env,
|
|
59
|
+
});
|
|
33
60
|
if (result.exitCode !== 0 || result.timedOut)
|
|
34
61
|
return { ok: false, reason: "install-failed" };
|
|
35
62
|
return { ok: true };
|
|
36
63
|
},
|
|
37
64
|
async verifyInstalled(version) {
|
|
38
|
-
const list = await runner(npmCommand, ["list", "-g", PACKAGE_NAME, "--depth=0", "--json"], { timeoutMs, env });
|
|
65
|
+
const list = await runner(npmCommand, ["list", "-g", PACKAGE_NAME, "--depth=0", "--json"], { timeoutMs, cwd, env });
|
|
39
66
|
if (list.exitCode !== 0 || !verifyNpmListVersion(list.stdout, version)) {
|
|
40
67
|
return { ok: false, reason: "npm-list-version-mismatch" };
|
|
41
68
|
}
|
|
42
|
-
const pathVersion = await runner(loopAgentCommand, ["--version"], {
|
|
69
|
+
const pathVersion = await runner(loopAgentCommand, ["--version"], {
|
|
70
|
+
timeoutMs,
|
|
71
|
+
cwd,
|
|
72
|
+
env,
|
|
73
|
+
});
|
|
43
74
|
if (pathVersion.exitCode !== 0 || !verifyPathVersion(pathVersion.stdout, version)) {
|
|
44
75
|
return { ok: false, reason: "path-version-mismatch" };
|
|
45
76
|
}
|
|
@@ -80,6 +111,7 @@ export function verifyPathVersion(stdout, expectedVersion) {
|
|
|
80
111
|
async function proveGlobalNpmInstall(options) {
|
|
81
112
|
const root = await options.runner(options.npmCommand, ["root", "-g"], {
|
|
82
113
|
timeoutMs: options.timeoutMs,
|
|
114
|
+
cwd: options.cwd,
|
|
83
115
|
env: options.env,
|
|
84
116
|
});
|
|
85
117
|
if (root.exitCode !== 0 || root.timedOut)
|
|
@@ -113,6 +145,7 @@ function isStrictRelease(version) {
|
|
|
113
145
|
async function runCommand(command, args, options) {
|
|
114
146
|
return new Promise((resolve) => {
|
|
115
147
|
const child = spawn(command, [...args], {
|
|
148
|
+
cwd: options.cwd,
|
|
116
149
|
env: options.env ? { ...process.env, ...options.env } : process.env,
|
|
117
150
|
stdio: ["ignore", "pipe", "pipe"],
|
|
118
151
|
shell: false,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
1
|
+
import { mkdir, readFile, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
export function updateStatePath(homeDir) {
|
|
4
4
|
return path.join(homeDir, ".loop-agent", "update-state.json");
|
|
@@ -18,6 +18,7 @@ export async function readUpdateState(statePath) {
|
|
|
18
18
|
latestVersion: stringOrUndefined(parsed.latestVersion),
|
|
19
19
|
dismissedVersion: stringOrUndefined(parsed.dismissedVersion),
|
|
20
20
|
retryAfter: stringOrUndefined(parsed.retryAfter),
|
|
21
|
+
lastPromptedLocalDate: stringOrUndefined(parsed.lastPromptedLocalDate),
|
|
21
22
|
};
|
|
22
23
|
}
|
|
23
24
|
catch {
|
|
@@ -36,28 +37,55 @@ export async function acquireUpdateLock(lockPath, options = {}) {
|
|
|
36
37
|
await mkdir(path.dirname(lockPath), { recursive: true });
|
|
37
38
|
try {
|
|
38
39
|
await mkdir(lockPath);
|
|
40
|
+
return await claimLock(lockPath, now);
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
if (await isFreshLock(lockPath, now, staleMs))
|
|
44
|
+
return undefined;
|
|
45
|
+
try {
|
|
46
|
+
await rm(lockPath, { recursive: true, force: true });
|
|
47
|
+
await mkdir(lockPath);
|
|
48
|
+
return await claimLock(lockPath, now);
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
async function claimLock(lockPath, now) {
|
|
56
|
+
try {
|
|
39
57
|
await writeFile(path.join(lockPath, "owner.json"), JSON.stringify({ pid: process.pid, at: now().toISOString() }), "utf-8");
|
|
40
58
|
return async () => {
|
|
41
59
|
await rm(lockPath, { recursive: true, force: true });
|
|
42
60
|
};
|
|
43
61
|
}
|
|
62
|
+
catch {
|
|
63
|
+
await rm(lockPath, { recursive: true, force: true }).catch(() => undefined);
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
async function isFreshLock(lockPath, now, staleMs) {
|
|
68
|
+
try {
|
|
69
|
+
const ownerRaw = await readFile(path.join(lockPath, "owner.json"), "utf-8");
|
|
70
|
+
const owner = JSON.parse(ownerRaw);
|
|
71
|
+
if (typeof owner.at !== "string")
|
|
72
|
+
return false;
|
|
73
|
+
const at = Date.parse(owner.at);
|
|
74
|
+
if (!Number.isFinite(at))
|
|
75
|
+
return false;
|
|
76
|
+
return now().getTime() - at <= staleMs;
|
|
77
|
+
}
|
|
44
78
|
catch {
|
|
45
79
|
try {
|
|
46
|
-
const
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
await writeFile(path.join(lockPath, "owner.json"), JSON.stringify({ pid: process.pid, at: now().toISOString() }), "utf-8");
|
|
52
|
-
return async () => {
|
|
53
|
-
await rm(lockPath, { recursive: true, force: true });
|
|
54
|
-
};
|
|
55
|
-
}
|
|
80
|
+
const info = await stat(lockPath);
|
|
81
|
+
const mtime = Number.isFinite(info.mtimeMs)
|
|
82
|
+
? info.mtimeMs
|
|
83
|
+
: info.mtime.getTime();
|
|
84
|
+
return now().getTime() - mtime <= staleMs;
|
|
56
85
|
}
|
|
57
86
|
catch {
|
|
58
|
-
return
|
|
87
|
+
return false;
|
|
59
88
|
}
|
|
60
|
-
return undefined;
|
|
61
89
|
}
|
|
62
90
|
}
|
|
63
91
|
function emptyState() {
|
|
@@ -35,8 +35,15 @@ const HEADING_ALIASES = {
|
|
|
35
35
|
assumptions: new Set(["假设", "assumptions"]),
|
|
36
36
|
};
|
|
37
37
|
const OBJECTIVE_MAX_CHARS = 1200;
|
|
38
|
-
/**
|
|
39
|
-
|
|
38
|
+
/**
|
|
39
|
+
* Product Requirement V3/V4 style embedded AC heading, e.g. AC-FE-001 /
|
|
40
|
+
* AC-BE-12 / AC-001 — and catalog-qualified feature segments such as
|
|
41
|
+
* AC-FE-INS-001 / AC-FE-CHAT-005 (uppercase letter/digit segments).
|
|
42
|
+
* Catalog ids embedded in bold (“**AC-FE-INS-001**:…”) must bind as-is;
|
|
43
|
+
* falling through to a minted AC-00N historically produced dual ids and
|
|
44
|
+
* acceptanceCoverage.missing noise (2026-08-23 fe-test loop finding 5).
|
|
45
|
+
*/
|
|
46
|
+
const EMBEDDED_AC_HEADING = /^(AC(?:[-_](?:FE|BE|UI|API))?[-_]?[A-Z0-9]+(?:[-_][A-Z0-9]+)*)\s*[::]?\s*(.*)$/u;
|
|
40
47
|
function stripFencedAndComments(text) {
|
|
41
48
|
// Remove fenced code blocks and HTML comments so headings inside them are ignored.
|
|
42
49
|
return text
|
|
@@ -52,12 +59,25 @@ function normalizeAcceptanceId(raw) {
|
|
|
52
59
|
.replace(/^(AC)(FE|BE|UI|API)(?=-|\d)/u, "$1-$2");
|
|
53
60
|
}
|
|
54
61
|
function parseEmbeddedAcHeading(headingText) {
|
|
55
|
-
const normalized = headingText
|
|
62
|
+
const normalized = headingText
|
|
63
|
+
.trim()
|
|
64
|
+
// Strip leading section numbers ("2.1、" style) and bold/emphasis
|
|
65
|
+
// wrappers ("**AC-FE-INS-001**") so embedded catalog ids bind.
|
|
66
|
+
.replace(/^(?:\d+(?:\.\d+)*[..、]?\s*)+/u, "")
|
|
67
|
+
.replace(/^\*\*|\*\*$/g, "")
|
|
68
|
+
.replace(/^__|__$/g, "")
|
|
69
|
+
.trim();
|
|
56
70
|
const match = EMBEDDED_AC_HEADING.exec(normalized);
|
|
57
71
|
if (!match)
|
|
58
72
|
return null;
|
|
59
73
|
const id = normalizeAcceptanceId(match[1]);
|
|
60
|
-
|
|
74
|
+
// Bold-wrapped ids leave their closing marker inside the captured text
|
|
75
|
+
// ("**AC-FE-INS-001**:xxx" captures "**:xxx"); strip emphasis remnants
|
|
76
|
+
// and leading separators so the bound text is clean prose.
|
|
77
|
+
const text = (match[2] ?? "")
|
|
78
|
+
.replace(/^[*_]+\s*/, "")
|
|
79
|
+
.replace(/^\s*[::]\s*/, "")
|
|
80
|
+
.trim() || id;
|
|
61
81
|
return { id, text };
|
|
62
82
|
}
|
|
63
83
|
function normalizeHeadingText(raw) {
|
package/dist/worker/cli.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { readFile } from "node:fs/promises";
|
|
3
|
+
import os from "node:os";
|
|
3
4
|
import path from "node:path";
|
|
4
5
|
import { fileURLToPath } from "node:url";
|
|
5
6
|
import { Command } from "commander";
|
|
7
|
+
import { createNpmUpdateClient } from "../shared/update/npm-client.js";
|
|
8
|
+
import { runConsoleDailyUpdateCheck } from "../shared/update/console-notifier.js";
|
|
6
9
|
import { readPackageVersion } from "../shared/package-metadata.js";
|
|
7
10
|
import YAML from "yaml";
|
|
8
11
|
import { LoopAgentClient } from "./loop-agent/loop-agent-client.js";
|
|
@@ -838,10 +841,33 @@ function parseOptionalTimeoutMs(raw) {
|
|
|
838
841
|
return value;
|
|
839
842
|
}
|
|
840
843
|
/** Console HTTP entry for bare `agent-worker console` (listen first, then optional browser open). */
|
|
841
|
-
async function runConsoleServe(options) {
|
|
844
|
+
export async function runConsoleServe(options) {
|
|
842
845
|
const { createConsoleServer } = await import("./console/server.js");
|
|
843
846
|
const { maybeOpenConsoleBrowser } = await import("./console/open-browser.js");
|
|
844
847
|
try {
|
|
848
|
+
try {
|
|
849
|
+
const packageRoot = path.dirname(path.dirname(path.dirname(fileURLToPath(import.meta.url))));
|
|
850
|
+
const currentVersion = readPackageVersion(packageRoot) ?? "0.0.0";
|
|
851
|
+
await runConsoleDailyUpdateCheck({
|
|
852
|
+
currentVersion,
|
|
853
|
+
packageRoot,
|
|
854
|
+
homeDir: os.homedir(),
|
|
855
|
+
cwd: options.repoRoot,
|
|
856
|
+
env: process.env,
|
|
857
|
+
stdinIsTTY: process.stdin.isTTY === true,
|
|
858
|
+
stderrIsTTY: process.stderr.isTTY === true,
|
|
859
|
+
client: createNpmUpdateClient({
|
|
860
|
+
currentVersion,
|
|
861
|
+
packageRoot,
|
|
862
|
+
cwd: options.repoRoot,
|
|
863
|
+
env: process.env,
|
|
864
|
+
}),
|
|
865
|
+
});
|
|
866
|
+
}
|
|
867
|
+
catch (error) {
|
|
868
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
869
|
+
process.stderr.write(`loop-agent 更新检查已跳过:${detail}\n`);
|
|
870
|
+
}
|
|
845
871
|
const server = await createConsoleServer({
|
|
846
872
|
repoRoot: options.repoRoot,
|
|
847
873
|
host: options.host,
|
|
@@ -857,6 +883,10 @@ async function runConsoleServe(options) {
|
|
|
857
883
|
noOpen: options.noOpen === true,
|
|
858
884
|
serverUrl: server.url,
|
|
859
885
|
});
|
|
886
|
+
if (options.holdUntilSignal === false) {
|
|
887
|
+
await server.close();
|
|
888
|
+
return;
|
|
889
|
+
}
|
|
860
890
|
await new Promise((resolve) => {
|
|
861
891
|
const shutdown = () => resolve();
|
|
862
892
|
process.once("SIGINT", shutdown);
|
|
@@ -270,6 +270,7 @@ export function parseChatImages(value) {
|
|
|
270
270
|
}
|
|
271
271
|
function gateMutation(req, deps) {
|
|
272
272
|
const failure = evaluateMutationGate(req, {
|
|
273
|
+
cookieName: deps.bootToken.cookieName,
|
|
273
274
|
bootToken: deps.bootToken.value,
|
|
274
275
|
confirmationToken: deps.bootToken.confirmationToken,
|
|
275
276
|
consoleOrigin: deps.getConsoleOrigin(),
|
|
@@ -35,6 +35,32 @@ import { hashDagBytes, } from "./dag-confirmation.js";
|
|
|
35
35
|
* framework invocations that don't embed the word "verify".
|
|
36
36
|
*/
|
|
37
37
|
export const STRUCTURED_VERIFICATION_COMMAND_RE = /\b(vitest|jest|playwright|cypress|pytest|py-test|go\s+test|cargo\s+test|npm\s+run\s+(lint|typecheck|test|verify)|npm\s+(test|run\s+test)|check-repo\.sh|loop-agent-standard-verify)\b/i;
|
|
38
|
+
/**
|
|
39
|
+
* Deterministic shell preset keys whose executors are themselves structured
|
|
40
|
+
* verification/gate materializers: their shell.commands[] is empty and the
|
|
41
|
+
* preset alone carries the verification authority (hash-bound artifact
|
|
42
|
+
* gates, evidence validators, report finalize). Mirrors the named-shell
|
|
43
|
+
* handler registry in shell-executor.ts so preset-only workflows
|
|
44
|
+
* (frontend-test finalize/reports, backend-test pipeline, verify-evidence
|
|
45
|
+
* gates) pass the G2 autonomous execution gate without depending on node-id
|
|
46
|
+
* naming conventions.
|
|
47
|
+
*/
|
|
48
|
+
export const STRUCTURED_VERIFICATION_SHELL_PRESETS = new Set([
|
|
49
|
+
"frontendTestCaseChecklist",
|
|
50
|
+
"frontendTestCaseManifest",
|
|
51
|
+
"frontendTestEvidenceValidation",
|
|
52
|
+
"frontendTestResultFinalize",
|
|
53
|
+
"frontendTestL5Report",
|
|
54
|
+
"frontendTestHtmlReport",
|
|
55
|
+
"frontendTestReports",
|
|
56
|
+
"backendTestPipeline",
|
|
57
|
+
"verifyEvidence",
|
|
58
|
+
"repairArtifactGate",
|
|
59
|
+
"jacocoCoverage",
|
|
60
|
+
"requirementCoverageGate",
|
|
61
|
+
"jsonArtifactGate",
|
|
62
|
+
"verdictGate",
|
|
63
|
+
]);
|
|
38
64
|
export const DEFAULT_EXECUTION_RECEIPT_TTL_MS = 30 * 60 * 1000;
|
|
39
65
|
function normalizePathEntry(entry) {
|
|
40
66
|
return entry.trim().replace(/\\/g, "/").replace(/^\.\//, "");
|
|
@@ -157,6 +183,13 @@ export function deriveEligibilityFactsFromDagSpec(input) {
|
|
|
157
183
|
return false;
|
|
158
184
|
if (/verify|verification/i.test(task.id ?? ""))
|
|
159
185
|
return true;
|
|
186
|
+
// Preset-only verification shells (empty commands[]): the preset key is
|
|
187
|
+
// the verification authority (frontend-test finalize, backend-test
|
|
188
|
+
// pipeline, evidence/artifact gates).
|
|
189
|
+
const shellRecord = task.shell;
|
|
190
|
+
const hasVerificationPreset = Object.keys(shellRecord).some((key) => STRUCTURED_VERIFICATION_SHELL_PRESETS.has(key));
|
|
191
|
+
if (hasVerificationPreset)
|
|
192
|
+
return true;
|
|
160
193
|
const commands = stringList(task.shell.commands);
|
|
161
194
|
return commands.some((command) => STRUCTURED_VERIFICATION_COMMAND_RE.test(command));
|
|
162
195
|
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { access, readFile } from "node:fs/promises";
|
|
2
2
|
import { buildOperatorCapabilitiesDocument, OFFICIAL_TAIL_ACTION_IDS, } from "../../shared/operator/capabilities.js";
|
|
3
|
+
import { DAG_RERUN_PROMPT_OVERRIDE_ENV, isDagRerunPromptOverrideEnabled, MAX_DAG_RERUN_PROMPT_OVERRIDE_CHARS, } from "../../shared/dag-prompt-override.js";
|
|
3
4
|
import { buildOperatorResult, operatorFailed, operatorSucceeded, } from "../../shared/operator/envelope.js";
|
|
4
5
|
import { CANONICAL_TASK_ID_PATTERN, formatLocalBusinessDate, getTaskPaths, loadTaskConfig, } from "../../task/runtime.js";
|
|
5
6
|
import { diffTaskContract, validateDraftForTask, } from "../../task/contract/index.js";
|
|
@@ -401,6 +402,85 @@ async function runReadCli(ctx, args, command) {
|
|
|
401
402
|
});
|
|
402
403
|
}
|
|
403
404
|
}
|
|
405
|
+
/**
|
|
406
|
+
* Parse and bound the experimental promptOverrides action param. The env
|
|
407
|
+
* switch is checked separately so disabled deployments fail closed before any
|
|
408
|
+
* staging happens. `mode` defaults to "replace"; "append" keeps the frozen
|
|
409
|
+
* base prompt and only appends the operator text as a marked addendum.
|
|
410
|
+
*/
|
|
411
|
+
function parsePromptOverrideParams(raw) {
|
|
412
|
+
if (raw === undefined || raw === null)
|
|
413
|
+
return { kind: "ok", overrides: [] };
|
|
414
|
+
if (!Array.isArray(raw)) {
|
|
415
|
+
return {
|
|
416
|
+
kind: "error",
|
|
417
|
+
message: "promptOverrides must be an array of {nodeId, prompt, mode?}",
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
if (raw.length === 0)
|
|
421
|
+
return { kind: "ok", overrides: [] };
|
|
422
|
+
if (raw.length > 8) {
|
|
423
|
+
return { kind: "error", message: "promptOverrides supports at most 8 nodes" };
|
|
424
|
+
}
|
|
425
|
+
const overrides = [];
|
|
426
|
+
const seen = new Set();
|
|
427
|
+
for (const item of raw) {
|
|
428
|
+
if (!item || typeof item !== "object") {
|
|
429
|
+
return {
|
|
430
|
+
kind: "error",
|
|
431
|
+
message: "promptOverrides entries must be objects {nodeId, prompt, mode?}",
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
const record = item;
|
|
435
|
+
const nodeId = typeof record.nodeId === "string" ? record.nodeId.trim() : "";
|
|
436
|
+
const prompt = typeof record.prompt === "string" ? record.prompt : "";
|
|
437
|
+
const mode = record.mode === undefined || record.mode === null
|
|
438
|
+
? undefined
|
|
439
|
+
: typeof record.mode === "string"
|
|
440
|
+
? record.mode
|
|
441
|
+
: "invalid";
|
|
442
|
+
if (!nodeId) {
|
|
443
|
+
return { kind: "error", message: "promptOverrides nodeId is required" };
|
|
444
|
+
}
|
|
445
|
+
if (seen.has(nodeId)) {
|
|
446
|
+
return {
|
|
447
|
+
kind: "error",
|
|
448
|
+
message: `promptOverrides contains duplicate nodeId: ${nodeId}`,
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
seen.add(nodeId);
|
|
452
|
+
if (mode !== undefined && mode !== "append" && mode !== "replace") {
|
|
453
|
+
return {
|
|
454
|
+
kind: "error",
|
|
455
|
+
message: `promptOverrides mode for ${nodeId} must be append or replace`,
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
if (!prompt.trim() || prompt.length > MAX_DAG_RERUN_PROMPT_OVERRIDE_CHARS) {
|
|
459
|
+
return {
|
|
460
|
+
kind: "error",
|
|
461
|
+
message: `promptOverrides prompt for ${nodeId} must be 1-${MAX_DAG_RERUN_PROMPT_OVERRIDE_CHARS} characters`,
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
overrides.push(mode === undefined ? { nodeId, prompt } : { nodeId, prompt, mode });
|
|
465
|
+
}
|
|
466
|
+
return { kind: "ok", overrides };
|
|
467
|
+
}
|
|
468
|
+
/** Stage override bodies under app-data and build `--prompt-override` CLI
|
|
469
|
+
* args. The flag carries the mode: `--prompt-override-append` for append. */
|
|
470
|
+
async function buildPromptOverrideCliArgs(ctx, overrides) {
|
|
471
|
+
const args = [];
|
|
472
|
+
for (const override of overrides) {
|
|
473
|
+
const staged = await stageUtf8Text(ctx.appData, override.prompt, {
|
|
474
|
+
extension: ".md",
|
|
475
|
+
label: `prompt-override-${override.nodeId}`,
|
|
476
|
+
});
|
|
477
|
+
const flag = override.mode === "append"
|
|
478
|
+
? "--prompt-override-append"
|
|
479
|
+
: "--prompt-override";
|
|
480
|
+
args.push(flag, `${override.nodeId}@${staged.path}`);
|
|
481
|
+
}
|
|
482
|
+
return args;
|
|
483
|
+
}
|
|
404
484
|
async function acceptOperation(ctx, input) {
|
|
405
485
|
const created = await ctx.operations.create({
|
|
406
486
|
clientRequestId: input.clientRequestId,
|
|
@@ -2078,12 +2158,35 @@ export async function dispatchOperatorAction(ctx, req) {
|
|
|
2078
2158
|
}
|
|
2079
2159
|
return accepted;
|
|
2080
2160
|
}
|
|
2161
|
+
case "dagPromptOverrideConfig": {
|
|
2162
|
+
// Experimental internal switch; the browser UI hides the prompt-edit
|
|
2163
|
+
// entry unless this reports enabled=true.
|
|
2164
|
+
return {
|
|
2165
|
+
kind: "sync",
|
|
2166
|
+
status: 200,
|
|
2167
|
+
body: operatorSucceeded(action, {
|
|
2168
|
+
enabled: isDagRerunPromptOverrideEnabled(),
|
|
2169
|
+
envVar: DAG_RERUN_PROMPT_OVERRIDE_ENV,
|
|
2170
|
+
}),
|
|
2171
|
+
};
|
|
2172
|
+
}
|
|
2081
2173
|
case "dagRerunPlan": {
|
|
2082
2174
|
const runId = str(p.runId);
|
|
2083
2175
|
const fromNode = str(p.fromNode);
|
|
2084
2176
|
if (!runId || !fromNode) {
|
|
2085
2177
|
return invalid(action, "runId and fromNode are required");
|
|
2086
2178
|
}
|
|
2179
|
+
const parsedOverrides = parsePromptOverrideParams(p.promptOverrides);
|
|
2180
|
+
if (parsedOverrides.kind === "error") {
|
|
2181
|
+
return invalid(action, parsedOverrides.message);
|
|
2182
|
+
}
|
|
2183
|
+
if (parsedOverrides.overrides.length > 0 &&
|
|
2184
|
+
!isDagRerunPromptOverrideEnabled()) {
|
|
2185
|
+
return invalid(action, `prompt override rerun is disabled; set ${DAG_RERUN_PROMPT_OVERRIDE_ENV}=1 to enable this experimental capability`);
|
|
2186
|
+
}
|
|
2187
|
+
const overrideArgs = parsedOverrides.overrides.length > 0
|
|
2188
|
+
? await buildPromptOverrideCliArgs(ctx, parsedOverrides.overrides)
|
|
2189
|
+
: [];
|
|
2087
2190
|
const body = await runReadCli(ctx, [
|
|
2088
2191
|
"dag",
|
|
2089
2192
|
"rerun",
|
|
@@ -2092,6 +2195,7 @@ export async function dispatchOperatorAction(ctx, req) {
|
|
|
2092
2195
|
"--from-node",
|
|
2093
2196
|
fromNode,
|
|
2094
2197
|
"--plan",
|
|
2198
|
+
...overrideArgs,
|
|
2095
2199
|
"--json",
|
|
2096
2200
|
], "dag rerun plan");
|
|
2097
2201
|
return { kind: "sync", status: body.ok ? 200 : 400, body };
|
|
@@ -2105,6 +2209,17 @@ export async function dispatchOperatorAction(ctx, req) {
|
|
|
2105
2209
|
if (!runId || !fromNode || !planHash || !reason || !clientRequestId) {
|
|
2106
2210
|
return invalid(action, "runId, fromNode, planHash, reason, and clientRequestId are required");
|
|
2107
2211
|
}
|
|
2212
|
+
const parsedOverrides = parsePromptOverrideParams(p.promptOverrides);
|
|
2213
|
+
if (parsedOverrides.kind === "error") {
|
|
2214
|
+
return invalid(action, parsedOverrides.message);
|
|
2215
|
+
}
|
|
2216
|
+
if (parsedOverrides.overrides.length > 0 &&
|
|
2217
|
+
!isDagRerunPromptOverrideEnabled()) {
|
|
2218
|
+
return invalid(action, `prompt override rerun is disabled; set ${DAG_RERUN_PROMPT_OVERRIDE_ENV}=1 to enable this experimental capability`);
|
|
2219
|
+
}
|
|
2220
|
+
const overrideArgs = parsedOverrides.overrides.length > 0
|
|
2221
|
+
? await buildPromptOverrideCliArgs(ctx, parsedOverrides.overrides)
|
|
2222
|
+
: [];
|
|
2108
2223
|
return acceptOperation(ctx, {
|
|
2109
2224
|
action,
|
|
2110
2225
|
actionParams: p,
|
|
@@ -2122,6 +2237,7 @@ export async function dispatchOperatorAction(ctx, req) {
|
|
|
2122
2237
|
clientRequestId,
|
|
2123
2238
|
"--reason",
|
|
2124
2239
|
reason,
|
|
2240
|
+
...overrideArgs,
|
|
2125
2241
|
"--json",
|
|
2126
2242
|
],
|
|
2127
2243
|
});
|
|
@@ -3,7 +3,7 @@ import { rewriteRecoveryOperatorError } from "./recovery-error-copy.js";
|
|
|
3
3
|
const MUTATION_GATE_COPY = [
|
|
4
4
|
{
|
|
5
5
|
pattern: /invalid (boot capability|confirmation header) token/i,
|
|
6
|
-
copy: "
|
|
6
|
+
copy: "页面凭证已失效(控制台可能已重启),请刷新页面(F5)后重试。",
|
|
7
7
|
},
|
|
8
8
|
{
|
|
9
9
|
pattern: /mutation requires boot capability cookie|mutation requires confirmation header token/i,
|
|
@@ -23,7 +23,7 @@ export function formatOperatorUserError(message) {
|
|
|
23
23
|
if (recovery)
|
|
24
24
|
return recovery;
|
|
25
25
|
if (/controller identity changed|package fingerprint changed|entry binary sha256 changed/i.test(raw)) {
|
|
26
|
-
return "控制器版本已变更(Console
|
|
26
|
+
return "控制器版本已变更(Console 启动后代码被重新编译,或提交了 package.json / bin / dist / skills 等指纹范围文件)。请重启 Console 进程使其重新加载当前构建后再重试本操作;一次会话中途不要重建控制器。";
|
|
27
27
|
}
|
|
28
28
|
if (/Pi session file missing or not openable|cannot reopen chat session .* no persisted Pi session file/i.test(raw)) {
|
|
29
29
|
return "该对话的 Pi 会话文件已丢失,无法恢复上下文。请新建对话继续(历史记录无法重开)。";
|