blun-king-cli 9.1.589 → 9.1.595

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 (46) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/README.md +12 -0
  3. package/agent-spine-plugin/CHANGELOG.md +1 -0
  4. package/agent-spine-plugin/docs/host-integration.md +15 -1
  5. package/agent-spine-plugin/docs/preflight-recall.md +1 -1
  6. package/agent-spine-plugin/scripts/check-install-hook.js +6 -5
  7. package/agent-spine-plugin/scripts/check-install-king.js +37 -0
  8. package/agent-spine-plugin/scripts/check-install.js +6 -1
  9. package/agent-spine-plugin/scripts/release-check.js +3 -2
  10. package/agent-spine-plugin/src/cli-agent.js +20 -1
  11. package/agent-spine-plugin/src/cli.js +1 -0
  12. package/agent-spine-plugin/src/index.js +1 -1
  13. package/agent-spine-plugin/src/lib/delivery-command-actions.js +16 -7
  14. package/agent-spine-plugin/src/lib/gateway-control.js +69 -1
  15. package/agent-spine-plugin/src/lib/gateway-host-fencing.js +92 -0
  16. package/agent-spine-plugin/src/lib/gateway-host-lifecycle.js +9 -1
  17. package/agent-spine-plugin/src/lib/gateway-prepared-host.js +28 -0
  18. package/agent-spine-plugin/src/lib/gateway-runs.js +46 -10
  19. package/agent-spine-plugin/src/lib/gateway-runtime.js +1 -1
  20. package/agent-spine-plugin/src/lib/gateway-state.js +3 -1
  21. package/agent-spine-plugin/src/lib/hook-context.js +8 -3
  22. package/agent-spine-plugin/src/lib/hook-output.js +2 -3
  23. package/agent-spine-plugin/src/lib/hook-process-advisory.js +1 -2
  24. package/agent-spine-plugin/src/lib/host-instruction-budget.js +17 -0
  25. package/agent-spine-plugin/src/lib/preflight.js +5 -19
  26. package/agent-spine-plugin/src/lib/source-roots.js +3 -2
  27. package/agent-spine-plugin/src/worker.js +30 -12
  28. package/bin/agentspine-king-goal-inbox.mjs +127 -0
  29. package/bin/agentspine-king-goal-intake.mjs +106 -0
  30. package/bin/agentspine-king-host-runner.mjs +109 -0
  31. package/bin/agentspine-king-snapshot-policy.cjs +80 -0
  32. package/bin/agentspine-king-status-policy.mjs +226 -0
  33. package/bin/agentspine-king-worker-host.mjs +160 -0
  34. package/bin/core-bootstrap.js +20 -3
  35. package/bin/launcher-mode.js +38 -1
  36. package/bin/launcher-restart-policy.cjs +131 -0
  37. package/bin/launcher-runtime.js +48 -9
  38. package/bin/runtime-exit-ledger.cjs +144 -0
  39. package/bin/runtime-exit-ledger.d.cts +23 -0
  40. package/bin/windows-node-crash-dump.cjs +289 -0
  41. package/blun.mjs +83241 -74234
  42. package/bundled-agent-sources.json +109 -34
  43. package/codebase-index/codebase_index.py +470 -0
  44. package/package.json +6 -1
  45. package/telegram-plugin/dist/bridge.mjs +390 -4
  46. package/worker-host.mjs +348023 -0
@@ -1,7 +1,9 @@
1
1
  import { loadChannelPolicy, loadChannelRuntime } from "./channel-runtime.js";
2
+ import { executionBlocks, heldExecution, newExecutionHold, requireHostQuiescence,
3
+ settleHostExecution } from "./gateway-host-fencing.js";
2
4
  import { reviewGroupResponse } from "./gateway-group-response.js";
3
5
  import {
4
- assertGatewayCompletionMode, exactGatewayLeaseGeneration, failGatewayLane,
6
+ AMBIGUOUS_HOST_OUTCOME, assertGatewayCompletionMode, exactGatewayLeaseGeneration, failGatewayLane,
5
7
  markHostStarted, newGatewayLease, normalizeGatewayExecutionMode,
6
8
  requireExactGatewayLease, requireExactHostLease
7
9
  } from "./gateway-host-lifecycle.js";
@@ -20,11 +22,22 @@ import { reviewExecutionResult } from "./gateway-execution.js";
20
22
  import {
21
23
  activateNextPlanStep, conflictingResources, currentPlanStep, effectiveQueuePriority,
22
24
  newGoalQueue, normalizePolicy, normalizeRuntime, pathsFor, planQueueKey,
23
- planStepAgentId, readJson, withLock, writeJson
25
+ planStepAgentId, planStepResources, queuePlanStep, readJson, withLock, writeJson
24
26
  } from "./gateway-state.js";
25
27
 
28
+ export function gatewayWorkMatcher({ agentId = null, projectId = null, groupId, goalOnly = false } = {}) {
29
+ agentId = exactId(agentId, "agentId", true);
30
+ projectId = exactId(projectId, "projectId", true);
31
+ groupId = groupId === undefined ? undefined : exactId(groupId, "groupId", true);
32
+ return (item) => Boolean(item && (agentId === null || item.agentId === agentId)
33
+ && (projectId === null || item.projectId === projectId)
34
+ && (groupId === undefined || item.groupId === groupId)
35
+ && (!goalOnly || (item.goalId && !item.channelEventId)));
36
+ }
37
+
26
38
  export async function claimGatewayWork({ root = process.cwd(), workerId, leaseSeconds = 120,
27
- executionMode = "host-effect", now = new Date() }) {
39
+ executionMode = "host-effect", agentId = null, projectId = null, groupId, goalOnly = false, now = new Date() }) {
40
+ const matchesWork = gatewayWorkMatcher({ agentId, projectId, groupId, goalOnly });
28
41
  const paths = await pathsFor(root);
29
42
  return withLock(paths, async () => {
30
43
  const [policy, runtime, personas] = await Promise.all([
@@ -38,6 +51,7 @@ export async function claimGatewayWork({ root = process.cwd(), workerId, leaseSe
38
51
  const seconds = Number(leaseSeconds);
39
52
  if (!Number.isInteger(seconds) || seconds < 15 || seconds > 900) throw new Error("leaseSeconds must be 15-900");
40
53
  const items = runtime.queue.filter((item) => item.status === "pending" && new Date(item.availableAt) <= new Date(current)
54
+ && matchesWork(item)
41
55
  && !currentLane(runtime, item.agentId));
42
56
  items.sort((a, b) => effectiveQueuePriority(policy, b) - effectiveQueuePriority(policy, a)
43
57
  || a.createdAt.localeCompare(b.createdAt) || a.queueId.localeCompare(b.queueId));
@@ -67,6 +81,8 @@ export async function claimGatewayWork({ root = process.cwd(), workerId, leaseSe
67
81
  const resourceConflict = runtime.queue.some((leased) => leased.status === "leased"
68
82
  && leased.queueId !== candidate.queueId && conflictingResources(policy, candidate, leased).length > 0);
69
83
  if (resourceConflict) continue;
84
+ if (runtime.queue.some((held) => executionBlocks(held, candidate,
85
+ planStepResources(queuePlanStep(policy, candidate))))) continue;
70
86
  item = candidate;
71
87
  break;
72
88
  }
@@ -75,6 +91,8 @@ export async function claimGatewayWork({ root = process.cwd(), workerId, leaseSe
75
91
  preserve(runtime, "queue", item, "leased", current);
76
92
  item.status = "leased"; item.attempts += 1; item.updatedAt = current;
77
93
  item.lease = newGatewayLease(workerId, current, seconds, executionMode);
94
+ if (executionMode === "host-effect") item.executionHold = newExecutionHold(item,
95
+ [...planStepResources(queuePlanStep(policy, item))]);
78
96
  runtime.lanes = runtime.lanes.filter((lane) => lane.agentId !== item.agentId || lane.status !== "leased");
79
97
  runtime.lanes.push({ agentId: item.agentId, queueId: item.queueId, workerId, status: "leased", claimedAt: current,
80
98
  expiresAt: item.lease.expiresAt, updatedAt: current, authority: "execution-state-only" });
@@ -86,7 +104,7 @@ export async function claimGatewayWork({ root = process.cwd(), workerId, leaseSe
86
104
  });
87
105
  }
88
106
 
89
- export async function markGatewayHostStarted({ root = process.cwd(), queueId, workerId, claimedAt, attempt,
107
+ export async function markGatewayHostStarted({ root = process.cwd(), queueId, workerId, claimedAt, attempt, hostSessionId,
90
108
  now = new Date() }) {
91
109
  const paths = await pathsFor(root);
92
110
  return withLock(paths, async () => {
@@ -99,14 +117,17 @@ export async function markGatewayHostStarted({ root = process.cwd(), queueId, wo
99
117
  workerId = exactId(workerId, "workerId"); claimedAt = exactGatewayLeaseGeneration(claimedAt, attempt, "host start");
100
118
  const current = timestamp(now);
101
119
  const lane = requireExactHostLease(runtime, item, { workerId, claimedAt, attempt, current });
120
+ const sessionId = hostSessionId === undefined ? null : exactId(hostSessionId, "hostSessionId");
102
121
  const receipt = markHostStarted(runtime, item, lane, workerId, current, { preserve, appendReceipt });
122
+ item.executionHold.hostSessionId = sessionId;
123
+ item.executionHold.hostStartedAt = current;
103
124
  await writeJson(paths.gatewayRuntimePath, runtime);
104
125
  return { item: structuredClone(item), receipt };
105
126
  });
106
127
  }
107
128
 
108
129
  export async function completeGatewayRun({ root = process.cwd(), queueId, workerId, claimedAt, attempt,
109
- result, now = new Date() }) {
130
+ result, quiescence, now = new Date() }) {
110
131
  const paths = await pathsFor(root);
111
132
  return withLock(paths, async () => {
112
133
  const [policy, runtime, channelPolicy, channelRuntime, personas] = await Promise.all([
@@ -120,11 +141,25 @@ export async function completeGatewayRun({ root = process.cwd(), queueId, worker
120
141
  workerId = exactId(workerId, "workerId");
121
142
  claimedAt = exactGatewayLeaseGeneration(claimedAt, attempt, "run completion"); const current = timestamp(now);
122
143
  const lane = requireExactGatewayLease(runtime, item,
123
- { workerId, claimedAt, attempt, current, action: "run completion" });
124
- const markedHostRun = assertGatewayCompletionMode(item.lease, result);
144
+ { workerId, claimedAt, attempt, current, action: "run completion", allowHeld: true });
145
+ const hold = heldExecution(item);
146
+ const hostProof = requireHostQuiescence(item, quiescence, result, current);
147
+ const markedHostRun = assertGatewayCompletionMode(item.lease ??
148
+ { executionMode: "host-effect", hostStartedAt: hold?.hostStartedAt }, result);
125
149
  const runIdentity = assertActivePersona(personas.policy, personas.runtime, item.agentId, item.projectId, item.groupId);
126
150
  const boundGoal = item.goalId ? policy.goals.find((entry) => entry.goalId === item.goalId) : null;
151
+ const previousGoal = boundGoal ? structuredClone(boundGoal) : null;
127
152
  const boundStep = item.goalStepId ? boundGoal && currentPlanStep(boundGoal) : null;
153
+ if (hostProof && boundGoal && policy.goals.some((goal) => goal.goalId !== boundGoal.goalId
154
+ && goal.agentId === boundGoal.agentId && goal.status === "active")) {
155
+ result = { ...result, completed: false, blocked: true, knowledgeGap: null, selfHelp: null,
156
+ blocker: "Checkpoint saved; another focused goal was assigned while this host was unresolved." };
157
+ }
158
+ if (hostProof && boundGoal?.status === "blocked" && boundGoal.blocker === AMBIGUOUS_HOST_OUTCOME
159
+ && (!boundStep || boundStep.blocker === AMBIGUOUS_HOST_OUTCOME)) {
160
+ boundGoal.status = "active"; boundGoal.blocker = null;
161
+ if (boundStep) { boundStep.status = "active"; boundStep.blocker = null; }
162
+ }
128
163
  if (item.goalStepId) {
129
164
  if (!boundGoal?.plan || boundGoal.status !== "active" || boundStep?.stepId !== item.goalStepId
130
165
  || boundStep.status !== "active" || item.agentId !== planStepAgentId(boundGoal, boundStep)) {
@@ -191,7 +226,7 @@ export async function completeGatewayRun({ root = process.cwd(), queueId, worker
191
226
  ? "blocked" : "completed"; item.completedAt = current;
192
227
  const goal = boundGoal;
193
228
  if (goal) {
194
- policy.history.push({ kind: "goal", at: current, value: structuredClone(goal), authority: "authenticated-goal-policy" });
229
+ policy.history.push({ kind: "goal", at: current, value: previousGoal, authority: "authenticated-goal-policy" });
195
230
  const checkpoint = resultCheckpoint;
196
231
  goal.checkpoint = checkpoint; goal.heartbeatAt = current;
197
232
  goal.blocker = result?.blocked ? safeText(result.blocker || "Run blocked.", "blocker", 500)
@@ -275,12 +310,13 @@ export async function completeGatewayRun({ root = process.cwd(), queueId, worker
275
310
  }
276
311
  }
277
312
  item.lease = null; item.updatedAt = current; lane.status = "completed"; lane.updatedAt = current;
313
+ const hostSettlement = settleHostExecution(runtime, item, hostProof, current, appendReceipt);
278
314
  if (markedHostRun) runtime.health.host = "healthy";
279
315
  runtime.health.worker = "healthy"; runtime.health.lastTickAt = current;
280
316
  appendReceipt(runtime, "run-terminal", item.queueId, current, { status: item.status, goalStepId: item.goalStepId || null }); runtime.revision += 1;
281
317
  await writeGatewayStatePair(policy, runtime);
282
318
  return { item, outbox: runtime.outbox.find((entry) => entry.queueId === item.queueId) || null,
283
- clarification, exploration, selfHelp, selfHelpRequired, executionReview, premortemReview, communication };
319
+ clarification, exploration, selfHelp, selfHelpRequired, executionReview, premortemReview, communication, hostSettlement };
284
320
  });
285
321
  }
286
322
 
@@ -296,7 +332,7 @@ export async function failGatewayRun({ root = process.cwd(), queueId, workerId,
296
332
  workerId = exactId(workerId, "workerId"); claimedAt = exactGatewayLeaseGeneration(claimedAt, attempt, "run failure");
297
333
  const current = timestamp(now);
298
334
  const lane = requireExactGatewayLease(runtime, item,
299
- { workerId, claimedAt, attempt, current, action: "run failure" });
335
+ { workerId, claimedAt, attempt, current, action: "run failure", allowHeld: true });
300
336
  const message = safeText(String(error || "host runtime unavailable"), "runError", 500);
301
337
  const delay = Number(retryAfterMs);
302
338
  if (!Number.isFinite(delay) || delay < 250 || delay > 300000) throw new Error("retryAfterMs must be 250-300000");
@@ -6,7 +6,7 @@ export {
6
6
  export { executionAttemptForStep } from "./gateway-execution.js";
7
7
  export {
8
8
  assignGoal, enqueueGatewayWake, reconcileGateway, resolveGoalKnowledgeGap,
9
- setGatewayControl
9
+ reviewLegacyGatewayHold, setGatewayControl
10
10
  } from "./gateway-control.js";
11
11
  export {
12
12
  claimGatewayWork, completeGatewayRun, failGatewayRun, markGatewayHostStarted
@@ -1,4 +1,5 @@
1
1
  import { join } from "node:path";
2
+ import { retainLegacyExecutionHolds, validExecutionHold } from "./gateway-host-fencing.js";
2
3
  import { buildCatalog } from "./catalog.js";
3
4
  import { matchesGatewayLaneLease, validGatewayLeaseExecution } from "./gateway-host-lifecycle.js";
4
5
  import * as goalPremortem from "./gateway-premortem.js";
@@ -181,6 +182,7 @@ export function validQueue(item) {
181
182
  && Number.isFinite(new Date(item.availableAt).getTime()) && Number.isFinite(new Date(item.updatedAt).getTime())
182
183
  && (item.completedAt === null || Number.isFinite(new Date(item.completedAt).getTime()))
183
184
  && (item.lastError === null || (typeof item.lastError === "string" && item.lastError.length <= 500 && !SECRET_RE.test(item.lastError)))
185
+ && validExecutionHold(item)
184
186
  && (item.lease === null || (item.status === "leased" && ID_RE.test(item.lease.workerId || "")
185
187
  && Number.isFinite(new Date(item.lease.claimedAt).getTime())
186
188
  && Number.isFinite(new Date(item.lease.expiresAt).getTime())
@@ -269,6 +271,7 @@ export function normalizeRuntime(value, root) {
269
271
  if (receiptIndex >= 0) invalid.push("receipts:" + receiptIndex);
270
272
  if (historyIndex >= 0) invalid.push("history:" + historyIndex);
271
273
  if (invalid.length) throw new Error("gateway runtime is invalid (" + invalid.join(",") + "); worker is disabled");
274
+ retainLegacyExecutionHolds(value);
272
275
  return value;
273
276
  }
274
277
 
@@ -339,4 +342,3 @@ export function gatewayRuntimeFindings(policy, runtime) {
339
342
  if (!validHealth(runtime.health)) findings.push("invalid-gateway-health");
340
343
  return findings;
341
344
  }
342
-
@@ -7,6 +7,7 @@ import { resolveSessionJob, startOrResumeJob } from "./selfstarter.js";
7
7
 
8
8
  const STANDARD_HOST_CONTEXT_BYTES = 9500;
9
9
  const MAX_CLAUDE_OVERFLOW_CONTEXT_BYTES = 32 * 1024;
10
+ const CODEX_CONTEXT_ENVELOPE_BYTES = 32 * 1024;
10
11
  const ID_RE = /^[A-Za-z0-9][A-Za-z0-9:_.@/-]{0,127}$/;
11
12
  export const ATTENTION_WRITE_EVENTS = new Set(["UserPromptSubmit", "PostToolUse", "Stop", "SubagentStop"]);
12
13
  const SELFSTART_EVENTS = new Set(["SessionStart", "PostCompact"]);
@@ -43,9 +44,13 @@ export function promptFromInput(input) {
43
44
  }
44
45
 
45
46
  export function hostContextLimit(preflight) {
46
- return preflight?.receipt?.instructionBudget?.mode === "claude-required-overflow"
47
- ? MAX_CLAUDE_OVERFLOW_CONTEXT_BYTES
48
- : STANDARD_HOST_CONTEXT_BYTES;
47
+ // Source budgets bound the content; JSON escaping must not create another gate.
48
+ if (preflight?.receipt?.instructionHost === "codex") {
49
+ return CODEX_CONTEXT_ENVELOPE_BYTES + Buffer.byteLength(JSON.stringify(preflight.briefing || {}));
50
+ }
51
+ const mode = preflight?.receipt?.instructionBudget?.mode;
52
+ if (mode === "claude-required-overflow") return MAX_CLAUDE_OVERFLOW_CONTEXT_BYTES;
53
+ return STANDARD_HOST_CONTEXT_BYTES;
49
54
  }
50
55
 
51
56
  export function hostFromInput(input) {
@@ -208,7 +208,7 @@ export function blunRuntimeMessage(context) {
208
208
 
209
209
  export function hookOutput(event, context, env = process.env) {
210
210
  if (env.BLUN_PLUGIN_ROOT) {
211
- return { hookSpecificOutput: { hookEventName: event, message: blunRuntimeMessage(context) } };
211
+ return { hookSpecificOutput: { hookEventName: event, additionalContext: blunRuntimeMessage(context) } };
212
212
  }
213
213
  return { hookSpecificOutput: { hookEventName: event, additionalContext: context } };
214
214
  }
@@ -259,6 +259,5 @@ export function lifecycleOutput(event, artifactGuard, premortem, deliveryVerific
259
259
  }));
260
260
  }
261
261
  if (!messages.length) return {};
262
- const field = env.BLUN_PLUGIN_ROOT ? "message" : "additionalContext";
263
- return { hookSpecificOutput: { hookEventName: event, [field]: messages.join("\n") } };
262
+ return { hookSpecificOutput: { hookEventName: event, additionalContext: messages.join("\n") } };
264
263
  }
@@ -29,9 +29,8 @@ export async function processAdvisory(input, payload, root, scope, details) {
29
29
  diagnostic: fresh ? diagnostic : null
30
30
  };
31
31
  if (payload) return result;
32
- const field = process.env.BLUN_PLUGIN_ROOT ? "message" : "additionalContext";
33
32
  const output = fresh ? { hookSpecificOutput: {
34
- hookEventName: event, [field]: JSON.stringify(diagnostic)
33
+ hookEventName: event, additionalContext: JSON.stringify(diagnostic)
35
34
  } } : {};
36
35
  process.stdout.write(`${JSON.stringify(output)}\n`);
37
36
  }
@@ -0,0 +1,17 @@
1
+ const STANDARD_BYTES = 8 * 1024;
2
+ export const KING_SOURCE_MAX_BYTES = 4 * 1024 * 1024;
3
+ export const KING_TOTAL_MAX_BYTES = 8 * 1024 * 1024;
4
+
5
+ export function isKingHost(env = process.env) {
6
+ return Boolean(env.BLUN_PLUGIN_ROOT && env.BLUN_HOME);
7
+ }
8
+
9
+ export function instructionBudget(host, usedBytes = 0, env = process.env) {
10
+ const hardLimitBytes = host === "codex" && isKingHost(env) ? KING_TOTAL_MAX_BYTES
11
+ : host === "claude" ? 16 * 1024 : host === "codex" ? 32 * 1024 : STANDARD_BYTES;
12
+ const overflowBytes = Math.max(0, usedBytes - STANDARD_BYTES);
13
+ return {
14
+ mode: overflowBytes ? `${host}-required-overflow` : "standard",
15
+ standardBytes: STANDARD_BYTES, hardLimitBytes, usedBytes, overflowBytes
16
+ };
17
+ }
@@ -7,6 +7,7 @@ import { isFileLockContention, replaceFileWithRetry } from "./filesystem-retry.j
7
7
  import { ancestorsBetween, isInside, stateRoot } from "./paths.js";
8
8
  import { preflightDeliveryId } from "./preflight-delivery-id.js";
9
9
  import { resolveHostSourceCatalog } from "./source-roots.js";
10
+ import { instructionBudget } from "./host-instruction-budget.js";
10
11
 
11
12
  export const PREFLIGHT_SCHEMA = "agentspine.preflight/v2";
12
13
  export const PREFLIGHT_POLICY_SCHEMA = "agentspine.preflight-policy/v1";
@@ -22,8 +23,6 @@ const ENV_RE = /^[A-Z_][A-Z0-9_]{0,127}$/;
22
23
  const MAX_POLICY_BYTES = 1024 * 1024;
23
24
  const MAX_STATE_BYTES = 8 * 1024 * 1024;
24
25
  const MAX_PROVIDER_BYTES = 1024 * 1024;
25
- const STANDARD_REQUIRED_INSTRUCTIONS_BYTES = 8 * 1024;
26
- const MAX_CLAUDE_REQUIRED_INSTRUCTIONS_BYTES = 16 * 1024;
27
26
  const MAX_REQUIRED_MEMORY_BYTES = 6 * 1024;
28
27
  const RECEIPT_TTL_MS = 60_000;
29
28
  const FORBIDDEN_MEMORY = /-----BEGIN [A-Z ]*PRIVATE KEY-----|\b(?:sk|gh[opusu])_[A-Za-z0-9_-]{20,}\b|\b(?:password|passwort|secret|token|api[-_ ]?key|credential|permission|rights?|roles?|delegat|authoriz|berechtig|freigabe|approval|tool access|file access|network|production|payment|zahlung|policy)\b/i;
@@ -378,19 +377,6 @@ function instructionDocuments(catalog, host) {
378
377
  return catalog.documents.filter((item) => item.layer === "constitution" && pattern.test(item.relativePath))
379
378
  .sort((left, right) => left.precedence - right.precedence || left.relativePath.localeCompare(right.relativePath));
380
379
  }
381
- function instructionBudget(host, usedBytes = 0) {
382
- const hardLimitBytes = host === "claude"
383
- ? MAX_CLAUDE_REQUIRED_INSTRUCTIONS_BYTES
384
- : STANDARD_REQUIRED_INSTRUCTIONS_BYTES;
385
- const overflowBytes = Math.max(0, usedBytes - STANDARD_REQUIRED_INSTRUCTIONS_BYTES);
386
- return {
387
- mode: overflowBytes ? "claude-required-overflow" : "standard",
388
- standardBytes: STANDARD_REQUIRED_INSTRUCTIONS_BYTES,
389
- hardLimitBytes,
390
- usedBytes,
391
- overflowBytes
392
- };
393
- }
394
380
  async function rejectKnownInstructionSymlinks(resolvedSources, host) {
395
381
  const candidates = host === "claude"
396
382
  ? [join(resolvedSources.hostHome, "CLAUDE.md"), ...ancestorsBetween(resolvedSources.projectRoot, resolvedSources.cwd)
@@ -505,7 +491,7 @@ export async function runPreflight({ input, scope, resolvedSources, prompt, now
505
491
  const documents = instructionDocuments(resolvedSources.catalog, instructionHost);
506
492
  const requiredInstructions = [];
507
493
  let instructionBytes = 0;
508
- const maximumInstructionBytes = instructionBudget(instructionHost).hardLimitBytes;
494
+ const maximumInstructionBytes = instructionBudget(instructionHost, 0, env).hardLimitBytes;
509
495
  for (const document of documents) {
510
496
  const allowedRoot = document.sourceScope === "user" ? resolvedSources.hostHome : resolvedSources.projectRoot;
511
497
  const snapshot = await safeReadRequired(document.path, allowedRoot, maximumInstructionBytes, fileHooks);
@@ -517,7 +503,7 @@ export async function runPreflight({ input, scope, resolvedSources, prompt, now
517
503
  requiredInstructions.push({ path: document.path, displayPath: document.relativePath, scope: document.sourceScope,
518
504
  bytes: snapshot.bytes, sha256: snapshot.sha256, identity: snapshot.identity, content: snapshot.content });
519
505
  }
520
- const appliedInstructionBudget = instructionBudget(instructionHost, instructionBytes);
506
+ const appliedInstructionBudget = instructionBudget(instructionHost, instructionBytes, env);
521
507
  const memoryState = validateMemories(await readJson(paths.memories, MAX_STATE_BYTES, emptyMemories));
522
508
  const mustRemember = memoryState.entries.filter((item) => memoryMatches(item, exactScope));
523
509
  if (Buffer.byteLength(JSON.stringify(mustRemember.map((item) => item.claim))) > MAX_REQUIRED_MEMORY_BYTES) {
@@ -620,7 +606,7 @@ export async function verifyPreflightReceipt({ receipt, input, scope, resolvedSo
620
606
  if (currentDocuments.length !== receipt.instructionFiles.length
621
607
  || currentDocuments.some((document, index) => document.path !== receipt.instructionFiles[index]?.path)) return false;
622
608
  let instructionBytes = 0;
623
- const maximumInstructionBytes = instructionBudget(receipt.instructionHost).hardLimitBytes;
609
+ const maximumInstructionBytes = instructionBudget(receipt.instructionHost, 0, env).hardLimitBytes;
624
610
  for (const instruction of receipt.instructionFiles) {
625
611
  const allowedRoot = instruction.scope === "user" ? freshSources.hostHome : freshSources.projectRoot;
626
612
  const currentSnapshot = await safeReadRequired(instruction.path, allowedRoot, maximumInstructionBytes);
@@ -629,7 +615,7 @@ export async function verifyPreflightReceipt({ receipt, input, scope, resolvedSo
629
615
  instructionBytes += currentSnapshot.bytes;
630
616
  }
631
617
  if (instructionBytes > maximumInstructionBytes
632
- || canonical(receipt.instructionBudget) !== canonical(instructionBudget(receipt.instructionHost, instructionBytes))) return false;
618
+ || canonical(receipt.instructionBudget) !== canonical(instructionBudget(receipt.instructionHost, instructionBytes, env))) return false;
633
619
  } catch { return false; }
634
620
  if (consume) {
635
621
  const paths = storagePaths(env);
@@ -8,6 +8,7 @@ import { isFileLockContention, replaceFileWithRetry } from "./filesystem-retry.j
8
8
  import { purgeIndexedMemoryCache, resolveIndexedMemory } from "./indexed-memory.js";
9
9
  import { hookMemoryQuery, loadLessonRecallSelection, rememberLessonRecallSelection } from "./lesson-recall-session.js";
10
10
  import { catalogScanPolicy } from "./catalog.js";
11
+ import { isKingHost, KING_SOURCE_MAX_BYTES } from "./host-instruction-budget.js";
11
12
  import {
12
13
  SOURCE_SCAN_INCOMPLETE, boundedMarkdownTree, existingDirectory, existingRegular, sourceScanError
13
14
  } from "./source-tree-scan.js";
@@ -358,10 +359,10 @@ export async function resolveHostSourceCatalog({ host, cwd = process.cwd(), inpu
358
359
  let hostDetails = {};
359
360
  let rootResolution = "explicit-root";
360
361
  if (host === "codex") {
361
- const codexHome = env.BLUN_PLUGIN_ROOT && env.BLUN_HOME
362
- ? env.BLUN_HOME : env.CODEX_HOME || env.BLUN_HOME || join(homedir(), ".codex");
362
+ const codexHome = isKingHost(env) ? env.BLUN_HOME : env.CODEX_HOME || env.BLUN_HOME || join(homedir(), ".codex");
363
363
  hostHome = await existingDirectory(resolve(codexHome)) || resolve(codexHome);
364
364
  const config = await codexConfig(hostHome);
365
+ if (isKingHost(env)) config.maxBytes = KING_SOURCE_MAX_BYTES;
365
366
  if (env.AGENTSPINE_ROOT) projectRoot = await canonicalPath(env.AGENTSPINE_ROOT);
366
367
  else ({ root: projectRoot, resolution: rootResolution } = await findRoot(canonicalCwd, config.rootMarkers));
367
368
  sources = await codexSources({ cwd: canonicalCwd, projectRoot, codexHome: hostHome, config });
@@ -14,6 +14,8 @@ import { groupResponseContract, settleSuppressedGroupEvents } from "./lib/gatewa
14
14
  import { loadPersonaRuntime, syncPersonaRosterFromEnvironment } from "./lib/persona-runtime.js";
15
15
  import { selfHelpPolicyForWorkItem } from "./lib/knowledge-evidence.js";
16
16
  import { isMainModule } from "./lib/runtime.js";
17
+ import { executePreparedHost } from "./lib/gateway-prepared-host.js";
18
+ import { gatewayWorkMatcher } from "./lib/gateway-runs.js";
17
19
 
18
20
  const MAX_FRAME = 64 * 1024;
19
21
  const WAKE_FILES = new Set(["attention.json", "channel-runtime.json", "gateway-policy.json", "persona-policy.json", "persona-runtime.json"]);
@@ -140,8 +142,14 @@ async function hostWorkItem(root, item) {
140
142
  };
141
143
  }
142
144
 
143
- export async function runWorkerTick({ root = process.cwd(), workerId = "gateway-worker:local", now = new Date(),
144
- hostRunner = invokeHostRunner, adapter = null, env = process.env } = {}) {
145
+ export async function runWorkerTick({ root = process.cwd(), workerId = "gateway-worker:local", now = null,
146
+ hostRunner = invokeHostRunner, hostFactory = null, agentId = null, projectId = null, groupId, goalOnly = false,
147
+ clock = null, adapter = null, env = process.env } = {}) {
148
+ // Explicit `now` remains a deterministic test clock; production reads the clock at each boundary.
149
+ const fixedTime = now;
150
+ const currentTime = clock ?? (fixedTime === null ? () => new Date() : () => fixedTime);
151
+ const matchesWork = gatewayWorkMatcher({ agentId, projectId, groupId, goalOnly });
152
+ now = currentTime();
145
153
  const deliveryAdapter = adapter || createTelegramAdapter({ root, env });
146
154
  await syncPersonaRosterFromEnvironment({ root, env, now });
147
155
  const initial = await loadGatewayRuntime(root);
@@ -157,7 +165,9 @@ export async function runWorkerTick({ root = process.cwd(), workerId = "gateway-
157
165
  await reconcileGateway({ root, now });
158
166
  await updateGatewayHealth({ root, worker: "healthy", adapter: "healthy", now });
159
167
  const afterReconcile = await loadGatewayRuntime(root);
168
+ const queueById = new Map(afterReconcile.runtime.queue.map((item) => [item.queueId, item]));
160
169
  const dueOutbox = afterReconcile.runtime.outbox.filter((item) => ["prepared", "failed"].includes(item.status)
170
+ && matchesWork(queueById.get(item.queueId))
161
171
  && new Date(item.nextAttemptAt) <= new Date(now)).sort((a, b) => a.nextAttemptAt.localeCompare(b.nextAttemptAt)
162
172
  || a.createdAt.localeCompare(b.createdAt) || a.outboxId.localeCompare(b.outboxId))[0] || null;
163
173
  if (dueOutbox) {
@@ -168,20 +178,28 @@ export async function runWorkerTick({ root = process.cwd(), workerId = "gateway-
168
178
  return { status: delivery.outbox.status, processed: true, outboxId: delivery.outbox.outboxId,
169
179
  recoveredDelivery: true };
170
180
  }
171
- const claim = await claimGatewayWork({ root, workerId, executionMode: "host-effect", now });
181
+ const claim = await claimGatewayWork({ root, workerId, agentId, projectId, groupId, goalOnly, executionMode: "host-effect", now });
172
182
  if (!claim.item) return { status: claim.reason, processed: false };
173
- await markGatewayHostStarted({ root, queueId: claim.item.queueId, workerId,
174
- claimedAt: claim.item.lease.claimedAt, attempt: claim.item.attempts, now });
175
- let result;
176
- try { result = await hostRunner(await hostWorkItem(root, claim.item), { env }); }
183
+ let result; let quiescence;
184
+ try {
185
+ const item = await hostWorkItem(root, claim.item);
186
+ if (hostFactory) ({ result, quiescence } = await executePreparedHost({
187
+ root, item, hostFactory, env, clock: currentTime
188
+ }));
189
+ else {
190
+ await markGatewayHostStarted({ root, queueId: item.queueId, workerId,
191
+ claimedAt: item.lease.claimedAt, attempt: item.attempts, now });
192
+ result = await hostRunner(item, { env });
193
+ }
194
+ }
177
195
  catch (error) {
178
196
  const failed = await failGatewayRun({ root, queueId: claim.item.queueId, workerId,
179
197
  claimedAt: claim.item.lease.claimedAt, attempt: claim.item.attempts,
180
- error: "Host runtime unavailable: " + String(error.message).slice(0, 400), now });
198
+ error: "Host runtime unavailable: " + String(error.message).slice(0, 400), now: currentTime() });
181
199
  return { status: failed.item.status, processed: true, queueId: failed.item.queueId, retryAt: failed.item.availableAt };
182
200
  }
183
201
  const completed = await completeGatewayRun({ root, queueId: claim.item.queueId, workerId,
184
- claimedAt: claim.item.lease.claimedAt, attempt: claim.item.attempts, result, now });
202
+ claimedAt: claim.item.lease.claimedAt, attempt: claim.item.attempts, result, quiescence, now: currentTime() });
185
203
  if (completed.communication?.suppressed) {
186
204
  await settleSuppressedGroupEvents({ root, now });
187
205
  return { status: "silent", processed: true, queueId: completed.item.queueId,
@@ -191,17 +209,17 @@ export async function runWorkerTick({ root = process.cwd(), workerId = "gateway-
191
209
  status: completed.clarification ? "needs-clarification"
192
210
  : completed.exploration ? "exploring" : completed.selfHelp ? "self-help-resolved"
193
211
  : completed.selfHelpRequired ? "self-help-required" : completed.item.status,
194
- processed: true, queueId: completed.item.queueId,
212
+ processed: true, queueId: completed.item.queueId, hostSettlement: completed.hostSettlement,
195
213
  ...(completed.clarification ? { clarification: completed.clarification } : {}),
196
214
  ...(completed.exploration ? { exploration: completed.exploration } : {}),
197
215
  ...(completed.selfHelp ? { selfHelp: completed.selfHelp } : {}),
198
216
  ...(completed.selfHelpRequired ? { selfHelpRequired: completed.selfHelpRequired } : {})
199
217
  };
200
218
  const delivery = await deliverPrepared({ root, outboxId: completed.outbox.outboxId,
201
- adapter: deliveryAdapter, now });
219
+ adapter: deliveryAdapter, now: currentTime() });
202
220
  if (delivery.outbox.status === "delivered") await acknowledgeChannelDelivery({ root,
203
221
  eventId: delivery.outbox.eventId, bindingId: delivery.outbox.bindingId,
204
- deliveryReceiptId: delivery.receipt.id, now });
222
+ deliveryReceiptId: delivery.receipt.id, now: currentTime() });
205
223
  return { status: delivery.outbox.status, processed: true, queueId: completed.item.queueId, outboxId: delivery.outbox.outboxId };
206
224
  }
207
225
 
@@ -0,0 +1,127 @@
1
+ import { open, lstat, mkdir, readFile, rename, writeFile } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { createHash } from 'node:crypto';
4
+
5
+ import { ingestGoalInstruction, parseGoalInstruction } from './agentspine-king-goal-intake.mjs';
6
+
7
+ const CHECKPOINT_SCHEMA = 'blun.king-goal-queue-checkpoint/v1';
8
+ const MAX_READ_BYTES = 4 * 1024 * 1024;
9
+
10
+ function queueIdentity(metadata) {
11
+ return `${metadata.dev}:${metadata.ino}:${metadata.birthtimeMs}`;
12
+ }
13
+
14
+ async function readCheckpoint(filePath) {
15
+ try {
16
+ const value = JSON.parse(await readFile(filePath, 'utf8'));
17
+ if (value?.schema === CHECKPOINT_SCHEMA && typeof value.fileId === 'string'
18
+ && Number.isSafeInteger(value.offset) && value.offset >= 0 && Array.isArray(value.accepted)
19
+ && value.accepted.length <= 512 && value.accepted.every(item => item && /^[a-f0-9]{64}$/.test(item.sha256)
20
+ && typeof item.goalId === 'string' && item.goalId.length > 0)) return value;
21
+ throw new Error('GOAL_QUEUE_CHECKPOINT_INVALID');
22
+ } catch (error) {
23
+ if (error?.code !== 'ENOENT') throw error;
24
+ }
25
+ return { schema: CHECKPOINT_SCHEMA, fileId: '', offset: 0, lastMessageId: null, accepted: [] };
26
+ }
27
+
28
+ async function writeCheckpoint(filePath, value) {
29
+ await mkdir(path.dirname(filePath), { recursive: true });
30
+ const temporary = `${filePath}.${process.pid}.${Date.now()}.tmp`;
31
+ await writeFile(temporary, `${JSON.stringify(value, null, 2)}\n`, {
32
+ encoding: 'utf8', flag: 'wx', mode: 0o600,
33
+ });
34
+ await rename(temporary, filePath);
35
+ }
36
+
37
+ function parseQueueLine(line) {
38
+ let envelope;
39
+ try { envelope = JSON.parse(line); } catch { return null; }
40
+ if (!envelope || typeof envelope.text !== 'string' || !envelope.meta || Array.isArray(envelope.meta)
41
+ || typeof envelope.meta !== 'object') return null;
42
+ return envelope;
43
+ }
44
+
45
+ function isAuthorizedInstruction(envelope, input) {
46
+ const addressed = envelope.meta.addressed === true || envelope.meta.addressed === 'true';
47
+ const threads = ['thread_id', 'telegram_thread_id', 'message_thread_id']
48
+ .map(key => envelope.meta[key]).filter(value => value !== null && value !== undefined && value !== '').map(String);
49
+ const thread = input.instructionThreadId === null || input.instructionThreadId === undefined ? null : String(input.instructionThreadId);
50
+ return addressed && String(envelope.meta.user_id) === String(input.instructionSenderId)
51
+ && String(envelope.meta.chat_id) === String(input.instructionChatId)
52
+ && (threads.length > 0 ? threads.every(value => value === thread) : thread === null);
53
+ }
54
+
55
+ async function queueChunk(filePath, checkpoint) {
56
+ const metadata = await lstat(filePath);
57
+ if (!metadata.isFile() || metadata.isSymbolicLink()) throw new Error('GOAL_QUEUE_NOT_REGULAR');
58
+ const fileId = queueIdentity(metadata);
59
+ const offset = checkpoint.fileId === fileId && metadata.size >= checkpoint.offset ? checkpoint.offset : 0;
60
+ const bytes = Math.min(MAX_READ_BYTES, metadata.size - offset);
61
+ if (bytes <= 0) return { fileId, offset, lines: [] };
62
+ const handle = await open(filePath, 'r');
63
+ try {
64
+ const buffer = Buffer.alloc(bytes);
65
+ const { bytesRead } = await handle.read(buffer, 0, bytes, offset);
66
+ const content = buffer.subarray(0, bytesRead);
67
+ const lastNewline = content.lastIndexOf(10);
68
+ if (lastNewline < 0) {
69
+ if (bytesRead >= MAX_READ_BYTES) throw new Error('GOAL_QUEUE_LINE_TOO_LARGE');
70
+ return { fileId, offset, lines: [] };
71
+ }
72
+ const complete = content.subarray(0, lastNewline + 1);
73
+ return { fileId, offset, lines: complete.toString('utf8').split('\n').slice(0, -1) };
74
+ } finally { await handle.close(); }
75
+ }
76
+
77
+ async function ingestGoalInbox(input) {
78
+ const checkpoint = await readCheckpoint(input.checkpointFile);
79
+ let cursor = checkpoint.offset;
80
+ const results = [];
81
+ const chunk = await queueChunk(input.queueFile, checkpoint);
82
+ if (chunk.offset !== checkpoint.offset || chunk.fileId !== checkpoint.fileId) cursor = chunk.offset;
83
+ for (const line of chunk.lines) {
84
+ cursor += Buffer.byteLength(line, 'utf8') + 1;
85
+ const envelope = parseQueueLine(line);
86
+ if (!envelope || !isAuthorizedInstruction(envelope, input)
87
+ || !/\bZIEL-[A-Za-z0-9._-]+\.json\b/u.test(envelope.text)) continue;
88
+ try {
89
+ const instruction = parseGoalInstruction(envelope.text);
90
+ const accepted = checkpoint.accepted.find((item) => item.sha256 === instruction.sha256);
91
+ if (accepted) {
92
+ results.push({ status: 'duplicate', messageId: String(envelope.meta.message_id),
93
+ sha256: instruction.sha256, goal: { goalId: accepted.goalId } });
94
+ continue;
95
+ }
96
+ const ingested = await ingestGoalInstruction({ ...input, instructionText: envelope.text });
97
+ results.push({ status: ingested.duplicate ? 'duplicate' : 'ingested',
98
+ messageId: String(envelope.meta.message_id), ...ingested });
99
+ checkpoint.accepted.push({ sha256: ingested.sha256, goalId: ingested.goal.goalId });
100
+ checkpoint.accepted = checkpoint.accepted.slice(-512);
101
+ } catch (error) {
102
+ results.push({ status: 'rejected', messageId: String(envelope.meta.message_id),
103
+ error: String(error?.message || error).slice(0, 200) });
104
+ }
105
+ const result = results.at(-1);
106
+ result.eventId = createHash('sha256').update(line).digest('hex');
107
+ // Keep status persistence outside the ingestion catch and before advancing the durable cursor.
108
+ await input.recordResult?.(result);
109
+ }
110
+ await writeCheckpoint(input.checkpointFile, {
111
+ schema: CHECKPOINT_SCHEMA,
112
+ fileId: chunk.fileId,
113
+ offset: cursor,
114
+ lastMessageId: results.at(-1)?.messageId || checkpoint.lastMessageId || null,
115
+ accepted: checkpoint.accepted,
116
+ updatedAt: new Date().toISOString(),
117
+ });
118
+ return results;
119
+ }
120
+
121
+ export {
122
+ CHECKPOINT_SCHEMA,
123
+ ingestGoalInbox,
124
+ isAuthorizedInstruction,
125
+ parseQueueLine,
126
+ queueIdentity,
127
+ };