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.
- package/CHANGELOG.md +36 -0
- package/README.md +12 -0
- package/agent-spine-plugin/CHANGELOG.md +1 -0
- package/agent-spine-plugin/docs/host-integration.md +15 -1
- package/agent-spine-plugin/docs/preflight-recall.md +1 -1
- package/agent-spine-plugin/scripts/check-install-hook.js +6 -5
- package/agent-spine-plugin/scripts/check-install-king.js +37 -0
- package/agent-spine-plugin/scripts/check-install.js +6 -1
- package/agent-spine-plugin/scripts/release-check.js +3 -2
- package/agent-spine-plugin/src/cli-agent.js +20 -1
- package/agent-spine-plugin/src/cli.js +1 -0
- package/agent-spine-plugin/src/index.js +1 -1
- package/agent-spine-plugin/src/lib/delivery-command-actions.js +16 -7
- package/agent-spine-plugin/src/lib/gateway-control.js +69 -1
- package/agent-spine-plugin/src/lib/gateway-host-fencing.js +92 -0
- package/agent-spine-plugin/src/lib/gateway-host-lifecycle.js +9 -1
- package/agent-spine-plugin/src/lib/gateway-prepared-host.js +28 -0
- package/agent-spine-plugin/src/lib/gateway-runs.js +46 -10
- package/agent-spine-plugin/src/lib/gateway-runtime.js +1 -1
- package/agent-spine-plugin/src/lib/gateway-state.js +3 -1
- package/agent-spine-plugin/src/lib/hook-context.js +8 -3
- package/agent-spine-plugin/src/lib/hook-output.js +2 -3
- package/agent-spine-plugin/src/lib/hook-process-advisory.js +1 -2
- package/agent-spine-plugin/src/lib/host-instruction-budget.js +17 -0
- package/agent-spine-plugin/src/lib/preflight.js +5 -19
- package/agent-spine-plugin/src/lib/source-roots.js +3 -2
- package/agent-spine-plugin/src/worker.js +30 -12
- package/bin/agentspine-king-goal-inbox.mjs +127 -0
- package/bin/agentspine-king-goal-intake.mjs +106 -0
- package/bin/agentspine-king-host-runner.mjs +109 -0
- package/bin/agentspine-king-snapshot-policy.cjs +80 -0
- package/bin/agentspine-king-status-policy.mjs +226 -0
- package/bin/agentspine-king-worker-host.mjs +160 -0
- package/bin/core-bootstrap.js +20 -3
- package/bin/launcher-mode.js +38 -1
- package/bin/launcher-restart-policy.cjs +131 -0
- package/bin/launcher-runtime.js +48 -9
- package/bin/runtime-exit-ledger.cjs +144 -0
- package/bin/runtime-exit-ledger.d.cts +23 -0
- package/bin/windows-node-crash-dump.cjs +289 -0
- package/blun.mjs +83241 -74234
- package/bundled-agent-sources.json +109 -34
- package/codebase-index/codebase_index.py +470 -0
- package/package.json +6 -1
- package/telegram-plugin/dist/bridge.mjs +390 -4
- 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
|
|
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:
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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,
|
|
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
|
-
|
|
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,
|
|
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.
|
|
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 =
|
|
144
|
-
hostRunner = invokeHostRunner,
|
|
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
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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
|
+
};
|