pi-proactive 0.2.0-beta.2 → 0.2.0-beta.3

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 (243) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/README.md +32 -28
  3. package/build/cli.mjs +121 -0
  4. package/build/extensions/proactive.js +47 -6
  5. package/build/src/commands.js +78 -2
  6. package/build/src/config.js +511 -4
  7. package/build/src/daemon-config.js +221 -0
  8. package/build/src/daemon-contact.js +103 -0
  9. package/build/src/daemon-host.js +84 -0
  10. package/build/src/daemon.js +193 -3
  11. package/build/src/health.js +84 -0
  12. package/build/src/lineage.js +41 -2
  13. package/build/src/notify.js +183 -0
  14. package/build/src/progress-eta.js +134 -0
  15. package/build/src/trigger.js +43 -0
  16. package/build/src/trust-loop.js +321 -0
  17. package/build/src/trust-metrics.js +236 -0
  18. package/build/src/trust-notify.js +73 -0
  19. package/build/src/trust-policy.js +58 -0
  20. package/build/src/trust-registry.js +323 -0
  21. package/daemon/launchd.plist +48 -0
  22. package/daemon/pi-proactive-daemon.service +29 -0
  23. package/daemon/run-daemon.sh +84 -0
  24. package/node_modules/typebox/build/compile/code.mjs +2 -1
  25. package/node_modules/typebox/build/compile/validator.mjs +1 -5
  26. package/node_modules/typebox/build/format/date_time.d.mts +1 -2
  27. package/node_modules/typebox/build/format/date_time.mjs +3 -12
  28. package/node_modules/typebox/build/format/duration.d.mts +2 -2
  29. package/node_modules/typebox/build/format/duration.mjs +2 -2
  30. package/node_modules/typebox/build/format/idn_email.d.mts +0 -1
  31. package/node_modules/typebox/build/format/idn_email.mjs +0 -1
  32. package/node_modules/typebox/build/format/idn_hostname.d.mts +1 -1
  33. package/node_modules/typebox/build/format/idn_hostname.mjs +1 -1
  34. package/node_modules/typebox/build/format/ipv4.d.mts +2 -2
  35. package/node_modules/typebox/build/format/ipv4.mjs +2 -2
  36. package/node_modules/typebox/build/format/ipv6.d.mts +1 -1
  37. package/node_modules/typebox/build/format/ipv6.mjs +1 -1
  38. package/node_modules/typebox/build/format/iri.mjs +9 -16
  39. package/node_modules/typebox/build/format/relative_json_pointer.d.mts +2 -2
  40. package/node_modules/typebox/build/format/relative_json_pointer.mjs +2 -2
  41. package/node_modules/typebox/build/guard/emit.mjs +2 -2
  42. package/node_modules/typebox/build/guard/guard.d.mts +5 -7
  43. package/node_modules/typebox/build/guard/guard.mjs +9 -14
  44. package/node_modules/typebox/build/guard/index.mjs +0 -6
  45. package/node_modules/typebox/build/guard/{unicode/unicode_segment.mjs → string.mjs} +19 -16
  46. package/node_modules/typebox/build/schema/build.mjs +5 -5
  47. package/node_modules/typebox/build/schema/check.mjs +1 -1
  48. package/node_modules/typebox/build/schema/engine/_context.d.mts +1 -5
  49. package/node_modules/typebox/build/schema/engine/_context.mjs +3 -16
  50. package/node_modules/typebox/build/schema/engine/_functions.d.mts +2 -2
  51. package/node_modules/typebox/build/schema/engine/_functions.mjs +1 -1
  52. package/node_modules/typebox/build/schema/engine/_reducer.d.mts +3 -3
  53. package/node_modules/typebox/build/schema/engine/_refine.d.mts +5 -5
  54. package/node_modules/typebox/build/schema/engine/_refine.mjs +9 -14
  55. package/node_modules/typebox/build/schema/engine/_stack.d.mts +20 -34
  56. package/node_modules/typebox/build/schema/engine/_stack.mjs +202 -143
  57. package/node_modules/typebox/build/schema/engine/_unique.d.mts +2 -4
  58. package/node_modules/typebox/build/schema/engine/_unique.mjs +3 -7
  59. package/node_modules/typebox/build/schema/engine/additionalItems.d.mts +4 -4
  60. package/node_modules/typebox/build/schema/engine/additionalItems.mjs +3 -3
  61. package/node_modules/typebox/build/schema/engine/additionalProperties.d.mts +8 -5
  62. package/node_modules/typebox/build/schema/engine/additionalProperties.mjs +32 -27
  63. package/node_modules/typebox/build/schema/engine/allOf.d.mts +4 -4
  64. package/node_modules/typebox/build/schema/engine/allOf.mjs +1 -1
  65. package/node_modules/typebox/build/schema/engine/anyOf.d.mts +4 -4
  66. package/node_modules/typebox/build/schema/engine/anyOf.mjs +9 -4
  67. package/node_modules/typebox/build/schema/engine/boolean.d.mts +4 -4
  68. package/node_modules/typebox/build/schema/engine/boolean.mjs +6 -2
  69. package/node_modules/typebox/build/schema/engine/const.d.mts +4 -4
  70. package/node_modules/typebox/build/schema/engine/const.mjs +6 -2
  71. package/node_modules/typebox/build/schema/engine/contains.d.mts +4 -4
  72. package/node_modules/typebox/build/schema/engine/contains.mjs +9 -5
  73. package/node_modules/typebox/build/schema/engine/dependencies.d.mts +4 -4
  74. package/node_modules/typebox/build/schema/engine/dependencies.mjs +7 -5
  75. package/node_modules/typebox/build/schema/engine/dependentRequired.d.mts +4 -4
  76. package/node_modules/typebox/build/schema/engine/dependentRequired.mjs +6 -2
  77. package/node_modules/typebox/build/schema/engine/dependentSchemas.d.mts +4 -4
  78. package/node_modules/typebox/build/schema/engine/dependentSchemas.mjs +1 -2
  79. package/node_modules/typebox/build/schema/engine/dynamicRef.d.mts +4 -4
  80. package/node_modules/typebox/build/schema/engine/dynamicRef.mjs +8 -12
  81. package/node_modules/typebox/build/schema/engine/enum.d.mts +4 -4
  82. package/node_modules/typebox/build/schema/engine/enum.mjs +6 -2
  83. package/node_modules/typebox/build/schema/engine/exclusiveMaximum.d.mts +4 -4
  84. package/node_modules/typebox/build/schema/engine/exclusiveMaximum.mjs +6 -2
  85. package/node_modules/typebox/build/schema/engine/exclusiveMinimum.d.mts +4 -4
  86. package/node_modules/typebox/build/schema/engine/exclusiveMinimum.mjs +6 -2
  87. package/node_modules/typebox/build/schema/engine/format.d.mts +4 -4
  88. package/node_modules/typebox/build/schema/engine/format.mjs +8 -11
  89. package/node_modules/typebox/build/schema/engine/if.d.mts +4 -4
  90. package/node_modules/typebox/build/schema/engine/if.mjs +13 -5
  91. package/node_modules/typebox/build/schema/engine/index.d.mts +0 -1
  92. package/node_modules/typebox/build/schema/engine/index.mjs +0 -1
  93. package/node_modules/typebox/build/schema/engine/items.d.mts +4 -4
  94. package/node_modules/typebox/build/schema/engine/items.mjs +5 -8
  95. package/node_modules/typebox/build/schema/engine/maxContains.d.mts +4 -4
  96. package/node_modules/typebox/build/schema/engine/maxContains.mjs +8 -4
  97. package/node_modules/typebox/build/schema/engine/maxItems.d.mts +4 -4
  98. package/node_modules/typebox/build/schema/engine/maxItems.mjs +6 -2
  99. package/node_modules/typebox/build/schema/engine/maxLength.d.mts +4 -4
  100. package/node_modules/typebox/build/schema/engine/maxLength.mjs +6 -2
  101. package/node_modules/typebox/build/schema/engine/maxProperties.d.mts +4 -4
  102. package/node_modules/typebox/build/schema/engine/maxProperties.mjs +6 -2
  103. package/node_modules/typebox/build/schema/engine/maximum.d.mts +4 -4
  104. package/node_modules/typebox/build/schema/engine/maximum.mjs +6 -2
  105. package/node_modules/typebox/build/schema/engine/minContains.d.mts +4 -4
  106. package/node_modules/typebox/build/schema/engine/minContains.mjs +9 -5
  107. package/node_modules/typebox/build/schema/engine/minItems.d.mts +5 -5
  108. package/node_modules/typebox/build/schema/engine/minItems.mjs +6 -2
  109. package/node_modules/typebox/build/schema/engine/minLength.d.mts +4 -4
  110. package/node_modules/typebox/build/schema/engine/minLength.mjs +6 -2
  111. package/node_modules/typebox/build/schema/engine/minProperties.d.mts +4 -4
  112. package/node_modules/typebox/build/schema/engine/minProperties.mjs +6 -2
  113. package/node_modules/typebox/build/schema/engine/minimum.d.mts +4 -4
  114. package/node_modules/typebox/build/schema/engine/minimum.mjs +6 -2
  115. package/node_modules/typebox/build/schema/engine/multipleOf.d.mts +4 -4
  116. package/node_modules/typebox/build/schema/engine/multipleOf.mjs +6 -2
  117. package/node_modules/typebox/build/schema/engine/not.d.mts +4 -4
  118. package/node_modules/typebox/build/schema/engine/not.mjs +8 -4
  119. package/node_modules/typebox/build/schema/engine/oneOf.d.mts +4 -4
  120. package/node_modules/typebox/build/schema/engine/oneOf.mjs +11 -6
  121. package/node_modules/typebox/build/schema/engine/pattern.d.mts +4 -4
  122. package/node_modules/typebox/build/schema/engine/pattern.mjs +7 -3
  123. package/node_modules/typebox/build/schema/engine/patternProperties.d.mts +4 -4
  124. package/node_modules/typebox/build/schema/engine/patternProperties.mjs +5 -6
  125. package/node_modules/typebox/build/schema/engine/prefixItems.d.mts +4 -4
  126. package/node_modules/typebox/build/schema/engine/prefixItems.mjs +1 -1
  127. package/node_modules/typebox/build/schema/engine/properties.d.mts +4 -4
  128. package/node_modules/typebox/build/schema/engine/properties.mjs +3 -4
  129. package/node_modules/typebox/build/schema/engine/propertyNames.d.mts +4 -4
  130. package/node_modules/typebox/build/schema/engine/propertyNames.mjs +10 -7
  131. package/node_modules/typebox/build/schema/engine/recursiveRef.d.mts +4 -4
  132. package/node_modules/typebox/build/schema/engine/recursiveRef.mjs +8 -12
  133. package/node_modules/typebox/build/schema/engine/ref.d.mts +4 -4
  134. package/node_modules/typebox/build/schema/engine/ref.mjs +14 -18
  135. package/node_modules/typebox/build/schema/engine/required.d.mts +4 -4
  136. package/node_modules/typebox/build/schema/engine/required.mjs +6 -2
  137. package/node_modules/typebox/build/schema/engine/schema.d.mts +7 -7
  138. package/node_modules/typebox/build/schema/engine/schema.mjs +138 -136
  139. package/node_modules/typebox/build/schema/engine/type.d.mts +4 -4
  140. package/node_modules/typebox/build/schema/engine/type.mjs +6 -2
  141. package/node_modules/typebox/build/schema/engine/unevaluatedItems.d.mts +4 -4
  142. package/node_modules/typebox/build/schema/engine/unevaluatedItems.mjs +9 -5
  143. package/node_modules/typebox/build/schema/engine/unevaluatedProperties.d.mts +4 -4
  144. package/node_modules/typebox/build/schema/engine/unevaluatedProperties.mjs +9 -5
  145. package/node_modules/typebox/build/schema/engine/uniqueItems.d.mts +4 -4
  146. package/node_modules/typebox/build/schema/engine/uniqueItems.mjs +6 -2
  147. package/node_modules/typebox/build/schema/errors.mjs +1 -1
  148. package/node_modules/typebox/build/schema/intern/intern.d.mts +9 -6
  149. package/node_modules/typebox/build/schema/intern/intern.mjs +56 -90
  150. package/node_modules/typebox/build/schema/parse.mjs +0 -3
  151. package/node_modules/typebox/build/schema/pointer/pointer.d.mts +6 -1
  152. package/node_modules/typebox/build/schema/pointer/pointer.mjs +64 -47
  153. package/node_modules/typebox/build/schema/resolve/resolve.d.mts +32 -6
  154. package/node_modules/typebox/build/schema/resolve/resolve.mjs +227 -231
  155. package/node_modules/typebox/build/schema/schema.d.mts +0 -1
  156. package/node_modules/typebox/build/schema/schema.mjs +0 -1
  157. package/node_modules/typebox/build/system/hashing/hash.mjs +34 -63
  158. package/node_modules/typebox/build/system/locale/en_US.mjs +1 -2
  159. package/node_modules/typebox/build/system/memory/assign.d.mts +8 -2
  160. package/node_modules/typebox/build/system/memory/assign.mjs +6 -13
  161. package/node_modules/typebox/build/system/memory/clone.d.mts +5 -4
  162. package/node_modules/typebox/build/system/memory/clone.mjs +96 -8
  163. package/node_modules/typebox/build/system/memory/create.d.mts +5 -1
  164. package/node_modules/typebox/build/system/memory/create.mjs +13 -18
  165. package/node_modules/typebox/build/system/memory/discard.d.mts +1 -1
  166. package/node_modules/typebox/build/system/memory/discard.mjs +8 -9
  167. package/node_modules/typebox/build/system/memory/freeze.d.mts +4 -0
  168. package/node_modules/typebox/build/system/memory/freeze.mjs +6 -0
  169. package/node_modules/typebox/build/system/memory/update.d.mts +4 -1
  170. package/node_modules/typebox/build/system/memory/update.mjs +16 -43
  171. package/node_modules/typebox/build/system/settings/settings.d.mts +0 -11
  172. package/node_modules/typebox/build/system/settings/settings.mjs +0 -14
  173. package/node_modules/typebox/build/type/engine/evaluate/composite.d.mts +24 -2
  174. package/node_modules/typebox/build/type/engine/evaluate/composite.mjs +29 -2
  175. package/node_modules/typebox/build/type/engine/evaluate/index.d.mts +0 -1
  176. package/node_modules/typebox/build/type/engine/evaluate/index.mjs +0 -1
  177. package/node_modules/typebox/build/type/engine/evaluate/narrow.d.mts +1 -5
  178. package/node_modules/typebox/build/type/engine/evaluate/narrow.mjs +1 -6
  179. package/node_modules/typebox/build/type/engine/immutable/instantiate_add.d.mts +3 -1
  180. package/node_modules/typebox/build/type/engine/immutable/instantiate_add.mjs +4 -1
  181. package/node_modules/typebox/build/type/engine/object/from_intersect.d.mts +2 -2
  182. package/node_modules/typebox/build/type/engine/object/from_intersect.mjs +2 -2
  183. package/node_modules/typebox/build/type/engine/object/from_union.d.mts +2 -2
  184. package/node_modules/typebox/build/type/engine/object/from_union.mjs +2 -2
  185. package/node_modules/typebox/build/type/engine/optional/instantiate_add.d.mts +3 -1
  186. package/node_modules/typebox/build/type/engine/optional/instantiate_add.mjs +4 -1
  187. package/node_modules/typebox/build/type/engine/readonly/instantiate_add.d.mts +3 -1
  188. package/node_modules/typebox/build/type/engine/readonly/instantiate_add.mjs +4 -1
  189. package/node_modules/typebox/build/type/extends/inference.d.mts +1 -1
  190. package/node_modules/typebox/build/type/extends/inference.mjs +2 -15
  191. package/node_modules/typebox/build/type/extends/tuple.d.mts +1 -2
  192. package/node_modules/typebox/build/type/extends/tuple.mjs +1 -21
  193. package/node_modules/typebox/build/value/clean/from_object.mjs +1 -1
  194. package/node_modules/typebox/build/value/clone/clone.d.mts +3 -4
  195. package/node_modules/typebox/build/value/clone/clone.mjs +7 -5
  196. package/node_modules/typebox/build/value/parse/parse.mjs +4 -10
  197. package/node_modules/typebox/build/value/shared/union_priority_sort.mjs +14 -25
  198. package/node_modules/typebox/package.json +1 -1
  199. package/node_modules/typebox/readme.md +84 -52
  200. package/package.json +11 -3
  201. package/skills/pi-proactive/SKILL.md +27 -24
  202. package/tools/cli.mjs +121 -0
  203. package/tools/daemon-install.sh +397 -0
  204. package/tools/daemon-uninstall.sh +226 -0
  205. package/node_modules/typebox/build/guard/unicode/unicode.d.mts +0 -6
  206. package/node_modules/typebox/build/guard/unicode/unicode.mjs +0 -35
  207. package/node_modules/typebox/build/schema/engine/_pathing.d.mts +0 -1
  208. package/node_modules/typebox/build/schema/engine/_pathing.mjs +0 -11
  209. package/node_modules/typebox/build/schema/meta/draft_2019_09.d.mts +0 -2
  210. package/node_modules/typebox/build/schema/meta/draft_2019_09.mjs +0 -288
  211. package/node_modules/typebox/build/schema/meta/draft_2020_12.d.mts +0 -2
  212. package/node_modules/typebox/build/schema/meta/draft_2020_12.mjs +0 -300
  213. package/node_modules/typebox/build/schema/meta/draft_3.d.mts +0 -2
  214. package/node_modules/typebox/build/schema/meta/draft_3.mjs +0 -143
  215. package/node_modules/typebox/build/schema/meta/draft_4.d.mts +0 -2
  216. package/node_modules/typebox/build/schema/meta/draft_4.mjs +0 -149
  217. package/node_modules/typebox/build/schema/meta/draft_6.d.mts +0 -2
  218. package/node_modules/typebox/build/schema/meta/draft_6.mjs +0 -155
  219. package/node_modules/typebox/build/schema/meta/draft_7.d.mts +0 -2
  220. package/node_modules/typebox/build/schema/meta/draft_7.mjs +0 -172
  221. package/node_modules/typebox/build/schema/meta/index.d.mts +0 -2
  222. package/node_modules/typebox/build/schema/meta/index.mjs +0 -2
  223. package/node_modules/typebox/build/schema/meta/meta.d.mts +0 -9
  224. package/node_modules/typebox/build/schema/meta/meta.mjs +0 -15
  225. package/node_modules/typebox/build/schema/meta/schema.d.mts +0 -6
  226. package/node_modules/typebox/build/schema/meta/schema.mjs +0 -1
  227. package/node_modules/typebox/build/system/memory/deep.d.mts +0 -2
  228. package/node_modules/typebox/build/system/memory/deep.mjs +0 -108
  229. package/node_modules/typebox/build/system/memory/finalize.d.mts +0 -4
  230. package/node_modules/typebox/build/system/memory/finalize.mjs +0 -30
  231. package/node_modules/typebox/build/system/memory/keys.d.mts +0 -2
  232. package/node_modules/typebox/build/system/memory/keys.mjs +0 -8
  233. package/node_modules/typebox/build/system/settings/internal.d.mts +0 -2
  234. package/node_modules/typebox/build/system/settings/internal.mjs +0 -20
  235. package/node_modules/typebox/build/type/engine/call/index.d.mts +0 -1
  236. package/node_modules/typebox/build/type/engine/call/index.mjs +0 -1
  237. package/node_modules/typebox/build/type/engine/evaluate/property.d.mts +0 -30
  238. package/node_modules/typebox/build/type/engine/evaluate/property.mjs +0 -29
  239. package/node_modules/typebox/build/type/engine/this/index.d.mts +0 -1
  240. package/node_modules/typebox/build/type/engine/this/index.mjs +0 -1
  241. package/node_modules/typebox/build/value/repair/from_cyclic.d.mts +0 -2
  242. package/node_modules/typebox/build/value/repair/from_cyclic.mjs +0 -6
  243. /package/node_modules/typebox/build/guard/{unicode/unicode_segment.d.mts → string.d.mts} +0 -0
@@ -0,0 +1,236 @@
1
+ /**
2
+ * 信任层的**指标与守恒等式**(卡 D1 的四段闭环 + 卡 D4 的三项指标)。
3
+ *
4
+ * 口径唯一源:**只读事件流**(`src/lineage.ts` 的 JSONL)——
5
+ * 不读登记册、不读内存状态 → 指标可由第三方**独立复算**(同一份账本 → 同一组数)。
6
+ *
7
+ * 三项指标(承 `docs/product/assistant-trust-model.md` §4 的口径):
8
+ * ```
9
+ * 回应率 = answered / delivered (它主动的 → 我回应的)
10
+ * 托付闭环率 = settled / registered (托付 → 到达 → 知会 → 结账)
11
+ * 守候时长 = 平均(知会时刻 − 登记时刻) (我不用守着的时间)
12
+ * ```
13
+ *
14
+ * 守恒等式(每条托付一条链,**不得跳段、不得重复**):
15
+ * ```
16
+ * registered → arrived → notified → settled
17
+ * 每条链缺一段 → 记一条违规(少一段 = 红)
18
+ * 聚合等式:登记数 = 结账数 + 撤销数 + 在途数(重复登记 / 悬空都会破坏它)
19
+ * ```
20
+ *
21
+ * @capability: trust-metrics
22
+ * @observable: pi-proactive/src/trust-metrics.ts:computeTrustMetrics
23
+ * @attribution: pi-proactive/src/trust-metrics.ts:computeTrustMetrics
24
+ * @outcome: pi-proactive/src/trust-metrics.ts:ConservationReport
25
+ * @checked: test:pi-proactive/tests/trust-metrics.test.ts#D4-1 三项指标可从事件流算出
26
+ */
27
+ import fs from "node:fs";
28
+ import path from "node:path";
29
+ /** 托付链的阶段顺序(**不得跳段**)。 */
30
+ const CHAIN = ["registered", "arrived", "notified", "settled"];
31
+ /** 只取信任层事件(`type === "trust"`)。 */
32
+ function trustEvents(events) {
33
+ const out = [];
34
+ for (const e of events) {
35
+ if (e.type !== "trust" || e.trust === undefined)
36
+ continue;
37
+ const id = e.trust.entrustmentId;
38
+ if (id === undefined || id === "")
39
+ continue;
40
+ out.push({ id, stage: e.trust.stage, ts: e.ts, event: e });
41
+ }
42
+ return out;
43
+ }
44
+ /**
45
+ * 托付四段的守恒等式。
46
+ *
47
+ * 判据(可机检、能红):
48
+ * - 每条托付的阶段链必须是 `registered → arrived → notified → settled` 的**子序列**;
49
+ * - **跳段即违规**(例如有 `settled` 却没有 `notified` → 「少一段」→ 红);
50
+ * - **重复即违规**(同一阶段两条 → 记一条;聚合等式也会因此失衡);
51
+ * - 聚合等式:`登记数 == 结账数 + 撤销数 + 在途数`。
52
+ */
53
+ export function evaluateEntrustmentConservation(events) {
54
+ const rows = trustEvents(events);
55
+ const byEntrustment = new Map();
56
+ for (const r of rows) {
57
+ const list = byEntrustment.get(r.id) ?? [];
58
+ list.push({ stage: r.stage, ts: r.ts });
59
+ byEntrustment.set(r.id, list);
60
+ }
61
+ const violations = [];
62
+ let registered = 0;
63
+ let arrived = 0;
64
+ let notified = 0;
65
+ let settled = 0;
66
+ let revoked = 0;
67
+ let humanGated = 0;
68
+ let inFlight = 0;
69
+ for (const [id, list] of byEntrustment) {
70
+ list.sort((a, b) => a.ts - b.ts);
71
+ const stages = list.map((l) => l.stage);
72
+ const countOf = (s) => stages.filter((x) => x === s).length;
73
+ registered += countOf("registered");
74
+ arrived += countOf("arrived");
75
+ notified += countOf("notified");
76
+ settled += countOf("settled");
77
+ revoked += countOf("revoked");
78
+ humanGated += countOf("human-gated");
79
+ // 重复阶段(去重失效 → 清单里同一件事出现两次)。
80
+ for (const s of CHAIN) {
81
+ if (countOf(s) > 1)
82
+ violations.push(`${id}: 重复阶段 ${s} ×${countOf(s)}`);
83
+ }
84
+ // 起始阶段必须是登记。
85
+ if (countOf("registered") === 0)
86
+ violations.push(`${id}: 缺阶段 registered`);
87
+ // 跳段检查:出现了靠后的阶段,就必须有它前面的阶段。
88
+ for (let i = 1; i < CHAIN.length; i += 1) {
89
+ const s = CHAIN[i];
90
+ if (countOf(s) === 0)
91
+ continue;
92
+ for (let j = 0; j < i; j += 1) {
93
+ const prev = CHAIN[j];
94
+ if (countOf(prev) === 0)
95
+ violations.push(`${id}: 缺阶段 ${prev}(但已有 ${s})`);
96
+ }
97
+ }
98
+ // 顺序检查:阶段必须按链的顺序出现(时间序)。
99
+ const orderIdx = new Map(CHAIN.map((s, i) => [s, i]));
100
+ let last = -1;
101
+ for (const s of stages) {
102
+ const idx = orderIdx.get(s);
103
+ if (idx === undefined)
104
+ continue; // revoked / human-gated 不在主链上
105
+ if (idx < last)
106
+ violations.push(`${id}: 阶段乱序 ${s}`);
107
+ last = Math.max(last, idx);
108
+ }
109
+ // 撤销与结账互斥。
110
+ if (countOf("settled") > 0 && countOf("revoked") > 0)
111
+ violations.push(`${id}: 同时结账与撤销`);
112
+ // 终态判定:在途 = 既没结账也没撤销。
113
+ if (countOf("settled") === 0 && countOf("revoked") === 0)
114
+ inFlight += 1;
115
+ }
116
+ const equationHolds = registered === settled + revoked + inFlight;
117
+ if (!equationHolds) {
118
+ violations.push(`聚合等式不成立:登记 ${registered} ≠ 结账 ${settled} + 撤销 ${revoked} + 在途 ${inFlight}`);
119
+ }
120
+ return {
121
+ registered,
122
+ arrived,
123
+ notified,
124
+ settled,
125
+ revoked,
126
+ humanGated,
127
+ inFlight,
128
+ equationHolds,
129
+ violations,
130
+ balanced: violations.length === 0 && equationHolds,
131
+ };
132
+ }
133
+ /**
134
+ * 三项指标(回应率 / 托付闭环率 / 守候时长)+ 守恒等式。
135
+ *
136
+ * 全部从**事件流**算 → 可由第三方用同一份账本复算。
137
+ */
138
+ export function computeTrustMetrics(events) {
139
+ let delivered = 0;
140
+ let answered = 0;
141
+ for (const e of events) {
142
+ if (e.type === "delivered" && e.result === "delivered")
143
+ delivered += 1;
144
+ if (e.type === "answered")
145
+ answered += 1;
146
+ }
147
+ const conservation = evaluateEntrustmentConservation(events);
148
+ // 守候时长:登记 → 知会(同一托付的第一条 registered 与 notified)。
149
+ const firstRegistered = new Map();
150
+ const firstNotified = new Map();
151
+ for (const e of events) {
152
+ if (e.type !== "trust" || e.trust === undefined)
153
+ continue;
154
+ const id = e.trust.entrustmentId;
155
+ if (id === undefined)
156
+ continue;
157
+ if (e.trust.stage === "registered" && !firstRegistered.has(id))
158
+ firstRegistered.set(id, e.ts);
159
+ if (e.trust.stage === "notified" && !firstNotified.has(id))
160
+ firstNotified.set(id, e.ts);
161
+ }
162
+ let totalMs = 0;
163
+ let count = 0;
164
+ for (const [id, at] of firstNotified) {
165
+ const from = firstRegistered.get(id);
166
+ if (from === undefined)
167
+ continue;
168
+ totalMs += Math.max(0, at - from);
169
+ count += 1;
170
+ }
171
+ const rate = (n, d) => (d === 0 ? 0 : n / d);
172
+ return {
173
+ responseRate: { numerator: answered, denominator: delivered, value: rate(answered, delivered) },
174
+ entrustmentClosureRate: { numerator: conservation.settled, denominator: conservation.registered, value: rate(conservation.settled, conservation.registered) },
175
+ watchDuration: { averageMs: count === 0 ? 0 : totalMs / count, totalMs, count },
176
+ conservation,
177
+ };
178
+ }
179
+ /**
180
+ * 读一个目录下的全部事件流文件(`*.jsonl`)→ 事件列表。
181
+ *
182
+ * 坏行跳过(不抛错)——与 `src/lineage.ts` 的读方口径一致。
183
+ */
184
+ export function readLineageEvents(dir) {
185
+ let files;
186
+ try {
187
+ files = fs.readdirSync(dir).filter((f) => f.endsWith(".jsonl"));
188
+ }
189
+ catch {
190
+ return [];
191
+ }
192
+ const events = [];
193
+ for (const f of files) {
194
+ let raw;
195
+ try {
196
+ raw = fs.readFileSync(path.join(dir, f), "utf8");
197
+ }
198
+ catch {
199
+ continue;
200
+ }
201
+ for (const line of raw.split("\n")) {
202
+ if (line.trim().length === 0)
203
+ continue;
204
+ try {
205
+ const parsed = JSON.parse(line);
206
+ if (typeof parsed?.ts === "number" && typeof parsed?.type === "string")
207
+ events.push(parsed);
208
+ }
209
+ catch {
210
+ // 坏行跳过
211
+ }
212
+ }
213
+ }
214
+ return events;
215
+ }
216
+ /**
217
+ * CLI:打印指标 JSON(供 `scripts/team/progress-snapshot.sh` 调用 → **同一份实现算指标**,
218
+ * 避免「脚本里另写一套口径」导致漂移)。
219
+ *
220
+ * 用法:`node --experimental-strip-types pi-proactive/src/trust-metrics.ts --dir <lineageDir>`
221
+ */
222
+ export function main(argv) {
223
+ const idx = argv.indexOf("--dir");
224
+ const dir = idx >= 0 ? argv[idx + 1] : undefined;
225
+ if (dir === undefined) {
226
+ console.error("用法:trust-metrics.ts --dir <lineageDir>");
227
+ return 2;
228
+ }
229
+ const metrics = computeTrustMetrics(readLineageEvents(dir));
230
+ console.log(JSON.stringify(metrics));
231
+ return 0;
232
+ }
233
+ const invokedDirectly = process.argv[1] !== undefined && process.argv[1].endsWith("trust-metrics.ts");
234
+ if (invokedDirectly) {
235
+ process.exitCode = main(process.argv.slice(2));
236
+ }
@@ -0,0 +1,73 @@
1
+ /**
2
+ * 通道判定(纯函数)。
3
+ *
4
+ * - 空闲 → `wake`(用户不在 → 直接起一轮);
5
+ * - 忙 → `queue`(排队,不打断正在做的事);
6
+ * - 需人立刻决定(urgent)→ `interrupt`(担责的事不能等)。
7
+ */
8
+ export function resolveTrustChannels(input) {
9
+ const session = input.urgent ? "interrupt" : input.idle ? "wake" : "queue";
10
+ // 设备级通知**一律发**(托付达成 = 用户明确交代过的事 → 值得一条设备通知;
11
+ // 会话通道决定「什么时候起一轮」,设备通道决定「用户不在电脑前也能收到」)。
12
+ const channels = [`session-${session}`, "device"];
13
+ return { session, device: true, channels };
14
+ }
15
+ /**
16
+ * 托付知会器。
17
+ *
18
+ * 幂等:同一条托付**只**知会一次(第二次 → `duplicate`,不再投递、不再落事件)。
19
+ */
20
+ export class TrustNotifier {
21
+ deps;
22
+ constructor(deps) {
23
+ this.deps = deps;
24
+ }
25
+ /**
26
+ * 知会一条**已到达**的托付。
27
+ *
28
+ * 顺序:查状态(已知会 → 重复,返回)→ 投递(会话 + 设备)→ 标记知会 → 落 `notified` 事件。
29
+ * 需要人决定的托付 → **不投递**(只产出 human-gated 记录):担责的事不代劳。
30
+ */
31
+ async notifyEntrustment(entrustmentId, opts = {}) {
32
+ const e = this.deps.registry.getEntrustment(entrustmentId);
33
+ if (e === undefined)
34
+ return { entrustmentId, delivered: false, reason: "not-found", channels: [] };
35
+ if (e.status === "notified" || e.status === "settled") {
36
+ return { entrustmentId, delivered: false, reason: "duplicate", channels: [] };
37
+ }
38
+ if (e.status !== "arrived") {
39
+ return { entrustmentId, delivered: false, reason: "not-arrived", channels: [] };
40
+ }
41
+ if (e.needsHuman === true) {
42
+ const reason = e.humanReason ?? "需要人决定";
43
+ this.deps.registry.markNeedsHuman(entrustmentId, reason);
44
+ this.deps.lineage?.noteTrust({ stage: "human-gated", entrustmentId, goalId: e.goalId, reason });
45
+ return { entrustmentId, delivered: false, reason: "needs-human", channels: [] };
46
+ }
47
+ const plan = resolveTrustChannels({ idle: opts.idle ?? true, urgent: opts.urgent ?? false });
48
+ const title = "托付达成";
49
+ const body = `${e.target} —— 条件已满足。`;
50
+ const channels = [];
51
+ if (this.deps.deliverSession !== undefined) {
52
+ await this.deps.deliverSession({ title, body, intent: plan.session });
53
+ channels.push(`session-${plan.session}`);
54
+ }
55
+ if (plan.device && this.deps.notifyDevice !== undefined) {
56
+ this.deps.notifyDevice(title, body);
57
+ channels.push("device");
58
+ }
59
+ const marked = this.deps.registry.markNotified(entrustmentId);
60
+ if (marked === undefined) {
61
+ return { entrustmentId, delivered: false, reason: "duplicate", channels };
62
+ }
63
+ const waitedMs = marked.notifiedAt !== undefined && marked.createdAt !== undefined ? marked.notifiedAt - marked.createdAt : undefined;
64
+ this.deps.lineage?.noteTrust({
65
+ stage: "notified",
66
+ entrustmentId,
67
+ goalId: e.goalId,
68
+ channel: channels.join("+"),
69
+ ...(waitedMs !== undefined ? { waitedMs } : {}),
70
+ });
71
+ return { entrustmentId, delivered: true, channels };
72
+ }
73
+ }
@@ -0,0 +1,58 @@
1
+ /**
2
+ * 主动性策略:**空闲触发降级为默认浅层**(卡 D2)。
3
+ *
4
+ * 依据 `docs/product/assistant-trust-model.md` §3:
5
+ * > 空闲触发只是**默认浅层用法**(应默认静默、能等就等);主动性的真正来源是**用户的托付**。
6
+ *
7
+ * 判据(两条闸门,缺一即不开口):
8
+ * 1. **有用户交代**:没有任何托付 / 未竟目标支撑 → 不开口(默认静默);
9
+ * 2. **确有价值**:DECIDE1 分数低于阈值 → 不开口(不产生无价值打扰)。
10
+ *
11
+ * 分数**复用 DECIDE1**(`src/idle.ts:scoreDecision` —— 同一把尺子,不另立公式),
12
+ * 输入的三个分量各自带**来源**(可复核「这个数从哪来」)。
13
+ *
14
+ * @capability: trust-shallow-idle
15
+ * @observable: pi-proactive/src/trust-policy.ts:evaluateShallowIdle
16
+ * @attribution: pi-proactive/src/trust-policy.ts:evaluateShallowIdle
17
+ * @outcome: pi-proactive/src/trigger.ts:Trigger
18
+ * @checked: test:pi-proactive/tests/trust-policy.test.ts#D2-4 接线实证
19
+ */
20
+ import { scoreDecision } from "./idle.js";
21
+ /** 浅层策略的缺省值(**默认静默**)。 */
22
+ export const DEFAULT_SHALLOW_IDLE_POLICY = {
23
+ enabled: true,
24
+ requireUserBacking: true,
25
+ threshold: 1,
26
+ observeRatio: 0.5,
27
+ };
28
+ /**
29
+ * 浅层空闲判定(纯函数)。
30
+ *
31
+ * - **默认静默**:没有用户交代 → `speak=false`(reason = `no-backing`);
32
+ * - **不产生无价值打扰**:分数 < 阈值 → `speak=false`(reason = `below-threshold`);
33
+ * - 两条都过 → `speak=true`(reason = `speak`)。
34
+ */
35
+ export function evaluateShallowIdle(input) {
36
+ const scoring = scoreDecision({
37
+ value: { score: input.value, source: input.valueSource },
38
+ urgency: { score: input.urgency, source: input.urgencySource },
39
+ interruptionCost: { score: input.interruptionCost, source: input.interruptionCostSource },
40
+ }, input.policy.threshold, input.policy.observeRatio);
41
+ if (input.policy.requireUserBacking && !input.backedByUserRequest) {
42
+ return { speak: false, reason: "no-backing", score: scoring.score };
43
+ }
44
+ if (scoring.band !== "trigger") {
45
+ return { speak: false, reason: "below-threshold", score: scoring.score };
46
+ }
47
+ return { speak: true, reason: "speak", score: scoring.score };
48
+ }
49
+ /**
50
+ * 这条信封是不是**空闲触发**(`src/idle.ts:buildIdleEnvelope` 造出来的)。
51
+ *
52
+ * 为什么用信封标记而不是 `cause`:既有空闲路径**没有**把 `cause:"idle"` 传给投递层
53
+ * (`src/idle.ts` 的 `fireIdle` → `deliverToModel` 不带 cause)→ 投递层只能靠信封自身
54
+ * 的确定性标记识别(`id` 前缀 + `subject`),两者都由 `buildIdleEnvelope` 固定生成。
55
+ */
56
+ export function isIdleTriggerEnvelope(msg) {
57
+ return msg.id.startsWith("idle-") || msg.subject === "空闲触发";
58
+ }
@@ -0,0 +1,323 @@
1
+ /**
2
+ * 托付与目标的**登记册**(信任层的事实源 · 卡 D1/D6)。
3
+ *
4
+ * 为什么要有登记册:托付("盯着 X")与目标("把发布流程跑通")若只存在于对话里,
5
+ * 就**无法查、无法撤、无法结账** → 主动性的价值无从证明。登记册把二者变成
6
+ * **有状态、可查、可撤销**的记录,每次状态变化都落成**只追加**的事件(`src/lineage.ts`)。
7
+ *
8
+ * 设计要点(对齐 `docs/product/agentic-loop.md` 与 `docs/product/assistant-trust-model.md`):
9
+ * - **托付 = 目标 + 条件**(L-B 是 L-C 的一个实例):托付带一个**可判定**的到达条件;
10
+ * - **目标必须可判定完成**:`doneWhen` 缺失或不可判定 → 目标**不得**进入自主推进(护栏①);
11
+ * - **状态机是显式的**:托付 `active → arrived → notified → settled`(或 `revoked`);
12
+ * 目标 `open → achieved | abandoned | needs-human` —— 「需人决策」是**一等状态**,
13
+ * 不允许被静默折叠成「完成」;
14
+ * - **落盘是原子的**:临时文件 + rename(避免半截 JSON 读坏整个登记册);
15
+ * - **不越界**:目标带 `scope`(范围标签)—— 自主推进只能改变范围内的状态。
16
+ *
17
+ * 落盘位置:`<agentDir>/proactive/trust/registry.json`(与邮箱 / 线程 / 事件流同族目录)。
18
+ *
19
+ * @capability: trust-entrust
20
+ * @observable: pi-proactive/src/trust-registry.ts:TrustRegistry
21
+ * @attribution: pi-proactive/src/trust-registry.ts:TrustRegistry.registerEntrustment
22
+ * @outcome: pi-proactive/src/trust-metrics.ts:ConservationReport
23
+ * @checked: test:pi-proactive/tests/trust-registry.test.ts#D1-2 四段闭环成立
24
+ */
25
+ import fs from "node:fs";
26
+ import path from "node:path";
27
+ import { randomUUID } from "node:crypto";
28
+ import { ensureDir, writeAtomicFile } from "./config.js";
29
+ /** 登记册文件格式版本。 */
30
+ export const REGISTRY_SCHEMA_VERSION = 1;
31
+ /** 目标是否**可判定完成**(护栏①的判据)。 */
32
+ export function isDecidable(goal) {
33
+ const d = goal.doneWhen;
34
+ if (d === undefined)
35
+ return false;
36
+ if (d.type === "checklist")
37
+ return d.items.length > 0;
38
+ return Number.isFinite(d.target);
39
+ }
40
+ /** 目标是否**已达成**(按完成定义判定,缺省定义 → 未达成)。 */
41
+ export function goalSatisfied(goal) {
42
+ const d = goal.doneWhen;
43
+ if (d === undefined)
44
+ return false;
45
+ if (d.type === "checklist")
46
+ return d.items.length > 0 && d.items.every((i) => i.done);
47
+ if (d.op === ">=")
48
+ return d.current >= d.target;
49
+ if (d.op === "<=")
50
+ return d.current <= d.target;
51
+ return d.current === d.target;
52
+ }
53
+ /** 目标差距(还差多少 / 哪些没做完)—— 候选行动的来源。 */
54
+ export function goalGap(goal) {
55
+ const d = goal.doneWhen;
56
+ if (d === undefined)
57
+ return { remaining: 1, total: 1, ratio: 1, items: [] };
58
+ if (d.type === "checklist") {
59
+ const undone = d.items.filter((i) => !i.done);
60
+ return {
61
+ remaining: undone.length,
62
+ total: d.items.length,
63
+ ratio: d.items.length === 0 ? 1 : undone.length / d.items.length,
64
+ items: undone.map((i) => i.text),
65
+ };
66
+ }
67
+ const span = Math.abs(d.target - d.current);
68
+ const step = span === 0 ? 1 : Math.max(1, Math.abs(d.target));
69
+ return { remaining: span, total: step, ratio: Math.min(1, span / step), items: [`${d.name} → ${d.op} ${d.target}`] };
70
+ }
71
+ /**
72
+ * 托付与目标的登记册(单一事实源)。
73
+ *
74
+ * 状态迁移全部走**显式方法**(`markArrived` / `markNotified` / `settle` / `revoke`),
75
+ * 不允许调用方直接改状态 → 状态机只有一处实现,事件流与登记册不会漂移。
76
+ */
77
+ export class TrustRegistry {
78
+ agentDir;
79
+ ownerAgentId;
80
+ now;
81
+ goals = [];
82
+ entrustments = [];
83
+ /** 载入时的坏数据计数(读不动的记录被跳过,不抛错)。 */
84
+ stale = 0;
85
+ constructor(opts) {
86
+ this.agentDir = opts.agentDir;
87
+ this.ownerAgentId = opts.agentId;
88
+ this.now = opts.now ?? Date.now;
89
+ this.load();
90
+ }
91
+ /** 登记册文件绝对路径。 */
92
+ path() {
93
+ return path.join(this.agentDir, "proactive", "trust", "registry.json");
94
+ }
95
+ /** 载入登记册(文件缺失 → 空册;坏记录 → 跳过并计数,绝不抛错)。 */
96
+ load() {
97
+ let raw;
98
+ try {
99
+ raw = fs.readFileSync(this.path(), "utf8");
100
+ }
101
+ catch {
102
+ return;
103
+ }
104
+ let parsed;
105
+ try {
106
+ parsed = JSON.parse(raw);
107
+ }
108
+ catch {
109
+ this.stale += 1;
110
+ return;
111
+ }
112
+ const file = parsed;
113
+ this.goals = Array.isArray(file.goals) ? file.goals.filter((g) => g && typeof g.id === "string") : [];
114
+ this.entrustments = Array.isArray(file.entrustments)
115
+ ? file.entrustments.filter((e) => e && typeof e.id === "string")
116
+ : [];
117
+ }
118
+ /** 落盘(临时文件 + rename → 原子;失败不抛错,只记日志)。 */
119
+ save() {
120
+ const file = this.path();
121
+ const payload = { schemaVersion: REGISTRY_SCHEMA_VERSION, goals: this.goals, entrustments: this.entrustments };
122
+ try {
123
+ ensureDir(path.dirname(file));
124
+ writeAtomicFile(file, `${JSON.stringify(payload, null, 2)}\n`);
125
+ }
126
+ catch (err) {
127
+ console.error("[proactive] 托付登记册落盘失败", err);
128
+ }
129
+ }
130
+ /** 载入时跳过的坏记录数(审计用)。 */
131
+ staleRecords() {
132
+ return this.stale;
133
+ }
134
+ // ---- 目标 ----
135
+ /** 登记一个目标(**完成定义缺省 = 不可判定** → 由护栏拦住自主推进)。 */
136
+ registerGoal(input) {
137
+ const at = this.now();
138
+ const goal = {
139
+ id: input.id ?? randomUUID(),
140
+ title: input.title,
141
+ ...(input.doneWhen !== undefined ? { doneWhen: input.doneWhen } : {}),
142
+ status: "open",
143
+ scope: input.scope ?? [],
144
+ budget: { maxActions: input.maxActions ?? 8, usedActions: 0 },
145
+ permission: input.permission ?? "auto",
146
+ progressSteps: 0,
147
+ createdAt: at,
148
+ updatedAt: at,
149
+ };
150
+ this.goals = [...this.goals.filter((g) => g.id !== goal.id), goal];
151
+ this.save();
152
+ return goal;
153
+ }
154
+ /** 取目标(不存在 → undefined)。 */
155
+ getGoal(id) {
156
+ return this.goals.find((g) => g.id === id);
157
+ }
158
+ /** 全部目标(按登记顺序)。 */
159
+ listGoals() {
160
+ return [...this.goals];
161
+ }
162
+ /** 未竟目标(`open`;自主推进的发现来源之一)。 */
163
+ listOpenGoals() {
164
+ return this.goals.filter((g) => g.status === "open");
165
+ }
166
+ /** 勾选清单项 / 更新指标当前值(完成定义的推进)。 */
167
+ updateDoneWhen(goalId, update) {
168
+ const goal = this.getGoal(goalId);
169
+ if (goal === undefined || goal.doneWhen === undefined)
170
+ return undefined;
171
+ if (goal.doneWhen.type === "checklist" && update.itemId !== undefined) {
172
+ goal.doneWhen.items = goal.doneWhen.items.map((i) => i.id === update.itemId ? { ...i, done: update.itemDone === true } : i);
173
+ }
174
+ if (goal.doneWhen.type === "metric" && update.metricCurrent !== undefined) {
175
+ goal.doneWhen.current = update.metricCurrent;
176
+ }
177
+ goal.updatedAt = this.now();
178
+ this.save();
179
+ return goal;
180
+ }
181
+ /** 归因推进:这次行动让目标前进了 `delta` 步(可追溯⑤的载体)。 */
182
+ recordProgress(goalId, delta) {
183
+ const goal = this.getGoal(goalId);
184
+ if (goal === undefined)
185
+ return undefined;
186
+ goal.progressSteps += delta;
187
+ goal.updatedAt = this.now();
188
+ this.save();
189
+ return goal;
190
+ }
191
+ /** 消耗预算(护栏②):超出上限 → 返回 false(调用方必须停)。 */
192
+ consumeBudget(goalId, amount = 1) {
193
+ const goal = this.getGoal(goalId);
194
+ if (goal === undefined)
195
+ return false;
196
+ if (goal.budget.usedActions + amount > goal.budget.maxActions)
197
+ return false;
198
+ goal.budget.usedActions += amount;
199
+ goal.updatedAt = this.now();
200
+ this.save();
201
+ return true;
202
+ }
203
+ /**
204
+ * 设置目标状态。
205
+ *
206
+ * `needs-human` **必须**带原因(否则就是「静默结账」的变体 → 违反红线)。
207
+ */
208
+ setGoalStatus(goalId, status, humanReason) {
209
+ const goal = this.getGoal(goalId);
210
+ if (goal === undefined)
211
+ return undefined;
212
+ if (status === "needs-human" && (humanReason === undefined || humanReason.trim() === "")) {
213
+ return undefined;
214
+ }
215
+ goal.status = status;
216
+ if (status === "needs-human")
217
+ goal.humanReason = humanReason;
218
+ goal.updatedAt = this.now();
219
+ this.save();
220
+ return goal;
221
+ }
222
+ // ---- 托付 ----
223
+ /** 登记一条托付("盯着 X")。 */
224
+ registerEntrustment(input) {
225
+ const at = this.now();
226
+ const e = {
227
+ id: input.id ?? randomUUID(),
228
+ target: input.target,
229
+ condition: input.condition,
230
+ status: "active",
231
+ ...(input.goalId !== undefined ? { goalId: input.goalId } : {}),
232
+ ...(input.needsHuman !== undefined ? { needsHuman: input.needsHuman } : {}),
233
+ ...(input.humanReason !== undefined ? { humanReason: input.humanReason } : {}),
234
+ createdAt: at,
235
+ };
236
+ this.entrustments = [...this.entrustments.filter((x) => x.id !== e.id), e];
237
+ this.save();
238
+ return e;
239
+ }
240
+ /** 取托付(不存在 → undefined)。 */
241
+ getEntrustment(id) {
242
+ return this.entrustments.find((e) => e.id === id);
243
+ }
244
+ /** 全部托付。 */
245
+ listEntrustments() {
246
+ return [...this.entrustments];
247
+ }
248
+ /** 未闭合托付(`active` / `arrived` / `notified` —— 自主推进的发现来源之二)。 */
249
+ listUnclosedEntrustments() {
250
+ return this.entrustments.filter((e) => e.status === "active" || e.status === "arrived" || e.status === "notified");
251
+ }
252
+ /** 到达(第二段):条件已满足。 */
253
+ markArrived(id) {
254
+ const e = this.getEntrustment(id);
255
+ if (e === undefined || e.status !== "active")
256
+ return undefined;
257
+ e.status = "arrived";
258
+ e.arrivedAt = this.now();
259
+ this.save();
260
+ return e;
261
+ }
262
+ /**
263
+ * 知会(第三段)。
264
+ *
265
+ * **幂等**:已 `notified` 的托付再调用 → 返回 undefined(不重复知会、不重复落事件)。
266
+ * (双通道去重的账本侧:通知器与登记册任一方被重复调用都不会产生第二次知会。)
267
+ */
268
+ markNotified(id) {
269
+ const e = this.getEntrustment(id);
270
+ if (e === undefined || e.status !== "arrived")
271
+ return undefined;
272
+ e.status = "notified";
273
+ e.notifiedAt = this.now();
274
+ this.save();
275
+ return e;
276
+ }
277
+ /**
278
+ * 结账(第四段)。
279
+ *
280
+ * **需要人决定的托付不得自动结账**(D3)→ `needsHuman` 的托付调用本方法 → 返回 undefined。
281
+ */
282
+ settle(id) {
283
+ const e = this.getEntrustment(id);
284
+ if (e === undefined)
285
+ return undefined;
286
+ if (e.needsHuman === true)
287
+ return undefined;
288
+ if (e.status !== "notified" && e.status !== "arrived")
289
+ return undefined;
290
+ e.status = "settled";
291
+ e.settledAt = this.now();
292
+ this.save();
293
+ return e;
294
+ }
295
+ /** 撤销(任何时候可撤 —— 护栏④)。 */
296
+ revoke(id) {
297
+ const e = this.getEntrustment(id);
298
+ if (e === undefined || e.status === "settled" || e.status === "revoked")
299
+ return undefined;
300
+ e.status = "revoked";
301
+ e.revokedAt = this.now();
302
+ this.save();
303
+ return e;
304
+ }
305
+ /**
306
+ * 标记「需人决策」(human-gated)。
307
+ *
308
+ * 记录**原因**;托付**不**被结账(保持 `arrived`/`notified`)→ 可查、可继续。
309
+ */
310
+ markNeedsHuman(id, reason) {
311
+ const e = this.getEntrustment(id);
312
+ if (e === undefined)
313
+ return undefined;
314
+ e.needsHuman = true;
315
+ e.humanReason = reason;
316
+ this.save();
317
+ return e;
318
+ }
319
+ /** 本 agent id(事件流用)。 */
320
+ get agentId() {
321
+ return this.ownerAgentId;
322
+ }
323
+ }