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,134 @@
1
+ /**
2
+ * 进度与 ETA 的**唯一接线点**(任务卡 ETA · E1)。
3
+ *
4
+ * 口径来源:`scripts/progress-clock.py`(**项目进度时钟**)——本模块**不另写一套 ETA 算法**,
5
+ * 只做三件事:
6
+ * ① 调用时钟的只读 ETA 模式(`--eta --json-only`:不写历史账本、不跑门禁/测试);
7
+ * ② 解析时钟输出的 ETA 契约(区间 + 置信度 + 归因 + 阻塞项);
8
+ * ③ 把**同一份正文**交给人侧命令与 AI 侧工具 → 两侧逐字一致(A3)。
9
+ *
10
+ * 为什么有 30s 缓存:时钟读数带时间戳;人侧命令与 AI 工具若各跑一次时钟,两侧文字会差一个
11
+ * 时间戳 → 「同源同口径」被时间戳破坏。缓存让两侧拿到**同一份快照**(30s 内的读数本身
12
+ * 也不会变)。缓存**不写**任何状态(只读)。
13
+ */
14
+ import { execFile } from "node:child_process";
15
+ import fs from "node:fs";
16
+ import path from "node:path";
17
+ import { fileURLToPath } from "node:url";
18
+ const CACHE_TTL_MS = 30_000;
19
+ let cached = null;
20
+ /** 向上找仓库根(判据:存在 `scripts/progress-clock.py`)。找不到 → null。 */
21
+ export function findClockScript(startDir) {
22
+ let dir = startDir;
23
+ for (let i = 0; i < 8; i += 1) {
24
+ const candidate = path.join(dir, "scripts", "progress-clock.py");
25
+ if (fs.existsSync(candidate))
26
+ return candidate;
27
+ const parent = path.dirname(dir);
28
+ if (parent === dir)
29
+ break;
30
+ dir = parent;
31
+ }
32
+ return null;
33
+ }
34
+ /** 仓库根(时钟脚本所在目录的上一级)。 */
35
+ function findRepoRoot(startDir) {
36
+ const script = findClockScript(startDir);
37
+ return script === null ? null : path.dirname(path.dirname(script));
38
+ }
39
+ function runClock(script, repoRoot, timeoutMs) {
40
+ return new Promise((resolve, reject) => {
41
+ execFile("python3", [script, "--eta", "--json-only"], { cwd: repoRoot, timeout: timeoutMs, maxBuffer: 8 * 1024 * 1024 }, (err, stdout, stderr) => {
42
+ // 退出码 1 = 时钟有告警(UNATTRIBUTED / NOT_CONVERGING …)→ **不是失败**,读数仍有效;
43
+ // 退出码 2 = 时钟自身出错(数据不可解析)→ 必须报错,不得拿半截输出当 ETA。
44
+ const out = String(stdout);
45
+ if (err) {
46
+ const code = err.code;
47
+ if (code === 1 && out.trim() !== "") {
48
+ resolve(out);
49
+ return;
50
+ }
51
+ const detail = String(stderr || err.message || "").trim();
52
+ reject(new Error(detail === "" ? err.message : detail));
53
+ return;
54
+ }
55
+ resolve(out);
56
+ });
57
+ });
58
+ }
59
+ /**
60
+ * 读一次进度与 ETA。
61
+ *
62
+ * @param opts.repoRoot 覆盖仓库根(测试用);默认从**模块自身位置**向上找
63
+ * (不能用 cwd:扩展装在用户机器上时 cwd 与本仓库无关)。
64
+ */
65
+ export async function readProgressEta(opts = {}) {
66
+ const now = Date.now();
67
+ if (!opts.fresh && cached !== null && now - cached.at < CACHE_TTL_MS) {
68
+ return cached.result;
69
+ }
70
+ const timeoutMs = opts.timeoutMs ?? 60_000;
71
+ const here = path.dirname(fileURLToPath(import.meta.url));
72
+ const repoRoot = opts.repoRoot ?? process.env.PROGRESS_CLOCK_REPO ?? findRepoRoot(here);
73
+ if (repoRoot === null) {
74
+ const msg = "proactive: 进度时钟不可用 —— 找不到 scripts/progress-clock.py" +
75
+ "(ETA 只在源码仓库内可算;安装后的扩展包不含仓库状态)";
76
+ const result = {
77
+ ok: false,
78
+ text: msg,
79
+ details: { ok: false, source: "progress-clock", error: msg },
80
+ };
81
+ cached = { at: now, result };
82
+ return result;
83
+ }
84
+ const script = path.join(repoRoot, "scripts", "progress-clock.py");
85
+ try {
86
+ const stdout = await runClock(script, repoRoot, timeoutMs);
87
+ const payload = JSON.parse(stdout.trim());
88
+ const contract = payload.eta_contract;
89
+ if (contract === undefined) {
90
+ throw new Error("时钟输出里没有 eta_contract 字段(口径不一致 → 拒绝给出 ETA)");
91
+ }
92
+ const result = {
93
+ ok: true,
94
+ text: contract.report,
95
+ details: {
96
+ ok: true,
97
+ range_low_hours: contract.range_low_hours,
98
+ range_high_hours: contract.range_high_hours,
99
+ confidence: contract.confidence,
100
+ confidence_basis: contract.confidence_basis,
101
+ blockers: (contract.blockers ?? []).map((b) => ({
102
+ source: String(b["source"] ?? ""),
103
+ blockKind: String(b["kind"] ?? ""),
104
+ hours: Number(b["hours"] ?? 0),
105
+ reason: String(b["reason"] ?? ""),
106
+ ...(b["from"] === undefined ? {} : { from: String(b["from"]) }),
107
+ ...(b["to"] === undefined ? {} : { to: String(b["to"]) }),
108
+ disposition: String(b["disposition"] ?? ""),
109
+ widens: b["widens"] === true,
110
+ })),
111
+ attribution: contract.attribution,
112
+ layer3_hours: contract.layer3_hours,
113
+ widening_hours: contract.widening_hours,
114
+ source: "scripts/progress-clock.py",
115
+ },
116
+ };
117
+ cached = { at: now, result };
118
+ return result;
119
+ }
120
+ catch (err) {
121
+ const msg = `proactive: 进度时钟读取失败:${err instanceof Error ? err.message : String(err)}`;
122
+ const result = {
123
+ ok: false,
124
+ text: msg,
125
+ details: { ok: false, source: "scripts/progress-clock.py", error: msg },
126
+ };
127
+ cached = { at: now, result };
128
+ return result;
129
+ }
130
+ }
131
+ /** 测试用:清掉缓存(避免用例之间互相污染)。 */
132
+ export function resetProgressEtaCache() {
133
+ cached = null;
134
+ }
@@ -1,4 +1,5 @@
1
1
  import { digestOf } from "./lineage.js";
2
+ import { evaluateShallowIdle, isIdleTriggerEnvelope } from "./trust-policy.js";
2
3
  /** 注入内容自带来源前缀(custom 消息在 LLM 层会变成 role:"user",模型无法自行区分来源)。 */
3
4
  const SOURCE_PREFIX = "[来自 proactive]";
4
5
  /** 本地触发的 customType(跨进程 peer 用 "proactive:peer",M4 才需要)。 */
@@ -87,6 +88,13 @@ export class Trigger {
87
88
  try {
88
89
  // **触发原因落盘(因)**:判定通过、决定触发的那一刻就把 cause + 判定依据写进事件流。
89
90
  this.noteTriggered(msg, cause);
91
+ // **空闲浅层闸门**(卡 D2):空闲触发默认静默 —— 没有用户交代、或分数不足 → 不开口。
92
+ // 缺省未注入 `trustPolicy` → 闸门不生效(既有行为逐字不变)。
93
+ const shallow = this.evaluateShallow(msg, cause);
94
+ if (shallow !== undefined && !shallow.speak) {
95
+ this.noteDropped(msg, cause, shallow.reason);
96
+ return "dropped";
97
+ }
90
98
  if (intent === "notify") {
91
99
  this.notifyOnly(msg.subject ?? "proactive", msg.body, msg);
92
100
  this.noteDelivery(msg, cause, "notified");
@@ -133,6 +141,41 @@ export class Trigger {
133
141
  console.error("[proactive] notify 失败", err);
134
142
  }
135
143
  }
144
+ /**
145
+ * 空闲浅层判定(卡 D2):**不是**空闲触发、或未注入策略 → 返回 undefined(闸门不生效)。
146
+ *
147
+ * 分量的来源(可复核):
148
+ * - 价值 = `已空闲时长 / 空闲阈值`(有判定依据时)——缺依据时按 1 计(已由上游判定为可触发);
149
+ * - 紧急度 = 1(空闲 = 用户不在场);
150
+ * - 打断代价 = 1(浅层判定发生在护栏记账之前,此处不重复计费)。
151
+ */
152
+ evaluateShallow(msg, cause) {
153
+ const policy = this.deps.trustPolicy;
154
+ if (policy === undefined || policy.enabled === false)
155
+ return undefined;
156
+ if (!isIdleTriggerEnvelope(msg) && cause.type !== "idle")
157
+ return undefined;
158
+ const basis = cause.basis;
159
+ const hasRatio = basis?.idleForMs !== undefined && basis.thresholdMs !== undefined && basis.thresholdMs > 0;
160
+ const value = hasRatio ? (basis?.idleForMs ?? 0) / (basis?.thresholdMs ?? 1) : 1;
161
+ let backed = false;
162
+ try {
163
+ backed = this.deps.hasUserBacking?.() === true;
164
+ }
165
+ catch (err) {
166
+ console.error("[proactive] 是否有用户交代判定失败(按没有交代处理)", err);
167
+ }
168
+ return evaluateShallowIdle({
169
+ policy,
170
+ value,
171
+ valueSource: hasRatio ? "trust-policy: idleForMs/thresholdMs" : "trust-policy: default",
172
+ urgency: 1,
173
+ urgencySource: "trust-policy: idle",
174
+ interruptionCost: 1,
175
+ interruptionCostSource: "trust-policy: default",
176
+ backedByUserRequest: backed,
177
+ });
178
+ }
136
179
  /**
137
180
  * 向用户提问,并把结局分成**回答 / 用户取消(Esc)/ 超时未答**三种。
138
181
  *
@@ -0,0 +1,321 @@
1
+ /**
2
+ * 目标驱动与自主推进的**执行环**(卡 D6 / D7)+ 人机分工(卡 D3)。
3
+ *
4
+ * 同一套循环,三档自主度(`docs/product/agentic-loop.md` §1):
5
+ * ```
6
+ * 目标 → 候选行动 → 打分(DECIDE1) → 执行 → 结账(带归因) → 回灌目标进度
7
+ * 自主档位:L-B 托付(条件由用户给) / L-C 目标驱动(条件由 AI 推导)
8
+ * / L-D 自主推进(目标由 AI 从账本的未竟事项里发现)
9
+ * ```
10
+ *
11
+ * **六条必要前提**(缺一即不得进入自主推进,`docs/product/agentic-loop.md` §2):
12
+ * ```
13
+ * ① 目标可判定 ② 预算硬上限 ③ 权限阶梯
14
+ * ④ 可中止可撤销 ⑤ 可追溯 ⑥ 不越界(范围外显式请求授权)
15
+ * ```
16
+ *
17
+ * 红线(§6):
18
+ * - 不得替人做**需要担责**的决定 → 不可逆 / 对外 / 价值取舍 → 一律 **human-gated**;
19
+ * - 不得**假装完成** → 未达成的目标必须显式产出「未达成 / 需人决策」记录,**不得静默结账**;
20
+ * - 不得在目标外动手 → 范围外 → 产出**显式请求授权**的记录(不是自动执行)。
21
+ *
22
+ * @capability: trust-loop
23
+ * @observable: pi-proactive/src/trust-loop.ts:advanceCandidate
24
+ * @attribution: pi-proactive/src/trust-loop.ts:advanceCandidate
25
+ * @outcome: pi-proactive/src/trust-metrics.ts:TrustMetrics
26
+ * @checked: test:pi-proactive/tests/trust-loop.test.ts#D6-3 执行 → 结账带归因
27
+ */
28
+ import { scoreDecision } from "./idle.js";
29
+ import { goalGap, goalSatisfied, isDecidable, } from "./trust-registry.js";
30
+ /**
31
+ * 权限阶梯(护栏③):**人的经历有效** → 需要担责的决定必须回到人手里。
32
+ *
33
+ * - `read` / `write`(可撤销)→ AI 可自主做;
34
+ * - `external`(对外承诺)/ `irreversible`(不可逆)/ `value-judgment`(取舍与价值排序)
35
+ * → **human-gated**:只能生成待人决策的记录,**不得自动执行**。
36
+ */
37
+ export function resolvePermissionTier(actionType) {
38
+ switch (actionType) {
39
+ case "read":
40
+ return "auto";
41
+ case "write":
42
+ return "limited";
43
+ default:
44
+ return "human-gated";
45
+ }
46
+ }
47
+ /** 六条护栏(缺一即不得进入自主推进)。 */
48
+ export const GUARDRAILS = [
49
+ "goal-decidable",
50
+ "budget-cap",
51
+ "permission-ladder",
52
+ "abortable-revocable",
53
+ "traceable",
54
+ "in-scope",
55
+ ];
56
+ /**
57
+ * 六条护栏的判定(纯函数)。
58
+ *
59
+ * @param budgetAvailable 预算是否还有余额(由调用方用登记册的预算账本判定)。
60
+ */
61
+ export function checkGuardrails(goal, candidate, budgetAvailable) {
62
+ const violations = [];
63
+ if (!isDecidable(goal))
64
+ violations.push({ rule: "goal-decidable", detail: "目标缺可判定的完成定义" });
65
+ if (!budgetAvailable || goal.budget.maxActions <= goal.budget.usedActions) {
66
+ violations.push({ rule: "budget-cap", detail: `预算已用尽(${goal.budget.usedActions}/${goal.budget.maxActions})` });
67
+ }
68
+ const expected = resolvePermissionTier(candidate.actionType);
69
+ if (candidate.permission !== expected) {
70
+ violations.push({ rule: "permission-ladder", detail: `权限档与动作种类不符(${candidate.permission} ≠ ${expected})` });
71
+ }
72
+ if (goal.permission === "human-gated" && candidate.permission !== "human-gated") {
73
+ violations.push({ rule: "permission-ladder", detail: "目标整体要求 human-gated" });
74
+ }
75
+ if (!candidate.reversible && candidate.permission !== "human-gated") {
76
+ violations.push({ rule: "abortable-revocable", detail: "不可撤销的动作不得自主执行" });
77
+ }
78
+ if (candidate.reason.trim() === "" || candidate.goalId === "") {
79
+ violations.push({ rule: "traceable", detail: "缺归因(为什么做 / 让哪个目标更近)" });
80
+ }
81
+ if (goal.scope.length > 0 && !goal.scope.includes(candidate.scope)) {
82
+ violations.push({ rule: "in-scope", detail: `范围外(${candidate.scope} ∉ ${goal.scope.join("/")})` });
83
+ }
84
+ return { ok: violations.length === 0, violations };
85
+ }
86
+ /**
87
+ * 由**目标差距**推导候选行动(卡 D6 的「候选」步)。
88
+ *
89
+ * - 完成定义缺失 → **没有候选**(不可判定的目标不许推进);
90
+ * - 清单型 → 每个未完成项一个候选;指标型 → 一个候选(把指标推向目标值)。
91
+ */
92
+ export function deriveCandidates(goal, opts = {}) {
93
+ if (!isDecidable(goal))
94
+ return [];
95
+ const threshold = opts.threshold ?? 1;
96
+ const observeRatio = opts.observeRatio ?? 0.5;
97
+ const actionType = opts.actionType ?? "write";
98
+ const gap = goalGap(goal);
99
+ const scope = goal.scope[0] ?? goal.id;
100
+ const value = gap.ratio;
101
+ const urgency = 1;
102
+ const interruptionCost = 1;
103
+ const scoring = scoreDecision({
104
+ value: { score: value, source: "trust-loop: goalGap.ratio" },
105
+ urgency: { score: urgency, source: "trust-loop: goal.open" },
106
+ interruptionCost: { score: interruptionCost, source: "trust-loop: default" },
107
+ }, threshold, observeRatio);
108
+ const base = {
109
+ goalId: goal.id,
110
+ actionType,
111
+ permission: resolvePermissionTier(actionType),
112
+ scope,
113
+ reversible: true,
114
+ value,
115
+ valueSource: "trust-loop: goalGap.ratio",
116
+ urgency,
117
+ urgencySource: "trust-loop: goal.open",
118
+ interruptionCost,
119
+ interruptionCostSource: "trust-loop: default",
120
+ score: scoring.score,
121
+ band: scoring.band,
122
+ };
123
+ if (goal.doneWhen?.type === "checklist") {
124
+ return goal.doneWhen.items
125
+ .filter((i) => !i.done)
126
+ .map((item) => ({
127
+ ...base,
128
+ id: `${goal.id}#${item.id}`,
129
+ action: item.text,
130
+ reason: `目标「${goal.title}」尚差:${item.text}(差距 ${gap.remaining}/${gap.total})`,
131
+ }));
132
+ }
133
+ const metric = goal.doneWhen;
134
+ return [
135
+ {
136
+ ...base,
137
+ id: `${goal.id}#metric`,
138
+ action: `${metric.name} → ${metric.op} ${metric.target}`,
139
+ reason: `目标「${goal.title}」的 ${metric.name} 当前 ${metric.current},目标 ${metric.op} ${metric.target}`,
140
+ },
141
+ ];
142
+ }
143
+ /**
144
+ * 推进一个候选行动(卡 D6 的「执行 → 结账」,卡 D7 的护栏)。
145
+ *
146
+ * 判定顺序(**顺序本身就是语义**):
147
+ * 1. 目标不存在 → failed(不静默);
148
+ * 2. 六条护栏 → 有违规 → blocked(**显式记录**,不执行);
149
+ * 3. 权限档 = human-gated → human-gated(**不执行、不结账**,产出待人决策记录);
150
+ * 4. 消耗预算(硬上限)→ 不足 → blocked;
151
+ * 5. 执行 → 失败 → failed(**不静默结账**);
152
+ * 6. 成功 → 结账:写归因(让哪个目标前进了多少)→ 回灌进度;
153
+ * 目标仍**未达成** → 显式产出「未达成」记录(**不得静默结账**)。
154
+ */
155
+ export async function advanceCandidate(deps, candidate) {
156
+ if (candidate.entrustmentId !== undefined) {
157
+ // 托付路径:自主推进发现的「未闭合待办」→ 走托付的知会/结账流程。
158
+ const e = deps.registry.getEntrustment(candidate.entrustmentId);
159
+ if (e === undefined) {
160
+ deps.lineage?.noteTrust({ stage: "blocked", entrustmentId: candidate.entrustmentId, reason: "entrustment-not-found" });
161
+ return { result: "failed", candidate, reason: "托付不存在" };
162
+ }
163
+ if (e.needsHuman === true) {
164
+ const reason = e.humanReason ?? "需要人决定";
165
+ deps.registry.markNeedsHuman(e.id, reason);
166
+ deps.lineage?.noteTrust({ stage: "human-gated", entrustmentId: e.id, goalId: e.goalId, reason });
167
+ return { result: "human-gated", candidate, reason };
168
+ }
169
+ if (e.status !== "arrived" && e.status !== "notified") {
170
+ const reason = `托付尚未到达(${e.status})→ 不结账`;
171
+ deps.lineage?.noteTrust({ stage: "blocked", entrustmentId: e.id, goalId: e.goalId, reason });
172
+ return { result: "blocked", candidate, violations: [{ rule: "goal-decidable", detail: reason }] };
173
+ }
174
+ const closed = closeEntrustment(deps, e.id);
175
+ if (closed.result === "human-gated") {
176
+ return { result: "human-gated", candidate, reason: closed.reason ?? "需人决定" };
177
+ }
178
+ return { result: "settled", candidate, attribution: { goalId: e.goalId ?? "", progressDelta: 1 } };
179
+ }
180
+ const goal = deps.registry.getGoal(candidate.goalId);
181
+ if (goal === undefined) {
182
+ deps.lineage?.noteTrust({ stage: "blocked", goalId: candidate.goalId, reason: "goal-not-found" });
183
+ return { result: "failed", candidate, reason: "目标不存在" };
184
+ }
185
+ const budgetAvailable = goal.budget.usedActions < goal.budget.maxActions;
186
+ const report = checkGuardrails(goal, candidate, budgetAvailable);
187
+ if (!report.ok) {
188
+ const detail = report.violations.map((v) => `${v.rule}:${v.detail}`).join(";");
189
+ deps.lineage?.noteTrust({ stage: "blocked", goalId: goal.id, reason: detail });
190
+ // 范围外 / 不可判定 → 显式请求授权或人决策(**不是**自动执行)。
191
+ if (report.violations.some((v) => v.rule === "in-scope" || v.rule === "goal-decidable")) {
192
+ deps.registry.setGoalStatus(goal.id, "needs-human", `需要人授权或补充定义:${detail}`);
193
+ }
194
+ return { result: "blocked", candidate, violations: report.violations };
195
+ }
196
+ if (candidate.permission === "human-gated") {
197
+ const reason = `该动作需人担责(${candidate.actionType})→ 不自动执行`;
198
+ deps.registry.setGoalStatus(goal.id, "needs-human", reason);
199
+ deps.lineage?.noteTrust({ stage: "human-gated", goalId: goal.id, reason });
200
+ return { result: "human-gated", candidate, reason };
201
+ }
202
+ if (!deps.registry.consumeBudget(goal.id)) {
203
+ const detail = `预算硬上限已到(${goal.budget.usedActions}/${goal.budget.maxActions})`;
204
+ deps.lineage?.noteTrust({ stage: "blocked", goalId: goal.id, reason: detail });
205
+ return { result: "blocked", candidate, violations: [{ rule: "budget-cap", detail }] };
206
+ }
207
+ let result;
208
+ try {
209
+ result = deps.execute !== undefined ? await deps.execute(candidate) : { ok: true, progressDelta: 1 };
210
+ }
211
+ catch (err) {
212
+ const reason = `执行异常:${err instanceof Error ? err.message : String(err)}`;
213
+ deps.lineage?.noteTrust({ stage: "blocked", goalId: goal.id, reason });
214
+ return { result: "failed", candidate, reason };
215
+ }
216
+ if (!result.ok) {
217
+ const reason = `执行未成功:${result.result ?? "未说明"}`;
218
+ deps.lineage?.noteTrust({ stage: "blocked", goalId: goal.id, reason });
219
+ return { result: "failed", candidate, reason };
220
+ }
221
+ const delta = result.progressDelta ?? 1;
222
+ deps.registry.recordProgress(goal.id, delta);
223
+ deps.lineage?.noteTrust({ stage: "progress", goalId: goal.id, progressDelta: delta });
224
+ const after = deps.registry.getGoal(goal.id);
225
+ const satisfied = after !== undefined && goalSatisfied(after);
226
+ if (satisfied && after !== undefined) {
227
+ deps.registry.setGoalStatus(after.id, "achieved");
228
+ deps.lineage?.noteTrust({ stage: "progress", goalId: after.id, progressDelta: delta, reason: "goal-achieved" });
229
+ }
230
+ else {
231
+ // **未达成不得静默结账**:显式记一条「仍未达成」。
232
+ deps.lineage?.noteTrust({ stage: "progress", goalId: goal.id, progressDelta: delta, reason: "goal-not-satisfied" });
233
+ }
234
+ return { result: "settled", candidate, attribution: { goalId: goal.id, progressDelta: delta } };
235
+ }
236
+ /**
237
+ * 登记一条托付并落 `registered` 事件(**登记与账本同一处** → 不会漏段)。
238
+ */
239
+ export function openEntrustment(deps, input) {
240
+ const e = deps.registry.registerEntrustment(input);
241
+ deps.lineage?.noteTrust({ stage: "registered", entrustmentId: e.id, goalId: e.goalId });
242
+ return e;
243
+ }
244
+ /**
245
+ * 托付到达(第二段):状态迁移 + `arrived` 事件(同一处 → 不会漏段)。
246
+ */
247
+ export function arriveEntrustment(deps, entrustmentId) {
248
+ const e = deps.registry.markArrived(entrustmentId);
249
+ if (e === undefined)
250
+ return undefined;
251
+ deps.lineage?.noteTrust({ stage: "arrived", entrustmentId: e.id, goalId: e.goalId });
252
+ return e;
253
+ }
254
+ /** 托付条件的判定(**可判定**:到点 / 状态命中)。 */
255
+ export function evaluateEntrustmentCondition(entrustment, now, state) {
256
+ const c = entrustment.condition;
257
+ if (c.type === "at")
258
+ return now >= c.dueAt;
259
+ if (state === undefined)
260
+ return false;
261
+ return state[c.key] === c.equals;
262
+ }
263
+ /**
264
+ * 关闭一条托付(**结账**;卡 D3:需要人决定的不得自动结账)。
265
+ *
266
+ * - `needsHuman` → 产出 human-gated 记录,**不结账**;
267
+ * - 否则 → 结账 + 落 `settled` 事件。
268
+ */
269
+ export function closeEntrustment(deps, entrustmentId) {
270
+ const e = deps.registry.getEntrustment(entrustmentId);
271
+ if (e === undefined)
272
+ return { result: "human-gated", reason: "托付不存在" };
273
+ if (e.needsHuman === true) {
274
+ const reason = e.humanReason ?? "需要人决定";
275
+ deps.registry.markNeedsHuman(entrustmentId, reason);
276
+ deps.lineage?.noteTrust({ stage: "human-gated", entrustmentId, goalId: e.goalId, reason });
277
+ return { result: "human-gated", reason };
278
+ }
279
+ const settled = deps.registry.settle(entrustmentId);
280
+ if (settled === undefined) {
281
+ const reason = `托付当前状态不可结账(${e.status})`;
282
+ deps.lineage?.noteTrust({ stage: "blocked", entrustmentId, goalId: e.goalId, reason });
283
+ return { result: "human-gated", reason };
284
+ }
285
+ deps.lineage?.noteTrust({ stage: "settled", entrustmentId, goalId: e.goalId });
286
+ return { result: "settled" };
287
+ }
288
+ /**
289
+ * 自主推进的**下一步发现**(卡 D7 的 L-D)。
290
+ *
291
+ * 来源:**未竟目标**(登记册里 `open` 的目标)与**未闭合待办**(未闭合的托付)。
292
+ * 不是凭空生成 —— 每个候选都能指回一条账本记录。
293
+ */
294
+ export function discoverNextCandidates(deps, opts = {}) {
295
+ const out = [];
296
+ for (const goal of deps.registry.listOpenGoals()) {
297
+ out.push(...deriveCandidates(goal, opts));
298
+ }
299
+ for (const e of deps.registry.listUnclosedEntrustments()) {
300
+ out.push({
301
+ id: `${e.id}#close`,
302
+ entrustmentId: e.id,
303
+ goalId: e.goalId ?? "",
304
+ action: `处理未闭合托付:${e.target}`,
305
+ actionType: "write",
306
+ permission: resolvePermissionTier("write"),
307
+ scope: e.id,
308
+ reversible: true,
309
+ value: 1,
310
+ valueSource: "trust-loop: unclosed-entrustment",
311
+ urgency: 1,
312
+ urgencySource: "trust-loop: unclosed-entrustment",
313
+ interruptionCost: 1,
314
+ interruptionCostSource: "trust-loop: default",
315
+ score: 1,
316
+ band: "trigger",
317
+ reason: `账本里有一条未闭合托付:${e.target}(${e.status})`,
318
+ });
319
+ }
320
+ return out;
321
+ }