blun-king-cli 9.1.567 → 9.1.570

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 (263) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/agent-spine-plugin/.claude-plugin/marketplace.json +1 -1
  3. package/agent-spine-plugin/.claude-plugin/plugin.json +1 -1
  4. package/agent-spine-plugin/.codex-plugin/plugin.json +2 -1
  5. package/agent-spine-plugin/CHANGELOG.md +1581 -0
  6. package/agent-spine-plugin/README.md +30 -4
  7. package/agent-spine-plugin/blun.plugin.json +3 -3
  8. package/agent-spine-plugin/docs/acceptance.md +61 -0
  9. package/agent-spine-plugin/docs/assignment-continuation.md +48 -0
  10. package/agent-spine-plugin/docs/host-integration.md +178 -0
  11. package/agent-spine-plugin/docs/preflight-recall.md +69 -0
  12. package/agent-spine-plugin/docs/preservation-contract.md +53 -0
  13. package/agent-spine-plugin/docs/quality-gates.md +50 -0
  14. package/agent-spine-plugin/docs/releasing.md +85 -0
  15. package/agent-spine-plugin/docs/session-timeline.md +251 -0
  16. package/agent-spine-plugin/docs/source-roots.md +113 -0
  17. package/agent-spine-plugin/docs/structured-completion.md +67 -0
  18. package/agent-spine-plugin/docs/world-model.md +94 -0
  19. package/agent-spine-plugin/hooks/codex.json +2 -2
  20. package/agent-spine-plugin/hooks/hooks.json +1 -1
  21. package/agent-spine-plugin/hooks/version.json +1 -1
  22. package/agent-spine-plugin/package.json +13 -3
  23. package/agent-spine-plugin/scripts/check-codex-install.js +226 -0
  24. package/agent-spine-plugin/scripts/check-hosts.js +14 -5
  25. package/agent-spine-plugin/scripts/check-install-hook.js +226 -0
  26. package/agent-spine-plugin/scripts/check-install-selfstarter.js +154 -0
  27. package/agent-spine-plugin/scripts/check-install.js +478 -0
  28. package/agent-spine-plugin/scripts/check-line-budget.js +58 -0
  29. package/agent-spine-plugin/scripts/check-syntax.js +29 -0
  30. package/agent-spine-plugin/scripts/github-actions.js +11 -0
  31. package/agent-spine-plugin/scripts/hermetic-process.js +183 -0
  32. package/agent-spine-plugin/scripts/release-check.js +145 -0
  33. package/agent-spine-plugin/scripts/run-acceptance.js +19 -0
  34. package/agent-spine-plugin/scripts/run-checks.js +47 -0
  35. package/agent-spine-plugin/scripts/run-tests-hermetic.js +89 -0
  36. package/agent-spine-plugin/skills/agent-spine/SKILL.md +16 -2
  37. package/agent-spine-plugin/spine-example/1-identity.md +12 -0
  38. package/agent-spine-plugin/spine-example/2-voice.md +6 -0
  39. package/agent-spine-plugin/spine-example/3-conduct.md +8 -0
  40. package/agent-spine-plugin/spine-example/4-history.md +4 -0
  41. package/agent-spine-plugin/src/cli-agent.js +296 -0
  42. package/agent-spine-plugin/src/cli-attention.js +95 -0
  43. package/agent-spine-plugin/src/cli-autonomy.js +36 -0
  44. package/agent-spine-plugin/src/cli-common.js +71 -0
  45. package/agent-spine-plugin/src/cli-continuity.js +116 -0
  46. package/agent-spine-plugin/src/cli-core.js +128 -0
  47. package/agent-spine-plugin/src/cli-diagnostics.js +291 -0
  48. package/agent-spine-plugin/src/cli-host.js +21 -0
  49. package/agent-spine-plugin/src/cli-learning.js +305 -0
  50. package/agent-spine-plugin/src/cli-premortem.js +16 -0
  51. package/agent-spine-plugin/src/cli-sharing.js +230 -0
  52. package/agent-spine-plugin/src/cli.js +40 -1350
  53. package/agent-spine-plugin/src/codex-reader-launcher.js +182 -0
  54. package/agent-spine-plugin/src/hook.js +185 -567
  55. package/agent-spine-plugin/src/index.js +16 -2
  56. package/agent-spine-plugin/src/lib/acceptance.js +113 -11
  57. package/agent-spine-plugin/src/lib/action-lesson-recall.js +53 -0
  58. package/agent-spine-plugin/src/lib/attention-context.js +167 -0
  59. package/agent-spine-plugin/src/lib/attention-events.js +113 -0
  60. package/agent-spine-plugin/src/lib/attention-privacy.js +72 -0
  61. package/agent-spine-plugin/src/lib/attention-schema.js +164 -0
  62. package/agent-spine-plugin/src/lib/attention-storage.js +93 -0
  63. package/agent-spine-plugin/src/lib/attention.js +9 -600
  64. package/agent-spine-plugin/src/lib/audit-premortem.js +223 -0
  65. package/agent-spine-plugin/src/lib/audit.js +56 -6
  66. package/agent-spine-plugin/src/lib/autonomy-policy.js +110 -0
  67. package/agent-spine-plugin/src/lib/autonomy-store.js +202 -0
  68. package/agent-spine-plugin/src/lib/autonomy.js +8 -0
  69. package/agent-spine-plugin/src/lib/briefing.js +67 -4
  70. package/agent-spine-plugin/src/lib/catalog-document-read.js +51 -0
  71. package/agent-spine-plugin/src/lib/catalog.js +1 -1
  72. package/agent-spine-plugin/src/lib/codex-installation.js +231 -0
  73. package/agent-spine-plugin/src/lib/codex-skill-installation.js +211 -0
  74. package/agent-spine-plugin/src/lib/context.js +3 -1
  75. package/agent-spine-plugin/src/lib/delivery-agent-usage.js +224 -0
  76. package/agent-spine-plugin/src/lib/delivery-assignment.js +220 -0
  77. package/agent-spine-plugin/src/lib/delivery-command-actions.js +453 -0
  78. package/agent-spine-plugin/src/lib/delivery-knowledge.js +78 -0
  79. package/agent-spine-plugin/src/lib/delivery-premortem-binding.js +107 -0
  80. package/agent-spine-plugin/src/lib/delivery-premortem-closure.js +171 -0
  81. package/agent-spine-plugin/src/lib/delivery-premortem-codec.js +45 -0
  82. package/agent-spine-plugin/src/lib/delivery-premortem-correction.js +101 -0
  83. package/agent-spine-plugin/src/lib/delivery-premortem-file.js +21 -0
  84. package/agent-spine-plugin/src/lib/delivery-premortem-index.js +493 -0
  85. package/agent-spine-plugin/src/lib/delivery-premortem-inspection.js +37 -0
  86. package/agent-spine-plugin/src/lib/delivery-premortem-recovery.js +120 -0
  87. package/agent-spine-plugin/src/lib/delivery-premortem-rejection.js +65 -0
  88. package/agent-spine-plugin/src/lib/delivery-premortem-results.js +28 -0
  89. package/agent-spine-plugin/src/lib/delivery-premortem-session-guard.js +46 -0
  90. package/agent-spine-plugin/src/lib/delivery-premortem-write-ledger.js +285 -0
  91. package/agent-spine-plugin/src/lib/delivery-premortem.js +499 -0
  92. package/agent-spine-plugin/src/lib/delivery-shell-heredoc.js +115 -0
  93. package/agent-spine-plugin/src/lib/delivery-shell-substitutions.js +111 -0
  94. package/agent-spine-plugin/src/lib/delivery-shell-wrapper.js +64 -0
  95. package/agent-spine-plugin/src/lib/delivery-target.js +73 -0
  96. package/agent-spine-plugin/src/lib/delivery-verification.js +445 -0
  97. package/agent-spine-plugin/src/lib/documents.js +27 -5
  98. package/agent-spine-plugin/src/lib/filesystem-retry.js +2 -0
  99. package/agent-spine-plugin/src/lib/gateway-common.js +68 -0
  100. package/agent-spine-plugin/src/lib/gateway-control.js +302 -0
  101. package/agent-spine-plugin/src/lib/gateway-delivery.js +103 -0
  102. package/agent-spine-plugin/src/lib/gateway-execution.js +350 -0
  103. package/agent-spine-plugin/src/lib/gateway-host-lifecycle.js +185 -0
  104. package/agent-spine-plugin/src/lib/gateway-inspection.js +85 -0
  105. package/agent-spine-plugin/src/lib/gateway-knowledge.js +129 -0
  106. package/agent-spine-plugin/src/lib/gateway-policy-provenance.js +197 -0
  107. package/agent-spine-plugin/src/lib/gateway-premortem-disposition.js +82 -0
  108. package/agent-spine-plugin/src/lib/gateway-premortem.js +363 -0
  109. package/agent-spine-plugin/src/lib/gateway-runs.js +300 -0
  110. package/agent-spine-plugin/src/lib/gateway-runtime-identity.js +31 -0
  111. package/agent-spine-plugin/src/lib/gateway-runtime-records.js +21 -0
  112. package/agent-spine-plugin/src/lib/gateway-runtime.js +18 -1623
  113. package/agent-spine-plugin/src/lib/gateway-state-transaction.js +356 -0
  114. package/agent-spine-plugin/src/lib/gateway-state.js +342 -0
  115. package/agent-spine-plugin/src/lib/hook-artifact-guards.js +356 -0
  116. package/agent-spine-plugin/src/lib/hook-audit.js +16 -2
  117. package/agent-spine-plugin/src/lib/hook-briefing-use.js +68 -0
  118. package/agent-spine-plugin/src/lib/hook-context.js +424 -0
  119. package/agent-spine-plugin/src/lib/hook-final-message.js +26 -0
  120. package/agent-spine-plugin/src/lib/hook-input.js +27 -0
  121. package/agent-spine-plugin/src/lib/hook-output.js +140 -0
  122. package/agent-spine-plugin/src/lib/hook-premortem.js +257 -0
  123. package/agent-spine-plugin/src/lib/hook-process-advisory.js +37 -0
  124. package/agent-spine-plugin/src/lib/hook-protection.js +95 -0
  125. package/agent-spine-plugin/src/lib/hook-stop-verification.js +84 -0
  126. package/agent-spine-plugin/src/lib/hook-timeline.js +91 -0
  127. package/agent-spine-plugin/src/lib/identifier-analysis.js +446 -0
  128. package/agent-spine-plugin/src/lib/indexed-memory.js +23 -6
  129. package/agent-spine-plugin/src/lib/knowledge-evidence.js +431 -0
  130. package/agent-spine-plugin/src/lib/learning-applications.js +441 -0
  131. package/agent-spine-plugin/src/lib/learning-candidates.js +274 -0
  132. package/agent-spine-plugin/src/lib/learning-context.js +130 -0
  133. package/agent-spine-plugin/src/lib/learning-delivery-contracts.js +310 -0
  134. package/agent-spine-plugin/src/lib/learning-evaluation-contracts.js +350 -0
  135. package/agent-spine-plugin/src/lib/learning-evaluation-registration.js +372 -0
  136. package/agent-spine-plugin/src/lib/learning-evaluation-revocation.js +292 -0
  137. package/agent-spine-plugin/src/lib/learning-evidence-contracts.js +321 -0
  138. package/agent-spine-plugin/src/lib/learning-findings.js +458 -0
  139. package/agent-spine-plugin/src/lib/learning-measurement-contracts.js +285 -0
  140. package/agent-spine-plugin/src/lib/learning-measurements.js +265 -0
  141. package/agent-spine-plugin/src/lib/learning-outcome-contracts.js +165 -0
  142. package/agent-spine-plugin/src/lib/learning-outcomes.js +343 -0
  143. package/agent-spine-plugin/src/lib/learning-reconciliation.js +290 -0
  144. package/agent-spine-plugin/src/lib/learning-retry-contracts.js +188 -0
  145. package/agent-spine-plugin/src/lib/learning-schema.js +229 -0
  146. package/agent-spine-plugin/src/lib/learning-scope-targets.js +424 -0
  147. package/agent-spine-plugin/src/lib/learning-state-upgrade.js +477 -0
  148. package/agent-spine-plugin/src/lib/learning-status-configuration.js +476 -0
  149. package/agent-spine-plugin/src/lib/learning-storage.js +203 -0
  150. package/agent-spine-plugin/src/lib/learning-trial-recovery.js +218 -0
  151. package/agent-spine-plugin/src/lib/learning-validation-contracts.js +375 -0
  152. package/agent-spine-plugin/src/lib/learning-validation-renewal.js +298 -0
  153. package/agent-spine-plugin/src/lib/learning-validation-runtime.js +234 -0
  154. package/agent-spine-plugin/src/lib/learning.js +36 -6923
  155. package/agent-spine-plugin/src/lib/lesson-recall-session.js +172 -0
  156. package/agent-spine-plugin/src/lib/mcp-autonomy-tools.js +37 -0
  157. package/agent-spine-plugin/src/lib/mcp-delivery-completion.js +124 -0
  158. package/agent-spine-plugin/src/lib/mcp-delivery-tools.js +45 -0
  159. package/agent-spine-plugin/src/lib/mcp-premortem.js +68 -0
  160. package/agent-spine-plugin/src/lib/mcp-runtime.js +269 -0
  161. package/agent-spine-plugin/src/lib/mcp-source-context.js +51 -0
  162. package/agent-spine-plugin/src/lib/mcp-timeline-tools.js +164 -0
  163. package/agent-spine-plugin/src/lib/mcp-world-tools.js +52 -0
  164. package/agent-spine-plugin/src/lib/owned-file-lock.js +30 -10
  165. package/agent-spine-plugin/src/lib/project-portfolio.js +176 -0
  166. package/agent-spine-plugin/src/lib/selfstarter-core.js +382 -0
  167. package/agent-spine-plugin/src/lib/selfstarter-jobs.js +149 -0
  168. package/agent-spine-plugin/src/lib/selfstarter-lease.js +204 -0
  169. package/agent-spine-plugin/src/lib/selfstarter-policy.js +90 -0
  170. package/agent-spine-plugin/src/lib/selfstarter-workspace.js +111 -0
  171. package/agent-spine-plugin/src/lib/selfstarter.js +11 -911
  172. package/agent-spine-plugin/src/lib/session-timeline-auth.js +316 -0
  173. package/agent-spine-plugin/src/lib/session-timeline-codex.js +58 -0
  174. package/agent-spine-plugin/src/lib/session-timeline-contract.js +48 -0
  175. package/agent-spine-plugin/src/lib/session-timeline-enrollment-source.js +41 -0
  176. package/agent-spine-plugin/src/lib/session-timeline-enrollment-storage.js +132 -0
  177. package/agent-spine-plugin/src/lib/session-timeline-enrollment-transport.js +17 -0
  178. package/agent-spine-plugin/src/lib/session-timeline-enrollment.js +500 -0
  179. package/agent-spine-plugin/src/lib/session-timeline-event-extract.js +157 -0
  180. package/agent-spine-plugin/src/lib/session-timeline-host-origin.js +74 -0
  181. package/agent-spine-plugin/src/lib/session-timeline-host-receipt.js +117 -0
  182. package/agent-spine-plugin/src/lib/session-timeline-invocation.js +201 -0
  183. package/agent-spine-plugin/src/lib/session-timeline-king.js +79 -0
  184. package/agent-spine-plugin/src/lib/session-timeline-prior.js +59 -0
  185. package/agent-spine-plugin/src/lib/session-timeline-provider.js +34 -0
  186. package/agent-spine-plugin/src/lib/session-timeline-query.js +55 -0
  187. package/agent-spine-plugin/src/lib/session-timeline-results.js +31 -0
  188. package/agent-spine-plugin/src/lib/session-timeline-root.js +11 -0
  189. package/agent-spine-plugin/src/lib/session-timeline-search.js +82 -0
  190. package/agent-spine-plugin/src/lib/session-timeline-sid-acl.js +217 -0
  191. package/agent-spine-plugin/src/lib/session-timeline-source.js +83 -0
  192. package/agent-spine-plugin/src/lib/session-timeline-state.js +45 -0
  193. package/agent-spine-plugin/src/lib/session-timeline-transport.js +50 -0
  194. package/agent-spine-plugin/src/lib/session-timeline-windows-acl.js +148 -0
  195. package/agent-spine-plugin/src/lib/session-timeline.js +453 -0
  196. package/agent-spine-plugin/src/lib/source-roots.js +69 -136
  197. package/agent-spine-plugin/src/lib/source-tree-scan.js +178 -0
  198. package/agent-spine-plugin/src/lib/task-knowledge-context.js +78 -0
  199. package/agent-spine-plugin/src/lib/timeline-tool-guard.js +202 -0
  200. package/agent-spine-plugin/src/lib/world-knowledge.js +249 -0
  201. package/agent-spine-plugin/src/lib/world-model.js +278 -0
  202. package/agent-spine-plugin/src/mcp.js +20 -161
  203. package/agent-spine-plugin/src/version.js +1 -1
  204. package/agent-spine-plugin/src/worker.js +22 -5
  205. package/bin/agent-resume-snapshot.cjs +2 -2
  206. package/bin/agentspine-king-goal-inbox.mjs +111 -0
  207. package/bin/agentspine-king-goal-intake.mjs +106 -0
  208. package/bin/baseline-skill-performance-policy.cjs +1 -16
  209. package/bin/core-bootstrap.js +3 -0
  210. package/bin/curiosity-scout-policy.cjs +5 -1
  211. package/bin/input-draft-persistence.cjs +2 -2
  212. package/bin/king-tui-function-contract.json +33 -0
  213. package/bin/launcher-restart-policy.cjs +150 -0
  214. package/bin/launcher-runtime.js +56 -44
  215. package/bin/managed-context-startup-policy.cjs +27 -0
  216. package/bin/managed-plugin-selection.cjs +116 -0
  217. package/bin/mistake-relevance-policy.cjs +1 -1
  218. package/bin/observer-hooks.cjs +14 -0
  219. package/bin/oversized-context-offload-policy.cjs +86 -0
  220. package/bin/plugin-bootstrap.js +7 -40
  221. package/bin/proactive-compaction-policy.cjs +1 -1
  222. package/bin/provider-model-refresh-deadline.cjs +53 -0
  223. package/bin/provider-model-refresh-policy.cjs +107 -0
  224. package/bin/release-artifact-freeze-policy.cjs +30 -0
  225. package/bin/repeated-user-message-projection.cjs +3 -126
  226. package/bin/research-page-result.cjs +74 -0
  227. package/bin/runtime-exit-ledger.cjs +1 -0
  228. package/bin/session-compaction-policy.cjs +84 -0
  229. package/bin/skill-listing-performance-policy.cjs +2 -2
  230. package/bin/standard-tools-bootstrap.js +0 -37
  231. package/bin/subagent-skill-policy.cjs +3 -1
  232. package/bin/telegram-approval-relay.cjs +12 -7
  233. package/bin/telegram-private-conversation-policy.cjs +3 -2
  234. package/bin/telegram-queue-handoff-policy.cjs +24 -0
  235. package/bin/thinking-activity-status-policy.cjs +1 -1
  236. package/bin/thinking-only-guard.cjs +16 -12
  237. package/bin/tool-call-loop-policy.cjs +0 -2
  238. package/bin/tool-result-offload-policy.cjs +11 -2
  239. package/bin/tui-functional-contract.cjs +55 -0
  240. package/bin/update-notice.js +18 -14
  241. package/bin/user-message-offload-policy.cjs +1 -1
  242. package/bin/user-prompt-hook-origin-policy.cjs +34 -0
  243. package/bin/windows-node-crash-dump.cjs +110 -0
  244. package/blun.mjs +2861 -1173
  245. package/codebase-index/codebase_index.py +4 -3
  246. package/package.json +5 -18
  247. package/standard-skills/research-evidence/SKILL.md +39 -0
  248. package/standard-skills/research-evidence/references/evidence-format.md +82 -0
  249. package/standard-skills/research-evidence/scripts/evidence-collection.cjs +254 -0
  250. package/standard-skills/research-evidence/scripts/score-report.cjs +112 -0
  251. package/standard-skills/web-lesen/SKILL.md +37 -22
  252. package/standard-skills/web-lesen/scripts/crawl_public.py +376 -0
  253. package/telegram-plugin/DELIVERY.md +36 -0
  254. package/telegram-plugin/bin/telegram-approval-relay.cjs +13 -7
  255. package/telegram-plugin/bin/telegram-launcher-status-queue.cjs +122 -0
  256. package/telegram-plugin/bin/telegram-private-conversation-policy.cjs +3 -2
  257. package/telegram-plugin/bin/telegram-reply-parts.cjs +149 -0
  258. package/telegram-plugin/dist/bridge.mjs +7 -56
  259. package/telegram-plugin/dist/mcp-server.mjs +33 -4
  260. package/agent-spine-plugin/skill/SKILL.md +0 -76
  261. package/bin/mnemo-connect-heartbeat.cjs +0 -204
  262. package/bin/mnemo-tool-agent-policy.cjs +0 -22
  263. package/telegram-plugin/bin/telegram-mnemo-capture.cjs +0 -297
@@ -0,0 +1,202 @@
1
+ import { canonicalPath } from "./paths.js";
2
+ import { resolveHostSourceCatalog } from "./source-roots.js";
3
+ import { authorizeSessionTimelineInvocation } from "./session-timeline.js";
4
+ import { timelineInvocationRequest, timelineToolKind } from "./mcp-timeline-tools.js";
5
+ import { gatewayEnvironmentContext, hookDeliveryId, hostFromInput, sessionId } from "./hook-context.js";
6
+ import { blockedHookOutput } from "./hook-output.js";
7
+ import { sessionTimelineBinding } from "./session-timeline-contract.js";
8
+ import { resolvePrivateSessionTimelineEnrollment } from "./session-timeline-enrollment.js";
9
+ import { timelineTransportDigest } from "./session-timeline-transport.js";
10
+ import { runtimeHostForTimeline, timelineHostForRuntime, timelineHostHome } from "./session-timeline-provider.js";
11
+
12
+ function plainObject(value) {
13
+ return value && typeof value === "object" && !Array.isArray(value)
14
+ && Object.getPrototypeOf(value) === Object.prototype;
15
+ }
16
+
17
+ function absent(value) { return value === undefined || value === null || value === ""; }
18
+
19
+ function rawGroupClaim(input, gateway) {
20
+ const nested = plainObject(input?.agent_spine_scope) ? input.agent_spine_scope : null;
21
+ return !absent(process.env.AGENTSPINE_GROUP_ID) || !absent(gateway?.groupId)
22
+ || !absent(input?.group_id ?? input?.groupId)
23
+ || !absent(nested?.group_id ?? nested?.groupId);
24
+ }
25
+
26
+ function gatewayMatchesEnrollment(gateway, binding) {
27
+ if (!gateway) return true;
28
+ if (!absent(gateway.groupId)) return false;
29
+ const fields = [
30
+ ["host", "host"], ["entityId", "entityId"], ["projectId", "projectId"],
31
+ ["taskId", "taskId"], ["goalId", "goalId"], ["goalStepId", "goalStepId"]
32
+ ];
33
+ return gateway.host === runtimeHostForTimeline(binding.host)
34
+ && fields.slice(1).every(([gatewayField, bindingField]) => gateway[gatewayField] === binding[bindingField])
35
+ && process.env.AGENTSPINE_USER_ID === binding.userId && process.env.AGENTSPINE_TENANT_ID === binding.tenantId;
36
+ }
37
+
38
+ function bindingScope(binding, enrollment) {
39
+ return { entityId: binding.entityId, userId: binding.userId, tenantId: binding.tenantId,
40
+ projectId: binding.projectId, groupId: null, currentTaskId: binding.taskId,
41
+ goalId: binding.goalId, goalStepId: binding.goalStepId,
42
+ timelineVisibility: enrollment.timelineVisibility };
43
+ }
44
+
45
+ function nestedScope(args) {
46
+ return plainObject(args?.agent_spine_scope) ? args.agent_spine_scope : null;
47
+ }
48
+
49
+ function validNestedScope(args) {
50
+ return args?.agent_spine_scope === undefined || args.agent_spine_scope === null || plainObject(args.agent_spine_scope);
51
+ }
52
+
53
+ function scopeValues(args, keys) {
54
+ const nested = nestedScope(args);
55
+ return keys.flatMap((key) => [args?.[key], nested?.[key]]);
56
+ }
57
+
58
+ function scopeClaimsMatch(args, keys, expected) {
59
+ return scopeValues(args, keys).every((value) => absent(value) || value === expected);
60
+ }
61
+
62
+ function noScopeClaims(args, keys) {
63
+ return scopeValues(args, keys).every(absent);
64
+ }
65
+
66
+ function matchesScopeBinding(args, binding) {
67
+ return validNestedScope(args)
68
+ && scopeClaimsMatch(args, ["sessionId", "session_id"], binding.sessionId)
69
+ && scopeClaimsMatch(args, ["entityId", "entity_id"], binding.entityId)
70
+ && scopeClaimsMatch(args, ["userId", "user_id"], binding.userId)
71
+ && scopeClaimsMatch(args, ["tenantId", "tenant_id"], binding.tenantId)
72
+ && scopeClaimsMatch(args, ["projectId", "project_id"], binding.projectId)
73
+ && scopeClaimsMatch(args, ["taskId", "task_id", "currentTaskId", "current_task_id"], binding.taskId)
74
+ && scopeClaimsMatch(args, ["goalId", "goal_id"], binding.goalId)
75
+ && scopeClaimsMatch(args, ["goalStepId", "goal_step_id"], binding.goalStepId)
76
+ && noScopeClaims(args, ["groupId", "group_id"]);
77
+ }
78
+
79
+ function matchesBinding(args, binding) {
80
+ return matchesScopeBinding(args, binding)
81
+ && noScopeClaims(args, ["accessProof", "access_proof", "invocationPermit", "invocation_permit",
82
+ "transportDigest", "transport_digest", "timelineVisibility", "timeline_visibility",
83
+ "enrollmentDigest", "enrollment_digest"]);
84
+ }
85
+
86
+ function matchesHostBinding(input, binding) {
87
+ return matchesScopeBinding(input, binding)
88
+ && scopeClaimsMatch(input, ["host", "provider"], binding.host)
89
+ && noScopeClaims(input, ["accessProof", "access_proof", "invocationPermit", "invocation_permit",
90
+ "transportDigest", "transport_digest", "enrollmentDigest", "enrollment_digest"]);
91
+ }
92
+
93
+ function claimedScope(input) {
94
+ const claim = (keys) => {
95
+ const values = scopeValues(input, keys).filter((value) => !absent(value));
96
+ return values.length && values.every((value) => value === values[0]) ? values[0] : null;
97
+ };
98
+ return {
99
+ entityId: claim(["entityId", "entity_id"]), userId: claim(["userId", "user_id"]),
100
+ tenantId: claim(["tenantId", "tenant_id"]), projectId: claim(["projectId", "project_id"]),
101
+ groupId: claim(["groupId", "group_id"]), currentTaskId: claim(["taskId", "task_id", "currentTaskId", "current_task_id"]),
102
+ goalId: claim(["goalId", "goal_id"]), goalStepId: claim(["goalStepId", "goal_step_id"])
103
+ };
104
+ }
105
+
106
+ async function matchesRoot(value, root) {
107
+ if (absent(value)) return true;
108
+ if (typeof value !== "string") return false;
109
+ try { return await canonicalPath(value) === root; }
110
+ catch { return false; }
111
+ }
112
+
113
+ function requestInput(tool, args) {
114
+ if (tool === "index") return args.maxBytes === undefined ? {} : { maxBytes: args.maxBytes };
115
+ if (args.at === undefined && args.query === undefined) return null;
116
+ if (args.includePriorSessions !== undefined && typeof args.includePriorSessions !== "boolean") return null;
117
+ return { ...(args.at === undefined ? {} : { at: args.at }), ...(args.query === undefined ? {} : { query: args.query }),
118
+ ...(args.windowSeconds === undefined ? {} : { windowSeconds: args.windowSeconds }),
119
+ ...(args.includePriorSessions === undefined ? {} : { includePriorSessions: args.includePriorSessions }) };
120
+ }
121
+
122
+ function denied(reason) {
123
+ return { blocked: true, reason, hostOutput: blockedHookOutput("PreToolUse", reason) };
124
+ }
125
+
126
+ function allowed(updatedInput) {
127
+ const permission = ["codex", "king"].includes(updatedInput.host) ? {} : {
128
+ permissionDecision: "ask",
129
+ permissionDecisionReason: "AgentSpine binds this one context-only timeline lookup to the current host session and local transport."
130
+ };
131
+ return {
132
+ blocked: false,
133
+ updatedInput,
134
+ hostOutput: {
135
+ hookSpecificOutput: {
136
+ hookEventName: "PreToolUse",
137
+ ...permission,
138
+ updatedInput
139
+ }
140
+ }
141
+ };
142
+ }
143
+
144
+ export async function runTimelineToolGuard(input) {
145
+ try {
146
+ const tool = timelineToolKind(input?.tool_name);
147
+ const runtimeHost = hostFromInput(input || {});
148
+ const host = timelineHostForRuntime(runtimeHost, process.env);
149
+ if (input?.hook_event_name !== "PreToolUse" || !tool || !host || !plainObject(input.tool_input)) {
150
+ return denied("AgentSpine session timeline requires a verified supported-host PreToolUse payload.");
151
+ }
152
+ const resolved = await resolveHostSourceCatalog({ host: runtimeHost, cwd: input.cwd || process.cwd(), input });
153
+ const root = resolved.projectRoot;
154
+ const historyHome = timelineHostHome(host) ?? resolved.hostHome;
155
+ const hostSession = sessionId(input);
156
+ const gateway = gatewayEnvironmentContext();
157
+ if (rawGroupClaim(input, gateway)) return denied("AgentSpine session timeline is unavailable for group-scoped activity.");
158
+ const provisionalBinding = sessionTimelineBinding({ host, sessionId: hostSession, scope: claimedScope(input) });
159
+ const transportDigest = timelineTransportDigest({ root, binding: provisionalBinding });
160
+ if (!transportDigest) {
161
+ return denied("AgentSpine session timeline requires a locally configured per-session transport capability.");
162
+ }
163
+ const enrollment = await resolvePrivateSessionTimelineEnrollment({ root, host, sessionId: hostSession,
164
+ transcriptPath: input.transcript_path ?? input.transcriptPath, hostHome: historyHome,
165
+ expectedTransportDigest: transportDigest });
166
+ if (enrollment.status !== "enrolled") {
167
+ return denied("AgentSpine session timeline has no active private local enrollment for this host session.");
168
+ }
169
+ const hostClaimsMatch = host === "king"
170
+ ? scopeClaimsMatch(input, ["host", "provider"], runtimeHost)
171
+ : matchesHostBinding(input, enrollment.binding);
172
+ if (!gatewayMatchesEnrollment(gateway, enrollment.binding) || !hostClaimsMatch
173
+ || !matchesScopeBinding(input, enrollment.binding)) {
174
+ return denied("AgentSpine session timeline binding does not match the authenticated gateway scope.");
175
+ }
176
+ if (!await matchesRoot(input.tool_input.root, root) || !matchesBinding(input.tool_input, enrollment.binding)) {
177
+ return denied("AgentSpine session timeline binding does not match this host session and scope.");
178
+ }
179
+ const scope = bindingScope(enrollment.binding, enrollment);
180
+ const requestFields = requestInput(tool, input.tool_input);
181
+ if (!requestFields) return denied("AgentSpine session timeline search needs an exact time or a concrete query.");
182
+ if (input.tool_input.host && input.tool_input.host !== host) return denied("Timeline provider binding mismatch.");
183
+ const updatedInput = { ...requestFields, ...(["codex", "king"].includes(host) ? { host } : {}), root, sessionId: enrollment.binding.sessionId,
184
+ entityId: enrollment.binding.entityId, userId: enrollment.binding.userId, tenantId: enrollment.binding.tenantId,
185
+ projectId: enrollment.binding.projectId, taskId: enrollment.binding.taskId, groupId: null,
186
+ goalId: enrollment.binding.goalId, goalStepId: enrollment.binding.goalStepId,
187
+ timelineVisibility: enrollment.timelineVisibility, enrollmentDigest: enrollment.enrollmentDigest };
188
+ const request = timelineInvocationRequest(tool, updatedInput, root);
189
+ const authorization = await authorizeSessionTimelineInvocation({
190
+ root, host, sessionId: hostSession, scope, hostHome: historyHome,
191
+ tool, request, toolUseId: hookDeliveryId(input), transportDigest, enrollmentDigest: enrollment.enrollmentDigest
192
+ });
193
+ if (!authorization) return denied("AgentSpine session timeline invocation is unavailable for this bound source.");
194
+ return allowed(updatedInput);
195
+ } catch {
196
+ return denied("AgentSpine session timeline invocation is unavailable for this bound source.");
197
+ }
198
+ }
199
+
200
+ export function emitTimelineToolGuard(result) {
201
+ process.stdout.write(`${JSON.stringify(result.hostOutput)}\n`);
202
+ }
@@ -0,0 +1,249 @@
1
+ import { taskKnowledgeContext } from "./task-knowledge-context.js";
2
+
3
+ const KNOWLEDGE_KINDS = new Set([
4
+ "fact", "user-preference", "decision", "task-state", "error-lesson"
5
+ ]);
6
+
7
+ const SESSION_REF = /^session-ref:[a-f0-9]{32}$/;
8
+ const MESSAGE_REF = /^[a-z][a-z0-9-]{0,31}:[A-Za-z0-9][A-Za-z0-9._/-]{0,190}$/;
9
+ const SECRET_RE = /-----BEGIN [A-Z ]*PRIVATE KEY-----|\b(?:sk[-_](?:proj[-_])?|gh[opusu]_)[A-Za-z0-9_-]{20,}\b|\b(?:xox[bapcrs]-|github_pat_|glpat-|npm_)[A-Za-z0-9_-]{12,}\b|\bAKIA[0-9A-Z]{16}\b|\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b/i;
10
+ const TASK_CONTINUATION_SCHEMA = "agentspine.task-continuation/v1";
11
+ const TASK_STATUSES = new Set(["active", "blocked", "paused", "completed"]);
12
+ const STEP_RESULTS = new Set(["passed", "failed", "blocked"]);
13
+ const STABLE_ID = /^[a-z][a-z0-9-]{0,31}:[A-Za-z0-9][A-Za-z0-9._/-]{0,190}$/;
14
+ const DIGEST = /^[a-f0-9]{64}$/;
15
+
16
+ function containsSecret(value) {
17
+ const pending = [value];
18
+ while (pending.length) {
19
+ const item = pending.pop();
20
+ if (typeof item === "string" && SECRET_RE.test(item)) return true;
21
+ if (Array.isArray(item)) pending.push(...item);
22
+ else if (item && typeof item === "object") pending.push(...Object.values(item));
23
+ }
24
+ return false;
25
+ }
26
+
27
+ function pairedSourceRefs(sessionRef, messageRef) {
28
+ if ((sessionRef === null) !== (messageRef === null)) {
29
+ throw new Error("sessionRef and messageRef must be provided together");
30
+ }
31
+ if (sessionRef !== null && (!SESSION_REF.test(sessionRef) || !MESSAGE_REF.test(messageRef))) {
32
+ throw new Error("sessionRef and messageRef must be stable provenance references");
33
+ }
34
+ }
35
+
36
+ function exactKeys(value, expected, field) {
37
+ if (!value || typeof value !== "object" || Array.isArray(value)
38
+ || Object.keys(value).sort().join("\0") !== [...expected].sort().join("\0")) {
39
+ throw new Error(`${field} has an invalid structure`);
40
+ }
41
+ }
42
+
43
+ function boundedText(value, field, maximum) {
44
+ if (typeof value !== "string" || !value.trim() || value.length > maximum) {
45
+ throw new Error(`${field} must be a bounded non-empty string`);
46
+ }
47
+ }
48
+
49
+ function validateContinuation(input, value) {
50
+ if (value?.schema !== TASK_CONTINUATION_SCHEMA) return;
51
+ exactKeys(value, ["schema", "taskId", "status", "objective", "lastVerifiedStep", "openQuestions", "nextStep"], "task continuation");
52
+ if (input.knowledgeKind !== "task-state" || input.predicate !== "task.continuation"
53
+ || value.taskId !== input.subjectId || !STABLE_ID.test(value.taskId || "")) {
54
+ throw new Error("task continuation must be a task-state bound to its task subject and predicate");
55
+ }
56
+ if (input.sessionRef === undefined || input.messageRef === undefined) {
57
+ throw new Error("task continuation requires stable session and message source references");
58
+ }
59
+ if (!TASK_STATUSES.has(value.status)) throw new Error("task continuation status is invalid");
60
+ boundedText(value.objective, "task continuation objective", 500);
61
+ if (!Array.isArray(value.openQuestions) || value.openQuestions.length > 8) {
62
+ throw new Error("task continuation openQuestions must be a bounded array");
63
+ }
64
+ for (const question of value.openQuestions) {
65
+ exactKeys(question, ["id", "question"], "task continuation question");
66
+ if (!STABLE_ID.test(question.id || "")) throw new Error("task continuation question id is invalid");
67
+ boundedText(question.question, "task continuation question", 300);
68
+ }
69
+ if (new Set(value.openQuestions.map((item) => item.id)).size !== value.openQuestions.length) {
70
+ throw new Error("task continuation question ids must be unique");
71
+ }
72
+ if (value.nextStep !== null) {
73
+ exactKeys(value.nextStep, ["id", "summary"], "task continuation nextStep");
74
+ if (!STABLE_ID.test(value.nextStep.id || "")) throw new Error("task continuation next step id is invalid");
75
+ boundedText(value.nextStep.summary, "task continuation next step", 500);
76
+ }
77
+ if (value.lastVerifiedStep !== null) {
78
+ exactKeys(value.lastVerifiedStep,
79
+ ["id", "summary", "result", "evidenceId", "evidenceDigest", "observedAt", "sessionRef", "messageRef"],
80
+ "task continuation lastVerifiedStep");
81
+ if (!STABLE_ID.test(value.lastVerifiedStep.id || "") || !STEP_RESULTS.has(value.lastVerifiedStep.result)
82
+ || !STABLE_ID.test(value.lastVerifiedStep.evidenceId || "")
83
+ || !DIGEST.test(value.lastVerifiedStep.evidenceDigest || "")) {
84
+ throw new Error("task continuation verified step evidence is invalid");
85
+ }
86
+ boundedText(value.lastVerifiedStep.summary, "task continuation verified step", 500);
87
+ if (new Date(value.lastVerifiedStep.observedAt).toISOString() !== value.lastVerifiedStep.observedAt
88
+ || new Date(value.lastVerifiedStep.observedAt).getTime() > new Date(input.observedAt).getTime()) {
89
+ throw new Error("task continuation verified step timestamp is invalid");
90
+ }
91
+ pairedSourceRefs(value.lastVerifiedStep.sessionRef, value.lastVerifiedStep.messageRef);
92
+ }
93
+ if (value.status === "completed" && (value.nextStep !== null || value.openQuestions.length)) {
94
+ throw new Error("completed task continuation cannot retain a next step or open questions");
95
+ }
96
+ if (value.status === "completed" && value.lastVerifiedStep?.result !== "passed") {
97
+ throw new Error("completed task continuation requires a passed verified step");
98
+ }
99
+ if (value.status === "completed" && input.evidenceKind !== "objective-measurement") {
100
+ throw new Error("completed task continuation requires objective measurement evidence");
101
+ }
102
+ if (value.status !== "completed" && value.nextStep === null) {
103
+ throw new Error("resumable task continuation requires a next step");
104
+ }
105
+ }
106
+
107
+ export function normalizeKnowledgeFields(input, evidenceKind, value) {
108
+ const kind = input.knowledgeKind ?? null;
109
+ if (kind === null) {
110
+ if (input.sessionRef !== undefined || input.messageRef !== undefined) {
111
+ throw new Error("session references require knowledgeKind");
112
+ }
113
+ return { knowledgeKind: null, sessionRef: null, messageRef: null };
114
+ }
115
+ if (!KNOWLEDGE_KINDS.has(kind)) throw new Error("unsupported knowledgeKind");
116
+ if (containsSecret(value) || containsSecret(input.reason || "")) {
117
+ throw new Error("secret-shaped content cannot enter structured knowledge");
118
+ }
119
+ validateContinuation(input, value);
120
+ if (kind === "user-preference" && !["explicit-user-feedback", "model-suggestion"].includes(evidenceKind)) {
121
+ throw new Error("user preferences require explicit feedback or remain a model suggestion");
122
+ }
123
+ if (kind === "decision" && !["explicit-user-feedback", "model-suggestion"].includes(evidenceKind)) {
124
+ throw new Error("decisions require explicit feedback or remain a model suggestion");
125
+ }
126
+ if (kind === "decision" && (typeof input.reason !== "string" || !input.reason.trim())) {
127
+ throw new Error("decisions require a bounded rationale");
128
+ }
129
+ const sessionRef = input.sessionRef ?? null;
130
+ const messageRef = input.messageRef ?? null;
131
+ pairedSourceRefs(sessionRef, messageRef);
132
+ return { knowledgeKind: kind, sessionRef, messageRef };
133
+ }
134
+
135
+ export function validKnowledgeFields(assertion) {
136
+ const kind = assertion.knowledgeKind ?? null;
137
+ if (kind === null) return assertion.sessionRef === undefined && assertion.messageRef === undefined;
138
+ if (!KNOWLEDGE_KINDS.has(kind)) return false;
139
+ if (containsSecret(assertion.value) || containsSecret(assertion.reason || "")) return false;
140
+ if (kind === "user-preference" && !["explicit-user-feedback", "model-suggestion"].includes(assertion.evidenceKind)) return false;
141
+ if (kind === "decision" && !["explicit-user-feedback", "model-suggestion"].includes(assertion.evidenceKind)) return false;
142
+ if (kind === "decision" && !assertion.reason) return false;
143
+ try {
144
+ validateContinuation(assertion, assertion.value);
145
+ pairedSourceRefs(assertion.sessionRef ?? null, assertion.messageRef ?? null);
146
+ return true;
147
+ } catch {
148
+ return false;
149
+ }
150
+ }
151
+
152
+ function continuationView(entries, taskId, maxItems) {
153
+ const eligible = entries.filter((item) => item.kind === "task-state" && item.status === "confirmed"
154
+ && item.predicate === "task.continuation" && item.value?.schema === TASK_CONTINUATION_SCHEMA
155
+ && (!taskId || item.subjectId === taskId));
156
+ const selected = new Map();
157
+ for (const item of eligible) {
158
+ if (!selected.has(item.subjectId)) selected.set(item.subjectId, item);
159
+ }
160
+ const capsules = [...selected.values()].map((item) => ({
161
+ schema: TASK_CONTINUATION_SCHEMA,
162
+ assertionId: item.id,
163
+ ...structuredClone(item.value),
164
+ source: structuredClone(item.source),
165
+ observedAt: item.observedAt,
166
+ scope: structuredClone(item.scope),
167
+ authority: "context-only"
168
+ }));
169
+ const limited = capsules.slice(0, Math.min(maxItems, 8));
170
+ return {
171
+ schema: "agentspine.task-continuation-context/v1",
172
+ tasks: limited.filter((item) => item.status !== "completed"),
173
+ terminal: limited.filter((item) => item.status === "completed"),
174
+ omitted: capsules.length - limited.length,
175
+ authority: "context-only",
176
+ note: "Continuation is descriptive context only and never grants permission, tools, delegation, or execution."
177
+ };
178
+ }
179
+
180
+ function publicEntry(assertion, status, statusReason) {
181
+ return {
182
+ id: assertion.id,
183
+ kind: assertion.knowledgeKind,
184
+ subjectId: assertion.subjectId,
185
+ predicate: assertion.predicate,
186
+ value: structuredClone(assertion.value),
187
+ status,
188
+ statusReason,
189
+ source: {
190
+ kind: assertion.evidenceKind,
191
+ id: assertion.evidenceId,
192
+ digest: assertion.evidenceDigest,
193
+ sessionRef: assertion.sessionRef ?? null,
194
+ messageRef: assertion.messageRef ?? null
195
+ },
196
+ observedAt: assertion.observedAt,
197
+ expiresAt: assertion.expiresAt,
198
+ recordedAt: assertion.recordedAt,
199
+ rationale: assertion.reason || null,
200
+ scope: {
201
+ projectId: assertion.projectId,
202
+ groupId: assertion.groupId,
203
+ privacy: assertion.privacy
204
+ },
205
+ authority: "context-only"
206
+ };
207
+ }
208
+
209
+ function statusFor(assertion, { staleIds, supersededIds, conflictIds }) {
210
+ if (staleIds.has(assertion.id)) return ["superseded", "expired"];
211
+ if (supersededIds.has(assertion.id)) return ["superseded", "replaced"];
212
+ if (assertion.status === "proposed") return ["assumption", "model-suggestion"];
213
+ if (conflictIds.has(assertion.id)) return ["contradictory", "active-conflict"];
214
+ return ["confirmed", "established-evidence"];
215
+ }
216
+
217
+ export function structuredKnowledgeView({
218
+ candidates, stale, active, supersededIds, conflictAssertions,
219
+ includeHistory = false, continuationTaskId = null, maxItems = 100
220
+ }) {
221
+ const staleIds = new Set(stale.map((item) => item.id));
222
+ const activeIds = new Set(active.map((item) => item.id));
223
+ const conflictIds = new Set(conflictAssertions.flatMap((items) => items.map((item) => item.id)));
224
+ const entries = candidates.filter((item) => item.knowledgeKind).map((assertion) => {
225
+ const [status, reason] = statusFor(assertion, { staleIds, supersededIds, conflictIds });
226
+ return publicEntry(assertion, status, reason);
227
+ }).sort((left, right) => right.observedAt.localeCompare(left.observedAt) || left.id.localeCompare(right.id));
228
+ const current = entries.filter((item) => activeIds.has(item.id));
229
+ const historical = entries.filter((item) => !activeIds.has(item.id));
230
+ const history = includeHistory ? historical : [];
231
+ const counts = Object.fromEntries(["confirmed", "assumption", "superseded", "contradictory"]
232
+ .map((status) => [status, entries.filter((item) => item.status === status).length]));
233
+ const continuation = continuationView(current, continuationTaskId, maxItems);
234
+ return {
235
+ schema: "agentspine.structured-knowledge/v1",
236
+ current: current.slice(0, maxItems),
237
+ history: history.slice(0, maxItems),
238
+ continuation,
239
+ taskContext: taskKnowledgeContext(current, {
240
+ taskId: continuationTaskId, continuation, maxItems
241
+ }),
242
+ counts,
243
+ omitted: {
244
+ current: Math.max(0, current.length - maxItems),
245
+ history: includeHistory ? Math.max(0, historical.length - maxItems) : historical.length
246
+ },
247
+ authority: "context-only"
248
+ };
249
+ }