larkin 0.3.3 → 0.4.1
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/dist/agent/agent-state-store.cjs +127 -21
- package/dist/agent/agent-state-store.mjs +128 -22
- package/dist/agent/agent-transport.cjs +6 -4
- package/dist/agent/agent-transport.mjs +6 -4
- package/dist/agent/host-reminder-orchestrator.cjs +9 -3
- package/dist/agent/host-reminder-orchestrator.mjs +9 -3
- package/dist/agent/inbox-projection.cjs +114 -23
- package/dist/agent/inbox-projection.mjs +112 -23
- package/dist/feishu/document-comment.cjs +2 -1
- package/dist/feishu/document-comment.mjs +2 -1
- package/dist/feishu/host-business-state.cjs +7 -2
- package/dist/feishu/host-business-state.mjs +7 -2
- package/dist/feishu/host-shell.cjs +66 -31
- package/dist/feishu/host-shell.mjs +66 -31
- package/dist/runtime/runtime-host.cjs +173 -10
- package/dist/runtime/runtime-host.mjs +173 -10
- package/package.json +1 -1
|
@@ -160,6 +160,16 @@ export function createHostShell({ env = process.env, runtimeHost, channelPackage
|
|
|
160
160
|
return store;
|
|
161
161
|
};
|
|
162
162
|
const hostState = new HostStateProjection(stateStore, log);
|
|
163
|
+
const recordInboundDeliveryFailure = (agent, code) => {
|
|
164
|
+
const at = new Date().toISOString();
|
|
165
|
+
const reason = "Inbound Runtime delivery failed non-retryably; durable Inbox/ledger state is retained for recovery.";
|
|
166
|
+
const nextAction = "Inspect the delivery/status error, correct the Runtime or canonical Inbox state, then restart to replay safely.";
|
|
167
|
+
hostState.updateStatus(agent, {
|
|
168
|
+
inboundDeliveryHealth: { state: "error", code, at, reason, nextAction },
|
|
169
|
+
runtimeReadiness: { runtime: agent.runtime, state: "incompatible", reason, nextAction },
|
|
170
|
+
});
|
|
171
|
+
hostState.recordStatusError(agent, `${reason} ${nextAction} code=${code}`);
|
|
172
|
+
};
|
|
163
173
|
const agentStates = new Map();
|
|
164
174
|
const saveAgentState = (record) => {
|
|
165
175
|
try {
|
|
@@ -295,6 +305,7 @@ export function createHostShell({ env = process.env, runtimeHost, channelPackage
|
|
|
295
305
|
return;
|
|
296
306
|
}
|
|
297
307
|
const telemetryMessageId = String(event.message_id || event.event_id || eventKey);
|
|
308
|
+
let canonicalInboxDurable = false;
|
|
298
309
|
if (wake)
|
|
299
310
|
telemetry?.beginMessage(agent.agentId, telemetryMessageId);
|
|
300
311
|
if (event.event_id)
|
|
@@ -305,58 +316,76 @@ export function createHostShell({ env = process.env, runtimeHost, channelPackage
|
|
|
305
316
|
senderIdentity.ensureChatNames(agent, event.chat_id, 3_000),
|
|
306
317
|
event._sender_is_bot ? Promise.resolve(null) : senderIdentity.ensureSenderSignature(agent, event.sender_id, 3_000),
|
|
307
318
|
]);
|
|
308
|
-
const
|
|
309
|
-
|
|
319
|
+
const projected = envelopeProjector.projectInbound(agent, event, { anchorReply: wake, names, signature });
|
|
320
|
+
projected.target = targetKeyOfInboxEnvelope({ ...projected, chat_id: event.chat_id, thread_id: event.thread_id });
|
|
310
321
|
if (wake)
|
|
311
|
-
|
|
312
|
-
const
|
|
322
|
+
projected.wake = true;
|
|
323
|
+
const candidate = projectInboxEnvelope(projected, {
|
|
313
324
|
chat_id: event.chat_id,
|
|
314
325
|
thread_id: event.thread_id,
|
|
315
326
|
...(event.create_time !== undefined ? { create_time: String(event.create_time) } : {}),
|
|
316
327
|
sender_id: event.sender_id,
|
|
317
|
-
content: String(
|
|
328
|
+
content: String(projected.content ?? event.content ?? ""),
|
|
318
329
|
});
|
|
330
|
+
let append;
|
|
319
331
|
try {
|
|
320
|
-
stateStore(agent).
|
|
332
|
+
append = stateStore(agent).appendCanonicalInboxOnce(candidate);
|
|
321
333
|
}
|
|
322
334
|
catch (error) {
|
|
323
335
|
throw new Error(`inbox 写失败: ${errorMessage(error)}`);
|
|
324
336
|
}
|
|
325
|
-
|
|
326
|
-
//
|
|
337
|
+
canonicalInboxDurable = true;
|
|
338
|
+
// An event becomes permanently transport-seen only after the canonical
|
|
339
|
+
// append/dedupe decision is durable. Agent model-seen state is untouched.
|
|
327
340
|
if (event.event_id)
|
|
328
341
|
seenEventIds.add(eventKey);
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
342
|
+
if (append.status === "duplicate_consumed")
|
|
343
|
+
return null;
|
|
344
|
+
const inboxEnvelope = append.envelope;
|
|
345
|
+
if (append.status === "appended")
|
|
346
|
+
hostState.appendConversation(agent, {
|
|
347
|
+
direction: "in", from: inboxEnvelope.sender_name, senderType: inboxEnvelope.sender_type,
|
|
348
|
+
target: String(inboxEnvelope.target || targetFor(event).target), wake, text: event.content, messageId: inboxEnvelope.message_id,
|
|
349
|
+
at: inboxEnvelope.timestamp || new Date().toISOString(),
|
|
350
|
+
});
|
|
351
|
+
return inboxEnvelope;
|
|
335
352
|
};
|
|
336
|
-
const
|
|
353
|
+
const inboxEnvelope = wake && telemetry
|
|
337
354
|
? await telemetry.phase(telemetryMessageId, "feishu.receive", SpanKind.CONSUMER, receive)
|
|
338
355
|
: await receive();
|
|
339
|
-
if (!wake)
|
|
356
|
+
if (!wake || !inboxEnvelope)
|
|
340
357
|
return;
|
|
341
|
-
const receipt = await runtimeHost.deliver(agent.agentId,
|
|
342
|
-
if (receipt.status === "
|
|
358
|
+
const receipt = await runtimeHost.deliver(agent.agentId, inboxEnvelope);
|
|
359
|
+
if (receipt.status === "error") {
|
|
360
|
+
recordInboundDeliveryFailure(agent, "non_retryable_receipt");
|
|
343
361
|
hostState.appendStatusLog(agent, "deliverLog", {
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
}, 30);
|
|
349
|
-
if (envelope.sender_type === "human" || envelope.sender_type === "agent")
|
|
350
|
-
processingEyes.add(agent, String(envelope.message_id || ""));
|
|
351
|
-
if (receipt.status === "deferred")
|
|
352
|
-
log(`Runtime 暂缓投递,消息保留在 inbox seq=${envelope.seq}: ${receipt.reason}`);
|
|
362
|
+
at: new Date().toISOString(), status: "error",
|
|
363
|
+
reason: "non-retryable Runtime delivery error; durable recovery state retained",
|
|
364
|
+
}, 80);
|
|
365
|
+
return;
|
|
353
366
|
}
|
|
367
|
+
hostState.appendStatusLog(agent, "deliverLog", {
|
|
368
|
+
from: inboxEnvelope.sender_name,
|
|
369
|
+
target: String(inboxEnvelope.target || targetFor(event).target),
|
|
370
|
+
excerpt: safeConversationExcerpt(event.content, 180),
|
|
371
|
+
at: new Date().toISOString(),
|
|
372
|
+
}, 30);
|
|
373
|
+
if (inboxEnvelope.sender_type === "human" || inboxEnvelope.sender_type === "agent")
|
|
374
|
+
processingEyes.add(agent, String(inboxEnvelope.message_id || ""));
|
|
375
|
+
if (receipt.status === "deferred")
|
|
376
|
+
log(`Runtime 暂缓投递,消息保留在 inbox seq=${inboxEnvelope.seq}: ${receipt.reason}`);
|
|
354
377
|
}
|
|
355
378
|
catch (error) {
|
|
356
379
|
if (wake)
|
|
357
380
|
telemetry?.delivery(agent.agentId, telemetryMessageId, "error");
|
|
358
|
-
|
|
359
|
-
|
|
381
|
+
if (canonicalInboxDurable) {
|
|
382
|
+
log(`onFeishuMessage Runtime delivery failed agent=${agent.name}; canonical Inbox retained`);
|
|
383
|
+
recordInboundDeliveryFailure(agent, "runtime_delivery_exception");
|
|
384
|
+
}
|
|
385
|
+
else {
|
|
386
|
+
log(`onFeishuMessage 异常 agent=${agent.name}: ${error instanceof Error ? error.stack || error.message : String(error)}`);
|
|
387
|
+
hostState.recordStatusError(agent, `onFeishuMessage: ${errorMessage(error)}`);
|
|
388
|
+
}
|
|
360
389
|
}
|
|
361
390
|
finally {
|
|
362
391
|
if (event.event_id)
|
|
@@ -1201,9 +1230,14 @@ export function createHostShell({ env = process.env, runtimeHost, channelPackage
|
|
|
1201
1230
|
return;
|
|
1202
1231
|
}
|
|
1203
1232
|
if (message.type === "delivery") {
|
|
1233
|
+
if (message.status === "error")
|
|
1234
|
+
recordInboundDeliveryFailure(agent, "runtime_delivery_event");
|
|
1204
1235
|
hostState.appendStatusLog(agent, "deliverLog", {
|
|
1205
1236
|
at: new Date().toISOString(), deliveryId: message.deliveryId, messageId: message.messageId,
|
|
1206
|
-
status: message.status,
|
|
1237
|
+
status: message.status,
|
|
1238
|
+
...(message.status === "error"
|
|
1239
|
+
? { reason: "non-retryable Runtime delivery error; durable recovery state retained" }
|
|
1240
|
+
: (message.reason ? { reason: safeConversationExcerpt(message.reason, 120) } : {})),
|
|
1207
1241
|
}, 80);
|
|
1208
1242
|
return;
|
|
1209
1243
|
}
|
|
@@ -1226,7 +1260,8 @@ export function createHostShell({ env = process.env, runtimeHost, channelPackage
|
|
|
1226
1260
|
hostState.recordStatusError(agent, `auth: ${readiness.reason}; ${readiness.nextAction}`);
|
|
1227
1261
|
}
|
|
1228
1262
|
else {
|
|
1229
|
-
|
|
1263
|
+
const category = message.event.errorCategory || "provider";
|
|
1264
|
+
hostState.recordStatusError(agent, `${category}: Runtime input failed ${message.event.retryable ? "retryably" : "non-retryably"}; inspect delivery health and Runtime/provider configuration`);
|
|
1230
1265
|
}
|
|
1231
1266
|
}
|
|
1232
1267
|
};
|
|
@@ -9,6 +9,7 @@ const node_crypto_1 = __importDefault(require("node:crypto"));
|
|
|
9
9
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
10
10
|
const node_path_1 = __importDefault(require("node:path"));
|
|
11
11
|
const agent_cli_capabilities_js_1 = require("../agent/agent-cli-capabilities.cjs");
|
|
12
|
+
const inbox_projection_js_1 = require("../agent/inbox-projection.cjs");
|
|
12
13
|
const api_1 = require("@opentelemetry/api");
|
|
13
14
|
const runtime_readiness_js_1 = require("./runtime-readiness.cjs");
|
|
14
15
|
const official_lark_cli_js_1 = require("../app/official-lark-cli.cjs");
|
|
@@ -27,6 +28,20 @@ exports.RuntimeSessionResetError = RuntimeSessionResetError;
|
|
|
27
28
|
const MAX_DELIVERIES = 2048;
|
|
28
29
|
const now = () => new Date().toISOString();
|
|
29
30
|
const isActiveDelivery = (status) => ["pending", "submitting", "accepted"].includes(status);
|
|
31
|
+
const REPLAY_FAILURE_CODES = new Set([
|
|
32
|
+
"canonical_inbox_row_missing", "canonical_inbox_malformed", "duplicate_message_id",
|
|
33
|
+
"inbox_state_conflict", "delivery_target_conflict", "structured_target_invalid",
|
|
34
|
+
"structured_target_missing", "wake_reason_conflict",
|
|
35
|
+
]);
|
|
36
|
+
function replayFailureReason(code) {
|
|
37
|
+
return `Runtime delivery quarantined (${code}): no safe canonical Inbox target is available; the Inbox/ledger remain durable for operator recovery`;
|
|
38
|
+
}
|
|
39
|
+
function replayFailureCodeOf(record) {
|
|
40
|
+
if (record.status !== "error" || typeof record.reason !== "string")
|
|
41
|
+
return null;
|
|
42
|
+
const code = /^Runtime delivery quarantined \(([^)]+)\):/.exec(record.reason)?.[1];
|
|
43
|
+
return code && REPLAY_FAILURE_CODES.has(code) ? code : null;
|
|
44
|
+
}
|
|
30
45
|
function deliveryFile(agent) {
|
|
31
46
|
return agent.config.stateDir ? node_path_1.default.join(agent.config.stateDir, "runtime-deliveries.json") : null;
|
|
32
47
|
}
|
|
@@ -159,6 +174,81 @@ function createRuntimeHost(options) {
|
|
|
159
174
|
emitConsumed(agent, persist(agent));
|
|
160
175
|
return agent.records.get(record.deliveryId) ?? record;
|
|
161
176
|
};
|
|
177
|
+
const scrubQuarantinedInput = (record, reason) => {
|
|
178
|
+
const staleInput = record.input && typeof record.input === "object" ? record.input : null;
|
|
179
|
+
record.input = {
|
|
180
|
+
inputId: typeof staleInput?.inputId === "string" && staleInput.inputId ? staleInput.inputId : record.deliveryId,
|
|
181
|
+
deliveryId: record.deliveryId,
|
|
182
|
+
kind: "wake",
|
|
183
|
+
text: reason,
|
|
184
|
+
attempt: Number.isSafeInteger(staleInput?.attempt) && Number(staleInput?.attempt) >= 0 ? Number(staleInput.attempt) : 0,
|
|
185
|
+
};
|
|
186
|
+
};
|
|
187
|
+
/** Rebuild every submitted notice from canonical structured state, never persisted text. */
|
|
188
|
+
const prepareRecordInput = (agent, record, busy) => {
|
|
189
|
+
if (record.target !== undefined && (typeof record.target !== "string" || !(0, inbox_projection_js_1.isCanonicalInboxTarget)(record.target))) {
|
|
190
|
+
return { status: "error", code: "structured_target_invalid", reason: replayFailureReason("structured_target_invalid") };
|
|
191
|
+
}
|
|
192
|
+
if (record.wakeReason !== undefined && typeof record.wakeReason !== "string") {
|
|
193
|
+
return { status: "error", code: "wake_reason_conflict", reason: replayFailureReason("wake_reason_conflict") };
|
|
194
|
+
}
|
|
195
|
+
let target = typeof record.target === "string" ? record.target : null;
|
|
196
|
+
let wakeReason = typeof record.wakeReason === "string" ? record.wakeReason : undefined;
|
|
197
|
+
if (agent.stateStore?.resolveInboxDeliverySource) {
|
|
198
|
+
let source;
|
|
199
|
+
try {
|
|
200
|
+
source = agent.stateStore.resolveInboxDeliverySource(record.messageId);
|
|
201
|
+
}
|
|
202
|
+
catch {
|
|
203
|
+
source = { status: "invalid", code: "canonical_inbox_malformed" };
|
|
204
|
+
}
|
|
205
|
+
if (source.status === "consumed")
|
|
206
|
+
return { status: "consumed" };
|
|
207
|
+
if (source.status === "missing" || source.status === "invalid") {
|
|
208
|
+
return { status: "error", code: source.code, reason: replayFailureReason(source.code) };
|
|
209
|
+
}
|
|
210
|
+
if (target && target !== source.target) {
|
|
211
|
+
return { status: "error", code: "delivery_target_conflict", reason: replayFailureReason("delivery_target_conflict") };
|
|
212
|
+
}
|
|
213
|
+
const canonicalWakeReason = typeof source.envelope.wake_reason === "string" ? source.envelope.wake_reason : undefined;
|
|
214
|
+
if (record.wakeReason !== undefined && wakeReason !== canonicalWakeReason) {
|
|
215
|
+
return { status: "error", code: "wake_reason_conflict", reason: replayFailureReason("wake_reason_conflict") };
|
|
216
|
+
}
|
|
217
|
+
target = source.target;
|
|
218
|
+
wakeReason = canonicalWakeReason;
|
|
219
|
+
}
|
|
220
|
+
if (!target)
|
|
221
|
+
return { status: "error", code: "structured_target_missing", reason: replayFailureReason("structured_target_missing") };
|
|
222
|
+
const staleInput = record.input && typeof record.input === "object" ? record.input : null;
|
|
223
|
+
const attempt = Number.isSafeInteger(staleInput?.attempt) && Number(staleInput?.attempt) >= 0 ? Number(staleInput.attempt) : 0;
|
|
224
|
+
const inputId = typeof staleInput?.inputId === "string" && staleInput.inputId ? staleInput.inputId : record.deliveryId;
|
|
225
|
+
record.target = target;
|
|
226
|
+
if (wakeReason)
|
|
227
|
+
record.wakeReason = wakeReason;
|
|
228
|
+
else
|
|
229
|
+
delete record.wakeReason;
|
|
230
|
+
record.input = {
|
|
231
|
+
inputId,
|
|
232
|
+
deliveryId: record.deliveryId,
|
|
233
|
+
kind: busy ? "inbox_update" : "wake",
|
|
234
|
+
text: options.promptBuilder.buildInboxNotice({ busy, count: 1, deliveryId: record.deliveryId, target,
|
|
235
|
+
...(wakeReason ? { wakeReason } : {}) }),
|
|
236
|
+
attempt,
|
|
237
|
+
};
|
|
238
|
+
return { status: "ready", target };
|
|
239
|
+
};
|
|
240
|
+
const quarantineRecord = (agent, record, code) => {
|
|
241
|
+
const reason = replayFailureReason(code);
|
|
242
|
+
scrubQuarantinedInput(record, reason);
|
|
243
|
+
record.reason = reason;
|
|
244
|
+
record.retryable = false;
|
|
245
|
+
const finalRecord = setRecord(agent, record, "error");
|
|
246
|
+
if (finalRecord.status === "consumed")
|
|
247
|
+
return { status: "accepted", deliveryId: record.deliveryId };
|
|
248
|
+
emit({ type: "delivery", agentId: agent.config.agentId, deliveryId: record.deliveryId,
|
|
249
|
+
messageId: record.messageId, status: "error", reason });
|
|
250
|
+
return { status: "error", deliveryId: record.deliveryId, reason, retryable: false };
|
|
251
|
+
};
|
|
162
252
|
const reconcileExternalConsumption = (agent) => {
|
|
163
253
|
if (agent.stateStore) {
|
|
164
254
|
agent.stateStore.withInboxTransaction(() => reconcileFile(agent, agent.stateStore.readJson("runtimeDeliveries", { version: 1, records: [] })));
|
|
@@ -315,6 +405,20 @@ function createRuntimeHost(options) {
|
|
|
315
405
|
if (!busy)
|
|
316
406
|
agent.busy = true; // Reserve the turn before prompt() can yield.
|
|
317
407
|
try {
|
|
408
|
+
const prepared = prepareRecordInput(agent, record, busy);
|
|
409
|
+
if (prepared.status === "consumed") {
|
|
410
|
+
if (!busy)
|
|
411
|
+
agent.busy = false;
|
|
412
|
+
const consumedRecord = setRecord(agent, record, "consumed");
|
|
413
|
+
emit({ type: "delivery", agentId: agent.config.agentId, deliveryId: consumedRecord.deliveryId,
|
|
414
|
+
messageId: consumedRecord.messageId, status: "consumed" });
|
|
415
|
+
return { status: "accepted", deliveryId: record.deliveryId };
|
|
416
|
+
}
|
|
417
|
+
if (prepared.status === "error") {
|
|
418
|
+
if (!busy)
|
|
419
|
+
agent.busy = false;
|
|
420
|
+
return quarantineRecord(agent, record, prepared.code);
|
|
421
|
+
}
|
|
318
422
|
const session = await ensureSession(agent);
|
|
319
423
|
const submittingRecord = setRecord(agent, record, "submitting");
|
|
320
424
|
if (submittingRecord.status === "consumed") {
|
|
@@ -807,7 +911,14 @@ function createRuntimeHost(options) {
|
|
|
807
911
|
const deliveryFile = stateStore.readJson("runtimeDeliveries", { version: 1, records: [] });
|
|
808
912
|
if (!stateStore.readNdjson)
|
|
809
913
|
return deliveryFile;
|
|
810
|
-
|
|
914
|
+
let inboxRows;
|
|
915
|
+
try {
|
|
916
|
+
inboxRows = stateStore.readNdjson("inbox");
|
|
917
|
+
}
|
|
918
|
+
catch {
|
|
919
|
+
return deliveryFile;
|
|
920
|
+
}
|
|
921
|
+
const inboxIds = new Set(inboxRows.flatMap((row) => typeof row?.message_id === "string" ? [row.message_id] : []));
|
|
811
922
|
let changed = false;
|
|
812
923
|
const records = deliveryFile.records.map((record) => {
|
|
813
924
|
const synthetic = /^(?:redeliver_|rem_|interaction_)/.test(record.messageId);
|
|
@@ -830,11 +941,34 @@ function createRuntimeHost(options) {
|
|
|
830
941
|
stabilityTimer: null, recreateReason: null, stopped: false, disabledReason: null,
|
|
831
942
|
configurationRecovery: null, stateStore, readiness: null,
|
|
832
943
|
turnInProgress: false, turnHadFailure: false, turnHadAuthenticatedOutput: false, authFailureActive: false };
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
944
|
+
const startupConsumed = [];
|
|
945
|
+
const startupQuarantined = [];
|
|
946
|
+
for (const record of agent.records.values()) {
|
|
947
|
+
const existingQuarantine = replayFailureCodeOf(record);
|
|
948
|
+
if (existingQuarantine) {
|
|
949
|
+
startupQuarantined.push({ record, code: existingQuarantine });
|
|
950
|
+
continue;
|
|
951
|
+
}
|
|
952
|
+
if (!isActiveDelivery(record.status))
|
|
953
|
+
continue;
|
|
954
|
+
record.status = "pending";
|
|
955
|
+
const prepared = prepareRecordInput(agent, record, false);
|
|
956
|
+
if (prepared.status === "consumed") {
|
|
957
|
+
record.status = "consumed";
|
|
958
|
+
record.updatedAt = now();
|
|
959
|
+
startupConsumed.push(record);
|
|
960
|
+
}
|
|
961
|
+
else if (prepared.status === "error") {
|
|
962
|
+
record.status = "error";
|
|
963
|
+
record.updatedAt = now();
|
|
964
|
+
scrubQuarantinedInput(record, prepared.reason);
|
|
965
|
+
record.reason = prepared.reason;
|
|
966
|
+
record.retryable = false;
|
|
967
|
+
startupQuarantined.push({ record, code: prepared.code });
|
|
968
|
+
}
|
|
969
|
+
}
|
|
837
970
|
managed.set(config.agentId, agent);
|
|
971
|
+
emitConsumed(agent, [...startupConsumed, ...persist(agent)]);
|
|
838
972
|
try {
|
|
839
973
|
await ensureSession(agent);
|
|
840
974
|
activeCount += 1;
|
|
@@ -852,6 +986,13 @@ function createRuntimeHost(options) {
|
|
|
852
986
|
emit({ type: "agent-status", agentId: config.agentId, status: "error", error: reason,
|
|
853
987
|
...(error instanceof runtime_readiness_js_1.RuntimePrerequisiteError ? { readiness: error.readiness } : {}) });
|
|
854
988
|
}
|
|
989
|
+
const visibleQuarantines = startupQuarantined.filter(({ record }) => agent.records.get(record.deliveryId)?.status === "error");
|
|
990
|
+
for (const { record, code } of visibleQuarantines)
|
|
991
|
+
emit({ type: "delivery", agentId: config.agentId,
|
|
992
|
+
deliveryId: record.deliveryId, messageId: record.messageId, status: "error", reason: replayFailureReason(code) });
|
|
993
|
+
if (visibleQuarantines.length)
|
|
994
|
+
emit({ type: "agent-status", agentId: config.agentId, status: "error",
|
|
995
|
+
error: `${visibleQuarantines.length} Runtime delivery record(s) quarantined; canonical Inbox recovery is required` });
|
|
855
996
|
agent.poller = setInterval(() => reconcileExternalConsumption(agent), 250);
|
|
856
997
|
agent.poller.unref?.();
|
|
857
998
|
});
|
|
@@ -867,12 +1008,33 @@ function createRuntimeHost(options) {
|
|
|
867
1008
|
telemetry?.delivery(agentId, messageId, "error");
|
|
868
1009
|
throw new Error(`unknown runtime Agent: ${agentId}`);
|
|
869
1010
|
}
|
|
1011
|
+
let target;
|
|
1012
|
+
try {
|
|
1013
|
+
target = (0, inbox_projection_js_1.targetKeyOfInboxEnvelope)(envelope);
|
|
1014
|
+
}
|
|
1015
|
+
catch (error) {
|
|
1016
|
+
const reason = `Inbox target derivation failed: ${error instanceof Error ? error.message : String(error)}`;
|
|
1017
|
+
const deliveryId = `invalid-target-${node_crypto_1.default.createHash("sha256").update(`${agentId}\0${messageId}`).digest("hex").slice(0, 24)}`;
|
|
1018
|
+
telemetry?.delivery(agentId, messageId, "error");
|
|
1019
|
+
emit({ type: "delivery", agentId, deliveryId, messageId, status: "error", reason });
|
|
1020
|
+
return { status: "error", deliveryId, reason, retryable: false };
|
|
1021
|
+
}
|
|
870
1022
|
reconcileExternalConsumption(agent);
|
|
871
1023
|
const existingId = agent.byMessage.get(messageId);
|
|
872
1024
|
if (existingId) {
|
|
873
1025
|
const existing = agent.records.get(existingId);
|
|
874
1026
|
if (existing?.status === "error") {
|
|
875
|
-
existing.
|
|
1027
|
+
if (existing.target && existing.target !== target)
|
|
1028
|
+
return quarantineRecord(agent, existing, "delivery_target_conflict");
|
|
1029
|
+
existing.target = target;
|
|
1030
|
+
if (typeof envelope.wake_reason === "string" && envelope.wake_reason)
|
|
1031
|
+
existing.wakeReason = envelope.wake_reason;
|
|
1032
|
+
else
|
|
1033
|
+
delete existing.wakeReason;
|
|
1034
|
+
const priorAttempt = existing.input && Number.isSafeInteger(existing.input.attempt) ? existing.input.attempt : 0;
|
|
1035
|
+
existing.input = existing.input && typeof existing.input === "object"
|
|
1036
|
+
? { ...existing.input, attempt: priorAttempt + 1 }
|
|
1037
|
+
: { inputId: existing.deliveryId, deliveryId: existing.deliveryId, kind: "wake", text: "", attempt: priorAttempt + 1 };
|
|
876
1038
|
delete existing.reason;
|
|
877
1039
|
delete existing.retryable;
|
|
878
1040
|
setRecord(agent, existing, "pending");
|
|
@@ -893,11 +1055,12 @@ function createRuntimeHost(options) {
|
|
|
893
1055
|
}
|
|
894
1056
|
const deliveryId = node_crypto_1.default.randomUUID();
|
|
895
1057
|
const busy = agent.busy || agent.submitting;
|
|
1058
|
+
const wakeReason = typeof envelope.wake_reason === "string" ? envelope.wake_reason : undefined;
|
|
896
1059
|
const input = { inputId: deliveryId, deliveryId, kind: busy ? "inbox_update" : "wake",
|
|
897
|
-
text: options.promptBuilder.buildInboxNotice({ busy, count: 1, deliveryId,
|
|
898
|
-
...(
|
|
899
|
-
|
|
900
|
-
|
|
1060
|
+
text: options.promptBuilder.buildInboxNotice({ busy, count: 1, deliveryId, target,
|
|
1061
|
+
...(wakeReason ? { wakeReason } : {}) }), attempt: 0 };
|
|
1062
|
+
const record = { deliveryId, messageId, status: "pending", input, target,
|
|
1063
|
+
...(wakeReason ? { wakeReason } : {}), updatedAt: now() };
|
|
901
1064
|
agent.records.set(deliveryId, record);
|
|
902
1065
|
agent.byMessage.set(messageId, deliveryId);
|
|
903
1066
|
persist(agent);
|