@ai-agent-forge/agent-forge 0.88.4 → 0.88.6

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 (198) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/bundled-plugins/agent-loop-policy/agent-forge.json +1 -1
  3. package/bundled-plugins/agent-loop-policy/plugin.json +1 -1
  4. package/bundled-plugins/alignment/agent-forge.json +19 -0
  5. package/bundled-plugins/alignment/dist/baseline.d.ts +12 -0
  6. package/bundled-plugins/alignment/dist/baseline.d.ts.map +1 -0
  7. package/bundled-plugins/alignment/dist/baseline.js +68 -0
  8. package/bundled-plugins/alignment/dist/baseline.js.map +1 -0
  9. package/bundled-plugins/alignment/dist/engine.d.ts +102 -0
  10. package/bundled-plugins/alignment/dist/engine.d.ts.map +1 -0
  11. package/bundled-plugins/alignment/dist/engine.js +654 -0
  12. package/bundled-plugins/alignment/dist/engine.js.map +1 -0
  13. package/bundled-plugins/alignment/dist/entry.d.ts +14 -0
  14. package/bundled-plugins/alignment/dist/entry.d.ts.map +1 -0
  15. package/bundled-plugins/alignment/dist/entry.js +23 -0
  16. package/bundled-plugins/alignment/dist/entry.js.map +1 -0
  17. package/bundled-plugins/alignment/dist/export.d.ts +15 -0
  18. package/bundled-plugins/alignment/dist/export.d.ts.map +1 -0
  19. package/bundled-plugins/alignment/dist/export.js +122 -0
  20. package/bundled-plugins/alignment/dist/export.js.map +1 -0
  21. package/bundled-plugins/alignment/dist/http-server.d.ts +33 -0
  22. package/bundled-plugins/alignment/dist/http-server.d.ts.map +1 -0
  23. package/bundled-plugins/alignment/dist/http-server.js +119 -0
  24. package/bundled-plugins/alignment/dist/http-server.js.map +1 -0
  25. package/bundled-plugins/alignment/dist/index.d.ts +24 -0
  26. package/bundled-plugins/alignment/dist/index.d.ts.map +1 -0
  27. package/bundled-plugins/alignment/dist/index.js +719 -0
  28. package/bundled-plugins/alignment/dist/index.js.map +1 -0
  29. package/bundled-plugins/alignment/dist/page.d.ts +15 -0
  30. package/bundled-plugins/alignment/dist/page.d.ts.map +1 -0
  31. package/bundled-plugins/alignment/dist/page.js +832 -0
  32. package/bundled-plugins/alignment/dist/page.js.map +1 -0
  33. package/bundled-plugins/alignment/dist/render.d.ts +40 -0
  34. package/bundled-plugins/alignment/dist/render.d.ts.map +1 -0
  35. package/bundled-plugins/alignment/dist/render.js +194 -0
  36. package/bundled-plugins/alignment/dist/render.js.map +1 -0
  37. package/bundled-plugins/alignment/dist/types.d.ts +217 -0
  38. package/bundled-plugins/alignment/dist/types.d.ts.map +1 -0
  39. package/bundled-plugins/alignment/dist/types.js +30 -0
  40. package/bundled-plugins/alignment/dist/types.js.map +1 -0
  41. package/bundled-plugins/alignment/plugin.json +12 -0
  42. package/bundled-plugins/browser-playwright/MANUAL.md +60 -0
  43. package/bundled-plugins/browser-playwright/agent-forge.json +25 -0
  44. package/bundled-plugins/browser-playwright/dist/capability.d.ts +25 -0
  45. package/bundled-plugins/browser-playwright/dist/capability.d.ts.map +1 -0
  46. package/bundled-plugins/browser-playwright/dist/capability.js +612 -0
  47. package/bundled-plugins/browser-playwright/dist/capability.js.map +1 -0
  48. package/bundled-plugins/browser-playwright/dist/entry.d.ts +13 -0
  49. package/bundled-plugins/browser-playwright/dist/entry.d.ts.map +1 -0
  50. package/bundled-plugins/browser-playwright/dist/entry.js +14 -0
  51. package/bundled-plugins/browser-playwright/dist/entry.js.map +1 -0
  52. package/bundled-plugins/browser-playwright/dist/index.d.ts +7 -0
  53. package/bundled-plugins/browser-playwright/dist/index.d.ts.map +1 -0
  54. package/bundled-plugins/browser-playwright/dist/index.js +5 -0
  55. package/bundled-plugins/browser-playwright/dist/index.js.map +1 -0
  56. package/bundled-plugins/browser-playwright/dist/operations.d.ts +69 -0
  57. package/bundled-plugins/browser-playwright/dist/operations.d.ts.map +1 -0
  58. package/bundled-plugins/browser-playwright/dist/operations.js +149 -0
  59. package/bundled-plugins/browser-playwright/dist/operations.js.map +1 -0
  60. package/bundled-plugins/browser-playwright/dist/playwright-driver.d.ts +275 -0
  61. package/bundled-plugins/browser-playwright/dist/playwright-driver.d.ts.map +1 -0
  62. package/bundled-plugins/browser-playwright/dist/playwright-driver.js +953 -0
  63. package/bundled-plugins/browser-playwright/dist/playwright-driver.js.map +1 -0
  64. package/bundled-plugins/browser-playwright/dist/setup.d.ts +88 -0
  65. package/bundled-plugins/browser-playwright/dist/setup.d.ts.map +1 -0
  66. package/bundled-plugins/browser-playwright/dist/setup.js +252 -0
  67. package/bundled-plugins/browser-playwright/dist/setup.js.map +1 -0
  68. package/bundled-plugins/browser-playwright/dist/types.d.ts +270 -0
  69. package/bundled-plugins/browser-playwright/dist/types.d.ts.map +1 -0
  70. package/bundled-plugins/browser-playwright/dist/types.js +2 -0
  71. package/bundled-plugins/browser-playwright/dist/types.js.map +1 -0
  72. package/bundled-plugins/browser-playwright/plugin.json +20 -0
  73. package/bundled-plugins/coding-tools/agent-forge.json +1 -1
  74. package/bundled-plugins/coding-tools/dist/bash.d.ts +2 -7
  75. package/bundled-plugins/coding-tools/dist/bash.d.ts.map +1 -1
  76. package/bundled-plugins/coding-tools/dist/bash.js +2 -34
  77. package/bundled-plugins/coding-tools/dist/bash.js.map +1 -1
  78. package/bundled-plugins/coding-tools/plugin.json +1 -1
  79. package/bundled-plugins/compaction-policy/agent-forge.json +1 -1
  80. package/bundled-plugins/compaction-policy/plugin.json +1 -1
  81. package/bundled-plugins/mcp-http/dist/mcp-http.d.ts.map +1 -1
  82. package/bundled-plugins/mcp-http/dist/mcp-http.js +84 -31
  83. package/bundled-plugins/mcp-http/dist/mcp-http.js.map +1 -1
  84. package/bundled-plugins/overflow-policy/agent-forge.json +1 -1
  85. package/bundled-plugins/overflow-policy/plugin.json +1 -1
  86. package/bundled-plugins/provider-failover/agent-forge.json +1 -1
  87. package/bundled-plugins/provider-failover/plugin.json +1 -1
  88. package/bundled-plugins/retry-policy/agent-forge.json +1 -1
  89. package/bundled-plugins/retry-policy/plugin.json +1 -1
  90. package/dist/bundle/.build-manifest.json +60 -58
  91. package/dist/bundle/chunks/{anthropic-messages-3FJHRAJL.js → anthropic-messages-NL4Y6WPB.js} +1 -1
  92. package/dist/bundle/chunks/{azure-openai-responses-GCL47IDS.js → azure-openai-responses-CPXC42HO.js} +1 -1
  93. package/dist/bundle/chunks/bedrock-converse-stream.js +1 -1
  94. package/dist/bundle/chunks/chunk-5LRQFZFE.js +2 -0
  95. package/dist/bundle/chunks/{chunk-HTEVUY7F.js → chunk-7HSJPQKN.js} +1 -1
  96. package/dist/bundle/chunks/{chunk-AC2FIS4G.js → chunk-EDO7Q6XR.js} +1 -1
  97. package/dist/bundle/chunks/{chunk-755VNWIF.js → chunk-ISLHFRER.js} +6 -6
  98. package/dist/bundle/chunks/{chunk-HV64ASMT.js → chunk-K3S6GTL7.js} +1 -1
  99. package/dist/bundle/chunks/{chunk-HEL6TAWE.js → chunk-POLDAUYA.js} +37 -36
  100. package/dist/bundle/chunks/{chunk-V3ACW7VD.js → chunk-WJSKB3FC.js} +1 -1
  101. package/dist/bundle/chunks/{chunk-32YDZI4J.js → chunk-ZS5JEI76.js} +1 -1
  102. package/dist/bundle/chunks/{google-generative-ai-6BMXWA44.js → google-generative-ai-UMZ53EFA.js} +1 -1
  103. package/dist/bundle/chunks/{google-vertex-6H7NLRTK.js → google-vertex-ZNR5NS6H.js} +1 -1
  104. package/dist/bundle/chunks/image-resize-worker.js +1 -1
  105. package/dist/bundle/chunks/{mistral-conversations-IXUHSFSJ.js → mistral-conversations-IT66WUNN.js} +1 -1
  106. package/dist/bundle/chunks/{openai-codex-responses-YHUAEVCM.js → openai-codex-responses-FOVCVG6I.js} +1 -1
  107. package/dist/bundle/chunks/{openai-completions-ULQCFC72.js → openai-completions-FLQ7CBOL.js} +1 -1
  108. package/dist/bundle/chunks/{openai-responses-NLLAJDPS.js → openai-responses-ABGYHS2T.js} +1 -1
  109. package/dist/bundle/chunks/{pi-messages-EJTI2EJ3.js → pi-messages-6K5W6LUN.js} +1 -1
  110. package/dist/bundle/chunks/{virtual-modules-TQWEGNNZ.js → virtual-modules-2QGILFV7.js} +1 -1
  111. package/dist/bundle/cli.js +1 -1
  112. package/dist/bundle/client.js +1 -1
  113. package/dist/bundle/index.js +1 -1
  114. package/dist/bundle/rpc-entry.js +1 -1
  115. package/dist/client/remote-session.d.ts.map +1 -1
  116. package/dist/client/remote-session.js +65 -6
  117. package/dist/client/remote-session.js.map +1 -1
  118. package/dist/core/agent-session-store.d.ts.map +1 -1
  119. package/dist/core/agent-session-store.js +20 -3
  120. package/dist/core/agent-session-store.js.map +1 -1
  121. package/dist/core/extensions/agent-composition.d.ts.map +1 -1
  122. package/dist/core/extensions/agent-composition.js +20 -1
  123. package/dist/core/extensions/agent-composition.js.map +1 -1
  124. package/dist/core/i18n/locales/zh-cn/core-errors.d.ts.map +1 -1
  125. package/dist/core/i18n/locales/zh-cn/core-errors.js +1 -0
  126. package/dist/core/i18n/locales/zh-cn/core-errors.js.map +1 -1
  127. package/dist/core/i18n/locales/zh-cn/interactive-mode.d.ts.map +1 -1
  128. package/dist/core/i18n/locales/zh-cn/interactive-mode.js +10 -3
  129. package/dist/core/i18n/locales/zh-cn/interactive-mode.js.map +1 -1
  130. package/dist/core/keybindings.d.ts +35 -0
  131. package/dist/core/keybindings.d.ts.map +1 -1
  132. package/dist/core/keybindings.js +34 -0
  133. package/dist/core/keybindings.js.map +1 -1
  134. package/dist/core/sdk.d.ts.map +1 -1
  135. package/dist/core/sdk.js +62 -15
  136. package/dist/core/sdk.js.map +1 -1
  137. package/dist/core/session-manager.d.ts.map +1 -1
  138. package/dist/core/session-manager.js +11 -3
  139. package/dist/core/session-manager.js.map +1 -1
  140. package/dist/core/system-prompt.d.ts.map +1 -1
  141. package/dist/core/system-prompt.js +2 -2
  142. package/dist/core/system-prompt.js.map +1 -1
  143. package/dist/core/tools/capability-mount.d.ts.map +1 -1
  144. package/dist/core/tools/capability-mount.js +4 -21
  145. package/dist/core/tools/capability-mount.js.map +1 -1
  146. package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  147. package/dist/modes/interactive/components/assistant-message.js +2 -2
  148. package/dist/modes/interactive/components/assistant-message.js.map +1 -1
  149. package/dist/modes/interactive/components/bash-execution.d.ts +2 -0
  150. package/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
  151. package/dist/modes/interactive/components/bash-execution.js +25 -0
  152. package/dist/modes/interactive/components/bash-execution.js.map +1 -1
  153. package/dist/modes/interactive/components/changelog-pager.d.ts +53 -0
  154. package/dist/modes/interactive/components/changelog-pager.d.ts.map +1 -0
  155. package/dist/modes/interactive/components/changelog-pager.js +239 -0
  156. package/dist/modes/interactive/components/changelog-pager.js.map +1 -0
  157. package/dist/modes/interactive/components/tool-execution.d.ts +6 -0
  158. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  159. package/dist/modes/interactive/components/tool-execution.js +12 -0
  160. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  161. package/dist/modes/interactive/components/tool-run-group.d.ts +41 -11
  162. package/dist/modes/interactive/components/tool-run-group.d.ts.map +1 -1
  163. package/dist/modes/interactive/components/tool-run-group.js +126 -14
  164. package/dist/modes/interactive/components/tool-run-group.js.map +1 -1
  165. package/dist/modes/interactive/interactive-commands.d.ts.map +1 -1
  166. package/dist/modes/interactive/interactive-commands.js +23 -13
  167. package/dist/modes/interactive/interactive-commands.js.map +1 -1
  168. package/dist/modes/interactive/interactive-events.d.ts +14 -4
  169. package/dist/modes/interactive/interactive-events.d.ts.map +1 -1
  170. package/dist/modes/interactive/interactive-events.js +94 -12
  171. package/dist/modes/interactive/interactive-events.js.map +1 -1
  172. package/dist/modes/interactive/interactive-mode.d.ts +6 -1
  173. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  174. package/dist/modes/interactive/interactive-mode.js +21 -5
  175. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  176. package/dist/plugins/ecosystem/node-local-package-source.d.ts +2 -1
  177. package/dist/plugins/ecosystem/node-local-package-source.d.ts.map +1 -1
  178. package/dist/plugins/ecosystem/node-local-package-source.js +110 -4
  179. package/dist/plugins/ecosystem/node-local-package-source.js.map +1 -1
  180. package/dist/plugins/ecosystem/node-package-install-host.d.ts.map +1 -1
  181. package/dist/plugins/ecosystem/node-package-install-host.js +106 -39
  182. package/dist/plugins/ecosystem/node-package-install-host.js.map +1 -1
  183. package/dist/plugins/ecosystem/node-plugin-bootstrap.d.ts.map +1 -1
  184. package/dist/plugins/ecosystem/node-plugin-bootstrap.js +59 -2
  185. package/dist/plugins/ecosystem/node-plugin-bootstrap.js.map +1 -1
  186. package/dist/suites/builtin-suites.d.ts.map +1 -1
  187. package/dist/suites/builtin-suites.js +2 -2
  188. package/dist/suites/builtin-suites.js.map +1 -1
  189. package/dist/utils/changelog.d.ts +27 -0
  190. package/dist/utils/changelog.d.ts.map +1 -1
  191. package/dist/utils/changelog.js +110 -0
  192. package/dist/utils/changelog.js.map +1 -1
  193. package/docs/keybindings.md +14 -0
  194. package/examples/extensions/with-deps/package-lock.json +2 -2
  195. package/examples/extensions/with-deps/package.json +1 -1
  196. package/examples/extensions/with-deps/plugin.json +1 -1
  197. package/package.json +1 -1
  198. package/dist/bundle/chunks/chunk-A3JYRWB6.js +0 -2
@@ -0,0 +1,719 @@
1
+ /**
2
+ * Alignment capability plugin (M1 of `docs/design/需求对齐插件设计.md`): a
3
+ * three-step requirement-alignment funnel (objective → detail → design) with
4
+ * block-level bidirectional feedback confirmed on a local HTML page.
5
+ *
6
+ * Persistence protocol, structurally identical to the goal/plan/todo/workflow
7
+ * plugins: append-only `alignment.state` full snapshots (the single rebuild
8
+ * source) plus one `alignment.transition` audit entry per conversion,
9
+ * announced as `alignment.changed` (version 1, correlationId
10
+ * "alignment.state", data = the complete AlignmentState snapshot).
11
+ *
12
+ * The round wait is non-blocking (设计 §5 总则 8): alignment_round returns the
13
+ * page URL immediately; page feedback arrives as a `alignment.feedback`
14
+ * custom message injected through SessionAPI.sendMessage (triggerTurn), and a
15
+ * round only closes after the injection succeeded (`round.delivered`,
16
+ * terminal audit D1).
17
+ */
18
+ import { Type } from "typebox";
19
+ import { activeStep, applyAlignmentAdvance, applyAlignmentSet, applyRoundPost, applyVersionRollback, finalBaselineLines, gate, markDelivered, } from "./engine.js";
20
+ import { exportTargetPath, writeAlignmentExport } from "./export.js";
21
+ import { startAlignmentServer } from "./http-server.js";
22
+ import { alignmentToolResultRenderer } from "./render.js";
23
+ import { AlignmentError, } from "./types.js";
24
+ const STATE_ENTRY = "alignment.state";
25
+ const TRANSITION_ENTRY = "alignment.transition";
26
+ const GOAL_STATE_ENTRY = "goal.state";
27
+ function isObject(value) {
28
+ return value !== null && typeof value === "object" && !Array.isArray(value);
29
+ }
30
+ /**
31
+ * 双调用约定(走查 2026-10-04,goal 插件同款问题的严谨版):会话路径以
32
+ * (toolCallId, params, signal, onUpdate) 调用插件工具——first 是调用 id 字符串;
33
+ * runtime facade 以 (input, context?, signal?) 调用——first 是入参对象。
34
+ * 按 first 的类型区分,两条路径(含 runtime 携带 context)都取到真实入参。
35
+ */
36
+ function toolInput(first, second) {
37
+ if (typeof first === "string") {
38
+ if (!isObject(second))
39
+ return {};
40
+ return second;
41
+ }
42
+ if (!isObject(first))
43
+ throw new Error("alignment tool input must be an object");
44
+ return first;
45
+ }
46
+ function loadState(api) {
47
+ const entries = api.session?.getBranchEntries() ?? [];
48
+ const entry = [...entries].reverse().find((item) => item.customType === STATE_ENTRY);
49
+ if (!entry?.data || !isObject(entry.data))
50
+ return { alignments: [] };
51
+ const data = entry.data;
52
+ return { alignments: Array.isArray(data.alignments) ? [...data.alignments] : [] };
53
+ }
54
+ function loadTransitions(api) {
55
+ const entries = api.session?.getBranchEntries() ?? [];
56
+ return entries
57
+ .filter((item) => item.customType === TRANSITION_ENTRY)
58
+ .map((item) => item.data)
59
+ .filter((item) => isObject(item));
60
+ }
61
+ /** Advisory goalId check (#10 soft-reference discipline, goal/plan precedent). */
62
+ function goalKnownToSession(api, goalId) {
63
+ const entries = api.session?.getBranchEntries() ?? [];
64
+ for (let index = entries.length - 1; index >= 0; index -= 1) {
65
+ const entry = entries[index];
66
+ if (entry.customType !== GOAL_STATE_ENTRY)
67
+ continue;
68
+ const data = entry.data;
69
+ if (!isObject(data))
70
+ return false;
71
+ const goals = data.goals;
72
+ if (!Array.isArray(goals))
73
+ return false;
74
+ return goals.some((goal) => isObject(goal) && goal.id === goalId);
75
+ }
76
+ return false;
77
+ }
78
+ /** Page projection (state.json): everything the fixed template renders. */
79
+ function pageView(record, baselineFile, awaitingRevision) {
80
+ const snapshot = gate(record);
81
+ // gate already validated the active step exists (it throws otherwise).
82
+ const blocks = activeStep(record).blocks;
83
+ // 终审/终态头部展示跨步总口径(走查 2026-10-03):completed 后仍按当前步
84
+ // 计数会显示「已确认 0/1」这类误导数字。
85
+ const countedBlocks = record.status === "aligning" ? blocks : record.steps.flatMap((s) => s.blocks);
86
+ return {
87
+ alignmentId: record.id,
88
+ title: record.title,
89
+ goalId: record.goalId,
90
+ status: record.status,
91
+ revision: record.revision,
92
+ round: record.roundCount,
93
+ activeStep: record.activeStep,
94
+ steps: record.steps.map((step) => ({
95
+ step: step.step,
96
+ status: step.status,
97
+ blocks: step.blocks.map((block) => ({
98
+ id: block.id,
99
+ kind: block.kind,
100
+ content: block.content,
101
+ rationale: block.rationale,
102
+ impact: block.impact,
103
+ status: block.status,
104
+ modifiedInRound: block.modifiedInRound,
105
+ draftAnswer: block.draftAnswer,
106
+ comments: block.comments,
107
+ })),
108
+ })),
109
+ gate: {
110
+ ...snapshot,
111
+ approvedCount: countedBlocks.filter((block) => block.status === "approved").length,
112
+ blockCount: countedBlocks.length,
113
+ },
114
+ pendingFeedback: record.pendingFeedback,
115
+ // Version snapshots for the read-only history switcher (走查 2026-10-03).
116
+ versions: record.versions ?? [],
117
+ awaitingRevision,
118
+ baselineFile,
119
+ };
120
+ }
121
+ function blockStateLine(block) {
122
+ const state = block.status === "approved" ? "已确认" : "待确认";
123
+ const first = block.content.split("\n")[0]?.slice(0, 80) ?? "";
124
+ const notes = [];
125
+ for (const comment of block.comments) {
126
+ if (comment.status !== "open")
127
+ continue;
128
+ notes.push(comment.author === "assistant"
129
+ ? `AI疑问待回应:${comment.note.slice(0, 40)}`
130
+ : `用户批注:${comment.note.slice(0, 40)}`);
131
+ }
132
+ return `- ${block.id}[${block.kind}|${state}] ${first}${notes.length ? `(${notes.join(";")})` : ""}`;
133
+ }
134
+ function historyLines(transitions) {
135
+ if (transitions.length === 0)
136
+ return "(无)";
137
+ return transitions
138
+ .slice(-20)
139
+ .map((entry) => {
140
+ const where = entry.step ? `${entry.step} ` : "";
141
+ return `rev${entry.revision} ${where}${entry.event}${entry.reason.detail ? `(${entry.reason.detail.slice(0, 60)})` : ""}`;
142
+ })
143
+ .join("\n");
144
+ }
145
+ function feedbackMessage(record, result, transitions) {
146
+ const snapshot = gate(record);
147
+ const lines = [];
148
+ lines.push(`[alignment.feedback] ${result.alignmentId} 第 ${result.round} 轮(${result.step} 步,revision ${result.revision},status ${record.status})`);
149
+ lines.push("== 本轮反馈 ==");
150
+ if (result.approvals.length > 0)
151
+ lines.push(`确认块:${result.approvals.join(", ")}`);
152
+ for (const comment of result.comments) {
153
+ lines.push(`用户批注[${comment.blockId}]${comment.escalate ? "(上游问题)" : ""}:「${comment.quote}」→ ${comment.note}`);
154
+ }
155
+ for (const reply of result.commentReplies) {
156
+ lines.push(reply.dismiss
157
+ ? `AI 疑问 ${reply.commentId}:用户标记不重要`
158
+ : `AI 疑问 ${reply.commentId} 的回答:${reply.answer}`);
159
+ }
160
+ for (const answer of result.answers) {
161
+ lines.push(`补充答案[${answer.blockId}]:${answer.value}`);
162
+ }
163
+ for (const addition of result.additions) {
164
+ lines.push(`自由补充:${addition}`);
165
+ }
166
+ if (result.stepConfirmed) {
167
+ if (record.status === "completed") {
168
+ lines.push("用户已在终审总览确认基线——对齐完成。请输出最终基线总结(plan 输入)。");
169
+ if (result.finalBaseline) {
170
+ lines.push("最终基线紧凑清单:");
171
+ for (const line of result.finalBaseline) {
172
+ lines.push(` [${line.kind}] ${line.id}: ${line.line}`);
173
+ }
174
+ }
175
+ }
176
+ else {
177
+ lines.push("用户已确认本步——基线锁定。请 alignment_set 出下一步块集并 alignment_round 刷新。");
178
+ }
179
+ }
180
+ lines.push("== 当前块集(全量状态)==");
181
+ if (record.status === "finalizing") {
182
+ lines.push("三步基线已全部锁定,等待用户终审总览确认;此阶段无需你操作。");
183
+ }
184
+ else {
185
+ for (const step of record.steps) {
186
+ if (step.blocks.length === 0)
187
+ continue;
188
+ lines.push(`[${step.step}|${step.status}]`);
189
+ for (const block of step.blocks)
190
+ lines.push(blockStateLine(block));
191
+ }
192
+ lines.push(`待办:${snapshot.unapproved} 块待确认、${snapshot.openComments} 批注未清、${snapshot.pendingAdditions} 补充未落账。`);
193
+ }
194
+ lines.push("== 历史轮次(revision 锚点)==");
195
+ lines.push(historyLines(transitions));
196
+ return lines.join("\n");
197
+ }
198
+ /**
199
+ * Single-record summary returned by all three tools (设计 §6, v3.7): tool
200
+ * results no longer carry the full state (8-17KB per round pushed a 27-minute
201
+ * real session from 34KB to 295KB of model context) — the authoritative
202
+ * current state reaches the model through the alignment.feedback injection,
203
+ * and the TUI renderer (render.ts) consumes exactly this shape.
204
+ */
205
+ function recordSummary(record, extra = {}) {
206
+ const snapshot = gate(record);
207
+ const step = activeStep(record);
208
+ return {
209
+ alignmentId: record.id,
210
+ status: record.status,
211
+ title: record.title,
212
+ activeStep: record.activeStep,
213
+ round: record.roundCount,
214
+ revision: record.revision,
215
+ gate: {
216
+ unapproved: snapshot.unapproved,
217
+ openComments: snapshot.openComments,
218
+ pendingAdditions: snapshot.pendingAdditions,
219
+ allGreen: snapshot.allGreen,
220
+ },
221
+ ...(extra.url !== undefined ? { url: extra.url } : {}),
222
+ ...(extra.warning !== undefined ? { warning: extra.warning } : {}),
223
+ step: {
224
+ name: record.activeStep,
225
+ status: step.status,
226
+ blocks: step.blocks.map((block) => ({
227
+ id: block.id,
228
+ kind: block.kind,
229
+ status: block.status,
230
+ preview: block.content.split("\n")[0]?.slice(0, 72) ?? "",
231
+ commentCount: block.comments.length,
232
+ ...(block.draftAnswer !== undefined ? { draftAnswer: block.draftAnswer } : {}),
233
+ })),
234
+ },
235
+ ...(extra.baseline !== undefined ? { baseline: extra.baseline } : {}),
236
+ };
237
+ }
238
+ const PROMPT_GUIDELINES = [
239
+ "进入分流(预检规则):用户显式要求对齐时直接进入;否则先做零成本预检——列出缺失信息、被迫假设、矛盾歧义。三项全空则一行结论直接出方案(跳过证据必须可见,禁止静默跳过);有任一项则列出要点并建议对齐,用户说直接做则跳过、但未决假设须在方案中显式标注。预检清单在对齐时直接作为第一轮块种子。",
240
+ "三步铁律:objective 只谈方向与非目标(≤10 块);detail 只谈业务规则/公式/数据流/异常(禁止技术选型);design 谈技术方案且不得与已锁定基线矛盾,发现矛盾必须停下向用户提出。",
241
+ "视觉对齐(涉及用户可见界面的任务):design 步必须产出 visual 视觉方向块——按六节契约(风格宣言/配色角色表/布局与层级策略/文案密度预算/视觉验收清单/明确不做项)逐节落块,建议委派 design-proposer 出厂角色产出(subagent_run role=design-proposer,其指令内嵌保底资产);纯后端/重构任务不产 visual 块;实现完成后渲染产物交 visual-reviewer 角色做视觉验收(防丑下限)。",
242
+ "块纪律:假设单独成 assumption 块;缺信息产 missing 块而不是脑补;可自查的先取证落 fact;assistant 批注只用于真实不确定点(公式口径、二义表述、潜在矛盾、与基线冲突)。",
243
+ "修订纪律:最小改动已确认的块(内容不变即继承确认);修改已确认块的内容会被引擎拒绝(approved-blocks-conflict)——确需修改时重新提交并携带 reviseApproved: true,这些块会重置为待确认、须请用户重新确认;每次 alignment_set 之后必须跟 alignment_round 告知用户去看新版;用户批注的 quote 匹配失败时向用户说明而不是猜。",
244
+ "填空落账:收到补充答案(补充答案[blockId])后,下一次 alignment_set 必须把答案重写进对应块内容并转为 fact/decision(missing 块不得带着未并入的答案被确认锁定);注入消息的「当前块集(全量状态)」是权威现状,以它为准做最小修订。",
245
+ "终审阶段(status finalizing):三步基线已全部锁定,页面进入只读总览,唯一动作是用户最终确认——此阶段不要调用 alignment_set;收到终审确认消息(alignment completed)即视为开发授权:先输出一行极简基线摘要,然后直接按基线实现——用 plan/todo 把基线拆成可执行任务,实现并自测,完成后向用户报告结果与验收方式。不要停在基线总结等用户再次指令(dogfood 2026-10-04:等指令会打断对齐→开发衔接)。",
246
+ "页面回退:用户可在页面上把任务回退到更早环节(step.retreated,后续环节作废重走)或把当前步回退到历史版本(version.rolled-back,当前未确认版本丢弃、批注保留)。收到这类消息后,以注入消息里的全量块集为准重新修订,不要复用回退前的块集。",
247
+ ];
248
+ const setSchema = Type.Object({
249
+ id: Type.String({ description: "Stable alignment id; charset [A-Za-z0-9._-], no leading dot" }),
250
+ title: Type.Optional(Type.String({ description: "Human-readable alignment title" })),
251
+ goalId: Type.Optional(Type.String({ description: "Soft goal reference; unknown ids only warn" })),
252
+ revision: Type.Optional(Type.Integer({ description: "Monotonic revision; must exceed the stored one", minimum: 0 })),
253
+ reviseApproved: Type.Optional(Type.Boolean({
254
+ description: "Explicitly confirm this write modifies blocks the user already approved (they reset to pending and need re-approval)",
255
+ })),
256
+ blocks: Type.Array(Type.Object({
257
+ id: Type.String({ description: "Stable block id within the step" }),
258
+ kind: Type.Union([
259
+ Type.Literal("fact"),
260
+ Type.Literal("assumption"),
261
+ Type.Literal("decision"),
262
+ Type.Literal("missing"),
263
+ Type.Literal("visual"),
264
+ ], { description: "fact=取证 assumption=假设 decision=决策 missing=待补充" }),
265
+ content: Type.String({ description: "Block content (markdown text, ≤8192 chars)" }),
266
+ rationale: Type.Optional(Type.String({ description: "Why this decision/assumption (≤2048 chars)" })),
267
+ impact: Type.Optional(Type.Array(Type.String(), { description: "Affected modules/files/interfaces" })),
268
+ comments: Type.Optional(Type.Array(Type.Object({
269
+ id: Type.String(),
270
+ note: Type.String({ description: "Assistant question; only real uncertainty" }),
271
+ quote: Type.Optional(Type.String()),
272
+ }), { description: "New assistant comments (AI 主动标注的不确定点)" })),
273
+ commentResolutions: Type.Optional(Type.Array(Type.Object({
274
+ commentId: Type.String(),
275
+ kind: Type.Union([Type.Literal("answered"), Type.Literal("dismissed"), Type.Literal("incorporated")]),
276
+ text: Type.Optional(Type.String()),
277
+ }), {
278
+ description: "Resolutions for existing comments (user comments may only be resolved, never rewritten)",
279
+ })),
280
+ }), { description: "Full block set of the active step (全量提交;未变块按内容指纹继承确认)" }),
281
+ });
282
+ const roundSchema = Type.Object({
283
+ alignmentId: Type.String({ description: "Alignment id to open the confirmation page for" }),
284
+ });
285
+ const advanceSchema = Type.Object({
286
+ alignmentId: Type.String(),
287
+ action: Type.Union([
288
+ Type.Literal("inspect"),
289
+ Type.Literal("retreat"),
290
+ Type.Literal("cancel"),
291
+ Type.Literal("rollback"),
292
+ ]),
293
+ step: Type.Optional(Type.Union([Type.Literal("objective"), Type.Literal("detail"), Type.Literal("design")], {
294
+ description: "Retreat target; must be an earlier step",
295
+ })),
296
+ note: Type.Optional(Type.String({ description: "Retreat reason (required for retreat)" })),
297
+ versionId: Type.Optional(Type.Integer({
298
+ description: "Rollback target: the active step's snapshot revision; the latest snapshot is used when omitted",
299
+ })),
300
+ });
301
+ function pendingText(items) {
302
+ if (items.length === 0)
303
+ return "nothing pending";
304
+ const reasons = items.map((item) => `${item.blockId}(${item.reason})`);
305
+ return reasons.join(" ");
306
+ }
307
+ function listLines(state) {
308
+ if (state.alignments.length === 0)
309
+ return "no alignments in this session; create one with alignment_set";
310
+ const lines = [`alignments: ${state.alignments.length}`];
311
+ for (const record of state.alignments) {
312
+ const snapshot = gate(record);
313
+ lines.push(`- ${record.id} [${record.status} → ${record.goalId ?? "-"}] ${record.title} · ${record.activeStep} r${record.roundCount} · ${snapshot.unapproved} pending / ${snapshot.openComments} open`);
314
+ }
315
+ return lines.join("\n");
316
+ }
317
+ function whyLines(record) {
318
+ const snapshot = gate(record);
319
+ const lines = [`${record.id} · ${record.activeStep} · round ${record.roundCount}`];
320
+ if (record.status !== "aligning") {
321
+ lines.push(`status ${record.status} — nothing to wait for`);
322
+ return lines.join("\n");
323
+ }
324
+ if (snapshot.allGreen) {
325
+ lines.push("ALL GREEN — waiting for the user to click 确认完成 on the page");
326
+ return lines.join("\n");
327
+ }
328
+ lines.push(`pending: ${pendingText(snapshot.pending)}`);
329
+ if (snapshot.pendingAdditions > 0) {
330
+ lines.push(`undelivered additions: ${record.pendingFeedback?.additions.join(" | ")}`);
331
+ }
332
+ return lines.join("\n");
333
+ }
334
+ function logLines(transitions, count) {
335
+ if (transitions.length === 0)
336
+ return "no alignment transitions recorded";
337
+ const slice = transitions.slice(-count).reverse();
338
+ return slice
339
+ .map((entry, index) => {
340
+ const where = entry.step ? `${entry.step} ` : "";
341
+ return `${slice.length - index}. [r${entry.round ?? "-"} rev${entry.revision}] ${where}${entry.event} ${entry.reason.code}${entry.reason.detail ? ` — ${entry.reason.detail}` : ""}`;
342
+ })
343
+ .join("\n");
344
+ }
345
+ export function createCapability(api, options = {}) {
346
+ let state = loadState(api);
347
+ let server;
348
+ let cachedRound;
349
+ const deliveredRoundByAlignment = new Map();
350
+ /** Revision at last successful injection — awaitingRevision = delivered and not yet superseded by a set. */
351
+ const deliveredRevisionByAlignment = new Map();
352
+ const persist = (next, transitions) => {
353
+ state = next;
354
+ api.session?.appendEntry(STATE_ENTRY, state);
355
+ for (const entry of transitions)
356
+ api.session?.appendEntry(TRANSITION_ENTRY, entry);
357
+ void api.publish({ type: "alignment.changed", version: 1, correlationId: STATE_ENTRY, data: state });
358
+ return state;
359
+ };
360
+ const activeRecord = () => state.alignments.find((record) => record.status === "aligning") ??
361
+ [...state.alignments].reverse().find((record) => record.status !== "aligning");
362
+ const ensureServer = async () => {
363
+ // 页面生命周期(走查 2026-10-04 用户裁决):服务随第一个 alignment_round
364
+ // 启动后常驻到插件 dispose(会话关闭);终态对齐的页面保持可查看,直到
365
+ // 下一次对齐在同一 URL 上覆盖内容。不再做终态 idle 停服。
366
+ if (server)
367
+ return server;
368
+ server = await startAlignmentServer({
369
+ getState: () => {
370
+ const record = activeRecord();
371
+ if (!record)
372
+ return undefined;
373
+ // awaitingRevision: feedback was delivered and the model has not
374
+ // bumped the revision yet — the page shows "AI revising…".
375
+ const awaiting = record.status === "aligning" && deliveredRevisionByAlignment.get(record.id) === record.revision;
376
+ return pageView(record, undefined, awaiting);
377
+ },
378
+ onRoundPost: (post) => handleRoundPost(post),
379
+ }, { port: options.serverPort });
380
+ return server;
381
+ };
382
+ const injectFeedback = async (cached) => {
383
+ const message = feedbackMessage(cached.record, cached.result, loadTransitions(api));
384
+ const send = api.session?.sendMessage;
385
+ if (!send) {
386
+ throw new AlignmentError("invalid-input", "host session does not expose sendMessage; feedback cannot be injected");
387
+ }
388
+ await send({ customType: "alignment.feedback", content: message, display: true }, { triggerTurn: true });
389
+ };
390
+ const handleRoundPost = async (post) => {
391
+ const record = activeRecord();
392
+ if (!record || (record.status !== "aligning" && record.status !== "finalizing")) {
393
+ return { status: 409, body: { error: "no alignment in progress" } };
394
+ }
395
+ const samePost = (candidate) => candidate.result.alignmentId === record.id && JSON.stringify(candidate.post) === JSON.stringify(post);
396
+ // Delivered idempotency: an identical replay of an already-delivered
397
+ // round returns the snapshot without re-injecting.
398
+ const pending = cachedRound;
399
+ if (pending && samePost(pending) && deliveredRoundByAlignment.get(record.id) === pending.result.round) {
400
+ return {
401
+ status: 200,
402
+ body: {
403
+ ok: true,
404
+ delivered: true,
405
+ stepConfirmed: pending.result.stepConfirmed,
406
+ revision: pending.record.revision,
407
+ },
408
+ };
409
+ }
410
+ // Submitted-but-undelivered replay retries only the injection.
411
+ if (pending && samePost(pending)) {
412
+ try {
413
+ await injectFeedback(pending);
414
+ // 同主路径:注入可能同步驱动模型回合,不回写回合前快照(走查 2026-10-04)。
415
+ for (const entry of markDelivered(pending.record, pending.result.round)) {
416
+ api.session?.appendEntry(TRANSITION_ENTRY, entry);
417
+ }
418
+ void api.publish({
419
+ type: "alignment.changed",
420
+ version: 1,
421
+ correlationId: STATE_ENTRY,
422
+ data: loadState(api),
423
+ });
424
+ state = loadState(api);
425
+ deliveredRoundByAlignment.set(record.id, pending.result.round);
426
+ // 重放路径同主路径:纯确认轮不进入"AI 修订中"(走查 2026-10-04)。
427
+ const replayHasContent = pending.result.comments.length > 0 ||
428
+ pending.result.commentReplies.length > 0 ||
429
+ pending.result.answers.length > 0 ||
430
+ pending.result.additions.length > 0;
431
+ if (replayHasContent)
432
+ deliveredRevisionByAlignment.set(record.id, pending.record.revision);
433
+ return {
434
+ status: 200,
435
+ body: {
436
+ ok: true,
437
+ delivered: true,
438
+ stepConfirmed: pending.result.stepConfirmed,
439
+ revision: pending.record.revision,
440
+ },
441
+ };
442
+ }
443
+ catch (error) {
444
+ return {
445
+ status: 503,
446
+ body: {
447
+ error: `feedback delivery failed: ${error instanceof Error ? error.message : String(error)}`,
448
+ retry: true,
449
+ },
450
+ };
451
+ }
452
+ }
453
+ // Page-initiated rollbacks (走查 2026-10-03) share the POST /round
454
+ // entry so the submission→injection→delivered protocol is identical.
455
+ let outcome;
456
+ let runOutcome;
457
+ try {
458
+ if (post.action === "retreat" || post.action === "rollback") {
459
+ if (post.baseRevision !== record.revision) {
460
+ return {
461
+ status: 409,
462
+ body: { code: "stale-state", error: "page snapshot is stale; reload and retry" },
463
+ };
464
+ }
465
+ }
466
+ if (post.action === "retreat") {
467
+ runOutcome = applyAlignmentAdvance(state, {
468
+ alignmentId: record.id,
469
+ action: "retreat",
470
+ step: post.targetStep,
471
+ note: "回退自确认页:用户在页面上点击了更早的环节",
472
+ });
473
+ }
474
+ else if (post.action === "rollback") {
475
+ runOutcome = applyVersionRollback(state, {
476
+ alignmentId: record.id,
477
+ targetRevision: post.targetRevision ?? -1,
478
+ });
479
+ }
480
+ else {
481
+ outcome = applyRoundPost(record, post);
482
+ }
483
+ }
484
+ catch (error) {
485
+ if (error instanceof AlignmentError) {
486
+ return {
487
+ status: error.code === "stale-state" ? 409 : 400,
488
+ body: { code: error.code, error: error.message },
489
+ };
490
+ }
491
+ return { status: 500, body: { error: error instanceof Error ? error.message : String(error) } };
492
+ }
493
+ // Persist the submission first (append-only fact), then attempt delivery.
494
+ const record2 = outcome
495
+ ? outcome.record
496
+ : runOutcome.state.alignments.find((candidate) => candidate.id === record.id);
497
+ const transitions2 = outcome ? outcome.transitions : runOutcome.transitions;
498
+ persist({
499
+ alignments: state.alignments.map((candidate) => (candidate.id === record2.id ? record2 : candidate)),
500
+ }, transitions2);
501
+ const syntheticResult = outcome?.result ?? {
502
+ alignmentId: record2.id,
503
+ round: record2.roundCount,
504
+ step: record2.activeStep,
505
+ revision: record2.revision,
506
+ approvals: [],
507
+ comments: [],
508
+ commentReplies: [],
509
+ answers: [],
510
+ additions: [],
511
+ stepConfirmed: false,
512
+ };
513
+ let cached = { post, record: record2, transitions: [], result: syntheticResult };
514
+ if (outcome?.result.stepConfirmed && outcome.record.status === "completed") {
515
+ // 终审确认(走查 2026-10-03):no workspace file is written; the
516
+ // compact listing rides the feedback message and `/alignment
517
+ // export` produces a file only on explicit request.
518
+ const lines = finalBaselineLines(outcome.record);
519
+ outcome.result.finalBaseline = lines;
520
+ cached = { ...cached, result: outcome.result };
521
+ }
522
+ cachedRound = cached;
523
+ try {
524
+ await injectFeedback(cached);
525
+ // injectFeedback 的 sendMessage(triggerTurn) 会同步驱动整个模型回合
526
+ //(agent-session sendCustomMessage → _runAgentPrompt):回合内的
527
+ // alignment_set 已把修订落账 entries。此处若回写回合前快照,会把模型
528
+ // 刚提交的修订整体抹掉(走查 2026-10-04 dogfood:细节步 10 块被还原成
529
+ // 空步)。只追加 round.delivered 审计条目,并从 entries 重建内存态。
530
+ for (const entry of markDelivered(cached.record, cached.result.round)) {
531
+ api.session?.appendEntry(TRANSITION_ENTRY, entry);
532
+ }
533
+ void api.publish({ type: "alignment.changed", version: 1, correlationId: STATE_ENTRY, data: loadState(api) });
534
+ state = loadState(api);
535
+ deliveredRoundByAlignment.set(cached.record.id, cached.result.round);
536
+ // 只有携带内容反馈(批注/回复/答案/补充)的轮次才进入"AI 修订中":
537
+ // 纯确认轮模型正确地不重发块,revision 不变——若也标记 awaiting,
538
+ // 页面会永远钉在机器人等待屏,用户反而无法确认剩余块(走查 2026-10-04)。
539
+ const hasContentFeedback = post.comments.length > 0 ||
540
+ post.commentReplies.length > 0 ||
541
+ post.answers.length > 0 ||
542
+ post.additions.length > 0;
543
+ if (hasContentFeedback)
544
+ deliveredRevisionByAlignment.set(cached.record.id, cached.record.revision);
545
+ return {
546
+ status: 200,
547
+ body: {
548
+ ok: true,
549
+ delivered: true,
550
+ stepConfirmed: cached.result.stepConfirmed,
551
+ revision: cached.record.revision,
552
+ },
553
+ };
554
+ }
555
+ catch (error) {
556
+ return {
557
+ status: 503,
558
+ body: {
559
+ error: `feedback delivery failed: ${error instanceof Error ? error.message : String(error)}`,
560
+ retry: true,
561
+ },
562
+ };
563
+ }
564
+ };
565
+ const runSet = (input) => {
566
+ const run = applyAlignmentSet(state, input);
567
+ const record = run.state.alignments.find((candidate) => candidate.id === input.id);
568
+ persist(run.state, run.transitions);
569
+ const warning = record.goalId && !goalKnownToSession(api, record.goalId)
570
+ ? `goalId "${record.goalId}" does not match any goal in this session's goal state; the alignment keeps the soft reference`
571
+ : undefined;
572
+ return recordSummary(record, { warning });
573
+ };
574
+ const setTool = api.registerTool({
575
+ name: "alignment_set",
576
+ label: "Set alignment",
577
+ description: "Create or update a requirement alignment task: full block set of the active step (goal→detail→design funnel). Unchanged blocks inherit user approval by content fingerprint; rewriting an approved block requires reviseApproved: true",
578
+ parameters: setSchema,
579
+ promptGuidelines: PROMPT_GUIDELINES,
580
+ renderResult: alignmentToolResultRenderer,
581
+ // 双调用约定(goal 插件同款,走查 2026-10-04):会话路径传
582
+ // (toolCallId, params, signal, onUpdate),runtime facade 路径传
583
+ // (input, context?, signal?)——second 缺省时取 first。
584
+ execute: async (first, second) => {
585
+ const input = toolInput(first, second);
586
+ return runSet(input);
587
+ },
588
+ });
589
+ const roundTool = api.registerTool({
590
+ name: "alignment_round",
591
+ label: "Open alignment page",
592
+ description: "Open (or refresh) the local confirmation page for the active alignment and return its URL plus progress facts. Non-blocking: feedback arrives later as an alignment.feedback message",
593
+ parameters: roundSchema,
594
+ promptGuidelines: PROMPT_GUIDELINES,
595
+ renderResult: alignmentToolResultRenderer,
596
+ execute: async (first, second) => {
597
+ const input = toolInput(first, second);
598
+ const id = typeof input.alignmentId === "string" ? input.alignmentId : "";
599
+ const record = state.alignments.find((candidate) => candidate.id === id);
600
+ if (!record)
601
+ throw new Error(`unknown alignment: ${id}`);
602
+ if (record.status !== "aligning")
603
+ throw new Error(`alignment is ${record.status}: ${id}`);
604
+ if (!api.session?.sendMessage) {
605
+ throw new Error("host session does not expose sendMessage; the alignment confirmation page cannot deliver feedback on this host");
606
+ }
607
+ const running = await ensureServer();
608
+ return recordSummary(record, { url: running.url });
609
+ },
610
+ });
611
+ const advanceTool = api.registerTool({
612
+ name: "alignment_advance",
613
+ label: "Advance alignment",
614
+ description: "inspect (read-only status/gate/baseline), retreat (roll back to an earlier step, resets downstream confirmations), rollback (restore the active step's archived version — the latest snapshot, or a specific versionId), or cancel the alignment",
615
+ parameters: advanceSchema,
616
+ promptGuidelines: PROMPT_GUIDELINES,
617
+ renderResult: alignmentToolResultRenderer,
618
+ execute: async (first, second) => {
619
+ const input = toolInput(first, second);
620
+ if (input.action === "inspect") {
621
+ const record = state.alignments.find((candidate) => candidate.id === input.alignmentId);
622
+ if (!record)
623
+ throw new Error(`unknown alignment: ${input.alignmentId}`);
624
+ return recordSummary(record, { baseline: finalBaselineLines(record) });
625
+ }
626
+ if (input.action === "rollback") {
627
+ const record = state.alignments.find((candidate) => candidate.id === input.alignmentId);
628
+ if (!record)
629
+ throw new Error(`unknown alignment: ${input.alignmentId}`);
630
+ // 缺省 versionId = active 步最近一次快照;无快照 / 指向不存在的
631
+ // 快照由 applyVersionRollback 抛既有结构化错误(设计 §6)。
632
+ const stepVersions = (record.versions ?? []).filter((snapshot) => snapshot.step === record.activeStep);
633
+ const targetRevision = input.versionId ?? stepVersions.at(-1)?.revision;
634
+ if (targetRevision === undefined) {
635
+ throw new AlignmentError("invalid-input", `no snapshot of step ${record.activeStep} to roll back to`);
636
+ }
637
+ const run = applyVersionRollback(state, { alignmentId: input.alignmentId, targetRevision });
638
+ persist(run.state, run.transitions);
639
+ return recordSummary(run.state.alignments.find((candidate) => candidate.id === input.alignmentId));
640
+ }
641
+ const run = applyAlignmentAdvance(state, input);
642
+ persist(run.state, run.transitions);
643
+ return recordSummary(run.state.alignments.find((candidate) => candidate.id === input.alignmentId));
644
+ },
645
+ });
646
+ const command = api.registerCommand({
647
+ name: "alignment",
648
+ description: "Show this session's alignments (progress, blockers, audit log); `export [id] [path]` writes a self-contained read-only HTML snapshot",
649
+ execute: async (args, context) => {
650
+ try {
651
+ const parts = args
652
+ .trim()
653
+ .split(/\s+/)
654
+ .filter((part) => part !== "");
655
+ if (parts.length === 0)
656
+ return { status: "success", message: listLines(state) };
657
+ if (parts[0] === "why") {
658
+ const record = state.alignments.find((candidate) => candidate.status === "aligning") ?? state.alignments.at(-1);
659
+ if (!record)
660
+ return { status: "error", message: "no alignments in this session" };
661
+ const extra = cachedRound && deliveredRoundByAlignment.get(record.id) !== cachedRound.result.round
662
+ ? `\nundelivered round: r${cachedRound.result.round} (injection failed; the page can retry)`
663
+ : "";
664
+ return { status: "success", message: whyLines(record) + extra };
665
+ }
666
+ if (parts[0] === "log") {
667
+ const count = Number.parseInt(parts[1] ?? "20", 10);
668
+ return {
669
+ status: "success",
670
+ message: logLines(loadTransitions(api), Number.isFinite(count) && count > 0 ? count : 20),
671
+ };
672
+ }
673
+ if (parts[0] === "export") {
674
+ const id = parts[1];
675
+ const record = id
676
+ ? state.alignments.find((candidate) => candidate.id === id)
677
+ : (state.alignments.find((candidate) => candidate.status === "aligning") ??
678
+ state.alignments.find((candidate) => candidate.status === "finalizing") ??
679
+ state.alignments.at(-1));
680
+ if (!record)
681
+ return { status: "error", message: `unknown alignment: ${id ?? "(none in session)"}` };
682
+ // Explicit user request — the only path that writes a file.
683
+ const target = exportTargetPath(context?.cwd ?? process.cwd(), record, parts[2]);
684
+ const written = writeAlignmentExport(record, loadTransitions(api), target);
685
+ return {
686
+ status: "success",
687
+ message: `exported: ${written}`,
688
+ links: [{ label: written, url: `file:///${written.split("\\").join("/")}` }],
689
+ };
690
+ }
691
+ return {
692
+ status: "error",
693
+ message: "Usage: /alignment | /alignment why | /alignment log [N] | /alignment export [id] [path]",
694
+ };
695
+ }
696
+ catch (error) {
697
+ return { status: "error", message: error instanceof Error ? error.message : String(error) };
698
+ }
699
+ },
700
+ });
701
+ const stopServer = () => {
702
+ const stopping = server;
703
+ server = undefined;
704
+ if (stopping)
705
+ void stopping.stop().catch(() => undefined);
706
+ };
707
+ // The command registration doubles as the teardown hook for the local
708
+ // confirmation server (plugin dispose must release the port).
709
+ const commandWithTeardown = {
710
+ id: command.id,
711
+ kind: command.kind,
712
+ dispose: () => {
713
+ void command.dispose();
714
+ stopServer();
715
+ },
716
+ };
717
+ return [setTool, roundTool, advanceTool, commandWithTeardown];
718
+ }
719
+ //# sourceMappingURL=index.js.map