@tea-agent/loop-agent 0.20.1-beta.0 → 0.20.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -0
- package/dist/application/dag/args.js +29 -0
- package/dist/application/dag/run-dag.js +3 -1
- package/dist/cli/command-definitions.js +15 -1
- package/dist/cli/program.js +11 -1
- package/dist/commands/dag-rerun-task.js +19 -0
- package/dist/commands/dag-rerun.js +111 -0
- package/dist/shared/operator/capabilities.js +54 -0
- package/dist/worker/console/index.js +1 -1
- package/dist/worker/console/inspect-split.js +82 -0
- package/dist/worker/console/operation-runner.js +3 -1
- package/dist/worker/console/operation-store.js +1 -0
- package/dist/worker/console/operator-actions.js +153 -2
- package/dist/worker/console/operator-user-error.js +10 -0
- package/dist/worker/console/pi-readiness.js +4 -0
- package/dist/worker/console/recovery-cta.js +116 -5
- package/dist/worker/console/recovery-selection.js +107 -0
- package/dist/worker/console/resolve-dag-run-for-task.js +50 -11
- package/dist/worker/console/routes.js +20 -0
- package/dist/worker/console/sibling-controller.js +12 -7
- package/dist/worker/console/static/assets/index-CUDke82y.js +18 -0
- package/dist/worker/console/static/assets/index-wSEksVSO.css +1 -0
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/observability/read-model.js +60 -0
- package/dist/worker/observe/static/index.html +1 -1
- package/dist/worker/run-task/run-task.js +7 -0
- package/dist/workflows/dag/frontend-test-result-contract.js +64 -0
- package/dist/workflows/dag/init-hybrid.js +90 -48
- package/dist/workflows/dag/node-execution.js +40 -6
- package/dist/workflows/dag/output-protocol.js +76 -0
- package/dist/workflows/dag/rerun-plan.js +611 -0
- package/dist/workflows/dag/rerun-run.js +497 -0
- package/dist/workflows/dag/rerun-task.js +284 -0
- package/dist/workflows/dag/retry-policy.js +20 -1
- package/dist/workflows/dag/runner.js +50 -0
- package/dist/workflows/dag/skill-snapshot.js +22 -3
- package/dist/workflows/dag/types.js +7 -0
- package/dist/workflows/dag/validate.js +11 -0
- package/dist/workflows/dag/workspace-checkpoint.js +163 -0
- package/docs/README.md +1 -0
- package/docs/templates/agent-dag.schema.json +17 -2
- package/docs/templates/frontend-test-case-checklist.md +16 -1
- package/docs/templates/frontend-test-dag.generate-cases.prompt.md +26 -2
- package/docs/templates/frontend-test-dag.json +65 -6
- package/docs/templates/frontend-test-dag.retrieve-context.prompt.md +4 -1
- package/package.json +1 -1
- package/skills/loop-agent/references/command-reference.md +3 -0
- package/skills/playwright-cli-case-generator/SKILL.md +35 -7
- package/dist/worker/console/static/assets/index-3vsjZJHq.js +0 -16
- package/dist/worker/console/static/assets/index-i1wV4LrY.css +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,13 +2,37 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.20.1] - 2026-07-24
|
|
6
|
+
|
|
5
7
|
### 新增
|
|
6
8
|
|
|
9
|
+
- LLM/provider 偶发失败时可在同节点自动重试;reviewer 漏写 `VERDICT` 等协议错误也会在节点内纠错重试,不必等下游 gate 才失败
|
|
10
|
+
- 新增 `loop-agent dag rerun`:在安全资格通过时,从选定节点重跑只读子图,并保留父 run 证据与 import 审计
|
|
11
|
+
- 新增 `loop-agent dag rerun-task`:一键完整重跑 standalone 任务(重新生成、校验并执行),不修改父 run
|
|
12
|
+
- Operator Console Recovery 提供明确动作:从节点重跑、完整重跑任务、重新排队 Worker Task(不再使用含糊的「重试」)
|
|
7
13
|
- 前端规范读取将知识库与 `openspec/` 作为并列来源:无论知识库是否可用或命中,均继续递归枚举 `<repoRoot>/openspec/**` 并读取索引与任务相关规范正文。`discoverFrontendProjectCapability` 在没有 `package.json` 或 `package.json` 不可读时仍能发现 `openspec/**` 规范候选。
|
|
8
14
|
- 前端预写门禁会校验 plan/design-review 的真实读取事件;生成期存在 `openspec` 规范候选但没有成功读取匹配文件时,在 writer 执行前阻断 DAG 并列出候选路径、检查节点和原因。
|
|
9
15
|
- Dashboard 规范证据 API/UI 分别展示知识库查询、`openspec` 检索和 `openspec` 成功读取证据,三种类别独立展示。
|
|
10
16
|
- 前端规范证据 API 新增 `openspecReads` 与 `openspecSearches` 字段,与通用 `specReads`/`specSearches` 并行。
|
|
11
17
|
|
|
18
|
+
### 改进
|
|
19
|
+
|
|
20
|
+
- Operator Console「恢复」面板自动拉取 DAG 运行与节点候选,优先展示失败项并以选择器填入,仍可手动输入
|
|
21
|
+
- 从节点重跑预检失败时展示真实安全资格阻断原因,不再误报为「无法获取 planHash」
|
|
22
|
+
- 恢复页长操作(完整重跑等)会回写终态成功/失败;不再只提示「已接受」后无下文
|
|
23
|
+
- 恢复原因按所选 run/节点与失败事实自动预填,并给出三步操作指引;手动编辑不会被覆盖
|
|
24
|
+
- 恢复页将「DAG 运行恢复」与「Worker Task 重排队」分成独立区域,避免把 runtime DAG 与 Task Pool 动作混在一起
|
|
25
|
+
- 恢复页去掉高亮「打开检视」,避免与顶部「观测」入口重复误点;需要时用「侧栏预览」或「全屏观测」
|
|
26
|
+
- 恢复页「侧栏预览」在本页打开可关闭;全屏观测顶栏改为「返回操作台」
|
|
27
|
+
- 修复侧栏预览:兼容 Observe 的 `nodeId`/`label`,并安全格式化 session-events(避免 React 渲染对象导致整页空白)
|
|
28
|
+
- 恢复 CTA:查看报告改走 `dag report`(按 run ID);推荐动作仅用橙色主按钮标出,悬停看说明
|
|
29
|
+
- 从节点重跑 / 完整重跑任务点击后,在按钮下方出现「确定 / 取消」确认条
|
|
30
|
+
- 恢复页不再展示仅弹 CLI 提示的按钮(如「重新生成契约/计划」)
|
|
31
|
+
- 「查看报告」在按钮下方展示交接摘要(失败分类与建议),不再只闪顶部提示
|
|
32
|
+
- 控制器指纹漂移错误改为中文提示:请重启 Console,勿在会话中途重建
|
|
33
|
+
- frontend-test 环境预检:在生成用例前对冻结的非生产 `baseUrl` 做 curl 可达性探测;不可达或缺少 curl 时以 `frontend-base-url-unreachable` / `curl-unavailable` 阻断 generate/map,并写入 `environmentProbe` 事实。节点 5 manifest 物化改为 ruleId 失败分类、map 配额预检与 temp+rename 原子写入;生成/清单/执行强制 playwright-cli-only,U/D 场景要求当前用户数据归属或 create/Mock/blocked。
|
|
34
|
+
- 前端测试 DAG 的证据自检改为按用例降级:个别缺失、空白或格式错误的证据会如实标为 blocked 并继续生成复盘报告;只有全部用例都未产生任何证据内容或证据路径不安全时才失败。
|
|
35
|
+
|
|
12
36
|
## [0.20.0] - 2026-07-23
|
|
13
37
|
|
|
14
38
|
### 新增
|
|
@@ -259,6 +259,7 @@ export function parseRunDagArgs(args, defaultCwd) {
|
|
|
259
259
|
let canvasName;
|
|
260
260
|
let canvasesDir;
|
|
261
261
|
let eventsJsonlPath;
|
|
262
|
+
let workerAssociation;
|
|
262
263
|
for (let i = 0; i < args.length; i += 1) {
|
|
263
264
|
const arg = args[i];
|
|
264
265
|
if (arg === "--dag") {
|
|
@@ -309,6 +310,12 @@ export function parseRunDagArgs(args, defaultCwd) {
|
|
|
309
310
|
else if (arg.startsWith("--events-jsonl=")) {
|
|
310
311
|
eventsJsonlPath = arg.slice("--events-jsonl=".length);
|
|
311
312
|
}
|
|
313
|
+
else if (arg === "--worker-association") {
|
|
314
|
+
workerAssociation = parseWorkerAssociation(args[++i]);
|
|
315
|
+
}
|
|
316
|
+
else if (arg.startsWith("--worker-association=")) {
|
|
317
|
+
workerAssociation = parseWorkerAssociation(arg.slice("--worker-association=".length));
|
|
318
|
+
}
|
|
312
319
|
else if (arg === "--json") {
|
|
313
320
|
// Accepted for operator/console callers; run-dag always emits JSON stdout.
|
|
314
321
|
}
|
|
@@ -336,8 +343,30 @@ export function parseRunDagArgs(args, defaultCwd) {
|
|
|
336
343
|
canvasName,
|
|
337
344
|
canvasesDir,
|
|
338
345
|
eventsJsonlPath,
|
|
346
|
+
...(workerAssociation ? { workerAssociation } : {}),
|
|
339
347
|
};
|
|
340
348
|
}
|
|
349
|
+
function parseWorkerAssociation(raw) {
|
|
350
|
+
if (!raw?.trim()) {
|
|
351
|
+
throw new Error("run-dag --worker-association requires JSON");
|
|
352
|
+
}
|
|
353
|
+
let parsed;
|
|
354
|
+
try {
|
|
355
|
+
parsed = JSON.parse(raw);
|
|
356
|
+
}
|
|
357
|
+
catch {
|
|
358
|
+
throw new Error("run-dag --worker-association must be valid JSON");
|
|
359
|
+
}
|
|
360
|
+
if (!parsed ||
|
|
361
|
+
typeof parsed !== "object" ||
|
|
362
|
+
parsed.schemaVersion !== 1 ||
|
|
363
|
+
typeof parsed.workerRunId !== "string" ||
|
|
364
|
+
typeof parsed.featureId !== "string" ||
|
|
365
|
+
typeof parsed.taskId !== "string") {
|
|
366
|
+
throw new Error("run-dag --worker-association requires {schemaVersion:1,workerRunId,featureId,taskId}");
|
|
367
|
+
}
|
|
368
|
+
return parsed;
|
|
369
|
+
}
|
|
341
370
|
export function parseDagReportArgs(args) {
|
|
342
371
|
let runId;
|
|
343
372
|
let lifecycle = "all";
|
|
@@ -87,8 +87,10 @@ export async function runDagUseCase(input) {
|
|
|
87
87
|
maxConcurrent: input.maxConcurrent,
|
|
88
88
|
runId: input.runId,
|
|
89
89
|
observer,
|
|
90
|
+
...(input.workerAssociation
|
|
91
|
+
? { workerAssociation: input.workerAssociation }
|
|
92
|
+
: {}),
|
|
90
93
|
});
|
|
91
|
-
await eventObserver?.flush();
|
|
92
94
|
const canvasError = await flushCanvasSafely(canvas);
|
|
93
95
|
if (resolvedCanvasPath) {
|
|
94
96
|
return {
|
|
@@ -36,6 +36,8 @@ import { runDagApprove } from "../commands/dag-approve.js";
|
|
|
36
36
|
import { runDagReject } from "../commands/dag-reject.js";
|
|
37
37
|
import { runDagReconcileTasks } from "../commands/dag-reconcile-tasks.js";
|
|
38
38
|
import { runDagReconcileRun } from "../commands/dag-reconcile-run.js";
|
|
39
|
+
import { runDagRerunTask } from "../commands/dag-rerun-task.js";
|
|
40
|
+
import { runDagRerun } from "../commands/dag-rerun.js";
|
|
39
41
|
import { runDagResume } from "../commands/dag-resume.js";
|
|
40
42
|
import { runDagDecisionInspect, runDagDecisionValidate, } from "../workflows/dag/decision-envelope.js";
|
|
41
43
|
import { runDagDoctor, runDagStatus } from "../workflows/dag/lifecycle.js";
|
|
@@ -131,6 +133,8 @@ const DAG_SUBCOMMANDS = [
|
|
|
131
133
|
"report",
|
|
132
134
|
"closeout-draft",
|
|
133
135
|
"reconcile-run",
|
|
136
|
+
"rerun",
|
|
137
|
+
"rerun-task",
|
|
134
138
|
"reconcile-tasks",
|
|
135
139
|
"final-verification",
|
|
136
140
|
"decision",
|
|
@@ -614,6 +618,16 @@ export const COMMAND_DEFINITIONS = [
|
|
|
614
618
|
await runDagReconcileRun(repoRoot, reconcileRunArgs);
|
|
615
619
|
return;
|
|
616
620
|
}
|
|
621
|
+
if (subcommand === "rerun") {
|
|
622
|
+
const rerunArgs = rest.filter((arg) => Boolean(arg));
|
|
623
|
+
await runDagRerun(repoRoot, rerunArgs);
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
626
|
+
if (subcommand === "rerun-task") {
|
|
627
|
+
const rerunTaskArgs = rest.filter((arg) => Boolean(arg));
|
|
628
|
+
await runDagRerunTask(repoRoot, rerunTaskArgs);
|
|
629
|
+
return;
|
|
630
|
+
}
|
|
617
631
|
if (subcommand === "reconcile-tasks") {
|
|
618
632
|
const reconcileArgs = rest.filter((arg) => Boolean(arg));
|
|
619
633
|
await runDagReconcileTasks(repoRoot, reconcileArgs);
|
|
@@ -647,7 +661,7 @@ export const COMMAND_DEFINITIONS = [
|
|
|
647
661
|
adapter: "required",
|
|
648
662
|
tier: "primary",
|
|
649
663
|
intent: "Execute a reviewed Agent DAG spec.",
|
|
650
|
-
usage: "run-dag --dag <path> [--cwd <dir>] [--init-only] [--dry-run] [--max-concurrent N] [--run-id id] [--canvas-path <abs-path> | --canvas <name> [--canvases-dir <dir>]] [--events-jsonl <path>]",
|
|
664
|
+
usage: "run-dag --dag <path> [--cwd <dir>] [--init-only] [--dry-run] [--max-concurrent N] [--run-id id] [--canvas-path <abs-path> | --canvas <name> [--canvases-dir <dir>]] [--events-jsonl <path>] [--worker-association <json>]",
|
|
651
665
|
handler: async ({ repoRoot, subcommand, rest }) => {
|
|
652
666
|
const runDagArgs = [subcommand, ...rest].filter((arg) => Boolean(arg));
|
|
653
667
|
await runRunDag(repoRoot, runDagArgs);
|
package/dist/cli/program.js
CHANGED
|
@@ -12,6 +12,8 @@ import { runDagFinalVerification } from "../commands/dag-final-verification.js";
|
|
|
12
12
|
import { runDagInitHybrid } from "../commands/dag-init-hybrid.js";
|
|
13
13
|
import { runDagReconcileTasks } from "../commands/dag-reconcile-tasks.js";
|
|
14
14
|
import { runDagReconcileRun } from "../commands/dag-reconcile-run.js";
|
|
15
|
+
import { runDagRerunTask } from "../commands/dag-rerun-task.js";
|
|
16
|
+
import { runDagRerun } from "../commands/dag-rerun.js";
|
|
15
17
|
import { runDagReject } from "../commands/dag-reject.js";
|
|
16
18
|
import { runDagCloseoutDraft, runDagReport } from "../commands/dag-report.js";
|
|
17
19
|
import { runDagResume } from "../commands/dag-resume.js";
|
|
@@ -346,6 +348,12 @@ async function runDagAction(repoRoot, subcommand, rest) {
|
|
|
346
348
|
case "reconcile-run":
|
|
347
349
|
await runDagReconcileRun(repoRoot, args);
|
|
348
350
|
return;
|
|
351
|
+
case "rerun":
|
|
352
|
+
await runDagRerun(repoRoot, args);
|
|
353
|
+
return;
|
|
354
|
+
case "rerun-task":
|
|
355
|
+
await runDagRerunTask(repoRoot, args);
|
|
356
|
+
return;
|
|
349
357
|
case "reconcile-tasks":
|
|
350
358
|
await runDagReconcileTasks(repoRoot, args);
|
|
351
359
|
return;
|
|
@@ -365,7 +373,7 @@ async function runDagAction(repoRoot, subcommand, rest) {
|
|
|
365
373
|
throw new Error("usage: dag decision <inspect|validate> --run-id <id> [--node-id <node-id>]");
|
|
366
374
|
}
|
|
367
375
|
default:
|
|
368
|
-
throw new Error("usage: dag <init-hybrid|run-task|validate|workflow-plan|workflow-validate|workflow-compile|approve|reject|resume|status|doctor|report|closeout-draft|reconcile-run|reconcile-tasks|final-verification|decision> ...");
|
|
376
|
+
throw new Error("usage: dag <init-hybrid|run-task|validate|workflow-plan|workflow-validate|workflow-compile|approve|reject|resume|status|doctor|report|closeout-draft|reconcile-run|rerun|rerun-task|reconcile-tasks|final-verification|decision> ...");
|
|
369
377
|
}
|
|
370
378
|
}
|
|
371
379
|
function compactArgs(args) {
|
|
@@ -507,6 +515,8 @@ function addDagCommand(program, defaultRepoRoot) {
|
|
|
507
515
|
"report",
|
|
508
516
|
"closeout-draft",
|
|
509
517
|
"reconcile-run",
|
|
518
|
+
"rerun",
|
|
519
|
+
"rerun-task",
|
|
510
520
|
"reconcile-tasks",
|
|
511
521
|
"final-verification",
|
|
512
522
|
], defaultRepoRoot);
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { parseDagRerunTaskArgs, rerunStandaloneTask, } from "../workflows/dag/rerun-task.js";
|
|
2
|
+
export { parseDagRerunTaskArgs, rerunStandaloneTask };
|
|
3
|
+
export async function runDagRerunTask(repoRoot, rawArgs) {
|
|
4
|
+
const parsed = parseDagRerunTaskArgs(rawArgs);
|
|
5
|
+
const result = await rerunStandaloneTask({
|
|
6
|
+
cwd: repoRoot,
|
|
7
|
+
parentRunId: parsed.parentRunId,
|
|
8
|
+
reason: parsed.reason,
|
|
9
|
+
requestId: parsed.requestId,
|
|
10
|
+
profile: parsed.profile,
|
|
11
|
+
profileExplicit: parsed.profileExplicit,
|
|
12
|
+
taskIdOverride: parsed.taskIdOverride,
|
|
13
|
+
execute: parsed.execute,
|
|
14
|
+
});
|
|
15
|
+
console.log(JSON.stringify(result, null, 2));
|
|
16
|
+
if (!result.ok) {
|
|
17
|
+
process.exitCode = result.mode === "blocked" ? 2 : 1;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { executeDagRerun, planDagRerun, } from "../workflows/dag/rerun-run.js";
|
|
2
|
+
export function parseDagRerunArgs(args) {
|
|
3
|
+
if (args.length === 0) {
|
|
4
|
+
throw new Error("usage: dag rerun --run-id <id> --from-node <node-id> --plan [--json] | dag rerun --run-id <id> --from-node <node-id> --plan-hash <hash> --request-id <key> --reason <text> [--json]");
|
|
5
|
+
}
|
|
6
|
+
let parentRunId;
|
|
7
|
+
let selectedNodeId;
|
|
8
|
+
let planOnly = false;
|
|
9
|
+
let planHash;
|
|
10
|
+
let requestId;
|
|
11
|
+
let reason;
|
|
12
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
13
|
+
const arg = args[index];
|
|
14
|
+
if (arg === "--run-id")
|
|
15
|
+
parentRunId = args[++index];
|
|
16
|
+
else if (arg.startsWith("--run-id="))
|
|
17
|
+
parentRunId = arg.slice("--run-id=".length);
|
|
18
|
+
else if (arg === "--from-node")
|
|
19
|
+
selectedNodeId = args[++index];
|
|
20
|
+
else if (arg.startsWith("--from-node="))
|
|
21
|
+
selectedNodeId = arg.slice("--from-node=".length);
|
|
22
|
+
else if (arg === "--plan")
|
|
23
|
+
planOnly = true;
|
|
24
|
+
else if (arg === "--plan-hash")
|
|
25
|
+
planHash = args[++index];
|
|
26
|
+
else if (arg.startsWith("--plan-hash="))
|
|
27
|
+
planHash = arg.slice("--plan-hash=".length);
|
|
28
|
+
else if (arg === "--request-id")
|
|
29
|
+
requestId = args[++index];
|
|
30
|
+
else if (arg.startsWith("--request-id="))
|
|
31
|
+
requestId = arg.slice("--request-id=".length);
|
|
32
|
+
else if (arg === "--reason")
|
|
33
|
+
reason = args[++index];
|
|
34
|
+
else if (arg.startsWith("--reason="))
|
|
35
|
+
reason = arg.slice("--reason=".length);
|
|
36
|
+
else if (arg === "--json")
|
|
37
|
+
continue;
|
|
38
|
+
else if (arg.startsWith("-"))
|
|
39
|
+
throw new Error(`unknown dag rerun flag: ${arg}`);
|
|
40
|
+
else
|
|
41
|
+
throw new Error(`unexpected positional argument: ${arg}`);
|
|
42
|
+
}
|
|
43
|
+
if (!parentRunId) {
|
|
44
|
+
throw new Error("dag rerun requires --run-id <id>");
|
|
45
|
+
}
|
|
46
|
+
if (!selectedNodeId) {
|
|
47
|
+
throw new Error("dag rerun requires --from-node <node-id>");
|
|
48
|
+
}
|
|
49
|
+
if (planOnly) {
|
|
50
|
+
if (planHash || requestId || reason) {
|
|
51
|
+
throw new Error("dag rerun --plan cannot be combined with execute flags");
|
|
52
|
+
}
|
|
53
|
+
return { parentRunId, selectedNodeId, planOnly: true };
|
|
54
|
+
}
|
|
55
|
+
if (!planHash?.trim()) {
|
|
56
|
+
throw new Error("dag rerun execute requires --plan-hash <hash>");
|
|
57
|
+
}
|
|
58
|
+
if (!requestId?.trim()) {
|
|
59
|
+
throw new Error("dag rerun execute requires --request-id <key>");
|
|
60
|
+
}
|
|
61
|
+
if (!reason?.trim()) {
|
|
62
|
+
throw new Error("dag rerun execute requires --reason <text>");
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
parentRunId,
|
|
66
|
+
selectedNodeId,
|
|
67
|
+
planOnly: false,
|
|
68
|
+
planHash: planHash.trim(),
|
|
69
|
+
requestId: requestId.trim(),
|
|
70
|
+
reason: reason.trim(),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
export async function runDagRerun(repoRoot, rawArgs) {
|
|
74
|
+
const parsed = parseDagRerunArgs(rawArgs);
|
|
75
|
+
if (parsed.planOnly) {
|
|
76
|
+
const plan = await planDagRerun({
|
|
77
|
+
cwd: repoRoot,
|
|
78
|
+
parentRunId: parsed.parentRunId,
|
|
79
|
+
selectedNodeId: parsed.selectedNodeId,
|
|
80
|
+
});
|
|
81
|
+
const result = {
|
|
82
|
+
ok: plan.eligible,
|
|
83
|
+
plan,
|
|
84
|
+
};
|
|
85
|
+
console.log(JSON.stringify(result, null, 2));
|
|
86
|
+
if (!plan.eligible) {
|
|
87
|
+
process.exitCode = 2;
|
|
88
|
+
}
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
const result = await executeDagRerun({
|
|
92
|
+
cwd: repoRoot,
|
|
93
|
+
parentRunId: parsed.parentRunId,
|
|
94
|
+
selectedNodeId: parsed.selectedNodeId,
|
|
95
|
+
planHash: parsed.planHash,
|
|
96
|
+
requestId: parsed.requestId,
|
|
97
|
+
reason: parsed.reason,
|
|
98
|
+
});
|
|
99
|
+
console.log(JSON.stringify(result, null, 2));
|
|
100
|
+
if (!result.ok) {
|
|
101
|
+
if (result.errorCode === "PLAN_DRIFT") {
|
|
102
|
+
process.exitCode = 3;
|
|
103
|
+
}
|
|
104
|
+
else if (result.errorCode === "INELIGIBLE") {
|
|
105
|
+
process.exitCode = 2;
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
process.exitCode = 1;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
@@ -167,6 +167,16 @@ export function buildOperatorCapabilitiesDocument() {
|
|
|
167
167
|
requiredErrorCodes: ["INVALID_INPUT", "OPERATION_NEEDS_RECONCILE"],
|
|
168
168
|
description: "Repo doctor (registry entry; envelope migration follow-up).",
|
|
169
169
|
},
|
|
170
|
+
{
|
|
171
|
+
action: "dagReport",
|
|
172
|
+
cli: "loop-agent dag report --run-id <runId> --json",
|
|
173
|
+
kind: "read",
|
|
174
|
+
inputSchemaVersion: 1,
|
|
175
|
+
resultSchemaVersion: 1,
|
|
176
|
+
envelopeSchemaVersion: 1,
|
|
177
|
+
requiredErrorCodes: ["NOT_FOUND", "INVALID_INPUT"],
|
|
178
|
+
description: "DAG run handoff report by run id.",
|
|
179
|
+
},
|
|
170
180
|
{
|
|
171
181
|
action: "status",
|
|
172
182
|
cli: "loop-agent status <taskId> --json",
|
|
@@ -226,6 +236,50 @@ export function buildOperatorCapabilitiesDocument() {
|
|
|
226
236
|
],
|
|
227
237
|
description: "Execute reviewed DAG; revalidate binding before writer start.",
|
|
228
238
|
},
|
|
239
|
+
{
|
|
240
|
+
action: "dagRerunPlan",
|
|
241
|
+
cli: "loop-agent dag rerun --run-id <id> --from-node <node> --plan --json",
|
|
242
|
+
kind: "read",
|
|
243
|
+
inputSchemaVersion: 1,
|
|
244
|
+
resultSchemaVersion: 1,
|
|
245
|
+
envelopeSchemaVersion: 1,
|
|
246
|
+
requiredErrorCodes: ["INVALID_INPUT", "NOT_FOUND"],
|
|
247
|
+
description: "Read-only subgraph rerun plan for confirmation (no mutation).",
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
action: "dagRerun",
|
|
251
|
+
cli: "loop-agent dag rerun --run-id <id> --from-node <node> --plan-hash <hash> --request-id <id> --reason <text> --json",
|
|
252
|
+
kind: "long-running",
|
|
253
|
+
inputSchemaVersion: 1,
|
|
254
|
+
resultSchemaVersion: 1,
|
|
255
|
+
envelopeSchemaVersion: 1,
|
|
256
|
+
requiredErrorCodes: [
|
|
257
|
+
...COMMON_MUTATION_ERRORS,
|
|
258
|
+
"BINDING_DRIFT",
|
|
259
|
+
"INVALID_INPUT",
|
|
260
|
+
],
|
|
261
|
+
description: "Execute continuation run from effective node with confirmed plan hash.",
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
action: "standaloneTaskRerun",
|
|
265
|
+
cli: "loop-agent dag rerun-task --run-id <id> --reason <text> --request-id <id> [--profile auto] [--task-id <id>] --json",
|
|
266
|
+
kind: "long-running",
|
|
267
|
+
inputSchemaVersion: 1,
|
|
268
|
+
resultSchemaVersion: 1,
|
|
269
|
+
envelopeSchemaVersion: 1,
|
|
270
|
+
requiredErrorCodes: [...COMMON_MUTATION_ERRORS, "INVALID_INPUT"],
|
|
271
|
+
description: "Full standalone task regenerate → validate → execute with parent lineage.",
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
action: "workerTaskRetry",
|
|
275
|
+
cli: "agent-worker task retry <task-id> --feature-id <feature-id> --repo . --reason <text>",
|
|
276
|
+
kind: "mutation",
|
|
277
|
+
inputSchemaVersion: 1,
|
|
278
|
+
resultSchemaVersion: 1,
|
|
279
|
+
envelopeSchemaVersion: 1,
|
|
280
|
+
requiredErrorCodes: [...COMMON_MUTATION_ERRORS, "INVALID_INPUT"],
|
|
281
|
+
description: "Requeue failed Task Pool task (Failed → Ready) via in-package pool store.",
|
|
282
|
+
},
|
|
229
283
|
];
|
|
230
284
|
return {
|
|
231
285
|
schemaVersion: 1,
|
|
@@ -5,7 +5,7 @@ export { isLoopbackHost } from "./loopback.js";
|
|
|
5
5
|
export { repoFingerprintV1, normalizeWorktreeRealpath, } from "./repo-fingerprint.js";
|
|
6
6
|
export { buildObserveDeepLink } from "./observe-link.js";
|
|
7
7
|
export { classifyObserveHealth, probeAndClassifyObserve, capabilityForObserveTarget, OBSERVE_START_COMMAND, DEFAULT_OBSERVE_BASE_URL, } from "./observe-health-match.js";
|
|
8
|
-
export { allowedRecoveryCtas, classifyRecoveryFact, recoveryMatrixInvariants, } from "./recovery-cta.js";
|
|
8
|
+
export { allowedRecoveryCtas, classifyRecoveryFact, partitionRecoveryCtas, recoveryMatrixInvariants, } from "./recovery-cta.js";
|
|
9
9
|
export { createClosedResourceLoader, createInterviewResourceLoader, tryActivateTool, assertOfficialToolAllowed, listOfficialTools, OFFICIAL_DENIED_TOOL_IDS, INTERVIEW_ALLOWED_TOOLS, INTERVIEW_DENIED_TOOLS, authorizeInterviewTool, } from "./resource-loader.js";
|
|
10
10
|
export { resolveSiblingLoopAgentBin } from "./sibling-controller.js";
|
|
11
11
|
export { openConsoleAppData, defaultConsoleAppDataRoot } from "./app-data.js";
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
function asDisplayText(value) {
|
|
2
|
+
if (value == null)
|
|
3
|
+
return null;
|
|
4
|
+
if (typeof value === "string") {
|
|
5
|
+
const trimmed = value.trim();
|
|
6
|
+
return trimmed || null;
|
|
7
|
+
}
|
|
8
|
+
if (typeof value === "number" || typeof value === "boolean") {
|
|
9
|
+
return String(value);
|
|
10
|
+
}
|
|
11
|
+
if (typeof value === "object") {
|
|
12
|
+
const record = value;
|
|
13
|
+
if (typeof record.text === "string" && record.text.trim()) {
|
|
14
|
+
return record.text.trim();
|
|
15
|
+
}
|
|
16
|
+
if (typeof record.errorMessage === "string" && record.errorMessage.trim()) {
|
|
17
|
+
return record.errorMessage.trim();
|
|
18
|
+
}
|
|
19
|
+
if (Array.isArray(record.content)) {
|
|
20
|
+
const parts = record.content
|
|
21
|
+
.map((part) => {
|
|
22
|
+
if (typeof part === "string")
|
|
23
|
+
return part;
|
|
24
|
+
if (part && typeof part === "object") {
|
|
25
|
+
const item = part;
|
|
26
|
+
if (typeof item.text === "string")
|
|
27
|
+
return item.text;
|
|
28
|
+
}
|
|
29
|
+
return "";
|
|
30
|
+
})
|
|
31
|
+
.filter(Boolean);
|
|
32
|
+
if (parts.length)
|
|
33
|
+
return parts.join("\n").slice(0, 240);
|
|
34
|
+
}
|
|
35
|
+
if (typeof record.role === "string") {
|
|
36
|
+
return `${record.role} 消息`;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
export function formatInspectTimelineEvent(event) {
|
|
42
|
+
const time = asDisplayText(event.at) ??
|
|
43
|
+
asDisplayText(event.timestamp) ??
|
|
44
|
+
asDisplayText(event.recordedAt) ??
|
|
45
|
+
"";
|
|
46
|
+
const text = asDisplayText(event.errorMessage) ??
|
|
47
|
+
asDisplayText(event.label) ??
|
|
48
|
+
asDisplayText(event.type) ??
|
|
49
|
+
asDisplayText(event.message) ??
|
|
50
|
+
"运行事件";
|
|
51
|
+
return time ? `${time} · ${text}` : text;
|
|
52
|
+
}
|
|
53
|
+
export function dagNodeKey(node) {
|
|
54
|
+
return String(node.nodeId ?? node.id ?? "").trim();
|
|
55
|
+
}
|
|
56
|
+
export function dagNodeLabel(node) {
|
|
57
|
+
const key = dagNodeKey(node);
|
|
58
|
+
return String(node.label ?? node.name ?? (key || "未命名节点"));
|
|
59
|
+
}
|
|
60
|
+
export function pickInspectNode(nodes, preferredNodeId) {
|
|
61
|
+
if (nodes.length === 0)
|
|
62
|
+
return undefined;
|
|
63
|
+
const preferred = preferredNodeId?.trim();
|
|
64
|
+
if (preferred) {
|
|
65
|
+
const match = nodes.find((node) => dagNodeKey(node) === preferred);
|
|
66
|
+
if (match)
|
|
67
|
+
return match;
|
|
68
|
+
}
|
|
69
|
+
const failed = nodes.find((node) => {
|
|
70
|
+
const status = String(node.status ?? "").toLowerCase();
|
|
71
|
+
return (status.includes("fail") ||
|
|
72
|
+
status.includes("error") ||
|
|
73
|
+
status === "blocked");
|
|
74
|
+
});
|
|
75
|
+
if (failed)
|
|
76
|
+
return failed;
|
|
77
|
+
const running = nodes.find((node) => {
|
|
78
|
+
const status = String(node.status ?? "").toLowerCase();
|
|
79
|
+
return status === "running" || status === "in_progress" || status === "active";
|
|
80
|
+
});
|
|
81
|
+
return running ?? nodes[0];
|
|
82
|
+
}
|
|
@@ -30,7 +30,9 @@ export async function runOperation(operationId, deps) {
|
|
|
30
30
|
});
|
|
31
31
|
stateEvent(deps.events, op, "starting", "spawning sibling loop-agent");
|
|
32
32
|
const run = deps.runCommand ??
|
|
33
|
-
((cliArgs, options) =>
|
|
33
|
+
((cliArgs, options) => op.externalCommand
|
|
34
|
+
? deps.client.runExternal(op.externalCommand, cliArgs, options)
|
|
35
|
+
: deps.client.run(cliArgs, options));
|
|
34
36
|
let finished;
|
|
35
37
|
try {
|
|
36
38
|
const result = await run(args, {
|
|
@@ -55,6 +55,7 @@ export class OperationStore {
|
|
|
55
55
|
createdAt: now,
|
|
56
56
|
taskId: input.taskId,
|
|
57
57
|
cliArgs: input.cliArgs,
|
|
58
|
+
...(input.externalCommand ? { externalCommand: input.externalCommand } : {}),
|
|
58
59
|
};
|
|
59
60
|
await writeSecureJson(operationFile(this.appData, operationId), operation);
|
|
60
61
|
await writeSecureJson(indexPath, { operationId, payloadHash });
|