@tea-agent/loop-agent 0.18.1 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +2 -2
- package/CHANGELOG.md +33 -0
- package/README.md +4 -6
- package/dist/application/dag/generate-task-dag.js +12 -1
- package/dist/commands/init.js +3 -3
- package/dist/executors/shell-executor.js +188 -2
- package/dist/governance/exec-plans.js +4 -0
- package/dist/worker/cli.js +13 -12
- package/dist/worker/console/doctor.js +55 -2
- package/dist/worker/console/index.js +1 -0
- package/dist/worker/console/loopback.js +2 -2
- package/dist/worker/console/observe-link.js +7 -2
- package/dist/worker/console/operator-actions.js +30 -2
- package/dist/worker/console/operator-selection.js +92 -0
- package/dist/worker/console/operator-surface-health.js +23 -0
- package/dist/worker/console/recovery-cta.js +2 -2
- package/dist/worker/console/routes.js +45 -19
- package/dist/worker/console/security.js +29 -4
- package/dist/worker/console/server.js +106 -8
- package/dist/worker/console/static/assets/index-3vsjZJHq.js +16 -0
- package/dist/worker/console/static/assets/index-i1wV4LrY.css +1 -0
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/observe/routes.js +63 -21
- package/dist/worker/observe/server.js +3 -10
- package/dist/worker/observe/static/index.html +6 -3
- package/dist/worker/observe/static/styles.css +53 -0
- package/dist/workflows/dag/backend-test-markdown-workflow.js +163 -41
- package/dist/workflows/dag/backend-test-result-contract.js +30 -7
- package/dist/workflows/dag/frontend-prewrite-gate.js +77 -0
- package/dist/workflows/dag/frontend-repair.js +7 -1
- package/dist/workflows/dag/frontend-review-context.js +43 -0
- package/dist/workflows/dag/frontend-verification-trace.js +34 -15
- package/dist/workflows/dag/governance-profile.js +14 -6
- package/dist/workflows/dag/init-hybrid.js +143 -399
- package/dist/workflows/dag/types.js +30 -0
- package/dist/workflows/dag/validate.js +22 -1
- package/docs/README.md +4 -2
- package/docs/architecture/evolution.md +6 -6
- package/docs/architecture/worker-and-feature.md +9 -9
- package/docs/templates/agent-dag.schema.json +40 -0
- package/docs/templates/backend-test-dag.generate-pytest.prompt.md +23 -192
- package/docs/templates/backend-test-dag.json +8 -8
- package/docs/templates/backend-test-dag.review-cases.prompt.md +22 -75
- package/package.json +1 -1
- package/skills/agent-worker/references/agent-worker-operator.md +2 -2
- package/skills/frontend-implementation/references/node-contracts.md +6 -8
- package/skills/frontend-review/SKILL.md +5 -8
- package/skills/loop-agent/references/command-reference.md +4 -2
- package/skills/loop-agent/references/harness-policy.md +1 -1
- package/skills/loop-agent/references/hybrid-dag.md +2 -2
- package/dist/worker/console/static/assets/index-KUSib7aM.js +0 -16
- package/dist/worker/console/static/assets/index-ucIzpaGJ.css +0 -1
package/AGENTS.md
CHANGED
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
10. 检查 `git status --short --branch`。
|
|
55
55
|
11. 运行本次任务相关的最小基线验证。
|
|
56
56
|
12. 如果用户提到"后端测试"、"接口测试"、"pytest"、"自动化测试",在任务 `task.json` 中设置 `taskKind: "backend-test"` 再 `dag run-task`;不要用 `--profile backend-test`(CLI 不接受该值,专用模板只走 taskKind)。知识回写用 `taskKind: "knowledge-sync"`(须 `featureId`),图谱开荒用 `taskKind: "knowledge-graph-bootstrap"`。`--profile` 仅表示治理强度:`auto|minimal|standard|reviewed|supervised`。
|
|
57
|
-
13. 如果用户提到"看板"、"observe"、"监控面板"、"启动看板",使用 `agent-worker observe serve --repo . --port 8787`
|
|
57
|
+
13. 如果用户提到"看板"、"observe"、"监控面板"、"启动看板",使用 `agent-worker console serve --repo . --port 8790` 启动统一 Operator Console;`/inspect/` 提供只读检视。`agent-worker observe serve --repo . --port 8787` 仅为兼容入口。
|
|
58
58
|
14. 如果用户要求“合并 `<source>` 到 `<target>`”或“合并 origin/main 到当前分支”,先阅读 `docs/branch-merge-guideline.md`,按影响自动选择快速、标准或深度模式;始终冻结 source SHA、审查双方功能、运行 merge-tree、生成 source-SHA 合并报告,并在提交前再次 fetch 防止主干前进。
|
|
59
59
|
|
|
60
60
|
## 会话协议
|
|
@@ -89,7 +89,7 @@
|
|
|
89
89
|
- 长期决策写入 `docs/`,不要只留在聊天里。
|
|
90
90
|
- 分支合并遵循 `docs/branch-merge-guideline.md`;快速模式只用于可证明的低风险/no-op 合并,涉及冲突、init/package/runtime/release/public API 时必须升级为标准或深度模式。
|
|
91
91
|
- 后端测试、接口/API 测试、pytest 或明确的后端自动化测试,必须把 `.harness/tasks/<task-id>/task.json` 的 `taskKind` 设置为 `"backend-test"`,不得保留默认 `standard`。`backend-test` 是 `taskKind`,不是 `--profile` 的可选值;`dag run-task` 继续使用 `--profile auto` 选择治理等级。仅说“自动化测试”且前后端不明时,先根据任务源和项目技术栈判断,禁止无条件路由。
|
|
92
|
-
-
|
|
92
|
+
- 本地 Operator Console:`agent-worker console serve --repo . --port 8790`,访问 `http://127.0.0.1:8790/`;其中 `/inspect/` 为只读运行检视。默认绑定本机 `127.0.0.1`;可用 `--host 0.0.0.0` / `--debug`,不要直接暴露到公开网络。
|
|
93
93
|
- 面向使用者的新增、修改、删除或修复,应同步更新根目录 `CHANGELOG.md`;保持版本级摘要即可,不写过细技术细节。
|
|
94
94
|
- 面向用户的中文更新日志、README 和说明文档应使用自然、结果导向的表达:先说明用户能获得什么或问题如何改善,保留必要的命令和产品术语,避免逐字翻译、内部实现细节和无意义的中英混杂。
|
|
95
95
|
- 涉及 `loop-agent init` 或目标项目投影的改动,必须同步考虑目标项目生成物:`AGENTS.md`、`README.md`、`harness.json`、`ai_workspace/loop-agent/`、`scripts/`、`.agents/skills/`、`.harness/prompts`、`.gitignore`(loop-agent runtime managed block)和 npm 包内置 assets;目标项目根 `docs/` 和根 `skills/` 的旧投影需要由 `init update --apply-safe` 安全迁移或退役。
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,39 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.20.0] - 2026-07-23
|
|
6
|
+
|
|
7
|
+
### 新增
|
|
8
|
+
|
|
9
|
+
- streamline implementation DAG (#61)
|
|
10
|
+
- improve human-readable artifacts
|
|
11
|
+
|
|
12
|
+
### 修复
|
|
13
|
+
|
|
14
|
+
- relax traceability mapping
|
|
15
|
+
|
|
16
|
+
## [0.19.0] - 2026-07-23
|
|
17
|
+
|
|
18
|
+
### 重点更新
|
|
19
|
+
|
|
20
|
+
- Operator Console 统一整合 Operate 与 Inspect,在同一服务即可检视只读 Dashboard、DAG、时间线与 artifacts
|
|
21
|
+
- 原 Observe 服务进入兼容期,仍可运行但会输出迁移提示,引导平滑过渡到统一控制台
|
|
22
|
+
|
|
23
|
+
### 新增
|
|
24
|
+
|
|
25
|
+
- 新增统一 Operator Console:`console serve` 同时提供 Operate 与 Inspect,支持通过 `/inspect/` 直接检视运行详情与 DAG
|
|
26
|
+
- Console 健康检查接口升级,包含 Inspect 就绪状态与路由能力信息
|
|
27
|
+
- `console serve` 新增局域网绑定(`--host 0.0.0.0`)与调试日志(`--debug`)选项,并在非本地访问时保留安全校验
|
|
28
|
+
|
|
29
|
+
### 改进
|
|
30
|
+
|
|
31
|
+
- `frontend-implementation` 保留独立 contract/scout,并把 Mock 策略与 implementation contract 合入 plan,把写前授权、验证/失败评估、复验和 review context 分别收敛为组合 shell 节点。standard/high-risk 固定 15 个顶层节点、small-risk 13 个;绿色路径执行 11 个节点、7 次 Pi,同时保留唯一 prewrite 写入授权、同 writeSet repair、真实 diff review 与 fail-closed closeout。
|
|
32
|
+
- 优化操作与观测统一界面的导航与布局,合并确认与运行为「开始运行」,DAG 详情跳转路径更加准确
|
|
33
|
+
|
|
34
|
+
### 修复
|
|
35
|
+
|
|
36
|
+
- 修复执行计划索引将命名约定占位符(如 `YYYY-MM-DD-<topic>.md`)误判为缺失计划引用的问题
|
|
37
|
+
|
|
5
38
|
## [0.18.1] - 2026-07-22
|
|
6
39
|
|
|
7
40
|
### 重点更新
|
package/README.md
CHANGED
|
@@ -85,24 +85,22 @@ loop-agent dag doctor --run-id <run-id> --markdown
|
|
|
85
85
|
loop-agent dag report --run-id <run-id>
|
|
86
86
|
```
|
|
87
87
|
|
|
88
|
-
### Official 本地操作面(CLI +
|
|
88
|
+
### Official 本地操作面(CLI + 统一 Operator Console)
|
|
89
89
|
|
|
90
90
|
| 入口 | 命令 | 角色 |
|
|
91
91
|
| --- | --- | --- |
|
|
92
92
|
| 受治理 CLI | `loop-agent …` / `agent-worker …` | 权威写入与诊断 |
|
|
93
|
-
| Loop Operator Console | `agent-worker console serve --repo .` | Official 本地控制面(默认 `127.0.0.1:8790
|
|
94
|
-
| Observe | `agent-worker observe serve --repo . --port 8787` |
|
|
93
|
+
| Loop Operator Console | `agent-worker console serve --repo .` | Official 本地控制面(默认 `127.0.0.1:8790`,含 Operate 与 Inspect) |
|
|
94
|
+
| Observe | `agent-worker observe serve --repo . --port 8787` | 兼容期独立只读入口;不再作为推荐路径 |
|
|
95
95
|
|
|
96
96
|
```bash
|
|
97
97
|
# 终端 A:Console(任务/运行操作面;canonical mutation 只经 sibling loop-agent)
|
|
98
98
|
agent-worker console serve --repo .
|
|
99
99
|
agent-worker console doctor --repo .
|
|
100
100
|
|
|
101
|
-
# 终端 B:独立 Observe(不经 Console mount/proxy)
|
|
102
|
-
agent-worker observe serve --repo . --port 8787
|
|
103
101
|
```
|
|
104
102
|
|
|
105
|
-
访问 Console <http://127.0.0.1:8790
|
|
103
|
+
访问 Console <http://127.0.0.1:8790/>;顶部「检视」以及 `/inspect/` 提供原 Observe 的只读 Dashboard、DAG、时间线与 artifacts。需要保留的旧书签或脚本仍可在兼容窗口使用 Observe `:8787`。openCode 等主会话仍是 **Compatibility / Operator Assist**,不得升级为与 Official 同等保证。
|
|
106
104
|
|
|
107
105
|
## 核心边界
|
|
108
106
|
|
|
@@ -105,6 +105,17 @@ function isVerificationShellTask(task) {
|
|
|
105
105
|
const commands = resolveShellCommands(task.shell);
|
|
106
106
|
return commands.some((command) => /(vitest|npm run (lint|typecheck|test)|check-repo\.sh|loop-agent-standard-verify)/.test(command));
|
|
107
107
|
}
|
|
108
|
+
function resolveReviewPacketShellCommands(task) {
|
|
109
|
+
const bundle = task.shell?.frontendVerificationBundle;
|
|
110
|
+
if (bundle) {
|
|
111
|
+
return [
|
|
112
|
+
...bundle.mockCommands,
|
|
113
|
+
...bundle.staticCommands,
|
|
114
|
+
...bundle.behaviorCommands,
|
|
115
|
+
];
|
|
116
|
+
}
|
|
117
|
+
return resolveShellCommands(task.shell);
|
|
118
|
+
}
|
|
108
119
|
async function buildReviewPacket(input) {
|
|
109
120
|
const spec = parseDagSpec(JSON.parse(await readFile(input.dagPath, "utf-8")));
|
|
110
121
|
const writers = collectWriterTasksForPacket(spec).map((task) => {
|
|
@@ -123,7 +134,7 @@ async function buildReviewPacket(input) {
|
|
|
123
134
|
.filter(isVerificationShellTask)
|
|
124
135
|
.map((task) => ({
|
|
125
136
|
nodeId: task.id,
|
|
126
|
-
commands:
|
|
137
|
+
commands: resolveReviewPacketShellCommands(task),
|
|
127
138
|
}));
|
|
128
139
|
return {
|
|
129
140
|
profileRouting: {
|
package/dist/commands/init.js
CHANGED
|
@@ -783,13 +783,13 @@ function buildManagedAgentsBlock(input) {
|
|
|
783
783
|
"",
|
|
784
784
|
"### 运行看板(只读)",
|
|
785
785
|
"",
|
|
786
|
-
"
|
|
786
|
+
"启动统一 Operator Console(含只读 Inspect):",
|
|
787
787
|
"",
|
|
788
788
|
"```bash",
|
|
789
|
-
"agent-worker
|
|
789
|
+
"agent-worker console serve --repo . --port 8790",
|
|
790
790
|
"```",
|
|
791
791
|
"",
|
|
792
|
-
"浏览器打开 `http://127.0.0.1:
|
|
792
|
+
"浏览器打开 `http://127.0.0.1:8790/`;检视面为 `http://127.0.0.1:8790/inspect/`。默认绑定本机 `127.0.0.1`;可用 `--host 0.0.0.0` 做局域网访问(无网络鉴权,仅可信网络),`--debug` 输出请求日志。不要直接暴露到公开网络。`agent-worker observe serve` 仅为兼容入口。",
|
|
793
793
|
"",
|
|
794
794
|
"### DAG 诊断与收口",
|
|
795
795
|
"",
|
|
@@ -12,10 +12,12 @@ import { materializeFrontendTestResult, } from "../workflows/dag/frontend-test-r
|
|
|
12
12
|
import { formatFrontendVerificationTraceStdout, runFrontendVerificationTraceGate, } from "../workflows/dag/frontend-verification-trace.js";
|
|
13
13
|
import { formatFrontendWorktreeDiffStdout, runFrontendWorktreeDiffGate, } from "../workflows/dag/frontend-worktree-diff.js";
|
|
14
14
|
import { formatFrontendFailureAssessStdout, formatFrontendRepairContractStdout, runFrontendFailureAssessGate, runFrontendRepairContractGate, } from "../workflows/dag/frontend-repair.js";
|
|
15
|
+
import { formatFrontendPrewriteGateStdout, runFrontendPrewriteGate, } from "../workflows/dag/frontend-prewrite-gate.js";
|
|
16
|
+
import { formatFrontendReviewContextStdout, runFrontendReviewContextGate, } from "../workflows/dag/frontend-review-context.js";
|
|
15
17
|
import { formatTraceabilityGateStdout, materializeBackendTestCaseManifest, runBackendTestTraceabilityGate, } from "../workflows/dag/backend-test-case-manifest.js";
|
|
16
18
|
import { materializeBackendTestExecutionContract } from "../workflows/dag/backend-test-execution-contract.js";
|
|
17
19
|
import { materializeBackendTestResultFromRunDir, parseJunitXml } from "../workflows/dag/backend-test-result-contract.js";
|
|
18
|
-
import { inspectBackendTestEnvironment, requiredBackendMarkdownCaseAcIds, renderBackendTestFacts, renderBackendTestHtml, redactBackendTestOutput, validateBackendMarkdownCases, validateBackendMarkdownTraceability, writeRunReport, } from "../workflows/dag/backend-test-markdown-workflow.js";
|
|
20
|
+
import { collectBackendTestHumanCaseCatalog, inspectBackendTestEnvironment, requiredBackendMarkdownCaseAcIds, renderBackendTestFacts, renderBackendTestHtml, redactBackendTestOutput, validateBackendMarkdownCases, validateBackendMarkdownTraceability, writeRunReport, } from "../workflows/dag/backend-test-markdown-workflow.js";
|
|
19
21
|
import { buildBackendTestCanonicalResultFromInitialShellSnippet, materializeBackendTestClassification, } from "../workflows/dag/backend-test-classification-contract.js";
|
|
20
22
|
import { backendTestSemanticReviewSchema, materializeBackendTestSemanticReview, } from "../workflows/dag/backend-test-semantic-review-contract.js";
|
|
21
23
|
import { pathsChangedDuringRun, readGitStatusPorcelain, snapshotGitStatusPorcelain, validateShellWriteGuard, } from "./shell-write-guard.js";
|
|
@@ -356,14 +358,16 @@ async function executeBackendTestPipeline(input, meta) {
|
|
|
356
358
|
if (![0, 1].includes(pytestExitCode))
|
|
357
359
|
throw new Error(`pytest did not complete with a reportable exit code: ${pytestExitCode}`);
|
|
358
360
|
const parsed = parseJunitXml(junitContent);
|
|
361
|
+
const cases = await collectBackendTestHumanCaseCatalog(input.cwd);
|
|
359
362
|
const htmlContent = renderBackendTestHtml({
|
|
360
363
|
title: meta.spec.title,
|
|
361
364
|
parsed,
|
|
365
|
+
cases,
|
|
362
366
|
environmentSummary: await readFile(path.join(reportsDir, "backend-test-environment.md"), "utf8"),
|
|
363
367
|
traceabilitySummary: await readFile(path.join(reportsDir, "backend-test-traceability.md"), "utf8"),
|
|
364
368
|
});
|
|
365
369
|
const htmlPath = await writeRunReport(meta.runDir, "backend-test.html", htmlContent);
|
|
366
|
-
const facts = renderBackendTestFacts({ parsed, pytestExitCode, junitRelativePath: "reports/backend-test.junit.xml", htmlRelativePath: "reports/backend-test.html", junitContent, htmlContent });
|
|
370
|
+
const facts = renderBackendTestFacts({ parsed, cases, pytestExitCode, junitRelativePath: "reports/backend-test.junit.xml", htmlRelativePath: "reports/backend-test.html", junitContent, htmlContent });
|
|
367
371
|
const factsPath = await writeRunReport(meta.runDir, "backend-test-facts.md", facts);
|
|
368
372
|
const sanitizedOutputs = results.map((result) => redactBackendTestOutput(result.stdout));
|
|
369
373
|
outputs.push(...sanitizedOutputs, `html=${htmlPath}`, `facts=${factsPath}`, facts);
|
|
@@ -601,8 +605,190 @@ async function executePipelineCommands(input, meta, overrideCommands) {
|
|
|
601
605
|
}
|
|
602
606
|
return results;
|
|
603
607
|
}
|
|
608
|
+
async function executeFrontendVerificationBundle(input, meta) {
|
|
609
|
+
const started = Date.now();
|
|
610
|
+
const shell = input.task.shell;
|
|
611
|
+
const bundle = shell.frontendVerificationBundle;
|
|
612
|
+
const cwd = resolveShellCwd(input.cwd, shell.cwd);
|
|
613
|
+
const results = [];
|
|
614
|
+
let beforeStatus;
|
|
615
|
+
try {
|
|
616
|
+
beforeStatus = await readGitStatusPorcelain(input.cwd);
|
|
617
|
+
}
|
|
618
|
+
catch {
|
|
619
|
+
beforeStatus = undefined;
|
|
620
|
+
}
|
|
621
|
+
const groups = [
|
|
622
|
+
{ name: "mock", commands: bundle.mockCommands },
|
|
623
|
+
{ name: "static", commands: bundle.staticCommands },
|
|
624
|
+
{ name: "behavior", commands: bundle.behaviorCommands },
|
|
625
|
+
];
|
|
626
|
+
for (const group of groups) {
|
|
627
|
+
for (const command of group.commands) {
|
|
628
|
+
const commandNumber = results.length + 1;
|
|
629
|
+
const result = await executeShellCommand({
|
|
630
|
+
command,
|
|
631
|
+
cwd,
|
|
632
|
+
timeoutMs: shell.timeoutMs ?? DEFAULT_SHELL_TIMEOUT_MS,
|
|
633
|
+
envAllowlist: shell.envAllowlist,
|
|
634
|
+
dagRunMeta: { runDir: meta.runDir, runId: meta.runId },
|
|
635
|
+
outputArtifacts: {
|
|
636
|
+
stdoutPath: path.join(meta.runDir, input.task.id, "commands", `${commandNumber}.stdout.txt`),
|
|
637
|
+
stderrPath: path.join(meta.runDir, input.task.id, "commands", `${commandNumber}.stderr.txt`),
|
|
638
|
+
},
|
|
639
|
+
});
|
|
640
|
+
results.push(result);
|
|
641
|
+
if (!result.ok)
|
|
642
|
+
break;
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
if (beforeStatus !== undefined) {
|
|
646
|
+
const guard = await runShellWriteGuard({
|
|
647
|
+
rootCwd: input.cwd,
|
|
648
|
+
task: input.task,
|
|
649
|
+
beforeStatus,
|
|
650
|
+
});
|
|
651
|
+
if (!guard.ok) {
|
|
652
|
+
return {
|
|
653
|
+
ok: false,
|
|
654
|
+
stdout: "",
|
|
655
|
+
stderr: `write guard failed: ${guard.violations.join(", ")}`,
|
|
656
|
+
failureCategory: "write-guard",
|
|
657
|
+
durationMs: Date.now() - started,
|
|
658
|
+
};
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
await writeDagNodeTextArtifact(meta.runDir, input.task.id, "result.summary.md", buildShellResultSummaryMarkdown({
|
|
662
|
+
nodeId: input.task.id,
|
|
663
|
+
runId: meta.runId,
|
|
664
|
+
rootCwd: input.cwd,
|
|
665
|
+
results,
|
|
666
|
+
}));
|
|
667
|
+
const commandResults = results.map((result) => ({
|
|
668
|
+
ok: result.ok,
|
|
669
|
+
exitCode: result.exitCode,
|
|
670
|
+
failureCategory: result.failureCategory,
|
|
671
|
+
command: result.command,
|
|
672
|
+
}));
|
|
673
|
+
const firstFailure = results.find((result) => !result.ok);
|
|
674
|
+
let traceError;
|
|
675
|
+
try {
|
|
676
|
+
await runFrontendVerificationTraceGate({
|
|
677
|
+
runDir: meta.runDir,
|
|
678
|
+
workspaceRoot: input.cwd,
|
|
679
|
+
evidence: {
|
|
680
|
+
static: {
|
|
681
|
+
nodeId: input.task.id,
|
|
682
|
+
commandLabels: bundle.staticEvidence.commandLabels,
|
|
683
|
+
},
|
|
684
|
+
behavior: {
|
|
685
|
+
nodeId: input.task.id,
|
|
686
|
+
commandLabels: bundle.behaviorEvidence.commandLabels,
|
|
687
|
+
},
|
|
688
|
+
},
|
|
689
|
+
});
|
|
690
|
+
}
|
|
691
|
+
catch (error) {
|
|
692
|
+
traceError = error instanceof Error ? error : new Error(String(error));
|
|
693
|
+
}
|
|
694
|
+
if (bundle.mode === "repair") {
|
|
695
|
+
if (firstFailure || traceError) {
|
|
696
|
+
return {
|
|
697
|
+
ok: false,
|
|
698
|
+
stdout: summarizeCommandResults(results).stdout,
|
|
699
|
+
stderr: firstFailure?.stderr || traceError?.message || "frontend reverify failed",
|
|
700
|
+
failureCategory: firstFailure?.failureCategory ?? "invalid-output",
|
|
701
|
+
durationMs: Date.now() - started,
|
|
702
|
+
...{ commandResults },
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
return {
|
|
706
|
+
ok: true,
|
|
707
|
+
stdout: "Frontend reverify bundle: pass",
|
|
708
|
+
stderr: "",
|
|
709
|
+
failureCategory: "success",
|
|
710
|
+
durationMs: Date.now() - started,
|
|
711
|
+
...{ commandResults },
|
|
712
|
+
};
|
|
713
|
+
}
|
|
714
|
+
const failureFacts = [];
|
|
715
|
+
if (firstFailure) {
|
|
716
|
+
failureFacts.push({
|
|
717
|
+
nodeId: input.task.id,
|
|
718
|
+
record: {
|
|
719
|
+
status: "FINISHED",
|
|
720
|
+
failureCategory: firstFailure.failureCategory,
|
|
721
|
+
stdout: summarizeCommandResults(results).stdout,
|
|
722
|
+
stderr: firstFailure.stderr,
|
|
723
|
+
commandResults,
|
|
724
|
+
},
|
|
725
|
+
});
|
|
726
|
+
}
|
|
727
|
+
if (traceError) {
|
|
728
|
+
failureFacts.push({
|
|
729
|
+
nodeId: "frontend-verification-trace-shell",
|
|
730
|
+
record: {
|
|
731
|
+
status: "FINISHED",
|
|
732
|
+
failureCategory: "invalid-output",
|
|
733
|
+
stderr: traceError.message,
|
|
734
|
+
},
|
|
735
|
+
});
|
|
736
|
+
}
|
|
737
|
+
try {
|
|
738
|
+
const assessment = await runFrontendFailureAssessGate({
|
|
739
|
+
runDir: meta.runDir,
|
|
740
|
+
failureFacts,
|
|
741
|
+
});
|
|
742
|
+
await runFrontendRepairContractGate({ runDir: meta.runDir });
|
|
743
|
+
return {
|
|
744
|
+
ok: true,
|
|
745
|
+
stdout: formatFrontendFailureAssessStdout(assessment),
|
|
746
|
+
stderr: "",
|
|
747
|
+
failureCategory: "success",
|
|
748
|
+
durationMs: Date.now() - started,
|
|
749
|
+
...{ commandResults },
|
|
750
|
+
};
|
|
751
|
+
}
|
|
752
|
+
catch (error) {
|
|
753
|
+
return {
|
|
754
|
+
ok: false,
|
|
755
|
+
stdout: "",
|
|
756
|
+
stderr: error instanceof Error ? error.message : String(error),
|
|
757
|
+
failureCategory: "invalid-output",
|
|
758
|
+
durationMs: Date.now() - started,
|
|
759
|
+
...{ commandResults },
|
|
760
|
+
};
|
|
761
|
+
}
|
|
762
|
+
}
|
|
604
763
|
export async function executeDagShellNode(input, meta) {
|
|
605
764
|
const shell = input.task.shell;
|
|
765
|
+
if (shell?.frontendPrewriteGate) {
|
|
766
|
+
const started = Date.now();
|
|
767
|
+
try {
|
|
768
|
+
const result = await runFrontendPrewriteGate({
|
|
769
|
+
runDir: meta.runDir,
|
|
770
|
+
config: shell.frontendPrewriteGate,
|
|
771
|
+
sourceBinding: meta.spec.sourceBinding,
|
|
772
|
+
});
|
|
773
|
+
return { ok: true, stdout: formatFrontendPrewriteGateStdout(result), stderr: "", failureCategory: "success", durationMs: Date.now() - started };
|
|
774
|
+
}
|
|
775
|
+
catch (error) {
|
|
776
|
+
return { ok: false, stdout: "", stderr: error instanceof Error ? error.message : String(error), failureCategory: "invalid-output", durationMs: Date.now() - started };
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
if (shell?.frontendVerificationBundle) {
|
|
780
|
+
return executeFrontendVerificationBundle(input, meta);
|
|
781
|
+
}
|
|
782
|
+
if (shell?.frontendReviewContext) {
|
|
783
|
+
const started = Date.now();
|
|
784
|
+
try {
|
|
785
|
+
const result = await runFrontendReviewContextGate({ runDir: meta.runDir, workspaceRoot: input.cwd });
|
|
786
|
+
return { ok: true, stdout: formatFrontendReviewContextStdout(result), stderr: "", failureCategory: "success", durationMs: Date.now() - started };
|
|
787
|
+
}
|
|
788
|
+
catch (error) {
|
|
789
|
+
return { ok: false, stdout: "", stderr: error instanceof Error ? error.message : String(error), failureCategory: "invalid-output", durationMs: Date.now() - started };
|
|
790
|
+
}
|
|
791
|
+
}
|
|
606
792
|
if (shell?.backendTestPipeline) {
|
|
607
793
|
return executeBackendTestPipelineWithWriteGuard(input, meta);
|
|
608
794
|
}
|
|
@@ -177,6 +177,10 @@ function localIndexReference(rawReference) {
|
|
|
177
177
|
.replaceAll("\\", "/");
|
|
178
178
|
if (!normalized.endsWith(".md") || normalized.includes("/"))
|
|
179
179
|
return null;
|
|
180
|
+
// Naming-convention placeholders (e.g. `YYYY-MM-DD-<topic>.md`) and other
|
|
181
|
+
// unsafe stems are documentation, not index entries. Align with assertSafePlanId.
|
|
182
|
+
if (/[<>:"|?*]/.test(normalized))
|
|
183
|
+
return null;
|
|
180
184
|
return normalized;
|
|
181
185
|
}
|
|
182
186
|
function extractIndexReferences(indexContent) {
|
package/dist/worker/cli.js
CHANGED
|
@@ -559,9 +559,9 @@ export function buildAgentWorkerProgram() {
|
|
|
559
559
|
.command("serve")
|
|
560
560
|
.requiredOption("--repo <repo-root>", "Target repo root")
|
|
561
561
|
.option("--port <port>", "HTTP port", "8787")
|
|
562
|
-
.option("--host <host>", "Bind host (default 127.0.0.1; use 0.0.0.0 for LAN, no auth)", "127.0.0.1")
|
|
562
|
+
.option("--host <host>", "Bind host (default 127.0.0.1; use 0.0.0.0 for LAN, no network auth)", "127.0.0.1")
|
|
563
563
|
.option("--debug", "Log request source/method/path/status to stderr")
|
|
564
|
-
.description("Start read-only observe HTTP server")
|
|
564
|
+
.description("Start read-only observe HTTP server (compat entry; prefer console serve)")
|
|
565
565
|
.action(async (options) => {
|
|
566
566
|
const repoRoot = path.resolve(options.repo);
|
|
567
567
|
const server = await createObserveServer({
|
|
@@ -570,6 +570,9 @@ export function buildAgentWorkerProgram() {
|
|
|
570
570
|
port: Number.parseInt(options.port, 10),
|
|
571
571
|
debug: options.debug === true,
|
|
572
572
|
});
|
|
573
|
+
process.stderr.write("[OBSERVE_SERVE_DEPRECATED] agent-worker observe serve 作为独立看板入口将在兼容窗口后下线。\n" +
|
|
574
|
+
"请改用:agent-worker console serve --repo <repo> --port 8790\n" +
|
|
575
|
+
"统一 Operator Console 已包含原 Observe 的只读检视能力:http://127.0.0.1:8790/\n");
|
|
573
576
|
process.stdout.write(`${server.url}\n`);
|
|
574
577
|
await new Promise((resolve) => {
|
|
575
578
|
const shutdown = () => resolve();
|
|
@@ -593,22 +596,18 @@ export function buildAgentWorkerProgram() {
|
|
|
593
596
|
.command("serve")
|
|
594
597
|
.requiredOption("--repo <repo-root>", "Target repo root")
|
|
595
598
|
.option("--port <port>", "HTTP port", "8790")
|
|
596
|
-
.option("--host <host>", "Bind host (default 127.0.0.1;
|
|
599
|
+
.option("--host <host>", "Bind host (default 127.0.0.1; use 0.0.0.0 for LAN, no network auth)", "127.0.0.1")
|
|
600
|
+
.option("--debug", "Log request source/method/path/status to stderr")
|
|
597
601
|
.option("--app-data <path>", "Override Console application-data root (Draft/Operation/confirmation)")
|
|
598
|
-
.description("Start Loop Operator Console HTTP server (static SPA + Operator API)")
|
|
602
|
+
.description("Start Loop Operator Console HTTP server (Operate + Inspect; static SPA + Operator API)")
|
|
599
603
|
.action(async (options) => {
|
|
600
604
|
const { createConsoleServer } = await import("./console/server.js");
|
|
601
|
-
const { isLoopbackHost } = await import("./console/loopback.js");
|
|
602
|
-
if (!isLoopbackHost(options.host)) {
|
|
603
|
-
process.stderr.write(`console serve: non-loopback host "${options.host}" is not allowed (fail closed)\n`);
|
|
604
|
-
process.exitCode = 1;
|
|
605
|
-
return;
|
|
606
|
-
}
|
|
607
605
|
const repoRoot = path.resolve(options.repo);
|
|
608
606
|
const server = await createConsoleServer({
|
|
609
607
|
repoRoot,
|
|
610
608
|
host: options.host,
|
|
611
609
|
port: Number.parseInt(options.port, 10),
|
|
610
|
+
debug: options.debug === true,
|
|
612
611
|
appDataRoot: options.appData
|
|
613
612
|
? path.resolve(options.appData)
|
|
614
613
|
: undefined,
|
|
@@ -625,13 +624,15 @@ export function buildAgentWorkerProgram() {
|
|
|
625
624
|
.command("doctor")
|
|
626
625
|
.requiredOption("--repo <repo-root>", "Target repo root")
|
|
627
626
|
.option("--json", "Emit JSON report on stdout")
|
|
628
|
-
.option("--observe-url <url>", "
|
|
629
|
-
.
|
|
627
|
+
.option("--observe-url <url>", "Legacy external Observe base URL to probe (non-blocking)")
|
|
628
|
+
.option("--console-url <url>", "Unified Console base URL to probe")
|
|
629
|
+
.description("Report sibling identity, preflight, and unified Inspect readiness")
|
|
630
630
|
.action(async (options) => {
|
|
631
631
|
const { formatConsoleDoctorHuman, runConsoleDoctor } = await import("./console/doctor.js");
|
|
632
632
|
const report = await runConsoleDoctor({
|
|
633
633
|
repoRoot: path.resolve(options.repo),
|
|
634
634
|
observeBaseUrl: options.observeUrl,
|
|
635
|
+
consoleBaseUrl: options.consoleUrl,
|
|
635
636
|
});
|
|
636
637
|
if (options.json) {
|
|
637
638
|
process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
1
2
|
import { mkdtemp } from "node:fs/promises";
|
|
2
3
|
import os from "node:os";
|
|
3
4
|
import path from "node:path";
|
|
@@ -7,6 +8,8 @@ import { probePiReadiness, } from "./pi-readiness.js";
|
|
|
7
8
|
import { DEFAULT_OBSERVE_BASE_URL, OBSERVE_START_COMMAND, probeAndClassifyObserve, } from "./observe-health-match.js";
|
|
8
9
|
import { repoFingerprintV1 } from "./repo-fingerprint.js";
|
|
9
10
|
import { resolveSiblingLoopAgentBin } from "./sibling-controller.js";
|
|
11
|
+
import { defaultObserveStaticDir, ROUTES } from "../observe/routes.js";
|
|
12
|
+
import { deriveObserveRouteCapabilities } from "../observe/health.js";
|
|
10
13
|
async function probeObserve(url, localFingerprint, fetchImpl) {
|
|
11
14
|
const classified = await probeAndClassifyObserve({
|
|
12
15
|
baseUrl: url,
|
|
@@ -42,6 +45,48 @@ async function probeObserve(url, localFingerprint, fetchImpl) {
|
|
|
42
45
|
message: classified.message,
|
|
43
46
|
};
|
|
44
47
|
}
|
|
48
|
+
async function probeInspect(consoleBaseUrl, fetchImpl) {
|
|
49
|
+
const routeCapabilities = deriveObserveRouteCapabilities(ROUTES);
|
|
50
|
+
if (!consoleBaseUrl) {
|
|
51
|
+
const ready = existsSync(path.join(defaultObserveStaticDir(), "index.html"));
|
|
52
|
+
return {
|
|
53
|
+
status: ready ? "ready" : "degraded",
|
|
54
|
+
source: "static-assets",
|
|
55
|
+
routeCapabilities,
|
|
56
|
+
message: ready
|
|
57
|
+
? "Inspect 静态资源可用;启动 agent-worker console serve 以提供统一入口"
|
|
58
|
+
: "Inspect 静态资源缺失;请运行构建或检查发布包",
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
const url = `${consoleBaseUrl.replace(/\/+$/, "")}/api/health`;
|
|
62
|
+
try {
|
|
63
|
+
const response = await fetchImpl(url, {
|
|
64
|
+
method: "GET",
|
|
65
|
+
signal: AbortSignal.timeout(1500),
|
|
66
|
+
});
|
|
67
|
+
const body = (await response.json());
|
|
68
|
+
const ready = response.ok &&
|
|
69
|
+
body.ok === true &&
|
|
70
|
+
body.schemaVersion === 1 &&
|
|
71
|
+
body.inspect?.ready === true;
|
|
72
|
+
return {
|
|
73
|
+
status: ready ? "ready" : "degraded",
|
|
74
|
+
source: "console-health",
|
|
75
|
+
url,
|
|
76
|
+
routeCapabilities: body.inspect?.routeCapabilities ?? routeCapabilities,
|
|
77
|
+
message: ready ? undefined : "Console health 未确认 Inspect 已就绪",
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
return {
|
|
82
|
+
status: "degraded",
|
|
83
|
+
source: "console-health",
|
|
84
|
+
url,
|
|
85
|
+
routeCapabilities,
|
|
86
|
+
message: error instanceof Error ? error.message : String(error),
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
}
|
|
45
90
|
/**
|
|
46
91
|
* Run console doctor checks. Observe unavailability never fails the report.
|
|
47
92
|
*/
|
|
@@ -115,8 +160,14 @@ export async function runConsoleDoctor(options) {
|
|
|
115
160
|
message: "cannot run preflight without sibling loop-agent bin",
|
|
116
161
|
};
|
|
117
162
|
}
|
|
118
|
-
const
|
|
119
|
-
const observe =
|
|
163
|
+
const inspect = await probeInspect(options.consoleBaseUrl, options.fetchImpl ?? fetch);
|
|
164
|
+
const observe = options.observeBaseUrl
|
|
165
|
+
? await probeObserve(options.observeBaseUrl, fingerprint, options.fetchImpl ?? fetch)
|
|
166
|
+
: {
|
|
167
|
+
status: "unavailable",
|
|
168
|
+
url: `${DEFAULT_OBSERVE_BASE_URL}/api/health`,
|
|
169
|
+
message: "未探测兼容 Observe;统一 Inspect 不依赖 8787",
|
|
170
|
+
};
|
|
120
171
|
const pi = await probePiReadiness({
|
|
121
172
|
override: options.piReadinessProbe,
|
|
122
173
|
siblingControllerOk: Boolean(controllerIdentity && "packageVersion" in controllerIdentity),
|
|
@@ -133,6 +184,7 @@ export async function runConsoleDoctor(options) {
|
|
|
133
184
|
piReadiness: pi.state,
|
|
134
185
|
pi,
|
|
135
186
|
observe,
|
|
187
|
+
inspect,
|
|
136
188
|
generatedAt: new Date().toISOString(),
|
|
137
189
|
};
|
|
138
190
|
}
|
|
@@ -148,6 +200,7 @@ export function formatConsoleDoctorHuman(report) {
|
|
|
148
200
|
`pi.interviewSession: ${report.pi.probe.interviewSessionOk}`,
|
|
149
201
|
`preflight: ${report.preflight.ok ? `ok (${report.preflight.summary})` : `fail ${report.preflight.code}: ${report.preflight.message}`}`,
|
|
150
202
|
`observe: ${report.observe.status}${report.observe.status === "unavailable" || report.observe.status === "mismatch" ? ` (${report.observe.message})` : ""}`,
|
|
203
|
+
`inspect: ${report.inspect.status}${report.inspect.message ? ` (${report.inspect.message})` : ""}`,
|
|
151
204
|
];
|
|
152
205
|
if (report.observe.status === "unavailable" ||
|
|
153
206
|
report.observe.status === "mismatch") {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { createConsoleServer, defaultConsoleStaticDir } from "./server.js";
|
|
2
|
+
export { buildOperatorSurfaceHealthV1 } from "./operator-surface-health.js";
|
|
2
3
|
export { runConsoleDoctor, formatConsoleDoctorHuman } from "./doctor.js";
|
|
3
4
|
export { isLoopbackHost } from "./loopback.js";
|
|
4
5
|
export { repoFingerprintV1, normalizeWorktreeRealpath, } from "./repo-fingerprint.js";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Console-local loopback host check.
|
|
3
|
-
*
|
|
2
|
+
* Console-local loopback host check.
|
|
3
|
+
* Used for default bind safety messaging and mutation Host checks on loopback binds.
|
|
4
4
|
*/
|
|
5
5
|
export function isLoopbackHost(host) {
|
|
6
6
|
const normalized = host.trim().toLowerCase();
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Observe
|
|
3
|
-
*
|
|
2
|
+
* Deprecated Observe deep-link compatibility helpers.
|
|
3
|
+
* New unified-surface callers should use buildInspectDeepLink.
|
|
4
4
|
*/
|
|
5
|
+
import { buildInspectUrl, selectionFromObserveLinkTarget, } from "./operator-selection.js";
|
|
5
6
|
function stripTrailingSlash(baseUrl) {
|
|
6
7
|
return baseUrl.replace(/\/+$/, "");
|
|
7
8
|
}
|
|
@@ -31,3 +32,7 @@ export function buildObserveDeepLink(baseUrl, target) {
|
|
|
31
32
|
}
|
|
32
33
|
}
|
|
33
34
|
}
|
|
35
|
+
/** Build a canonical same-origin Inspect URL for the unified Operator surface. */
|
|
36
|
+
export function buildInspectDeepLink(origin, target) {
|
|
37
|
+
return buildInspectUrl(origin, selectionFromObserveLinkTarget(target));
|
|
38
|
+
}
|
|
@@ -5,7 +5,7 @@ import { getTaskPaths } from "../../task/runtime.js";
|
|
|
5
5
|
import { stageUtf8Text } from "./app-data.js";
|
|
6
6
|
import { ALL_CONFIRMATION_CHALLENGES, hashDagBytes, } from "./dag-confirmation.js";
|
|
7
7
|
import { capabilityForObserveTarget, DEFAULT_OBSERVE_BASE_URL, OBSERVE_START_COMMAND, probeAndClassifyObserve, } from "./observe-health-match.js";
|
|
8
|
-
import { buildObserveDeepLink, } from "./observe-link.js";
|
|
8
|
+
import { buildInspectDeepLink, buildObserveDeepLink, } from "./observe-link.js";
|
|
9
9
|
import { resolveLatestDagRunIdForTask } from "./resolve-dag-run-for-task.js";
|
|
10
10
|
import { repoFingerprintV1 } from "./repo-fingerprint.js";
|
|
11
11
|
import { scheduleOperation, } from "./operation-runner.js";
|
|
@@ -285,7 +285,8 @@ export async function dispatchOperatorAction(ctx, req) {
|
|
|
285
285
|
};
|
|
286
286
|
}
|
|
287
287
|
case "observeLink": {
|
|
288
|
-
const
|
|
288
|
+
const consoleOrigin = ctx.getConsoleOrigin?.();
|
|
289
|
+
const baseUrl = ctx.observeBaseUrl ?? consoleOrigin ?? DEFAULT_OBSERVE_BASE_URL;
|
|
289
290
|
const dagRunId = str(p.dagRunId);
|
|
290
291
|
const taskId = str(p.taskId);
|
|
291
292
|
const featureId = str(p.featureId);
|
|
@@ -320,6 +321,33 @@ export async function dispatchOperatorAction(ctx, req) {
|
|
|
320
321
|
const requiredCapability = target
|
|
321
322
|
? capabilityForObserveTarget(target)
|
|
322
323
|
: undefined;
|
|
324
|
+
const usesUnifiedSurface = Boolean(consoleOrigin) &&
|
|
325
|
+
(!ctx.observeBaseUrl ||
|
|
326
|
+
ctx.observeBaseUrl.replace(/\/+$/, "") ===
|
|
327
|
+
consoleOrigin?.replace(/\/+$/, ""));
|
|
328
|
+
if (usesUnifiedSurface) {
|
|
329
|
+
const inspectPath = target
|
|
330
|
+
? buildInspectDeepLink(consoleOrigin, target).slice(consoleOrigin.length)
|
|
331
|
+
: "/inspect/#/";
|
|
332
|
+
return {
|
|
333
|
+
kind: "sync",
|
|
334
|
+
status: 200,
|
|
335
|
+
body: operatorSucceeded("observeLink", {
|
|
336
|
+
url: target
|
|
337
|
+
? buildInspectDeepLink(consoleOrigin, target)
|
|
338
|
+
: `${consoleOrigin.replace(/\/+$/, "")}/inspect/#/`,
|
|
339
|
+
inspectPath,
|
|
340
|
+
status: "same-origin",
|
|
341
|
+
requiredCapability: requiredCapability ?? null,
|
|
342
|
+
dagRunId: target?.kind === "dag"
|
|
343
|
+
? target.dagRunId
|
|
344
|
+
: resolvedFromTask?.dagRunId,
|
|
345
|
+
taskId: taskId ?? undefined,
|
|
346
|
+
home: openHome || undefined,
|
|
347
|
+
fallbackToTask: fallbackToTask || undefined,
|
|
348
|
+
}),
|
|
349
|
+
};
|
|
350
|
+
}
|
|
323
351
|
let localFingerprint;
|
|
324
352
|
try {
|
|
325
353
|
localFingerprint = repoFingerprintV1(ctx.repoRoot);
|