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,302 @@
1
+ import { attentionFindings, loadAttention } from "./attention.js";
2
+ import { channelRuntimeFindings, loadChannelPolicy, loadChannelRuntime } from "./channel-runtime.js";
3
+ import { loadGraph } from "./graph.js";
4
+ import { expireGatewayLane } from "./gateway-host-lifecycle.js";
5
+ import * as goalPremortem from "./gateway-premortem.js";
6
+ import { resolveKnowledgeGapCandidate, sameKnowledgeGapResolution } from "./knowledge-evidence.js";
7
+ import { loadPersonaRuntime, personaRuntimeFindings } from "./persona-runtime.js";
8
+ import { assertActivePersona } from "./gateway-runtime-identity.js";
9
+ import { appendReceipt, preserve } from "./gateway-runtime-records.js";
10
+ import { writeGatewayStatePair } from "./gateway-state-transaction.js";
11
+ import {
12
+ CONFIRMATION, PRIORITY, WAKE_KINDS, emptyPolicy, emptyRuntime, exactId,
13
+ safeText, sha256, timestamp
14
+ } from "./gateway-common.js";
15
+ import { currentExecutionAttempt } from "./gateway-execution.js";
16
+ import {
17
+ createGoalPlan, currentPlanStep, newGoalQueue, normalizePolicy, normalizeRuntime,
18
+ pathsFor, planQueueKey, planStepAgentId, readJson, validGoal, withLock, writeJson
19
+ } from "./gateway-state.js";
20
+
21
+ export async function setGatewayControl({ root = process.cwd(), enabled, killSwitch, confirmation, now = new Date() }) {
22
+ if (confirmation !== CONFIRMATION) throw new Error("gateway control changes require explicit local owner confirmation");
23
+ const paths = await pathsFor(root);
24
+ return withLock(paths, async () => {
25
+ const policy = await readJson(paths.gatewayPolicyPath, paths.catalog.root, normalizePolicy, emptyPolicy);
26
+ const previous = { enabled: policy.enabled, killSwitch: policy.killSwitch };
27
+ if (enabled !== undefined) policy.enabled = Boolean(enabled);
28
+ if (killSwitch !== undefined) policy.killSwitch = Boolean(killSwitch);
29
+ policy.revision += 1;
30
+ policy.history.push({ kind: "control", at: timestamp(now), value: previous, authority: "authenticated-goal-policy" });
31
+ await writeJson(paths.gatewayPolicyPath, policy);
32
+ return { enabled: policy.enabled, killSwitch: policy.killSwitch, revision: policy.revision };
33
+ });
34
+ }
35
+
36
+ export async function assignGoal({ root = process.cwd(), goalId, agentId, ownerSubjectId, projectId, groupId = null,
37
+ priority = 70, successCriterion, nextSafeStep = null, steps = null, deadline = null, confirmation, now = new Date() }) {
38
+ if (confirmation !== CONFIRMATION) throw new Error("goal assignment requires explicit local owner confirmation");
39
+ const paths = await pathsFor(root);
40
+ return withLock(paths, async () => {
41
+ const [policy, runtime, personas] = await Promise.all([
42
+ readJson(paths.gatewayPolicyPath, paths.catalog.root, normalizePolicy, emptyPolicy),
43
+ readJson(paths.gatewayRuntimePath, paths.catalog.root, normalizeRuntime, emptyRuntime),
44
+ loadPersonaRuntime(paths.catalog.root, paths.catalog)
45
+ ]);
46
+ goalId = exactId(goalId, "goalId"); agentId = exactId(agentId, "agentId");
47
+ projectId = exactId(projectId, "projectId"); groupId = exactId(groupId, "groupId", true);
48
+ const leadIdentity = assertActivePersona(personas.policy, personas.runtime, agentId, projectId, groupId);
49
+ const createdAt = timestamp(now);
50
+ const active = policy.goals.find((item) => item.agentId === agentId && item.status === "active" && item.goalId !== goalId);
51
+ if (active) throw new Error("an agent may have only one active focused goal");
52
+ const previous = policy.goals.find((item) => item.goalId === goalId);
53
+ const premortemContractVersion = previous?.plan ? goalPremortem.goalPremortemContractVersion(policy, goalId) : 1;
54
+ const plan = steps === null ? null : createGoalPlan(steps, createdAt, agentId, {
55
+ goals: policy.goals, scope: { goalId, projectId, groupId, before: createdAt },
56
+ premortemContractVersion
57
+ });
58
+ if (plan) {
59
+ for (const stepAgentId of new Set(plan.steps.map((step) => step.agentId))) {
60
+ const stepIdentity = assertActivePersona(personas.policy, personas.runtime, stepAgentId, projectId, groupId);
61
+ if (stepIdentity.binding.tenantId !== leadIdentity.binding.tenantId) {
62
+ throw new Error("goal-plan team members must share the authenticated tenant and exact project group");
63
+ }
64
+ }
65
+ }
66
+ const firstStep = plan ? plan.steps.find((step) => step.stepId === plan.currentStepId) : null;
67
+ const goal = { goalId, agentId, ownerSubjectId: exactId(ownerSubjectId, "ownerSubjectId"),
68
+ projectId, groupId, priority: Number(priority), successCriterion: safeText(successCriterion, "successCriterion"),
69
+ nextSafeStep: safeText(firstStep?.title || nextSafeStep, "nextSafeStep"), deadline: deadline === null ? null : timestamp(deadline),
70
+ status: "active", checkpoint: null, heartbeatAt: null, blocker: null,
71
+ createdAt: previous?.createdAt || createdAt, updatedAt: createdAt,
72
+ plan, authority: "authenticated-goal-policy" };
73
+ if (!validGoal(goal, paths.catalog.root, plan ? premortemContractVersion : undefined)) throw new Error("goal assignment is invalid");
74
+ if (previous && [previous.agentId, previous.ownerSubjectId, previous.projectId, previous.groupId].join("\0")
75
+ !== [goal.agentId, goal.ownerSubjectId, goal.projectId, goal.groupId].join("\0")) throw new Error("goal scope is immutable");
76
+ if (previous?.plan && previous.plan.definitionsDigest !== goal.plan?.definitionsDigest) {
77
+ throw new Error("goal plan definitions are immutable; assign a new goal ID");
78
+ }
79
+ if (previous?.plan && previous.status === "blocked") {
80
+ const blockedStep = currentPlanStep(previous);
81
+ if (!blockedStep || blockedStep.status !== "blocked") throw new Error("blocked goal plan has no bound blocked step");
82
+ if ((blockedStep.knowledgeGaps || []).some((gap) => gap.status === "open")) {
83
+ throw new Error("blocked goal plan has an open knowledge gap; resolve it with goal-clarify");
84
+ }
85
+ if (blockedStep.execution?.explorationMaxAttempts !== undefined
86
+ && currentExecutionAttempt(blockedStep.execution, blockedStep.executionOutcomes || []) === null) {
87
+ throw new Error("bounded exploration is exhausted or stopped by a blocking defect; assign a new goal ID");
88
+ }
89
+ policy.history.push({ kind: "goal", at: createdAt, value: structuredClone(previous), authority: "authenticated-goal-policy" });
90
+ blockedStep.status = "active"; blockedStep.blocker = null; blockedStep.updatedAt = createdAt;
91
+ previous.status = "active"; previous.blocker = null; previous.updatedAt = createdAt; previous.plan.revision += 1;
92
+ policy.revision += 1;
93
+ const key = planQueueKey(previous.goalId, blockedStep.stepId, "owner-resume", String(previous.plan.revision));
94
+ const queued = newGoalQueue(previous, blockedStep, "follow-up", key, createdAt);
95
+ runtime.queue.push(queued); runtime.revision += 1;
96
+ appendReceipt(runtime, "goal-step-resumed", queued.queueId, createdAt, { goalStepId: blockedStep.stepId });
97
+ await writeGatewayStatePair(policy, runtime);
98
+ return { goal: structuredClone(previous), gatewayPolicyPath: paths.gatewayPolicyPath, resumed: true };
99
+ }
100
+ if (previous?.plan) return { goal: structuredClone(previous), gatewayPolicyPath: paths.gatewayPolicyPath, duplicate: true };
101
+ if (previous) {
102
+ policy.history.push({ kind: "goal", at: createdAt, value: structuredClone(previous), authority: "authenticated-goal-policy" });
103
+ goal.createdAt = previous.createdAt;
104
+ }
105
+ policy.goals = policy.goals.filter((item) => item.goalId !== goal.goalId);
106
+ policy.goals.push(goal);
107
+ if (plan) goalPremortem.registerGoalPremortemContract(policy, goal, premortemContractVersion, createdAt);
108
+ policy.revision += 1;
109
+ const key = firstStep ? planQueueKey(goal.goalId, firstStep.stepId, "assignment") : "goal:" + goal.goalId + ":assignment";
110
+ if (!runtime.queue.some((item) => item.dedupeKey === key)) {
111
+ runtime.queue.push(newGoalQueue(goal, firstStep, "assignment", key, createdAt));
112
+ runtime.revision += 1;
113
+ appendReceipt(runtime, "queued", "gateway-queue:" + sha256(key).slice(0, 32), createdAt, { kind: "assignment", dedupeKey: key });
114
+ }
115
+ await writeGatewayStatePair(policy, runtime);
116
+ return { goal, gatewayPolicyPath: paths.gatewayPolicyPath };
117
+ });
118
+ }
119
+
120
+ export async function resolveGoalKnowledgeGap({ root = process.cwd(), goalId, gapId, answer,
121
+ answerSource = "owner-input", sourceDigest = null, confirmation, now = new Date() }) {
122
+ if (confirmation !== CONFIRMATION) throw new Error("knowledge gap resolution requires explicit local owner confirmation");
123
+ const paths = await pathsFor(root);
124
+ return withLock(paths, async () => {
125
+ const [policy, runtime, personas] = await Promise.all([
126
+ readJson(paths.gatewayPolicyPath, paths.catalog.root, normalizePolicy, emptyPolicy),
127
+ readJson(paths.gatewayRuntimePath, paths.catalog.root, normalizeRuntime, emptyRuntime),
128
+ loadPersonaRuntime(paths.catalog.root, paths.catalog)
129
+ ]);
130
+ goalId = exactId(goalId, "goalId"); gapId = exactId(gapId, "gapId");
131
+ const goal = policy.goals.find((item) => item.goalId === goalId);
132
+ const step = goal && currentPlanStep(goal);
133
+ if (!goal?.plan || !step) throw new Error("knowledge gap resolution requires the exact current goal-plan step");
134
+ assertActivePersona(personas.policy, personas.runtime, goal.agentId, goal.projectId, goal.groupId);
135
+ const gap = (step.knowledgeGaps || []).find((item) => item.gapId === gapId);
136
+ if (!gap) throw new Error("knowledge gap is not bound to the current goal-plan step");
137
+ if (gap.status === "resolved") {
138
+ if (!sameKnowledgeGapResolution(gap, { answer, answerSource, sourceDigest })) {
139
+ throw new Error("knowledge gap already has a different bound resolution");
140
+ }
141
+ return { goal: structuredClone(goal), gap: structuredClone(gap), duplicate: true };
142
+ }
143
+ if (goal.status !== "blocked" || step.status !== "blocked") {
144
+ throw new Error("open knowledge gap resolution requires the exact blocked goal-plan step");
145
+ }
146
+ const resolvedAt = timestamp(now);
147
+ const candidate = resolveKnowledgeGapCandidate(gap, { answer, answerSource, sourceDigest,
148
+ resolvedAt, resolvedBySubjectId: goal.ownerSubjectId });
149
+ policy.history.push({ kind: "goal", at: resolvedAt, value: structuredClone(goal), authority: "authenticated-goal-policy" });
150
+ Object.assign(gap, candidate);
151
+ step.status = "active"; step.blocker = null; step.updatedAt = resolvedAt;
152
+ goal.status = "active"; goal.blocker = null; goal.updatedAt = resolvedAt; goal.nextSafeStep = step.title;
153
+ goal.plan.revision += 1; policy.revision += 1;
154
+ const key = planQueueKey(goal.goalId, step.stepId, "clarified", gap.resolutionDigest.slice(0, 20));
155
+ let queued = runtime.queue.find((item) => item.dedupeKey === key);
156
+ if (!queued) {
157
+ queued = newGoalQueue(goal, step, "follow-up", key, resolvedAt);
158
+ runtime.queue.push(queued); runtime.revision += 1;
159
+ appendReceipt(runtime, "knowledge-gap-resolved", queued.queueId, resolvedAt, {
160
+ goalStepId: step.stepId, gapId: gap.gapId, answerSource
161
+ });
162
+ }
163
+ await writeGatewayStatePair(policy, runtime);
164
+ return { goal: structuredClone(goal), gap: structuredClone(gap), queueId: queued.queueId, duplicate: false };
165
+ });
166
+ }
167
+
168
+ export async function enqueueGatewayWake({ root = process.cwd(), kind, agentId, projectId, groupId = null, goalId = null,
169
+ channelEventId = null, dedupeKey, availableAt = null, now = new Date() }) {
170
+ if (!WAKE_KINDS.has(kind)) throw new Error("unsupported gateway wake kind");
171
+ const paths = await pathsFor(root);
172
+ return withLock(paths, async () => {
173
+ const [policy, runtime, personas] = await Promise.all([
174
+ readJson(paths.gatewayPolicyPath, paths.catalog.root, normalizePolicy, emptyPolicy),
175
+ readJson(paths.gatewayRuntimePath, paths.catalog.root, normalizeRuntime, emptyRuntime),
176
+ loadPersonaRuntime(paths.catalog.root, paths.catalog)
177
+ ]);
178
+ if (!policy.enabled || policy.killSwitch) throw new Error("gateway is disabled by local policy");
179
+ agentId = exactId(agentId, "agentId"); projectId = exactId(projectId, "projectId"); groupId = exactId(groupId, "groupId", true);
180
+ assertActivePersona(personas.policy, personas.runtime, agentId, projectId, groupId);
181
+ goalId = exactId(goalId, "goalId", true); channelEventId = exactId(channelEventId, "channelEventId", true);
182
+ const key = exactId(dedupeKey, "dedupeKey");
183
+ const existing = runtime.queue.find((item) => item.dedupeKey === key);
184
+ if (existing) return { item: existing, duplicate: true };
185
+ const createdAt = timestamp(now);
186
+ const item = { queueId: "gateway-queue:" + sha256(key).slice(0, 32), dedupeKey: key, kind, agentId, projectId, groupId,
187
+ goalId, goalStepId: null, channelEventId, priority: PRIORITY[kind], status: "pending", attempts: 0, lease: null,
188
+ availableAt: availableAt === null ? createdAt : timestamp(availableAt), createdAt, updatedAt: createdAt,
189
+ completedAt: null, lastError: null, authority: "execution-state-only" };
190
+ runtime.queue.push(item); runtime.revision += 1;
191
+ appendReceipt(runtime, "queued", item.queueId, createdAt, { kind, dedupeKey: key });
192
+ await writeJson(paths.gatewayRuntimePath, runtime);
193
+ return { item, duplicate: false };
194
+ });
195
+ }
196
+
197
+ export async function reconcileGateway({ root = process.cwd(), now = new Date() }) {
198
+ const paths = await pathsFor(root);
199
+ return withLock(paths, async () => {
200
+ const [policy, runtime, channelPolicy, channel, attention, personas, { graph }] = await Promise.all([
201
+ readJson(paths.gatewayPolicyPath, paths.catalog.root, normalizePolicy, emptyPolicy),
202
+ readJson(paths.gatewayRuntimePath, paths.catalog.root, normalizeRuntime, emptyRuntime),
203
+ loadChannelPolicy(paths.catalog.root, paths.catalog), loadChannelRuntime(paths.catalog.root, paths.catalog),
204
+ loadAttention(paths.catalog.root, paths.catalog), loadPersonaRuntime(paths.catalog.root, paths.catalog),
205
+ loadGraph(paths.catalog.root, paths.catalog)
206
+ ]);
207
+ const current = timestamp(now);
208
+ const findings = personaRuntimeFindings(personas.policy, personas.runtime, graph);
209
+ if (findings.length) throw new Error("persona runtime is unhealthy: " + findings.join(", "));
210
+ const channelFindings = channelRuntimeFindings(channel.runtime, channelPolicy.policy, graph);
211
+ if (channelFindings.length) throw new Error("channel runtime is unhealthy: " + channelFindings.join(", "));
212
+ const attentionIssues = attentionFindings(attention.attention);
213
+ if (attentionIssues.length) throw new Error("attention runtime is unhealthy: " + attentionIssues.join(", "));
214
+ let policyChanged = false;
215
+ for (const lane of runtime.lanes.filter((item) => item.status === "leased" && new Date(item.expiresAt) <= new Date(current))) {
216
+ policyChanged = expireGatewayLane(policy, runtime, lane, current,
217
+ { preserve, appendReceipt }) || policyChanged;
218
+ }
219
+ for (const outbox of runtime.outbox.filter((item) => item.status === "sending")) {
220
+ preserve(runtime, "outbox", outbox, "ambiguous-send-recovery", current);
221
+ outbox.status = "delivery-unknown"; outbox.updatedAt = current;
222
+ appendReceipt(runtime, "delivery-unknown", outbox.outboxId, current, { reason: "crash-during-send" });
223
+ }
224
+ if (policy.enabled && !policy.killSwitch) {
225
+ for (const goal of policy.goals.filter((item) => item.status === "active" && item.plan)) {
226
+ const step = currentPlanStep(goal);
227
+ if (!step) throw new Error("active goal plan has no current step");
228
+ try {
229
+ assertActivePersona(personas.policy, personas.runtime, planStepAgentId(goal, step), goal.projectId, goal.groupId);
230
+ } catch {
231
+ policy.history.push({ kind: "goal", at: current, value: structuredClone(goal), authority: "authenticated-goal-policy" });
232
+ const blocker = "Assigned team member is unavailable in this exact project group.";
233
+ step.status = "blocked"; step.blocker = blocker; step.updatedAt = current;
234
+ goal.status = "blocked"; goal.blocker = blocker; goal.updatedAt = current; goal.plan.revision += 1;
235
+ for (const queued of runtime.queue.filter((item) => item.goalId === goal.goalId
236
+ && item.goalStepId === step.stepId && ["pending", "leased"].includes(item.status))) {
237
+ preserve(runtime, "queue", queued, "step-agent-unavailable", current);
238
+ queued.status = "cancelled"; queued.lease = null; queued.completedAt = current; queued.updatedAt = current;
239
+ for (const lane of runtime.lanes.filter((item) => item.queueId === queued.queueId && item.status === "leased")) {
240
+ lane.status = "expired"; lane.updatedAt = current;
241
+ }
242
+ appendReceipt(runtime, "step-agent-unavailable", queued.queueId, current, { goalStepId: step.stepId });
243
+ }
244
+ policy.revision += 1; policyChanged = true;
245
+ continue;
246
+ }
247
+ const runnable = runtime.queue.some((item) => item.goalId === goal.goalId && item.goalStepId === step.stepId
248
+ && item.agentId === planStepAgentId(goal, step)
249
+ && ["pending", "leased", "awaiting-delivery"].includes(item.status));
250
+ if (!runnable) {
251
+ const key = planQueueKey(goal.goalId, step.stepId, "recovery", String(goal.plan.revision));
252
+ if (!runtime.queue.some((item) => item.dedupeKey === key)) {
253
+ const queued = newGoalQueue(goal, step, "follow-up", key, current);
254
+ runtime.queue.push(queued);
255
+ appendReceipt(runtime, "queued", queued.queueId, current, { kind: "follow-up", goalStepId: step.stepId });
256
+ }
257
+ }
258
+ }
259
+ for (const goal of policy.goals.filter((item) => item.status === "active" && item.deadline
260
+ && new Date(item.deadline) <= new Date(current))) {
261
+ const step = currentPlanStep(goal);
262
+ const deadlineAgentId = planStepAgentId(goal, step);
263
+ try { assertActivePersona(personas.policy, personas.runtime, deadlineAgentId, goal.projectId, goal.groupId); }
264
+ catch { continue; }
265
+ const key = "goal:" + goal.goalId + ":deadline:" + goal.deadline;
266
+ if (!runtime.queue.some((item) => item.dedupeKey === key)) runtime.queue.push({
267
+ queueId: "gateway-queue:" + sha256(key).slice(0, 32), dedupeKey: key, kind: "deadline",
268
+ agentId: deadlineAgentId, projectId: goal.projectId, groupId: goal.groupId, goalId: goal.goalId,
269
+ goalStepId: step?.stepId || null, channelEventId: null, priority: PRIORITY.deadline, status: "pending", attempts: 0, lease: null,
270
+ availableAt: current, createdAt: current, updatedAt: current, completedAt: null, lastError: null,
271
+ authority: "execution-state-only"
272
+ });
273
+ }
274
+ for (const event of channel.runtime.events.filter((item) => item.status === "pending")) {
275
+ if (!personas.runtime.personas.some((persona) => persona.personaId === event.agentId && persona.status === "active")) continue;
276
+ const key = "channel:" + event.eventId;
277
+ if (!runtime.queue.some((item) => item.dedupeKey === key)) runtime.queue.push({ queueId: "gateway-queue:" + sha256(key).slice(0, 32),
278
+ dedupeKey: key, kind: "direct-message", agentId: event.agentId, projectId: event.projectId, groupId: event.groupId,
279
+ goalId: null, goalStepId: null, channelEventId: event.eventId, priority: PRIORITY["direct-message"], status: "pending", attempts: 0,
280
+ lease: null, availableAt: current, createdAt: current, updatedAt: current, completedAt: null, lastError: null, authority: "execution-state-only" });
281
+ }
282
+ for (const event of attention.attention.events.filter((item) => item.entityId
283
+ && ((item.kind === "promise" && item.status === "open") || (item.kind === "blocker" && item.status === "resolved")))) {
284
+ try { assertActivePersona(personas.policy, personas.runtime, event.entityId, event.projectId, event.groupId); }
285
+ catch { continue; }
286
+ const kind = event.kind === "promise" ? "promise" : "resolved-blocker";
287
+ const key = "attention:" + event.id + ":" + event.status;
288
+ if (!runtime.queue.some((entry) => entry.dedupeKey === key)) runtime.queue.push({ queueId: "gateway-queue:" + sha256(key).slice(0, 32),
289
+ dedupeKey: key, kind, agentId: event.entityId, projectId: event.projectId, groupId: event.groupId,
290
+ goalId: null, goalStepId: null, channelEventId: null, priority: PRIORITY[kind], status: "pending", attempts: 0, lease: null,
291
+ availableAt: event.dueAt || current, createdAt: current, updatedAt: current, completedAt: null, lastError: null, authority: "execution-state-only" });
292
+ }
293
+ }
294
+ runtime.health.gateway = policy.enabled && !policy.killSwitch ? "running" : "stopped";
295
+ runtime.health.scheduler = "healthy"; runtime.health.queue = "healthy"; runtime.health.lastReconciledAt = current;
296
+ runtime.revision += 1;
297
+ if (policyChanged) await writeGatewayStatePair(policy, runtime);
298
+ else await writeJson(paths.gatewayRuntimePath, runtime);
299
+ return { policy, runtime, recovered: true };
300
+ });
301
+ }
302
+
@@ -0,0 +1,103 @@
1
+ import { loadChannelPolicy, loadChannelRuntime } from "./channel-runtime.js";
2
+ import { loadPersonaRuntime } from "./persona-runtime.js";
3
+ import { assertActivePersona, exactReplyBinding } from "./gateway-runtime-identity.js";
4
+ import { appendReceipt, preserve } from "./gateway-runtime-records.js";
5
+ import { HEALTH_VALUES, emptyPolicy, emptyRuntime, exactId, safeText, timestamp } from "./gateway-common.js";
6
+ import { normalizePolicy, normalizeRuntime, pathsFor, readJson, withLock, writeJson } from "./gateway-state.js";
7
+
8
+ export async function deliverPrepared({ root = process.cwd(), outboxId, adapter, now = new Date() }) {
9
+ if (!adapter || typeof adapter.send !== "function") throw new Error("delivery adapter is unavailable");
10
+ const paths = await pathsFor(root);
11
+ let prepared;
12
+ await withLock(paths, async () => {
13
+ const [policy, runtime, channelPolicy, channelRuntime, personas] = await Promise.all([
14
+ readJson(paths.gatewayPolicyPath, paths.catalog.root, normalizePolicy, emptyPolicy),
15
+ readJson(paths.gatewayRuntimePath, paths.catalog.root, normalizeRuntime, emptyRuntime),
16
+ loadChannelPolicy(paths.catalog.root, paths.catalog), loadChannelRuntime(paths.catalog.root, paths.catalog),
17
+ loadPersonaRuntime(paths.catalog.root, paths.catalog)
18
+ ]);
19
+ if (!policy.enabled || policy.killSwitch) throw new Error("gateway was disabled before delivery");
20
+ const outbox = runtime.outbox.find((item) => item.outboxId === exactId(outboxId, "outboxId"));
21
+ if (!outbox) throw new Error("unknown outbox item");
22
+ if (["delivered", "acknowledged"].includes(outbox.status)) { prepared = { duplicate: true, outbox: structuredClone(outbox) }; return; }
23
+ if (outbox.status !== "prepared" && outbox.status !== "failed") throw new Error("outbox item is not safely retryable");
24
+ if (new Date(outbox.nextAttemptAt) > new Date(timestamp(now))) throw new Error("outbox retry is not due");
25
+ const event = channelRuntime.runtime.events.find((item) => item.eventId === outbox.eventId);
26
+ if (!event) throw new Error("outbox channel event is missing");
27
+ try {
28
+ assertActivePersona(personas.policy, personas.runtime, event.agentId, event.projectId, event.groupId);
29
+ exactReplyBinding(channelPolicy.policy, event);
30
+ }
31
+ catch (error) {
32
+ const current = timestamp(now);
33
+ preserve(runtime, "outbox", outbox, "capability-revoked", current);
34
+ outbox.status = "dead-letter"; outbox.lastError = safeText(error.message, "adapterError", 500);
35
+ outbox.updatedAt = current;
36
+ const queue = runtime.queue.find((item) => item.queueId === outbox.queueId);
37
+ if (queue) { queue.status = "dead-letter"; queue.completedAt = current; queue.updatedAt = current; }
38
+ runtime.health.adapter = "failed"; runtime.revision += 1;
39
+ const receipt = appendReceipt(runtime, "dead-letter", outbox.outboxId, current, { reason: "identity-or-capability-revoked" });
40
+ await writeJson(paths.gatewayRuntimePath, runtime);
41
+ prepared = { duplicate: false, terminal: true, outbox: structuredClone(outbox), receipt };
42
+ return;
43
+ }
44
+ preserve(runtime, "outbox", outbox, "sending", timestamp(now));
45
+ outbox.status = "sending"; outbox.attempts += 1; outbox.updatedAt = timestamp(now); runtime.revision += 1;
46
+ appendReceipt(runtime, "sending", outbox.outboxId, outbox.updatedAt, { attempt: outbox.attempts });
47
+ await writeJson(paths.gatewayRuntimePath, runtime);
48
+ prepared = { duplicate: false, outbox: structuredClone(outbox) };
49
+ });
50
+ if (prepared.duplicate || prepared.terminal) return prepared;
51
+ let outcome;
52
+ try { outcome = await adapter.send(structuredClone(prepared.outbox)); }
53
+ catch (error) { outcome = { ok: false, effect: "unknown", error: error.message }; }
54
+ return withLock(paths, async () => {
55
+ const runtime = await readJson(paths.gatewayRuntimePath, paths.catalog.root, normalizeRuntime, emptyRuntime);
56
+ const outbox = runtime.outbox.find((item) => item.outboxId === prepared.outbox.outboxId);
57
+ if (!outbox || outbox.status !== "sending") throw new Error("outbox sending state changed unexpectedly");
58
+ const current = timestamp(now);
59
+ preserve(runtime, "outbox", outbox, outcome?.ok ? "delivered" : "send-failed", current);
60
+ if (outcome?.ok) {
61
+ outbox.status = "delivered"; outbox.deliveredAt = current; outbox.adapterReceipt = safeText(String(outcome.receipt || "delivered"), "adapterReceipt", 500);
62
+ runtime.health.adapter = "healthy";
63
+ const queue = runtime.queue.find((item) => item.queueId === outbox.queueId);
64
+ if (queue) { queue.status = "completed"; queue.completedAt = current; queue.updatedAt = current; }
65
+ } else if (outcome?.effect === "none") {
66
+ outbox.status = outbox.attempts < 3 ? "failed" : "dead-letter";
67
+ outbox.lastError = safeText(String(outcome.error || "adapter failure"), "adapterError", 500);
68
+ runtime.health.adapter = outbox.status === "failed" ? "degraded" : "failed";
69
+ if (outbox.status === "failed") {
70
+ const delay = Math.min(300000, Number(outcome.retryAfterMs) || 1000 * (2 ** outbox.attempts));
71
+ outbox.nextAttemptAt = new Date(new Date(current).getTime() + delay).toISOString();
72
+ } else {
73
+ const queue = runtime.queue.find((item) => item.queueId === outbox.queueId);
74
+ if (queue) { queue.status = "dead-letter"; queue.completedAt = current; queue.updatedAt = current; }
75
+ }
76
+ } else {
77
+ outbox.status = "delivery-unknown"; outbox.lastError = safeText(String(outcome?.error || "delivery outcome is ambiguous"), "adapterError", 500);
78
+ runtime.health.adapter = "failed";
79
+ }
80
+ outbox.updatedAt = current; runtime.revision += 1;
81
+ const receipt = appendReceipt(runtime, outbox.status, outbox.outboxId, current, { adapterReceipt: outbox.adapterReceipt });
82
+ await writeJson(paths.gatewayRuntimePath, runtime);
83
+ return { outbox, receipt, duplicate: false };
84
+ });
85
+ }
86
+
87
+ export async function updateGatewayHealth({ root = process.cwd(), worker = null, adapter = null, host = null,
88
+ now = new Date() } = {}) {
89
+ const paths = await pathsFor(root);
90
+ return withLock(paths, async () => {
91
+ const runtime = await readJson(paths.gatewayRuntimePath, paths.catalog.root, normalizeRuntime, emptyRuntime);
92
+ for (const [key, value] of Object.entries({ worker, adapter, host })) {
93
+ if (value !== null) {
94
+ if (!HEALTH_VALUES.has(value)) throw new Error("unsupported gateway health value");
95
+ runtime.health[key] = value;
96
+ }
97
+ }
98
+ runtime.health.lastTickAt = timestamp(now); runtime.revision += 1;
99
+ await writeJson(paths.gatewayRuntimePath, runtime);
100
+ return structuredClone(runtime.health);
101
+ });
102
+ }
103
+