blun-king-cli 9.1.567 → 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 +7 -56
  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,269 @@
1
+ import { runAudit } from "./audit.js";
2
+ import {
3
+ attentionContext, configureAttention, deleteAttention,
4
+ recordActivity, resolveAttention, upsertAttention
5
+ } from "./attention.js";
6
+ import { sessionBriefing } from "./briefing.js";
7
+ import { deliveryKnowledgeQuery } from "./delivery-knowledge.js";
8
+ import { advisoryDeliveryUse } from "./hook-briefing-use.js";
9
+ import { recordDeliveryBriefingUse, recordDeliveryKnowledgeUse,
10
+ verifyDeliveryAgentUse } from "./delivery-agent-usage.js";
11
+ import { scanAndSave, verifyCatalog } from "./catalog.js";
12
+ import { checkDelegation, createTask, taskContext, updateTask } from "./coordination.js";
13
+ import { readDocument, resolveContext } from "./context.js";
14
+ import { recordDeliveryPremortem } from "./delivery-premortem.js";
15
+ import { recoverDeliveryPremortem } from "./delivery-premortem-correction.js";
16
+ import { completeDelivery } from "./mcp-delivery-completion.js";
17
+ import {
18
+ annotateDocument, linkDocuments, linkEntities,
19
+ relationshipContext, upsertEntity
20
+ } from "./graph.js";
21
+ import {
22
+ addLearningEvidence, configureLearning, deleteLearning, evaluateLearning,
23
+ learningContext, learningOutcomeStatus, proposeLearning, reviewLearning, rollbackLearning
24
+ } from "./learning.js";
25
+ import { sharedContext } from "./sharing.js";
26
+ import { recordWorldAssertion, worldContext } from "./world-model.js";
27
+ import { indexSessionTimeline, searchSessionTimeline } from "./session-timeline.js";
28
+ import { gatewayEnvironmentContext } from "./hook-context.js";
29
+ import {
30
+ timelineInvocationInput, timelineInvocationRequest
31
+ } from "./mcp-timeline-tools.js";
32
+ import { sessionTimelineBinding } from "./session-timeline-contract.js";
33
+ import { timelineTransportDigest } from "./session-timeline-transport.js";
34
+ import { runtimeHostForTimeline, timelineHostHome } from "./session-timeline-provider.js";
35
+ import { boundBriefingArguments, rejectInternalSourceArguments, resolveMcpSources } from "./mcp-source-context.js";
36
+ import { evaluateAutonomyAction, projectPortfolioContext, recordProjectObservation } from "./autonomy.js";
37
+
38
+ function textResult(value, isError = false) {
39
+ return { content: [{ type: "text", text: JSON.stringify(value, null, 2) }], isError };
40
+ }
41
+
42
+ function timelineTransport(input, root, environment) {
43
+ const { scope } = input;
44
+ const host = input.request.host || "claude";
45
+ return { scope, host, transportDigest: timelineTransportDigest({ root,
46
+ binding: sessionTimelineBinding({ host, sessionId: input.request.sessionId, scope }), environment }) };
47
+ }
48
+
49
+ function absent(value) { return value === undefined || value === null || value === ""; }
50
+
51
+ // The MCP process can outlive a host turn. A group claim that appears after a
52
+ // private permit was issued must therefore suppress consumption here as well
53
+ // as in PreToolUse; tool arguments cannot clear an authenticated environment.
54
+ function runtimeTimelineGroup(environment, input) {
55
+ try {
56
+ const gateway = gatewayEnvironmentContext(environment);
57
+ return Boolean(input?.groupClaim) || !absent(environment?.AGENTSPINE_GROUP_ID) || !absent(gateway?.groupId);
58
+ } catch {
59
+ return true;
60
+ }
61
+ }
62
+
63
+ function runtimeGatewayMatchesTimeline(environment, scope, host) {
64
+ let gateway;
65
+ try { gateway = gatewayEnvironmentContext(environment); }
66
+ catch { return false; }
67
+ if (!gateway) return true;
68
+ if (!absent(gateway.groupId)) return false;
69
+ const claims = [
70
+ [gateway.host, runtimeHostForTimeline(host)], [gateway.entityId, scope.entityId], [gateway.projectId, scope.projectId],
71
+ [gateway.taskId, scope.currentTaskId], [gateway.goalId, scope.goalId], [gateway.goalStepId, scope.goalStepId]
72
+ ];
73
+ if (!claims.every(([actual, expected]) => actual === expected)) return false;
74
+ // Gateway context predates these two claims. In an authenticated gateway
75
+ // process, accepting an unverifiable user or tenant would let a stale private
76
+ // permit cross a newly selected identity, so their canonical env claims are
77
+ // deliberately mandatory until the gateway context carries them itself.
78
+ return environment?.AGENTSPINE_USER_ID === scope.userId
79
+ && environment?.AGENTSPINE_TENANT_ID === scope.tenantId;
80
+ }
81
+
82
+ function unavailableTimelineInvocation(reason) {
83
+ return { status: "unavailable", reason, authority: "context-only" };
84
+ }
85
+
86
+ async function callTool(name, args = {}, environment = process.env) {
87
+ const root = args.root || process.cwd();
88
+ if (name === "scan") return textResult(await scanAndSave(root));
89
+ if (name === "resolve_context") {
90
+ rejectInternalSourceArguments(args);
91
+ const sources = await resolveMcpSources({ root, cwd: args.cwd || root, host: args.host });
92
+ return textResult(await resolveContext({
93
+ root,
94
+ cwd: args.cwd || root,
95
+ host: sources.host,
96
+ maxBytes: args.maxBytes ?? 65536,
97
+ includeContent: args.includeContent ?? true,
98
+ catalog: sources.catalog
99
+ }));
100
+ }
101
+ if (name === "session_briefing") {
102
+ const scoped = await boundBriefingArguments({ ...args, root });
103
+ const sources = await resolveMcpSources({ ...scoped, required: Boolean(args.requirementId) });
104
+ const briefing = await sessionBriefing({ ...scoped, host: sources.host,
105
+ catalog: sources.catalog, userStateRoot: sources.userStateRoot, sourceDiagnostics: sources.diagnostics });
106
+ if (!args.requirementId) return textResult(briefing);
107
+ const receipt = await recordDeliveryBriefingUse({ root,
108
+ requirementId: args.requirementId, input: args, result: briefing });
109
+ return textResult({ ...briefing, deliveryUseReceipt: advisoryDeliveryUse(receipt) });
110
+ }
111
+ if (name === "delivery_knowledge_query") {
112
+ rejectInternalSourceArguments(args);
113
+ const knowledge = await deliveryKnowledgeQuery({ ...args, root });
114
+ if (knowledge.blocked) return textResult(knowledge, true);
115
+ const receipt = await recordDeliveryKnowledgeUse({ root,
116
+ requirementId: args.requirementId, input: args, result: knowledge });
117
+ return textResult({ ...knowledge, deliveryUseReceipt: advisoryDeliveryUse(receipt) });
118
+ }
119
+ if (name === "read_document") {
120
+ rejectInternalSourceArguments(args);
121
+ const sources = await resolveMcpSources({ root, host: args.host });
122
+ return textResult(await readDocument({
123
+ root, path: args.path, offset: args.offset ?? 0, length: args.length ?? 65536,
124
+ catalog: sources.catalog
125
+ }));
126
+ }
127
+ if (name === "verify") return textResult(await verifyCatalog(root));
128
+ if (name === "link_documents") return textResult(await linkDocuments({ ...args, root }));
129
+ if (name === "annotate_document") return textResult(await annotateDocument({ ...args, root }));
130
+ if (name === "upsert_entity") return textResult(await upsertEntity({ ...args, root }));
131
+ if (name === "link_entities") return textResult(await linkEntities({ ...args, root }));
132
+ if (name === "relationship_context") return textResult(await relationshipContext({ ...args, root }));
133
+ if (name === "upsert_attention") return textResult(await upsertAttention({ ...args, root }));
134
+ if (name === "record_activity") return textResult(await recordActivity({ ...args, root }));
135
+ if (name === "attention_context") return textResult(await attentionContext({ ...args, root }));
136
+ if (name === "resolve_attention") return textResult(await resolveAttention({ ...args, root }));
137
+ if (name === "configure_attention") return textResult(await configureAttention({ ...args, root }));
138
+ if (name === "delete_attention") return textResult(await deleteAttention({ ...args, root }));
139
+ if (name === "propose_learning") return textResult(await proposeLearning({ ...args, root }));
140
+ if (name === "add_learning_evidence") return textResult(await addLearningEvidence({ ...args, root }));
141
+ if (name === "review_learning") return textResult(await reviewLearning({ ...args, root }));
142
+ if (name === "learning_context") return textResult(await learningContext({ ...args, root }));
143
+ if (name === "learning_outcome_status") return textResult(await learningOutcomeStatus({ ...args, root }));
144
+ if (name === "evaluate_learning") return textResult(await evaluateLearning({ ...args, root }));
145
+ if (name === "rollback_learning") return textResult(await rollbackLearning({ ...args, root }));
146
+ if (name === "configure_learning") return textResult(await configureLearning({ ...args, root }));
147
+ if (name === "delete_learning") return textResult(await deleteLearning({ ...args, root }));
148
+ if (name === "check_delegation") return textResult(await checkDelegation({ ...args, root }));
149
+ if (name === "create_task") return textResult(await createTask({ ...args, root }));
150
+ if (name === "update_task") return textResult(await updateTask({ ...args, root }));
151
+ if (name === "task_context") return textResult(await taskContext({ ...args, root }));
152
+ if (name === "shared_context") return textResult(await sharedContext({ ...args, root }));
153
+ if (name === "record_world_assertion") return textResult(await recordWorldAssertion({ ...args, root }));
154
+ if (name === "world_context") return textResult(await worldContext({ ...args, root }));
155
+ if (name === "project_portfolio") return textResult(await projectPortfolioContext({ ...args, root }));
156
+ if (name === "record_project_observation") return textResult(await recordProjectObservation({ ...args, root }));
157
+ if (name === "evaluate_autonomy_action") return textResult(await evaluateAutonomyAction({ ...args, root }));
158
+ if (name === "session_timeline_index") {
159
+ const input = timelineInvocationInput(args);
160
+ if (runtimeTimelineGroup(environment, input)) return textResult(unavailableTimelineInvocation("timeline-group-suppressed"));
161
+ if (!input.valid) return textResult(unavailableTimelineInvocation(input.reason || "timeline-scope-invalid"));
162
+ const timeline = timelineTransport(input, root, environment);
163
+ if (!runtimeGatewayMatchesTimeline(environment, timeline.scope, timeline.host)) {
164
+ return textResult(unavailableTimelineInvocation("timeline-gateway-binding-mismatch"));
165
+ }
166
+ const invocationRequest = timelineInvocationRequest("index", args, root);
167
+ if (!invocationRequest) return textResult(unavailableTimelineInvocation("timeline-invocation-unavailable"));
168
+ return textResult(await indexSessionTimeline({ root, host: timeline.host, sessionId: input.request.sessionId, scope: timeline.scope,
169
+ maxBytes: args.maxBytes, enrollmentDigest: input.request.enrollmentDigest,
170
+ hostHome: timelineHostHome(timeline.host, environment), transportDigest: timeline.transportDigest, invocationRequest }));
171
+ }
172
+ if (name === "session_timeline_search") {
173
+ const input = timelineInvocationInput(args);
174
+ if (runtimeTimelineGroup(environment, input)) return textResult(unavailableTimelineInvocation("timeline-group-suppressed"));
175
+ if (!input.valid) return textResult(unavailableTimelineInvocation(input.reason || "timeline-scope-invalid"));
176
+ const timeline = timelineTransport(input, root, environment);
177
+ if (!runtimeGatewayMatchesTimeline(environment, timeline.scope, timeline.host)) {
178
+ return textResult(unavailableTimelineInvocation("timeline-gateway-binding-mismatch"));
179
+ }
180
+ const invocationRequest = timelineInvocationRequest("search", args, root);
181
+ if (!invocationRequest) return textResult(unavailableTimelineInvocation("timeline-invocation-unavailable"));
182
+ return textResult(await searchSessionTimeline({ root, host: timeline.host, sessionId: input.request.sessionId, scope: timeline.scope,
183
+ at: args.at, query: args.query, windowSeconds: args.windowSeconds, includePriorSessions: args.includePriorSessions,
184
+ enrollmentDigest: input.request.enrollmentDigest,
185
+ hostHome: timelineHostHome(timeline.host, environment), transportDigest: timeline.transportDigest,
186
+ invocationRequest }));
187
+ }
188
+ if (name === "audit") return textResult(await runAudit(root));
189
+ if (name === "complete_delivery") {
190
+ const completed = await completeDelivery(args);
191
+ return textResult(completed, completed.blocked || completed.status === "degraded");
192
+ }
193
+ if (name === "record_delivery_premortem") {
194
+ if (typeof args.root !== "string" || !args.root) {
195
+ throw new Error("record_delivery_premortem requires the exact project root");
196
+ }
197
+ const usage = await verifyDeliveryAgentUse({ root: args.root,
198
+ requirementId: args.requirementId });
199
+ if (usage.status === "mismatch") return textResult(usage, true);
200
+ if (usage.blocked || usage.status === "degraded") return textResult({ status: "unverified",
201
+ blocked: false, items: args.items, agentSpineUse: advisoryDeliveryUse(usage),
202
+ completionVerified: false, automaticRetry: false });
203
+ const premortem = await recordDeliveryPremortem({
204
+ root: args.root,
205
+ requirementId: args.requirementId,
206
+ items: args.items
207
+ });
208
+ return textResult({ ...premortem, agentSpineUse: usage }, premortem.blocked);
209
+ }
210
+ if (name === "recover_delivery_premortem") {
211
+ if (typeof args.root !== "string" || !args.root) {
212
+ throw new Error("recover_delivery_premortem requires the exact project root");
213
+ }
214
+ const recovery = await recoverDeliveryPremortem({ root: args.root,
215
+ predecessorRequirementId: args.predecessorRequirementId,
216
+ taskId: args.taskId || null });
217
+ return textResult(recovery, recovery.blocked);
218
+ }
219
+ throw new Error(`Unknown tool: ${name}`);
220
+ }
221
+
222
+ async function dispatch(message, tools, version, environment) {
223
+ const { id, method, params = {} } = message;
224
+ if (method === "initialize") {
225
+ return {
226
+ jsonrpc: "2.0", id,
227
+ result: {
228
+ protocolVersion: params.protocolVersion || "2025-06-18",
229
+ capabilities: { tools: { listChanged: false } },
230
+ serverInfo: { name: "agent-spine", version }
231
+ }
232
+ };
233
+ }
234
+ if (method === "ping") return { jsonrpc: "2.0", id, result: {} };
235
+ if (method === "tools/list") return { jsonrpc: "2.0", id, result: { tools } };
236
+ if (method === "tools/call") {
237
+ try {
238
+ return { jsonrpc: "2.0", id, result: await callTool(params.name, params.arguments, environment) };
239
+ } catch (error) {
240
+ return { jsonrpc: "2.0", id, result: textResult({ error: error.message }, true) };
241
+ }
242
+ }
243
+ if (method?.startsWith("notifications/")) return null;
244
+ return { jsonrpc: "2.0", id, error: { code: -32601, message: `Method not found: ${method}` } };
245
+ }
246
+
247
+ export function startMcpProtocol({ input, output, tools, version, environment = process.env }) {
248
+ input.setEncoding("utf8");
249
+ let buffer = "";
250
+ let queue = Promise.resolve();
251
+ input.on("data", (chunk) => {
252
+ buffer += chunk;
253
+ let newline;
254
+ while ((newline = buffer.indexOf("\n")) >= 0) {
255
+ const line = buffer.slice(0, newline).trim();
256
+ buffer = buffer.slice(newline + 1);
257
+ if (!line) continue;
258
+ queue = queue.then(async () => {
259
+ let response;
260
+ try {
261
+ response = await dispatch(JSON.parse(line), tools, version, environment);
262
+ } catch (error) {
263
+ response = { jsonrpc: "2.0", id: null, error: { code: -32700, message: error.message } };
264
+ }
265
+ if (response) output.write(`${JSON.stringify(response)}\n`);
266
+ });
267
+ }
268
+ });
269
+ }
@@ -0,0 +1,51 @@
1
+ import { canonicalPath, isInside } from "./paths.js";
2
+ import { resolveHostSourceCatalog } from "./source-roots.js";
3
+ import { inspectPremortemState } from "./delivery-premortem.js";
4
+
5
+ const INTERNAL = new Set(["catalog", "userStateRoot", "sourceDiagnostics", "sourceRegistry",
6
+ "env", "input", "memoryHooks", "resolvedSources"]);
7
+
8
+ export function rejectInternalSourceArguments(args) {
9
+ for (const key of INTERNAL) {
10
+ if (Object.hasOwn(args, key)) throw new Error(`unsupported internal source argument: ${key}`);
11
+ }
12
+ }
13
+
14
+ export async function boundBriefingArguments(args) {
15
+ rejectInternalSourceArguments(args);
16
+ if (!args.requirementId) return args;
17
+ const current = await inspectPremortemState({ root: args.root, requirementId: args.requirementId });
18
+ if (current.blocked || current.status === "degraded" || !current.binding) {
19
+ throw new Error("delivery source binding is unavailable; no briefing receipt issued");
20
+ }
21
+ const result = { ...args };
22
+ for (const [key, field] of [["host", "host"], ["entityId", "entityId"],
23
+ ["projectId", "projectId"], ["groupId", "groupId"], ["currentTaskId", "taskId"]]) {
24
+ const expected = current.binding[field];
25
+ if (Object.hasOwn(args, key) && args[key] !== expected) {
26
+ throw new Error(`delivery source ${key} does not match its requirement`);
27
+ }
28
+ result[key] = expected;
29
+ }
30
+ return result;
31
+ }
32
+
33
+ export async function resolveMcpSources({ root, cwd = root,
34
+ host = process.env.AGENTSPINE_HOST || "generic", entityId = null,
35
+ groupId = null, projectId = null, currentTaskId = null, required = false }) {
36
+ const canonicalRoot = await canonicalPath(root);
37
+ const canonicalCwd = await canonicalPath(cwd);
38
+ if (!isInside(canonicalRoot, canonicalCwd)) throw new Error("MCP cwd must remain inside its root");
39
+ // Only the process environment and the locally read registry select sources.
40
+ // No MCP-provided catalog, registry, profile, or state location enters here.
41
+ const sources = await resolveHostSourceCatalog({ host, cwd: canonicalCwd,
42
+ input: { agent_spine_scope: { entity_id: entityId, group_id: groupId,
43
+ project_id: projectId, task_id: currentTaskId } } });
44
+ if (sources.projectRoot !== canonicalRoot) {
45
+ throw new Error("MCP root differs from the resolved host project root");
46
+ }
47
+ if (required && (sources.diagnostics.incomplete || sources.diagnostics.skipped.length)) {
48
+ throw new Error("MCP source discovery is incomplete; no delivery receipt issued");
49
+ }
50
+ return sources;
51
+ }
@@ -0,0 +1,164 @@
1
+ import { TIMELINE_HOSTS } from "./session-timeline-provider.js";
2
+
3
+ const stableId = {
4
+ type: "string",
5
+ pattern: "^[A-Za-z0-9][A-Za-z0-9:_.@/-]{0,127}$"
6
+ };
7
+
8
+ const optionalId = { anyOf: [stableId, { type: "null" }] };
9
+
10
+ // Hosts qualify MCP tools differently. These prefixes must stay exact: a
11
+ // foreign MCP server can expose an identically named tool but must never
12
+ // receive a timeline invocation permit.
13
+ export const AGENTSPINE_TIMELINE_TOOL_PREFIX = "mcp__plugin_agent-spine_agent-spine__";
14
+ export const BLUN_TIMELINE_TOOL_PREFIX = "mcp__agent-spine__";
15
+ export const KING_PLUGIN_TIMELINE_TOOL_PREFIX = "mcp__plugin-agent-spine_agent-spine__";
16
+
17
+ const TIMELINE_TOOL_PREFIXES = [
18
+ AGENTSPINE_TIMELINE_TOOL_PREFIX,
19
+ BLUN_TIMELINE_TOOL_PREFIX,
20
+ KING_PLUGIN_TIMELINE_TOOL_PREFIX
21
+ ];
22
+
23
+ const SCOPE_FIELDS = [
24
+ ["entityId", ["entityId", "entity_id"]],
25
+ ["userId", ["userId", "user_id"]],
26
+ ["tenantId", ["tenantId", "tenant_id"]],
27
+ ["projectId", ["projectId", "project_id"]],
28
+ ["groupId", ["groupId", "group_id"]],
29
+ ["currentTaskId", ["taskId", "task_id", "currentTaskId", "current_task_id"]],
30
+ ["goalId", ["goalId", "goal_id"]],
31
+ ["goalStepId", ["goalStepId", "goal_step_id"]],
32
+ ["timelineVisibility", ["timelineVisibility", "timeline_visibility"]]
33
+ ];
34
+
35
+ const REQUEST_FIELDS = [
36
+ ["host", ["host"]],
37
+ ["sessionId", ["sessionId", "session_id"]],
38
+ ["enrollmentDigest", ["enrollmentDigest", "enrollment_digest"]]
39
+ ];
40
+
41
+ function owns(value, key) {
42
+ return Boolean(value) && Object.prototype.hasOwnProperty.call(value, key);
43
+ }
44
+
45
+ function plainObject(value) {
46
+ return value && typeof value === "object" && !Array.isArray(value)
47
+ && Object.getPrototypeOf(value) === Object.prototype;
48
+ }
49
+
50
+ function absent(value) { return value === undefined || value === null || value === ""; }
51
+
52
+ function containers(args) {
53
+ if (!plainObject(args)) return { valid: false, reason: "timeline-scope-invalid", values: [] };
54
+ if (!owns(args, "agent_spine_scope") || args.agent_spine_scope === null) {
55
+ return { valid: true, reason: null, values: [args] };
56
+ }
57
+ if (!plainObject(args.agent_spine_scope)) {
58
+ return { valid: false, reason: "timeline-scope-invalid", values: [args] };
59
+ }
60
+ return { valid: true, reason: null, values: [args, args.agent_spine_scope] };
61
+ }
62
+
63
+ function claim(values, aliases) {
64
+ const supplied = values.flatMap((value) => aliases.filter((key) => owns(value, key)).map((key) => value[key]));
65
+ const present = supplied.filter((value) => !absent(value));
66
+ if (!present.length) return { valid: true, value: null, present: false };
67
+ if (present.some((value) => typeof value !== "string") || !present.every((value) => value === present[0])) {
68
+ return { valid: false, value: null, present: true };
69
+ }
70
+ return { valid: true, value: present[0], present: true };
71
+ }
72
+
73
+ function validId(value) {
74
+ return value === null || (typeof value === "string" && /^[A-Za-z0-9][A-Za-z0-9:_.@/-]{0,127}$/.test(value));
75
+ }
76
+
77
+ function resolvedClaims(args) {
78
+ const nested = containers(args);
79
+ const values = nested.values;
80
+ const result = { valid: nested.valid, reason: nested.reason, groupClaim: false, scope: {}, request: {} };
81
+ for (const [field, aliases] of [...SCOPE_FIELDS, ...REQUEST_FIELDS]) {
82
+ const resolved = claim(values, aliases);
83
+ if (field === "groupId") result.groupClaim = resolved.present;
84
+ if (!resolved.valid || (field === "host" && resolved.value !== null && !TIMELINE_HOSTS.includes(resolved.value))
85
+ || (field !== "timelineVisibility" && !validId(resolved.value))
86
+ || (field === "timelineVisibility" && resolved.value !== null && resolved.value !== "private-verified")
87
+ || (field === "enrollmentDigest" && resolved.value !== null && !/^[a-f0-9]{64}$/.test(resolved.value))) {
88
+ result.valid = false;
89
+ result.reason ||= "timeline-scope-conflict";
90
+ }
91
+ if (SCOPE_FIELDS.some(([name]) => name === field)) result.scope[field] = resolved.value;
92
+ else result.request[field] = resolved.value;
93
+ }
94
+ return result;
95
+ }
96
+
97
+ // Direct stdio callers can bypass the MCP schema. Merge the flat and nested
98
+ // representations only when every present claim agrees. In particular, a
99
+ // nested group claim stays visible when a top-level argument tries to clear
100
+ // it with null, so no existing private permit crosses into a group turn.
101
+ export function timelineInvocationInput(args = {}) {
102
+ return resolvedClaims(args);
103
+ }
104
+
105
+ export function timelineScope(args = {}) {
106
+ return resolvedClaims(args).scope;
107
+ }
108
+
109
+ export function timelineToolKind(name) {
110
+ const qualified = String(name || "");
111
+ if (TIMELINE_TOOL_PREFIXES.some((prefix) => qualified === `${prefix}session_timeline_index`)) return "index";
112
+ if (TIMELINE_TOOL_PREFIXES.some((prefix) => qualified === `${prefix}session_timeline_search`)) return "search";
113
+ return null;
114
+ }
115
+
116
+ export function timelineInvocationRequest(tool, args, root) {
117
+ const input = resolvedClaims(args);
118
+ if (!input.valid || input.groupClaim || !input.request.sessionId) return null;
119
+ const scope = input.scope;
120
+ const request = { root, tool, sessionId: input.request.sessionId, entityId: scope.entityId, userId: scope.userId,
121
+ tenantId: scope.tenantId, projectId: scope.projectId, groupId: scope.groupId, taskId: scope.currentTaskId,
122
+ goalId: scope.goalId, goalStepId: scope.goalStepId, timelineVisibility: scope.timelineVisibility,
123
+ enrollmentDigest: input.request.enrollmentDigest };
124
+ if (tool === "index") return { ...request, maxBytes: args.maxBytes ?? 4 * 1024 * 1024 };
125
+ return { ...request, at: args.at ?? null, query: args.query ?? null,
126
+ windowSeconds: args.windowSeconds === undefined ? 0 : args.windowSeconds,
127
+ includePriorSessions: args.includePriorSessions === true };
128
+ }
129
+
130
+ const scopeProperties = {
131
+ host: { enum: TIMELINE_HOSTS },
132
+ entityId: stableId, userId: stableId, tenantId: stableId, projectId: stableId,
133
+ taskId: stableId, goalId: optionalId, goalStepId: optionalId
134
+ };
135
+
136
+ export const sessionTimelineTools = [
137
+ {
138
+ name: "session_timeline_index",
139
+ description: "Index bounded evidence from one explicitly enrolled immutable provider transcript snapshot. Claude, Codex and King use separate format adapters; changed sources require a renewed host receipt and this tool never copies a transcript or grants authority.",
140
+ inputSchema: {
141
+ type: "object", additionalProperties: false,
142
+ required: [],
143
+ properties: { root: { type: "string", minLength: 1 }, sessionId: stableId, ...scopeProperties,
144
+ enrollmentDigest: { type: "string", pattern: "^[a-f0-9]{64}$" },
145
+ timelineVisibility: { const: "private-verified" }, groupId: { anyOf: [stableId, { type: "null" }] },
146
+ maxBytes: { type: "integer", minimum: 65536, maximum: 16777216 } }
147
+ }
148
+ },
149
+ {
150
+ name: "session_timeline_search",
151
+ description: "Search the current or a same-task prior explicitly enrolled immutable Claude, Codex or King source for redacted objective evidence by exact UTC time or at least two concrete terms. Prior-session search opens only one indexed source and never grants authority.",
152
+ inputSchema: {
153
+ type: "object", additionalProperties: false,
154
+ required: [],
155
+ anyOf: [{ required: ["at"] }, { required: ["query"] }],
156
+ properties: { root: { type: "string", minLength: 1 }, sessionId: stableId, ...scopeProperties,
157
+ enrollmentDigest: { type: "string", pattern: "^[a-f0-9]{64}$" },
158
+ timelineVisibility: { const: "private-verified" }, groupId: { anyOf: [stableId, { type: "null" }] },
159
+ at: { type: "string", format: "date-time" }, query: { type: "string", minLength: 3, maxLength: 512 },
160
+ windowSeconds: { type: "integer", minimum: 0, maximum: 900 },
161
+ includePriorSessions: { type: "boolean" } }
162
+ }
163
+ }
164
+ ];
@@ -0,0 +1,52 @@
1
+ const stableId = {
2
+ type: "string",
3
+ pattern: "^[a-z][a-z0-9-]{0,31}:[A-Za-z0-9][A-Za-z0-9._/-]{0,190}$"
4
+ };
5
+
6
+ const nullableStableId = { anyOf: [stableId, { type: "null" }] };
7
+
8
+ export const worldModelTools = [
9
+ {
10
+ name: "record_world_assertion",
11
+ description: "Persist one provenance-bound world, user, relationship, or team assertion outside source files. Optional typed knowledge distinguishes facts, user preferences, decisions, task state, and error lessons. Measurements and explicit user feedback become context-only facts; model output remains a proposal. Authority predicates and secret-shaped structured knowledge are rejected.",
12
+ inputSchema: {
13
+ type: "object", additionalProperties: false,
14
+ required: ["id", "subjectId", "predicate", "value", "evidenceKind", "evidenceId", "evidenceDigest", "observedAt"],
15
+ properties: {
16
+ root: { type: "string" }, id: stableId, subjectId: stableId,
17
+ predicate: { type: "string", pattern: "^[a-z][a-z0-9.-]{0,127}$" },
18
+ value: {},
19
+ evidenceKind: { type: "string", enum: ["objective-measurement", "explicit-user-feedback", "model-suggestion"] },
20
+ evidenceId: stableId,
21
+ evidenceDigest: { type: "string", pattern: "^[a-f0-9]{64}$" },
22
+ knowledgeKind: {
23
+ type: "string",
24
+ enum: ["fact", "user-preference", "decision", "task-state", "error-lesson"]
25
+ },
26
+ sessionRef: { type: "string", pattern: "^session-ref:[a-f0-9]{32}$" },
27
+ messageRef: stableId,
28
+ observedAt: { type: "string", format: "date-time" },
29
+ expiresAt: { anyOf: [{ type: "string", format: "date-time" }, { type: "null" }] },
30
+ projectId: nullableStableId, groupId: nullableStableId,
31
+ privacy: { type: "string", enum: ["private", "shared", "group"] },
32
+ supersedes: { type: "array", maxItems: 50, uniqueItems: true, items: stableId },
33
+ reason: { type: "string", maxLength: 500 }
34
+ }
35
+ }
36
+ },
37
+ {
38
+ name: "world_context",
39
+ description: "Read privacy-filtered world context. Conflicts, expiry, and suggestions remain separate; continuationTaskId adds bounded, sourced task-relevant knowledge.",
40
+ inputSchema: {
41
+ type: "object", additionalProperties: false,
42
+ properties: {
43
+ root: { type: "string" }, subjectId: nullableStableId,
44
+ projectId: nullableStableId, groupId: nullableStableId,
45
+ includePrivate: { type: "boolean" }, includeKnowledgeHistory: { type: "boolean" },
46
+ continuationTaskId: nullableStableId,
47
+ maxItems: { type: "integer", minimum: 1, maximum: 500 },
48
+ now: { type: "string", format: "date-time" }
49
+ }
50
+ }
51
+ }
52
+ ];
@@ -35,9 +35,10 @@ function sameFile(left, right) {
35
35
  && left.mtimeMs === right.mtimeMs && left.ctimeMs === right.ctimeMs;
36
36
  }
37
37
 
38
- async function removeStaleLock(path, staleAfterMs) {
38
+ async function removeStaleLock(path, staleAfterMs, assertPath = null) {
39
39
  let before;
40
40
  try {
41
+ await assertPath?.();
41
42
  before = await stat(path);
42
43
  } catch (error) {
43
44
  if (isTransientLockMetadataError(error)) return false;
@@ -47,6 +48,7 @@ async function removeStaleLock(path, staleAfterMs) {
47
48
  await readOwner(path);
48
49
  let after;
49
50
  try {
51
+ await assertPath?.();
50
52
  after = await stat(path);
51
53
  } catch (error) {
52
54
  if (isTransientLockMetadataError(error)) return false;
@@ -54,7 +56,9 @@ async function removeStaleLock(path, staleAfterMs) {
54
56
  }
55
57
  if (!sameFile(before, after) || Date.now() - after.mtimeMs <= staleAfterMs) return false;
56
58
  try {
59
+ await assertPath?.();
57
60
  await unlink(path);
61
+ await assertPath?.();
58
62
  return true;
59
63
  } catch (error) {
60
64
  if (isFileLockContention(error) || isTransientLockMetadataError(error)) return false;
@@ -66,7 +70,8 @@ export async function withOwnedFileLock(path, task, {
66
70
  staleAfterMs = 15000,
67
71
  heartbeatIntervalMs = 1000,
68
72
  retryDelayMs = 25,
69
- maxAttempts = 80
73
+ maxAttempts = 80,
74
+ assertPath = null
70
75
  } = {}) {
71
76
  if (typeof task !== "function") throw new Error("owned file lock requires a task");
72
77
  if (!Number.isInteger(staleAfterMs) || staleAfterMs < 50
@@ -78,13 +83,16 @@ export async function withOwnedFileLock(path, task, {
78
83
  }
79
84
  const token = randomUUID();
80
85
  const acquiredAt = new Date().toISOString();
81
- let acquired = false;
86
+ let acquired = false; let recovered = false;
82
87
  for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
83
- let handle;
88
+ let handle; let created = false;
84
89
  try {
90
+ await assertPath?.();
85
91
  handle = await open(path, "wx", 0o600);
92
+ created = true;
86
93
  const payload = `${JSON.stringify(lockPayload(token, acquiredAt, staleAfterMs))}\n`;
87
94
  await handle.writeFile(payload, "utf8");
95
+ await assertPath?.();
88
96
  acquired = true;
89
97
  break;
90
98
  } catch (error) {
@@ -92,13 +100,16 @@ export async function withOwnedFileLock(path, task, {
92
100
  if (handle) {
93
101
  await handle.close();
94
102
  handle = null;
95
- await unlink(path).catch((cleanupError) => {
103
+ try {
104
+ await assertPath?.();
105
+ if (created) await unlink(path);
106
+ } catch (cleanupError) {
96
107
  if (cleanupError.code !== "ENOENT") error.cleanupError = cleanupError;
97
- });
108
+ }
98
109
  }
99
110
  throw error;
100
111
  }
101
- await removeStaleLock(path, staleAfterMs);
112
+ recovered ||= await removeStaleLock(path, staleAfterMs, assertPath);
102
113
  if (attempt + 1 < maxAttempts) await delay(retryDelayMs);
103
114
  } finally {
104
115
  await handle?.close();
@@ -110,7 +121,9 @@ export async function withOwnedFileLock(path, task, {
110
121
  let heartbeat = Promise.resolve();
111
122
  const assertOwned = async () => {
112
123
  if (ownershipError) throw ownershipError;
124
+ await assertPath?.();
113
125
  const owner = await readOwner(path);
126
+ await assertPath?.();
114
127
  if (!owner || owner.token !== token) {
115
128
  ownershipError = new Error("state lock ownership was lost; mutation aborted");
116
129
  throw ownershipError;
@@ -120,6 +133,7 @@ export async function withOwnedFileLock(path, task, {
120
133
  await assertOwned();
121
134
  const now = new Date();
122
135
  await utimes(path, now, now);
136
+ await assertPath?.();
123
137
  };
124
138
  const timer = setInterval(() => {
125
139
  heartbeat = heartbeat.then(renew).catch((error) => { ownershipError ||= error; });
@@ -127,15 +141,21 @@ export async function withOwnedFileLock(path, task, {
127
141
  timer.unref?.();
128
142
 
129
143
  try {
130
- const result = await task({ token, acquiredAt, assertOwned });
144
+ const result = await task({ token, acquiredAt, recovered, assertOwned });
131
145
  await assertOwned();
132
146
  return result;
133
147
  } finally {
134
148
  clearInterval(timer);
135
149
  await heartbeat;
136
- const owner = await readOwner(path).catch(() => null);
150
+ const owner = await (async () => {
151
+ await assertPath?.();
152
+ return readOwner(path);
153
+ })().catch(() => null);
137
154
  if (owner?.token === token) {
138
- await unlink(path).catch((error) => {
155
+ await (async () => {
156
+ await assertPath?.();
157
+ await unlink(path);
158
+ })().catch((error) => {
139
159
  if (error.code !== "ENOENT") throw error;
140
160
  });
141
161
  }