blun-king-cli 9.1.566 → 9.1.569

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 (262) hide show
  1. package/agent-spine-plugin/.claude-plugin/marketplace.json +1 -1
  2. package/agent-spine-plugin/.claude-plugin/plugin.json +1 -1
  3. package/agent-spine-plugin/.codex-plugin/plugin.json +2 -1
  4. package/agent-spine-plugin/CHANGELOG.md +1581 -0
  5. package/agent-spine-plugin/README.md +30 -4
  6. package/agent-spine-plugin/blun.plugin.json +3 -3
  7. package/agent-spine-plugin/docs/acceptance.md +61 -0
  8. package/agent-spine-plugin/docs/assignment-continuation.md +48 -0
  9. package/agent-spine-plugin/docs/host-integration.md +178 -0
  10. package/agent-spine-plugin/docs/preflight-recall.md +69 -0
  11. package/agent-spine-plugin/docs/preservation-contract.md +53 -0
  12. package/agent-spine-plugin/docs/quality-gates.md +50 -0
  13. package/agent-spine-plugin/docs/releasing.md +85 -0
  14. package/agent-spine-plugin/docs/session-timeline.md +251 -0
  15. package/agent-spine-plugin/docs/source-roots.md +113 -0
  16. package/agent-spine-plugin/docs/structured-completion.md +67 -0
  17. package/agent-spine-plugin/docs/world-model.md +94 -0
  18. package/agent-spine-plugin/hooks/codex.json +2 -2
  19. package/agent-spine-plugin/hooks/hooks.json +1 -1
  20. package/agent-spine-plugin/hooks/version.json +1 -1
  21. package/agent-spine-plugin/package.json +13 -3
  22. package/agent-spine-plugin/scripts/check-codex-install.js +226 -0
  23. package/agent-spine-plugin/scripts/check-hosts.js +14 -5
  24. package/agent-spine-plugin/scripts/check-install-hook.js +226 -0
  25. package/agent-spine-plugin/scripts/check-install-selfstarter.js +154 -0
  26. package/agent-spine-plugin/scripts/check-install.js +478 -0
  27. package/agent-spine-plugin/scripts/check-line-budget.js +58 -0
  28. package/agent-spine-plugin/scripts/check-syntax.js +29 -0
  29. package/agent-spine-plugin/scripts/github-actions.js +11 -0
  30. package/agent-spine-plugin/scripts/hermetic-process.js +183 -0
  31. package/agent-spine-plugin/scripts/release-check.js +145 -0
  32. package/agent-spine-plugin/scripts/run-acceptance.js +19 -0
  33. package/agent-spine-plugin/scripts/run-checks.js +47 -0
  34. package/agent-spine-plugin/scripts/run-tests-hermetic.js +89 -0
  35. package/agent-spine-plugin/skills/agent-spine/SKILL.md +16 -2
  36. package/agent-spine-plugin/spine-example/1-identity.md +12 -0
  37. package/agent-spine-plugin/spine-example/2-voice.md +6 -0
  38. package/agent-spine-plugin/spine-example/3-conduct.md +8 -0
  39. package/agent-spine-plugin/spine-example/4-history.md +4 -0
  40. package/agent-spine-plugin/src/cli-agent.js +296 -0
  41. package/agent-spine-plugin/src/cli-attention.js +95 -0
  42. package/agent-spine-plugin/src/cli-autonomy.js +36 -0
  43. package/agent-spine-plugin/src/cli-common.js +71 -0
  44. package/agent-spine-plugin/src/cli-continuity.js +116 -0
  45. package/agent-spine-plugin/src/cli-core.js +128 -0
  46. package/agent-spine-plugin/src/cli-diagnostics.js +291 -0
  47. package/agent-spine-plugin/src/cli-host.js +21 -0
  48. package/agent-spine-plugin/src/cli-learning.js +305 -0
  49. package/agent-spine-plugin/src/cli-premortem.js +16 -0
  50. package/agent-spine-plugin/src/cli-sharing.js +230 -0
  51. package/agent-spine-plugin/src/cli.js +40 -1350
  52. package/agent-spine-plugin/src/codex-reader-launcher.js +182 -0
  53. package/agent-spine-plugin/src/hook.js +185 -567
  54. package/agent-spine-plugin/src/index.js +16 -2
  55. package/agent-spine-plugin/src/lib/acceptance.js +113 -11
  56. package/agent-spine-plugin/src/lib/action-lesson-recall.js +53 -0
  57. package/agent-spine-plugin/src/lib/attention-context.js +167 -0
  58. package/agent-spine-plugin/src/lib/attention-events.js +113 -0
  59. package/agent-spine-plugin/src/lib/attention-privacy.js +72 -0
  60. package/agent-spine-plugin/src/lib/attention-schema.js +164 -0
  61. package/agent-spine-plugin/src/lib/attention-storage.js +93 -0
  62. package/agent-spine-plugin/src/lib/attention.js +9 -600
  63. package/agent-spine-plugin/src/lib/audit-premortem.js +223 -0
  64. package/agent-spine-plugin/src/lib/audit.js +56 -6
  65. package/agent-spine-plugin/src/lib/autonomy-policy.js +110 -0
  66. package/agent-spine-plugin/src/lib/autonomy-store.js +202 -0
  67. package/agent-spine-plugin/src/lib/autonomy.js +8 -0
  68. package/agent-spine-plugin/src/lib/briefing.js +67 -4
  69. package/agent-spine-plugin/src/lib/catalog-document-read.js +51 -0
  70. package/agent-spine-plugin/src/lib/catalog.js +1 -1
  71. package/agent-spine-plugin/src/lib/codex-installation.js +231 -0
  72. package/agent-spine-plugin/src/lib/codex-skill-installation.js +211 -0
  73. package/agent-spine-plugin/src/lib/context.js +3 -1
  74. package/agent-spine-plugin/src/lib/delivery-agent-usage.js +224 -0
  75. package/agent-spine-plugin/src/lib/delivery-assignment.js +220 -0
  76. package/agent-spine-plugin/src/lib/delivery-command-actions.js +453 -0
  77. package/agent-spine-plugin/src/lib/delivery-knowledge.js +78 -0
  78. package/agent-spine-plugin/src/lib/delivery-premortem-binding.js +107 -0
  79. package/agent-spine-plugin/src/lib/delivery-premortem-closure.js +171 -0
  80. package/agent-spine-plugin/src/lib/delivery-premortem-codec.js +45 -0
  81. package/agent-spine-plugin/src/lib/delivery-premortem-correction.js +101 -0
  82. package/agent-spine-plugin/src/lib/delivery-premortem-file.js +21 -0
  83. package/agent-spine-plugin/src/lib/delivery-premortem-index.js +493 -0
  84. package/agent-spine-plugin/src/lib/delivery-premortem-inspection.js +37 -0
  85. package/agent-spine-plugin/src/lib/delivery-premortem-recovery.js +120 -0
  86. package/agent-spine-plugin/src/lib/delivery-premortem-rejection.js +65 -0
  87. package/agent-spine-plugin/src/lib/delivery-premortem-results.js +28 -0
  88. package/agent-spine-plugin/src/lib/delivery-premortem-session-guard.js +46 -0
  89. package/agent-spine-plugin/src/lib/delivery-premortem-write-ledger.js +285 -0
  90. package/agent-spine-plugin/src/lib/delivery-premortem.js +499 -0
  91. package/agent-spine-plugin/src/lib/delivery-shell-heredoc.js +115 -0
  92. package/agent-spine-plugin/src/lib/delivery-shell-substitutions.js +111 -0
  93. package/agent-spine-plugin/src/lib/delivery-shell-wrapper.js +64 -0
  94. package/agent-spine-plugin/src/lib/delivery-target.js +73 -0
  95. package/agent-spine-plugin/src/lib/delivery-verification.js +445 -0
  96. package/agent-spine-plugin/src/lib/documents.js +27 -5
  97. package/agent-spine-plugin/src/lib/filesystem-retry.js +2 -0
  98. package/agent-spine-plugin/src/lib/gateway-common.js +68 -0
  99. package/agent-spine-plugin/src/lib/gateway-control.js +302 -0
  100. package/agent-spine-plugin/src/lib/gateway-delivery.js +103 -0
  101. package/agent-spine-plugin/src/lib/gateway-execution.js +350 -0
  102. package/agent-spine-plugin/src/lib/gateway-host-lifecycle.js +185 -0
  103. package/agent-spine-plugin/src/lib/gateway-inspection.js +85 -0
  104. package/agent-spine-plugin/src/lib/gateway-knowledge.js +129 -0
  105. package/agent-spine-plugin/src/lib/gateway-policy-provenance.js +197 -0
  106. package/agent-spine-plugin/src/lib/gateway-premortem-disposition.js +82 -0
  107. package/agent-spine-plugin/src/lib/gateway-premortem.js +363 -0
  108. package/agent-spine-plugin/src/lib/gateway-runs.js +300 -0
  109. package/agent-spine-plugin/src/lib/gateway-runtime-identity.js +31 -0
  110. package/agent-spine-plugin/src/lib/gateway-runtime-records.js +21 -0
  111. package/agent-spine-plugin/src/lib/gateway-runtime.js +18 -1623
  112. package/agent-spine-plugin/src/lib/gateway-state-transaction.js +356 -0
  113. package/agent-spine-plugin/src/lib/gateway-state.js +342 -0
  114. package/agent-spine-plugin/src/lib/hook-artifact-guards.js +356 -0
  115. package/agent-spine-plugin/src/lib/hook-audit.js +16 -2
  116. package/agent-spine-plugin/src/lib/hook-briefing-use.js +68 -0
  117. package/agent-spine-plugin/src/lib/hook-context.js +424 -0
  118. package/agent-spine-plugin/src/lib/hook-final-message.js +26 -0
  119. package/agent-spine-plugin/src/lib/hook-input.js +27 -0
  120. package/agent-spine-plugin/src/lib/hook-output.js +140 -0
  121. package/agent-spine-plugin/src/lib/hook-premortem.js +257 -0
  122. package/agent-spine-plugin/src/lib/hook-process-advisory.js +37 -0
  123. package/agent-spine-plugin/src/lib/hook-protection.js +95 -0
  124. package/agent-spine-plugin/src/lib/hook-stop-verification.js +84 -0
  125. package/agent-spine-plugin/src/lib/hook-timeline.js +91 -0
  126. package/agent-spine-plugin/src/lib/identifier-analysis.js +446 -0
  127. package/agent-spine-plugin/src/lib/indexed-memory.js +23 -6
  128. package/agent-spine-plugin/src/lib/knowledge-evidence.js +431 -0
  129. package/agent-spine-plugin/src/lib/learning-applications.js +441 -0
  130. package/agent-spine-plugin/src/lib/learning-candidates.js +274 -0
  131. package/agent-spine-plugin/src/lib/learning-context.js +130 -0
  132. package/agent-spine-plugin/src/lib/learning-delivery-contracts.js +310 -0
  133. package/agent-spine-plugin/src/lib/learning-evaluation-contracts.js +350 -0
  134. package/agent-spine-plugin/src/lib/learning-evaluation-registration.js +372 -0
  135. package/agent-spine-plugin/src/lib/learning-evaluation-revocation.js +292 -0
  136. package/agent-spine-plugin/src/lib/learning-evidence-contracts.js +321 -0
  137. package/agent-spine-plugin/src/lib/learning-findings.js +458 -0
  138. package/agent-spine-plugin/src/lib/learning-measurement-contracts.js +285 -0
  139. package/agent-spine-plugin/src/lib/learning-measurements.js +265 -0
  140. package/agent-spine-plugin/src/lib/learning-outcome-contracts.js +165 -0
  141. package/agent-spine-plugin/src/lib/learning-outcomes.js +343 -0
  142. package/agent-spine-plugin/src/lib/learning-reconciliation.js +290 -0
  143. package/agent-spine-plugin/src/lib/learning-retry-contracts.js +188 -0
  144. package/agent-spine-plugin/src/lib/learning-schema.js +229 -0
  145. package/agent-spine-plugin/src/lib/learning-scope-targets.js +424 -0
  146. package/agent-spine-plugin/src/lib/learning-state-upgrade.js +477 -0
  147. package/agent-spine-plugin/src/lib/learning-status-configuration.js +476 -0
  148. package/agent-spine-plugin/src/lib/learning-storage.js +203 -0
  149. package/agent-spine-plugin/src/lib/learning-trial-recovery.js +218 -0
  150. package/agent-spine-plugin/src/lib/learning-validation-contracts.js +375 -0
  151. package/agent-spine-plugin/src/lib/learning-validation-renewal.js +298 -0
  152. package/agent-spine-plugin/src/lib/learning-validation-runtime.js +234 -0
  153. package/agent-spine-plugin/src/lib/learning.js +36 -6923
  154. package/agent-spine-plugin/src/lib/lesson-recall-session.js +172 -0
  155. package/agent-spine-plugin/src/lib/mcp-autonomy-tools.js +37 -0
  156. package/agent-spine-plugin/src/lib/mcp-delivery-completion.js +124 -0
  157. package/agent-spine-plugin/src/lib/mcp-delivery-tools.js +45 -0
  158. package/agent-spine-plugin/src/lib/mcp-premortem.js +68 -0
  159. package/agent-spine-plugin/src/lib/mcp-runtime.js +269 -0
  160. package/agent-spine-plugin/src/lib/mcp-source-context.js +51 -0
  161. package/agent-spine-plugin/src/lib/mcp-timeline-tools.js +164 -0
  162. package/agent-spine-plugin/src/lib/mcp-world-tools.js +52 -0
  163. package/agent-spine-plugin/src/lib/owned-file-lock.js +30 -10
  164. package/agent-spine-plugin/src/lib/project-portfolio.js +176 -0
  165. package/agent-spine-plugin/src/lib/selfstarter-core.js +382 -0
  166. package/agent-spine-plugin/src/lib/selfstarter-jobs.js +149 -0
  167. package/agent-spine-plugin/src/lib/selfstarter-lease.js +204 -0
  168. package/agent-spine-plugin/src/lib/selfstarter-policy.js +90 -0
  169. package/agent-spine-plugin/src/lib/selfstarter-workspace.js +111 -0
  170. package/agent-spine-plugin/src/lib/selfstarter.js +11 -911
  171. package/agent-spine-plugin/src/lib/session-timeline-auth.js +316 -0
  172. package/agent-spine-plugin/src/lib/session-timeline-codex.js +58 -0
  173. package/agent-spine-plugin/src/lib/session-timeline-contract.js +48 -0
  174. package/agent-spine-plugin/src/lib/session-timeline-enrollment-source.js +41 -0
  175. package/agent-spine-plugin/src/lib/session-timeline-enrollment-storage.js +132 -0
  176. package/agent-spine-plugin/src/lib/session-timeline-enrollment-transport.js +17 -0
  177. package/agent-spine-plugin/src/lib/session-timeline-enrollment.js +500 -0
  178. package/agent-spine-plugin/src/lib/session-timeline-event-extract.js +157 -0
  179. package/agent-spine-plugin/src/lib/session-timeline-host-origin.js +74 -0
  180. package/agent-spine-plugin/src/lib/session-timeline-host-receipt.js +117 -0
  181. package/agent-spine-plugin/src/lib/session-timeline-invocation.js +201 -0
  182. package/agent-spine-plugin/src/lib/session-timeline-king.js +79 -0
  183. package/agent-spine-plugin/src/lib/session-timeline-prior.js +59 -0
  184. package/agent-spine-plugin/src/lib/session-timeline-provider.js +34 -0
  185. package/agent-spine-plugin/src/lib/session-timeline-query.js +55 -0
  186. package/agent-spine-plugin/src/lib/session-timeline-results.js +31 -0
  187. package/agent-spine-plugin/src/lib/session-timeline-root.js +11 -0
  188. package/agent-spine-plugin/src/lib/session-timeline-search.js +82 -0
  189. package/agent-spine-plugin/src/lib/session-timeline-sid-acl.js +217 -0
  190. package/agent-spine-plugin/src/lib/session-timeline-source.js +83 -0
  191. package/agent-spine-plugin/src/lib/session-timeline-state.js +45 -0
  192. package/agent-spine-plugin/src/lib/session-timeline-transport.js +50 -0
  193. package/agent-spine-plugin/src/lib/session-timeline-windows-acl.js +148 -0
  194. package/agent-spine-plugin/src/lib/session-timeline.js +453 -0
  195. package/agent-spine-plugin/src/lib/source-roots.js +69 -136
  196. package/agent-spine-plugin/src/lib/source-tree-scan.js +178 -0
  197. package/agent-spine-plugin/src/lib/task-knowledge-context.js +78 -0
  198. package/agent-spine-plugin/src/lib/timeline-tool-guard.js +202 -0
  199. package/agent-spine-plugin/src/lib/world-knowledge.js +249 -0
  200. package/agent-spine-plugin/src/lib/world-model.js +278 -0
  201. package/agent-spine-plugin/src/mcp.js +20 -161
  202. package/agent-spine-plugin/src/version.js +1 -1
  203. package/agent-spine-plugin/src/worker.js +22 -5
  204. package/bin/agent-resume-snapshot.cjs +2 -2
  205. package/bin/agentspine-king-goal-inbox.mjs +111 -0
  206. package/bin/agentspine-king-goal-intake.mjs +106 -0
  207. package/bin/baseline-skill-performance-policy.cjs +1 -16
  208. package/bin/core-bootstrap.js +2 -0
  209. package/bin/curiosity-scout-policy.cjs +5 -1
  210. package/bin/input-draft-persistence.cjs +2 -2
  211. package/bin/king-tui-function-contract.json +33 -0
  212. package/bin/launcher-restart-policy.cjs +150 -0
  213. package/bin/launcher-runtime.js +56 -44
  214. package/bin/managed-context-startup-policy.cjs +27 -0
  215. package/bin/managed-plugin-selection.cjs +116 -0
  216. package/bin/mistake-relevance-policy.cjs +1 -1
  217. package/bin/observer-hooks.cjs +14 -0
  218. package/bin/oversized-context-offload-policy.cjs +86 -0
  219. package/bin/plugin-bootstrap.js +7 -40
  220. package/bin/proactive-compaction-policy.cjs +1 -1
  221. package/bin/provider-model-refresh-deadline.cjs +53 -0
  222. package/bin/provider-model-refresh-policy.cjs +107 -0
  223. package/bin/release-artifact-freeze-policy.cjs +30 -0
  224. package/bin/repeated-user-message-projection.cjs +3 -126
  225. package/bin/research-page-result.cjs +74 -0
  226. package/bin/runtime-exit-ledger.cjs +1 -0
  227. package/bin/session-compaction-policy.cjs +84 -0
  228. package/bin/skill-listing-performance-policy.cjs +2 -2
  229. package/bin/standard-tools-bootstrap.js +0 -37
  230. package/bin/subagent-skill-policy.cjs +3 -1
  231. package/bin/telegram-approval-relay.cjs +12 -7
  232. package/bin/telegram-private-conversation-policy.cjs +3 -2
  233. package/bin/telegram-queue-handoff-policy.cjs +24 -0
  234. package/bin/thinking-activity-status-policy.cjs +1 -1
  235. package/bin/thinking-only-guard.cjs +16 -12
  236. package/bin/tool-call-loop-policy.cjs +0 -2
  237. package/bin/tool-result-offload-policy.cjs +11 -2
  238. package/bin/tui-functional-contract.cjs +55 -0
  239. package/bin/update-notice.js +18 -14
  240. package/bin/user-message-offload-policy.cjs +1 -1
  241. package/bin/user-prompt-hook-origin-policy.cjs +34 -0
  242. package/bin/windows-node-crash-dump.cjs +110 -0
  243. package/blun.mjs +2517 -1048
  244. package/codebase-index/codebase_index.py +4 -3
  245. package/package.json +3 -17
  246. package/standard-skills/research-evidence/SKILL.md +39 -0
  247. package/standard-skills/research-evidence/references/evidence-format.md +82 -0
  248. package/standard-skills/research-evidence/scripts/evidence-collection.cjs +254 -0
  249. package/standard-skills/research-evidence/scripts/score-report.cjs +112 -0
  250. package/standard-skills/web-lesen/SKILL.md +37 -22
  251. package/standard-skills/web-lesen/scripts/crawl_public.py +376 -0
  252. package/telegram-plugin/DELIVERY.md +36 -0
  253. package/telegram-plugin/bin/telegram-approval-relay.cjs +13 -7
  254. package/telegram-plugin/bin/telegram-launcher-status-queue.cjs +122 -0
  255. package/telegram-plugin/bin/telegram-private-conversation-policy.cjs +3 -2
  256. package/telegram-plugin/bin/telegram-reply-parts.cjs +149 -0
  257. package/telegram-plugin/dist/bridge.mjs +11 -57
  258. package/telegram-plugin/dist/mcp-server.mjs +33 -4
  259. package/agent-spine-plugin/skill/SKILL.md +0 -76
  260. package/bin/mnemo-connect-heartbeat.cjs +0 -204
  261. package/bin/mnemo-tool-agent-policy.cjs +0 -22
  262. package/telegram-plugin/bin/telegram-mnemo-capture.cjs +0 -297
@@ -0,0 +1,154 @@
1
+ import { pathToFileURL } from "node:url";
2
+ import { join } from "node:path";
3
+
4
+ const PREMORTEM_ITEMS = [
5
+ {
6
+ category: "baseline-environment",
7
+ failure: "this delivery fails because the installed synthetic baseline is stale",
8
+ check: "Compare the installed synthetic project and state roots."
9
+ },
10
+ {
11
+ category: "contract-tests",
12
+ failure: "this delivery fails because the installed hook contract regresses",
13
+ check: "Run the installed hook lifecycle check."
14
+ },
15
+ {
16
+ category: "delivery-path",
17
+ failure: "this delivery fails because the installed artifact path is wrong",
18
+ check: "Verify the installed synthetic artifact path."
19
+ }
20
+ ];
21
+
22
+ async function withStateRoot(stateRoot, action) {
23
+ const previous = process.env.AGENTSPINE_STATE_DIR;
24
+ process.env.AGENTSPINE_STATE_DIR = stateRoot;
25
+ try {
26
+ return await action();
27
+ } finally {
28
+ if (previous === undefined) delete process.env.AGENTSPINE_STATE_DIR;
29
+ else process.env.AGENTSPINE_STATE_DIR = previous;
30
+ }
31
+ }
32
+
33
+ async function prepareRuntime(pluginRoot, projectRoot, stateRoot, host) {
34
+ await withStateRoot(stateRoot, async () => {
35
+ const graph = await import(pathToFileURL(join(pluginRoot, "src/lib/graph.js")).href);
36
+ const coordination = await import(pathToFileURL(join(pluginRoot, "src/lib/coordination.js")).href);
37
+ const selfstarter = await import(pathToFileURL(join(pluginRoot, "src/lib/selfstarter.js")).href);
38
+ for (const [id, kind] of [
39
+ ["person:install-owner", "person"],
40
+ ["agent:install-worker", "agent"],
41
+ ["project:install-runtime", "project"]
42
+ ]) await graph.upsertEntity({ root: projectRoot, id, kind, privacy: "shared" });
43
+ await coordination.createTask({
44
+ root: projectRoot,
45
+ id: `task:install-${host}`,
46
+ actorId: "agent:install-worker",
47
+ assigneeId: "agent:install-worker",
48
+ projectId: "project:install-runtime",
49
+ title: `Installed ${host} self-starter check`,
50
+ privacy: "private"
51
+ });
52
+ await selfstarter.grantExecution({
53
+ root: projectRoot,
54
+ id: `execution-grant:install-${host}`,
55
+ jobId: `job:install-${host}`,
56
+ actorId: "agent:install-worker",
57
+ taskId: `task:install-${host}`,
58
+ targetId: "person:install-owner",
59
+ projectId: "project:install-runtime",
60
+ host,
61
+ capabilities: ["tool:Write"],
62
+ reason: `Owner approved the exact installed ${host} synthetic write.`,
63
+ confirmation: "local-owner-confirmed"
64
+ });
65
+ await selfstarter.registerJob({
66
+ root: projectRoot,
67
+ id: `job:install-${host}`,
68
+ grantId: `execution-grant:install-${host}`,
69
+ confirmation: "local-owner-confirmed"
70
+ });
71
+ });
72
+ }
73
+
74
+ async function registerPremortem(pluginRoot, projectRoot, stateRoot, requirementId) {
75
+ if (!/^premortem-requirement:[a-f0-9]{64}:[a-f0-9]{64}$/.test(requirementId || "")) {
76
+ throw new Error("installed UserPromptSubmit did not inject an exact premortem requirement");
77
+ }
78
+ return withStateRoot(stateRoot, async () => {
79
+ const premortem = await import(pathToFileURL(join(pluginRoot,
80
+ "src/lib/delivery-premortem.js")).href);
81
+ const usage = await import(pathToFileURL(join(pluginRoot,
82
+ "src/lib/delivery-agent-usage.js")).href);
83
+ await usage.recordDeliveryBriefingUse({ root: projectRoot, requirementId,
84
+ input: { root: projectRoot }, result: { schema: "synthetic-installed-briefing" } });
85
+ await usage.recordDeliveryKnowledgeUse({ root: projectRoot, requirementId,
86
+ input: { targets: ["AGENTS.md"] }, result: { schema: "synthetic-installed-knowledge" } });
87
+ const recorded = await premortem.recordDeliveryPremortem({
88
+ root: projectRoot,
89
+ requirementId,
90
+ items: PREMORTEM_ITEMS
91
+ });
92
+ if (recorded.blocked) throw new Error(`installed premortem registration failed: ${recorded.reason}`);
93
+ });
94
+ }
95
+
96
+ export async function invokeInstalledSelfstarter({
97
+ pluginRoot, projectRoot, stateRoot, host, invokeInstalledHook, writeFile
98
+ }) {
99
+ await prepareRuntime(pluginRoot, projectRoot, stateRoot, host);
100
+ const session = `session:install-${host}-one`;
101
+ const nextSession = `session:install-${host}-two`;
102
+ const toolUseId = `tool:install-${host}`;
103
+ const taskId = `task:install-${host}`;
104
+ const artifact = join(projectRoot, `installed-${host}-artifact.txt`);
105
+ const toolInput = { file_path: artifact, content: `installed ${host} checkpoint\n` };
106
+ const shared = {
107
+ cwd: projectRoot,
108
+ host,
109
+ entity_id: "agent:install-worker",
110
+ project_id: "project:install-runtime",
111
+ task_id: taskId
112
+ };
113
+ const started = await invokeInstalledHook(pluginRoot, projectRoot, stateRoot, host, {
114
+ ...shared, hook_event_name: "SessionStart", session_id: session
115
+ });
116
+ if (!started.selfstarter?.active || started.selfstarter.action !== "start") {
117
+ throw new Error(`${host} installed hook did not start the exact authorized job`);
118
+ }
119
+ const prompted = await invokeInstalledHook(pluginRoot, projectRoot, stateRoot, host, {
120
+ ...shared, hook_event_name: "UserPromptSubmit", session_id: session,
121
+ event_id: `prompt:install-${host}`, prompt: "Write the installed synthetic checkpoint."
122
+ });
123
+ await registerPremortem(pluginRoot, projectRoot, stateRoot,
124
+ prompted.preflight?.premortem?.requirementId);
125
+ const authorized = await invokeInstalledHook(pluginRoot, projectRoot, stateRoot, host, {
126
+ ...shared, hook_event_name: "PreToolUse", session_id: session,
127
+ tool_name: "Write", tool_use_id: toolUseId, tool_input: toolInput
128
+ }, { requireBriefing: false });
129
+ if (authorized.decision === "block") {
130
+ throw new Error(`${host} installed PreToolUse denied the exact authorized effect: ${authorized.reason}`);
131
+ }
132
+ await writeFile(artifact, toolInput.content, "utf8");
133
+ await invokeInstalledHook(pluginRoot, projectRoot, stateRoot, host, {
134
+ ...shared, hook_event_name: "PostToolUse", session_id: session,
135
+ tool_name: "Write", tool_use_id: toolUseId, tool_input: toolInput,
136
+ success: true, tool_result: { ok: true }
137
+ }, { requireBriefing: false });
138
+ await invokeInstalledHook(pluginRoot, projectRoot, stateRoot, host, {
139
+ ...shared, hook_event_name: "Stop", session_id: session
140
+ }, { requireBriefing: false });
141
+ const resumed = await invokeInstalledHook(pluginRoot, projectRoot, stateRoot, host, {
142
+ ...shared, hook_event_name: "SessionStart", session_id: nextSession
143
+ });
144
+ if (!resumed.selfstarter?.active || resumed.selfstarter.action !== "resume"
145
+ || resumed.selfstarter.checkpointSequence !== 1) {
146
+ throw new Error(`${host} installed hook did not resume the durable checkpoint`);
147
+ }
148
+ return {
149
+ started: started.selfstarter.action,
150
+ resumed: resumed.selfstarter.action,
151
+ checkpointSequence: resumed.selfstarter.checkpointSequence,
152
+ mcpCalls: 0
153
+ };
154
+ }
@@ -0,0 +1,478 @@
1
+ #!/usr/bin/env node
2
+ import { createHash, createHmac } from "node:crypto";
3
+ import { cp, mkdir, mkdtemp, readFile, rename, rm, symlink, writeFile } from "node:fs/promises";
4
+ import { tmpdir } from "node:os";
5
+ import { basename, join, resolve } from "node:path";
6
+ import { setTimeout as delay } from "node:timers/promises";
7
+ import { fileURLToPath, pathToFileURL } from "node:url";
8
+ import { checkHosts } from "./check-hosts.js";
9
+ import { checkCodexInstall } from "./check-codex-install.js";
10
+ import { releaseCheck } from "./release-check.js";
11
+ import {
12
+ invokeInstalledBlockingProtocols, invokeInstalledBlunPostWriteDigest, invokeInstalledHook
13
+ } from "./check-install-hook.js";
14
+ import { invokeInstalledSelfstarter as runInstalledSelfstarter } from "./check-install-selfstarter.js";
15
+
16
+ function hash(value) {
17
+ return createHash("sha256").update(value).digest("hex");
18
+ }
19
+
20
+ function copyFilter(source) {
21
+ const name = basename(source);
22
+ return !new Set([".git", "node_modules"]).has(name) && !name.endsWith(".tgz");
23
+ }
24
+
25
+ async function copyBundle(source, target) {
26
+ await cp(source, target, { recursive: true, filter: copyFilter });
27
+ }
28
+
29
+ async function removeTree(path, options = {}) {
30
+ for (let attempt = 0; ; attempt += 1) {
31
+ try {
32
+ await rm(path, { recursive: true, ...options });
33
+ return;
34
+ } catch (error) {
35
+ const transient = process.platform === "win32" && ["EACCES", "EBUSY", "EPERM"].includes(error?.code);
36
+ if (!transient || attempt >= 7) throw error;
37
+ await delay(10 * (attempt + 1));
38
+ }
39
+ }
40
+ }
41
+
42
+ async function makePreviousCache(target) {
43
+ const version = "0.65.0";
44
+ for (const path of ["package.json", "package-lock.json", "blun.plugin.json",
45
+ ".claude-plugin/plugin.json", ".codex-plugin/plugin.json",
46
+ ".claude-plugin/marketplace.json", "hooks/version.json"]) {
47
+ const file = join(target, path);
48
+ let value;
49
+ try { value = JSON.parse(await readFile(file, "utf8")); } catch (error) {
50
+ if (path !== "package-lock.json" || error.code !== "ENOENT") throw error;
51
+ const pkg = JSON.parse(await readFile(join(target, "package.json"), "utf8"));
52
+ value = { name: pkg.name, version, packages: { "": { name: pkg.name, version } } };
53
+ }
54
+ if (path === ".claude-plugin/marketplace.json") value.plugins[0].version = version;
55
+ else value.version = version;
56
+ if (path === "package-lock.json") value.packages[""].version = version;
57
+ if (path === ".codex-plugin/plugin.json") delete value.hooks;
58
+ await writeFile(file, `${JSON.stringify(value, null, 2)}\n`, "utf8");
59
+ }
60
+ await writeFile(join(target, "src/version.js"), `export const VERSION = "${version}";\n`, "utf8");
61
+ return version;
62
+ }
63
+
64
+ async function invokeInstalledLiveRoots(pluginRoot, workspace, stateRoot) {
65
+ const home = join(workspace, "live-home");
66
+ const claudeHome = join(home, ".claude-live");
67
+ const codexHome = join(home, ".codex-live");
68
+ const projectA = join(home, "AgentSpine");
69
+ const projectB = join(home, "other-project");
70
+ const nestedB = join(projectB, "api");
71
+ const loose = join(workspace, "foreign-cwd");
72
+ const memory = join(claudeHome, "projects", "project-a", "memory");
73
+ for (const path of [claudeHome, codexHome, projectA, nestedB, loose, memory]) await mkdir(path, { recursive: true });
74
+ await mkdir(join(projectA, ".git"));
75
+ await mkdir(join(projectB, ".git"));
76
+ const protectedSources = new Map([
77
+ [join(claudeHome, "CLAUDE.md"), "# Installed user style\n\nBe calm and humane.\n"],
78
+ [join(projectA, "CLAUDE.md"), "# Installed project A\n"],
79
+ [join(projectB, "CLAUDE.md"), "# Installed project B\n"],
80
+ [join(memory, "MEMORY.md"), "# Installed project A memory\n\n- [Communication style](style.md) <!-- agentspine:always -->\n"],
81
+ [join(memory, "style.md"), "# Installed indexed style\n\nKeep answers calm and direct.\n"],
82
+ [join(memory, "unindexed.md"), "# Installed unindexed private note\n\nThis must never enter the live hook.\n"],
83
+ [join(codexHome, "AGENTS.override.md"), "# Installed Codex user style\n"],
84
+ [join(projectA, "AGENTS.md"), "# Installed Codex A\n"],
85
+ [join(projectB, "TEAM_GUIDE.md"), "# Installed Codex B\n"],
86
+ [join(nestedB, "AGENTS.override.md"), "# Installed Codex API override\n"],
87
+ [join(home, "PRIVATE.md"), "# Never scan this home file\n"]
88
+ ]);
89
+ for (const [path, content] of protectedSources) await writeFile(path, content, "utf8");
90
+ const transcript = join(claudeHome, "projects", "project-a", "session.jsonl");
91
+ await writeFile(transcript, "{}\n", "utf8");
92
+ await writeFile(join(loose, "CLAUDE.md"), "# Installed loose cwd\n", "utf8");
93
+ await writeFile(join(codexHome, "config.toml"), [
94
+ 'project_doc_fallback_filenames = ["TEAM_GUIDE.md"]', 'project_root_markers = [".git"]', ""
95
+ ].join("\n"), "utf8");
96
+ const before = new Map(await Promise.all([...protectedSources.keys()].map(async (path) => [path, hash(await readFile(path))])));
97
+ const claudeEnv = { HOME: home, CLAUDE_CONFIG_DIR: claudeHome };
98
+ const codexEnv = { HOME: home, CODEX_HOME: codexHome };
99
+ const claudeA = await invokeInstalledHook(pluginRoot, projectA, stateRoot, "claude", {
100
+ hook_event_name: "SessionStart", host: "claude", cwd: projectA, transcript_path: transcript
101
+ }, { extraEnv: claudeEnv });
102
+ const claudeRestart = await invokeInstalledHook(pluginRoot, projectA, stateRoot, "claude", {
103
+ hook_event_name: "PostCompact", host: "claude", cwd: projectA
104
+ }, { extraEnv: claudeEnv });
105
+ const claudeLoose = await invokeInstalledHook(pluginRoot, loose, stateRoot, "claude", {
106
+ hook_event_name: "SessionStart", host: "claude", cwd: loose
107
+ }, { extraEnv: claudeEnv });
108
+ const codexA = await invokeInstalledHook(pluginRoot, projectA, stateRoot, "codex", {
109
+ hook_event_name: "SessionStart", host: "codex", cwd: projectA
110
+ }, { extraEnv: codexEnv });
111
+ const codexB = await invokeInstalledHook(pluginRoot, nestedB, stateRoot, "codex", {
112
+ hook_event_name: "PostCompact", host: "codex", cwd: nestedB
113
+ }, { extraEnv: codexEnv });
114
+ if (!claudeA.sourceIds.includes("claude:user/CLAUDE.md") || !claudeA.sourceIds.includes("claude:memory/MEMORY.md")
115
+ || !claudeA.sourceIds.includes("claude:memory/style.md") || claudeA.sourceIds.includes("claude:memory/unindexed.md")
116
+ || !claudeRestart.sourceIds.includes("claude:memory/MEMORY.md") || !claudeRestart.sourceIds.includes("claude:memory/style.md")
117
+ || claudeRestart.sourceIds.includes("claude:memory/unindexed.md") || claudeLoose.sourceIds.some((id) => id.startsWith("claude:memory/"))) {
118
+ throw new Error("installed Claude hook did not preserve exact user/project/memory source scope across restart and foreign cwd");
119
+ }
120
+ for (const result of [claudeA, claudeRestart]) {
121
+ const diagnostic = result.sourceResolution?.memory;
122
+ if (diagnostic?.indexed !== 1 || diagnostic.loaded !== 1 || diagnostic.directoryEnumeration !== 0) {
123
+ throw new Error("installed Claude hook did not use bounded indexed-memory resolution");
124
+ }
125
+ }
126
+ if (claudeA.sourceContents.some((content) => content.includes("Never scan this"))
127
+ || claudeLoose.sourceContents.some((content) => content.includes("Installed project A"))) {
128
+ throw new Error("installed Claude hook leaked a broad-home or foreign-project source");
129
+ }
130
+ const codexAIds = codexA.sourceIds.filter((id) => id.startsWith("codex:"));
131
+ const codexBIds = codexB.sourceIds.filter((id) => id.startsWith("codex:"));
132
+ if (JSON.stringify(codexAIds) !== JSON.stringify(["codex:user/AGENTS.override.md", "codex:project/AGENTS.md"])
133
+ || JSON.stringify(codexBIds) !== JSON.stringify(["codex:user/AGENTS.override.md", "codex:project/TEAM_GUIDE.md", "codex:project/api/AGENTS.override.md"])) {
134
+ throw new Error("installed Codex hook did not preserve native override and fallback precedence");
135
+ }
136
+ for (const [path, expected] of before) {
137
+ if (hash(await readFile(path)) !== expected) throw new Error(`installed live-root hook changed a protected source: ${basename(path)}`);
138
+ }
139
+ return {
140
+ claude: { project: claudeA.sourceIds, restart: claudeRestart.sourceIds, foreign: claudeLoose.sourceIds,
141
+ indexedMemory: claudeA.sourceResolution.memory },
142
+ codex: { projectA: codexAIds, projectB: codexBIds }, broadHomeScan: false, mcpCalls: 0
143
+ };
144
+ }
145
+
146
+ async function invokeInstalledSelfstarter(pluginRoot, projectRoot, stateRoot, host) {
147
+ return runInstalledSelfstarter({
148
+ pluginRoot, projectRoot, stateRoot, host, invokeInstalledHook, writeFile
149
+ });
150
+ }
151
+
152
+ async function invokeInstalledAcceptance(pluginRoot) {
153
+ const acceptance = await import(pathToFileURL(join(pluginRoot, "src/lib/acceptance.js")).href);
154
+ const report = await acceptance.runVisibleAcceptance();
155
+ if (!report.ok || report.passed !== report.total || report.mcpCalls !== 0) {
156
+ throw new Error("installed bundle did not pass visible lifecycle acceptance");
157
+ }
158
+ return {
159
+ passed: report.passed,
160
+ total: report.total,
161
+ receiptDigest: report.receiptDigest,
162
+ hosts: report.hosts,
163
+ languages: report.languages,
164
+ mcpCalls: report.mcpCalls
165
+ };
166
+ }
167
+
168
+ async function prepareInstalledAttention(pluginRoot, projectRoot, stateRoot) {
169
+ const previous = process.env.AGENTSPINE_STATE_DIR;
170
+ process.env.AGENTSPINE_STATE_DIR = stateRoot;
171
+ try {
172
+ const graph = await import(pathToFileURL(join(pluginRoot, "src/lib/graph.js")).href);
173
+ const coordination = await import(pathToFileURL(join(pluginRoot, "src/lib/coordination.js")).href);
174
+ const continuity = await import(pathToFileURL(join(pluginRoot, "src/lib/continuity.js")).href);
175
+ await graph.upsertEntity({ root: projectRoot, id: "person:install", kind: "person", privacy: "shared" });
176
+ await graph.upsertEntity({ root: projectRoot, id: "project:install", kind: "project", privacy: "shared" });
177
+ await coordination.createTask({
178
+ root: projectRoot, id: "task:install", actorId: "person:install", assigneeId: "person:install",
179
+ projectId: "project:install", title: "Installed lifecycle check", privacy: "private"
180
+ });
181
+ await continuity.configureContinuity({
182
+ root: projectRoot, config: { enabled: true }, confirmation: "local-user-opt-in"
183
+ });
184
+ } finally {
185
+ if (previous === undefined) delete process.env.AGENTSPINE_STATE_DIR;
186
+ else process.env.AGENTSPINE_STATE_DIR = previous;
187
+ }
188
+ }
189
+
190
+ async function invokeInstalledAttention(pluginRoot, projectRoot, stateRoot, host) {
191
+ await prepareInstalledAttention(pluginRoot, projectRoot, stateRoot);
192
+ const shared = {
193
+ cwd: projectRoot, host, entity_id: "person:install",
194
+ project_id: "project:install", task_id: "task:install", session_id: `session:installed-${host}-attention`
195
+ };
196
+ const captured = await invokeInstalledHook(pluginRoot, projectRoot, stateRoot, host, {
197
+ ...shared, hook_event_name: "UserPromptSubmit", event_id: "install:promise",
198
+ prompt: "I promise to verify the installed lifecycle."
199
+ });
200
+ const restarted = await invokeInstalledHook(pluginRoot, projectRoot, stateRoot, host, {
201
+ ...shared, hook_event_name: "SessionStart", session_id: "install:restart"
202
+ });
203
+ if (captured.capturedAttentionKind !== "promise" || !restarted.attentionKinds.includes("promise")) {
204
+ throw new Error(`${host} installed hooks did not persist and inject an attention event`);
205
+ }
206
+ return { captured: captured.capturedAttentionKind, restarted: restarted.attentionKinds,
207
+ preflight: captured.preflight ? { schema: captured.preflight.schema,
208
+ receiptId: captured.preflight.receiptId, instructions: captured.preflight.briefing?.instructions?.length || 0 } : null };
209
+ }
210
+
211
+ async function prepareInstalledChannelWake(pluginRoot, projectRoot, stateRoot) {
212
+ const previous = process.env.AGENTSPINE_STATE_DIR;
213
+ process.env.AGENTSPINE_STATE_DIR = stateRoot;
214
+ try {
215
+ const graph = await import(pathToFileURL(join(pluginRoot, "src/lib/graph.js")).href);
216
+ const channel = await import(pathToFileURL(join(pluginRoot, "src/lib/channel-runtime.js")).href);
217
+ await graph.upsertEntity({
218
+ root: projectRoot, id: "agent:install-channel", kind: "agent", displayName: "Franz",
219
+ attributes: { language: "de-DE", voice: { warmth: "warm", directness: "clear", length: "concise" } },
220
+ privacy: "shared"
221
+ });
222
+ await graph.upsertEntity({ root: projectRoot, id: "project:install-channel", kind: "project", privacy: "shared" });
223
+ await graph.upsertEntity({ root: projectRoot, id: "group:install-channel", kind: "group", privacy: "shared" });
224
+ await graph.linkEntities({
225
+ root: projectRoot, from: "agent:install-channel", to: "group:install-channel",
226
+ relation: "member-of", privacy: "group"
227
+ });
228
+ await channel.grantChannelBinding({
229
+ root: projectRoot, id: "channel-binding:install", provider: "telegram",
230
+ tenantId: "tenant:install", accountId: "bot:install", chatId: "chat:install", threadId: "topic:install",
231
+ senderIds: ["user:install"], agentId: "agent:install-channel", projectId: "project:install-channel",
232
+ groupId: "group:install-channel", sessionKey: "session:install-channel",
233
+ secretEnv: "AGENTSPINE_INSTALL_CHANNEL_SECRET", outboundSecretEnv: "AGENTSPINE_INSTALL_CHANNEL_SECRET", capabilities: ["receive", "reply"],
234
+ confirmation: "local-owner-confirmed", now: "2032-01-01T00:00:00.000Z"
235
+ });
236
+ const event = {
237
+ schema: channel.CHANNEL_EVENT_SCHEMA, eventId: "telegram:update:install", provider: "telegram",
238
+ tenantId: "tenant:install", accountId: "bot:install", chatId: "chat:install", threadId: "topic:install",
239
+ senderId: "user:install", replyTo: "message:install", observedAt: "2032-01-01T00:00:01.000Z",
240
+ privacy: "group", text: "Bitte antworte im gebundenen Telegram-Thema."
241
+ };
242
+ const secret = "synthetic-installed-channel-secret-32-bytes";
243
+ const signature = `sha256=${createHmac("sha256", secret).update(channel.channelEventSigningPayload(event)).digest("hex")}`;
244
+ await channel.ingestChannelEvent({
245
+ root: projectRoot, event, signature, env: { AGENTSPINE_INSTALL_CHANNEL_SECRET: secret },
246
+ now: "2032-01-01T00:00:02.000Z"
247
+ });
248
+ return { event, secret };
249
+ } finally {
250
+ if (previous === undefined) delete process.env.AGENTSPINE_STATE_DIR;
251
+ else process.env.AGENTSPINE_STATE_DIR = previous;
252
+ }
253
+ }
254
+
255
+ async function invokeInstalledChannelWake(pluginRoot, projectRoot, stateRoot, host) {
256
+ const { event, secret } = await prepareInstalledChannelWake(pluginRoot, projectRoot, stateRoot);
257
+ const result = await invokeInstalledHook(pluginRoot, projectRoot, stateRoot, host, {
258
+ hook_event_name: "SessionStart", cwd: projectRoot, host,
259
+ entity_id: "agent:install-channel", project_id: "project:install-channel",
260
+ group_id: "group:install-channel", session_id: `session:installed-${host}-channel`,
261
+ agent_spine_channel_event: { event_id: event.eventId, provider: event.provider }
262
+ }, { extraEnv: { AGENTSPINE_INSTALL_CHANNEL_SECRET: secret } });
263
+ if (result.channelEvent?.eventId !== event.eventId || result.channelEvent?.chatId !== event.chatId
264
+ || result.channelEvent?.threadId !== event.threadId || result.voiceBrief?.displayName !== "Franz"
265
+ || result.voiceBrief?.profile?.warmth !== "warm") {
266
+ throw new Error(`${host} installed hook did not inject the exact channel event and voice brief`);
267
+ }
268
+ return {
269
+ eventId: result.channelEvent.eventId, provider: result.channelEvent.provider,
270
+ route: [result.channelEvent.chatId, result.channelEvent.threadId],
271
+ voice: { displayName: result.voiceBrief.displayName, profile: result.voiceBrief.profile },
272
+ mcpCalls: 0
273
+ };
274
+ }
275
+
276
+ async function invokeInstalledGateway(pluginRoot, projectRoot, stateRoot) {
277
+ const previous = process.env.AGENTSPINE_STATE_DIR;
278
+ process.env.AGENTSPINE_STATE_DIR = stateRoot;
279
+ try {
280
+ const graph = await import(pathToFileURL(join(pluginRoot, "src/lib/graph.js")).href);
281
+ const persona = await import(pathToFileURL(join(pluginRoot, "src/lib/persona-runtime.js")).href);
282
+ const channel = await import(pathToFileURL(join(pluginRoot, "src/lib/channel-runtime.js")).href);
283
+ const gateway = await import(pathToFileURL(join(pluginRoot, "src/lib/gateway-runtime.js")).href);
284
+ const worker = await import(pathToFileURL(join(pluginRoot, "src/worker.js")).href);
285
+ await graph.upsertEntity({ root: projectRoot, id: "project:installed-gateway", kind: "project", privacy: "shared" });
286
+ const roster = await persona.applyPersonaRoster({
287
+ root: projectRoot,
288
+ bindings: [{
289
+ id: "persona-binding:installed-gateway", authenticator: "host-manifest", issuer: "host:installed",
290
+ tenantId: "tenant:installed-gateway", host: "codex", profileId: "profile:installed-gateway",
291
+ subjectId: "subject:installed-gateway", kind: "agent", displayName: "Installed Franz",
292
+ sourceBinding: ".codex/agents/installed-franz.md", groupId: "group:installed-gateway"
293
+ }, {
294
+ id: "persona-binding:installed-peer", authenticator: "host-manifest", issuer: "host:installed",
295
+ tenantId: "tenant:installed-gateway", host: "codex", profileId: "profile:installed-gateway",
296
+ subjectId: "subject:installed-peer", kind: "bot", displayName: "Installed Peer",
297
+ sourceBinding: ".codex/agents/installed-peer.md", groupId: "group:installed-gateway"
298
+ }],
299
+ confirmation: "local-owner-confirmed", now: "2032-01-02T00:00:00.000Z"
300
+ });
301
+ const agentId = roster.runtime.personas.find((item) => item.bindingId === "persona-binding:installed-gateway").personaId;
302
+ const peerId = roster.runtime.personas.find((item) => item.bindingId === "persona-binding:installed-peer").personaId;
303
+ const relationship = await graph.relationshipContext({ root: projectRoot, entityId: agentId, groupId: "group:installed-gateway" });
304
+ if (!roster.graphReconciled || !relationship.relatedEntities.some((item) => item.id === peerId)) {
305
+ throw new Error("installed persona roster did not materialize its group-scoped team neighborhood");
306
+ }
307
+ await gateway.setGatewayControl({
308
+ root: projectRoot, enabled: true, killSwitch: false,
309
+ confirmation: "local-owner-confirmed", now: "2032-01-02T00:00:00.500Z"
310
+ });
311
+ await channel.grantChannelBinding({
312
+ root: projectRoot, id: "channel-binding:installed-gateway", provider: "telegram",
313
+ tenantId: "tenant:installed-gateway", accountId: "123456789", chatId: "-1001234567890", threadId: "77",
314
+ senderIds: ["888"], agentId, projectId: "project:installed-gateway", groupId: "group:installed-gateway",
315
+ sessionKey: "session:installed-gateway", secretEnv: "AGENTSPINE_INSTALLED_GATEWAY_SECRET",
316
+ outboundSecretEnv: "AGENTSPINE_INSTALLED_GATEWAY_TOKEN", capabilities: ["receive", "reply"],
317
+ confirmation: "local-owner-confirmed", now: "2032-01-02T00:00:01.000Z"
318
+ });
319
+ const event = {
320
+ schema: channel.CHANNEL_EVENT_SCHEMA, eventId: "telegram:update:installed-gateway", provider: "telegram",
321
+ tenantId: "tenant:installed-gateway", accountId: "123456789", chatId: "-1001234567890", threadId: "77",
322
+ senderId: "888", replyTo: "990", observedAt: "2032-01-02T00:00:02.000Z", privacy: "group",
323
+ text: "Bitte führe den installierten Gateway-Test aus."
324
+ };
325
+ const secret = "synthetic-installed-gateway-secret-32-bytes";
326
+ await channel.ingestChannelEvent({
327
+ root: projectRoot, event,
328
+ signature: `sha256=${createHmac("sha256", secret).update(channel.channelEventSigningPayload(event)).digest("hex")}`,
329
+ env: { AGENTSPINE_INSTALLED_GATEWAY_SECRET: secret }, now: "2032-01-02T00:00:03.000Z"
330
+ });
331
+ let hostStart = null; let delivered = null;
332
+ const result = await worker.runWorkerTick({
333
+ root: projectRoot, workerId: "gateway-worker:installed", now: "2032-01-02T00:00:04.000Z",
334
+ env: { AGENTSPINE_STATE_DIR: stateRoot },
335
+ hostRunner: async (item) => { hostStart = item.channelStart; return { text: "Installierter Gateway-Test bestanden." }; },
336
+ adapter: { send: async (outbox) => { delivered = outbox; return { ok: true, receipt: "telegram-message:991" }; } }
337
+ });
338
+ const channelState = await channel.loadChannelRuntime(projectRoot);
339
+ if (result.status !== "delivered" || channelState.runtime.events.at(-1)?.status !== "completed"
340
+ || hostStart?.agent_spine_channel_event?.event_id !== event.eventId
341
+ || delivered?.chatId !== event.chatId || delivered?.threadId !== event.threadId || delivered?.replyTo !== event.replyTo) {
342
+ throw new Error("installed gateway did not complete the exact channel wake and delivery path: " + JSON.stringify({
343
+ result, channelStatus: channelState.runtime.events.at(-1)?.status || null,
344
+ hostEvent: hostStart?.agent_spine_channel_event?.event_id || null,
345
+ route: delivered ? [delivered.chatId, delivered.threadId, delivered.replyTo] : null
346
+ }));
347
+ }
348
+ return { status: result.status, eventId: event.eventId, agentId,
349
+ route: [delivered.chatId, delivered.threadId, delivered.replyTo], teamPeers: 1,
350
+ graphChanges: roster.graphChanges, mcpCalls: 0 };
351
+ } finally {
352
+ if (previous === undefined) delete process.env.AGENTSPINE_STATE_DIR;
353
+ else process.env.AGENTSPINE_STATE_DIR = previous;
354
+ }
355
+ }
356
+
357
+ export async function checkInstall(root = process.cwd()) {
358
+ root = resolve(root);
359
+ const currentVersion = JSON.parse(await readFile(join(root, "package.json"), "utf8")).version;
360
+ const workspace = await mkdtemp(join(tmpdir(), "agentspine-install-check-"));
361
+ try {
362
+ const userProject = join(workspace, "user-project");
363
+ const source = join(userProject, "SOUL.md");
364
+ await mkdir(userProject, { recursive: true });
365
+ await writeFile(source, "# Existing soul\n\nNever modify me.\n", "utf8");
366
+ await writeFile(join(userProject, "CLAUDE.md"), "# Installed Claude rules\n\nLoad this before every answer.\n", "utf8");
367
+ await writeFile(join(userProject, "AGENTS.md"), "# Installed Codex rules\n\nLoad this before every answer.\n", "utf8");
368
+ const protectedInstallSources = [source, join(userProject, "CLAUDE.md"), join(userProject, "AGENTS.md")];
369
+ const sourceHashes = new Map(await Promise.all(protectedInstallSources.map(async (path) => [path, hash(await readFile(path))])));
370
+
371
+ const fresh = join(workspace, "fresh", "agent-spine");
372
+ await copyBundle(root, fresh);
373
+ const freshResult = await checkHosts(fresh);
374
+ const aliasRoot = join(workspace, "fresh-alias");
375
+ await symlink(join(workspace, "fresh"), aliasRoot, process.platform === "win32" ? "junction" : "dir");
376
+ const aliasResult = await checkHosts(join(aliasRoot, "agent-spine"));
377
+ const freshState = join(workspace, "state-fresh");
378
+ const freshHook = await invokeInstalledHook(fresh, userProject, freshState, "claude");
379
+ const freshBlockingProtocols = await invokeInstalledBlockingProtocols(
380
+ fresh, userProject, join(workspace, "state-protocol-fresh")
381
+ );
382
+ const freshBlunPost = await invokeInstalledBlunPostWriteDigest(
383
+ fresh, userProject, join(workspace, "state-blun-post-fresh")
384
+ );
385
+ const freshAttention = await invokeInstalledAttention(fresh, userProject, freshState, "claude");
386
+ const freshSelfstarter = await invokeInstalledSelfstarter(fresh, userProject, freshState, "claude");
387
+ const freshChannelWake = await invokeInstalledChannelWake(fresh, userProject, freshState, "claude");
388
+ const freshGateway = await invokeInstalledGateway(fresh, userProject, freshState);
389
+ const freshAcceptance = await invokeInstalledAcceptance(fresh);
390
+ const freshLiveRoots = await invokeInstalledLiveRoots(fresh, workspace, join(workspace, "state-live-fresh"));
391
+
392
+ const installed = join(workspace, "cache", "agent-spine");
393
+ await copyBundle(root, installed);
394
+ const previousVersion = await makePreviousCache(installed);
395
+ let previousReleaseFailure = null;
396
+ try {
397
+ await releaseCheck({ root: installed, tag: `v${previousVersion}`, allowDirty: true, skipPack: true });
398
+ } catch (error) { previousReleaseFailure = error.message; }
399
+ if (previousReleaseFailure !== "Codex release manifest must select hooks/codex.json") {
400
+ throw new Error(`previous cache failed release validation for an unexpected reason: ${previousReleaseFailure}`);
401
+ }
402
+ let previousHostFailure = null;
403
+ try { await checkHosts(installed); } catch (error) { previousHostFailure = error.message; }
404
+ if (previousHostFailure !== "Codex manifest must select its host-specific hook adapter") {
405
+ throw new Error(`previous cache failed host validation for an unexpected reason: ${previousHostFailure}`);
406
+ }
407
+
408
+ const staging = `${installed}.${currentVersion}.staging`;
409
+ await copyBundle(root, staging);
410
+ await removeTree(installed);
411
+ await rename(staging, installed);
412
+ const upgraded = await checkHosts(installed);
413
+ const upgradeState = join(workspace, "state-upgrade");
414
+ const upgradedHook = await invokeInstalledHook(installed, userProject, upgradeState, "codex");
415
+ const upgradedBlockingProtocols = await invokeInstalledBlockingProtocols(
416
+ installed, userProject, join(workspace, "state-protocol-upgrade")
417
+ );
418
+ const upgradedBlunPost = await invokeInstalledBlunPostWriteDigest(
419
+ installed, userProject, join(workspace, "state-blun-post-upgrade")
420
+ );
421
+ const upgradedAttention = await invokeInstalledAttention(installed, userProject, upgradeState, "codex");
422
+ const upgradedSelfstarter = await invokeInstalledSelfstarter(installed, userProject, upgradeState, "codex");
423
+ const upgradedChannelWake = await invokeInstalledChannelWake(installed, userProject, upgradeState, "codex");
424
+ const upgradedGateway = await invokeInstalledGateway(installed, userProject, upgradeState);
425
+ const upgradedAcceptance = await invokeInstalledAcceptance(installed);
426
+ const upgradedLiveRoots = await invokeInstalledLiveRoots(installed, join(workspace, "upgrade-live"), join(workspace, "state-live-upgrade"));
427
+ const codexRegistration = await checkCodexInstall(installed, join(workspace, "codex-registration"));
428
+
429
+ await removeTree(fresh);
430
+ await removeTree(installed);
431
+ for (const [path, expected] of sourceHashes) {
432
+ if (hash(await readFile(path)) !== expected) throw new Error("install or uninstall changed an existing source Markdown file");
433
+ }
434
+ return {
435
+ ok: true,
436
+ version: upgraded.version,
437
+ fresh: freshResult.exactlyOnce,
438
+ upgrade: upgraded.exactlyOnce,
439
+ automaticBriefing: { fresh: freshHook, upgrade: upgradedHook },
440
+ blockingProtocols: { fresh: freshBlockingProtocols, upgrade: upgradedBlockingProtocols },
441
+ blunPostWriteDigest: { fresh: freshBlunPost, upgrade: upgradedBlunPost },
442
+ automaticAttention: { fresh: freshAttention, upgrade: upgradedAttention },
443
+ automaticSelfstarter: { fresh: freshSelfstarter, upgrade: upgradedSelfstarter },
444
+ automaticChannelWake: { fresh: freshChannelWake, upgrade: upgradedChannelWake },
445
+ automaticGateway: { fresh: freshGateway, upgrade: upgradedGateway },
446
+ visibleAcceptance: { fresh: freshAcceptance, upgrade: upgradedAcceptance },
447
+ liveRootResolution: { fresh: freshLiveRoots, upgrade: upgradedLiveRoots },
448
+ codexRegistration,
449
+ canonicalAliasLaunch: aliasResult.ok,
450
+ previousCacheRejected: true,
451
+ previousCache: { version: previousVersion,
452
+ releaseContractFailure: previousReleaseFailure, hostContractFailure: previousHostFailure },
453
+ uninstallPreservedSources: true,
454
+ authority: "installation-check-only"
455
+ };
456
+ } finally {
457
+ await removeTree(workspace, { force: true });
458
+ }
459
+ }
460
+
461
+ async function main() {
462
+ const args = process.argv.slice(2);
463
+ let root = process.cwd();
464
+ let pretty = false;
465
+ for (let index = 0; index < args.length; index += 1) {
466
+ if (args[index] === "--root") root = args[++index];
467
+ else if (args[index] === "--json") pretty = true;
468
+ else throw new Error(`unknown install-check argument: ${args[index]}`);
469
+ }
470
+ process.stdout.write(`${JSON.stringify(await checkInstall(root), null, pretty ? 2 : 0)}\n`);
471
+ }
472
+
473
+ if (process.argv[1] && resolve(process.argv[1]) === resolve(fileURLToPath(import.meta.url))) {
474
+ main().catch((error) => {
475
+ process.stderr.write(`AgentSpine install check failed: ${error.message}\n`);
476
+ process.exitCode = 1;
477
+ });
478
+ }