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
@@ -409,6 +409,505 @@ function applyContentRefs(cfg, raw, dir) {
409
409
  if (Object.keys(resolved).length > 0)
410
410
  cfg.externalContent = resolved;
411
411
  }
412
+ /** 全部配置项(顺序 = 清单展示顺序)。 */
413
+ export const CONFIG_KEY_SPECS = [
414
+ {
415
+ path: "enabled",
416
+ section: "总开关与检测",
417
+ type: "boolean",
418
+ defaultValue: true,
419
+ range: "true / false",
420
+ description: "主动性总开关:关掉后定时投递与空闲触发都不发生。",
421
+ },
422
+ {
423
+ path: "idleDetect",
424
+ section: "总开关与检测",
425
+ type: "enum",
426
+ defaultValue: "off",
427
+ values: ["terminal", "input-only", "off"],
428
+ range: "terminal | input-only | off",
429
+ description: "空闲检测档位:terminal(仅 TUI)/ input-only(只靠输入事件)/ off(关闭)。",
430
+ },
431
+ {
432
+ path: "askTimeoutMs",
433
+ section: "总开关与检测",
434
+ type: "number",
435
+ defaultValue: 120000,
436
+ range: "≥ 0(毫秒)",
437
+ description: "向你提问后等待回答的超时(毫秒)。",
438
+ aliases: ["asktimeout", "asktimeoutms", "questiontimeout"],
439
+ },
440
+ {
441
+ path: "idleTrigger.enabled",
442
+ section: "空闲触发",
443
+ type: "boolean",
444
+ defaultValue: false,
445
+ range: "true / false",
446
+ description: "空闲触发开关(缺省关 → 装上不打扰你)。",
447
+ aliases: ["idletriggerenabled", "idletriggeron"],
448
+ },
449
+ {
450
+ path: "idleTrigger.afterMs",
451
+ section: "空闲触发",
452
+ type: "number",
453
+ defaultValue: 600000,
454
+ range: "≥ 0(毫秒)",
455
+ description: "连续空闲多久后触发一次(毫秒)。",
456
+ aliases: ["idletimeout", "idleafterms", "idletriggerafterms", "idleafter"],
457
+ },
458
+ {
459
+ path: "idleTrigger.intent",
460
+ section: "空闲触发",
461
+ type: "enum",
462
+ defaultValue: "wake",
463
+ values: ["wake", "interrupt", "queue", "notify"],
464
+ range: "wake | interrupt | queue | notify",
465
+ description: "空闲触发时的投递意图(wake 唤醒 / interrupt 打断 / queue 排队 / notify 知会)。",
466
+ },
467
+ {
468
+ path: "idleTrigger.askUser",
469
+ section: "空闲触发",
470
+ type: "boolean",
471
+ range: "true / false(不写 = 不问)",
472
+ description: "空闲触发时是否向你提问。",
473
+ aliases: ["askuser"],
474
+ },
475
+ {
476
+ path: "idleTrigger.prompt",
477
+ section: "空闲触发",
478
+ type: "string",
479
+ defaultValue: "",
480
+ range: "任意文本",
481
+ description: "空闲触发时投给模型的正文(空 = 用缺省)。",
482
+ },
483
+ {
484
+ path: "idleTrigger.promptRef",
485
+ section: "空闲触发",
486
+ type: "string",
487
+ range: "<配置目录>/prompts/<名字>.md",
488
+ description: "引用外部提示词文件(比把长提示词塞进 JSON 好维护)。",
489
+ aliases: ["promptref"],
490
+ },
491
+ {
492
+ path: "limits.minGapMs",
493
+ section: "频控与静默",
494
+ type: "number",
495
+ defaultValue: 300000,
496
+ range: "≥ 0(毫秒)",
497
+ description: "两次主动打扰之间的最小间隔(毫秒)。",
498
+ aliases: ["mingap", "mingapms", "mininterval"],
499
+ },
500
+ {
501
+ path: "limits.maxPerHour",
502
+ section: "频控与静默",
503
+ type: "number",
504
+ defaultValue: 6,
505
+ range: "≥ 0(次 / 小时)",
506
+ description: "每小时最多主动打扰几次。",
507
+ aliases: ["maxperhour", "hourlymax", "perhour"],
508
+ },
509
+ {
510
+ path: "limits.quietHours.from",
511
+ section: "频控与静默",
512
+ type: "string",
513
+ defaultValue: "22:00",
514
+ range: "HH:MM",
515
+ description: "静默时段开始(这段时间不主动打扰)。",
516
+ },
517
+ {
518
+ path: "limits.quietHours.to",
519
+ section: "频控与静默",
520
+ type: "string",
521
+ defaultValue: "08:00",
522
+ range: "HH:MM",
523
+ description: "静默时段结束。",
524
+ },
525
+ {
526
+ path: "decision.enabled",
527
+ section: "决策环",
528
+ type: "boolean",
529
+ defaultValue: true,
530
+ range: "true / false",
531
+ description: "决策环开关(false = 退回旧的 afterMs 判定)。",
532
+ },
533
+ {
534
+ path: "decision.threshold",
535
+ section: "决策环",
536
+ type: "number",
537
+ defaultValue: 1,
538
+ range: "≥ 0",
539
+ description: "触发阈值:分数 ≥ 阈值才触发。",
540
+ aliases: ["threshold", "scorethreshold"],
541
+ },
542
+ {
543
+ path: "decision.observeRatio",
544
+ section: "决策环",
545
+ type: "number",
546
+ defaultValue: 0.5,
547
+ range: "0 ~ 1",
548
+ description: "观察带下界(相对阈值):这一段只记账、不打扰。",
549
+ aliases: ["observeratio", "observethreshold"],
550
+ },
551
+ {
552
+ path: "decision.repeatedInterruptionWeight",
553
+ section: "决策环",
554
+ type: "number",
555
+ defaultValue: 0.5,
556
+ range: "≥ 0",
557
+ description: "近 1 小时重复打扰时,打断代价的加成。",
558
+ aliases: ["repeatedinterruptionweight"],
559
+ },
560
+ {
561
+ path: "decision.busyInterruptionWeight",
562
+ section: "决策环",
563
+ type: "number",
564
+ defaultValue: 1,
565
+ range: "≥ 0",
566
+ description: "你正忙(代理在跑)时,打断代价的加成。",
567
+ aliases: ["busyinterruptionweight"],
568
+ },
569
+ {
570
+ path: "decision.quietInterruptionWeight",
571
+ section: "决策环",
572
+ type: "number",
573
+ defaultValue: 1,
574
+ range: "≥ 0",
575
+ description: "落在静默时段时,打断代价的加成。",
576
+ aliases: ["quietinterruptionweight"],
577
+ },
578
+ {
579
+ path: "peerWatch.enabled",
580
+ section: "跨会话协作",
581
+ type: "boolean",
582
+ defaultValue: false,
583
+ range: "true / false",
584
+ description: "空闲时是否主动询问其它会话的进度。",
585
+ aliases: ["peerwatchenabled", "peerwatchon"],
586
+ },
587
+ {
588
+ path: "peerWatch.afterIdleMs",
589
+ section: "跨会话协作",
590
+ type: "number",
591
+ defaultValue: 600000,
592
+ range: "≥ 0(毫秒)",
593
+ description: "连续空闲多久后才去询问别的会话。",
594
+ aliases: ["afteridlems", "peeridle"],
595
+ },
596
+ {
597
+ path: "contentRefs",
598
+ section: "外置内容与版本",
599
+ type: "object",
600
+ range: "{ 名字: { promptRef | agentRef } }",
601
+ description: "引用外部提示词 / agent 定义的表(名字随便起,值里写 promptRef 或 agentRef)。",
602
+ aliases: ["contentrefs"],
603
+ },
604
+ {
605
+ path: "version",
606
+ section: "外置内容与版本",
607
+ type: "string",
608
+ defaultValue: ON_DISK_VERSION,
609
+ range: ON_DISK_VERSION,
610
+ description: "落盘格式版本(当前只认 v1)。",
611
+ },
612
+ ];
613
+ /** 已知**容器**路径(对象本身不是可配置项,只承载子项)。 */
614
+ const CONFIG_CONTAINER_PATHS = [
615
+ "idleTrigger",
616
+ "limits",
617
+ "limits.quietHours",
618
+ "decision",
619
+ "peerWatch",
620
+ ];
621
+ /** 归一化:只留小写字母与数字(别名 / 编辑距离比较用)。 */
622
+ function normalizeKey(text) {
623
+ return text.toLowerCase().replace(/[^a-z0-9]/g, "");
624
+ }
625
+ /** 编辑距离(Levenshtein)——「最相近的已知键」用。 */
626
+ function editDistance(a, b) {
627
+ if (a === b)
628
+ return 0;
629
+ if (a.length === 0)
630
+ return b.length;
631
+ if (b.length === 0)
632
+ return a.length;
633
+ let prev = Array.from({ length: b.length + 1 }, (_unused, i) => i);
634
+ for (let i = 1; i <= a.length; i++) {
635
+ const cur = [i];
636
+ for (let j = 1; j <= b.length; j++) {
637
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
638
+ cur[j] = Math.min((cur[j - 1] ?? 0) + 1, (prev[j] ?? 0) + 1, (prev[j - 1] ?? 0) + cost);
639
+ }
640
+ prev = cur;
641
+ }
642
+ return prev[b.length] ?? 0;
643
+ }
644
+ /**
645
+ * 「你是不是想写 …」的建议:给一个写错/不认识的键名,指到最相近的已知键。
646
+ *
647
+ * 两级策略(先精确后模糊):
648
+ * 1. **别名精确命中**(path 本身或口语别名)—— 用户嘴里的说法(「空闲超时」→ `idleTrigger.afterMs`);
649
+ * 2. **编辑距离最近**(path 与口语别名都参与比对;带路径时比全名,不带路径时比末段)
650
+ * —— 拼写笔误(「maxPerHOur」「ideltimeout」)。
651
+ *
652
+ * 毫无相近项(距离 > 2)时返回 undefined —— **不乱指**(乱指比不指更坏)。
653
+ */
654
+ export function suggestConfigKey(unknown) {
655
+ const q = normalizeKey(unknown);
656
+ if (q === "")
657
+ return undefined;
658
+ for (const spec of CONFIG_KEY_SPECS) {
659
+ for (const alias of [spec.path, ...(spec.aliases ?? [])]) {
660
+ if (normalizeKey(alias) === q)
661
+ return spec.path;
662
+ }
663
+ }
664
+ const isPath = unknown.includes(".");
665
+ let best;
666
+ for (const spec of CONFIG_KEY_SPECS) {
667
+ const last = spec.path.split(".").pop() ?? spec.path;
668
+ const dFull = editDistance(q, normalizeKey(spec.path));
669
+ const dLast = editDistance(q, normalizeKey(last));
670
+ // 别名同样参与模糊比对:用户拼错的常是「口语别名」(ideltimeout ← idletimeout),
671
+ // 只比 path 会漏掉这类 → 指不出「你是不是想写 idleTrigger.afterMs」。
672
+ const dAlias = Math.min(Infinity, ...(spec.aliases ?? []).map((a) => editDistance(q, normalizeKey(a))));
673
+ const score = isPath ? Math.min(dFull, dAlias) : Math.min(dLast, dFull, dAlias);
674
+ if (best === undefined || score < best.score) {
675
+ best = { path: spec.path, score };
676
+ continue;
677
+ }
678
+ if (score === best.score) {
679
+ // 平手 → 取更短的路径(顶层项优先),再按字典序(结果确定)。
680
+ const shorter = spec.path.length < best.path.length;
681
+ const sameLen = spec.path.length === best.path.length && spec.path < best.path;
682
+ if (shorter || sameLen)
683
+ best = { path: spec.path, score };
684
+ }
685
+ }
686
+ if (best === undefined || best.score > 2)
687
+ return undefined;
688
+ return best.path;
689
+ }
690
+ /** 未知配置项 → 抛这个错(**不得**静默忽略)。 */
691
+ export class ConfigValidationError extends Error {
692
+ /** 逐条问题(机器可读)。 */
693
+ issues;
694
+ constructor(issues) {
695
+ super(formatConfigIssues(issues));
696
+ this.name = "ConfigValidationError";
697
+ this.issues = issues;
698
+ }
699
+ }
700
+ /** 问题的类型名(人话)。 */
701
+ function typeLabel(spec) {
702
+ switch (spec.type) {
703
+ case "boolean":
704
+ return "布尔(true / false)";
705
+ case "number":
706
+ return "数字";
707
+ case "string":
708
+ return "字符串";
709
+ case "enum":
710
+ return `枚举(${spec.values?.join(" | ") ?? ""})`;
711
+ case "object":
712
+ return "对象";
713
+ }
714
+ }
715
+ /** 实际值的类型名(人话)。 */
716
+ function valueTypeLabel(value) {
717
+ if (value === null)
718
+ return "null";
719
+ if (Array.isArray(value))
720
+ return "数组";
721
+ return typeof value;
722
+ }
723
+ /** 值是否与规格相符。 */
724
+ function matchesSpec(spec, value) {
725
+ switch (spec.type) {
726
+ case "boolean":
727
+ return typeof value === "boolean";
728
+ case "number":
729
+ return typeof value === "number" && Number.isFinite(value);
730
+ case "string":
731
+ return typeof value === "string";
732
+ case "enum":
733
+ return typeof value === "string" && (spec.values ?? []).includes(value);
734
+ case "object":
735
+ return isPlainObject(value);
736
+ }
737
+ }
738
+ /**
739
+ * 校验配置里的**键**(M8):未知键 → 指名(阻断加载);类型不符 → 指名(不阻断,按缺省回落)。
740
+ *
741
+ * 为什么类型不符不阻断:既有契约是「字段类型错误 → 回退缺省且不抛错」
742
+ * (`pi-proactive/tests/config.test.ts` 锁着这条)→ 改它会误伤既有用户配置。
743
+ * 但「静默」必须改成「**指名**」:问题照样进清单与体检报告。
744
+ */
745
+ export function validateConfig(raw, file) {
746
+ const issues = [];
747
+ if (!isPlainObject(raw))
748
+ return issues;
749
+ const byPath = new Map(CONFIG_KEY_SPECS.map((spec) => [spec.path, spec]));
750
+ const walk = (obj, prefix) => {
751
+ for (const [key, value] of Object.entries(obj)) {
752
+ const p = prefix === "" ? key : `${prefix}.${key}`;
753
+ if (p === "contentRefs") {
754
+ walkContentRefs(value, p, file, issues);
755
+ continue;
756
+ }
757
+ const spec = byPath.get(p);
758
+ if (spec !== undefined) {
759
+ if (!matchesSpec(spec, value)) {
760
+ issues.push({
761
+ category: "type-mismatch",
762
+ key: p,
763
+ file,
764
+ message: `配置项 "${p}" 类型不对(期望 ${typeLabel(spec)},实际 ${valueTypeLabel(value)})` +
765
+ `→ 已按缺省值处理(文件:${file})`,
766
+ });
767
+ }
768
+ continue;
769
+ }
770
+ if (CONFIG_CONTAINER_PATHS.includes(p)) {
771
+ if (isPlainObject(value)) {
772
+ walk(value, p);
773
+ }
774
+ else {
775
+ issues.push({
776
+ category: "type-mismatch",
777
+ key: p,
778
+ file,
779
+ message: `配置项 "${p}" 类型不对(期望对象,实际 ${valueTypeLabel(value)})→ 已按缺省值处理(文件:${file})`,
780
+ });
781
+ }
782
+ continue;
783
+ }
784
+ const suggestion = suggestConfigKey(p);
785
+ issues.push({
786
+ category: "unknown-key",
787
+ key: p,
788
+ file,
789
+ ...(suggestion === undefined ? {} : { suggestion }),
790
+ message: `未知配置项 "${p}"(文件:${file})` +
791
+ (suggestion === undefined ? "→ 没有相近的已知配置项" : `→ 你是不是想写 "${suggestion}"?`),
792
+ });
793
+ }
794
+ };
795
+ walk(raw, "");
796
+ return issues;
797
+ }
798
+ /** `contentRefs` 的内层键(只允许 promptRef / agentRef)。 */
799
+ function walkContentRefs(value, prefix, file, issues) {
800
+ if (!isPlainObject(value)) {
801
+ issues.push({
802
+ category: "type-mismatch",
803
+ key: prefix,
804
+ file,
805
+ message: `配置项 "${prefix}" 类型不对(期望对象,实际 ${valueTypeLabel(value)})→ 已按缺省值处理(文件:${file})`,
806
+ });
807
+ return;
808
+ }
809
+ for (const [name, entry] of Object.entries(value)) {
810
+ if (!isPlainObject(entry))
811
+ continue;
812
+ for (const key of Object.keys(entry)) {
813
+ if (key === "promptRef" || key === "agentRef")
814
+ continue;
815
+ const p = `${prefix}.${name}.${key}`;
816
+ issues.push({
817
+ category: "unknown-key",
818
+ key: p,
819
+ file,
820
+ message: `未知配置项 "${p}"(文件:${file})→ 只允许 promptRef 或 agentRef`,
821
+ });
822
+ }
823
+ }
824
+ }
825
+ /** 问题清单 → 人话 + 机器可读的一整段文本。 */
826
+ export function formatConfigIssues(issues) {
827
+ const lines = ["[proactive] 配置校验失败(写错的配置项不会被静默忽略):"];
828
+ for (const issue of issues) {
829
+ lines.push(` · ${issue.message}`);
830
+ lines.push(` 未知键名:${issue.key}` +
831
+ ` 机器可读:CONFIG_${issue.category === "unknown-key" ? "UNKNOWN_KEY" : "TYPE_MISMATCH"}` +
832
+ ` key="${issue.key}" file="${issue.file}"` +
833
+ (issue.suggestion === undefined ? "" : ` suggestion="${issue.suggestion}"`));
834
+ }
835
+ return lines.join("\n");
836
+ }
837
+ /** 按点号路径取值(取不到 → undefined)。 */
838
+ function readPath(raw, dotted) {
839
+ let cur = raw;
840
+ for (const seg of dotted.split(".")) {
841
+ if (!isPlainObject(cur))
842
+ return undefined;
843
+ cur = cur[seg];
844
+ }
845
+ return cur;
846
+ }
847
+ /**
848
+ * 读配置清单(M6):全部项 + 当前值(**文件里写的**)+ 缺省值 + 取值范围 + 说明 + 问题清单。
849
+ *
850
+ * 为什么「当前值」取自**文件**而不是合并后的运行期配置:文件才是用户写的东西;
851
+ * 运行期配置在有未知项时会整体回落缺省 → 用它会把「你写的值」显示成「缺省值」(更迷惑)。
852
+ */
853
+ export function readConfigReport(opts) {
854
+ const dir = proactiveDir(opts?.agentDir ?? resolveAgentDir());
855
+ const file = path.join(dir, "config.json");
856
+ let raw;
857
+ let exists = false;
858
+ try {
859
+ exists = fs.existsSync(file);
860
+ if (exists)
861
+ raw = JSON.parse(fs.readFileSync(file, "utf8"));
862
+ }
863
+ catch {
864
+ raw = undefined;
865
+ }
866
+ const items = CONFIG_KEY_SPECS.map((spec) => ({
867
+ path: spec.path,
868
+ section: spec.section,
869
+ type: typeLabel(spec),
870
+ current: readPath(raw, spec.path),
871
+ defaultValue: spec.defaultValue,
872
+ range: spec.range,
873
+ description: spec.description,
874
+ }));
875
+ return { file, exists, items, issues: validateConfig(raw, file) };
876
+ }
877
+ /** 备份文件后缀(M9)。 */
878
+ export const CONFIG_BACKUP_SUFFIX = ".bak";
879
+ /**
880
+ * 原子写配置(M9):**先备份**再原子写(tmp 独占 + 同目录 rename + 0600 + fsync)。
881
+ *
882
+ * 备份 = 写前的那份内容(`<config.json>.bak`)→ 撤销就是把它写回去。
883
+ */
884
+ export function writeConfigAtomic(next, opts) {
885
+ const dir = proactiveDir(opts?.agentDir ?? resolveAgentDir());
886
+ const file = path.join(dir, "config.json");
887
+ let backup;
888
+ if (opts?.backup !== false && fs.existsSync(file)) {
889
+ backup = `${file}${CONFIG_BACKUP_SUFFIX}`;
890
+ writeAtomicFile(backup, fs.readFileSync(file, "utf8"), { durable: true });
891
+ }
892
+ writeAtomicFile(file, `${JSON.stringify(next, null, 2)}\n`, { durable: true });
893
+ return backup === undefined ? { file } : { file, backup };
894
+ }
895
+ /**
896
+ * 撤销上一次配置改动(M9):把备份写回配置文件(原子写),并消费掉这份备份。
897
+ *
898
+ * 没有备份 → 如实说「没有可撤销的改动」(不抛错)。
899
+ */
900
+ export function undoConfigChange(opts) {
901
+ const dir = proactiveDir(opts?.agentDir ?? resolveAgentDir());
902
+ const file = path.join(dir, "config.json");
903
+ const backup = `${file}${CONFIG_BACKUP_SUFFIX}`;
904
+ if (!fs.existsSync(backup)) {
905
+ return { ok: false, file, message: `没有可撤销的改动:找不到备份文件 ${backup}` };
906
+ }
907
+ writeAtomicFile(file, fs.readFileSync(backup, "utf8"), { durable: true });
908
+ fs.rmSync(backup, { force: true });
909
+ return { ok: true, file, message: `已撤销上一次配置改动(备份 ${backup} 已写回 ${file})` };
910
+ }
412
911
  /**
413
912
  * 读取配置(含**外置内容解析**)。
414
913
  *
@@ -419,9 +918,9 @@ function applyContentRefs(cfg, raw, dir) {
419
918
  export function loadConfig(opts) {
420
919
  const base = defaultConfig();
421
920
  const dir = proactiveDir();
921
+ const file = path.join(dir, "config.json");
422
922
  let raw;
423
923
  try {
424
- const file = path.join(dir, "config.json");
425
924
  if (!fs.existsSync(file))
426
925
  return base;
427
926
  raw = JSON.parse(fs.readFileSync(file, "utf8"));
@@ -432,9 +931,17 @@ export function loadConfig(opts) {
432
931
  // 未知版本 → **拒绝按已知格式解析**(规范 V3):回落缺省 + 可观察提示,
433
932
  // 且**不覆盖原文件**(saveEnabled 侧同样拒绝写入)。
434
933
  if (isPlainObject(raw) && !isKnownOnDiskVersion(raw.version)) {
435
- reportForeignVersion("config.json", path.join(dir, "config.json"), raw.version);
934
+ reportForeignVersion("config.json", file, raw.version);
436
935
  return base;
437
936
  }
937
+ // M8:**未知配置项 → 报错并指名**(不再静默忽略:拼错键名是最恶劣的一种体验)。
938
+ const issues = validateConfig(raw, file);
939
+ const unknown = issues.filter((issue) => issue.category === "unknown-key");
940
+ if (unknown.length > 0)
941
+ throw new ConfigValidationError(unknown);
942
+ // 类型不符 → **指名但不阻断**(既有契约:按缺省回落,不抛错)。
943
+ for (const issue of issues)
944
+ console.error(`[proactive] ${issue.message}`);
438
945
  const merged = mergeConfig(base, raw);
439
946
  // 外置内容引用失败 → **抛错**(不静默回退):ref 与路径都在错误消息里。
440
947
  applyContentRefs(merged, raw, dir);
@@ -467,8 +974,8 @@ export function saveEnabled(enabled, opts) {
467
974
  return file;
468
975
  }
469
976
  raw.enabled = enabled;
470
- // 原子写(tmp 独占 + 同目录 rename + 0600 + fsync:配置属「不能丢」的面,规范 I3)。
471
- writeAtomicFile(file, `${JSON.stringify(raw, null, 2)}\n`, { durable: true });
977
+ // 原子写 + **写前备份**(M9:tmp 独占 + 同目录 rename + 0600 + fsync;备份让「撤销上一次改动」可行)。
978
+ writeConfigAtomic(raw, { agentDir });
472
979
  return file;
473
980
  }
474
981
  /**