@tea-agent/loop-agent 0.18.0 → 0.19.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 +29 -73
- package/README.md +4 -6
- package/dist/commands/docs-audit.js +1 -0
- package/dist/commands/init.js +3 -3
- 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 +58 -33
- package/dist/workflows/dag/init-hybrid.js +3 -3
- package/docs/README.md +3 -2
- package/docs/architecture/evolution.md +6 -6
- package/docs/architecture/worker-and-feature.md +9 -9
- package/docs/templates/backend-test-dag.json +4 -4
- package/package.json +1 -1
- package/skills/agent-worker/references/agent-worker-operator.md +2 -2
- package/skills/loop-agent/references/command-reference.md +4 -2
- package/skills/loop-agent/references/harness-policy.md +1 -1
- 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,86 +2,42 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## [0.19.0] - 2026-07-23
|
|
6
6
|
|
|
7
|
-
###
|
|
7
|
+
### 重点更新
|
|
8
8
|
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
- `backend-test` 的 pre-run Spine 审计不再要求尚未执行的 `artifacts/requirement-coverage.json`:未覆盖需求在该 taskKind 下记为 warning,并交给运行内 Markdown validation 与 traceability gate 确定性阻断;普通实现任务仍保持 error/fail-closed。
|
|
14
|
-
- `backend-test` Markdown 用例门禁现在从 DAG `sourceBinding` 精确识别 `Source References`,支持反引号以及中英文括号/标点,不再将说明文字吞入路径;未绑定引用、路径前缀伪装和绑定文件缺失仍然 fail-closed。
|
|
15
|
-
- `backend-test` Markdown writer/reviewer 的 reference index 现在同时提供 task-relative 引用路径与 repo-relative `readPath`,Pi 会直接只读 `.harness/tasks/<taskId>/source/**` 的绑定文件,不再把 `source/references/**` 误读为仓库根路径,也不会在读取失败后回退到 `docs/**` 替代文件。
|
|
16
|
-
- `backend-test` Markdown 用例门禁会通过 DAG `sourceBinding` 将 `source/需求.md`、`source/执行约束.md` 和 `source/references/**` 解析到对应任务目录,不再错误地相对仓库根查找,也不会接受未绑定的同名仓库文件。
|
|
17
|
-
- `backend-test` traceability 门禁现在优先用 Markdown 中已知 Case ID 匹配 `test_BE_<MODULE>_<NNN>_` 函数前缀,不再把函数描述中的 `100`、`400`、`404` 等三位数字误识别为 Case ID。
|
|
18
|
-
- `backend-test` traceability 门禁允许仅包裹 cleanup 调用的 `except requests.RequestException: pass`,避免 best-effort 资源清理被误报;bare/宽异常、`AssertionError` 及非 cleanup 主体仍然 fail-closed。
|
|
9
|
+
- Operator Console 统一整合 Operate 与 Inspect,在同一服务即可检视只读 Dashboard、DAG、时间线与 artifacts
|
|
10
|
+
- 原 Observe 服务进入兼容期,仍可运行但会输出迁移提示,引导平滑过渡到统一控制台
|
|
11
|
+
|
|
12
|
+
### 新增
|
|
19
13
|
|
|
20
|
-
-
|
|
21
|
-
-
|
|
22
|
-
-
|
|
23
|
-
- `frontend-mock-assess-pi` 的 `MOCK_STRATEGY:` 协议行规范化现在可处理列表、引用、inline code、中文冒号、`Mock strategy` 标签和标签/值分行等常见格式漂移;仍只接受明确 Mock strategy 标签和五态白名单,不从普通正文猜默认策略。
|
|
14
|
+
- 新增统一 Operator Console:`console serve` 同时提供 Operate 与 Inspect,支持通过 `/inspect/` 直接检视运行详情与 DAG
|
|
15
|
+
- Console 健康检查接口升级,包含 Inspect 就绪状态与路由能力信息
|
|
16
|
+
- `console serve` 新增局域网绑定(`--host 0.0.0.0`)与调试日志(`--debug`)选项,并在非本地访问时保留安全校验
|
|
24
17
|
|
|
25
18
|
### 改进
|
|
26
19
|
|
|
27
|
-
-
|
|
28
|
-
|
|
29
|
-
- **Console Phase 3(Observe 协作)**:
|
|
30
|
-
- Observe `GET /api/health` 升级为 versioned DTO(`schemaVersion: 1`、`repoFingerprint` 复用 Console `repoFingerprintV1`、package name/version、由 ROUTES 派生的 `routeCapabilities`)。
|
|
31
|
-
- Console `observeLink` fail-closed:仅 health **match**(同仓 fingerprint + schema + 目标 kind 所需 capability)时返回深链;offline / mismatch 返回诊断与 `agent-worker observe serve --repo . --port 8787`,不打开错误 URL。
|
|
32
|
-
- Doctor 的 Observe 探测扩展 match / mismatch / unavailable(Observe 仍非阻塞,doctor `ok` 保持)。
|
|
33
|
-
- Recovery CTA 矩阵(report / doctor / decision / resume / reconcile / regenerate / observeLink):**无** Cancel、**无**主 CTA「直接改代码」。
|
|
34
|
-
- Console 与 Observe 仍为独立进程;不 mount、不 proxy、不复制第二套 read model。
|
|
35
|
-
- Phase 1 Console skeleton:`agent-worker console serve|doctor`(默认 loopback `127.0.0.1:8790`、非 loopback fail-closed)、Vite SPA 静态产物 + Node health/static server、boot capability cookie + mutation Host/Origin 门禁、封闭 ResourceLoader 骨架、sibling `loop-agent` identity doctor、`repoFingerprintV1` / Observe 深链 builder、以及 `UPSTREAM.md`(pi-web 源 commit)与预算 report。
|
|
36
|
-
- Phase 0.5 operator surface repair:`buildOperatorCapabilitiesDocument()` 在权威 `src/shared/operator` 上补齐 Console 协商块 `dag`(`generatedSpecVersion: 4`、`requiresTaskContractBinding: true`,与 `dagSpecVersions` 同源常量),并删除冲突的 `src/task/operator` 双实现;CLI `--json` 继续写 `process.stdout`,测试侧改为捕获 stdout。
|
|
37
|
-
- Nightly 自动发布改为双基线:`lastTag`(最新稳定 `vX.Y.Z`)只负责未发布增量、breaking 检测与 release notes;当前 minor 的 `vX.Y.0`(缺失则回退 `lastTag`)只累计严格 Conventional Commit `feat:` 以决定 minor/patch。手动 patch 不再抹掉同 minor 周期的 feature 信号;历史 minor 窗口中的 breaking 不再永久阻断后续自动发布。`Publish npm` 手动入口未改。
|
|
38
|
-
|
|
39
|
-
- `frontend-implementation` 绿色路径由固定全链改为按需分支:删除重复的首轮 design shell gate,初审通过直接复用原计划,只有 `request-revision` 才执行计划修订与终审;只有可修复失败才执行 repair/reverify/retrace。常规全绿路径因此少执行 3 个 Pi 与 4 个 shell 节点,同时保留 pass-only 写入门禁、验证/评审证据和只读 `frontend-closeout-pi`。原计划/修订计划的结构化合同与需求覆盖使用同一有效来源,且只有主分支产物缺失时才回退;已存在但 malformed/schema-invalid 的产物继续 fail-closed。
|
|
40
|
-
- Observe 节点检查器“规范证据”页签新增安全文件预览:已绑定任务源和已读取规范文件条目现以可聚焦按钮呈现,点击后在检查器内下钻查看内容(不新增弹窗),提供“返回规范证据”按钮并恢复触发行焦点。新增只读接口 `GET /api/dag-runs/:runId/nodes/:nodeId/spec-evidence/file?source=binding|read&path=...`,服务端重新校验证据成员身份(binding 必须在 `sourceBinding.sources`,read 必须有成功配对 read),拒 `..`/绝对/目录/二进制/仓库外符号链接;正文上限 64KB 并返回截断标志,统一走 Observe 脱敏规则;binding 返回 `sha256`/`currentSha256`/`hashMatch`,read 返回 session event 中实际读取正文与 `readAt`(不以当前工作区文件替代)。
|
|
41
|
-
- 通用 standard/reviewed/supervised 实现 DAG 现在会在目标仓库存在 `AGENTS.md` 时,按本次 writer 的实际变更解析适用的根级/嵌套指令和仓库内代码规范索引:standard 复用 `verify-pi` 与条件 shell gate,reviewed/supervised 复用现有 `review-pi`/review gate,不新增模型节点;无 `AGENTS.md` 或无适用规范时保持原行为,审查节点只读且不会直接改代码。专用测试、前端和知识类 DAG 不受影响。
|
|
42
|
-
|
|
43
|
-
- GitHub Actions CI 降低私有仓分钟消耗:合并为单 job(避免两次 `npm ci`)、同分支/`PR` concurrency 取消旧 run、Draft PR 跳过完整 CI,且仅忽略 `docs/reports/**`、`docs/progress/**`、`docs/design/archive/**`、`docs/exec-plans/completed/**` 等纯运营文档路径;治理文档与代码门禁仍全量跑。
|
|
44
|
-
- 明确 **Compatibility / Operator Assist**:openCode 等主会话只编排已发布 `loop-agent` / `agent-worker` CLI 与只读诊断,不得绕过 CLI 直接改业务实现,失败只走 doctor / reconcile / human gate / CLI 重跑。`loop-agent init` 写入的 `AGENTS.md` managed block、包内 skills 与 website 快速开始/治理说明已对齐;架构决策见 `docs/decisions/0005-governed-operator-surface.md`(accepted)。已有目标项目用 `loop-agent init check-update` / `init update --apply-safe` 或 `init reconcile` 刷新 managed block。
|
|
45
|
-
- 前端测试 DAG 收紧体验:execution preflight 仅硬校验绝对非生产 `baseUrl`;用例 map 缩短为优先用 `playwright-cli` 执行;复盘合并执行证据审查且不依赖 outcome=pass,失败也能出报告。
|
|
46
|
-
- 前端测试用例生成现在会解析明确 `baseUrl`:优先读取任务源 `config.md` 中的前端 URL,缺失时默认 `http://localhost:5173`,并写入 RAG `context.md`;生成命令不得再保留 `<base-url>` 占位符。
|
|
47
|
-
- 前端浏览器测试 DAG 的复盘报告现写入 `testcase/frontend/reports/**`,不再要求 `docs/test-reports/**` 权限;任务执行约束可以安全禁止整个 `docs/**`,同时仍保留可审计的测试资产。
|
|
48
|
-
- `frontend-test` 结果链新增 run-owned `frontend-test-result-v1`、单次用例修订与终审门禁;只有所有浏览器用例和 AC 覆盖通过且结果合同明确为 real/pass 时,Worker 才会投影真实集成。
|
|
49
|
-
- 后端测试 DAG 从 24 个收敛为 15 个真实顶层节点;用例与语义 `request-revision` 由确定性 gate 直接失败,pytest 只执行一次,TestBug 保留分类并转为独立 follow-up,不再触发同 run repair 或 final rerun。唯一 initial Result 会确定性复制为 canonical `contracts/backend-test-result.json`,最终 outcome 只认该结构化事实。
|
|
50
|
-
- Backend Test Analysis、Case Manifest、Semantic Review 与 Classification 的严格 JSON 契约进一步对齐,生成节点明确字段白名单、`sourceBinding` 与 evidence gap 约束,避免模型自定义字段导致确定性门禁失败。
|
|
51
|
-
- 新增升级后 init surface notifier:普通安全仓库命令结束后,当目标项目存在有效 `.harness/init-surface.json` 且 `controllerVersion` 与当前包版本不同时自动检测。surface 缺失/损坏、源仓库、未初始化目录与版本一致时直接跳过;`checkInitUpdate().ok` 时仅在无活跃运行时静默刷新 state;非 TTY 只向 stderr 输出 `loop-agent init reconcile --repo-root ...` 提示且不写入目标;TTY 且无 human decisions、无活跃 DAG/Worker 时可经明确 `y/yes` 同意后应用 deterministic safe actions;只有 model merge 时直接输出有边界指引,不询问“应用 0 个动作”。notifier 遵循全局 `--repo-root`、不写 stdout、不改变原命令退出码,CI、禁用环境变量、help/version、`--json`/`--markdown`、`init`、`run-dag`、`dag`、`loop`、`delegate`、`pi-prompt`、`cursor-prompt` 均跳过。
|
|
52
|
-
- 新增统一命令 `loop-agent init reconcile --repo-root <target>`:surface 缺失返回 `needs-baseline` 且零写入,存在 human decisions 返回 `needs-human-decision` 且零写入,活跃 DAG/Worker 或 Worker 状态无法确认时返回 `blocked-active-runtime` 且零写入;其余情况复用现有 `applyInitUpdate({ applySafe: true })` 并复查返回 `clean`/`needs-model-merge`/`needs-safe-update`,支持 `--json`/`--markdown`。
|
|
53
|
-
- 测试报告新增 L-5 指标消费与 Python coverage.py JSON、Java JaCoCo XML 的统一 Code Coverage v1 报告入口,覆盖通过率、AC/自动化覆盖率、稳定性、失败原因、缺陷、风险和回归建议;缺失覆盖率证据保持 `unavailable`,不改变 backend-test outcome gate。
|
|
54
|
-
- 现有 backend-test DAG 在 retrospective 之后新增独立的 `l5-metrics-pi` 指标节点;它基于前序测试报告和 run-owned 证据输出 `L-5 ready/not-ready`,不创建独立 L-5 DAG,也不改变 Result v1 outcome gate。
|
|
55
|
-
- 后端测试 DAG 从 38 个收敛为 24 个真实顶层节点;使用 fail-closed `runIf` 和复合 Shell capability 减少调度,同时保留双合同、Manifest、语义评审、JUnit、Result、分类、修复安全、追踪和最终 outcome 证据。三条可选修订/修复分支仍各最多执行一次。
|
|
56
|
-
- Backend Test Analysis、Case Manifest、Semantic Review 与 Classification 的严格 JSON 契约进一步对齐,生成和修订节点明确字段白名单、`sourceBinding` 与 evidence gap 约束,避免模型自定义字段导致确定性门禁失败。
|
|
57
|
-
|
|
58
|
-
### 修复
|
|
59
|
-
|
|
60
|
-
- Init evolution 高影响校验现在覆盖升级后的 init surface notifier、eligibility policy、runtime activity gate 与 CLI entry 接线;这些专用路径单独变化时会触发 `model-review`,不再以 `no trigger matched` 静默跳过审查,同时不扩大到通用 CLI program/command registry 文件。
|
|
20
|
+
- 优化操作与观测统一界面的导航与布局,合并确认与运行为「开始运行」,DAG 详情跳转路径更加准确
|
|
61
21
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
-
|
|
71
|
-
-
|
|
72
|
-
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
-
|
|
77
|
-
-
|
|
78
|
-
-
|
|
79
|
-
-
|
|
80
|
-
-
|
|
81
|
-
- Observe 对新 backend-test run 不再伪造 repair attempt/status,只投影 single-run/canonical Result、分类与 coverage;历史 24 节点 run 的 eligibility、repair、final 和 effective evidence 仍可兼容读取。
|
|
82
|
-
- Pi SDK 对缺少响应 ID 的累计 Token 生命周期事件改为取本次执行最大快照,避免同一响应的匿名 usage 被重复累加。
|
|
83
|
-
- Pi SDK 执行长推理或大段结构化输出时不再把高频流式增量事件无界累积到内存;同一响应在多个生命周期事件中重复出现的 Token 用量只统计一次,避免 `Invalid string length` 和成本数据虚高。
|
|
84
|
-
- 后端测试复合执行节点继续保持 clean environment、失败分类和 fail-closed outcome,并为唯一 JUnit/initial Result、canonical Result、traceability 与 Observe 投影保留结构化运行证据。
|
|
22
|
+
### 修复
|
|
23
|
+
|
|
24
|
+
- 修复执行计划索引将命名约定占位符(如 `YYYY-MM-DD-<topic>.md`)误判为缺失计划引用的问题
|
|
25
|
+
|
|
26
|
+
## [0.18.1] - 2026-07-22
|
|
27
|
+
|
|
28
|
+
### 重点更新
|
|
29
|
+
|
|
30
|
+
- 放宽后端测试 Markdown 用例的映射与验收标准校验,减少合法用例被误拦截的情况
|
|
31
|
+
- 修复文档审计报错未阻断 CI 的治理漏洞,并全面修正失效链接
|
|
32
|
+
- 清理发布日志中残留的已发布记录,避免将已上线能力误展示为未发布
|
|
33
|
+
|
|
34
|
+
### 修复
|
|
35
|
+
|
|
36
|
+
- 放宽后端测试 Markdown 用例的每条补充 Case 必须包含 AC 标识的硬性要求,改为确保任务要求的业务 AC 场景至少被一条最终用例覆盖即可,整体缺失必需 AC 时仍会严格阻断
|
|
37
|
+
- 放宽后端测试 traceability 映射校验,Case ID 可位于测试函数名、装饰器、函数体或文档字符串区域,不再强制首行文档字符串或严格一对一映射,避免重复映射等情况导致的误阻断
|
|
38
|
+
- 修复文档审计在发现错误时未返回非零退出码导致 CI 假绿的问题,并将链接门禁覆盖范围扩展至完整的 docs/ 目录
|
|
39
|
+
- 修正文档报告分类迁移后遗留的 269 条失效链接
|
|
40
|
+
- 修复自动发布创建版本区块时未正确消费已发布内容并清理残留记录的问题,移除了随 0.18.0 版本发布的 59 条残留日志
|
|
85
41
|
|
|
86
42
|
## [0.18.0] - 2026-07-22
|
|
87
43
|
|
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
|
|
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
|
"",
|
|
@@ -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);
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
function segment(value) {
|
|
2
|
+
return encodeURIComponent(value);
|
|
3
|
+
}
|
|
4
|
+
function nonEmpty(value) {
|
|
5
|
+
return typeof value === "string" && value.length > 0;
|
|
6
|
+
}
|
|
7
|
+
export function encodeInspectHash(selection) {
|
|
8
|
+
switch (selection.kind) {
|
|
9
|
+
case "feature":
|
|
10
|
+
return `#/feature/${segment(selection.featureId)}`;
|
|
11
|
+
case "feature-task":
|
|
12
|
+
return `#/feature/${segment(selection.featureId)}/task/${segment(selection.taskId)}`;
|
|
13
|
+
case "legacy-task":
|
|
14
|
+
return `#/task/${segment(selection.taskId)}`;
|
|
15
|
+
case "dag-run":
|
|
16
|
+
// Inspect currently owns DAG-level routing. nodeId remains URL-adjacent
|
|
17
|
+
// selection state until the static router explicitly supports it.
|
|
18
|
+
return `#/dag/${segment(selection.dagRunId)}`;
|
|
19
|
+
case "worker-run":
|
|
20
|
+
return `#/run/${segment(selection.workerRunId)}`;
|
|
21
|
+
case "batch":
|
|
22
|
+
return `#/batch/${segment(selection.batchRunId)}`;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
export function buildInspectPath(selection) {
|
|
26
|
+
return `/inspect/${selection ? encodeInspectHash(selection) : "#/"}`;
|
|
27
|
+
}
|
|
28
|
+
export function buildInspectUrl(origin, selection) {
|
|
29
|
+
const base = origin.trim().replace(/\/+$/, "");
|
|
30
|
+
if (!base)
|
|
31
|
+
throw new Error("buildInspectUrl: origin is required");
|
|
32
|
+
return `${base}${buildInspectPath(selection)}`;
|
|
33
|
+
}
|
|
34
|
+
function decodeSegment(value) {
|
|
35
|
+
if (!nonEmpty(value))
|
|
36
|
+
return null;
|
|
37
|
+
try {
|
|
38
|
+
const decoded = decodeURIComponent(value);
|
|
39
|
+
return decoded ? decoded : null;
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
export function decodeInspectHash(hash) {
|
|
46
|
+
const normalized = hash.startsWith("#") ? hash.slice(1) : hash;
|
|
47
|
+
const parts = normalized.split("/").filter(Boolean);
|
|
48
|
+
if (parts.length === 0)
|
|
49
|
+
return null;
|
|
50
|
+
const [kind, first, third, fourth] = parts;
|
|
51
|
+
const id = decodeSegment(first);
|
|
52
|
+
if (!id)
|
|
53
|
+
return null;
|
|
54
|
+
if (kind === "feature" && parts.length === 2) {
|
|
55
|
+
return { kind: "feature", featureId: id };
|
|
56
|
+
}
|
|
57
|
+
if (kind === "feature" && parts.length === 4 && third === "task") {
|
|
58
|
+
const taskId = decodeSegment(fourth);
|
|
59
|
+
return taskId ? { kind: "feature-task", featureId: id, taskId } : null;
|
|
60
|
+
}
|
|
61
|
+
if (kind === "task" && parts.length === 2)
|
|
62
|
+
return { kind: "legacy-task", taskId: id };
|
|
63
|
+
if (kind === "dag" && parts.length === 2)
|
|
64
|
+
return { kind: "dag-run", dagRunId: id };
|
|
65
|
+
if (kind === "run" && parts.length === 2)
|
|
66
|
+
return { kind: "worker-run", workerRunId: id };
|
|
67
|
+
if (kind === "batch" && parts.length === 2)
|
|
68
|
+
return { kind: "batch", batchRunId: id };
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
export function selectionFromObserveLinkTarget(target) {
|
|
72
|
+
switch (target.kind) {
|
|
73
|
+
case "dag":
|
|
74
|
+
return { kind: "dag-run", dagRunId: target.dagRunId };
|
|
75
|
+
case "task":
|
|
76
|
+
return { kind: "legacy-task", taskId: target.taskId };
|
|
77
|
+
case "feature":
|
|
78
|
+
return { kind: "feature", featureId: target.featureId };
|
|
79
|
+
case "worker":
|
|
80
|
+
return { kind: "worker-run", workerRunId: target.workerRunId };
|
|
81
|
+
case "batch":
|
|
82
|
+
return { kind: "batch", batchRunId: target.batchRunId };
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/** Canonicalize only the historical Console root hash shape. */
|
|
86
|
+
export function normalizeLegacyConsoleDagHash(pathnameAndHash) {
|
|
87
|
+
const [pathname, hash = ""] = pathnameAndHash.split("#", 2);
|
|
88
|
+
if ((pathname === "/" || pathname === "") && /^\/dag\/[^/]+$/.test(hash)) {
|
|
89
|
+
return `/inspect/#${hash}`;
|
|
90
|
+
}
|
|
91
|
+
return pathnameAndHash;
|
|
92
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { deriveObserveRouteCapabilities, resolveObservePackageIdentity, } from "../observe/health.js";
|
|
2
|
+
import { repoFingerprintV1 } from "./repo-fingerprint.js";
|
|
3
|
+
export function buildOperatorSurfaceHealthV1(input) {
|
|
4
|
+
const { packageName, packageVersion } = resolveObservePackageIdentity();
|
|
5
|
+
const snapshotAvailable = input.routes.some((route) => route.method === "GET" && route.pattern.test("/api/snapshot"));
|
|
6
|
+
const eventStreamAvailable = input.routes.some((route) => route.method === "GET" && route.pattern.test("/api/events/stream"));
|
|
7
|
+
return {
|
|
8
|
+
schemaVersion: 1,
|
|
9
|
+
ok: true,
|
|
10
|
+
product: "loop-operator-console",
|
|
11
|
+
repoFingerprint: repoFingerprintV1(input.repoRoot),
|
|
12
|
+
packageName,
|
|
13
|
+
packageVersion,
|
|
14
|
+
piReadiness: input.piReadiness,
|
|
15
|
+
inspect: {
|
|
16
|
+
ready: input.inspectReady,
|
|
17
|
+
routeCapabilities: deriveObserveRouteCapabilities(input.routes),
|
|
18
|
+
snapshotAvailable,
|
|
19
|
+
eventStreamAvailable,
|
|
20
|
+
},
|
|
21
|
+
generatedAt: input.generatedAt ?? new Date().toISOString(),
|
|
22
|
+
};
|
|
23
|
+
}
|
|
@@ -38,9 +38,9 @@ const CTA = {
|
|
|
38
38
|
},
|
|
39
39
|
observeLink: {
|
|
40
40
|
id: "observeLink",
|
|
41
|
-
label: "
|
|
41
|
+
label: "打开检视",
|
|
42
42
|
action: "observeLink",
|
|
43
|
-
commandHint: "agent-worker
|
|
43
|
+
commandHint: "agent-worker console serve --repo . --port 8790",
|
|
44
44
|
},
|
|
45
45
|
};
|
|
46
46
|
const MATRIX = {
|