@slock-ai/daemon 0.57.0 → 0.57.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/{chunk-DQN3TW2I.js → chunk-6RZCYFJP.js} +884 -307
- package/dist/core.js +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
|
@@ -2235,6 +2235,412 @@ function reduceApmGatedFlushReadiness(state, input) {
|
|
|
2235
2235
|
};
|
|
2236
2236
|
}
|
|
2237
2237
|
|
|
2238
|
+
// src/agentInboxStateMachine.ts
|
|
2239
|
+
var DEFAULT_HELD_CONTEXT_LIMIT = 3;
|
|
2240
|
+
function planAgentInboxSideEffect(input) {
|
|
2241
|
+
const heldContextLimit = input.heldContextLimit ?? DEFAULT_HELD_CONTEXT_LIMIT;
|
|
2242
|
+
const trace = [
|
|
2243
|
+
{
|
|
2244
|
+
step: "input",
|
|
2245
|
+
data: compactTraceData({
|
|
2246
|
+
action: input.action,
|
|
2247
|
+
target: input.target,
|
|
2248
|
+
continueAnyway: input.continueAnyway,
|
|
2249
|
+
pendingCount: input.pendingMessages.length,
|
|
2250
|
+
recentCount: input.recentMessages.length,
|
|
2251
|
+
existingSeenUpToSeq: input.existingSeenUpToSeq,
|
|
2252
|
+
modelSeenSeq: input.modelSeenSeq
|
|
2253
|
+
})
|
|
2254
|
+
}
|
|
2255
|
+
];
|
|
2256
|
+
if (input.continueAnyway) {
|
|
2257
|
+
appendTrace(trace, "continue_anyway_bypass");
|
|
2258
|
+
return forwardPlan(input, {
|
|
2259
|
+
action: input.action,
|
|
2260
|
+
decision: "bypass",
|
|
2261
|
+
target: input.target,
|
|
2262
|
+
inboxTrustState: "trusted",
|
|
2263
|
+
reason: "continue_anyway"
|
|
2264
|
+
}, trace);
|
|
2265
|
+
}
|
|
2266
|
+
if (input.pendingMessages.length > 0) {
|
|
2267
|
+
appendTrace(trace, "pending_messages_found", { pendingCount: input.pendingMessages.length });
|
|
2268
|
+
const pending = sortInboxMessagesBySeq(normalizeInboxVisibleMessages(input.pendingMessages, input.target));
|
|
2269
|
+
const boundary2 = resolveFreshnessBoundary(pending);
|
|
2270
|
+
if (!boundary2.ok) {
|
|
2271
|
+
appendTrace(trace, "pending_context_missing_boundary");
|
|
2272
|
+
return forwardWithoutDecision(input, trace);
|
|
2273
|
+
}
|
|
2274
|
+
const alreadySeenPending = [];
|
|
2275
|
+
const unconsumedMessages = [];
|
|
2276
|
+
for (const message of pending) {
|
|
2277
|
+
if (isMessageModelSeen(input, message)) {
|
|
2278
|
+
alreadySeenPending.push(message);
|
|
2279
|
+
} else {
|
|
2280
|
+
unconsumedMessages.push(message);
|
|
2281
|
+
}
|
|
2282
|
+
}
|
|
2283
|
+
appendTrace(trace, "pending_context_classified", {
|
|
2284
|
+
pendingCount: pending.length,
|
|
2285
|
+
unseenCount: unconsumedMessages.length
|
|
2286
|
+
});
|
|
2287
|
+
if (unconsumedMessages.length === 0) {
|
|
2288
|
+
const contiguousBoundary = maxKnownContiguousBoundary(input);
|
|
2289
|
+
const canAdvanceBoundary = typeof contiguousBoundary === "number" && contiguousBoundary >= boundary2.seenUpToSeq;
|
|
2290
|
+
appendTrace(trace, "pending_context_already_seen", { boundarySeq: boundary2.seenUpToSeq });
|
|
2291
|
+
return forwardPlan(input, {
|
|
2292
|
+
action: input.action,
|
|
2293
|
+
decision: "forward",
|
|
2294
|
+
target: input.target,
|
|
2295
|
+
inboxTrustState: "trusted",
|
|
2296
|
+
reason: "exact_target_pending_already_seen",
|
|
2297
|
+
pendingCount: pending.length,
|
|
2298
|
+
pendingMaxSeq: boundary2.seenUpToSeq,
|
|
2299
|
+
modelSeenSeq: contiguousBoundary,
|
|
2300
|
+
heldMessageCount: 0,
|
|
2301
|
+
omittedMessageCount: 0
|
|
2302
|
+
}, trace, {
|
|
2303
|
+
forwardSeenUpToSeq: input.action === "send" && canAdvanceBoundary ? boundary2.seenUpToSeq : void 0,
|
|
2304
|
+
consumeEffect: {
|
|
2305
|
+
type: "consume_visible_messages",
|
|
2306
|
+
target: input.target,
|
|
2307
|
+
messages: exactSeenConsumeMessages(input, pending),
|
|
2308
|
+
boundarySeq: canAdvanceBoundary ? boundary2.seenUpToSeq : void 0,
|
|
2309
|
+
source: "side_effect_preflight_context"
|
|
2310
|
+
}
|
|
2311
|
+
});
|
|
2312
|
+
}
|
|
2313
|
+
const heldBoundary = resolveFreshnessBoundary(unconsumedMessages);
|
|
2314
|
+
if (heldBoundary.ok) {
|
|
2315
|
+
const heldMessages = latestVisibleMessages(unconsumedMessages, heldContextLimit);
|
|
2316
|
+
const omittedMessageCount = Math.max(0, unconsumedMessages.length - heldMessages.length);
|
|
2317
|
+
const context = {
|
|
2318
|
+
heldMessages,
|
|
2319
|
+
newMessageCount: unconsumedMessages.length,
|
|
2320
|
+
shownMessageCount: heldMessages.length,
|
|
2321
|
+
omittedMessageCount,
|
|
2322
|
+
seenUpToSeq: heldBoundary.seenUpToSeq
|
|
2323
|
+
};
|
|
2324
|
+
appendTrace(trace, "held_context_built", {
|
|
2325
|
+
boundarySeq: boundary2.seenUpToSeq,
|
|
2326
|
+
heldBoundarySeq: heldBoundary.seenUpToSeq,
|
|
2327
|
+
heldCount: context.shownMessageCount,
|
|
2328
|
+
omittedCount: context.omittedMessageCount
|
|
2329
|
+
});
|
|
2330
|
+
return heldPlan(input, {
|
|
2331
|
+
decision: {
|
|
2332
|
+
action: input.action,
|
|
2333
|
+
decision: "local_hold",
|
|
2334
|
+
target: input.target,
|
|
2335
|
+
inboxTrustState: "trusted",
|
|
2336
|
+
reason: "exact_target_pending",
|
|
2337
|
+
pendingCount: input.pendingMessages.length,
|
|
2338
|
+
pendingMaxSeq: heldBoundary.seenUpToSeq,
|
|
2339
|
+
modelSeenSeq: input.modelSeenSeq,
|
|
2340
|
+
heldMessageCount: context.shownMessageCount,
|
|
2341
|
+
omittedMessageCount: context.omittedMessageCount
|
|
2342
|
+
},
|
|
2343
|
+
context,
|
|
2344
|
+
consumeMessages: sortInboxMessagesBySeq([
|
|
2345
|
+
...exactSeenConsumeMessages(input, alreadySeenPending),
|
|
2346
|
+
...context.heldMessages
|
|
2347
|
+
]),
|
|
2348
|
+
consumeBoundarySeq: heldBoundary.seenUpToSeq
|
|
2349
|
+
}, trace);
|
|
2350
|
+
}
|
|
2351
|
+
appendTrace(trace, "pending_unseen_context_missing_boundary");
|
|
2352
|
+
return forwardWithoutDecision(input, trace);
|
|
2353
|
+
}
|
|
2354
|
+
const boundary = Math.max(input.existingSeenUpToSeq ?? 0, input.modelSeenSeq ?? 0);
|
|
2355
|
+
appendTrace(trace, "model_boundary_checked", { boundary });
|
|
2356
|
+
if (boundary > 0) {
|
|
2357
|
+
appendTrace(trace, "model_boundary_selected", { boundary });
|
|
2358
|
+
return forwardPlan(input, {
|
|
2359
|
+
action: input.action,
|
|
2360
|
+
decision: "forward",
|
|
2361
|
+
target: input.target,
|
|
2362
|
+
inboxTrustState: "trusted",
|
|
2363
|
+
reason: "model_seen_boundary",
|
|
2364
|
+
pendingCount: 0,
|
|
2365
|
+
modelSeenSeq: boundary
|
|
2366
|
+
}, trace, { forwardSeenUpToSeq: input.action === "send" ? boundary : void 0 });
|
|
2367
|
+
}
|
|
2368
|
+
if (input.recentMessages.length > 0) {
|
|
2369
|
+
return planFirstTouchRecentContext(input, heldContextLimit, trace);
|
|
2370
|
+
}
|
|
2371
|
+
appendTrace(trace, "no_context_available");
|
|
2372
|
+
return forwardPlan(input, {
|
|
2373
|
+
action: input.action,
|
|
2374
|
+
decision: "forward",
|
|
2375
|
+
target: input.target,
|
|
2376
|
+
inboxTrustState: "trusted",
|
|
2377
|
+
reason: "no_exact_target_pending_or_recent_context",
|
|
2378
|
+
pendingCount: 0,
|
|
2379
|
+
modelSeenSeq: 0
|
|
2380
|
+
}, trace);
|
|
2381
|
+
}
|
|
2382
|
+
function normalizeInboxVisibleMessage(message, target) {
|
|
2383
|
+
const targetFields = target ? parseTargetFields(target) : {};
|
|
2384
|
+
const normalized = {
|
|
2385
|
+
...targetFields,
|
|
2386
|
+
...message,
|
|
2387
|
+
message_id: message.message_id ?? message.id,
|
|
2388
|
+
timestamp: message.timestamp ?? message.createdAt,
|
|
2389
|
+
sender_type: message.sender_type ?? message.senderType,
|
|
2390
|
+
sender_name: message.sender_name ?? message.senderName,
|
|
2391
|
+
sender_description: message.sender_description ?? message.senderDescription ?? null
|
|
2392
|
+
};
|
|
2393
|
+
const senderId = messageSenderId(message);
|
|
2394
|
+
if (senderId) normalized.sender_id = senderId;
|
|
2395
|
+
return normalized;
|
|
2396
|
+
}
|
|
2397
|
+
function normalizeInboxVisibleMessages(messages, target) {
|
|
2398
|
+
return messages.map((message) => normalizeInboxVisibleMessage(message, target));
|
|
2399
|
+
}
|
|
2400
|
+
function maxInboxMessageSeq(messages) {
|
|
2401
|
+
let maxSeq = 0;
|
|
2402
|
+
for (const message of messages) {
|
|
2403
|
+
const seq = Math.floor(messageSeq(message));
|
|
2404
|
+
if (Number.isFinite(seq) && seq > 0) maxSeq = Math.max(maxSeq, seq);
|
|
2405
|
+
}
|
|
2406
|
+
return maxSeq > 0 ? maxSeq : void 0;
|
|
2407
|
+
}
|
|
2408
|
+
function maxKnownContiguousBoundary(input) {
|
|
2409
|
+
const boundary = Math.max(input.existingSeenUpToSeq ?? 0, input.modelSeenSeq ?? 0);
|
|
2410
|
+
return boundary > 0 ? boundary : void 0;
|
|
2411
|
+
}
|
|
2412
|
+
function exactSeenConsumeMessages(input, messages) {
|
|
2413
|
+
const boundary = maxKnownContiguousBoundary(input);
|
|
2414
|
+
return messages.map((message) => {
|
|
2415
|
+
const seq = Math.floor(messageSeq(message));
|
|
2416
|
+
if (Number.isFinite(seq) && seq > 0 && typeof boundary === "number" && boundary >= seq) {
|
|
2417
|
+
return message;
|
|
2418
|
+
}
|
|
2419
|
+
const id = typeof message.message_id === "string" && message.message_id.length > 0 ? message.message_id : typeof message.id === "string" && message.id.length > 0 ? message.id : void 0;
|
|
2420
|
+
return id ? { ...message, seq: void 0 } : message;
|
|
2421
|
+
});
|
|
2422
|
+
}
|
|
2423
|
+
function sortInboxMessagesBySeq(messages) {
|
|
2424
|
+
return [...messages].sort((a, b) => messageSeq(a) - messageSeq(b));
|
|
2425
|
+
}
|
|
2426
|
+
function planFirstTouchRecentContext(input, heldContextLimit, trace) {
|
|
2427
|
+
const recent = sortInboxMessagesBySeq(normalizeInboxVisibleMessages(input.recentMessages, input.target));
|
|
2428
|
+
const unconsumedMessages = recent.filter((message) => !isMessageModelSeen(input, message));
|
|
2429
|
+
appendTrace(trace, "recent_context_loaded", {
|
|
2430
|
+
recentCount: recent.length,
|
|
2431
|
+
unseenCount: unconsumedMessages.length
|
|
2432
|
+
});
|
|
2433
|
+
const boundary = resolveFreshnessBoundary(recent);
|
|
2434
|
+
if (!boundary.ok) {
|
|
2435
|
+
appendTrace(trace, "recent_context_missing_boundary");
|
|
2436
|
+
return forwardPlan(input, {
|
|
2437
|
+
action: input.action,
|
|
2438
|
+
decision: "forward",
|
|
2439
|
+
target: input.target,
|
|
2440
|
+
inboxTrustState: "untrusted",
|
|
2441
|
+
reason: "target_first_touch_recent_context_without_seq_boundary",
|
|
2442
|
+
pendingCount: 0,
|
|
2443
|
+
modelSeenSeq: 0
|
|
2444
|
+
}, trace);
|
|
2445
|
+
}
|
|
2446
|
+
appendTrace(trace, "recent_boundary_resolved", { boundarySeq: boundary.seenUpToSeq });
|
|
2447
|
+
if (unconsumedMessages.length === 0) {
|
|
2448
|
+
appendTrace(trace, "recent_context_already_seen");
|
|
2449
|
+
return forwardPlan(input, {
|
|
2450
|
+
action: input.action,
|
|
2451
|
+
decision: "forward",
|
|
2452
|
+
target: input.target,
|
|
2453
|
+
inboxTrustState: "untrusted",
|
|
2454
|
+
reason: "target_first_touch_recent_context_already_seen",
|
|
2455
|
+
pendingCount: 0,
|
|
2456
|
+
pendingMaxSeq: boundary.seenUpToSeq,
|
|
2457
|
+
modelSeenSeq: boundary.seenUpToSeq,
|
|
2458
|
+
heldMessageCount: 0,
|
|
2459
|
+
omittedMessageCount: 0
|
|
2460
|
+
}, trace, {
|
|
2461
|
+
forwardSeenUpToSeq: input.action === "send" ? boundary.seenUpToSeq : void 0,
|
|
2462
|
+
consumeEffect: {
|
|
2463
|
+
type: "consume_visible_messages",
|
|
2464
|
+
target: input.target,
|
|
2465
|
+
messages: recent,
|
|
2466
|
+
boundarySeq: boundary.seenUpToSeq,
|
|
2467
|
+
source: "side_effect_preflight_context"
|
|
2468
|
+
}
|
|
2469
|
+
});
|
|
2470
|
+
}
|
|
2471
|
+
const heldBoundary = resolveFreshnessBoundary(unconsumedMessages);
|
|
2472
|
+
if (!heldBoundary.ok) {
|
|
2473
|
+
appendTrace(trace, "unseen_context_missing_boundary");
|
|
2474
|
+
return forwardPlan(input, {
|
|
2475
|
+
action: input.action,
|
|
2476
|
+
decision: "forward",
|
|
2477
|
+
target: input.target,
|
|
2478
|
+
inboxTrustState: "untrusted",
|
|
2479
|
+
reason: "target_first_touch_unseen_context_without_seq_boundary",
|
|
2480
|
+
pendingCount: 0,
|
|
2481
|
+
modelSeenSeq: 0
|
|
2482
|
+
}, trace);
|
|
2483
|
+
}
|
|
2484
|
+
appendTrace(trace, "unseen_boundary_resolved", { boundarySeq: heldBoundary.seenUpToSeq });
|
|
2485
|
+
const heldMessages = latestVisibleMessages(unconsumedMessages, heldContextLimit);
|
|
2486
|
+
const omittedMessageCount = Math.max(0, unconsumedMessages.length - heldMessages.length);
|
|
2487
|
+
appendTrace(trace, "unseen_hold_selected", {
|
|
2488
|
+
heldCount: heldMessages.length,
|
|
2489
|
+
omittedCount: omittedMessageCount,
|
|
2490
|
+
consumeBoundarySeq: boundary.seenUpToSeq
|
|
2491
|
+
});
|
|
2492
|
+
return heldPlan(input, {
|
|
2493
|
+
decision: {
|
|
2494
|
+
action: input.action,
|
|
2495
|
+
decision: "syncing_hold",
|
|
2496
|
+
target: input.target,
|
|
2497
|
+
inboxTrustState: "untrusted",
|
|
2498
|
+
reason: "target_first_touch_recent_context",
|
|
2499
|
+
pendingCount: 0,
|
|
2500
|
+
pendingMaxSeq: heldBoundary.seenUpToSeq,
|
|
2501
|
+
modelSeenSeq: 0,
|
|
2502
|
+
heldMessageCount: heldMessages.length,
|
|
2503
|
+
omittedMessageCount
|
|
2504
|
+
},
|
|
2505
|
+
context: {
|
|
2506
|
+
heldMessages,
|
|
2507
|
+
newMessageCount: unconsumedMessages.length,
|
|
2508
|
+
shownMessageCount: heldMessages.length,
|
|
2509
|
+
omittedMessageCount,
|
|
2510
|
+
seenUpToSeq: boundary.seenUpToSeq
|
|
2511
|
+
},
|
|
2512
|
+
consumeMessages: recent,
|
|
2513
|
+
consumeBoundarySeq: boundary.seenUpToSeq
|
|
2514
|
+
}, trace);
|
|
2515
|
+
}
|
|
2516
|
+
function heldPlan(input, held, trace) {
|
|
2517
|
+
const producerFactId = buildApmFreshnessDecisionProducerFactId(input.agentId, held.decision);
|
|
2518
|
+
const decision = { ...held.decision, producerFactId };
|
|
2519
|
+
appendTrace(trace, "plan_built", {
|
|
2520
|
+
outcome: "held",
|
|
2521
|
+
decision: decision.decision,
|
|
2522
|
+
effectCount: 2,
|
|
2523
|
+
localResponseState: "held",
|
|
2524
|
+
seenUpToSeq: held.context.seenUpToSeq
|
|
2525
|
+
});
|
|
2526
|
+
return {
|
|
2527
|
+
outcome: "held",
|
|
2528
|
+
target: input.target,
|
|
2529
|
+
effects: [
|
|
2530
|
+
{
|
|
2531
|
+
type: "consume_visible_messages",
|
|
2532
|
+
target: input.target,
|
|
2533
|
+
messages: held.consumeMessages,
|
|
2534
|
+
boundarySeq: held.consumeBoundarySeq,
|
|
2535
|
+
source: "side_effect_preflight_context"
|
|
2536
|
+
},
|
|
2537
|
+
{ type: "record_freshness_decision", decision }
|
|
2538
|
+
],
|
|
2539
|
+
trace,
|
|
2540
|
+
localResponse: projectApmHeldFreshnessEnvelope({
|
|
2541
|
+
producerFactId,
|
|
2542
|
+
action: input.action,
|
|
2543
|
+
heldMessages: held.context.heldMessages,
|
|
2544
|
+
newMessageCount: held.context.newMessageCount,
|
|
2545
|
+
omittedMessageCount: held.context.omittedMessageCount,
|
|
2546
|
+
seenUpToSeq: held.context.seenUpToSeq
|
|
2547
|
+
}).body
|
|
2548
|
+
};
|
|
2549
|
+
}
|
|
2550
|
+
function forwardPlan(input, decision, trace, options = {}) {
|
|
2551
|
+
appendTrace(trace, "plan_built", {
|
|
2552
|
+
outcome: "forward",
|
|
2553
|
+
decision: decision.decision,
|
|
2554
|
+
effectCount: options.consumeEffect ? 2 : 1,
|
|
2555
|
+
forwardSeenUpToSeq: options.forwardSeenUpToSeq
|
|
2556
|
+
});
|
|
2557
|
+
return {
|
|
2558
|
+
outcome: "forward",
|
|
2559
|
+
target: input.target,
|
|
2560
|
+
forwardSeenUpToSeq: options.forwardSeenUpToSeq,
|
|
2561
|
+
effects: [
|
|
2562
|
+
...options.consumeEffect ? [options.consumeEffect] : [],
|
|
2563
|
+
{ type: "record_freshness_decision", decision }
|
|
2564
|
+
],
|
|
2565
|
+
trace
|
|
2566
|
+
};
|
|
2567
|
+
}
|
|
2568
|
+
function forwardWithoutDecision(input, trace) {
|
|
2569
|
+
appendTrace(trace, "plan_built", {
|
|
2570
|
+
outcome: "forward",
|
|
2571
|
+
decision: "none",
|
|
2572
|
+
effectCount: 0
|
|
2573
|
+
});
|
|
2574
|
+
return {
|
|
2575
|
+
outcome: "forward",
|
|
2576
|
+
target: input.target,
|
|
2577
|
+
effects: [],
|
|
2578
|
+
trace
|
|
2579
|
+
};
|
|
2580
|
+
}
|
|
2581
|
+
function resolveFreshnessBoundary(messages) {
|
|
2582
|
+
const seenUpToSeq = maxInboxMessageSeq(messages);
|
|
2583
|
+
return typeof seenUpToSeq === "number" ? { ok: true, seenUpToSeq } : { ok: false, reason: "missing_seq_boundary" };
|
|
2584
|
+
}
|
|
2585
|
+
function latestVisibleMessages(messages, limit) {
|
|
2586
|
+
const sorted = sortInboxMessagesBySeq(messages);
|
|
2587
|
+
return sorted.slice(Math.max(0, sorted.length - limit));
|
|
2588
|
+
}
|
|
2589
|
+
function isMessageModelSeen(input, message) {
|
|
2590
|
+
const seq = Math.floor(messageSeq(message));
|
|
2591
|
+
if (Number.isFinite(seq) && seq > 0 && typeof input.modelSeenSeq === "number" && input.modelSeenSeq >= seq) return true;
|
|
2592
|
+
return input.isMessageModelSeen?.({ target: input.target, message }) === true;
|
|
2593
|
+
}
|
|
2594
|
+
function messageSeq(message) {
|
|
2595
|
+
return Number(message.seq ?? 0);
|
|
2596
|
+
}
|
|
2597
|
+
function messageSenderId(message) {
|
|
2598
|
+
if (typeof message.sender_id === "string" && message.sender_id.length > 0) return message.sender_id;
|
|
2599
|
+
if (typeof message.senderId === "string" && message.senderId.length > 0) return message.senderId;
|
|
2600
|
+
return void 0;
|
|
2601
|
+
}
|
|
2602
|
+
function appendTrace(trace, step, data) {
|
|
2603
|
+
const compacted = compactTraceData(data);
|
|
2604
|
+
trace.push(Object.keys(compacted).length > 0 ? { step, data: compacted } : { step });
|
|
2605
|
+
}
|
|
2606
|
+
function compactTraceData(data) {
|
|
2607
|
+
const compacted = {};
|
|
2608
|
+
if (!data) return compacted;
|
|
2609
|
+
for (const [key, value] of Object.entries(data)) {
|
|
2610
|
+
if (value !== void 0) compacted[key] = value;
|
|
2611
|
+
}
|
|
2612
|
+
return compacted;
|
|
2613
|
+
}
|
|
2614
|
+
function parseTargetFields(target) {
|
|
2615
|
+
if (target.startsWith("dm:@")) {
|
|
2616
|
+
const rest = target.slice("dm:@".length);
|
|
2617
|
+
const [peer, threadId] = rest.split(":", 2);
|
|
2618
|
+
if (threadId) {
|
|
2619
|
+
return {
|
|
2620
|
+
channel_type: "thread",
|
|
2621
|
+
channel_name: threadId,
|
|
2622
|
+
parent_channel_type: "dm",
|
|
2623
|
+
parent_channel_name: peer
|
|
2624
|
+
};
|
|
2625
|
+
}
|
|
2626
|
+
return { channel_type: "dm", channel_name: peer };
|
|
2627
|
+
}
|
|
2628
|
+
if (target.startsWith("#")) {
|
|
2629
|
+
const rest = target.slice(1);
|
|
2630
|
+
const [channel, threadId] = rest.split(":", 2);
|
|
2631
|
+
if (threadId) {
|
|
2632
|
+
return {
|
|
2633
|
+
channel_type: "thread",
|
|
2634
|
+
channel_name: threadId,
|
|
2635
|
+
parent_channel_type: "channel",
|
|
2636
|
+
parent_channel_name: channel
|
|
2637
|
+
};
|
|
2638
|
+
}
|
|
2639
|
+
return { channel_type: "channel", channel_name: channel };
|
|
2640
|
+
}
|
|
2641
|
+
return {};
|
|
2642
|
+
}
|
|
2643
|
+
|
|
2238
2644
|
// src/agentInboxProjection.ts
|
|
2239
2645
|
function projectAgentInboxSnapshot(messages) {
|
|
2240
2646
|
const buckets = /* @__PURE__ */ new Map();
|
|
@@ -2264,16 +2670,16 @@ function projectBucket(target, messages) {
|
|
|
2264
2670
|
channelType: latest.channel_type,
|
|
2265
2671
|
pendingCount: messages.length,
|
|
2266
2672
|
firstPendingMsgId: messageId(first),
|
|
2267
|
-
firstPendingSeq:
|
|
2673
|
+
firstPendingSeq: messageSeq2(first),
|
|
2268
2674
|
latestMsgId: messageId(latest),
|
|
2269
|
-
latestSeq:
|
|
2675
|
+
latestSeq: messageSeq2(latest),
|
|
2270
2676
|
latestSenderName: latest.sender_name ?? latest.senderName,
|
|
2271
2677
|
latestSenderType: normalizeSenderType(latest.sender_type ?? latest.senderType),
|
|
2272
2678
|
flags: [...flags].sort()
|
|
2273
2679
|
});
|
|
2274
2680
|
}
|
|
2275
2681
|
function compareInboxMessages(a, b) {
|
|
2276
|
-
return (
|
|
2682
|
+
return (messageSeq2(a) ?? 0) - (messageSeq2(b) ?? 0) || (messageId(a) ?? "").localeCompare(messageId(b) ?? "");
|
|
2277
2683
|
}
|
|
2278
2684
|
function formatInboxMessageTarget(message) {
|
|
2279
2685
|
if (message.channel_type === "thread" && message.parent_channel_name && message.channel_name) {
|
|
@@ -2289,7 +2695,7 @@ function messageId(message) {
|
|
|
2289
2695
|
if (!message) return void 0;
|
|
2290
2696
|
return nonEmptyString(message.message_id) ?? nonEmptyString(message.id);
|
|
2291
2697
|
}
|
|
2292
|
-
function
|
|
2698
|
+
function messageSeq2(message) {
|
|
2293
2699
|
if (!message || typeof message.seq !== "number" || !Number.isFinite(message.seq) || message.seq <= 0) return void 0;
|
|
2294
2700
|
return Math.floor(message.seq);
|
|
2295
2701
|
}
|
|
@@ -2814,42 +3220,9 @@ async function readRequestBody(req) {
|
|
|
2814
3220
|
}
|
|
2815
3221
|
return Buffer.concat(chunks);
|
|
2816
3222
|
}
|
|
2817
|
-
function
|
|
3223
|
+
function messageSeq3(message) {
|
|
2818
3224
|
return Number(message.seq ?? 0);
|
|
2819
3225
|
}
|
|
2820
|
-
function maxMessageSeq(messages) {
|
|
2821
|
-
let maxSeq = 0;
|
|
2822
|
-
for (const message of messages) {
|
|
2823
|
-
const seq = Math.floor(messageSeq2(message));
|
|
2824
|
-
if (Number.isFinite(seq) && seq > 0) maxSeq = Math.max(maxSeq, seq);
|
|
2825
|
-
}
|
|
2826
|
-
return maxSeq > 0 ? maxSeq : void 0;
|
|
2827
|
-
}
|
|
2828
|
-
function messageSenderId(message) {
|
|
2829
|
-
if (typeof message.sender_id === "string" && message.sender_id.length > 0) return message.sender_id;
|
|
2830
|
-
if (typeof message.senderId === "string" && message.senderId.length > 0) return message.senderId;
|
|
2831
|
-
return void 0;
|
|
2832
|
-
}
|
|
2833
|
-
function isSelfAuthoredMessage(registration, message) {
|
|
2834
|
-
return messageSenderId(message) === registration.agentId;
|
|
2835
|
-
}
|
|
2836
|
-
function isMessageModelSeen(coordinator, target, message) {
|
|
2837
|
-
const seq = Math.floor(messageSeq2(message));
|
|
2838
|
-
const boundary = coordinator.getBoundary(target);
|
|
2839
|
-
if (Number.isFinite(seq) && seq > 0 && typeof boundary === "number" && boundary >= seq) return true;
|
|
2840
|
-
return coordinator.isMessageModelSeen?.({ target, message }) === true;
|
|
2841
|
-
}
|
|
2842
|
-
function resolveFreshnessBoundary(messages) {
|
|
2843
|
-
const seenUpToSeq = maxMessageSeq(messages);
|
|
2844
|
-
return typeof seenUpToSeq === "number" ? { ok: true, seenUpToSeq } : { ok: false, reason: "missing_seq_boundary" };
|
|
2845
|
-
}
|
|
2846
|
-
function sortBySeq(messages) {
|
|
2847
|
-
return [...messages].sort((a, b) => messageSeq2(a) - messageSeq2(b));
|
|
2848
|
-
}
|
|
2849
|
-
function latestVisibleMessages(messages, limit) {
|
|
2850
|
-
const sorted = sortBySeq(messages);
|
|
2851
|
-
return sorted.slice(Math.max(0, sorted.length - limit));
|
|
2852
|
-
}
|
|
2853
3226
|
function localAgentApiInboxResponse(registration) {
|
|
2854
3227
|
const coordinator = registration.inboxCoordinator;
|
|
2855
3228
|
if (!coordinator) return void 0;
|
|
@@ -2897,9 +3270,9 @@ function localAgentApiEventsResponse(registration, target) {
|
|
|
2897
3270
|
return { status: 400, body: parsedQuery.error };
|
|
2898
3271
|
}
|
|
2899
3272
|
if (pending.length === 0) return void 0;
|
|
2900
|
-
const normalized =
|
|
3273
|
+
const normalized = sortInboxMessagesBySeq(normalizeInboxVisibleMessages(pending));
|
|
2901
3274
|
const filtered = parsedQuery.sinceSeq !== null ? normalized.filter((message) => {
|
|
2902
|
-
const seq =
|
|
3275
|
+
const seq = messageSeq3(message);
|
|
2903
3276
|
return Number.isFinite(seq) && seq > parsedQuery.sinceSeq;
|
|
2904
3277
|
}) : normalized;
|
|
2905
3278
|
const events = filtered.slice(0, parsedQuery.limit);
|
|
@@ -2908,7 +3281,7 @@ function localAgentApiEventsResponse(registration, target) {
|
|
|
2908
3281
|
const lastSeenMsgId = newestEvent?.message_id ?? newestEvent?.id ?? null;
|
|
2909
3282
|
const lastSeenSeq = newestEvent?.seq ?? parsedQuery.sinceSeq;
|
|
2910
3283
|
if (events.length > 0) {
|
|
2911
|
-
coordinator.consumeVisibleMessages({ messages: events, source: "
|
|
3284
|
+
coordinator.consumeVisibleMessages({ messages: events, source: "agent_api_events_local" });
|
|
2912
3285
|
}
|
|
2913
3286
|
coordinator.recordDrainOutcome?.({
|
|
2914
3287
|
source: "daemon_pending",
|
|
@@ -2930,30 +3303,6 @@ function localAgentApiEventsResponse(registration, target) {
|
|
|
2930
3303
|
}
|
|
2931
3304
|
};
|
|
2932
3305
|
}
|
|
2933
|
-
function localHeldContext(input) {
|
|
2934
|
-
if (input.messages.length === 0) return { ok: false, reason: "empty_context" };
|
|
2935
|
-
const normalized = sortBySeq(normalizeVisibleMessages(input.messages, input.target));
|
|
2936
|
-
const heldMessages = latestVisibleMessages(normalized, LOCAL_HELD_CONTEXT_LIMIT);
|
|
2937
|
-
const omittedMessageCount = Math.max(0, normalized.length - heldMessages.length);
|
|
2938
|
-
const boundary = resolveFreshnessBoundary(normalized);
|
|
2939
|
-
if (!boundary.ok) return boundary;
|
|
2940
|
-
input.coordinator.consumeVisibleMessages({
|
|
2941
|
-
target: input.target,
|
|
2942
|
-
messages: heldMessages,
|
|
2943
|
-
boundarySeq: boundary.seenUpToSeq,
|
|
2944
|
-
source: input.source
|
|
2945
|
-
});
|
|
2946
|
-
return {
|
|
2947
|
-
ok: true,
|
|
2948
|
-
context: {
|
|
2949
|
-
heldMessages,
|
|
2950
|
-
newMessageCount: normalized.length,
|
|
2951
|
-
shownMessageCount: heldMessages.length,
|
|
2952
|
-
omittedMessageCount,
|
|
2953
|
-
seenUpToSeq: boundary.seenUpToSeq
|
|
2954
|
-
}
|
|
2955
|
-
};
|
|
2956
|
-
}
|
|
2957
3306
|
function recordFreshnessDecision(coordinator, decision) {
|
|
2958
3307
|
coordinator?.recordFreshnessDecision?.(decision);
|
|
2959
3308
|
}
|
|
@@ -2967,53 +3316,6 @@ function sideEffectTarget(action, body) {
|
|
|
2967
3316
|
const field = action === "send" ? body.target : body.channel;
|
|
2968
3317
|
return typeof field === "string" && field.length > 0 ? field : void 0;
|
|
2969
3318
|
}
|
|
2970
|
-
function parseTargetFields(target) {
|
|
2971
|
-
if (target.startsWith("dm:@")) {
|
|
2972
|
-
const rest = target.slice("dm:@".length);
|
|
2973
|
-
const [peer, threadId] = rest.split(":", 2);
|
|
2974
|
-
if (threadId) {
|
|
2975
|
-
return {
|
|
2976
|
-
channel_type: "thread",
|
|
2977
|
-
channel_name: threadId,
|
|
2978
|
-
parent_channel_type: "dm",
|
|
2979
|
-
parent_channel_name: peer
|
|
2980
|
-
};
|
|
2981
|
-
}
|
|
2982
|
-
return { channel_type: "dm", channel_name: peer };
|
|
2983
|
-
}
|
|
2984
|
-
if (target.startsWith("#")) {
|
|
2985
|
-
const rest = target.slice(1);
|
|
2986
|
-
const [channel, threadId] = rest.split(":", 2);
|
|
2987
|
-
if (threadId) {
|
|
2988
|
-
return {
|
|
2989
|
-
channel_type: "thread",
|
|
2990
|
-
channel_name: threadId,
|
|
2991
|
-
parent_channel_type: "channel",
|
|
2992
|
-
parent_channel_name: channel
|
|
2993
|
-
};
|
|
2994
|
-
}
|
|
2995
|
-
return { channel_type: "channel", channel_name: channel };
|
|
2996
|
-
}
|
|
2997
|
-
return {};
|
|
2998
|
-
}
|
|
2999
|
-
function normalizeVisibleMessage(message, target) {
|
|
3000
|
-
const targetFields = target ? parseTargetFields(target) : {};
|
|
3001
|
-
const normalized = {
|
|
3002
|
-
...targetFields,
|
|
3003
|
-
...message,
|
|
3004
|
-
message_id: message.message_id ?? message.id,
|
|
3005
|
-
timestamp: message.timestamp ?? message.createdAt,
|
|
3006
|
-
sender_type: message.sender_type ?? message.senderType,
|
|
3007
|
-
sender_name: message.sender_name ?? message.senderName,
|
|
3008
|
-
sender_description: message.sender_description ?? message.senderDescription ?? null
|
|
3009
|
-
};
|
|
3010
|
-
const senderId = messageSenderId(message);
|
|
3011
|
-
if (senderId) normalized.sender_id = senderId;
|
|
3012
|
-
return normalized;
|
|
3013
|
-
}
|
|
3014
|
-
function normalizeVisibleMessages(messages, target) {
|
|
3015
|
-
return messages.map((message) => normalizeVisibleMessage(message, target));
|
|
3016
|
-
}
|
|
3017
3319
|
async function loadRecentTargetMessages(registration, headers, target) {
|
|
3018
3320
|
const historyUrl = new URL2("/internal/agent-api/history", registration.serverUrl);
|
|
3019
3321
|
historyUrl.searchParams.set("channel", target);
|
|
@@ -3024,7 +3326,21 @@ async function loadRecentTargetMessages(registration, headers, target) {
|
|
|
3024
3326
|
const res = await fetch(historyUrl, { method: "GET", headers: historyHeaders });
|
|
3025
3327
|
if (!res.ok) return [];
|
|
3026
3328
|
const parsed = await res.json().catch(() => null);
|
|
3027
|
-
return Array.isArray(parsed?.messages) ?
|
|
3329
|
+
return Array.isArray(parsed?.messages) ? normalizeInboxVisibleMessages(parsed.messages, target) : [];
|
|
3330
|
+
}
|
|
3331
|
+
function applyAgentInboxStateMachineEffects(coordinator, effects) {
|
|
3332
|
+
for (const effect of effects) {
|
|
3333
|
+
if (effect.type === "record_freshness_decision") {
|
|
3334
|
+
recordFreshnessDecision(coordinator, effect.decision);
|
|
3335
|
+
continue;
|
|
3336
|
+
}
|
|
3337
|
+
coordinator.consumeVisibleMessages({
|
|
3338
|
+
target: effect.target,
|
|
3339
|
+
messages: effect.messages,
|
|
3340
|
+
boundarySeq: effect.boundarySeq,
|
|
3341
|
+
source: effect.source
|
|
3342
|
+
});
|
|
3343
|
+
}
|
|
3028
3344
|
}
|
|
3029
3345
|
async function prepareAgentApiSideEffectForward(registration, headers, rawBody, action) {
|
|
3030
3346
|
let body;
|
|
@@ -3038,166 +3354,28 @@ async function prepareAgentApiSideEffectForward(registration, headers, rawBody,
|
|
|
3038
3354
|
if (!target || !coordinator) {
|
|
3039
3355
|
return { bodyText: JSON.stringify(body), target };
|
|
3040
3356
|
}
|
|
3041
|
-
if (action === "send" && body.continueAnyway === true) {
|
|
3042
|
-
recordFreshnessDecision(coordinator, {
|
|
3043
|
-
action,
|
|
3044
|
-
decision: "bypass",
|
|
3045
|
-
target,
|
|
3046
|
-
inboxTrustState: "trusted",
|
|
3047
|
-
reason: "continue_anyway"
|
|
3048
|
-
});
|
|
3049
|
-
return { bodyText: JSON.stringify(body), target };
|
|
3050
|
-
}
|
|
3051
3357
|
const pending = coordinator.getPendingMessages(target);
|
|
3052
|
-
if (pending.length > 0) {
|
|
3053
|
-
const modelSeenSeq = coordinator.getBoundary(target);
|
|
3054
|
-
const contextResult = localHeldContext({
|
|
3055
|
-
target,
|
|
3056
|
-
messages: pending,
|
|
3057
|
-
coordinator,
|
|
3058
|
-
source: "side_effect_preflight_context"
|
|
3059
|
-
});
|
|
3060
|
-
if (contextResult.ok) {
|
|
3061
|
-
const { context } = contextResult;
|
|
3062
|
-
const decision = {
|
|
3063
|
-
action,
|
|
3064
|
-
decision: "local_hold",
|
|
3065
|
-
target,
|
|
3066
|
-
inboxTrustState: "trusted",
|
|
3067
|
-
reason: "exact_target_pending",
|
|
3068
|
-
pendingCount: pending.length,
|
|
3069
|
-
pendingMaxSeq: context.seenUpToSeq,
|
|
3070
|
-
modelSeenSeq,
|
|
3071
|
-
heldMessageCount: context.shownMessageCount,
|
|
3072
|
-
omittedMessageCount: context.omittedMessageCount
|
|
3073
|
-
};
|
|
3074
|
-
const producerFactId = buildApmFreshnessDecisionProducerFactId(registration.agentId, decision);
|
|
3075
|
-
const localResponse = projectApmHeldFreshnessEnvelope({
|
|
3076
|
-
producerFactId,
|
|
3077
|
-
action,
|
|
3078
|
-
heldMessages: context.heldMessages,
|
|
3079
|
-
newMessageCount: context.newMessageCount,
|
|
3080
|
-
omittedMessageCount: context.omittedMessageCount,
|
|
3081
|
-
seenUpToSeq: context.seenUpToSeq
|
|
3082
|
-
}).body;
|
|
3083
|
-
recordFreshnessDecision(coordinator, { ...decision, producerFactId });
|
|
3084
|
-
return {
|
|
3085
|
-
bodyText: JSON.stringify(body),
|
|
3086
|
-
target,
|
|
3087
|
-
localResponse
|
|
3088
|
-
};
|
|
3089
|
-
}
|
|
3090
|
-
return {
|
|
3091
|
-
bodyText: JSON.stringify(body),
|
|
3092
|
-
target
|
|
3093
|
-
};
|
|
3094
|
-
}
|
|
3095
3358
|
const existingBoundary = typeof body.seenUpToSeq === "number" && Number.isFinite(body.seenUpToSeq) ? Math.max(0, Math.floor(body.seenUpToSeq)) : void 0;
|
|
3096
|
-
const
|
|
3097
|
-
const
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
action,
|
|
3102
|
-
decision: "forward",
|
|
3103
|
-
target,
|
|
3104
|
-
inboxTrustState: "trusted",
|
|
3105
|
-
reason: "model_seen_boundary",
|
|
3106
|
-
pendingCount: 0,
|
|
3107
|
-
modelSeenSeq: boundary
|
|
3108
|
-
});
|
|
3109
|
-
return { bodyText: JSON.stringify(body), target };
|
|
3110
|
-
}
|
|
3111
|
-
const recent = await loadRecentTargetMessages(registration, headers, target);
|
|
3112
|
-
if (recent.length > 0) {
|
|
3113
|
-
const unconsumedCounterparty = recent.filter(
|
|
3114
|
-
(message) => !isSelfAuthoredMessage(registration, message) && !isMessageModelSeen(coordinator, target, message)
|
|
3115
|
-
);
|
|
3116
|
-
const boundary2 = resolveFreshnessBoundary(recent);
|
|
3117
|
-
if (!boundary2.ok) {
|
|
3118
|
-
recordFreshnessDecision(coordinator, {
|
|
3119
|
-
action,
|
|
3120
|
-
decision: "forward",
|
|
3121
|
-
target,
|
|
3122
|
-
inboxTrustState: "untrusted",
|
|
3123
|
-
reason: "target_first_touch_recent_context_without_seq_boundary",
|
|
3124
|
-
pendingCount: 0,
|
|
3125
|
-
modelSeenSeq: 0
|
|
3126
|
-
});
|
|
3127
|
-
return { bodyText: JSON.stringify(body), target };
|
|
3128
|
-
}
|
|
3129
|
-
if (unconsumedCounterparty.length === 0) {
|
|
3130
|
-
const { seenUpToSeq: seenUpToSeq2 } = boundary2;
|
|
3131
|
-
if (action === "send") body.seenUpToSeq = seenUpToSeq2;
|
|
3132
|
-
coordinator.consumeVisibleMessages({ target, messages: recent, boundarySeq: seenUpToSeq2, source: "side_effect_preflight_context" });
|
|
3133
|
-
recordFreshnessDecision(coordinator, {
|
|
3134
|
-
action,
|
|
3135
|
-
decision: "forward",
|
|
3136
|
-
target,
|
|
3137
|
-
inboxTrustState: "untrusted",
|
|
3138
|
-
reason: "target_first_touch_recent_context_already_seen",
|
|
3139
|
-
pendingCount: 0,
|
|
3140
|
-
pendingMaxSeq: seenUpToSeq2,
|
|
3141
|
-
modelSeenSeq: seenUpToSeq2,
|
|
3142
|
-
heldMessageCount: 0,
|
|
3143
|
-
omittedMessageCount: 0
|
|
3144
|
-
});
|
|
3145
|
-
return { bodyText: JSON.stringify(body), target };
|
|
3146
|
-
}
|
|
3147
|
-
const heldBoundary = resolveFreshnessBoundary(unconsumedCounterparty);
|
|
3148
|
-
if (!heldBoundary.ok) {
|
|
3149
|
-
recordFreshnessDecision(coordinator, {
|
|
3150
|
-
action,
|
|
3151
|
-
decision: "forward",
|
|
3152
|
-
target,
|
|
3153
|
-
inboxTrustState: "untrusted",
|
|
3154
|
-
reason: "target_first_touch_counterparty_context_without_seq_boundary",
|
|
3155
|
-
pendingCount: 0,
|
|
3156
|
-
modelSeenSeq: 0
|
|
3157
|
-
});
|
|
3158
|
-
return { bodyText: JSON.stringify(body), target };
|
|
3159
|
-
}
|
|
3160
|
-
const heldMessages = latestVisibleMessages(unconsumedCounterparty, LOCAL_HELD_CONTEXT_LIMIT);
|
|
3161
|
-
const omittedMessageCount = Math.max(0, unconsumedCounterparty.length - heldMessages.length);
|
|
3162
|
-
const { seenUpToSeq } = boundary2;
|
|
3163
|
-
coordinator.consumeVisibleMessages({ target, messages: recent, boundarySeq: seenUpToSeq, source: "side_effect_preflight_context" });
|
|
3164
|
-
const decision = {
|
|
3165
|
-
action,
|
|
3166
|
-
decision: "syncing_hold",
|
|
3167
|
-
target,
|
|
3168
|
-
inboxTrustState: "untrusted",
|
|
3169
|
-
reason: "target_first_touch_recent_context",
|
|
3170
|
-
pendingCount: 0,
|
|
3171
|
-
pendingMaxSeq: heldBoundary.seenUpToSeq,
|
|
3172
|
-
modelSeenSeq: 0,
|
|
3173
|
-
heldMessageCount: heldMessages.length,
|
|
3174
|
-
omittedMessageCount
|
|
3175
|
-
};
|
|
3176
|
-
const producerFactId = buildApmFreshnessDecisionProducerFactId(registration.agentId, decision);
|
|
3177
|
-
recordFreshnessDecision(coordinator, { ...decision, producerFactId });
|
|
3178
|
-
return {
|
|
3179
|
-
bodyText: JSON.stringify(body),
|
|
3180
|
-
target,
|
|
3181
|
-
localResponse: projectApmHeldFreshnessEnvelope({
|
|
3182
|
-
producerFactId,
|
|
3183
|
-
action,
|
|
3184
|
-
heldMessages,
|
|
3185
|
-
newMessageCount: unconsumedCounterparty.length,
|
|
3186
|
-
omittedMessageCount,
|
|
3187
|
-
seenUpToSeq
|
|
3188
|
-
}).body
|
|
3189
|
-
};
|
|
3190
|
-
}
|
|
3191
|
-
recordFreshnessDecision(coordinator, {
|
|
3359
|
+
const continueAnyway = action === "send" && body.continueAnyway === true;
|
|
3360
|
+
const shouldLoadRecent = pending.length === 0 && !continueAnyway && Math.max(existingBoundary ?? 0, coordinator.getBoundary(target) ?? 0) <= 0;
|
|
3361
|
+
const recent = shouldLoadRecent ? await loadRecentTargetMessages(registration, headers, target) : [];
|
|
3362
|
+
const plan = planAgentInboxSideEffect({
|
|
3363
|
+
agentId: registration.agentId,
|
|
3192
3364
|
action,
|
|
3193
|
-
decision: "forward",
|
|
3194
3365
|
target,
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
3366
|
+
continueAnyway,
|
|
3367
|
+
existingSeenUpToSeq: existingBoundary,
|
|
3368
|
+
modelSeenSeq: coordinator.getBoundary(target),
|
|
3369
|
+
pendingMessages: pending,
|
|
3370
|
+
recentMessages: recent,
|
|
3371
|
+
isMessageModelSeen: (messageInput) => coordinator.isMessageModelSeen?.(messageInput) === true,
|
|
3372
|
+
heldContextLimit: LOCAL_HELD_CONTEXT_LIMIT
|
|
3199
3373
|
});
|
|
3200
|
-
|
|
3374
|
+
applyAgentInboxStateMachineEffects(coordinator, plan.effects);
|
|
3375
|
+
if (typeof plan.forwardSeenUpToSeq === "number") {
|
|
3376
|
+
if (action === "send") body.seenUpToSeq = plan.forwardSeenUpToSeq;
|
|
3377
|
+
}
|
|
3378
|
+
return { bodyText: JSON.stringify(body), target, localResponse: plan.localResponse };
|
|
3201
3379
|
}
|
|
3202
3380
|
function shouldBufferJsonResponse(upstream, pathname, registration) {
|
|
3203
3381
|
if (!registration.inboxCoordinator) return false;
|
|
@@ -3217,27 +3395,26 @@ function consumeVisibleResponse(registration, targetUrl, sendTarget, responseTex
|
|
|
3217
3395
|
if (targetUrl.pathname === "/internal/agent-api/send" && parsed.state === "held" && Array.isArray(parsed.heldMessages)) {
|
|
3218
3396
|
coordinator.consumeVisibleMessages({
|
|
3219
3397
|
target: sendTarget,
|
|
3220
|
-
messages:
|
|
3398
|
+
messages: normalizeInboxVisibleMessages(parsed.heldMessages, sendTarget),
|
|
3221
3399
|
boundarySeq: typeof parsed.seenUpToSeq === "number" ? parsed.seenUpToSeq : void 0,
|
|
3222
3400
|
source: "server_held_context"
|
|
3223
3401
|
});
|
|
3224
3402
|
return;
|
|
3225
3403
|
}
|
|
3226
3404
|
if (targetUrl.pathname === "/internal/agent-api/send" && parsed.state === "sent") {
|
|
3227
|
-
const
|
|
3228
|
-
if (sendTarget &&
|
|
3405
|
+
const messageSeq4 = typeof parsed.messageSeq === "number" && Number.isFinite(parsed.messageSeq) ? Math.floor(parsed.messageSeq) : void 0;
|
|
3406
|
+
if (sendTarget && messageSeq4 && messageSeq4 > 0) {
|
|
3229
3407
|
coordinator.consumeVisibleMessages({
|
|
3230
3408
|
target: sendTarget,
|
|
3231
|
-
messages:
|
|
3232
|
-
boundarySeq: messageSeq3,
|
|
3409
|
+
messages: normalizeInboxVisibleMessages([{ id: parsed.messageId }], sendTarget),
|
|
3233
3410
|
source: "agent_api_send_commit"
|
|
3234
3411
|
});
|
|
3235
3412
|
}
|
|
3236
3413
|
return;
|
|
3237
3414
|
}
|
|
3238
3415
|
if (targetUrl.pathname === "/internal/agent-api/events" && Array.isArray(parsed.events)) {
|
|
3239
|
-
const messages =
|
|
3240
|
-
coordinator.consumeVisibleMessages({ messages, source: "
|
|
3416
|
+
const messages = normalizeInboxVisibleMessages(parsed.events);
|
|
3417
|
+
coordinator.consumeVisibleMessages({ messages, source: "agent_api_events_server" });
|
|
3241
3418
|
coordinator.recordDrainOutcome?.({
|
|
3242
3419
|
source: "server_events",
|
|
3243
3420
|
sinceCursorKind: parseAgentApiEventsQuery(targetUrl).sinceCursorKind,
|
|
@@ -3249,8 +3426,8 @@ function consumeVisibleResponse(registration, targetUrl, sendTarget, responseTex
|
|
|
3249
3426
|
}
|
|
3250
3427
|
if (targetUrl.pathname === "/internal/agent-api/history" && Array.isArray(parsed.messages)) {
|
|
3251
3428
|
const target = targetUrl.searchParams.get("channel") ?? void 0;
|
|
3252
|
-
const messages =
|
|
3253
|
-
coordinator.consumeVisibleMessages({ target, messages, boundarySeq:
|
|
3429
|
+
const messages = normalizeInboxVisibleMessages(parsed.messages, target);
|
|
3430
|
+
coordinator.consumeVisibleMessages({ target, messages, boundarySeq: maxInboxMessageSeq(messages), source: "agent_api_history" });
|
|
3254
3431
|
}
|
|
3255
3432
|
}
|
|
3256
3433
|
async function registerAgentCredentialProxy(input) {
|
|
@@ -7204,9 +7381,26 @@ var RuntimeProgressState = class {
|
|
|
7204
7381
|
};
|
|
7205
7382
|
|
|
7206
7383
|
// src/runtimeNotificationState.ts
|
|
7384
|
+
function computeInboxNoticeFingerprint(messages) {
|
|
7385
|
+
const keys = [];
|
|
7386
|
+
for (const m of messages) {
|
|
7387
|
+
const seq = typeof m.seq === "number" && Number.isFinite(m.seq) && m.seq > 0 ? Math.floor(m.seq) : null;
|
|
7388
|
+
if (seq !== null) {
|
|
7389
|
+
keys.push(`s:${seq}`);
|
|
7390
|
+
continue;
|
|
7391
|
+
}
|
|
7392
|
+
const id = typeof m.message_id === "string" && m.message_id.length > 0 ? m.message_id : typeof m.id === "string" && m.id.length > 0 ? m.id : "";
|
|
7393
|
+
if (id.length > 0) keys.push(`m:${id}`);
|
|
7394
|
+
}
|
|
7395
|
+
if (keys.length === 0) return "";
|
|
7396
|
+
keys.sort();
|
|
7397
|
+
return keys.join(",");
|
|
7398
|
+
}
|
|
7207
7399
|
var RuntimeNotificationState = class {
|
|
7208
7400
|
timerValue = null;
|
|
7209
7401
|
pendingCountValue = 0;
|
|
7402
|
+
lastNoticeFingerprint = null;
|
|
7403
|
+
lastNoticeSessionId = null;
|
|
7210
7404
|
get pendingCount() {
|
|
7211
7405
|
return this.pendingCountValue;
|
|
7212
7406
|
}
|
|
@@ -7237,6 +7431,25 @@ var RuntimeNotificationState = class {
|
|
|
7237
7431
|
clear() {
|
|
7238
7432
|
this.clearPending();
|
|
7239
7433
|
this.clearTimer();
|
|
7434
|
+
this.clearNoticeFingerprint();
|
|
7435
|
+
}
|
|
7436
|
+
/**
|
|
7437
|
+
* True iff this exact unread-set was already successfully written in THIS
|
|
7438
|
+
* session (per-(agent,session) scope — never suppress across a session
|
|
7439
|
+
* change). Empty fingerprint always returns false (fail toward sending).
|
|
7440
|
+
*/
|
|
7441
|
+
isDuplicateNotice(fingerprint, sessionId) {
|
|
7442
|
+
if (fingerprint.length === 0) return false;
|
|
7443
|
+
return this.lastNoticeFingerprint === fingerprint && this.lastNoticeSessionId === sessionId;
|
|
7444
|
+
}
|
|
7445
|
+
/** Register a fingerprint as written — call ONLY after a successful stdin write. */
|
|
7446
|
+
recordNoticeWritten(fingerprint, sessionId) {
|
|
7447
|
+
this.lastNoticeFingerprint = fingerprint;
|
|
7448
|
+
this.lastNoticeSessionId = sessionId;
|
|
7449
|
+
}
|
|
7450
|
+
clearNoticeFingerprint() {
|
|
7451
|
+
this.lastNoticeFingerprint = null;
|
|
7452
|
+
this.lastNoticeSessionId = null;
|
|
7240
7453
|
}
|
|
7241
7454
|
schedule(callback, delayMs) {
|
|
7242
7455
|
if (this.timerValue) return false;
|
|
@@ -7368,34 +7581,24 @@ function toLocalTime(iso) {
|
|
|
7368
7581
|
function formatChannelLabel(message) {
|
|
7369
7582
|
return message.channel_type === "dm" ? `DM:@${message.channel_name}` : `#${message.channel_name}`;
|
|
7370
7583
|
}
|
|
7371
|
-
function
|
|
7372
|
-
if (
|
|
7373
|
-
const shortId = getMessageShortId(
|
|
7374
|
-
if (
|
|
7375
|
-
return `dm:@${
|
|
7584
|
+
function computeTarget(channelType, channelName, parentChannelName, parentChannelType) {
|
|
7585
|
+
if (channelType === "thread" && parentChannelName) {
|
|
7586
|
+
const shortId = getMessageShortId(String(channelName ?? ""));
|
|
7587
|
+
if (parentChannelType === "dm") {
|
|
7588
|
+
return `dm:@${parentChannelName}:${shortId}`;
|
|
7376
7589
|
}
|
|
7377
|
-
return `#${
|
|
7590
|
+
return `#${parentChannelName}:${shortId}`;
|
|
7378
7591
|
}
|
|
7379
|
-
if (
|
|
7380
|
-
return `dm:@${
|
|
7592
|
+
if (channelType === "dm") {
|
|
7593
|
+
return `dm:@${channelName}`;
|
|
7381
7594
|
}
|
|
7382
|
-
return `#${
|
|
7595
|
+
return `#${channelName}`;
|
|
7596
|
+
}
|
|
7597
|
+
function formatMessageTarget(message) {
|
|
7598
|
+
return computeTarget(message.channel_type, message.channel_name, message.parent_channel_name, message.parent_channel_type);
|
|
7383
7599
|
}
|
|
7384
7600
|
function formatVisibleMessageTarget(message) {
|
|
7385
|
-
|
|
7386
|
-
const shortId = getMessageShortId(String(message.channel_name));
|
|
7387
|
-
if (message.parent_channel_type === "dm") {
|
|
7388
|
-
return `dm:@${message.parent_channel_name}:${shortId}`;
|
|
7389
|
-
}
|
|
7390
|
-
return `#${message.parent_channel_name}:${shortId}`;
|
|
7391
|
-
}
|
|
7392
|
-
if (message.channel_type === "dm" && message.channel_name) {
|
|
7393
|
-
return `dm:@${message.channel_name}`;
|
|
7394
|
-
}
|
|
7395
|
-
if (message.channel_name) {
|
|
7396
|
-
return `#${message.channel_name}`;
|
|
7397
|
-
}
|
|
7398
|
-
return null;
|
|
7601
|
+
return computeTarget(message.channel_type, message.channel_name, message.parent_channel_name, message.parent_channel_type);
|
|
7399
7602
|
}
|
|
7400
7603
|
function getMessageShortId(messageId2) {
|
|
7401
7604
|
return messageId2.startsWith("thread-") ? messageId2.slice(7) : messageId2.slice(0, 8);
|
|
@@ -7617,6 +7820,8 @@ var TRAJECTORY_COALESCE_MS = 350;
|
|
|
7617
7820
|
var ACTIVITY_HEARTBEAT_MS = 6e4;
|
|
7618
7821
|
var STDIN_NOTIFICATION_INITIAL_DELAY_MS = 3e3;
|
|
7619
7822
|
var STDIN_NOTIFICATION_RETRY_DELAY_MS = 15e3;
|
|
7823
|
+
var RUNTIME_ERROR_DELIVERY_BACKOFF_BASE_MS = 1e4;
|
|
7824
|
+
var RUNTIME_ERROR_DELIVERY_BACKOFF_MAX_MS = 5 * 6e4;
|
|
7620
7825
|
var COMPACTION_STALE_MS = 5 * 6e4;
|
|
7621
7826
|
var RUNTIME_PROGRESS_STALE_MS = 15 * 6e4;
|
|
7622
7827
|
var DEFAULT_RUNTIME_START_TIMEOUT_MS = 2 * 6e4;
|
|
@@ -8030,6 +8235,14 @@ function createRuntimeTraceCounters() {
|
|
|
8030
8235
|
thinkingEvents: 0
|
|
8031
8236
|
};
|
|
8032
8237
|
}
|
|
8238
|
+
function createRuntimeErrorDeliveryBackoffState() {
|
|
8239
|
+
return {
|
|
8240
|
+
attempts: 0,
|
|
8241
|
+
untilMs: 0,
|
|
8242
|
+
timer: null,
|
|
8243
|
+
reason: null
|
|
8244
|
+
};
|
|
8245
|
+
}
|
|
8033
8246
|
function cleanupAgentCredentialProxy(agentId, launchId) {
|
|
8034
8247
|
unregisterAgentCredentialProxyForLaunch({ agentId, launchId });
|
|
8035
8248
|
}
|
|
@@ -8474,6 +8687,10 @@ var AgentProcessManager = class _AgentProcessManager {
|
|
|
8474
8687
|
defaultAgentEnvVarsProvider;
|
|
8475
8688
|
tracer;
|
|
8476
8689
|
stdinNotificationRetryMs;
|
|
8690
|
+
runtimeErrorDeliveryBackoffBaseMs;
|
|
8691
|
+
runtimeErrorDeliveryBackoffMaxMs;
|
|
8692
|
+
runtimeErrorDeliveryBackoffJitterRatio;
|
|
8693
|
+
runtimeErrorDeliveryBackoffFailPointForTesting;
|
|
8477
8694
|
cliTransportTraceDir = null;
|
|
8478
8695
|
deliveryTraceContexts = /* @__PURE__ */ new WeakMap();
|
|
8479
8696
|
runtimeExitTraceAttrs = /* @__PURE__ */ new WeakMap();
|
|
@@ -8497,6 +8714,19 @@ var AgentProcessManager = class _AgentProcessManager {
|
|
|
8497
8714
|
0,
|
|
8498
8715
|
Math.floor(opts.stdinNotificationRetryMs ?? STDIN_NOTIFICATION_RETRY_DELAY_MS)
|
|
8499
8716
|
);
|
|
8717
|
+
this.runtimeErrorDeliveryBackoffBaseMs = Math.max(
|
|
8718
|
+
0,
|
|
8719
|
+
Math.floor(opts.runtimeErrorDeliveryBackoff?.baseMs ?? RUNTIME_ERROR_DELIVERY_BACKOFF_BASE_MS)
|
|
8720
|
+
);
|
|
8721
|
+
this.runtimeErrorDeliveryBackoffMaxMs = Math.max(
|
|
8722
|
+
this.runtimeErrorDeliveryBackoffBaseMs,
|
|
8723
|
+
Math.floor(opts.runtimeErrorDeliveryBackoff?.maxMs ?? RUNTIME_ERROR_DELIVERY_BACKOFF_MAX_MS)
|
|
8724
|
+
);
|
|
8725
|
+
this.runtimeErrorDeliveryBackoffJitterRatio = Math.max(
|
|
8726
|
+
0,
|
|
8727
|
+
Math.min(1, opts.runtimeErrorDeliveryBackoff?.jitterRatio ?? 0.1)
|
|
8728
|
+
);
|
|
8729
|
+
this.runtimeErrorDeliveryBackoffFailPointForTesting = opts.runtimeErrorDeliveryBackoff?.failPointForTesting ?? null;
|
|
8500
8730
|
this.maxConcurrentAgentStarts = Math.max(
|
|
8501
8731
|
1,
|
|
8502
8732
|
Math.floor(
|
|
@@ -8550,6 +8780,183 @@ var AgentProcessManager = class _AgentProcessManager {
|
|
|
8550
8780
|
this.sendStdinNotification(agentId);
|
|
8551
8781
|
}, delayMs);
|
|
8552
8782
|
}
|
|
8783
|
+
clearRuntimeErrorDeliveryBackoff(ap) {
|
|
8784
|
+
if (ap.runtimeErrorDeliveryBackoff.timer) {
|
|
8785
|
+
clearTimeout(ap.runtimeErrorDeliveryBackoff.timer);
|
|
8786
|
+
}
|
|
8787
|
+
ap.runtimeErrorDeliveryBackoff = createRuntimeErrorDeliveryBackoffState();
|
|
8788
|
+
}
|
|
8789
|
+
clearRuntimeErrorDeliveryBackoffAfterProgress(agentId, ap, eventKind) {
|
|
8790
|
+
if (ap.runtimeErrorDeliveryBackoff.attempts === 0 && ap.runtimeErrorDeliveryBackoff.untilMs === 0) return;
|
|
8791
|
+
const attempts = ap.runtimeErrorDeliveryBackoff.attempts;
|
|
8792
|
+
const reason = ap.runtimeErrorDeliveryBackoff.reason;
|
|
8793
|
+
this.clearRuntimeErrorDeliveryBackoff(ap);
|
|
8794
|
+
this.recordDaemonTrace("daemon.agent.runtime_error_delivery_backoff.reset", {
|
|
8795
|
+
agentId,
|
|
8796
|
+
runtime: ap.config.runtime,
|
|
8797
|
+
model: ap.config.model,
|
|
8798
|
+
launchId: ap.launchId || void 0,
|
|
8799
|
+
reason: reason || void 0,
|
|
8800
|
+
attempts,
|
|
8801
|
+
reset_source: eventKind
|
|
8802
|
+
});
|
|
8803
|
+
}
|
|
8804
|
+
runtimeErrorDeliveryBackoffRemainingMs(ap) {
|
|
8805
|
+
return Math.max(0, ap.runtimeErrorDeliveryBackoff.untilMs - Date.now());
|
|
8806
|
+
}
|
|
8807
|
+
scheduleRuntimeErrorDeliveryBackoffFlush(agentId, ap) {
|
|
8808
|
+
const delayMs = this.runtimeErrorDeliveryBackoffRemainingMs(ap);
|
|
8809
|
+
if (delayMs <= 0 || ap.runtimeErrorDeliveryBackoff.timer) return false;
|
|
8810
|
+
ap.runtimeErrorDeliveryBackoff.timer = setTimeout(() => {
|
|
8811
|
+
ap.runtimeErrorDeliveryBackoff.timer = null;
|
|
8812
|
+
this.flushRuntimeErrorDeliveryBackoff(agentId);
|
|
8813
|
+
}, delayMs);
|
|
8814
|
+
ap.runtimeErrorDeliveryBackoff.timer.unref?.();
|
|
8815
|
+
return true;
|
|
8816
|
+
}
|
|
8817
|
+
recoverableRuntimeDeliveryBackoffReason(message, terminalFailure, stickyTerminalFailure, reasonOverride) {
|
|
8818
|
+
if (stickyTerminalFailure || terminalFailure?.actionRequired) return null;
|
|
8819
|
+
if (reasonOverride !== void 0) return reasonOverride;
|
|
8820
|
+
if (terminalFailure) return "recoverable_terminal_runtime_error";
|
|
8821
|
+
const runtimeErrorClass = buildRuntimeErrorDiagnosticEnvelope(message).spanAttrs.runtime_error_class;
|
|
8822
|
+
switch (runtimeErrorClass) {
|
|
8823
|
+
case "RateLimitError":
|
|
8824
|
+
return "rate_limited";
|
|
8825
|
+
case "ProviderServerError":
|
|
8826
|
+
return "provider_server_error";
|
|
8827
|
+
case "ProviderConnectionError":
|
|
8828
|
+
return "provider_connection_error";
|
|
8829
|
+
case "ProviderStreamError":
|
|
8830
|
+
return "provider_stream_error";
|
|
8831
|
+
default:
|
|
8832
|
+
return null;
|
|
8833
|
+
}
|
|
8834
|
+
}
|
|
8835
|
+
noteRuntimeErrorDeliveryBackoff(agentId, ap, message, terminalFailure, stickyTerminalFailure, reasonOverride) {
|
|
8836
|
+
const reason = this.recoverableRuntimeDeliveryBackoffReason(message, terminalFailure, stickyTerminalFailure, reasonOverride);
|
|
8837
|
+
if (!reason) return false;
|
|
8838
|
+
const attempts = ap.runtimeErrorDeliveryBackoff.attempts + 1;
|
|
8839
|
+
const exponentialDelayMs = this.runtimeErrorDeliveryBackoffBaseMs * 2 ** Math.max(0, attempts - 1);
|
|
8840
|
+
const cappedDelayMs = Math.min(this.runtimeErrorDeliveryBackoffMaxMs, exponentialDelayMs);
|
|
8841
|
+
const jitterMs = Math.floor(cappedDelayMs * this.runtimeErrorDeliveryBackoffJitterRatio * Math.random());
|
|
8842
|
+
const delayMs = Math.min(this.runtimeErrorDeliveryBackoffMaxMs, cappedDelayMs + jitterMs);
|
|
8843
|
+
if (ap.runtimeErrorDeliveryBackoff.timer) {
|
|
8844
|
+
clearTimeout(ap.runtimeErrorDeliveryBackoff.timer);
|
|
8845
|
+
ap.runtimeErrorDeliveryBackoff.timer = null;
|
|
8846
|
+
}
|
|
8847
|
+
ap.runtimeErrorDeliveryBackoff.attempts = attempts;
|
|
8848
|
+
ap.runtimeErrorDeliveryBackoff.reason = reason;
|
|
8849
|
+
ap.runtimeErrorDeliveryBackoff.untilMs = Date.now() + delayMs;
|
|
8850
|
+
if (ap.inbox.length > 0) {
|
|
8851
|
+
this.scheduleRuntimeErrorDeliveryBackoffFlush(agentId, ap);
|
|
8852
|
+
}
|
|
8853
|
+
this.recordDaemonTrace("daemon.agent.runtime_error_delivery_backoff", {
|
|
8854
|
+
agentId,
|
|
8855
|
+
runtime: ap.config.runtime,
|
|
8856
|
+
model: ap.config.model,
|
|
8857
|
+
launchId: ap.launchId || void 0,
|
|
8858
|
+
reason,
|
|
8859
|
+
attempts,
|
|
8860
|
+
delay_ms: delayMs,
|
|
8861
|
+
until_ms: ap.runtimeErrorDeliveryBackoff.untilMs,
|
|
8862
|
+
inbox_count: ap.inbox.length,
|
|
8863
|
+
pending_notification_count: ap.notifications.pendingCount
|
|
8864
|
+
});
|
|
8865
|
+
return true;
|
|
8866
|
+
}
|
|
8867
|
+
queueDeliveryForRuntimeErrorBackoff(agentId, ap, message) {
|
|
8868
|
+
const remainingMs = this.runtimeErrorDeliveryBackoffRemainingMs(ap);
|
|
8869
|
+
if (remainingMs <= 0) return false;
|
|
8870
|
+
ap.inbox.push(message);
|
|
8871
|
+
if (!ap.isIdle && ap.driver.supportsStdinNotification && ap.sessionId) {
|
|
8872
|
+
ap.notifications.add();
|
|
8873
|
+
}
|
|
8874
|
+
const scheduled = this.scheduleRuntimeErrorDeliveryBackoffFlush(agentId, ap);
|
|
8875
|
+
this.recordDaemonTrace("daemon.agent.delivery.routed", this.deliveryTraceAttrs(agentId, message, {
|
|
8876
|
+
outcome: "queued_runtime_error_backoff",
|
|
8877
|
+
accepted: true,
|
|
8878
|
+
process_present: true,
|
|
8879
|
+
runtime: ap.config.runtime,
|
|
8880
|
+
session_id_present: Boolean(ap.sessionId),
|
|
8881
|
+
launchId: ap.launchId || void 0,
|
|
8882
|
+
is_idle: ap.isIdle,
|
|
8883
|
+
inbox_count: ap.inbox.length,
|
|
8884
|
+
pending_notification_count: ap.notifications.pendingCount,
|
|
8885
|
+
runtime_error_backoff_remaining_ms: remainingMs,
|
|
8886
|
+
runtime_error_backoff_attempts: ap.runtimeErrorDeliveryBackoff.attempts,
|
|
8887
|
+
runtime_error_backoff_reason: ap.runtimeErrorDeliveryBackoff.reason || void 0,
|
|
8888
|
+
runtime_error_backoff_timer_scheduled: scheduled || ap.runtimeErrorDeliveryBackoff.timer !== null
|
|
8889
|
+
}));
|
|
8890
|
+
return true;
|
|
8891
|
+
}
|
|
8892
|
+
flushRuntimeErrorDeliveryBackoff(agentId) {
|
|
8893
|
+
const ap = this.agents.get(agentId);
|
|
8894
|
+
if (!ap) return false;
|
|
8895
|
+
const remainingMs = this.runtimeErrorDeliveryBackoffRemainingMs(ap);
|
|
8896
|
+
if (remainingMs > 0) {
|
|
8897
|
+
this.scheduleRuntimeErrorDeliveryBackoffFlush(agentId, ap);
|
|
8898
|
+
return false;
|
|
8899
|
+
}
|
|
8900
|
+
if (ap.inbox.length === 0) return false;
|
|
8901
|
+
const reason = ap.runtimeErrorDeliveryBackoff.reason || "runtime_error_backoff";
|
|
8902
|
+
if (ap.isIdle && ap.driver.supportsStdinNotification && ap.sessionId) {
|
|
8903
|
+
const messages = [...ap.inbox];
|
|
8904
|
+
ap.notifications.clearPending();
|
|
8905
|
+
ap.notifications.clearTimer();
|
|
8906
|
+
this.commitApmIdleState(agentId, ap, false);
|
|
8907
|
+
this.startRuntimeTrace(agentId, ap, "runtime-error-backoff-idle-delivery", messages);
|
|
8908
|
+
this.broadcastActivity(agentId, "working", "Message received");
|
|
8909
|
+
const accepted2 = this.deliverInboxUpdateViaStdin(
|
|
8910
|
+
agentId,
|
|
8911
|
+
ap,
|
|
8912
|
+
messages,
|
|
8913
|
+
"idle",
|
|
8914
|
+
"runtime_error_backoff_idle_delivery"
|
|
8915
|
+
);
|
|
8916
|
+
this.recordDaemonTrace("daemon.agent.runtime_error_delivery_backoff.flush", {
|
|
8917
|
+
agentId,
|
|
8918
|
+
runtime: ap.config.runtime,
|
|
8919
|
+
model: ap.config.model,
|
|
8920
|
+
launchId: ap.launchId || void 0,
|
|
8921
|
+
reason,
|
|
8922
|
+
mode: "idle",
|
|
8923
|
+
outcome: accepted2 ? "written" : "not_written",
|
|
8924
|
+
inbox_count: ap.inbox.length,
|
|
8925
|
+
messages_count: messages.length
|
|
8926
|
+
});
|
|
8927
|
+
return accepted2;
|
|
8928
|
+
}
|
|
8929
|
+
if (!ap.driver.supportsStdinNotification || !ap.sessionId) {
|
|
8930
|
+
this.recordDaemonTrace("daemon.agent.runtime_error_delivery_backoff.flush", {
|
|
8931
|
+
agentId,
|
|
8932
|
+
runtime: ap.config.runtime,
|
|
8933
|
+
model: ap.config.model,
|
|
8934
|
+
launchId: ap.launchId || void 0,
|
|
8935
|
+
reason,
|
|
8936
|
+
outcome: "queued_without_stdin",
|
|
8937
|
+
inbox_count: ap.inbox.length,
|
|
8938
|
+
session_id_present: Boolean(ap.sessionId),
|
|
8939
|
+
supports_stdin_notification: ap.driver.supportsStdinNotification
|
|
8940
|
+
});
|
|
8941
|
+
return false;
|
|
8942
|
+
}
|
|
8943
|
+
if (ap.notifications.pendingCount === 0) {
|
|
8944
|
+
ap.notifications.add(ap.inbox.length);
|
|
8945
|
+
}
|
|
8946
|
+
const accepted = this.sendStdinNotification(agentId);
|
|
8947
|
+
this.recordDaemonTrace("daemon.agent.runtime_error_delivery_backoff.flush", {
|
|
8948
|
+
agentId,
|
|
8949
|
+
runtime: ap.config.runtime,
|
|
8950
|
+
model: ap.config.model,
|
|
8951
|
+
launchId: ap.launchId || void 0,
|
|
8952
|
+
reason,
|
|
8953
|
+
mode: "busy",
|
|
8954
|
+
outcome: accepted ? "written" : "not_written",
|
|
8955
|
+
inbox_count: ap.inbox.length,
|
|
8956
|
+
pending_notification_count: ap.notifications.pendingCount
|
|
8957
|
+
});
|
|
8958
|
+
return accepted;
|
|
8959
|
+
}
|
|
8553
8960
|
allPendingVisibleMessages(agentId) {
|
|
8554
8961
|
const collect = (messages) => (messages ?? []).filter((message) => typeof message.seq === "number" && message.seq > 0);
|
|
8555
8962
|
return [
|
|
@@ -8596,10 +9003,13 @@ var AgentProcessManager = class _AgentProcessManager {
|
|
|
8596
9003
|
if (byTarget.size === 0) return;
|
|
8597
9004
|
const boundaryMap = this.visibleBoundaryMap(agentId);
|
|
8598
9005
|
const visibleIds = this.visibleMessageIdMap(agentId);
|
|
9006
|
+
const advancesBoundary = input.source === "spawn_wake_message" || input.source === "agent_api_events_local" || input.source.startsWith("stdin_");
|
|
8599
9007
|
for (const [target, bucket] of byTarget) {
|
|
8600
|
-
|
|
8601
|
-
|
|
8602
|
-
|
|
9008
|
+
if (advancesBoundary) {
|
|
9009
|
+
const highWaterSeq = Math.max(bucket.maxSeq, bucket.boundarySeq);
|
|
9010
|
+
const previous = boundaryMap.get(target) ?? 0;
|
|
9011
|
+
boundaryMap.set(target, Math.max(previous, highWaterSeq));
|
|
9012
|
+
}
|
|
8603
9013
|
if (bucket.ids.size > 0) {
|
|
8604
9014
|
let targetIds = visibleIds.get(target);
|
|
8605
9015
|
if (!targetIds) {
|
|
@@ -8663,6 +9073,7 @@ var AgentProcessManager = class _AgentProcessManager {
|
|
|
8663
9073
|
active.notifications.remove(removedActive);
|
|
8664
9074
|
if (active.inbox.length === 0) {
|
|
8665
9075
|
active.notifications.clear();
|
|
9076
|
+
this.clearRuntimeErrorDeliveryBackoff(active);
|
|
8666
9077
|
}
|
|
8667
9078
|
}
|
|
8668
9079
|
const removedCount = removedActive + removedStarting;
|
|
@@ -9177,6 +9588,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
9177
9588
|
recentStdout: [],
|
|
9178
9589
|
recentStderr: [],
|
|
9179
9590
|
lastRuntimeError: null,
|
|
9591
|
+
runtimeErrorDeliveryBackoff: createRuntimeErrorDeliveryBackoffState(),
|
|
9180
9592
|
spawnError: null,
|
|
9181
9593
|
exitCode: null,
|
|
9182
9594
|
exitSignal: null,
|
|
@@ -9279,6 +9691,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
9279
9691
|
const ap = this.agents.get(agentId);
|
|
9280
9692
|
if (ap.runtime !== runtime) return;
|
|
9281
9693
|
ap.notifications.clearTimer();
|
|
9694
|
+
this.clearRuntimeErrorDeliveryBackoff(ap);
|
|
9282
9695
|
if (ap.pendingTrajectory?.timer) {
|
|
9283
9696
|
clearTimeout(ap.pendingTrajectory.timer);
|
|
9284
9697
|
}
|
|
@@ -9396,6 +9809,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
9396
9809
|
logger.warn(`[Agent ${agentId}] Recoverable provider stream failure (${reason}) \u2014 keeping agent wakeable`);
|
|
9397
9810
|
this.sendAgentStatus(agentId, "active", ap.launchId);
|
|
9398
9811
|
} else if (startupTimeoutTermination) {
|
|
9812
|
+
this.cacheStartupTimeoutRetryConfig(agentId, ap);
|
|
9399
9813
|
logger.warn(`[Agent ${agentId}] Startup timeout cleanup completed (${reason})`);
|
|
9400
9814
|
} else {
|
|
9401
9815
|
this.idleAgentConfigs.delete(agentId);
|
|
@@ -9466,6 +9880,23 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
9466
9880
|
this.agents.delete(agentId);
|
|
9467
9881
|
this.idleAgentConfigs.delete(agentId);
|
|
9468
9882
|
}
|
|
9883
|
+
cacheStartupTimeoutRetryConfig(agentId, ap) {
|
|
9884
|
+
const retryConfig = {
|
|
9885
|
+
...stripManagedRunnerCredential(ap.config),
|
|
9886
|
+
sessionId: ap.sessionId
|
|
9887
|
+
};
|
|
9888
|
+
this.idleAgentConfigs.set(agentId, {
|
|
9889
|
+
config: retryConfig,
|
|
9890
|
+
sessionId: ap.sessionId,
|
|
9891
|
+
launchId: ap.launchId
|
|
9892
|
+
});
|
|
9893
|
+
this.recordDaemonTrace("daemon.agent.startup_timeout.retry_config_cached", {
|
|
9894
|
+
agentId,
|
|
9895
|
+
launchId: ap.launchId || void 0,
|
|
9896
|
+
runtime: ap.config.runtime,
|
|
9897
|
+
session_id_present: Boolean(ap.sessionId)
|
|
9898
|
+
});
|
|
9899
|
+
}
|
|
9469
9900
|
async buildSpawnConfig(agentId, config) {
|
|
9470
9901
|
const baseConfig = config.serverUrl === this.serverUrl ? config : { ...config, serverUrl: this.serverUrl };
|
|
9471
9902
|
const runnerConfig = await this.ensureManagedRunnerCredential(agentId, baseConfig);
|
|
@@ -9693,6 +10124,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
9693
10124
|
return;
|
|
9694
10125
|
}
|
|
9695
10126
|
ap.notifications.clearTimer();
|
|
10127
|
+
this.clearRuntimeErrorDeliveryBackoff(ap);
|
|
9696
10128
|
if (ap.activityHeartbeat) {
|
|
9697
10129
|
clearInterval(ap.activityHeartbeat);
|
|
9698
10130
|
}
|
|
@@ -9747,6 +10179,14 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
9747
10179
|
this.deliveryTraceContexts.set(message, traceContext);
|
|
9748
10180
|
}
|
|
9749
10181
|
const transientDelivery = this.isTransientDelivery(message);
|
|
10182
|
+
if (!transientDelivery && this.isVisibleMessageModelSeen(agentId, formatMessageTarget(message), message)) {
|
|
10183
|
+
this.recordDaemonTrace("daemon.agent.delivery.routed", this.deliveryTraceAttrs(agentId, message, {
|
|
10184
|
+
outcome: "dropped_already_consumed",
|
|
10185
|
+
accepted: true,
|
|
10186
|
+
process_present: Boolean(this.agents.get(agentId))
|
|
10187
|
+
}));
|
|
10188
|
+
return true;
|
|
10189
|
+
}
|
|
9750
10190
|
const ap = this.agents.get(agentId);
|
|
9751
10191
|
if (!ap) {
|
|
9752
10192
|
if (this.agentsStarting.has(agentId) || this.queuedAgentStarts.has(agentId)) {
|
|
@@ -9890,6 +10330,9 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
9890
10330
|
return true;
|
|
9891
10331
|
}
|
|
9892
10332
|
}
|
|
10333
|
+
if (!transientDelivery && this.queueDeliveryForRuntimeErrorBackoff(agentId, ap, message)) {
|
|
10334
|
+
return true;
|
|
10335
|
+
}
|
|
9893
10336
|
if (ap.isIdle && ap.driver.supportsStdinNotification && ap.sessionId) {
|
|
9894
10337
|
if (transientDelivery) {
|
|
9895
10338
|
this.commitApmIdleState(agentId, ap, false);
|
|
@@ -10942,6 +11385,19 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
10942
11385
|
return written;
|
|
10943
11386
|
}
|
|
10944
11387
|
case "deliver_stdin": {
|
|
11388
|
+
const runtimeErrorBackoffRemainingMs = this.runtimeErrorDeliveryBackoffRemainingMs(ap);
|
|
11389
|
+
if (runtimeErrorBackoffRemainingMs > 0) {
|
|
11390
|
+
const scheduled = this.scheduleRuntimeErrorDeliveryBackoffFlush(agentId, ap);
|
|
11391
|
+
this.recordApmGatedSteeringEffectTrace(agentId, ap, effect, {
|
|
11392
|
+
outcome: "suppressed_runtime_error_backoff",
|
|
11393
|
+
runtime_error_backoff_remaining_ms: runtimeErrorBackoffRemainingMs,
|
|
11394
|
+
runtime_error_backoff_attempts: ap.runtimeErrorDeliveryBackoff.attempts,
|
|
11395
|
+
runtime_error_backoff_reason: ap.runtimeErrorDeliveryBackoff.reason || void 0,
|
|
11396
|
+
runtime_error_backoff_timer_scheduled: scheduled || ap.runtimeErrorDeliveryBackoff.timer !== null,
|
|
11397
|
+
delivered_messages_count: 0
|
|
11398
|
+
});
|
|
11399
|
+
return false;
|
|
11400
|
+
}
|
|
10945
11401
|
const messages = [...ap.inbox];
|
|
10946
11402
|
ap.notifications.clear();
|
|
10947
11403
|
if (messages.length === 0) {
|
|
@@ -11051,7 +11507,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
11051
11507
|
logger.warn(`[Agent ${agentId}] ${ap.driver.id} did not emit a startup runtime event within ${timeoutMs}ms; terminating process`);
|
|
11052
11508
|
this.broadcastActivity(agentId, "error", detail, [{ kind: "text", text: `Error: ${detail}` }], ap.launchId);
|
|
11053
11509
|
this.sendAgentStatus(agentId, "inactive", ap.launchId);
|
|
11054
|
-
this.
|
|
11510
|
+
this.cacheStartupTimeoutRetryConfig(agentId, ap);
|
|
11055
11511
|
try {
|
|
11056
11512
|
this.runtimeExitTraceAttrs.set(ap.runtime, {
|
|
11057
11513
|
stop_source: "startup_timeout",
|
|
@@ -11182,6 +11638,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
11182
11638
|
}
|
|
11183
11639
|
if (event.kind === "internal_progress") {
|
|
11184
11640
|
ap.runtimeProgress.noteInternalProgress();
|
|
11641
|
+
this.clearRuntimeErrorDeliveryBackoffAfterProgress(agentId, ap, event.kind);
|
|
11185
11642
|
this.recordRuntimeTraceEvent(agentId, ap, "runtime.progress.internal_observed", {
|
|
11186
11643
|
turn_outcome: "held",
|
|
11187
11644
|
turn_subtype: "runtime_progress",
|
|
@@ -11206,6 +11663,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
11206
11663
|
this.completeCompactionIfActive(agentId, "Context compaction finished (inferred from resumed output)");
|
|
11207
11664
|
this.queueTrajectoryText(agentId, "thinking", event.text);
|
|
11208
11665
|
if (ap) {
|
|
11666
|
+
this.clearRuntimeErrorDeliveryBackoffAfterProgress(agentId, ap, event.kind);
|
|
11209
11667
|
const reduction = reduceApmGatedAssistantContinuation(ap.gatedSteering);
|
|
11210
11668
|
this.commitGatedSteeringDecisionState(agentId, ap, reduction.nextState, { event: "thinking" });
|
|
11211
11669
|
}
|
|
@@ -11215,6 +11673,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
11215
11673
|
this.completeCompactionIfActive(agentId, "Context compaction finished (inferred from resumed output)");
|
|
11216
11674
|
this.queueTrajectoryText(agentId, "text", event.text);
|
|
11217
11675
|
if (ap) {
|
|
11676
|
+
this.clearRuntimeErrorDeliveryBackoffAfterProgress(agentId, ap, event.kind);
|
|
11218
11677
|
const reduction = reduceApmGatedAssistantContinuation(ap.gatedSteering);
|
|
11219
11678
|
this.commitGatedSteeringDecisionState(agentId, ap, reduction.nextState, { event: "text" });
|
|
11220
11679
|
}
|
|
@@ -11225,6 +11684,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
11225
11684
|
this.flushPendingTrajectory(agentId);
|
|
11226
11685
|
const invocation = normalizeToolDisplayInvocation(event.name, event.input);
|
|
11227
11686
|
if (ap) {
|
|
11687
|
+
this.clearRuntimeErrorDeliveryBackoffAfterProgress(agentId, ap, event.kind);
|
|
11228
11688
|
const reduction = reduceApmGatedToolUse(ap.gatedSteering, { kind: "tool_call" });
|
|
11229
11689
|
this.recordRuntimeTraceEvent(agentId, ap, "tool.call.started", { tool: invocation.toolName });
|
|
11230
11690
|
this.commitGatedSteeringDecisionState(agentId, ap, reduction.nextState, {
|
|
@@ -11244,6 +11704,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
11244
11704
|
case "tool_output": {
|
|
11245
11705
|
const invocation = normalizeToolDisplayInvocation(event.name, {});
|
|
11246
11706
|
if (ap) {
|
|
11707
|
+
this.clearRuntimeErrorDeliveryBackoffAfterProgress(agentId, ap, event.kind);
|
|
11247
11708
|
const reduction = reduceApmGatedToolUse(ap.gatedSteering, { kind: "tool_output" });
|
|
11248
11709
|
this.recordRuntimeTraceEvent(agentId, ap, "tool.output.observed", { tool: invocation.toolName });
|
|
11249
11710
|
this.recordRuntimeTraceEvent(agentId, ap, "runtime.continuation.expected");
|
|
@@ -11303,6 +11764,8 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
11303
11764
|
this.commitApmIdleState(agentId, ap, true);
|
|
11304
11765
|
if (stickyTerminalFailure) {
|
|
11305
11766
|
this.broadcastActivity(agentId, "error", stickyTerminalFailure.detail);
|
|
11767
|
+
} else if (ap.lastRuntimeError && ap.runtimeErrorDeliveryBackoff.attempts > 0) {
|
|
11768
|
+
this.broadcastActivity(agentId, "error", ap.lastRuntimeError);
|
|
11306
11769
|
} else {
|
|
11307
11770
|
this.broadcastActivity(agentId, "online", "Idle");
|
|
11308
11771
|
}
|
|
@@ -11352,12 +11815,19 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
11352
11815
|
visibleErrorMessage = formatRuntimeLoginRequiredMessage(ap.driver.id);
|
|
11353
11816
|
}
|
|
11354
11817
|
const shouldDisableToolBoundaryFlush = ap.runtime.descriptor.busyDelivery === "gated" && this.isThinkingBlockMutationError(event.message);
|
|
11355
|
-
const
|
|
11818
|
+
const backoffFailPoint = this.runtimeErrorDeliveryBackoffFailPointForTesting?.({
|
|
11819
|
+
agentId,
|
|
11820
|
+
message: event.message
|
|
11821
|
+
}) ?? null;
|
|
11822
|
+
const terminalFailure = backoffFailPoint && Object.prototype.hasOwnProperty.call(backoffFailPoint, "terminalFailure") ? backoffFailPoint.terminalFailure ?? null : classifyTerminalFailure(ap);
|
|
11823
|
+
const stickyTerminalFailure = backoffFailPoint && Object.prototype.hasOwnProperty.call(backoffFailPoint, "stickyTerminalFailure") ? backoffFailPoint.stickyTerminalFailure ?? null : classifyStickyTerminalFailure(ap);
|
|
11824
|
+
const backoffReasonOverride = backoffFailPoint && Object.prototype.hasOwnProperty.call(backoffFailPoint, "reason") ? backoffFailPoint.reason ?? null : void 0;
|
|
11356
11825
|
const reduction = reduceApmGatedError(ap.gatedSteering, {
|
|
11357
11826
|
disableToolBoundaryFlush: shouldDisableToolBoundaryFlush,
|
|
11358
11827
|
terminalWakeable: Boolean(ap.driver.supportsStdinNotification && terminalFailure && !terminalFailure.actionRequired)
|
|
11359
11828
|
});
|
|
11360
11829
|
this.commitGatedSteeringDecisionState(agentId, ap, reduction.nextState, { event: "error" });
|
|
11830
|
+
this.noteRuntimeErrorDeliveryBackoff(agentId, ap, event.message, terminalFailure, stickyTerminalFailure, backoffReasonOverride);
|
|
11361
11831
|
if (reduction.shouldDisableToolBoundaryFlush) {
|
|
11362
11832
|
this.recordGatedSteeringEvent(agentId, ap, "disabled", {
|
|
11363
11833
|
reason: "thinking_block_mutation_error",
|
|
@@ -11378,7 +11848,6 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
11378
11848
|
...this.finalizeRuntimeProfileTurnControl(agentId, ap, "runtime_error")
|
|
11379
11849
|
});
|
|
11380
11850
|
if (ap.driver.supportsStdinNotification && terminalFailure) {
|
|
11381
|
-
const stickyTerminalFailure = classifyStickyTerminalFailure(ap);
|
|
11382
11851
|
if (terminalFailure.actionRequired) {
|
|
11383
11852
|
logger.warn(`[Agent ${agentId}] ${ap.driver.id} auth requires user action; terminating runtime process`);
|
|
11384
11853
|
try {
|
|
@@ -11396,7 +11865,8 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
11396
11865
|
this.sendAgentStatus(agentId, "inactive", ap.launchId);
|
|
11397
11866
|
logger.warn(`[Agent ${agentId}] ${ap.driver.id} terminal runtime error requires explicit recovery`);
|
|
11398
11867
|
} else {
|
|
11399
|
-
ap.notifications.
|
|
11868
|
+
ap.notifications.clearPending();
|
|
11869
|
+
ap.notifications.clearTimer();
|
|
11400
11870
|
logger.info(`[Agent ${agentId}] Marked ${ap.driver.id} wakeable after terminal runtime error`);
|
|
11401
11871
|
}
|
|
11402
11872
|
}
|
|
@@ -11507,6 +11977,27 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
11507
11977
|
if (count === 0) return false;
|
|
11508
11978
|
if (ap.isIdle) return false;
|
|
11509
11979
|
if (!ap.sessionId) return false;
|
|
11980
|
+
const runtimeErrorBackoffRemainingMs = this.runtimeErrorDeliveryBackoffRemainingMs(ap);
|
|
11981
|
+
if (runtimeErrorBackoffRemainingMs > 0) {
|
|
11982
|
+
ap.notifications.add(count);
|
|
11983
|
+
const scheduled = this.scheduleRuntimeErrorDeliveryBackoffFlush(agentId, ap);
|
|
11984
|
+
this.recordDaemonTrace("daemon.agent.stdin_notification", {
|
|
11985
|
+
agentId,
|
|
11986
|
+
runtime: ap.config.runtime,
|
|
11987
|
+
model: ap.config.model,
|
|
11988
|
+
launchId: ap.launchId || void 0,
|
|
11989
|
+
outcome: "suppressed_runtime_error_backoff",
|
|
11990
|
+
mode: "busy",
|
|
11991
|
+
pending_notification_count: count,
|
|
11992
|
+
inbox_count: ap.inbox.length,
|
|
11993
|
+
session_id_present: true,
|
|
11994
|
+
runtime_error_backoff_remaining_ms: runtimeErrorBackoffRemainingMs,
|
|
11995
|
+
runtime_error_backoff_attempts: ap.runtimeErrorDeliveryBackoff.attempts,
|
|
11996
|
+
runtime_error_backoff_reason: ap.runtimeErrorDeliveryBackoff.reason || void 0,
|
|
11997
|
+
runtime_error_backoff_timer_scheduled: scheduled || ap.runtimeErrorDeliveryBackoff.timer !== null
|
|
11998
|
+
});
|
|
11999
|
+
return false;
|
|
12000
|
+
}
|
|
11510
12001
|
if (ap.gatedSteering.compacting) {
|
|
11511
12002
|
this.recordRuntimeTraceEvent(agentId, ap, "runtime.compaction_boundary.delivery_suppressed", {
|
|
11512
12003
|
pendingNotificationCount: count,
|
|
@@ -11522,6 +12013,22 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
11522
12013
|
const inboxCount = ap.inbox.length;
|
|
11523
12014
|
if (inboxCount === 0) return false;
|
|
11524
12015
|
const changedMessages = ap.inbox.slice(Math.max(0, ap.inbox.length - count));
|
|
12016
|
+
const noticeFingerprint = computeInboxNoticeFingerprint(changedMessages);
|
|
12017
|
+
if (ap.notifications.isDuplicateNotice(noticeFingerprint, ap.sessionId)) {
|
|
12018
|
+
this.recordDaemonTrace("daemon.agent.stdin_notification", {
|
|
12019
|
+
agentId,
|
|
12020
|
+
runtime: ap.config.runtime,
|
|
12021
|
+
model: ap.config.model,
|
|
12022
|
+
launchId: ap.launchId || void 0,
|
|
12023
|
+
outcome: "suppressed_duplicate",
|
|
12024
|
+
mode: "busy",
|
|
12025
|
+
pending_notification_count: count,
|
|
12026
|
+
inbox_count: ap.inbox.length,
|
|
12027
|
+
session_id_present: true
|
|
12028
|
+
});
|
|
12029
|
+
logger.info(`[Agent ${agentId}] Suppressing duplicate stdin inbox notice (unread-set unchanged since last write); pending=${ap.inbox.length}`);
|
|
12030
|
+
return false;
|
|
12031
|
+
}
|
|
11525
12032
|
const inboxRows = projectAgentInboxSnapshot(changedMessages);
|
|
11526
12033
|
const notification = `[Slock inbox notice:
|
|
11527
12034
|
${formatAgentInboxDelta(inboxRows, { totalPendingMessages: inboxCount })}]`;
|
|
@@ -11559,6 +12066,7 @@ ${formatAgentInboxDelta(inboxRows, { totalPendingMessages: inboxCount })}]`;
|
|
|
11559
12066
|
inbox_target_count: inboxRows.length,
|
|
11560
12067
|
session_id_present: true
|
|
11561
12068
|
});
|
|
12069
|
+
ap.notifications.recordNoticeWritten(noticeFingerprint, ap.sessionId);
|
|
11562
12070
|
return true;
|
|
11563
12071
|
} else {
|
|
11564
12072
|
ap.notifications.add(count);
|
|
@@ -11855,6 +12363,8 @@ var DaemonConnection = class {
|
|
|
11855
12363
|
lastDroppedSendLogAt = 0;
|
|
11856
12364
|
lastInboundAt = null;
|
|
11857
12365
|
lastInboundMessageKind = null;
|
|
12366
|
+
pendingActivityByAgent = /* @__PURE__ */ new Map();
|
|
12367
|
+
latestObservedLaunchIdByAgent = /* @__PURE__ */ new Map();
|
|
11858
12368
|
constructor(options) {
|
|
11859
12369
|
this.options = options;
|
|
11860
12370
|
this.clock = options.clock ?? systemClock;
|
|
@@ -11868,6 +12378,7 @@ var DaemonConnection = class {
|
|
|
11868
12378
|
}
|
|
11869
12379
|
disconnect() {
|
|
11870
12380
|
this.shouldConnect = false;
|
|
12381
|
+
this.pendingActivityByAgent.clear();
|
|
11871
12382
|
this.clearWatchdog();
|
|
11872
12383
|
if (this.reconnectTimer) {
|
|
11873
12384
|
this.clock.clearTimeout(this.reconnectTimer);
|
|
@@ -11881,9 +12392,15 @@ var DaemonConnection = class {
|
|
|
11881
12392
|
}
|
|
11882
12393
|
send(msg) {
|
|
11883
12394
|
if (this.ws?.readyState === WebSocket.OPEN) {
|
|
12395
|
+
this.observeLaunchIdentity(msg);
|
|
12396
|
+
if (msg.type === "agent:activity") {
|
|
12397
|
+
this.pendingActivityByAgent.delete(msg.agentId);
|
|
12398
|
+
}
|
|
11884
12399
|
this.ws.send(JSON.stringify(msg));
|
|
11885
12400
|
return;
|
|
11886
12401
|
}
|
|
12402
|
+
this.observeLaunchIdentity(msg);
|
|
12403
|
+
this.queueReplayableMessage(msg);
|
|
11887
12404
|
const now = this.clock.now();
|
|
11888
12405
|
if (now - this.lastDroppedSendLogAt > 5e3) {
|
|
11889
12406
|
this.lastDroppedSendLogAt = now;
|
|
@@ -11926,6 +12443,7 @@ var DaemonConnection = class {
|
|
|
11926
12443
|
reconnect_attempt: priorReconnectAttempt,
|
|
11927
12444
|
inbound_watchdog_ms: this.options.inboundWatchdogMs ?? INBOUND_WATCHDOG_MS
|
|
11928
12445
|
});
|
|
12446
|
+
this.flushPendingActivity(ws);
|
|
11929
12447
|
this.options.onConnect();
|
|
11930
12448
|
});
|
|
11931
12449
|
ws.on("message", (data) => {
|
|
@@ -12023,6 +12541,65 @@ var DaemonConnection = class {
|
|
|
12023
12541
|
this.lastInboundAt = this.clock.now();
|
|
12024
12542
|
this.lastInboundMessageKind = messageKind;
|
|
12025
12543
|
}
|
|
12544
|
+
queueReplayableMessage(msg) {
|
|
12545
|
+
if (msg.type !== "agent:activity") return;
|
|
12546
|
+
const latestLaunchId = this.latestObservedLaunchIdByAgent.get(msg.agentId);
|
|
12547
|
+
if (msg.launchId && latestLaunchId && msg.launchId !== latestLaunchId) {
|
|
12548
|
+
this.trace("daemon.connection.pending_activity_invalidated", {
|
|
12549
|
+
reason: "launch_changed",
|
|
12550
|
+
message_type: msg.type,
|
|
12551
|
+
agentId: msg.agentId,
|
|
12552
|
+
stale_launch_id_present: true,
|
|
12553
|
+
next_launch_id_present: true
|
|
12554
|
+
});
|
|
12555
|
+
return;
|
|
12556
|
+
}
|
|
12557
|
+
this.pendingActivityByAgent.set(msg.agentId, msg);
|
|
12558
|
+
}
|
|
12559
|
+
observeLaunchIdentity(msg) {
|
|
12560
|
+
const identity = this.agentLaunchIdentity(msg);
|
|
12561
|
+
if (!identity?.launchId) return;
|
|
12562
|
+
if (msg.type !== "agent:activity") {
|
|
12563
|
+
this.latestObservedLaunchIdByAgent.set(identity.agentId, identity.launchId);
|
|
12564
|
+
}
|
|
12565
|
+
const pending = this.pendingActivityByAgent.get(identity.agentId);
|
|
12566
|
+
if (!pending || pending.launchId === identity.launchId) return;
|
|
12567
|
+
this.pendingActivityByAgent.delete(identity.agentId);
|
|
12568
|
+
this.trace("daemon.connection.pending_activity_invalidated", {
|
|
12569
|
+
reason: "launch_changed",
|
|
12570
|
+
message_type: msg.type,
|
|
12571
|
+
agentId: identity.agentId,
|
|
12572
|
+
stale_launch_id_present: Boolean(pending.launchId),
|
|
12573
|
+
next_launch_id_present: true
|
|
12574
|
+
});
|
|
12575
|
+
}
|
|
12576
|
+
agentLaunchIdentity(msg) {
|
|
12577
|
+
switch (msg.type) {
|
|
12578
|
+
case "agent:activity":
|
|
12579
|
+
case "agent:status":
|
|
12580
|
+
case "agent:session":
|
|
12581
|
+
case "agent:runtime_profile":
|
|
12582
|
+
case "agent:runtime_profile:migration:ack":
|
|
12583
|
+
case "agent:runtime_profile:migration_done":
|
|
12584
|
+
case "agent:runtime_profile:daemon_release_notice:ack":
|
|
12585
|
+
return { agentId: msg.agentId, launchId: msg.launchId };
|
|
12586
|
+
default:
|
|
12587
|
+
return null;
|
|
12588
|
+
}
|
|
12589
|
+
}
|
|
12590
|
+
flushPendingActivity(ws) {
|
|
12591
|
+
if (this.pendingActivityByAgent.size === 0) return;
|
|
12592
|
+
if (this.ws !== ws || ws.readyState !== WebSocket.OPEN) return;
|
|
12593
|
+
const pending = [...this.pendingActivityByAgent.values()];
|
|
12594
|
+
this.pendingActivityByAgent.clear();
|
|
12595
|
+
for (const msg of pending) {
|
|
12596
|
+
ws.send(JSON.stringify(msg));
|
|
12597
|
+
}
|
|
12598
|
+
this.trace("daemon.connection.outbound_replayed", {
|
|
12599
|
+
message_type: "agent:activity",
|
|
12600
|
+
message_count: pending.length
|
|
12601
|
+
});
|
|
12602
|
+
}
|
|
12026
12603
|
lastInboundAgeBucket() {
|
|
12027
12604
|
return durationMsBucket(this.lastInboundAt == null ? null : this.clock.now() - this.lastInboundAt);
|
|
12028
12605
|
}
|