@tea-agent/loop-agent 0.34.4 → 0.34.5

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.
Files changed (35) hide show
  1. package/AGENTS.md +1 -1
  2. package/CHANGELOG.md +16 -0
  3. package/README.md +2 -2
  4. package/dist/shared/operator/capabilities.js +0 -7
  5. package/dist/worker/cli.js +21 -27
  6. package/dist/worker/console/chat/chat-event-store.js +49 -1
  7. package/dist/worker/console/chat/pi-runtime.js +78 -1
  8. package/dist/worker/console/chat/routes.js +24 -1
  9. package/dist/worker/console/chat/shortcuts.js +208 -9
  10. package/dist/worker/console/chat/tool-preview.js +162 -5
  11. package/dist/worker/console/doctor.js +1 -1
  12. package/dist/worker/console/index.js +1 -0
  13. package/dist/worker/console/open-browser.js +134 -0
  14. package/dist/worker/console/recovery-cta.js +1 -1
  15. package/dist/worker/console/server.js +1 -1
  16. package/dist/worker/console/static/assets/index-qpkysQYW.css +1 -0
  17. package/dist/worker/console/static/assets/index-y980PqtP.js +56 -0
  18. package/dist/worker/console/static/index.html +2 -2
  19. package/dist/worker/console/static-src/chat-markdown-security.js +38 -0
  20. package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +1 -3
  21. package/dist/worker/console/static-src/operator-chat/format.js +2 -2
  22. package/dist/worker/console/static-src/operator-chat/useChatThread.js +11 -5
  23. package/dist/worker/console/static-src/operator-chat/useComposer.js +81 -3
  24. package/docs/architecture/evolution.md +1 -1
  25. package/docs/architecture/system-overview.md +1 -1
  26. package/docs/architecture/worker-and-feature.md +1 -1
  27. package/docs/templates/evaluation/agents-map-slim-v1.md +1 -1
  28. package/docs/templates/evaluation/agents-map-verbose-v0.md +2 -2
  29. package/docs/templates/init-managed-agents.md +2 -2
  30. package/package.json +4 -1
  31. package/skills/agent-worker/SKILL.md +1 -1
  32. package/skills/agent-worker/references/agent-worker-operator.md +2 -2
  33. package/skills/loop-agent/references/command-reference.md +2 -3
  34. package/dist/worker/console/static/assets/index-B6Qdbk8V.js +0 -29
  35. package/dist/worker/console/static/assets/index-Bt0NUxcQ.css +0 -1
@@ -6,8 +6,8 @@
6
6
  <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
7
7
  <link rel="stylesheet" href="/inspect/operator-chrome.css" />
8
8
  <title>Loop 操作台 · Operator Console</title>
9
- <script type="module" crossorigin src="/assets/index-B6Qdbk8V.js"></script>
10
- <link rel="stylesheet" crossorigin href="/assets/index-Bt0NUxcQ.css">
9
+ <script type="module" crossorigin src="/assets/index-y980PqtP.js"></script>
10
+ <link rel="stylesheet" crossorigin href="/assets/index-qpkysQYW.css">
11
11
  </head>
12
12
  <body>
13
13
  <div id="root"></div>
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Pure security helpers for Operator Chat Markdown (UI-07).
3
+ * Kept free of JSX so root `tsc` (no jsx) and vitest can import them.
4
+ */
5
+ const DANGEROUS_PROTOCOLS = /^(javascript|vbscript|data):/i;
6
+ export function isSafeMarkdownHref(href) {
7
+ if (!href)
8
+ return false;
9
+ const trimmed = href.trim();
10
+ if (!trimmed)
11
+ return false;
12
+ if (DANGEROUS_PROTOCOLS.test(trimmed))
13
+ return false;
14
+ return true;
15
+ }
16
+ /** Controlled local/session image paths may load; remote/unknown never auto-fetch. */
17
+ export function isAllowedMarkdownImageSrc(src) {
18
+ if (!src)
19
+ return false;
20
+ const trimmed = src.trim();
21
+ if (!trimmed)
22
+ return false;
23
+ if (DANGEROUS_PROTOCOLS.test(trimmed))
24
+ return false;
25
+ if (trimmed.startsWith("blob:") || trimmed.startsWith("data:image/")) {
26
+ return false;
27
+ }
28
+ if (/^https?:\/\//i.test(trimmed))
29
+ return false;
30
+ if (trimmed.startsWith("/") ||
31
+ trimmed.startsWith("./") ||
32
+ trimmed.startsWith("../")) {
33
+ return true;
34
+ }
35
+ if (!/^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(trimmed))
36
+ return true;
37
+ return false;
38
+ }
@@ -9,7 +9,7 @@ import { requestId } from "./format.js";
9
9
  * original request would have. `assistantId` is the in-flight assistant
10
10
  * message id to patch for streaming text; reconnect creates one if none. */
11
11
  export function createChatSseEventApplier(deps) {
12
- const { refs, onReconcile, setError, setProcessOpen, setInterview, setHumanGateCards, setArtifactCards, setStreaming, setStreamingAssistantId, setUnread, setOperationCards, setUsage, setMessages, setToolCalls, } = deps;
12
+ const { refs, onReconcile, setError, setInterview, setHumanGateCards, setArtifactCards, setStreaming, setStreamingAssistantId, setUnread, setOperationCards, setUsage, setMessages, setToolCalls, } = deps;
13
13
  return (eventName, data, assistantId, options) => {
14
14
  if (options?.generation !== undefined &&
15
15
  refs.sessionGenerationRef.current !== options.generation)
@@ -275,8 +275,6 @@ export function createChatSseEventApplier(deps) {
275
275
  ? payload.resultPreview
276
276
  : "{}";
277
277
  const now = Date.now();
278
- if (isError)
279
- setProcessOpen(true);
280
278
  setToolCalls((prev) => {
281
279
  const exists = prev.find((t) => t.id === id);
282
280
  if (exists) {
@@ -1,4 +1,4 @@
1
- import { truncateToolResult } from "../../chat/tool-preview.js";
1
+ import { extractToolResultText, truncateToolResult } from "../../chat/tool-preview.js";
2
2
  export function confirmationToken() {
3
3
  const w = window;
4
4
  return w.__CONSOLE_CONFIRMATION_TOKEN__ ?? "";
@@ -41,7 +41,7 @@ export function fmtDaySep(ts) {
41
41
  }
42
42
  export function resultPreview(result) {
43
43
  try {
44
- const s = typeof result === "string" ? result : JSON.stringify(result, null, 2);
44
+ const s = extractToolResultText(result);
45
45
  return truncateToolResult(s).text;
46
46
  }
47
47
  catch {
@@ -15,7 +15,10 @@ const EMPTY_USAGE = {
15
15
  * slices it patches and the derived timeline projections.
16
16
  */
17
17
  export function useChatThread(params) {
18
- const { origin, refs, sessionId, setError, setProcessOpen, onReconcile } = params;
18
+ const { origin, refs, sessionId, setError, onReconcile } = params;
19
+ // setProcessOpen remains on the hook API for session lifecycle close/reset
20
+ // (UI-08); the SSE applier must not open the process panel.
21
+ void params.setProcessOpen;
19
22
  const [messages, setMessages] = useState([]);
20
23
  const [toolCalls, setToolCalls] = useState([]);
21
24
  const [operationCards, setOperationCards] = useState([]);
@@ -37,7 +40,6 @@ export function useChatThread(params) {
37
40
  refs,
38
41
  onReconcile,
39
42
  setError,
40
- setProcessOpen,
41
43
  setInterview,
42
44
  setHumanGateCards,
43
45
  setArtifactCards,
@@ -48,7 +50,7 @@ export function useChatThread(params) {
48
50
  setUsage,
49
51
  setMessages,
50
52
  setToolCalls,
51
- }), [refs, onReconcile, setError, setProcessOpen]);
53
+ }), [refs, onReconcile, setError]);
52
54
  /** Parse + apply one raw SSE block (frames separated by \n\n). Returns the
53
55
  * eventId if the block carried an `id:` line (for Last-Event-ID tracking). */
54
56
  const applySseBlock = useCallback((block, assistantId, options) => {
@@ -138,13 +140,17 @@ export function useChatThread(params) {
138
140
  let statusLabel = "完成";
139
141
  let statusKind = "ok";
140
142
  if (errored.length > 0) {
141
- statusLabel = `${errored.length} 出错`;
143
+ statusLabel = `${errored.length} 次出错`;
142
144
  statusKind = "error";
143
145
  }
144
146
  else if (running.length > 0) {
145
- statusLabel = current ? `正在调用 ${current.toolName}` : "运行中";
147
+ statusLabel = current ? `${current.toolName} 运行中` : "运行中";
146
148
  statusKind = "running";
147
149
  }
150
+ else {
151
+ // Completed is default/neutral; not shown as a permanent green success chip.
152
+ statusLabel = "";
153
+ }
148
154
  return {
149
155
  count: toolCalls.length,
150
156
  statusLabel,
@@ -1,5 +1,5 @@
1
1
  import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, } from "react";
2
- import { SHORTCUTS } from "../../chat/shortcuts.js";
2
+ import { filterSlashCommands, mergeSlashCommands, } from "../../chat/shortcuts.js";
3
3
  import { confirmationToken } from "./format.js";
4
4
  import { attachRuntimeSnapshotController } from "./useRuntimeSnapshot.js";
5
5
  /** Composer state: input text, @file palette, image attachments, /shortcuts,
@@ -11,12 +11,86 @@ export function useComposer(params) {
11
11
  const [pendingImages, setPendingImages] = useState([]);
12
12
  const [shortcutOpen, setShortcutOpen] = useState(false);
13
13
  const [shortcutIndex, setShortcutIndex] = useState(0);
14
+ const [dynamicCommands, setDynamicCommands] = useState([]);
15
+ const [dynamicCommandsLoading, setDynamicCommandsLoading] = useState(false);
16
+ const [dynamicCommandsError, setDynamicCommandsError] = useState(null);
17
+ const [commandsEpoch, setCommandsEpoch] = useState(0);
14
18
  const textareaRef = useRef(null);
19
+ const commandsSessionRef = useRef(undefined);
15
20
  const handleInputChange = useCallback((value) => {
16
21
  setInput(value);
17
22
  setShortcutOpen(value.startsWith("/"));
18
23
  setShortcutIndex(0);
19
24
  }, []);
25
+ const loadDynamicCommands = useCallback(async () => {
26
+ if (!sessionId) {
27
+ setDynamicCommands([]);
28
+ setDynamicCommandsError(null);
29
+ setDynamicCommandsLoading(false);
30
+ return;
31
+ }
32
+ const target = sessionId;
33
+ commandsSessionRef.current = target;
34
+ setDynamicCommandsLoading(true);
35
+ setDynamicCommandsError(null);
36
+ try {
37
+ const res = await fetch(`${origin}/api/operator/v1/chat/sessions/${encodeURIComponent(target)}/commands`, { credentials: "include" });
38
+ if (commandsSessionRef.current !== target)
39
+ return;
40
+ if (!res.ok) {
41
+ setDynamicCommandsError("动态命令加载失败");
42
+ setDynamicCommands([]);
43
+ return;
44
+ }
45
+ const body = (await res.json().catch(() => null));
46
+ const items = [];
47
+ for (const raw of body?.commands ?? []) {
48
+ const source = raw.source;
49
+ if (source !== "extension" &&
50
+ source !== "prompt" &&
51
+ source !== "skill") {
52
+ continue;
53
+ }
54
+ const command = String(raw.command ?? raw.name ?? "").trim();
55
+ if (!command)
56
+ continue;
57
+ items.push({
58
+ command: command.startsWith("/") ? command : `/${command}`,
59
+ label: String(raw.label ?? raw.name ?? command),
60
+ description: String(raw.description ?? ""),
61
+ source,
62
+ });
63
+ }
64
+ if (commandsSessionRef.current !== target)
65
+ return;
66
+ setDynamicCommands(items);
67
+ }
68
+ catch {
69
+ if (commandsSessionRef.current !== target)
70
+ return;
71
+ setDynamicCommandsError("动态命令加载失败");
72
+ setDynamicCommands([]);
73
+ }
74
+ finally {
75
+ if (commandsSessionRef.current === target) {
76
+ setDynamicCommandsLoading(false);
77
+ }
78
+ }
79
+ }, [origin, sessionId]);
80
+ // Lazy-load dynamic commands when slash palette opens or session changes.
81
+ useEffect(() => {
82
+ setDynamicCommands([]);
83
+ setDynamicCommandsError(null);
84
+ if (!sessionId)
85
+ return;
86
+ if (!shortcutOpen && commandsEpoch === 0)
87
+ return;
88
+ void loadDynamicCommands();
89
+ }, [sessionId, shortcutOpen, commandsEpoch, loadDynamicCommands]);
90
+ const retryDynamicCommands = useCallback(() => {
91
+ setCommandsEpoch((n) => n + 1);
92
+ void loadDynamicCommands();
93
+ }, [loadDynamicCommands]);
20
94
  useEffect(() => {
21
95
  if (!sessionId)
22
96
  return;
@@ -94,9 +168,10 @@ export function useComposer(params) {
94
168
  }, 400);
95
169
  return () => window.clearTimeout(timer);
96
170
  }, [origin, sessionId, input]);
171
+ const mergedCommands = useMemo(() => mergeSlashCommands(dynamicCommands), [dynamicCommands]);
97
172
  const filteredShortcuts = useMemo(() => input.startsWith("/")
98
- ? SHORTCUTS.filter((item) => item.command.startsWith(input.trim().split(/\s+/, 1)[0].toLowerCase()))
99
- : [], [input]);
173
+ ? filterSlashCommands(mergedCommands, input)
174
+ : [], [input, mergedCommands]);
100
175
  const onCompositionStart = useCallback(() => {
101
176
  refs.isComposingRef.current = true;
102
177
  }, [refs]);
@@ -132,6 +207,9 @@ export function useComposer(params) {
132
207
  shortcutIndex,
133
208
  setShortcutIndex,
134
209
  filteredShortcuts,
210
+ dynamicCommandsLoading,
211
+ dynamicCommandsError,
212
+ retryDynamicCommands,
135
213
  insertFileReference,
136
214
  textareaRef,
137
215
  onCompositionStart,
@@ -21,7 +21,7 @@
21
21
  | Frontend-test | 默认 short-chain RAG;内容/evidence 质量进入 advisory findings,路径与安全边界仍 fail-closed | `CHANGELOG.md [0.21.0]` / `[0.22.0]` |
22
22
  | Frontend-implementation | Contract / trace / repair / Mock assess;`ai_workspace` + 知识库 + `openspec` 规范发现;lint 基线债务隔离 | `CHANGELOG.md [0.22.0]` |
23
23
  | Inspect(原 Observe) | 统一 Console 的 `/inspect/` 只读运营面与富时间线;独立 `observe serve` 已硬下线 | `website/docs/guides/observe-ui.md`、`CHANGELOG.md [Unreleased]` |
24
- | Local Operator Console | `agent-worker console serve\|doctor`(loopback);Operate + Inspect、Happy Path / Interview / split view / recovery CTA;**0.23.x 起含 General Operator Chat**(Pi SDK services session、operator-only tool surface);**非**远端多用户 Console | unify design;ADR 0005;completed `2026-07-25-console-phase-4-general-operator-chat.md` |
24
+ | Local Operator Console | `agent-worker console\|doctor`(loopback;默认打开系统浏览器,`--no-open` 禁止);Operate + Inspect、Happy Path / Interview / split view / recovery CTA;**0.23.x 起含 General Operator Chat**(Pi SDK services session、operator-only tool surface);**非**远端多用户 Console | unify design;ADR 0005;completed `2026-07-25-console-phase-4-general-operator-chat.md` |
25
25
  | Task Contract / operator surface | journaled `task contract *`;machine envelope;**DagSpec v4 `taskContractBinding`**(exclusive writer 强制) | `CHANGELOG.md [0.17.0]`–`[0.17.2]`;ADR 0005 |
26
26
  | DagSpec / repair | v3 `runtimeContract` + 显式 `repairNodeId`;新 writer 生成默认 v4 binding | `dag-execution.md`、`CHANGELOG.md [0.11.0]` / `[0.17.0]` |
27
27
  | 自适应 liveness | runner lease、Provider/tool/output 活动与 meaningful progress 分离;静默 stall 受控终止,退出未确认时禁止自动重试 | `dag-execution.md`、active `2026-07-23-dag-adaptive-liveness.md` |
@@ -61,7 +61,7 @@ workflow runtime(调度 Pi / shell / static 节点)
61
61
 
62
62
  ### 本地 Operator Console(Official,0.17.x MVP,与 Observe 协作)
63
63
 
64
- - `agent-worker console serve|doctor` 提供 loopback Operator Console(默认 `127.0.0.1:8790`);Happy Path / Interview 策略面、Task Contract 落地与 recovery CTA 已随 **0.17.0–0.17.2** 发布。
64
+ - `agent-worker console|doctor` 提供 loopback Operator Console(默认 `127.0.0.1:8790`;本地图形环境 listen 后默认打开浏览器,`--no-open` 禁止);Happy Path / Interview 策略面、Task Contract 落地与 recovery CTA 已随 **0.17.0–0.17.2** 发布。
65
65
  - canonical mutation 只经 sibling 已发布 `loop-agent`(`LoopAgentClient`),不 in-process 跑 DAG kernel。
66
66
  - Observe 保持**独立**只读进程(默认 `8787`);Console 通过 versioned Observe `/api/health`(`schemaVersion`、`repoFingerprint`、route capabilities)做深链 fail-closed,**不** mount / proxy。
67
67
  - openCode 等主会话仍是 Compatibility / Operator Assist,与 Official Console **不等同**。设计锚点:ADR 0005、`docs/design/archive/2026-07-21-local-operator-console-from-pi-web.md`;handoff `docs/reports/feature/2026-07-22-console-mvp-handoff.md`。
@@ -101,7 +101,7 @@ controller identity 与 DAG skill snapshot 是两个不同冻结层(前者跨
101
101
  - 模块:`src/worker/observe/`、`src/worker/observability/{read-model,event-store}.ts`。
102
102
  - 全局快照:`buildGlobalSnapshot({ repoRoot })`(`src/worker/observability/read-model.ts`),是 **derived** 视图,消费 `.harness/` 与 Task Pool 事实,**不**改变执行成败。
103
103
  - Observe read model 是本地只读 Inspect 能力;snapshot 投影失败返回安全错误摘要而非全零健康状态。
104
- - `agent-worker console serve` 在同一进程挂载 `/inspect/` 与现有 GET `/api/**`;`/api/health` 返回 `OperatorSurfaceHealthV1`。兼容期独立 Observe `:8787` 保持旧 health DTO 与 GET-only 行为。
104
+ - `agent-worker console` 在同一进程挂载 `/inspect/` 与现有 GET `/api/**`;`/api/health` 返回 `OperatorSurfaceHealthV1`。`observe serve` 已硬下线(REMOVED / exit 2)。
105
105
 
106
106
  ### Local Operator Console(Official,0.18.0)
107
107
 
@@ -37,7 +37,7 @@
37
37
  4. 涉及测试纪律/验证声明/调试时继续读:`docs/harness-methodology-*.md`。
38
38
  5. 查看最近提交、相关 plan/progress/report;`git status --short --branch`;跑最小基线验证。
39
39
  6. 后端/接口/pytest → `taskKind: "backend-test"`(不是 `--profile`);知识回写 `knowledge-sync`;图谱开荒 `knowledge-graph-bootstrap`。`--profile` 仅 `auto|minimal|standard|reviewed|supervised`。
40
- 7. 看板/observe → `agent-worker console`(默认 repo=当前目录、port=8790;兼容 `agent-worker console serve --repo . --port 8790`)(`/inspect/` 只读);`observe serve` 已下线(REMOVED / exit 2);`observe snapshot` 仍可用。
40
+ 7. 看板/observe → `agent-worker console`(默认 repo=当前目录、port=8790;`--no-open` 禁止自动打开浏览器)(`/inspect/` 只读);`observe serve` 已下线(REMOVED / exit 2);`observe snapshot` 仍可用。
41
41
  8. 分支合并 → 先读 `docs/operations/branch-merge-guideline.md`。
42
42
 
43
43
  ## 会话协议
@@ -54,7 +54,7 @@
54
54
  10. 检查 `git status --short --branch`。
55
55
  11. 运行本次任务相关的最小基线验证。
56
56
  12. 如果用户提到"后端测试"、"接口测试"、"pytest"、"自动化测试",在任务 `task.json` 中设置 `taskKind: "backend-test"` 再 `task advance`;不要用 `--profile backend-test`(CLI 不接受该值,专用模板只走 taskKind)。知识回写用 `taskKind: "knowledge-sync"`(须 `featureId`),图谱开荒用 `taskKind: "knowledge-graph-bootstrap"`。`--profile` 仅表示治理强度:`auto|minimal|standard|reviewed|supervised`。
57
- 13. 如果用户提到"看板"、"observe"、"监控面板"、"启动看板",使用 `agent-worker console` 启动统一 Operator Console(默认 repo=当前目录、port=8790;兼容入口 `agent-worker console serve --repo . --port 8790`);`/inspect/` 提供只读检视。`agent-worker observe serve` 已下线(REMOVED / exit 2)。
57
+ 13. 如果用户提到"看板"、"observe"、"监控面板"、"启动看板",使用 `agent-worker console` 启动统一 Operator Console(默认 repo=当前目录、port=8790;本地图形环境默认打开浏览器,`--no-open` 禁止);`/inspect/` 提供只读检视。`agent-worker observe serve` 已下线(REMOVED / exit 2)。
58
58
  14. 如果用户要求“合并 `<source>` 到 `<target>`”或“合并 origin/main 到当前分支”,先阅读 `docs/operations/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/operations/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` 的可选值;`task advance` 继续使用 `--profile auto` 选择治理等级。仅说“自动化测试”且前后端不明时,先根据任务源和项目技术栈判断,禁止无条件路由。
92
- - 本地 Operator Console:`agent-worker console`(默认 repo=当前目录、port=8790;兼容 `agent-worker console serve --repo . --port 8790`),访问 `http://127.0.0.1:8790/`;其中 `/inspect/` 为只读运行检视。默认绑定本机 `127.0.0.1`;可用 `--host 0.0.0.0` / `--debug`,不要直接暴露到公开网络。端口被占用时不会自动更换,请用 `--port <port>` 显式指定。
92
+ - 本地 Operator Console:`agent-worker console`(默认 repo=当前目录、port=8790;`--no-open` 可禁止自动打开浏览器),访问 `http://127.0.0.1:8790/`;其中 `/inspect/` 为只读运行检视。默认绑定本机 `127.0.0.1`;可用 `--host 0.0.0.0` / `--debug`,不要直接暴露到公开网络。端口被占用时不会自动更换,请用 `--port <port>` 显式指定。
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` 安全迁移或退役。
@@ -127,8 +127,8 @@ loop-agent task status <task-id> --json
127
127
  ### 运行看板(只读)
128
128
 
129
129
  ```bash
130
- agent-worker console # 默认 repo=当前目录,port=8790
131
- agent-worker console serve --repo . --port 8790 # 兼容入口,等价于上面裸入口
130
+ agent-worker console # 默认 repo=当前目录,port=8790;本地图形环境 listen 后默认打开浏览器
131
+ agent-worker console --no-open # 只启动服务,不打开浏览器
132
132
  ```
133
133
 
134
134
  浏览器打开 `http://127.0.0.1:8790/`;检视面为 `http://127.0.0.1:8790/inspect/`。默认绑定本机 `127.0.0.1`;不要直接暴露到公开网络。端口被占用时不会自动更换,请用 `--port <port>` 显式指定。`agent-worker observe serve` 已下线(REMOVED / exit 2)。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tea-agent/loop-agent",
3
- "version": "0.34.4",
3
+ "version": "0.34.5",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "loop-agent": "bin/loop-agent.js",
@@ -75,12 +75,15 @@
75
75
  "@earendil-works/pi-coding-agent": "0.80.10"
76
76
  },
77
77
  "devDependencies": {
78
+ "@remixicon/react": "^4.9.0",
78
79
  "@types/node": "^24.6.0",
79
80
  "@types/react": "^19.1.8",
80
81
  "@types/react-dom": "^19.1.6",
81
82
  "@types/semver": "^7.7.1",
82
83
  "react": "^19.1.0",
83
84
  "react-dom": "^19.1.0",
85
+ "react-markdown": "^10.1.0",
86
+ "remark-gfm": "^4.0.1",
84
87
  "tsx": "^4.20.6",
85
88
  "typescript": "^5.9.3",
86
89
  "vite": "^7.0.0",
@@ -16,7 +16,7 @@ references:
16
16
  - **允许**:`agent-worker` / `loop-agent` CLI;只读 `pool doctor`、`observe`、status/report;冻结 controller identity;选择 Ready 工作与 recovery 命令。
17
17
  - **禁止**:绕过 CLI 直接 Edit 业务实现;Worker/DAG 失败后主会话「救火改文件」。
18
18
  - **失败时只允许**:保留 evidence → `task retry` / `task reconcile` / `pool mark-failed` / human gate → 再经 CLI 重跑;实现写入仍只经 published `loop-agent` DAG。
19
- - **Official vs Compatibility**:`agent-worker console` 是 Official 本地控制面(裸入口直接启动;默认 repo=当前目录、port=8790;兼容入口 `agent-worker console serve --repo . --port 8790` 等价)。同进程提供 Operate + Inspect,Inspect 路径 `/inspect/#/...`;openCode 等主会话仍是 Compatibility Assist,二者**不是**同等保证。`observe serve` 已硬下线(`OBSERVE_SERVE_REMOVED` + exit 2,不监听端口);只读检视请用 Console `/inspect/`;`observe snapshot` 仍输出 GlobalSnapshot JSON。
19
+ - **Official vs Compatibility**:`agent-worker console` 是 Official 本地控制面唯一启动入口(默认 repo=当前目录、port=8790;本地图形环境 listen 后默认打开系统浏览器,`--no-open` 禁止)。同进程提供 Operate + Inspect,Inspect 路径 `/inspect/#/...`;openCode 等主会话仍是 Compatibility Assist,二者**不是**同等保证。`observe serve` 已硬下线(`OBSERVE_SERVE_REMOVED` + exit 2,不监听端口);只读检视请用 Console `/inspect/`;`observe snapshot` 仍输出 GlobalSnapshot JSON。
20
20
 
21
21
  ## Route the Work
22
22
 
@@ -54,8 +54,8 @@ Leaf DAG nodes 不得递归启动 `agent-worker`。Worker 负责 DAG 之外的 s
54
54
  ```
55
55
 
56
56
  doctor 只读;migrate 默认零写入,apply 失败全回滚且不改 JSONL。
57
- 8. Inspect(原 Observe)只读:仅推荐 `agent-worker console`(或 `console serve`)的 `/inspect/#/...`;`observe snapshot` 仍可用。`observe serve` 已下线(REMOVED / exit 2)。canonical Task 路由为 `/api/features/:featureId/tasks/:taskId` 与 `#/feature/:featureId/task/:taskId`。8790 `/api/health` 为 `OperatorSurfaceHealthV1`。
58
- 9. Official Console:`agent-worker console serve|doctor`(默认 loopback;可 `--host 0.0.0.0` / `--debug`)。Recovery CTA 为 report/doctor/decision/resume/reconcile/regenerate/打开检视;无 Cancel、无主 CTA「直接改代码」。主会话 Compatibility Assist 不得替代 Console/CLI 执法。
57
+ 8. Inspect(原 Observe)只读:仅推荐 `agent-worker console` `/inspect/#/...`;`observe snapshot` 仍可用。`observe serve` 已下线(REMOVED / exit 2)。canonical Task 路由为 `/api/features/:featureId/tasks/:taskId` 与 `#/feature/:featureId/task/:taskId`。8790 `/api/health` 为 `OperatorSurfaceHealthV1`。
58
+ 9. Official Console:`agent-worker console|doctor`(默认 loopback;可 `--host 0.0.0.0` / `--debug` / `--no-open`)。Recovery CTA 为 report/doctor/decision/resume/reconcile/regenerate/打开检视;无 Cancel、无主 CTA「直接改代码」。主会话 Compatibility Assist 不得替代 Console/CLI 执法。
59
59
 
60
60
  ## Feature Packet Scaffold
61
61
 
@@ -575,8 +575,7 @@ agent-worker scheduler clock status --repo <repo-root> [--json] # CLI-only
575
575
  agent-worker scheduler clock uninstall --repo <repo-root> [--json] # CLI-only
576
576
  agent-worker scheduler submit --repo <repo-root> --feature-id <id> --task-id <id> ... # low-level planning fact
577
577
  agent-worker scheduler transition <schedule-id> --repo <repo-root> --to <status> ... # internal lifecycle
578
- agent-worker console [--repo <repo-root>] [--port 8790] [--host 127.0.0.1] # Official 裸入口;repo 默认当前目录
579
- agent-worker console serve --repo <repo-root> [--port 8790] [--host 127.0.0.1] # 兼容入口,等价于裸入口
578
+ agent-worker console [--repo <repo-root>] [--port 8790] [--host 127.0.0.1] [--no-open] # Official 唯一入口;repo 默认当前目录;默认打开浏览器
580
579
  agent-worker console doctor --repo <repo-root> [--json] [--console-url <url>]
581
580
  agent-worker observe serve ... # REMOVED:stderr OBSERVE_SERVE_REMOVED,exit 2,不监听端口;请用 console + /inspect/
582
581
  agent-worker observe snapshot --repo <repo-root> # 输出 GlobalSnapshot JSON 到 stdout
@@ -602,7 +601,7 @@ agent-worker observe snapshot --repo <repo-root> # 输出 GlobalSnapshot JSON
602
601
  - `report metrics` 按 UTC 月去重投影 Feature/Failure/Follow-up/Delivery/AC/decision/recovery/boundary 指标,同时写 JSON 与 Markdown;每项保留 numerator、denominator、sampleSize 和 missingData。
603
602
  - `task draft-followup` 会按全部 failure category 生成 TaskDraft 或人工行动卡:ProductBug/TestBug/FlakyTest/DependencyFailure 可批准;EnvFailure 连续两次后才生成 ENV-CHECK;Spec/Contract/Risk/Human/Unknown 只给行动卡。人工以 `feature approve-followup --dry-run` 预览,再带非空 `--owner` 批准 TaskDraft;行动卡不能批准。批准在 staging validation 后写 TaskSpec、graph、Ready/approval/event,原失败事实不改写,并有 rename/state/approval/index/event 回滚门禁。
604
603
  - `task retry` 是失败 Task 的唯一重试入口。它会保留原有运行记录和 failure handoff,并让下一次 `batch run-ready` 使用新的 `workerRunId`;不要删除运行态文件或手动修改状态来重试。
605
- - **推荐**裸入口 `agent-worker console`(repo 默认当前目录,默认 `127.0.0.1:8790`)提供 Operate + Inspect;`console serve` 是等价兼容入口。Inspect 路径为 `/inspect/#/...`,API 仍为根 `/api/**`。`observe serve` 已硬下线(`OBSERVE_SERVE_REMOVED` + exit 2,不监听);`observe snapshot` 保留。Inspect 本身不会启动、暂停或重试 Task / Worker / DAG。
604
+ - **推荐**裸入口 `agent-worker console`(repo 默认当前目录,默认 `127.0.0.1:8790`)提供 Operate + Inspect;本地图形环境 listen 成功后默认打开系统浏览器,`--no-open` 禁止;`console serve` 已删除。Inspect 路径为 `/inspect/#/...`,API 仍为根 `/api/**`。`observe serve` 已硬下线(`OBSERVE_SERVE_REMOVED` + exit 2,不监听);`observe snapshot` 保留。Inspect 本身不会启动、暂停或重试 Task / Worker / DAG。
606
605
  - Night Scheduler(本地夜间自治):白天 `admission prepare` 冻结 worktree + DAG writeSet gate;`scheduler add` 消费 gate 并预约 Task Pool `Queued`(不立即执行);`scheduler clock install` 安装本机 user-level OS timer(launchd / systemd --user / schtasks)周期调用 `scheduler tick`;tick 写统一 Clock receipt,doctor/morning/status 可解释 missing/stale/error/busy/drift;成功后 `pending-harvest`,早晨 `scheduler harvest`(exact-base FF)或 `scheduler discard`;`report morning --window night` 与 Inspect `#/night` / Operate 夜间面板读取同一套 facts。Clock install/status/uninstall 为 CLI-only。
607
606
  - 当前 Worker 仍是 v0(库 + CLI + dogfood);日间批处理未强制定时/CI 驱动;`report morning` 默认可从 Task Pool runs 汇总,并支持 `--window night` 投影 Scheduler facts。
608
607