@bolloon/bolloon-agent 0.4.32 → 0.5.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.
Files changed (157) hide show
  1. package/dist/agents/chain/explorer.js +52 -0
  2. package/dist/agents/chain/index.js +2 -0
  3. package/dist/agents/chain/onchain-trade.js +15 -1
  4. package/dist/agents/contacts/chain.js +27 -7
  5. package/dist/agents/execution-supervisor.js +493 -35
  6. package/dist/agents/external-events.js +35 -6
  7. package/dist/agents/gateway-group.js +100 -12
  8. package/dist/agents/goal-criteria.js +10 -3
  9. package/dist/agents/goal-flywheel/block-executor.js +346 -0
  10. package/dist/agents/goal-flywheel/continuation-decision.js +592 -0
  11. package/dist/agents/goal-flywheel/goal-change.js +754 -0
  12. package/dist/agents/goal-flywheel/index.js +26 -0
  13. package/dist/agents/goal-flywheel/memory-layers.js +227 -0
  14. package/dist/agents/goal-flywheel/run-closure.js +809 -0
  15. package/dist/agents/goal-flywheel/skill-candidate.js +628 -0
  16. package/dist/agents/goal-flywheel/types.js +329 -0
  17. package/dist/agents/goal-flywheel/wiring/change.js +288 -0
  18. package/dist/agents/goal-flywheel/wiring/closure.js +697 -0
  19. package/dist/agents/goal-flywheel/wiring/continuation.js +432 -0
  20. package/dist/agents/goal-flywheel/wiring/contract.js +270 -0
  21. package/dist/agents/goal-flywheel/wiring/index.js +26 -0
  22. package/dist/agents/goal-flywheel/wiring/monitor.js +254 -0
  23. package/dist/agents/goal-flywheel/wiring/seams.js +592 -0
  24. package/dist/agents/goal-flywheel/work-contract.js +478 -0
  25. package/dist/agents/goal-flywheel/work-monitor.js +488 -0
  26. package/dist/agents/goal-flywheel-wiring.js +1870 -0
  27. package/dist/agents/goal-state-reducer.js +406 -0
  28. package/dist/agents/goal-store.js +35 -1
  29. package/dist/agents/mobile-flywheel-view.js +144 -0
  30. package/dist/agents/mobile-task-actions-verify.js +74 -0
  31. package/dist/agents/mobile-task-actions.js +178 -0
  32. package/dist/agents/mobile-task-views.js +199 -0
  33. package/dist/agents/network-pulse.js +525 -27
  34. package/dist/agents/pi-sdk.js +75 -20
  35. package/dist/agents/run-store.js +18 -5
  36. package/dist/agents/skill-readiness.js +52 -10
  37. package/dist/agents/subagent-manager.js +209 -3
  38. package/dist/agents/task/task-onchain-runner.js +19 -1
  39. package/dist/agents/task/task-runner.js +36 -6
  40. package/dist/agents/task-board.js +702 -0
  41. package/dist/agents/task-group.js +524 -0
  42. package/dist/agents/task-public-text.js +89 -0
  43. package/dist/cli/commands/index.js +3 -0
  44. package/dist/cli/commands/tasks.js +977 -8
  45. package/dist/cli/identity-command.js +177 -0
  46. package/dist/cli/protocol-envelope.js +4 -0
  47. package/dist/cli/setup-wizard.js +71 -0
  48. package/dist/cli/update-commands.js +150 -24
  49. package/dist/cli-entry.js +12 -2
  50. package/dist/electron-build/agents/chain/explorer.js +59 -0
  51. package/dist/electron-build/agents/chain/explorer.js.map +1 -0
  52. package/dist/electron-build/agents/contacts/chain.js +26 -6
  53. package/dist/electron-build/agents/contacts/chain.js.map +1 -1
  54. package/dist/electron-build/agents/execution-supervisor.js +492 -34
  55. package/dist/electron-build/agents/execution-supervisor.js.map +1 -1
  56. package/dist/electron-build/agents/external-events.js +34 -5
  57. package/dist/electron-build/agents/external-events.js.map +1 -1
  58. package/dist/electron-build/agents/goal-criteria.js +10 -3
  59. package/dist/electron-build/agents/goal-criteria.js.map +1 -1
  60. package/dist/electron-build/agents/goal-flywheel/continuation-decision.js +598 -0
  61. package/dist/electron-build/agents/goal-flywheel/continuation-decision.js.map +1 -0
  62. package/dist/electron-build/agents/goal-flywheel/goal-change.js +773 -0
  63. package/dist/electron-build/agents/goal-flywheel/goal-change.js.map +1 -0
  64. package/dist/electron-build/agents/goal-flywheel/memory-layers.js +269 -0
  65. package/dist/electron-build/agents/goal-flywheel/memory-layers.js.map +1 -0
  66. package/dist/electron-build/agents/goal-flywheel/run-closure.js +818 -0
  67. package/dist/electron-build/agents/goal-flywheel/run-closure.js.map +1 -0
  68. package/dist/electron-build/agents/goal-flywheel/skill-candidate.js +642 -0
  69. package/dist/electron-build/agents/goal-flywheel/skill-candidate.js.map +1 -0
  70. package/dist/electron-build/agents/goal-flywheel/types.js +333 -0
  71. package/dist/electron-build/agents/goal-flywheel/types.js.map +1 -0
  72. package/dist/electron-build/agents/goal-flywheel/wiring/change.js +294 -0
  73. package/dist/electron-build/agents/goal-flywheel/wiring/change.js.map +1 -0
  74. package/dist/electron-build/agents/goal-flywheel/wiring/closure.js +742 -0
  75. package/dist/electron-build/agents/goal-flywheel/wiring/closure.js.map +1 -0
  76. package/dist/electron-build/agents/goal-flywheel/wiring/continuation.js +444 -0
  77. package/dist/electron-build/agents/goal-flywheel/wiring/continuation.js.map +1 -0
  78. package/dist/electron-build/agents/goal-flywheel/wiring/contract.js +279 -0
  79. package/dist/electron-build/agents/goal-flywheel/wiring/contract.js.map +1 -0
  80. package/dist/electron-build/agents/goal-flywheel/wiring/index.js +111 -0
  81. package/dist/electron-build/agents/goal-flywheel/wiring/index.js.map +1 -0
  82. package/dist/electron-build/agents/goal-flywheel/wiring/monitor.js +264 -0
  83. package/dist/electron-build/agents/goal-flywheel/wiring/monitor.js.map +1 -0
  84. package/dist/electron-build/agents/goal-flywheel/wiring/seams.js +612 -0
  85. package/dist/electron-build/agents/goal-flywheel/wiring/seams.js.map +1 -0
  86. package/dist/electron-build/agents/goal-flywheel/work-contract.js +488 -0
  87. package/dist/electron-build/agents/goal-flywheel/work-contract.js.map +1 -0
  88. package/dist/electron-build/agents/goal-flywheel/work-monitor.js +501 -0
  89. package/dist/electron-build/agents/goal-flywheel/work-monitor.js.map +1 -0
  90. package/dist/electron-build/agents/goal-flywheel-wiring.js +1962 -0
  91. package/dist/electron-build/agents/goal-flywheel-wiring.js.map +1 -0
  92. package/dist/electron-build/agents/goal-state-reducer.js +414 -0
  93. package/dist/electron-build/agents/goal-state-reducer.js.map +1 -0
  94. package/dist/electron-build/agents/goal-store.js +35 -1
  95. package/dist/electron-build/agents/goal-store.js.map +1 -1
  96. package/dist/electron-build/agents/local-signer.js +101 -0
  97. package/dist/electron-build/agents/local-signer.js.map +1 -0
  98. package/dist/electron-build/agents/network-pulse.js +531 -28
  99. package/dist/electron-build/agents/network-pulse.js.map +1 -1
  100. package/dist/electron-build/agents/pi-sdk.js +74 -19
  101. package/dist/electron-build/agents/pi-sdk.js.map +1 -1
  102. package/dist/electron-build/agents/run-store.js +20 -5
  103. package/dist/electron-build/agents/run-store.js.map +1 -1
  104. package/dist/electron-build/agents/skill-readiness.js +52 -9
  105. package/dist/electron-build/agents/skill-readiness.js.map +1 -1
  106. package/dist/electron-build/agents/task/task-runner.js +35 -5
  107. package/dist/electron-build/agents/task/task-runner.js.map +1 -1
  108. package/dist/electron-build/agents/task-board.js +761 -0
  109. package/dist/electron-build/agents/task-board.js.map +1 -0
  110. package/dist/electron-build/agents/task-contract.js +382 -0
  111. package/dist/electron-build/agents/task-contract.js.map +1 -0
  112. package/dist/electron-build/orbitdb/cid-database.js +83 -17
  113. package/dist/electron-build/orbitdb/cid-database.js.map +1 -1
  114. package/dist/electron-build/orbitdb/ipfs-node.js +31 -2
  115. package/dist/electron-build/orbitdb/ipfs-node.js.map +1 -1
  116. package/dist/electron-build/utils/dual-source.js +471 -0
  117. package/dist/electron-build/utils/dual-source.js.map +1 -0
  118. package/dist/electron-build/utils/update-manager.js +331 -57
  119. package/dist/electron-build/utils/update-manager.js.map +1 -1
  120. package/dist/electron-build/utils/update-state.js +16 -1
  121. package/dist/electron-build/utils/update-state.js.map +1 -1
  122. package/dist/electron-build/utils/version-info.js +82 -2
  123. package/dist/electron-build/utils/version-info.js.map +1 -1
  124. package/dist/index.js +103 -12
  125. package/dist/ios/chain-explorer.js +617 -0
  126. package/dist/ios/explorer.html +163 -0
  127. package/dist/ios/index.html +96 -0
  128. package/dist/ios/mobile-agent.js +2 -2
  129. package/dist/ios/mobile-contacts.js +329 -0
  130. package/dist/ios/mobile-core.js +1379 -226
  131. package/dist/ios/mobile-privacy.js +185 -0
  132. package/dist/ios/mobile.css +19 -0
  133. package/dist/ios/mobile.html +96 -0
  134. package/dist/ios/mobile.js +706 -7
  135. package/dist/ios/routes-chain-index.js +108 -0
  136. package/dist/ios/routes-contacts.js +420 -0
  137. package/dist/ios/routes-onchain-trade.js +99 -0
  138. package/dist/ios/routes-x402-info.js +1 -0
  139. package/dist/ios/server.js +762 -0
  140. package/dist/ios/task-frame-server.js +211 -0
  141. package/dist/orbitdb/cid-database.js +81 -16
  142. package/dist/orbitdb/ipfs-node.js +31 -2
  143. package/dist/utils/dual-source.js +426 -0
  144. package/dist/utils/update-cli.js +16 -21
  145. package/dist/utils/update-health.js +35 -5
  146. package/dist/utils/update-manager.js +329 -57
  147. package/dist/utils/update-state.js +16 -1
  148. package/dist/utils/version-info.js +75 -1
  149. package/dist/web/mobile-contacts.js +44 -11
  150. package/dist/web/mobile-core.js +1197 -513
  151. package/dist/web/mobile-tasks.js +339 -0
  152. package/dist/web/mobile.css +19 -0
  153. package/dist/web/mobile.html +64 -0
  154. package/dist/web/mobile.js +531 -1
  155. package/dist/web/routes-mobile-tasks.js +353 -0
  156. package/dist/web/server.js +163 -4
  157. package/package.json +3 -1
@@ -0,0 +1,329 @@
1
+ /**
2
+ * goal-flywheel/types.ts — 「Goal 长期执行飞轮」**接口冻结层** (2026-09-25)
3
+ *
4
+ * 这一层**只有类型与文档**, 没有实现, 不改任何现有调用方。
5
+ * 目的: 把已经存在但没收敛的能力, 收敛成一个**长期执行飞轮**的可复用接口面 ——
6
+ * 而不是再造一个更大的 Agent 平台。
7
+ *
8
+ * 飞轮 (一轮的目标生命周期):
9
+ * 目标 → 判断下一步 → 自主决定节奏 → 执行或派遣 → 监控阻塞 → 动态注入新要求
10
+ * → 汇总结果 → 写入 Memory → 形成 Skill 改进候选 → 下一次直接复用
11
+ *
12
+ * 与既有模块的关系 (刻意不重复、不冲突):
13
+ * - `goal-store.ts` 的 `GoalContinuation` 是**已有**的调度元数据 (nextAction/wakeAt/…)。
14
+ * 本文件**不重复定义**它, 而是给出**冻结超集** `GoalContinuationRecord`
15
+ * (机器继续记录) —— 见 §7。两者必须结构兼容, 由 `goal-flywheel-types.test.ts` 钉住。
16
+ * - `skill-writer.ts` 的 `SkillCandidate` 是 run-end 的**文本候选** (name/description/body)。
17
+ * 本飞轮的 Skill 改进候选是**更强的结构** (必须有输入输出 schema/失败边界/证据/批准),
18
+ * 因此刻意命名为 `SkillImprovementCandidate` —— **不是同一个东西**, 见 §4。
19
+ * - `contacts/policy.ts` 的 `BlockKind` 是**联系方式策略域**的拒绝原因;
20
+ * 本文件 §6 的 `BlockKind` 是**长期执行域**的阻塞类型。同名不同域, 永不合并。
21
+ * - `Watchdog/heartbeat` 只看进程存活; 本文件的 `BlockRecord` 看的是**任务有没有卡住**。
22
+ *
23
+ * 硬底线 (不是"任务节奏", 而是安全线): 单 Run 时间上限 · 单 Goal 预算上限 ·
24
+ * 无进展熔断阈值 —— 见 `HardLimits`。**不再以「第几轮」作为继续依据**。
25
+ */
26
+ export const RISK_LEVELS = ['low', 'medium', 'high'];
27
+ // ============================================================================
28
+ // §1. P0 — 节奏由进展决定 (ContinuationDecision)
29
+ // ============================================================================
30
+ /** 长期执行的状态: 它描述"为什么下一步是它", 不描述"跑了第几轮" */
31
+ export const CONTINUATION_STATES = [
32
+ 'progressing', // 有新的可核验进展
33
+ 'blocked', // 被阻塞 (见 BlockRecord)
34
+ 'waiting_external', // 等外部回话 (事件或 wakeAt 唤醒)
35
+ 'waiting_agent', // 等子 Agent / 另一个执行者回报
36
+ 'needs_decision', // 需要人做一个取舍
37
+ 'no_progress', // 连续无新证据 / 无新完成判据 → stalled
38
+ 'completed', // 判据全满足且有证据
39
+ 'failed', // 判据不可能再满足 (不是"这次没跑好")
40
+ ];
41
+ /** 每个 Run 结束必须产出的**唯一决策** (Agent 提, Supervisor 采用) */
42
+ export const CONTINUATION_DECISIONS = [
43
+ 'continue', // 有进展 → 直接开下一个 Run
44
+ 'wait', // 等外部/等时间 → 挂 wakeAt 或等事件
45
+ 'delegate', // 需要另一个 Agent/能力 → 签 AgentWorkContract
46
+ 'ask_human', // 需要人决定 (判据 / 预算 / 权限 / 取舍)
47
+ 'complete', // 判据全满足且有证据
48
+ 'fail', // 不可能再满足 (附原因)
49
+ 'pause', // 人主动暂停 (不是自动决策)
50
+ ];
51
+ /** Agent 判断"继续没有价值"时, 必须交出的理由 (由 Supervisor 负责真的停) */
52
+ export const STOP_REASONS = [
53
+ 'no_value_continuing', // 继续也改变不了结果
54
+ 'objective_unreachable', // 客观不可能 (缺条件/缺权限/物理限制)
55
+ 'duplicate_work', // 与已完成的工作重复
56
+ 'out_of_scope', // 超出了目标范围
57
+ ];
58
+ // ============================================================================
59
+ // §2. P1 — 强制收尾飞轮 (四类产物)
60
+ // ============================================================================
61
+ /**
62
+ * Run 结束的**固定顺序** (成功 / 失败 / 中断恢复后的 Run **都必须走**):
63
+ * 主执行结束 → 读完整步骤与证据 → Final Review → 提取事实/教训/Skill 候选
64
+ * → 写 Memory → 写 Skill Candidate → 更新 Goal continuation → 用户汇报 → 决定是否继续
65
+ */
66
+ export const RUN_CLOSURE_STEPS = [
67
+ 'run_ended',
68
+ 'read_full_steps_and_evidence',
69
+ 'final_review',
70
+ 'extract_facts_lessons_and_candidates',
71
+ 'write_memory',
72
+ 'write_skill_candidate',
73
+ 'update_goal_continuation',
74
+ 'user_report',
75
+ 'continuation_decision',
76
+ ];
77
+ /** 收尾产物的四类 (缺任何一类 = 收尾未完成) */
78
+ export const CLOSURE_ARTIFACTS = [
79
+ 'facts', // 发生了什么 / 哪些证据已确认 / 哪些只是推测
80
+ 'lessons', // 什么方法有效 / 失败 / 下次改什么
81
+ 'skill_candidates', // 仅当: 重复出现 + 可复用 + 边界清晰 + 输入输出明确 + 有真实成功证据
82
+ 'next_step', // 下次先做什么 / 为何 / 等什么 / 需哪个 Agent 与 Skill
83
+ ];
84
+ // ============================================================================
85
+ // §3. P1b — Memory 分层
86
+ // ============================================================================
87
+ export const MEMORY_LAYERS = [
88
+ 'run_fact', // 每次可自动写
89
+ 'lesson', // 需 Review 判为可复用
90
+ 'decision', // 涉用户偏好或重要取舍, 必须保留来源
91
+ 'skill_signal', // 只生成候选, 不许直接成 Skill
92
+ 'temporary', // 任务结束自动过期归档
93
+ ];
94
+ /** 事实的确定性: 已确认 vs 推测 (推测**永不**当证据用) */
95
+ export const ASSERTION_KINDS = ['confirmed', 'inferred'];
96
+ // ============================================================================
97
+ // §4. P1b — Skill 改进候选与更新流程
98
+ // ============================================================================
99
+ /** Skill 更新流程 (固定 8 步; 自动更新**不得**覆盖正在执行的 snapshot) */
100
+ export const SKILL_UPDATE_FLOW = [
101
+ 'run_evidence',
102
+ 'review_extract_candidate',
103
+ 'compare_with_existing_skill',
104
+ 'validate_candidate',
105
+ 'draft_new_version',
106
+ 'run_skill_acceptance',
107
+ 'approve',
108
+ 'write_content_hash_and_change_reason',
109
+ ];
110
+ /**
111
+ * **防 Skill 垃圾**: 命中任意一条 → 不得成为正式 Skill。
112
+ * (一次偶然成功不得直接成正式 Skill)
113
+ */
114
+ export const SKILL_JUNK_REASONS = [
115
+ 'single_success', // 只成功一次
116
+ 'no_io_schema', // 无明确输入输出
117
+ 'temp_path_dependency', // 依赖临时路径
118
+ 'no_failure_boundary', // 无失败边界
119
+ 'unverifiable_result', // 结果不可验证
120
+ 'one_line_experience', // 只是一句经验
121
+ 'duplicate_of_existing', // 与已有重复
122
+ ];
123
+ export const SKILL_CANDIDATE_STATUSES = [
124
+ 'draft',
125
+ 'pending_review',
126
+ 'approved',
127
+ 'rejected',
128
+ 'promoted',
129
+ ];
130
+ /** 父负责做的事 */
131
+ export const PARENT_RESPONSIBILITIES = [
132
+ 'decompose_goal',
133
+ 'assign_work',
134
+ 'merge_results',
135
+ 'final_judgement',
136
+ 'report_to_user',
137
+ ];
138
+ /** 子负责做的事 */
139
+ export const CHILD_RESPONSIBILITIES = [
140
+ 'single_clear_subgoal',
141
+ 'only_contract_allowed_tools',
142
+ 'periodic_heartbeat',
143
+ 'report_blocks_proactively',
144
+ 'structured_result',
145
+ ];
146
+ /** 子 Agent **不得**做的事 (越界即判失败, 由父/监控层拦) */
147
+ export const CHILD_PROHIBITIONS = [
148
+ 'mutate_parent_goal_state',
149
+ 'expand_own_budget',
150
+ 'spawn_unbounded_subtasks',
151
+ 'mark_unverified_as_complete',
152
+ 'rewrite_success_criteria',
153
+ ];
154
+ export const WORK_REPORT_STATUSES = [
155
+ 'completed',
156
+ 'partial',
157
+ 'blocked',
158
+ 'failed',
159
+ 'cancelled',
160
+ ];
161
+ export const CHECK_VERDICTS = ['pass', 'fail', 'unknown'];
162
+ // ============================================================================
163
+ // §6. P3 — 阻塞监控 (WorkMonitor / BlockRecord)
164
+ // ============================================================================
165
+ /**
166
+ * 长期执行域的阻塞类型。
167
+ * 与 `contacts/policy.ts` 的 `BlockKind` (联系方式策略拒绝原因) **同名不同域**, 永不合并。
168
+ */
169
+ export const BLOCK_KINDS = [
170
+ 'no_heartbeat', // 子 Agent 不发心跳了
171
+ 'waiting_dependency', // 等依赖 (另一个 work / 外部服务)
172
+ 'repeated_failure', // 反复失败
173
+ 'no_progress', // 活着但没进展
174
+ 'tool_blocked', // 工具/资源被阻
175
+ 'budget_blocked', // 预算用尽 (硬底线)
176
+ 'permission_blocked', // 权限不足
177
+ 'runner_unavailable', // 没有可用执行器 (只诊断, 不假装跑过)
178
+ 'external_timeout', // 外部等待超时
179
+ 'report_missing', // 报告不完整 / 根本没回
180
+ ];
181
+ /** 谁该处理这个阻塞 */
182
+ export const BLOCK_OWNERS = ['parent', 'child', 'external', 'system', 'user'];
183
+ /** 处理动作 (监控层可选的动作集, 不自动绕过 Harness) */
184
+ export const BLOCK_RESOLUTION_ACTIONS = [
185
+ 'send_adjustment', // 先发一次调整指令
186
+ 'replace_child', // 停掉并替换执行者
187
+ 'takeover', // 父接管 (先查 lease)
188
+ 'request_report', // 要求补齐报告
189
+ 'escalate_parent', // 上报父
190
+ 'change_plan', // 允许父改计划
191
+ 'wait_dependency', // 继续等 (挂 escalationAt)
192
+ 'needs_human', // 转人工
193
+ ];
194
+ /**
195
+ * 用户可见的**六类**状态 (其余内部状态不外露):
196
+ * 正在执行 / 等待外部回复 / 子 Agent 被阻塞 / 暂时没有进展 / 需要你决定 / **已结束**
197
+ *
198
+ * 第 6 类 (`ended`) 是 2026-09-25 接线时补的: 前五类里没有"已结束", 于是 P1 的收尾汇报只能
199
+ * 借用 `executing` —— 把一个**已经结束**的目标显示成"正在执行"正是 P3/P4 要防的假象。
200
+ * 补法只有一种: 改冻结层 + 单独一个提交 (见 `goal-flywheel-types.test.ts` 的计数门)。
201
+ */
202
+ export const USER_VISIBLE_STATES = [
203
+ 'executing',
204
+ 'waiting_external_reply',
205
+ 'child_blocked',
206
+ 'no_progress',
207
+ 'needs_your_decision',
208
+ // 2026-09-25 新增 (第 6 类): 目标已结束 (completed / failed / abandoned) ——
209
+ // "已结束"必然需要人接一下 (接受结论 / 重试 / 立新目标), 但**不许**说成"正在执行"。
210
+ 'ended',
211
+ ];
212
+ /** 用户态文案 (只暴露六类, 不出现 lease/reducer/retry counter 这类内部词) */
213
+ export const USER_VISIBLE_STATE_LABELS = {
214
+ executing: { zh: '正在执行', en: 'Executing' },
215
+ waiting_external_reply: { zh: '等待外部回复', en: 'Waiting for an external reply' },
216
+ child_blocked: { zh: '子 Agent 被阻塞', en: 'A sub-agent is blocked' },
217
+ no_progress: { zh: '暂时没有进展', en: 'Temporarily no progress' },
218
+ needs_your_decision: { zh: '需要你决定', en: 'Your decision is needed' },
219
+ ended: { zh: '已结束', en: 'Ended' },
220
+ };
221
+ // ============================================================================
222
+ // §7. P4 / P4b — 新要求注入 (GoalChangeRequest) 与两份输出
223
+ // ============================================================================
224
+ /** 变更分类 */
225
+ export const CHANGE_KINDS = [
226
+ 'clarification', // 补充说明 (不改判据)
227
+ 'priority_change', // 优先级变化
228
+ 'success_criteria_change', // 完成判据变化 (必须增版本号)
229
+ 'budget_change', // 预算变化 (扩大不许 Agent 自动批)
230
+ 'permission_change', // 权限变化
231
+ 'scope_expansion', // 扩大范围
232
+ 'scope_reduction', // 缩小范围
233
+ 'abort', // 撤销 / 终止
234
+ ];
235
+ export const CHANGE_SOURCES = ['user', 'agent', 'external', 'system'];
236
+ /** `user_revocation` 优先级最高 (用户明确撤销压过一切) */
237
+ export const CHANGE_PRIORITIES = ['user_revocation', 'high', 'normal', 'low'];
238
+ export const CHANGE_STATUSES = [
239
+ 'received',
240
+ 'triaged',
241
+ 'needs_approval',
242
+ 'scheduled_next_run',
243
+ 'applied',
244
+ 'rejected',
245
+ 'superseded',
246
+ ];
247
+ export const CHANGE_SCOPE_KINDS = [
248
+ 'goal',
249
+ 'run',
250
+ 'criteria',
251
+ 'budget',
252
+ 'permission',
253
+ 'child_work',
254
+ ];
255
+ /** 新要求注入的固定规则 (写进类型, 不靠记忆) */
256
+ export const GOAL_CHANGE_RULES = [
257
+ 'user_revocation_wins', // 用户明确撤销优先级最高
258
+ 'agent_cannot_approve_budget', // 扩预算不得由 Agent 自动批准
259
+ 'criteria_change_bumps_version', // 改完成判据必须增版本号
260
+ 'history_not_rewritten', // 当前 Run 历史不可被新要求改写 (只影响后续)
261
+ 'children_receive_new_version', // 子 Agent 必须收到变更版本
262
+ ];
263
+ /** 长期执行生命周期状态 (与 goal-store 的 GoalStatus 同域; 这里作为冻结面重列一次) */
264
+ export const GOAL_LIFECYCLE_STATES = [
265
+ 'active',
266
+ 'recovering',
267
+ 'retry_wait',
268
+ 'awaiting_external',
269
+ 'stalled',
270
+ 'paused',
271
+ 'needs_human',
272
+ 'completed',
273
+ 'failed',
274
+ 'abandoned',
275
+ ];
276
+ /** 只有这几个状态可以"结束" (其余必须回答下一步) */
277
+ export const GOAL_TERMINAL_STATES = ['completed', 'failed', 'abandoned'];
278
+ /** 汇报里**允许出现**的字段名 (与 MUST_NOT_EXPOSE_FIELDS 无交集) */
279
+ export const USER_REPORT_FIELDS = [
280
+ 'conclusion',
281
+ 'completed',
282
+ 'evidence',
283
+ 'remaining',
284
+ 'blockReasons',
285
+ 'nextStep',
286
+ 'willContinue',
287
+ 'expectedResumeAt',
288
+ 'visibleState',
289
+ ];
290
+ /** 汇报里**不得出现**的内部字段 (只给用户看结论与下一步) */
291
+ export const MUST_NOT_EXPOSE_FIELDS = [
292
+ 'lease',
293
+ 'reducer',
294
+ 'internal_status',
295
+ 'retry_counter',
296
+ 'worker_owner',
297
+ ];
298
+ // ============================================================================
299
+ // §8. P5 — 统一长周期验收 (后续阶段跑; 本轮只冻结用例清单)
300
+ // ============================================================================
301
+ export const LONG_RUN_ACCEPTANCE_CASES = [
302
+ // 正例
303
+ 'self_terminate_by_evidence', // 不设最大轮次, 按证据自行结束
304
+ 'no_progress_circuit_breaker', // 无进展自动熔断
305
+ 'child_block_detected_and_handled', // 子 Agent 被阻塞被发现/接管/升级
306
+ 'user_change_effective_next_run', // 用户中途注入新要求 → 下一 Run 生效
307
+ 'run_end_writes_memory_and_candidate', // Run 结束自动写 Memory + Skill Candidate
308
+ 'auto_resume_by_wake_at_or_event', // 汇报后按 wakeAt/事件自动继续
309
+ // 强负例
310
+ 'evidence_less_child_report_blocks_goal', // 漂亮但无证据 → 父 Goal **不完成**
311
+ 'single_success_never_promotes_skill', // 只有一次偶然成功 → **不得**自动晋升正式 Skill
312
+ ];
313
+ // ============================================================================
314
+ // §9. 本轮**不做** (写进类型面, 防止被"顺手实现")
315
+ // ============================================================================
316
+ export const FLYWHEEL_NON_GOALS = [
317
+ 'unbounded_autonomous_agent_swarms',
318
+ 'auto_spawn_many_subagents',
319
+ 'multi_level_recursive_delegation',
320
+ 'agent_run_its_own_agent_marketplace',
321
+ 'auto_write_every_result_as_skill',
322
+ 'auto_overwrite_formal_skills',
323
+ 'evidence_less_smart_scoring',
324
+ 'complex_pm_dashboards',
325
+ 'multiple_task_databases',
326
+ 'a_second_workflow_engine',
327
+ 'fixed_round_count_masquerading_as_long_run',
328
+ 'running_forever_instead_of_real_progress',
329
+ ];
@@ -0,0 +1,288 @@
1
+ /**
2
+ * wiring/change.ts — 接缝 ⑤「人中途改了什么要求, 用户该看到哪一类状态?」 (**M4 独占**)
3
+ *
4
+ * 归属: `seams.ts` 的 `SEAM_ROSTER` 里 `id: 'change'`, `stage: 'M4'`。
5
+ * M4 只准改本文件 + 声明的 `wiringPoints` (`goal-flywheel/goal-change.ts` · `web/server.ts`)。
6
+ *
7
+ * ## 这条接缝要回答的四件事 (以前只回答第四件)
8
+ *
9
+ * ```
10
+ * 用户/外部在目标**跑着的时候**提要求
11
+ * ① 原话逐字入档了吗? → 没原文 / 被改写过 → 拒绝 (原话就是证据)
12
+ * ② 在跑的这一轮怎么办? → 撤销 = 停; 其余 = 让这一轮跑完, 只影响后续 Run (规则 4)
13
+ * ③ 在跑的子 Agent 怎么办? → 规则 5: 逐 workId 一份下发内容, 必须先 ack 再继续
14
+ * ④ 用户该看到哪一类状态? → 有待决定的变更 → `needs_your_decision` (只升不降)
15
+ * ```
16
+ *
17
+ * ## 为什么"在跑的 Run 要停"这件事归这条接缝
18
+ *
19
+ * 用户撤销的语义是"别再继续了"。撤销落到 Goal 上 (`abandoned`) 之后, **在飞的 Run 并不会自己
20
+ * 发现** —— 执行器只在每一轮循环开头读一次自己 Run 记录的状态 (`paused` / `aborted` 就自行停下,
21
+ * 轮内不打断)。所以"停"必须由**注入变更的那一方**写进 Run 记录: 除了这条接缝, 没有第二个人
22
+ * 知道"用户刚刚撤销了"。写状态用的是**既有的外部控制面原语**(与 `POST /api/runs/:id/abort`
23
+ * 同一条路), 不是新造一条停止通道 —— 否则就是第二套事实。
24
+ *
25
+ * ## 两条边界 (接缝的自律)
26
+ *
27
+ * · **判定在 `goal-change.ts` (纯函数), 副作用在接线层** —— 接缝自己不读盘/不写盘/不读钟:
28
+ * "在跑的 Run 是谁"由宿主注入 (`runningRun`), "怎么停"由宿主注入 (`stopRunningRun`)。
29
+ * 宿主没注入 → 接缝返回 `stopped: false` + 结构化原因, **绝不假装停过** (也不偷偷放行)。
30
+ * · **只升不降**: 变更对用户可见态的影响只有"抬成 `needs_your_decision`"这一种, 绝不把
31
+ * `ended` / `child_blocked` / `no_progress` 降级成"正在执行"。已结束的目标上, 待批的变更
32
+ * 已经没有生效对象 → 不许把 `ended` 抬成"需要你决定"。
33
+ */
34
+ import { nextStatusFor, planChangeInjection, userOnlyChangeKind } from '../goal-change.js';
35
+ import { refuse } from './seams.js';
36
+ export const CHANGE_SEAM_ID = 'change';
37
+ /** 只有人能做"撤销/缩范围"这类动作 */
38
+ export const USER_ONLY_CHANGE_KINDS = ['abort', 'scope_reduction'];
39
+ /** `applyChange` 的三个 outcome (宿主返回别的值 = 接线坏了, 抛错而不是当没看见) */
40
+ export const CHANGE_OUTCOMES = ['next_run', 'pending_approval', 'rejected'];
41
+ /** 变更记录状态里"还在等一个人拍板"的那些 → 用户可见态必须说"需要你决定" */
42
+ export const DECISION_PENDING_STATUSES = ['needs_approval', 'triaged'];
43
+ /**
44
+ * 变更驱动的用户可见态覆盖 —— **只升不降**。
45
+ *
46
+ * 为什么必须有这一条: 目标在跑 (`executing`) 时用户提了一条"要加权限"的要求, 它被正确判成
47
+ * `needs_approval` 等待人批准 —— 但界面上仍然是"正在执行", 于是没有人知道这里有东西卡在
48
+ * 等人。设计稿 §8 的第五类状态 (`needs_your_decision`) 正是为这一刻存在的。
49
+ *
50
+ * 不覆盖的情形也有理由: `ended` (目标已结束, 待批的变更已经没有生效对象)。
51
+ * `base === null` = 基础态**读不到**: 有待拍板的变更时仍抬成"需要你决定" (有人必须看一眼这件事
52
+ * 与基础态无关); 没有待拍板的变更时返回 `null` —— 不编一个态出来。
53
+ */
54
+ export function changeVisibleState(input) {
55
+ const waiting = input.changes.filter((c) => DECISION_PENDING_STATUSES.includes(String(c.status)));
56
+ if (waiting.length === 0) {
57
+ return {
58
+ state: input.base,
59
+ elevated: false,
60
+ reason: input.base === null
61
+ ? '基础可见态读不到, 且没有等拍板的变更 → 不断言 (不把"读不到"说成"正在执行")'
62
+ : '没有等拍板的变更 → 用户可见态不变',
63
+ };
64
+ }
65
+ const ids = waiting.map((c) => c.changeId).slice(0, 3).join(', ');
66
+ if (input.base === 'ended') {
67
+ return {
68
+ state: 'ended',
69
+ elevated: false,
70
+ reason: `目标已结束 → 不把 ${waiting.length} 条变更 (${ids}) 抬成"需要你决定" (已结束的目标上没有生效对象)`,
71
+ };
72
+ }
73
+ if (input.base === 'needs_your_decision') {
74
+ return {
75
+ state: 'needs_your_decision',
76
+ elevated: false,
77
+ reason: `本来就是"需要你决定", 另等 ${waiting.length} 条变更拍板 (${ids})`,
78
+ };
79
+ }
80
+ return {
81
+ state: 'needs_your_decision',
82
+ elevated: true,
83
+ reason: `${waiting.length} 条变更等你决定 (${ids}) → 覆盖 "${input.base ?? '读不到的基础态'}" `
84
+ + '(否则界面会一直显示"正在执行", 没人知道有东西在等人)',
85
+ };
86
+ }
87
+ // ────────────────────────────────────────────────────────────────────────────
88
+ // 接缝实例
89
+ // ────────────────────────────────────────────────────────────────────────────
90
+ /** 在跑的 Run 的事实: 宿主当场传入 > 依赖注入 > 不知道 (undefined) */
91
+ async function resolveRunningRun(deps, input) {
92
+ if (input.runningRun !== undefined)
93
+ return input.runningRun;
94
+ if (!deps.runningRun)
95
+ return undefined;
96
+ try {
97
+ return await deps.runningRun(input.goalId);
98
+ }
99
+ catch {
100
+ // 探测失败 = 事实缺失 (不是"没有在跑的 Run"): 上层拿到 factsMissing 后不做任何断言
101
+ return undefined;
102
+ }
103
+ }
104
+ /**
105
+ * 在跑的子 Agent 工作的事实: 调用方当场传入 > 依赖注入 > 没给 (`undefined`)。
106
+ *
107
+ * `undefined` 的语义与"宿主没传这份事实"**完全一致** —— 接缝不在这里另立一套
108
+ * "事实缺失"标记 (`ChangeInjectionPlan` 没有这个字段, 那是 M4 纯函数的既定口径);
109
+ * 探针抛错 (读盘失败) 也按"没给"处理并返回 undefined, 绝不编出几个 workId 来。
110
+ */
111
+ async function resolveLiveWorkIds(deps, input) {
112
+ if (input.liveWorkIds !== undefined)
113
+ return input.liveWorkIds;
114
+ if (!deps.liveWorkIds)
115
+ return undefined;
116
+ try {
117
+ const ids = await deps.liveWorkIds(input.goalId);
118
+ return Array.isArray(ids) ? ids : undefined;
119
+ }
120
+ catch {
121
+ return undefined;
122
+ }
123
+ }
124
+ /**
125
+ * 基础用户可见态 (读不到就返回 null + 原因)。
126
+ *
127
+ * 为什么必须容错: 入档本身**已经成功**了 (原话落档 + 判定 + 下一 Run 指令)。这时如果读可见态
128
+ * 抛错, 整个人口会 500 —— 人会以为"没提上去"而重提一遍 (重复变更)。所以读不到就读不到:
129
+ * 如实返回 null, 让视图说"读不到", 而不是把已经生效的变更报成失败。
130
+ */
131
+ async function resolveBaseVisible(deps, input) {
132
+ try {
133
+ return { base: await deps.visibleState(input), reason: null };
134
+ }
135
+ catch (err) {
136
+ return { base: null, reason: `用户可见态读不到 (${String(err?.message || err).slice(0, 120)})` };
137
+ }
138
+ }
139
+ function dedupeChanges(list) {
140
+ const byId = new Map();
141
+ for (const c of list)
142
+ byId.set(String(c.changeId), c);
143
+ return [...byId.values()];
144
+ }
145
+ export function createChangeSeam(deps) {
146
+ async function applyRunBoundary(input) {
147
+ const b = input.plan.runBoundary;
148
+ if (b.action === 'let_finish') {
149
+ return {
150
+ action: 'let_finish',
151
+ stopped: false,
152
+ runId: b.runId,
153
+ runStatus: null,
154
+ reason: `不需要停动作 (在跑的 Run 继续跑完): ${b.reason}`,
155
+ };
156
+ }
157
+ const runStatus = b.runStatus === 'paused' || b.runStatus === 'aborted' ? b.runStatus : 'paused';
158
+ if (!b.runId) {
159
+ return {
160
+ action: 'stop_running_run',
161
+ stopped: false,
162
+ runId: null,
163
+ runStatus,
164
+ reason: '计划要求停 Run, 但没有 runId → 拒绝凭空停一个 Run (拿不到对象就不动手)',
165
+ };
166
+ }
167
+ const stop = input.stop ?? deps.stopRunningRun;
168
+ if (!stop) {
169
+ return {
170
+ action: 'stop_running_run',
171
+ stopped: false,
172
+ runId: b.runId,
173
+ runStatus,
174
+ reason: `计划要求停 Run ${b.runId} (→${runStatus}), 但宿主没有注入停 Run 的执行器 → **没有停**`
175
+ + ' (需要主线在 M0 的 createChangeSeam 注入 stopRunningRun, 或由入口按参数传入; 本接缝不做 I/O, 也不假装停过)',
176
+ };
177
+ }
178
+ const res = await stop({ goalId: input.plan.goalId, runId: b.runId, runStatus, reason: b.reason, now: input.now });
179
+ return {
180
+ action: 'stop_running_run',
181
+ stopped: !!res?.ok,
182
+ runId: b.runId,
183
+ runStatus,
184
+ reason: res?.ok
185
+ ? `已把 Run ${b.runId} 置为 ${runStatus}: 执行器下一轮循环读到后自行停下 (轮内不打断, 已发生的步骤不改写)`
186
+ : `停 Run ${b.runId} 被拒绝: ${res?.reason || '执行器没给原因 (不静默当成功)'}`,
187
+ };
188
+ }
189
+ return {
190
+ id: CHANGE_SEAM_ID,
191
+ stage: 'M4',
192
+ async ingestChange(input) {
193
+ const raw = String(input.instruction ?? '');
194
+ // ① 原话是证据: 没有原文就不许往下走 (一句"理解"不能代替人说过的话)
195
+ if (!raw.trim()) {
196
+ return refuse('child_cannot_mutate_goal', '拒绝: 变更没有原文 —— 不许用一句"理解"代替人说过的话');
197
+ }
198
+ // ② 只有人能撤销 / 缩小意图 (设计稿「意图的落位」纪律 2)。
199
+ // 判据 = 真实分诊 (`userOnlyChangeKind`), 不再只认字面关键词:
200
+ // "别继续做了 / abort this / 只做第一条" 这些说法现在也认得出来。
201
+ const userOnly = userOnlyChangeKind(raw);
202
+ if (userOnly && input.source !== 'user') {
203
+ return refuse('only_goal_reducer_changes_goal_state', `拒绝: ${input.source} 想提「${userOnly}」类变更 —— 只有人能撤销/缩小意图 `
204
+ + '(Agent 不得因为"执行起来方便"就替人改意图)');
205
+ }
206
+ const out = await deps.ingest({ ...input, instruction: raw });
207
+ if (!out?.request || !out?.application) {
208
+ throw new Error('[change 接缝] 宿主没有返回变更记录/生效判定 (接缝不替它编一份)');
209
+ }
210
+ if (!CHANGE_OUTCOMES.includes(String(out.application.outcome))) {
211
+ throw new Error(`[change 接缝] 宿主返回的 outcome 非法: "${String(out.application.outcome)}" (合法: ${CHANGE_OUTCOMES.join('/')})`);
212
+ }
213
+ // ③ 规则 4 是**宿主契约**: 记录必须标着"只影响后续 Run"。违反 = 接线坏了 → 抛错, 不假绿。
214
+ if (out.request.appliesToFutureRunsOnly !== true) {
215
+ throw new Error('[change 接缝] 宿主返回的变更记录没标 appliesToFutureRunsOnly —— 规则 4: 已发生的 Run 历史不可被新要求改写');
216
+ }
217
+ // ④ 原话必须逐字入档: 被改写过的"原话"不是证据
218
+ if (out.request.instruction !== raw) {
219
+ return refuse('child_cannot_mutate_goal', '拒绝: 变更记录里的 instruction 与用户原话不一致 (原话必须逐字入档)');
220
+ }
221
+ // ⑤ 正在运行的 Goal: 拿事实 → 判定 (纯函数) → 按判定执行 (宿主注入的执行器)
222
+ const runningRun = await resolveRunningRun(deps, input);
223
+ const liveWorkIds = await resolveLiveWorkIds(deps, input);
224
+ const plan = planChangeInjection({
225
+ request: out.request,
226
+ application: out.application,
227
+ runningRun,
228
+ liveWorkIds,
229
+ });
230
+ const runBoundary = await applyRunBoundary({ plan, now: input.now });
231
+ // ⑥ 规则 5: 逐 workId 一份下发内容 (计划里已经算好; 这里只补"要不要先补全影响面")
232
+ const childDelivery = {
233
+ directives: plan.childDirectives,
234
+ workIds: plan.liveWorkIds,
235
+ scopeMissing: plan.childScopeMissing,
236
+ blocked: plan.childScopeMissing,
237
+ mustAck: true,
238
+ note: plan.childScopeMissing
239
+ ? `在跑的 ${plan.liveWorkIds.length} 个子 Agent 必须收到变更版本, 但这条变更没点明影响面 → 下发前先用 scopeChangeToWork 补全 (不许静默漏发)`
240
+ : plan.childDirectives.length
241
+ ? `已为 ${plan.childDirectives.length} 个在跑的子 Agent 各生成一份下发内容 (每个都必须先 ack 再继续)`
242
+ : '没有在跑的子 Agent 需要下发 (子 Agent 条款仍在下一 Run 指令里)',
243
+ };
244
+ // ⑦ 用户可见态: 有待拍板的变更 → "需要你决定" (只升不降; 基础态读不到也不编一个态出来)
245
+ const observed = await resolveBaseVisible(deps, { goalId: input.goalId, now: input.now });
246
+ const recorded = await deps.pending(input.goalId).catch(() => []);
247
+ const visible = changeVisibleState({
248
+ base: observed.base,
249
+ // 新入档这条用它自己的真实结论 (宿主回传的 request.status 停在分诊那一刻, 不是终态)
250
+ changes: dedupeChanges([
251
+ ...recorded,
252
+ { changeId: out.request.changeId, kind: out.request.kind, status: nextStatusFor(out.application.outcome) },
253
+ ]),
254
+ });
255
+ return {
256
+ request: out.request,
257
+ classification: out.application,
258
+ application: out.application,
259
+ instructionVerbatim: raw,
260
+ plan,
261
+ runBoundary,
262
+ childDelivery,
263
+ visibleState: visible.state,
264
+ visibleReason: observed.reason ? `${visible.reason}; ${observed.reason}` : visible.reason,
265
+ needsDisambiguation: out.request.status === 'received' || !out.request.interpreted,
266
+ };
267
+ },
268
+ applyRunBoundary,
269
+ nextRunDirective(goalId) {
270
+ return deps.nextRunDirective(goalId);
271
+ },
272
+ async markConsumed(goalId, runIndex, now) {
273
+ if (!Number.isInteger(runIndex) || runIndex < 0) {
274
+ throw new Error(`[change 接缝] markConsumed 的 runIndex 必须是 >=0 的整数 (收到 ${String(runIndex)}) —— 版本下发的记账不许记在一个不存在的 Run 序号上`);
275
+ }
276
+ await deps.markConsumed(goalId, runIndex, now);
277
+ },
278
+ async visibleState(input) {
279
+ const observed = await resolveBaseVisible(deps, { goalId: input.goalId, now: input.now });
280
+ const recorded = await deps.pending(input.goalId).catch(() => []);
281
+ // 基础态读不到且没有待拍板的变更 → null (不编一个态出来)
282
+ return changeVisibleState({ base: observed.base, changes: recorded }).state;
283
+ },
284
+ pendingChanges(goalId) {
285
+ return deps.pending(goalId);
286
+ },
287
+ };
288
+ }