@animalabs/agent-framework 0.6.7 → 0.6.8
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/src/agent.d.ts +1 -34
- package/dist/src/agent.d.ts.map +1 -1
- package/dist/src/agent.js +55 -137
- package/dist/src/agent.js.map +1 -1
- package/dist/src/framework.d.ts +0 -56
- package/dist/src/framework.d.ts.map +1 -1
- package/dist/src/framework.js +45 -1733
- package/dist/src/framework.js.map +1 -1
- package/dist/src/types/agent.d.ts +0 -12
- package/dist/src/types/agent.d.ts.map +1 -1
- package/dist/test/helpers/mock-membrane.js +1 -1
- package/dist/test/helpers/mock-membrane.js.map +1 -1
- package/dist/test/mcpl-awareness-barrier.test.js +1 -228
- package/dist/test/mcpl-awareness-barrier.test.js.map +1 -1
- package/dist/test/refusal-single-call.test.d.ts +2 -0
- package/dist/test/refusal-single-call.test.d.ts.map +1 -0
- package/dist/test/refusal-single-call.test.js +177 -0
- package/dist/test/refusal-single-call.test.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/src/agent.ts +60 -226
- package/src/framework.ts +72 -2387
- package/src/types/agent.ts +0 -12
- package/dist/src/canonical-json.d.ts +0 -2
- package/dist/src/canonical-json.d.ts.map +0 -1
- package/dist/src/canonical-json.js +0 -23
- package/dist/src/canonical-json.js.map +0 -1
- package/dist/test/primary-summary-refusal-fallback-integration.test.d.ts +0 -2
- package/dist/test/primary-summary-refusal-fallback-integration.test.d.ts.map +0 -1
- package/dist/test/primary-summary-refusal-fallback-integration.test.js +0 -1955
- package/dist/test/primary-summary-refusal-fallback-integration.test.js.map +0 -1
- package/dist/test/primary-summary-refusal-fallback.test.d.ts +0 -2
- package/dist/test/primary-summary-refusal-fallback.test.d.ts.map +0 -1
- package/dist/test/primary-summary-refusal-fallback.test.js +0 -753
- package/dist/test/primary-summary-refusal-fallback.test.js.map +0 -1
- package/src/canonical-json.ts +0 -22
package/dist/src/framework.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { join } from 'node:path';
|
|
2
2
|
import { appendFileSync, mkdirSync } from 'node:fs';
|
|
3
|
-
import { createHash } from 'node:crypto';
|
|
4
3
|
import { JsStore } from '@animalabs/chronicle';
|
|
5
4
|
import { MembraneError } from '@animalabs/membrane';
|
|
6
5
|
import { ContextManager, PassthroughStrategy } from '@animalabs/context-manager';
|
|
@@ -18,7 +17,6 @@ import { ConversationRouter } from './mcpl/conversation-router.js';
|
|
|
18
17
|
import { safeSlice } from './safe-slice.js';
|
|
19
18
|
import { toolResultDataToHistoryString } from './tool-result-history.js';
|
|
20
19
|
import { splitProseSegments } from './prose-segments.js';
|
|
21
|
-
import { canonicalJsonStringify } from './canonical-json.js';
|
|
22
20
|
/**
|
|
23
21
|
* Tools whose presence suppresses auto-routing of the surrounding prose,
|
|
24
22
|
* for two distinct reasons:
|
|
@@ -51,12 +49,6 @@ const FRAMEWORK_STATE_ID = 'framework/state';
|
|
|
51
49
|
const CONVERSATION_ROUTER_STATE_ID = 'framework/conversation-router';
|
|
52
50
|
const INFERENCE_LOG_ID = 'framework/inference-log';
|
|
53
51
|
const PROCESS_LOG_ID = 'framework/process-log';
|
|
54
|
-
const PRIMARY_SUMMARY_FALLBACK_STATE_ID = 'framework/primary-summary-fallback';
|
|
55
|
-
const PRIMARY_SUMMARY_SETTLEMENT_METADATA_KEY = 'primarySummarySettlement';
|
|
56
|
-
const PRIMARY_SUMMARY_SETTLEMENT_VERSION = 1;
|
|
57
|
-
const PRIMARY_SUMMARY_SETTLEMENT_MAX_ID_LENGTH = 1024;
|
|
58
|
-
const PRIMARY_SUMMARY_SETTLEMENT_MAX_NAME_LENGTH = 256;
|
|
59
|
-
const PRIMARY_SUMMARY_SETTLEMENT_MAX_REASON_LENGTH = 256;
|
|
60
52
|
const TURN_CHECKPOINTS_ID = 'framework/turn-checkpoints'; // legacy single-map layout, read-only fallback
|
|
61
53
|
const TURN_CHECKPOINTS_TREE_ID = 'framework/turn-checkpoints/tree';
|
|
62
54
|
/** Maximum number of turn checkpoints to keep per agent. */
|
|
@@ -197,8 +189,6 @@ export class AgentFramework {
|
|
|
197
189
|
processLoggingPersist;
|
|
198
190
|
processLoggingBroadcast;
|
|
199
191
|
activeStreams = new Map();
|
|
200
|
-
activeStreamContexts = new Map();
|
|
201
|
-
primarySummaryStateLock = Promise.resolve();
|
|
202
192
|
/** Per-agent output locus pinned for the CURRENT logical turn (see
|
|
203
193
|
* resolveTurnLocus in driveStream). Lives here — not in driveStream
|
|
204
194
|
* locals — so the pin survives a context-budget stream restart, which
|
|
@@ -424,12 +414,6 @@ export class AgentFramework {
|
|
|
424
414
|
// Already registered
|
|
425
415
|
}
|
|
426
416
|
}
|
|
427
|
-
try {
|
|
428
|
-
store.registerState({ id: PRIMARY_SUMMARY_FALLBACK_STATE_ID, strategy: 'snapshot' });
|
|
429
|
-
}
|
|
430
|
-
catch {
|
|
431
|
-
// Already registered
|
|
432
|
-
}
|
|
433
417
|
const discordAwarenessOutboxPath = config.discordAwarenessOutboxPath
|
|
434
418
|
?? (config.storePath ? defaultDiscordAwarenessOutboxPath(config.storePath) : undefined);
|
|
435
419
|
const discordAwarenessOutbox = discordAwarenessOutboxPath
|
|
@@ -453,13 +437,12 @@ export class AgentFramework {
|
|
|
453
437
|
throw new DiscordAwarenessAccountingError('startup reconciliation', error);
|
|
454
438
|
}
|
|
455
439
|
}
|
|
440
|
+
// Restore persisted usage data (if any) from prior session
|
|
441
|
+
framework.restoreUsageState();
|
|
456
442
|
// Create agents
|
|
457
443
|
for (const agentConfig of config.agents) {
|
|
458
444
|
await framework.createAgent(agentConfig);
|
|
459
445
|
}
|
|
460
|
-
// Restore persisted usage data (if any) from prior session
|
|
461
|
-
framework.restoreUsageState();
|
|
462
|
-
await framework.reconcilePrimarySummaryFallbackStateOnStartup();
|
|
463
446
|
// Finish any branch-local suppression interrupted after Chronicle switched
|
|
464
447
|
// branches. This runs before modules, MCPL connections, or inbound traffic.
|
|
465
448
|
await framework.resumePreparedDiscordSuppressions();
|
|
@@ -680,7 +663,7 @@ export class AgentFramework {
|
|
|
680
663
|
const queued = [...this.agents.values()].flatMap((agent) => {
|
|
681
664
|
const cm = agent.getContextManager();
|
|
682
665
|
const tools = this.getToolsForAgent(agent.name).filter((tool) => agent.canUseTool(tool.name));
|
|
683
|
-
cm.setToolDefinitions
|
|
666
|
+
cm.setToolDefinitions(tools);
|
|
684
667
|
if (cm.isReady())
|
|
685
668
|
return [];
|
|
686
669
|
const pending = cm.getPendingWork()?.description;
|
|
@@ -1251,1091 +1234,6 @@ export class AgentFramework {
|
|
|
1251
1234
|
});
|
|
1252
1235
|
return { promise, resolve, reject };
|
|
1253
1236
|
}
|
|
1254
|
-
hashJson(value) {
|
|
1255
|
-
return createHash('sha256').update(canonicalJsonStringify(value), 'utf8').digest('hex');
|
|
1256
|
-
}
|
|
1257
|
-
estimateStringTokens(text, overhead = 0) {
|
|
1258
|
-
return Math.ceil(Buffer.byteLength(text, 'utf8') / 4) + overhead;
|
|
1259
|
-
}
|
|
1260
|
-
estimateUnknownTokens(value, overhead = 0) {
|
|
1261
|
-
return this.estimateStringTokens(JSON.stringify(value) ?? 'null', overhead);
|
|
1262
|
-
}
|
|
1263
|
-
estimateRequestInputBoundTokens(request) {
|
|
1264
|
-
let total = 96;
|
|
1265
|
-
total += this.estimateStringTokens(request.system ?? '', 48);
|
|
1266
|
-
for (const message of request.messages) {
|
|
1267
|
-
total += 24;
|
|
1268
|
-
total += this.estimateStringTokens(message.participant, 8);
|
|
1269
|
-
if (message.cacheBreakpoint)
|
|
1270
|
-
total += 8;
|
|
1271
|
-
for (const block of message.content) {
|
|
1272
|
-
switch (block.type) {
|
|
1273
|
-
case 'text':
|
|
1274
|
-
total += this.estimateStringTokens(block.text, 24);
|
|
1275
|
-
break;
|
|
1276
|
-
case 'thinking':
|
|
1277
|
-
total += this.estimateStringTokens(block.thinking, 32);
|
|
1278
|
-
total += block.signature ? this.estimateStringTokens(block.signature, 8) : 0;
|
|
1279
|
-
break;
|
|
1280
|
-
case 'redacted_thinking':
|
|
1281
|
-
total += this.estimateUnknownTokens(block, 32);
|
|
1282
|
-
break;
|
|
1283
|
-
default:
|
|
1284
|
-
total += this.estimateUnknownTokens(block, 32);
|
|
1285
|
-
break;
|
|
1286
|
-
}
|
|
1287
|
-
}
|
|
1288
|
-
}
|
|
1289
|
-
if (request.tools && request.tools.length > 0) {
|
|
1290
|
-
total += 32;
|
|
1291
|
-
for (const tool of request.tools) {
|
|
1292
|
-
total += this.estimateUnknownTokens({
|
|
1293
|
-
name: tool.name,
|
|
1294
|
-
description: tool.description,
|
|
1295
|
-
inputSchema: tool.inputSchema,
|
|
1296
|
-
}, 48);
|
|
1297
|
-
}
|
|
1298
|
-
}
|
|
1299
|
-
total += this.estimateUnknownTokens(request.config, 48);
|
|
1300
|
-
if (request.providerParams !== undefined) {
|
|
1301
|
-
total += this.estimateUnknownTokens(request.providerParams, 32);
|
|
1302
|
-
}
|
|
1303
|
-
total += request.promptCaching ? 16 : 0;
|
|
1304
|
-
total += request.cacheTtl ? this.estimateStringTokens(request.cacheTtl, 8) : 0;
|
|
1305
|
-
total += request.assistantParticipant
|
|
1306
|
-
? this.estimateStringTokens(request.assistantParticipant, 8)
|
|
1307
|
-
: 0;
|
|
1308
|
-
return total;
|
|
1309
|
-
}
|
|
1310
|
-
estimateRequestCompleteBoundTokens(request) {
|
|
1311
|
-
const reserve = typeof request.config.maxTokens === 'number' && Number.isFinite(request.config.maxTokens)
|
|
1312
|
-
? request.config.maxTokens
|
|
1313
|
-
: 0;
|
|
1314
|
-
return this.estimateRequestInputBoundTokens(request) + reserve + 96;
|
|
1315
|
-
}
|
|
1316
|
-
primarySummaryAdmissionBoundTokens(originalRequestInputTokens, expandedRequest, originalProviderInputTokens) {
|
|
1317
|
-
const expandedInput = this.estimateRequestInputBoundTokens(expandedRequest);
|
|
1318
|
-
const expandedComplete = this.estimateRequestCompleteBoundTokens(expandedRequest);
|
|
1319
|
-
if (originalProviderInputTokens === undefined)
|
|
1320
|
-
return expandedComplete;
|
|
1321
|
-
const reserve = typeof expandedRequest.config.maxTokens === 'number' && Number.isFinite(expandedRequest.config.maxTokens)
|
|
1322
|
-
? expandedRequest.config.maxTokens
|
|
1323
|
-
: 0;
|
|
1324
|
-
const positiveDelta = Math.max(0, expandedInput - originalRequestInputTokens);
|
|
1325
|
-
return Math.max(expandedComplete, originalProviderInputTokens + positiveDelta + reserve + 96);
|
|
1326
|
-
}
|
|
1327
|
-
readPrimarySummaryFallbackState() {
|
|
1328
|
-
const raw = this.store
|
|
1329
|
-
?.getStateJson?.(PRIMARY_SUMMARY_FALLBACK_STATE_ID);
|
|
1330
|
-
if (!raw || typeof raw !== 'object')
|
|
1331
|
-
return { requests: [] };
|
|
1332
|
-
const requests = Array.isArray(raw.requests)
|
|
1333
|
-
? (raw.requests)
|
|
1334
|
-
: [];
|
|
1335
|
-
return { requests };
|
|
1336
|
-
}
|
|
1337
|
-
writePrimarySummaryFallbackState(state) {
|
|
1338
|
-
this.store.setStateJson(PRIMARY_SUMMARY_FALLBACK_STATE_ID, state);
|
|
1339
|
-
}
|
|
1340
|
-
async withPrimarySummaryStateLock(work) {
|
|
1341
|
-
const previous = this.primarySummaryStateLock;
|
|
1342
|
-
let release;
|
|
1343
|
-
this.primarySummaryStateLock = new Promise((resolve) => { release = resolve; });
|
|
1344
|
-
await previous;
|
|
1345
|
-
try {
|
|
1346
|
-
return await work();
|
|
1347
|
-
}
|
|
1348
|
-
finally {
|
|
1349
|
-
release();
|
|
1350
|
-
}
|
|
1351
|
-
}
|
|
1352
|
-
async reconcilePrimarySummaryFallbackStateOnStartup() {
|
|
1353
|
-
const alerts = [];
|
|
1354
|
-
const unsettledRequests = (await this.withPrimarySummaryStateLock(() => this.readPrimarySummaryFallbackState().requests
|
|
1355
|
-
.filter((record) => record.finalStatus === 'pending'
|
|
1356
|
-
|| record.finalStatus === 'refusal'
|
|
1357
|
-
|| record.fallbackStatus === 'pending')
|
|
1358
|
-
.map((record) => structuredClone(record))));
|
|
1359
|
-
const resolvedRequestIds = new Set();
|
|
1360
|
-
for (const record of unsettledRequests) {
|
|
1361
|
-
const result = await this.finalizePrimarySummarySettlementOnStartup(record);
|
|
1362
|
-
if (result === 'finalized') {
|
|
1363
|
-
resolvedRequestIds.add(record.requestId);
|
|
1364
|
-
continue;
|
|
1365
|
-
}
|
|
1366
|
-
if (result && typeof result === 'object' && 'invalidReason' in result) {
|
|
1367
|
-
resolvedRequestIds.add(record.requestId);
|
|
1368
|
-
alerts.push({ agentName: record.agentName, requestId: record.requestId, reason: result.invalidReason });
|
|
1369
|
-
await this.markPrimarySummaryRequestRestartFailure(record, result.invalidReason);
|
|
1370
|
-
}
|
|
1371
|
-
}
|
|
1372
|
-
await this.withPrimarySummaryStateLock(() => {
|
|
1373
|
-
const state = this.readPrimarySummaryFallbackState();
|
|
1374
|
-
let changed = false;
|
|
1375
|
-
state.requests = state.requests.map((record) => {
|
|
1376
|
-
if (resolvedRequestIds.has(record.requestId))
|
|
1377
|
-
return record;
|
|
1378
|
-
if (record.dispatchKind === 'primary_summary_fallback_retry' && (record.finalStatus === 'pending' || record.finalStatus === 'refusal')) {
|
|
1379
|
-
changed = true;
|
|
1380
|
-
const reason = 'primary_summary_fallback_unresolved_on_restart';
|
|
1381
|
-
alerts.push({ agentName: record.agentName, requestId: record.requestId, reason });
|
|
1382
|
-
return {
|
|
1383
|
-
...record,
|
|
1384
|
-
finalStatus: 'held',
|
|
1385
|
-
fallbackHeldReason: reason,
|
|
1386
|
-
};
|
|
1387
|
-
}
|
|
1388
|
-
if (record.fallbackIntent && record.fallbackStatus === 'pending') {
|
|
1389
|
-
changed = true;
|
|
1390
|
-
const reason = 'primary_summary_fallback_unresolved_on_restart';
|
|
1391
|
-
alerts.push({ agentName: record.agentName, requestId: record.requestId, reason });
|
|
1392
|
-
return {
|
|
1393
|
-
...record,
|
|
1394
|
-
fallbackStatus: 'held',
|
|
1395
|
-
fallbackHeldReason: reason,
|
|
1396
|
-
};
|
|
1397
|
-
}
|
|
1398
|
-
if (record.finalStatus === 'refusal') {
|
|
1399
|
-
changed = true;
|
|
1400
|
-
const reason = 'primary_summary_request_unresolved_on_restart';
|
|
1401
|
-
alerts.push({ agentName: record.agentName, requestId: record.requestId, reason });
|
|
1402
|
-
return {
|
|
1403
|
-
...record,
|
|
1404
|
-
finalStatus: 'held',
|
|
1405
|
-
fallbackStatus: 'held',
|
|
1406
|
-
fallbackHeldReason: reason,
|
|
1407
|
-
};
|
|
1408
|
-
}
|
|
1409
|
-
if (record.finalStatus === 'pending') {
|
|
1410
|
-
changed = true;
|
|
1411
|
-
const reason = 'primary_summary_request_unresolved_on_restart';
|
|
1412
|
-
alerts.push({ agentName: record.agentName, requestId: record.requestId, reason });
|
|
1413
|
-
return {
|
|
1414
|
-
...record,
|
|
1415
|
-
finalStatus: 'timeout_unknown',
|
|
1416
|
-
fallbackHeldReason: reason,
|
|
1417
|
-
};
|
|
1418
|
-
}
|
|
1419
|
-
return record;
|
|
1420
|
-
});
|
|
1421
|
-
if (changed)
|
|
1422
|
-
this.writePrimarySummaryFallbackState({ requests: state.requests.slice(-256) });
|
|
1423
|
-
});
|
|
1424
|
-
for (const alert of alerts) {
|
|
1425
|
-
this.opsAlert('refusal-held', alert.agentName, alert.reason, {
|
|
1426
|
-
data: { requestId: alert.requestId },
|
|
1427
|
-
});
|
|
1428
|
-
}
|
|
1429
|
-
}
|
|
1430
|
-
primarySummaryIdentityKey(identity) {
|
|
1431
|
-
return [
|
|
1432
|
-
identity.id,
|
|
1433
|
-
identity.contentHash,
|
|
1434
|
-
identity.carrierHash,
|
|
1435
|
-
identity.sourceLeafHash,
|
|
1436
|
-
].join('\u0000');
|
|
1437
|
-
}
|
|
1438
|
-
primarySummaryProjectionKeys(projection) {
|
|
1439
|
-
return projection?.selectedSummaries.map((selection) => this.primarySummaryIdentityKey(selection.identity)) ?? [];
|
|
1440
|
-
}
|
|
1441
|
-
primarySummaryFallbackConfig(agent) {
|
|
1442
|
-
const raw = agent.refusalHandling?.primarySummaryFallback;
|
|
1443
|
-
const fallbackBudgetBase = agent.contextBudgetTokens ?? 100_000;
|
|
1444
|
-
return {
|
|
1445
|
-
enabled: raw?.enabled === true,
|
|
1446
|
-
maxNewSummaries: typeof raw?.maxNewSummaries === 'number' && Number.isSafeInteger(raw.maxNewSummaries)
|
|
1447
|
-
? Math.max(0, raw.maxNewSummaries)
|
|
1448
|
-
: 4,
|
|
1449
|
-
requestBudgetTokens: typeof raw?.requestBudgetTokens === 'number' && Number.isFinite(raw.requestBudgetTokens) && raw.requestBudgetTokens > 0
|
|
1450
|
-
? raw.requestBudgetTokens
|
|
1451
|
-
: fallbackBudgetBase + agent.maxTokens,
|
|
1452
|
-
};
|
|
1453
|
-
}
|
|
1454
|
-
latestCompatibleHealthyPrimaryRequest(current, state) {
|
|
1455
|
-
for (let i = state.requests.length - 1; i >= 0; i--) {
|
|
1456
|
-
const candidate = state.requests[i];
|
|
1457
|
-
if (candidate.requestId === current.requestId)
|
|
1458
|
-
continue;
|
|
1459
|
-
if (candidate.agentName !== current.agentName)
|
|
1460
|
-
continue;
|
|
1461
|
-
if (candidate.namespace !== current.namespace)
|
|
1462
|
-
continue;
|
|
1463
|
-
if (candidate.branch.id !== current.branch.id)
|
|
1464
|
-
continue;
|
|
1465
|
-
if (candidate.branch.generation !== current.branch.generation)
|
|
1466
|
-
continue;
|
|
1467
|
-
if (candidate.systemHash !== current.systemHash)
|
|
1468
|
-
continue;
|
|
1469
|
-
if (candidate.modelConfigHash !== current.modelConfigHash)
|
|
1470
|
-
continue;
|
|
1471
|
-
if (candidate.toolContractHash !== current.toolContractHash)
|
|
1472
|
-
continue;
|
|
1473
|
-
if (candidate.dispatchKind !== 'primary')
|
|
1474
|
-
continue;
|
|
1475
|
-
if (candidate.fallbackIntent || candidate.fallbackStatus)
|
|
1476
|
-
continue;
|
|
1477
|
-
if (candidate.finalStatus !== 'success')
|
|
1478
|
-
continue;
|
|
1479
|
-
return candidate;
|
|
1480
|
-
}
|
|
1481
|
-
return null;
|
|
1482
|
-
}
|
|
1483
|
-
async persistPrimarySummaryRequestRecord(record) {
|
|
1484
|
-
await this.withPrimarySummaryStateLock(() => {
|
|
1485
|
-
const state = this.readPrimarySummaryFallbackState();
|
|
1486
|
-
const requests = state.requests.filter((entry) => entry.requestId !== record.requestId);
|
|
1487
|
-
requests.push(record);
|
|
1488
|
-
this.writePrimarySummaryFallbackState({ requests: requests.slice(-256) });
|
|
1489
|
-
});
|
|
1490
|
-
}
|
|
1491
|
-
async updatePrimarySummaryRequestRecord(requestId, update) {
|
|
1492
|
-
return this.withPrimarySummaryStateLock(() => {
|
|
1493
|
-
const state = this.readPrimarySummaryFallbackState();
|
|
1494
|
-
const index = state.requests.findIndex((entry) => entry.requestId === requestId);
|
|
1495
|
-
if (index < 0)
|
|
1496
|
-
return null;
|
|
1497
|
-
state.requests[index] = update(state.requests[index]);
|
|
1498
|
-
this.writePrimarySummaryFallbackState({ requests: state.requests.slice(-256) });
|
|
1499
|
-
return state.requests[index];
|
|
1500
|
-
});
|
|
1501
|
-
}
|
|
1502
|
-
async getPrimarySummaryRequestRecord(requestId) {
|
|
1503
|
-
return this.withPrimarySummaryStateLock(() => {
|
|
1504
|
-
const state = this.readPrimarySummaryFallbackState();
|
|
1505
|
-
return state.requests.find((entry) => entry.requestId === requestId) ?? null;
|
|
1506
|
-
});
|
|
1507
|
-
}
|
|
1508
|
-
primarySummaryContractsMatch(left, right) {
|
|
1509
|
-
return left.systemHash === right.systemHash
|
|
1510
|
-
&& left.modelConfigHash === right.modelConfigHash
|
|
1511
|
-
&& left.toolContractHash === right.toolContractHash;
|
|
1512
|
-
}
|
|
1513
|
-
primarySummaryFallbackOutputPhaseHash(blocks) {
|
|
1514
|
-
return this.hashJson(blocks);
|
|
1515
|
-
}
|
|
1516
|
-
primarySummaryFallbackBlockTypes(blocks) {
|
|
1517
|
-
return blocks.map((block) => block.type);
|
|
1518
|
-
}
|
|
1519
|
-
static NON_EXECUTED_TOOL_RESULT_TEXT = '[tool not executed]';
|
|
1520
|
-
buildNonExecutedToolResults(blocks) {
|
|
1521
|
-
const seenToolUseIds = new Set();
|
|
1522
|
-
return blocks
|
|
1523
|
-
.filter((block) => block.type === 'tool_use')
|
|
1524
|
-
.filter((block) => {
|
|
1525
|
-
if (seenToolUseIds.has(block.id))
|
|
1526
|
-
return false;
|
|
1527
|
-
seenToolUseIds.add(block.id);
|
|
1528
|
-
return true;
|
|
1529
|
-
})
|
|
1530
|
-
.map((block) => ({
|
|
1531
|
-
type: 'tool_result',
|
|
1532
|
-
toolUseId: block.id,
|
|
1533
|
-
content: AgentFramework.NON_EXECUTED_TOOL_RESULT_TEXT,
|
|
1534
|
-
isError: true,
|
|
1535
|
-
}));
|
|
1536
|
-
}
|
|
1537
|
-
buildPrimarySummarySettlementId(record, outcome) {
|
|
1538
|
-
return `${record.dispatchKind}:${outcome}:terminal:v${PRIMARY_SUMMARY_SETTLEMENT_VERSION}`;
|
|
1539
|
-
}
|
|
1540
|
-
buildPrimarySummarySettlementMetadata(record, options) {
|
|
1541
|
-
const settlementId = options.settlementId ?? this.buildPrimarySummarySettlementId(record, options.outcome);
|
|
1542
|
-
return {
|
|
1543
|
-
[PRIMARY_SUMMARY_SETTLEMENT_METADATA_KEY]: {
|
|
1544
|
-
version: PRIMARY_SUMMARY_SETTLEMENT_VERSION,
|
|
1545
|
-
requestId: record.requestId,
|
|
1546
|
-
settlementId,
|
|
1547
|
-
agentName: record.agentName,
|
|
1548
|
-
dispatchKind: record.dispatchKind,
|
|
1549
|
-
outcome: options.outcome,
|
|
1550
|
-
...(options.stopReason ? { stopReason: options.stopReason } : {}),
|
|
1551
|
-
...(options.holdReason ? { holdReason: options.holdReason } : {}),
|
|
1552
|
-
...(options.providerInputTokens !== undefined
|
|
1553
|
-
? { providerInputTokens: options.providerInputTokens }
|
|
1554
|
-
: {}),
|
|
1555
|
-
visibleAssistantOutput: options.visibleAssistantOutput,
|
|
1556
|
-
executedToolCalls: options.executedToolCalls,
|
|
1557
|
-
branch: {
|
|
1558
|
-
id: record.branch.id,
|
|
1559
|
-
name: record.branch.name,
|
|
1560
|
-
generation: record.branch.generation,
|
|
1561
|
-
},
|
|
1562
|
-
role: options.role,
|
|
1563
|
-
kind: options.kind,
|
|
1564
|
-
entryIndex: options.entryIndex,
|
|
1565
|
-
entryCount: options.entryCount,
|
|
1566
|
-
},
|
|
1567
|
-
};
|
|
1568
|
-
}
|
|
1569
|
-
isBoundedNonEmptyString(value, maxLength) {
|
|
1570
|
-
return typeof value === 'string' && value.length > 0 && value.length <= maxLength;
|
|
1571
|
-
}
|
|
1572
|
-
isSafeNonNegativeInteger(value) {
|
|
1573
|
-
return typeof value === 'number' && Number.isSafeInteger(value) && value >= 0;
|
|
1574
|
-
}
|
|
1575
|
-
primarySummarySettlementRequestId(message) {
|
|
1576
|
-
const raw = message.metadata?.[PRIMARY_SUMMARY_SETTLEMENT_METADATA_KEY];
|
|
1577
|
-
if (!raw || typeof raw !== 'object')
|
|
1578
|
-
return null;
|
|
1579
|
-
const requestId = raw.requestId;
|
|
1580
|
-
return typeof requestId === 'string' ? requestId : null;
|
|
1581
|
-
}
|
|
1582
|
-
parsePrimarySummarySettlementMetadata(message) {
|
|
1583
|
-
const raw = message.metadata?.[PRIMARY_SUMMARY_SETTLEMENT_METADATA_KEY];
|
|
1584
|
-
if (!raw || typeof raw !== 'object')
|
|
1585
|
-
return null;
|
|
1586
|
-
const candidate = raw;
|
|
1587
|
-
if (candidate.version !== PRIMARY_SUMMARY_SETTLEMENT_VERSION ||
|
|
1588
|
-
!this.isBoundedNonEmptyString(candidate.requestId, PRIMARY_SUMMARY_SETTLEMENT_MAX_ID_LENGTH) ||
|
|
1589
|
-
!this.isBoundedNonEmptyString(candidate.settlementId, PRIMARY_SUMMARY_SETTLEMENT_MAX_ID_LENGTH) ||
|
|
1590
|
-
!this.isBoundedNonEmptyString(candidate.agentName, PRIMARY_SUMMARY_SETTLEMENT_MAX_NAME_LENGTH) ||
|
|
1591
|
-
(candidate.dispatchKind !== 'primary' && candidate.dispatchKind !== 'primary_summary_fallback_retry') ||
|
|
1592
|
-
(candidate.outcome !== 'success' && candidate.outcome !== 'held') ||
|
|
1593
|
-
typeof candidate.visibleAssistantOutput !== 'boolean' ||
|
|
1594
|
-
!this.isSafeNonNegativeInteger(candidate.executedToolCalls) ||
|
|
1595
|
-
!this.isSafeNonNegativeInteger(candidate.entryIndex) ||
|
|
1596
|
-
!this.isSafeNonNegativeInteger(candidate.entryCount) ||
|
|
1597
|
-
(candidate.role !== 'assistant' && candidate.role !== 'user') ||
|
|
1598
|
-
(candidate.kind !== 'assistant_output' && candidate.kind !== 'generated_tool_result') ||
|
|
1599
|
-
!candidate.branch ||
|
|
1600
|
-
!this.isBoundedNonEmptyString(candidate.branch.id, PRIMARY_SUMMARY_SETTLEMENT_MAX_ID_LENGTH) ||
|
|
1601
|
-
!this.isBoundedNonEmptyString(candidate.branch.name, PRIMARY_SUMMARY_SETTLEMENT_MAX_NAME_LENGTH) ||
|
|
1602
|
-
!this.isSafeNonNegativeInteger(candidate.branch.generation) ||
|
|
1603
|
-
candidate.entryCount < 1 ||
|
|
1604
|
-
candidate.entryIndex >= candidate.entryCount ||
|
|
1605
|
-
(candidate.stopReason !== undefined
|
|
1606
|
-
&& !this.isBoundedNonEmptyString(candidate.stopReason, PRIMARY_SUMMARY_SETTLEMENT_MAX_REASON_LENGTH)) ||
|
|
1607
|
-
(candidate.holdReason !== undefined
|
|
1608
|
-
&& !this.isBoundedNonEmptyString(candidate.holdReason, PRIMARY_SUMMARY_SETTLEMENT_MAX_REASON_LENGTH)) ||
|
|
1609
|
-
(candidate.providerInputTokens !== undefined
|
|
1610
|
-
&& !this.isSafeNonNegativeInteger(candidate.providerInputTokens)) ||
|
|
1611
|
-
(candidate.outcome === 'held' && candidate.holdReason === undefined) ||
|
|
1612
|
-
(candidate.outcome === 'success' && candidate.holdReason !== undefined)) {
|
|
1613
|
-
return { invalidReason: 'primary_summary_settlement_metadata_invalid_on_restart' };
|
|
1614
|
-
}
|
|
1615
|
-
return candidate;
|
|
1616
|
-
}
|
|
1617
|
-
primarySummarySettlementMetadataConsistent(left, right) {
|
|
1618
|
-
return left.version === right.version
|
|
1619
|
-
&& left.requestId === right.requestId
|
|
1620
|
-
&& left.settlementId === right.settlementId
|
|
1621
|
-
&& left.agentName === right.agentName
|
|
1622
|
-
&& left.dispatchKind === right.dispatchKind
|
|
1623
|
-
&& left.outcome === right.outcome
|
|
1624
|
-
&& left.stopReason === right.stopReason
|
|
1625
|
-
&& left.holdReason === right.holdReason
|
|
1626
|
-
&& left.providerInputTokens === right.providerInputTokens
|
|
1627
|
-
&& left.visibleAssistantOutput === right.visibleAssistantOutput
|
|
1628
|
-
&& left.executedToolCalls === right.executedToolCalls
|
|
1629
|
-
&& left.branch.id === right.branch.id
|
|
1630
|
-
&& left.branch.name === right.branch.name
|
|
1631
|
-
&& left.branch.generation === right.branch.generation
|
|
1632
|
-
&& left.entryCount === right.entryCount;
|
|
1633
|
-
}
|
|
1634
|
-
primarySummaryShardEnvelopeConsistent(participant, left, candidateParticipant, right) {
|
|
1635
|
-
return participant === candidateParticipant
|
|
1636
|
-
&& this.primarySummarySettlementMetadataConsistent(left, right)
|
|
1637
|
-
&& left.role === right.role
|
|
1638
|
-
&& left.kind === right.kind
|
|
1639
|
-
&& left.entryIndex === right.entryIndex;
|
|
1640
|
-
}
|
|
1641
|
-
primarySummaryLogicalSettlementEntries(agent, requestId) {
|
|
1642
|
-
const allMessages = agent.getContextManager().getAllMessages()
|
|
1643
|
-
.slice()
|
|
1644
|
-
.sort((left, right) => left.sequence - right.sequence);
|
|
1645
|
-
const entries = [];
|
|
1646
|
-
for (let index = 0; index < allMessages.length;) {
|
|
1647
|
-
const message = allMessages[index];
|
|
1648
|
-
if (this.primarySummarySettlementRequestId(message) !== requestId) {
|
|
1649
|
-
index++;
|
|
1650
|
-
continue;
|
|
1651
|
-
}
|
|
1652
|
-
const parsed = this.parsePrimarySummarySettlementMetadata(message);
|
|
1653
|
-
if (!parsed) {
|
|
1654
|
-
index++;
|
|
1655
|
-
continue;
|
|
1656
|
-
}
|
|
1657
|
-
if ('invalidReason' in parsed)
|
|
1658
|
-
return parsed;
|
|
1659
|
-
if (message.bodyGroupId === undefined) {
|
|
1660
|
-
if (message.shardIndex !== undefined) {
|
|
1661
|
-
return { invalidReason: 'primary_summary_settlement_shard_group_invalid_on_restart' };
|
|
1662
|
-
}
|
|
1663
|
-
entries.push({
|
|
1664
|
-
messages: [message],
|
|
1665
|
-
settlement: parsed,
|
|
1666
|
-
participant: message.participant,
|
|
1667
|
-
content: message.content,
|
|
1668
|
-
});
|
|
1669
|
-
index++;
|
|
1670
|
-
continue;
|
|
1671
|
-
}
|
|
1672
|
-
const groupId = message.bodyGroupId;
|
|
1673
|
-
if (!this.isBoundedNonEmptyString(groupId, PRIMARY_SUMMARY_SETTLEMENT_MAX_ID_LENGTH)) {
|
|
1674
|
-
return { invalidReason: 'primary_summary_settlement_shard_group_invalid_on_restart' };
|
|
1675
|
-
}
|
|
1676
|
-
if (index > 0 && allMessages[index - 1]?.bodyGroupId === groupId) {
|
|
1677
|
-
return { invalidReason: 'primary_summary_settlement_shard_group_invalid_on_restart' };
|
|
1678
|
-
}
|
|
1679
|
-
const groupMessages = [];
|
|
1680
|
-
while (index < allMessages.length && allMessages[index].bodyGroupId === groupId) {
|
|
1681
|
-
groupMessages.push(allMessages[index]);
|
|
1682
|
-
index++;
|
|
1683
|
-
}
|
|
1684
|
-
if (allMessages.slice(index).some((candidate) => candidate.bodyGroupId === groupId)) {
|
|
1685
|
-
return { invalidReason: 'primary_summary_settlement_shard_group_invalid_on_restart' };
|
|
1686
|
-
}
|
|
1687
|
-
const orderedGroupMessages = [];
|
|
1688
|
-
let expectedShardIndex = 0;
|
|
1689
|
-
for (const groupMessage of groupMessages) {
|
|
1690
|
-
if (this.primarySummarySettlementRequestId(groupMessage) !== requestId) {
|
|
1691
|
-
return { invalidReason: 'primary_summary_settlement_shard_group_invalid_on_restart' };
|
|
1692
|
-
}
|
|
1693
|
-
const groupParsed = this.parsePrimarySummarySettlementMetadata(groupMessage);
|
|
1694
|
-
if (!groupParsed) {
|
|
1695
|
-
return { invalidReason: 'primary_summary_settlement_shard_group_invalid_on_restart' };
|
|
1696
|
-
}
|
|
1697
|
-
if ('invalidReason' in groupParsed)
|
|
1698
|
-
return groupParsed;
|
|
1699
|
-
if (!this.primarySummaryShardEnvelopeConsistent(message.participant, parsed, groupMessage.participant, groupParsed)) {
|
|
1700
|
-
return { invalidReason: 'primary_summary_settlement_shard_group_invalid_on_restart' };
|
|
1701
|
-
}
|
|
1702
|
-
if (groupMessage.shardIndex !== expectedShardIndex) {
|
|
1703
|
-
return { invalidReason: 'primary_summary_settlement_shard_group_invalid_on_restart' };
|
|
1704
|
-
}
|
|
1705
|
-
orderedGroupMessages.push(groupMessage);
|
|
1706
|
-
expectedShardIndex++;
|
|
1707
|
-
}
|
|
1708
|
-
entries.push({
|
|
1709
|
-
messages: orderedGroupMessages,
|
|
1710
|
-
settlement: parsed,
|
|
1711
|
-
participant: message.participant,
|
|
1712
|
-
content: orderedGroupMessages.flatMap((groupMessage) => groupMessage.content),
|
|
1713
|
-
bodyGroupId: groupId,
|
|
1714
|
-
});
|
|
1715
|
-
}
|
|
1716
|
-
return entries;
|
|
1717
|
-
}
|
|
1718
|
-
primarySummaryContentMatches(left, right) {
|
|
1719
|
-
return this.primarySummaryFallbackOutputPhaseHash(left) === this.primarySummaryFallbackOutputPhaseHash(right);
|
|
1720
|
-
}
|
|
1721
|
-
async persistPrimarySummaryChronicleEntries(agent, entries) {
|
|
1722
|
-
const messageIds = [];
|
|
1723
|
-
for (const entry of entries) {
|
|
1724
|
-
if (entry.content.length === 0)
|
|
1725
|
-
continue;
|
|
1726
|
-
messageIds.push(agent.getContextManager().addMessage(entry.participant, entry.content, entry.metadata));
|
|
1727
|
-
}
|
|
1728
|
-
return messageIds;
|
|
1729
|
-
}
|
|
1730
|
-
async markPrimarySummaryRequestRestartFailure(record, reason) {
|
|
1731
|
-
if (record.dispatchKind === 'primary_summary_fallback_retry') {
|
|
1732
|
-
const parent = await this.findPrimarySummaryRetryParent(record.requestId);
|
|
1733
|
-
await this.updatePrimarySummaryRequestRecord(record.requestId, (entry) => ({
|
|
1734
|
-
...entry,
|
|
1735
|
-
finalStatus: 'held',
|
|
1736
|
-
fallbackHeldReason: reason,
|
|
1737
|
-
}));
|
|
1738
|
-
if (parent) {
|
|
1739
|
-
await this.updatePrimarySummaryRequestRecord(parent.originalRequestId, (entry) => ({
|
|
1740
|
-
...entry,
|
|
1741
|
-
finalStatus: 'held',
|
|
1742
|
-
fallbackStatus: 'held',
|
|
1743
|
-
fallbackHeldReason: reason,
|
|
1744
|
-
}));
|
|
1745
|
-
}
|
|
1746
|
-
return;
|
|
1747
|
-
}
|
|
1748
|
-
await this.updatePrimarySummaryRequestRecord(record.requestId, (current) => ({
|
|
1749
|
-
...current,
|
|
1750
|
-
finalStatus: 'held',
|
|
1751
|
-
fallbackStatus: 'held',
|
|
1752
|
-
fallbackHeldReason: reason,
|
|
1753
|
-
}));
|
|
1754
|
-
}
|
|
1755
|
-
async persistPrimarySummaryHeldOutput(agent, requestId, holdReason, options) {
|
|
1756
|
-
const record = await this.getPrimarySummaryRequestRecord(requestId);
|
|
1757
|
-
if (!record) {
|
|
1758
|
-
throw new Error(`Primary summary request '${requestId}' disappeared before held-output persistence`);
|
|
1759
|
-
}
|
|
1760
|
-
const placeholderBlocks = this.buildNonExecutedToolResults(options.assistantBlocks);
|
|
1761
|
-
const entryCount = placeholderBlocks.length > 0 ? 2 : 1;
|
|
1762
|
-
const assistantMetadata = this.buildPrimarySummarySettlementMetadata(record, {
|
|
1763
|
-
outcome: 'held',
|
|
1764
|
-
stopReason: options.stopReason,
|
|
1765
|
-
holdReason,
|
|
1766
|
-
providerInputTokens: options.providerInputTokens,
|
|
1767
|
-
visibleAssistantOutput: options.visibleAssistantOutput,
|
|
1768
|
-
executedToolCalls: options.executedToolCalls,
|
|
1769
|
-
entryIndex: 0,
|
|
1770
|
-
entryCount,
|
|
1771
|
-
role: 'assistant',
|
|
1772
|
-
kind: 'assistant_output',
|
|
1773
|
-
});
|
|
1774
|
-
const settlementId = assistantMetadata[PRIMARY_SUMMARY_SETTLEMENT_METADATA_KEY].settlementId;
|
|
1775
|
-
const placeholderMetadata = placeholderBlocks.length > 0
|
|
1776
|
-
? this.buildPrimarySummarySettlementMetadata(record, {
|
|
1777
|
-
outcome: 'held',
|
|
1778
|
-
settlementId,
|
|
1779
|
-
stopReason: options.stopReason,
|
|
1780
|
-
holdReason,
|
|
1781
|
-
providerInputTokens: options.providerInputTokens,
|
|
1782
|
-
visibleAssistantOutput: options.visibleAssistantOutput,
|
|
1783
|
-
executedToolCalls: options.executedToolCalls,
|
|
1784
|
-
entryIndex: 1,
|
|
1785
|
-
entryCount,
|
|
1786
|
-
role: 'user',
|
|
1787
|
-
kind: 'generated_tool_result',
|
|
1788
|
-
})
|
|
1789
|
-
: undefined;
|
|
1790
|
-
const messageIds = await this.persistPrimarySummaryChronicleEntries(agent, [
|
|
1791
|
-
{ participant: agent.name, content: options.assistantBlocks, metadata: assistantMetadata },
|
|
1792
|
-
...(placeholderBlocks.length > 0 && placeholderMetadata
|
|
1793
|
-
? [{ participant: 'user', content: placeholderBlocks, metadata: placeholderMetadata }]
|
|
1794
|
-
: []),
|
|
1795
|
-
]);
|
|
1796
|
-
if (options.retryDispatch && options.retryPhase && options.assistantBlocks.length > 0) {
|
|
1797
|
-
await this.recordPrimarySummaryRetryOutput(options.retryDispatch.originalRequestId, options.retryDispatch.intentId, {
|
|
1798
|
-
phase: options.retryPhase,
|
|
1799
|
-
contentHash: this.primarySummaryFallbackOutputPhaseHash(options.assistantBlocks),
|
|
1800
|
-
messageId: messageIds[0] ?? '',
|
|
1801
|
-
blockTypes: this.primarySummaryFallbackBlockTypes(options.assistantBlocks),
|
|
1802
|
-
persistedAt: Date.now(),
|
|
1803
|
-
});
|
|
1804
|
-
}
|
|
1805
|
-
if (options.retryDispatch) {
|
|
1806
|
-
await this.holdPrimarySummaryRetryFamily(agent, requestId, options.retryDispatch, holdReason, options.holdData);
|
|
1807
|
-
return;
|
|
1808
|
-
}
|
|
1809
|
-
await this.holdPrimarySummaryRequest(agent, requestId, holdReason, options.holdData);
|
|
1810
|
-
}
|
|
1811
|
-
async findPrimarySummaryRetryParent(retryRequestId) {
|
|
1812
|
-
return this.withPrimarySummaryStateLock(() => {
|
|
1813
|
-
const state = this.readPrimarySummaryFallbackState();
|
|
1814
|
-
for (const record of state.requests) {
|
|
1815
|
-
const intent = record.fallbackIntent;
|
|
1816
|
-
if (intent?.retryRequestId === retryRequestId) {
|
|
1817
|
-
return { originalRequestId: record.requestId, intentId: intent.intentId };
|
|
1818
|
-
}
|
|
1819
|
-
}
|
|
1820
|
-
return null;
|
|
1821
|
-
});
|
|
1822
|
-
}
|
|
1823
|
-
async finalizePrimarySummarySettlementOnStartup(record) {
|
|
1824
|
-
const agent = this.agents.get(record.agentName);
|
|
1825
|
-
if (!agent)
|
|
1826
|
-
return false;
|
|
1827
|
-
const settlementEntries = this.primarySummaryLogicalSettlementEntries(agent, record.requestId);
|
|
1828
|
-
if ('invalidReason' in settlementEntries)
|
|
1829
|
-
return settlementEntries;
|
|
1830
|
-
if (settlementEntries.length === 0) {
|
|
1831
|
-
return false;
|
|
1832
|
-
}
|
|
1833
|
-
const currentBranch = agent.getCurrentBranchGeneration();
|
|
1834
|
-
const assistantEntry = settlementEntries[0];
|
|
1835
|
-
const settlement = assistantEntry.settlement;
|
|
1836
|
-
if (assistantEntry.participant !== record.agentName ||
|
|
1837
|
-
settlement.agentName !== record.agentName ||
|
|
1838
|
-
settlement.dispatchKind !== record.dispatchKind ||
|
|
1839
|
-
settlement.entryIndex !== 0 ||
|
|
1840
|
-
settlement.role !== 'assistant' ||
|
|
1841
|
-
settlement.kind !== 'assistant_output') {
|
|
1842
|
-
return { invalidReason: 'primary_summary_settlement_invalid_assistant_marker_on_restart' };
|
|
1843
|
-
}
|
|
1844
|
-
if (!currentBranch) {
|
|
1845
|
-
return { invalidReason: 'primary_summary_settlement_branch_unknown_on_restart' };
|
|
1846
|
-
}
|
|
1847
|
-
if (settlement.branch.id !== record.branch.id ||
|
|
1848
|
-
settlement.branch.name !== record.branch.name ||
|
|
1849
|
-
settlement.branch.generation !== record.branch.generation ||
|
|
1850
|
-
settlement.branch.id !== currentBranch.id ||
|
|
1851
|
-
settlement.branch.name !== currentBranch.name ||
|
|
1852
|
-
settlement.branch.generation !== currentBranch.generation) {
|
|
1853
|
-
return { invalidReason: 'primary_summary_settlement_branch_mismatch_on_restart' };
|
|
1854
|
-
}
|
|
1855
|
-
if (settlement.entryCount < 1 || settlement.entryCount > 2) {
|
|
1856
|
-
return { invalidReason: 'primary_summary_settlement_entry_count_invalid_on_restart' };
|
|
1857
|
-
}
|
|
1858
|
-
const entriesByIndex = new Map();
|
|
1859
|
-
for (const entry of settlementEntries) {
|
|
1860
|
-
if (!this.primarySummarySettlementMetadataConsistent(settlement, entry.settlement)) {
|
|
1861
|
-
return { invalidReason: 'primary_summary_settlement_marker_conflict_on_restart' };
|
|
1862
|
-
}
|
|
1863
|
-
if (entriesByIndex.has(entry.settlement.entryIndex)) {
|
|
1864
|
-
return { invalidReason: 'primary_summary_settlement_duplicate_entry_on_restart' };
|
|
1865
|
-
}
|
|
1866
|
-
entriesByIndex.set(entry.settlement.entryIndex, entry);
|
|
1867
|
-
}
|
|
1868
|
-
const placeholderEntry = entriesByIndex.get(1);
|
|
1869
|
-
if (placeholderEntry
|
|
1870
|
-
&& (placeholderEntry.participant !== 'user'
|
|
1871
|
-
|| placeholderEntry.settlement.role !== 'user'
|
|
1872
|
-
|| placeholderEntry.settlement.kind !== 'generated_tool_result')) {
|
|
1873
|
-
return { invalidReason: 'primary_summary_settlement_placeholder_marker_invalid_on_restart' };
|
|
1874
|
-
}
|
|
1875
|
-
if ([...entriesByIndex.keys()].some((index) => index !== 0 && index !== 1)) {
|
|
1876
|
-
return { invalidReason: 'primary_summary_settlement_out_of_order_entries_on_restart' };
|
|
1877
|
-
}
|
|
1878
|
-
const expectedPlaceholder = settlement.outcome === 'held'
|
|
1879
|
-
? this.buildNonExecutedToolResults(assistantEntry.content)
|
|
1880
|
-
: [];
|
|
1881
|
-
if (settlement.outcome === 'success') {
|
|
1882
|
-
if (settlement.entryCount !== 1 || placeholderEntry) {
|
|
1883
|
-
return { invalidReason: 'primary_summary_success_settlement_shape_invalid_on_restart' };
|
|
1884
|
-
}
|
|
1885
|
-
}
|
|
1886
|
-
else {
|
|
1887
|
-
const expectedEntryCount = expectedPlaceholder.length > 0 ? 2 : 1;
|
|
1888
|
-
if (settlement.entryCount !== expectedEntryCount) {
|
|
1889
|
-
return { invalidReason: 'primary_summary_held_settlement_shape_invalid_on_restart' };
|
|
1890
|
-
}
|
|
1891
|
-
if (placeholderEntry) {
|
|
1892
|
-
if (!this.primarySummaryContentMatches(placeholderEntry.content, expectedPlaceholder)) {
|
|
1893
|
-
return { invalidReason: 'primary_summary_settlement_placeholder_content_mismatch_on_restart' };
|
|
1894
|
-
}
|
|
1895
|
-
}
|
|
1896
|
-
else if (expectedPlaceholder.length > 0) {
|
|
1897
|
-
const placeholderMetadata = this.buildPrimarySummarySettlementMetadata(record, {
|
|
1898
|
-
outcome: 'held',
|
|
1899
|
-
settlementId: settlement.settlementId,
|
|
1900
|
-
stopReason: settlement.stopReason,
|
|
1901
|
-
holdReason: settlement.holdReason,
|
|
1902
|
-
providerInputTokens: settlement.providerInputTokens,
|
|
1903
|
-
visibleAssistantOutput: settlement.visibleAssistantOutput,
|
|
1904
|
-
executedToolCalls: settlement.executedToolCalls,
|
|
1905
|
-
entryIndex: 1,
|
|
1906
|
-
entryCount: settlement.entryCount,
|
|
1907
|
-
role: 'user',
|
|
1908
|
-
kind: 'generated_tool_result',
|
|
1909
|
-
});
|
|
1910
|
-
await this.persistPrimarySummaryChronicleEntries(agent, [
|
|
1911
|
-
{ participant: 'user', content: expectedPlaceholder, metadata: placeholderMetadata },
|
|
1912
|
-
]);
|
|
1913
|
-
}
|
|
1914
|
-
}
|
|
1915
|
-
if (record.dispatchKind === 'primary_summary_fallback_retry') {
|
|
1916
|
-
const parent = await this.findPrimarySummaryRetryParent(record.requestId);
|
|
1917
|
-
if (!parent)
|
|
1918
|
-
return { invalidReason: 'primary_summary_fallback_parent_missing_on_restart' };
|
|
1919
|
-
if (settlement.outcome === 'success') {
|
|
1920
|
-
await this.updatePrimarySummaryRequestRecord(record.requestId, (entry) => ({
|
|
1921
|
-
...entry,
|
|
1922
|
-
stopReason: settlement.stopReason ?? entry.stopReason,
|
|
1923
|
-
providerInputTokens: settlement.providerInputTokens ?? entry.providerInputTokens,
|
|
1924
|
-
visibleAssistantOutput: settlement.visibleAssistantOutput,
|
|
1925
|
-
executedToolCalls: settlement.executedToolCalls,
|
|
1926
|
-
finalStatus: 'success',
|
|
1927
|
-
fallbackHeldReason: undefined,
|
|
1928
|
-
}));
|
|
1929
|
-
await this.updatePrimarySummaryRequestRecord(parent.originalRequestId, (entry) => {
|
|
1930
|
-
const intent = entry.fallbackIntent;
|
|
1931
|
-
return {
|
|
1932
|
-
...entry,
|
|
1933
|
-
finalStatus: 'success',
|
|
1934
|
-
fallbackStatus: 'success',
|
|
1935
|
-
fallbackHeldReason: undefined,
|
|
1936
|
-
fallbackIntent: intent && intent.intentId === parent.intentId
|
|
1937
|
-
? { ...intent, finalizedAt: intent.finalizedAt ?? Date.now() }
|
|
1938
|
-
: intent,
|
|
1939
|
-
};
|
|
1940
|
-
});
|
|
1941
|
-
return 'finalized';
|
|
1942
|
-
}
|
|
1943
|
-
await this.updatePrimarySummaryRequestRecord(record.requestId, (entry) => ({
|
|
1944
|
-
...entry,
|
|
1945
|
-
stopReason: settlement.stopReason ?? entry.stopReason,
|
|
1946
|
-
providerInputTokens: settlement.providerInputTokens ?? entry.providerInputTokens,
|
|
1947
|
-
visibleAssistantOutput: settlement.visibleAssistantOutput,
|
|
1948
|
-
executedToolCalls: settlement.executedToolCalls,
|
|
1949
|
-
finalStatus: 'held',
|
|
1950
|
-
fallbackHeldReason: settlement.holdReason,
|
|
1951
|
-
}));
|
|
1952
|
-
await this.updatePrimarySummaryRequestRecord(parent.originalRequestId, (entry) => ({
|
|
1953
|
-
...entry,
|
|
1954
|
-
finalStatus: 'held',
|
|
1955
|
-
fallbackStatus: 'held',
|
|
1956
|
-
fallbackHeldReason: settlement.holdReason,
|
|
1957
|
-
}));
|
|
1958
|
-
return 'finalized';
|
|
1959
|
-
}
|
|
1960
|
-
await this.updatePrimarySummaryRequestRecord(record.requestId, (entry) => ({
|
|
1961
|
-
...entry,
|
|
1962
|
-
stopReason: settlement.stopReason ?? entry.stopReason,
|
|
1963
|
-
providerInputTokens: settlement.providerInputTokens ?? entry.providerInputTokens,
|
|
1964
|
-
visibleAssistantOutput: settlement.visibleAssistantOutput,
|
|
1965
|
-
executedToolCalls: settlement.executedToolCalls,
|
|
1966
|
-
finalStatus: settlement.outcome === 'success' ? 'success' : 'held',
|
|
1967
|
-
fallbackStatus: settlement.outcome === 'success' ? entry.fallbackStatus : 'held',
|
|
1968
|
-
fallbackHeldReason: settlement.outcome === 'held' ? settlement.holdReason : undefined,
|
|
1969
|
-
}));
|
|
1970
|
-
return 'finalized';
|
|
1971
|
-
}
|
|
1972
|
-
summarizePrimarySummaryRetryRequest(request) {
|
|
1973
|
-
const blockCounts = new Map();
|
|
1974
|
-
for (const message of request.messages) {
|
|
1975
|
-
for (const block of message.content) {
|
|
1976
|
-
blockCounts.set(block.type, (blockCounts.get(block.type) ?? 0) + 1);
|
|
1977
|
-
}
|
|
1978
|
-
}
|
|
1979
|
-
return {
|
|
1980
|
-
kind: 'primary_summary_fallback_retry',
|
|
1981
|
-
requestHash: this.hashJson(request),
|
|
1982
|
-
systemHash: this.hashJson(request.system ?? ''),
|
|
1983
|
-
messageCount: request.messages.length,
|
|
1984
|
-
toolCount: request.tools?.length ?? 0,
|
|
1985
|
-
participantCount: [...new Set(request.messages.map((message) => message.participant))].length,
|
|
1986
|
-
blockCounts: Object.fromEntries([...blockCounts.entries()].sort(([left], [right]) => left.localeCompare(right))),
|
|
1987
|
-
promptCaching: request.promptCaching === true,
|
|
1988
|
-
cacheTtl: request.cacheTtl,
|
|
1989
|
-
assistantParticipant: request.assistantParticipant,
|
|
1990
|
-
requestCompleteBoundTokens: this.estimateRequestCompleteBoundTokens(request),
|
|
1991
|
-
};
|
|
1992
|
-
}
|
|
1993
|
-
summarizePrimarySummaryRetryResponse(response) {
|
|
1994
|
-
const blockCounts = new Map();
|
|
1995
|
-
for (const block of response.content) {
|
|
1996
|
-
blockCounts.set(block.type, (blockCounts.get(block.type) ?? 0) + 1);
|
|
1997
|
-
}
|
|
1998
|
-
return {
|
|
1999
|
-
kind: 'primary_summary_fallback_retry',
|
|
2000
|
-
stopReason: response.stopReason,
|
|
2001
|
-
rawAssistantTextHash: response.rawAssistantText
|
|
2002
|
-
? this.hashJson(response.rawAssistantText)
|
|
2003
|
-
: undefined,
|
|
2004
|
-
blockCounts: Object.fromEntries([...blockCounts.entries()].sort(([left], [right]) => left.localeCompare(right))),
|
|
2005
|
-
toolCallIds: response.toolCalls.map((call) => call.id),
|
|
2006
|
-
usage: response.usage
|
|
2007
|
-
? {
|
|
2008
|
-
inputTokens: response.usage.inputTokens,
|
|
2009
|
-
outputTokens: response.usage.outputTokens,
|
|
2010
|
-
}
|
|
2011
|
-
: undefined,
|
|
2012
|
-
};
|
|
2013
|
-
}
|
|
2014
|
-
async claimPrimarySummaryRetryIntent(originalRequestId, intentId, retryRequestId) {
|
|
2015
|
-
let outcome = 'missing';
|
|
2016
|
-
await this.updatePrimarySummaryRequestRecord(originalRequestId, (record) => {
|
|
2017
|
-
const intent = record.fallbackIntent;
|
|
2018
|
-
if (!intent || intent.intentId !== intentId || record.fallbackStatus !== 'pending') {
|
|
2019
|
-
return record;
|
|
2020
|
-
}
|
|
2021
|
-
if (intent.retryRequestId && intent.retryRequestId !== retryRequestId) {
|
|
2022
|
-
outcome = 'duplicate';
|
|
2023
|
-
return record;
|
|
2024
|
-
}
|
|
2025
|
-
outcome = 'claimed';
|
|
2026
|
-
return {
|
|
2027
|
-
...record,
|
|
2028
|
-
fallbackIntent: {
|
|
2029
|
-
...intent,
|
|
2030
|
-
retryRequestId,
|
|
2031
|
-
dispatchedAt: intent.dispatchedAt ?? Date.now(),
|
|
2032
|
-
},
|
|
2033
|
-
};
|
|
2034
|
-
});
|
|
2035
|
-
return outcome;
|
|
2036
|
-
}
|
|
2037
|
-
async validatePrimarySummaryRetryMutation(agent, requestId, retryDispatch, compiledRequest, artifacts, phase) {
|
|
2038
|
-
const branch = agent.getCurrentBranchGeneration();
|
|
2039
|
-
if (!branch || branch.id !== retryDispatch.branch.id || branch.generation !== retryDispatch.branch.generation) {
|
|
2040
|
-
return {
|
|
2041
|
-
ok: false,
|
|
2042
|
-
reason: `primary_summary_fallback_branch_changed_${phase}`,
|
|
2043
|
-
data: {
|
|
2044
|
-
expectedBranchId: retryDispatch.branch.id,
|
|
2045
|
-
expectedGeneration: retryDispatch.branch.generation,
|
|
2046
|
-
actualBranchId: branch?.id,
|
|
2047
|
-
actualGeneration: branch?.generation,
|
|
2048
|
-
},
|
|
2049
|
-
};
|
|
2050
|
-
}
|
|
2051
|
-
const requestHash = this.hashJson(compiledRequest);
|
|
2052
|
-
if (requestHash !== retryDispatch.expectedRetryRequestHash) {
|
|
2053
|
-
return {
|
|
2054
|
-
ok: false,
|
|
2055
|
-
reason: `primary_summary_fallback_request_changed_${phase}`,
|
|
2056
|
-
data: {
|
|
2057
|
-
expectedRetryRequestHash: retryDispatch.expectedRetryRequestHash,
|
|
2058
|
-
actualRetryRequestHash: requestHash,
|
|
2059
|
-
},
|
|
2060
|
-
};
|
|
2061
|
-
}
|
|
2062
|
-
if (this.primarySummaryProjectionKeys(artifacts.compileResult.primarySummaryProjection ?? null).join('\u0000') !==
|
|
2063
|
-
retryDispatch.originalProjectionKeys.join('\u0000')) {
|
|
2064
|
-
return {
|
|
2065
|
-
ok: false,
|
|
2066
|
-
reason: `primary_summary_fallback_projection_changed_${phase}`,
|
|
2067
|
-
data: {
|
|
2068
|
-
expectedProjectionKeys: retryDispatch.originalProjectionKeys,
|
|
2069
|
-
actualProjectionKeys: this.primarySummaryProjectionKeys(artifacts.compileResult.primarySummaryProjection ?? null),
|
|
2070
|
-
},
|
|
2071
|
-
};
|
|
2072
|
-
}
|
|
2073
|
-
if (!this.primarySummaryContractsMatch(artifacts.contract, retryDispatch.originalArtifacts.contract)) {
|
|
2074
|
-
return {
|
|
2075
|
-
ok: false,
|
|
2076
|
-
reason: `primary_summary_fallback_contract_changed_${phase}`,
|
|
2077
|
-
data: {
|
|
2078
|
-
expectedContract: retryDispatch.originalArtifacts.contract,
|
|
2079
|
-
actualContract: artifacts.contract,
|
|
2080
|
-
},
|
|
2081
|
-
};
|
|
2082
|
-
}
|
|
2083
|
-
const original = await this.getPrimarySummaryRequestRecord(retryDispatch.originalRequestId);
|
|
2084
|
-
if (!original?.fallbackIntent ||
|
|
2085
|
-
original.fallbackIntent.intentId !== retryDispatch.intentId ||
|
|
2086
|
-
original.fallbackIntent.retryRequestId !== requestId ||
|
|
2087
|
-
original.fallbackStatus !== 'pending') {
|
|
2088
|
-
return {
|
|
2089
|
-
ok: false,
|
|
2090
|
-
reason: `primary_summary_fallback_intent_changed_${phase}`,
|
|
2091
|
-
data: {
|
|
2092
|
-
expectedIntentId: retryDispatch.intentId,
|
|
2093
|
-
actualIntentId: original?.fallbackIntent?.intentId,
|
|
2094
|
-
retryRequestId: original?.fallbackIntent?.retryRequestId,
|
|
2095
|
-
fallbackStatus: original?.fallbackStatus,
|
|
2096
|
-
},
|
|
2097
|
-
};
|
|
2098
|
-
}
|
|
2099
|
-
return { ok: true };
|
|
2100
|
-
}
|
|
2101
|
-
async recordPrimarySummaryRetryQuarantine(originalRequestId, intentId) {
|
|
2102
|
-
await this.updatePrimarySummaryRequestRecord(originalRequestId, (record) => {
|
|
2103
|
-
const intent = record.fallbackIntent;
|
|
2104
|
-
if (!intent || intent.intentId !== intentId || intent.quarantinePersistedAt)
|
|
2105
|
-
return record;
|
|
2106
|
-
return {
|
|
2107
|
-
...record,
|
|
2108
|
-
fallbackIntent: {
|
|
2109
|
-
...intent,
|
|
2110
|
-
quarantinePersistedAt: Date.now(),
|
|
2111
|
-
},
|
|
2112
|
-
};
|
|
2113
|
-
});
|
|
2114
|
-
}
|
|
2115
|
-
async recordPrimarySummaryRetryOutput(originalRequestId, intentId, output) {
|
|
2116
|
-
await this.updatePrimarySummaryRequestRecord(originalRequestId, (record) => {
|
|
2117
|
-
const intent = record.fallbackIntent;
|
|
2118
|
-
if (!intent || intent.intentId !== intentId)
|
|
2119
|
-
return record;
|
|
2120
|
-
const existing = intent.outputs?.find((entry) => entry.phase === output.phase);
|
|
2121
|
-
if (existing &&
|
|
2122
|
-
existing.contentHash === output.contentHash &&
|
|
2123
|
-
existing.messageId === output.messageId) {
|
|
2124
|
-
return record;
|
|
2125
|
-
}
|
|
2126
|
-
if (existing) {
|
|
2127
|
-
throw new Error(`Primary summary fallback output phase '${output.phase}' changed after persistence`);
|
|
2128
|
-
}
|
|
2129
|
-
return {
|
|
2130
|
-
...record,
|
|
2131
|
-
fallbackIntent: {
|
|
2132
|
-
...intent,
|
|
2133
|
-
outputs: [...(intent.outputs ?? []), output],
|
|
2134
|
-
},
|
|
2135
|
-
};
|
|
2136
|
-
});
|
|
2137
|
-
}
|
|
2138
|
-
async recordPrimarySummaryRetryToolDispatch(originalRequestId, intentId, call) {
|
|
2139
|
-
await this.updatePrimarySummaryRequestRecord(originalRequestId, (record) => {
|
|
2140
|
-
const intent = record.fallbackIntent;
|
|
2141
|
-
if (!intent || intent.intentId !== intentId)
|
|
2142
|
-
return record;
|
|
2143
|
-
const inputHash = this.hashJson(call.input);
|
|
2144
|
-
const existing = intent.toolDispatches?.find((entry) => entry.callId === call.id);
|
|
2145
|
-
if (existing?.inputHash === inputHash)
|
|
2146
|
-
return record;
|
|
2147
|
-
if (existing) {
|
|
2148
|
-
throw new Error(`Primary summary fallback tool dispatch '${call.id}' changed after persistence`);
|
|
2149
|
-
}
|
|
2150
|
-
return {
|
|
2151
|
-
...record,
|
|
2152
|
-
fallbackIntent: {
|
|
2153
|
-
...intent,
|
|
2154
|
-
toolDispatches: [
|
|
2155
|
-
...(intent.toolDispatches ?? []),
|
|
2156
|
-
{
|
|
2157
|
-
callId: call.id,
|
|
2158
|
-
inputHash,
|
|
2159
|
-
recordedAt: Date.now(),
|
|
2160
|
-
},
|
|
2161
|
-
],
|
|
2162
|
-
},
|
|
2163
|
-
};
|
|
2164
|
-
});
|
|
2165
|
-
}
|
|
2166
|
-
async resolveContextBudgetRestartDispatch(requestId, retryDispatch) {
|
|
2167
|
-
const retryIdentity = retryDispatch
|
|
2168
|
-
? {
|
|
2169
|
-
requestId,
|
|
2170
|
-
dispatchKind: 'primary_summary_fallback_retry',
|
|
2171
|
-
originalRequestId: retryDispatch.originalRequestId,
|
|
2172
|
-
intentId: retryDispatch.intentId,
|
|
2173
|
-
}
|
|
2174
|
-
: null;
|
|
2175
|
-
const record = await this.getPrimarySummaryRequestRecord(requestId);
|
|
2176
|
-
if (record?.dispatchKind === 'primary_summary_fallback_retry' && retryIdentity) {
|
|
2177
|
-
return retryIdentity;
|
|
2178
|
-
}
|
|
2179
|
-
if (retryIdentity) {
|
|
2180
|
-
const original = await this.getPrimarySummaryRequestRecord(retryIdentity.originalRequestId);
|
|
2181
|
-
if (original?.fallbackIntent?.intentId === retryIdentity.intentId
|
|
2182
|
-
&& original.fallbackIntent.retryRequestId === requestId) {
|
|
2183
|
-
return retryIdentity;
|
|
2184
|
-
}
|
|
2185
|
-
}
|
|
2186
|
-
return {
|
|
2187
|
-
requestId,
|
|
2188
|
-
dispatchKind: 'primary',
|
|
2189
|
-
};
|
|
2190
|
-
}
|
|
2191
|
-
contextBudgetRestartMatchesIdentity(request, identity) {
|
|
2192
|
-
if (request.reason !== 'context_budget_restart')
|
|
2193
|
-
return false;
|
|
2194
|
-
const restart = request.contextBudgetRestart;
|
|
2195
|
-
if (!restart)
|
|
2196
|
-
return false;
|
|
2197
|
-
if (restart.dispatchKind !== identity.dispatchKind)
|
|
2198
|
-
return false;
|
|
2199
|
-
if (restart.requestId !== identity.requestId)
|
|
2200
|
-
return false;
|
|
2201
|
-
if (identity.dispatchKind !== 'primary_summary_fallback_retry')
|
|
2202
|
-
return true;
|
|
2203
|
-
return restart.originalRequestId === identity.originalRequestId
|
|
2204
|
-
&& restart.intentId === identity.intentId;
|
|
2205
|
-
}
|
|
2206
|
-
clearQueuedContextBudgetRestart(identity) {
|
|
2207
|
-
const before = this.pendingRequests.length;
|
|
2208
|
-
this.pendingRequests = this.pendingRequests.filter((request) => !this.contextBudgetRestartMatchesIdentity(request, identity));
|
|
2209
|
-
return before - this.pendingRequests.length;
|
|
2210
|
-
}
|
|
2211
|
-
async markPrimarySummaryRetrySuccess(requestId, retryDispatch, stopReason, providerInputTokens, visibleAssistantOutput, executedToolCalls) {
|
|
2212
|
-
await this.updatePrimarySummaryRequestRecord(requestId, (record) => ({
|
|
2213
|
-
...record,
|
|
2214
|
-
stopReason,
|
|
2215
|
-
providerInputTokens,
|
|
2216
|
-
visibleAssistantOutput,
|
|
2217
|
-
executedToolCalls,
|
|
2218
|
-
finalStatus: 'success',
|
|
2219
|
-
fallbackHeldReason: undefined,
|
|
2220
|
-
}));
|
|
2221
|
-
await this.updatePrimarySummaryRequestRecord(retryDispatch.originalRequestId, (record) => {
|
|
2222
|
-
const intent = record.fallbackIntent;
|
|
2223
|
-
return {
|
|
2224
|
-
...record,
|
|
2225
|
-
finalStatus: 'success',
|
|
2226
|
-
fallbackStatus: 'success',
|
|
2227
|
-
fallbackHeldReason: undefined,
|
|
2228
|
-
fallbackIntent: intent && intent.intentId === retryDispatch.intentId
|
|
2229
|
-
? { ...intent, finalizedAt: Date.now() }
|
|
2230
|
-
: intent,
|
|
2231
|
-
};
|
|
2232
|
-
});
|
|
2233
|
-
}
|
|
2234
|
-
async holdPrimarySummaryRetryFamily(agent, requestId, retryDispatch, reason, data, options) {
|
|
2235
|
-
const clearedQueuedContextBudgetRestarts = this.clearQueuedContextBudgetRestart({
|
|
2236
|
-
requestId,
|
|
2237
|
-
dispatchKind: 'primary_summary_fallback_retry',
|
|
2238
|
-
originalRequestId: retryDispatch.originalRequestId,
|
|
2239
|
-
intentId: retryDispatch.intentId,
|
|
2240
|
-
});
|
|
2241
|
-
const holdData = clearedQueuedContextBudgetRestarts > 0
|
|
2242
|
-
? { ...(data ?? {}), clearedQueuedContextBudgetRestarts }
|
|
2243
|
-
: data;
|
|
2244
|
-
await this.updatePrimarySummaryRequestRecord(requestId, (record) => ({
|
|
2245
|
-
...record,
|
|
2246
|
-
...(options?.stopReason ? { stopReason: options.stopReason } : {}),
|
|
2247
|
-
finalStatus: 'held',
|
|
2248
|
-
fallbackHeldReason: reason,
|
|
2249
|
-
}));
|
|
2250
|
-
await this.updatePrimarySummaryRequestRecord(retryDispatch.originalRequestId, (record) => ({
|
|
2251
|
-
...record,
|
|
2252
|
-
finalStatus: 'held',
|
|
2253
|
-
fallbackStatus: 'held',
|
|
2254
|
-
fallbackHeldReason: reason,
|
|
2255
|
-
}));
|
|
2256
|
-
this.opsAlert('refusal-held', agent.name, reason, { data: holdData });
|
|
2257
|
-
if (options?.emitChronicleMarker === false)
|
|
2258
|
-
return;
|
|
2259
|
-
try {
|
|
2260
|
-
agent.getContextManager().addMessage('user', [{
|
|
2261
|
-
type: 'text',
|
|
2262
|
-
text: `[refusal-held] Your previous turn was refused by the provider and ` +
|
|
2263
|
-
`is being held for operator review rather than silently retried. ` +
|
|
2264
|
-
`Automatic raw-expansion recovery was either unsafe or unsuccessful.`,
|
|
2265
|
-
}], { system: true, kind: 'refusal-held', reason });
|
|
2266
|
-
}
|
|
2267
|
-
catch (error) {
|
|
2268
|
-
console.error('[refusal-held] could not record chronicle marker:', error);
|
|
2269
|
-
}
|
|
2270
|
-
}
|
|
2271
|
-
async primarySummaryRetryAlreadySucceeded(requestId, retryDispatch) {
|
|
2272
|
-
const [retryRecord, originalRecord] = await Promise.all([
|
|
2273
|
-
this.getPrimarySummaryRequestRecord(requestId),
|
|
2274
|
-
this.getPrimarySummaryRequestRecord(retryDispatch.originalRequestId),
|
|
2275
|
-
]);
|
|
2276
|
-
return retryRecord?.finalStatus === 'success'
|
|
2277
|
-
|| originalRecord?.fallbackStatus === 'success'
|
|
2278
|
-
|| originalRecord?.finalStatus === 'success';
|
|
2279
|
-
}
|
|
2280
|
-
async primarySummaryRetryFamilyAlreadySettled(requestId, retryDispatch) {
|
|
2281
|
-
const [retryRecord, originalRecord] = await Promise.all([
|
|
2282
|
-
this.getPrimarySummaryRequestRecord(requestId),
|
|
2283
|
-
this.getPrimarySummaryRequestRecord(retryDispatch.originalRequestId),
|
|
2284
|
-
]);
|
|
2285
|
-
return retryRecord?.finalStatus === 'held'
|
|
2286
|
-
|| retryRecord?.finalStatus === 'success'
|
|
2287
|
-
|| originalRecord?.fallbackStatus === 'held'
|
|
2288
|
-
|| originalRecord?.fallbackStatus === 'success'
|
|
2289
|
-
|| originalRecord?.finalStatus === 'held'
|
|
2290
|
-
|| originalRecord?.finalStatus === 'success';
|
|
2291
|
-
}
|
|
2292
|
-
async holdPrimarySummaryRequest(agent, requestId, reason, data) {
|
|
2293
|
-
await this.updatePrimarySummaryRequestRecord(requestId, (record) => ({
|
|
2294
|
-
...record,
|
|
2295
|
-
finalStatus: 'held',
|
|
2296
|
-
fallbackStatus: 'held',
|
|
2297
|
-
fallbackHeldReason: reason,
|
|
2298
|
-
}));
|
|
2299
|
-
this.opsAlert('refusal-held', agent.name, reason, { data });
|
|
2300
|
-
try {
|
|
2301
|
-
agent.getContextManager().addMessage('user', [{
|
|
2302
|
-
type: 'text',
|
|
2303
|
-
text: `[refusal-held] Your previous turn was refused by the provider and ` +
|
|
2304
|
-
`is being held for operator review rather than silently retried. ` +
|
|
2305
|
-
`Automatic raw-expansion recovery was either unsafe or unsuccessful.`,
|
|
2306
|
-
}], { system: true, kind: 'refusal-held', reason });
|
|
2307
|
-
}
|
|
2308
|
-
catch (error) {
|
|
2309
|
-
console.error('[refusal-held] could not record chronicle marker:', error);
|
|
2310
|
-
}
|
|
2311
|
-
}
|
|
2312
|
-
hasVisibleAssistantOutput(blocks) {
|
|
2313
|
-
return blocks.some((block) => block.type !== 'thinking' && block.type !== 'redacted_thinking');
|
|
2314
|
-
}
|
|
2315
|
-
buildPrimarySummaryRequestRecord(agent, requestId, dispatchKind, request, artifacts, branch, stopReason, visibleAssistantOutput, executedToolCalls, providerInputTokens, finalStatus) {
|
|
2316
|
-
const projection = artifacts?.compileResult.primarySummaryProjection ?? null;
|
|
2317
|
-
return {
|
|
2318
|
-
requestId,
|
|
2319
|
-
agentName: agent.name,
|
|
2320
|
-
namespace: projection?.namespace ?? `agents/${agent.name}`,
|
|
2321
|
-
timestamp: Date.now(),
|
|
2322
|
-
dispatchKind,
|
|
2323
|
-
branch: branch
|
|
2324
|
-
? { id: branch.id, name: branch.name, generation: branch.generation }
|
|
2325
|
-
: { id: 'unknown', name: 'unknown', generation: -1 },
|
|
2326
|
-
projection,
|
|
2327
|
-
requestInputBoundTokens: this.estimateRequestInputBoundTokens(request),
|
|
2328
|
-
requestCompleteBoundTokens: this.estimateRequestCompleteBoundTokens(request),
|
|
2329
|
-
providerInputTokens,
|
|
2330
|
-
systemHash: artifacts?.contract.systemHash ?? this.hashJson(request.system ?? ''),
|
|
2331
|
-
modelConfigHash: artifacts?.contract.modelConfigHash ?? this.hashJson({ request: request.config }),
|
|
2332
|
-
toolContractHash: artifacts?.contract.toolContractHash ?? this.hashJson(request.tools ?? []),
|
|
2333
|
-
stopReason,
|
|
2334
|
-
visibleAssistantOutput,
|
|
2335
|
-
executedToolCalls,
|
|
2336
|
-
finalStatus,
|
|
2337
|
-
};
|
|
2338
|
-
}
|
|
2339
1237
|
/** Liveness ping for an active ephemeral run (no-op otherwise). */
|
|
2340
1238
|
touchEphemeralRun(agentName, inferenceStarted = false) {
|
|
2341
1239
|
const run = this.ephemeralRuns.get(agentName);
|
|
@@ -3684,7 +2582,6 @@ export class AgentFramework {
|
|
|
3684
2582
|
// Cast to AgentState to bypass TypeScript's control flow narrowing
|
|
3685
2583
|
const currentState = agent.state;
|
|
3686
2584
|
if (currentState.status === 'ready') {
|
|
3687
|
-
const activeStreamContext = this.activeStreamContexts.get(agent.name);
|
|
3688
2585
|
// Flush pending assistant blocks (tool_use + preamble text) to context
|
|
3689
2586
|
const pendingBlocks = this.pendingAssistantBlocks.get(agent.name);
|
|
3690
2587
|
if (pendingBlocks) {
|
|
@@ -3795,34 +2692,6 @@ export class AgentFramework {
|
|
|
3795
2692
|
// a terminal failure — rejecting an ephemeral's promise mid-run
|
|
3796
2693
|
// and emitting a spurious inference:exhausted (same race shape as
|
|
3797
2694
|
// endTurn above).
|
|
3798
|
-
const requestId = activeStreamContext?.requestId;
|
|
3799
|
-
const primarySummaryRetry = activeStreamContext?.trigger?.primarySummaryFallbackRetry;
|
|
3800
|
-
const compiledRequest = activeStreamContext?.compiledRequest;
|
|
3801
|
-
const artifacts = activeStreamContext?.artifacts;
|
|
3802
|
-
const restartDispatch = await this.resolveContextBudgetRestartDispatch(requestId ?? `${agent.name}:context_budget_restart`, primarySummaryRetry);
|
|
3803
|
-
if (restartDispatch.dispatchKind === 'primary_summary_fallback_retry'
|
|
3804
|
-
&& requestId
|
|
3805
|
-
&& primarySummaryRetry
|
|
3806
|
-
&& compiledRequest
|
|
3807
|
-
&& artifacts) {
|
|
3808
|
-
const validation = await this.validatePrimarySummaryRetryMutation(agent, requestId, primarySummaryRetry, compiledRequest, artifacts, 'before_context_budget_restart_hold');
|
|
3809
|
-
if (!validation.ok) {
|
|
3810
|
-
await this.holdPrimarySummaryRetryFamily(agent, requestId, primarySummaryRetry, validation.reason, validation.data, { emitChronicleMarker: false, stopReason: 'abort' });
|
|
3811
|
-
}
|
|
3812
|
-
else {
|
|
3813
|
-
await this.holdPrimarySummaryRetryFamily(agent, requestId, primarySummaryRetry, 'primary_summary_fallback_context_budget_restart_required', {
|
|
3814
|
-
inputTokens: agent.lastStreamInputTokens,
|
|
3815
|
-
budget: agent.maxStreamTokens,
|
|
3816
|
-
originalRequestId: primarySummaryRetry.originalRequestId,
|
|
3817
|
-
intentId: primarySummaryRetry.intentId,
|
|
3818
|
-
}, { emitChronicleMarker: false, stopReason: 'abort' });
|
|
3819
|
-
}
|
|
3820
|
-
this.frameworkCancelledStreams.set(`${agent.name}:${agent.streamId}`, 'budget_restart');
|
|
3821
|
-
currentState.stream?.cancel();
|
|
3822
|
-
agent.reset();
|
|
3823
|
-
this.settleAgent(agent.name, { stopReason: 'completed', speech: '' });
|
|
3824
|
-
return;
|
|
3825
|
-
}
|
|
3826
2695
|
if (currentState.stream) {
|
|
3827
2696
|
this.frameworkCancelledStreams.set(`${agent.name}:${agent.streamId}`, 'budget_restart');
|
|
3828
2697
|
}
|
|
@@ -3839,8 +2708,6 @@ export class AgentFramework {
|
|
|
3839
2708
|
reason: 'context_budget_restart',
|
|
3840
2709
|
source: 'framework',
|
|
3841
2710
|
timestamp: Date.now(),
|
|
3842
|
-
channelId: activeStreamContext?.trigger?.channelId,
|
|
3843
|
-
contextBudgetRestart: restartDispatch,
|
|
3844
2711
|
});
|
|
3845
2712
|
}
|
|
3846
2713
|
else if (currentState.stream) {
|
|
@@ -4338,8 +3205,7 @@ export class AgentFramework {
|
|
|
4338
3205
|
// Clear pending (we'll re-add if inference doesn't run)
|
|
4339
3206
|
this.pendingRequests = [];
|
|
4340
3207
|
// Check each agent
|
|
4341
|
-
for (const [agentName,
|
|
4342
|
-
let requests = queuedRequests;
|
|
3208
|
+
for (const [agentName, requests] of requestsByAgent) {
|
|
4343
3209
|
const agent = this.agents.get(agentName);
|
|
4344
3210
|
if (!agent) {
|
|
4345
3211
|
// Agent not found — request is orphaned. Emit warning and drop.
|
|
@@ -4378,15 +3244,6 @@ export class AgentFramework {
|
|
|
4378
3244
|
this.pendingRequests.push(...requests);
|
|
4379
3245
|
continue;
|
|
4380
3246
|
}
|
|
4381
|
-
if (this.discordAwarenessBarrier) {
|
|
4382
|
-
this.pendingRequests.push(...requests);
|
|
4383
|
-
continue;
|
|
4384
|
-
}
|
|
4385
|
-
requests = requests.filter((request) => request.reason !== 'context_budget_restart'
|
|
4386
|
-
|| request.contextBudgetRestart?.dispatchKind !== 'primary_summary_fallback_retry');
|
|
4387
|
-
if (requests.length === 0) {
|
|
4388
|
-
continue;
|
|
4389
|
-
}
|
|
4390
3247
|
// Check policy
|
|
4391
3248
|
if (!this.inferencePolicy.shouldInfer(agentName, requests, state)) {
|
|
4392
3249
|
// Loud drop: a queued request that dies here is otherwise invisible —
|
|
@@ -4397,13 +3254,7 @@ export class AgentFramework {
|
|
|
4397
3254
|
continue;
|
|
4398
3255
|
}
|
|
4399
3256
|
// Start streaming inference (non-blocking — driveStream runs in background)
|
|
4400
|
-
const trigger = requests
|
|
4401
|
-
if (trigger.primarySummaryFallbackRetry) {
|
|
4402
|
-
for (const request of requests) {
|
|
4403
|
-
if (request !== trigger)
|
|
4404
|
-
this.pendingRequests.push(request);
|
|
4405
|
-
}
|
|
4406
|
-
}
|
|
3257
|
+
const trigger = requests[0];
|
|
4407
3258
|
// Route this turn's auto-published speech to the channel that triggered
|
|
4408
3259
|
// it (item-3 redux). A batched wake may carry several triggering channels
|
|
4409
3260
|
// (messages arrived in >1 channel while the agent was busy/idle) — reply
|
|
@@ -4418,7 +3269,7 @@ export class AgentFramework {
|
|
|
4418
3269
|
}
|
|
4419
3270
|
async startAgentStream(agent, trigger, attempt = 0) {
|
|
4420
3271
|
// Record turn checkpoint before inference (only on first attempt, not retries)
|
|
4421
|
-
if (attempt === 0
|
|
3272
|
+
if (attempt === 0) {
|
|
4422
3273
|
this.recordTurnCheckpoint(agent.name);
|
|
4423
3274
|
this.redoStacks.delete(agent.name); // new work invalidates redo
|
|
4424
3275
|
}
|
|
@@ -4438,180 +3289,46 @@ export class AgentFramework {
|
|
|
4438
3289
|
this.emitTrace({ type: 'inference:started', agentName: agent.name });
|
|
4439
3290
|
this.eventGate?.onInferenceStarted(agent.name);
|
|
4440
3291
|
this.lastInferenceAt.set(agent.name, { ...this.lastInferenceAt.get(agent.name), startedAt: Date.now() });
|
|
4441
|
-
const requestId = `${agent.name}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
4442
3292
|
try {
|
|
4443
3293
|
const requestSnapshot = this.captureInferenceToolSnapshot(agent);
|
|
4444
3294
|
const allTools = this.getToolsForAgent(agent.name, requestSnapshot);
|
|
4445
3295
|
const tools = allTools.filter((t) => agent.canUseTool(t.name));
|
|
4446
|
-
|
|
4447
|
-
|
|
4448
|
-
let
|
|
4449
|
-
|
|
4450
|
-
|
|
4451
|
-
|
|
4452
|
-
|
|
4453
|
-
|
|
4454
|
-
|
|
4455
|
-
|
|
4456
|
-
|
|
4457
|
-
|
|
4458
|
-
return;
|
|
4459
|
-
}
|
|
4460
|
-
const claim = await this.claimPrimarySummaryRetryIntent(retryDispatch.originalRequestId, retryDispatch.intentId, requestId);
|
|
4461
|
-
if (claim !== 'claimed') {
|
|
4462
|
-
await this.holdPrimarySummaryRequest(agent, retryDispatch.originalRequestId, claim === 'duplicate'
|
|
4463
|
-
? 'primary_summary_fallback_duplicate_retry_blocked'
|
|
4464
|
-
: 'primary_summary_fallback_retry_intent_missing', { intentId: retryDispatch.intentId, retryRequestId: requestId });
|
|
4465
|
-
agent.reset();
|
|
4466
|
-
this.settleAgent(agent.name, { stopReason: 'completed', speech: '' });
|
|
4467
|
-
this.eventGate?.onInferenceEnded(agent.name);
|
|
4468
|
-
this.lastInferenceAt.set(agent.name, { ...this.lastInferenceAt.get(agent.name), endedAt: Date.now() });
|
|
4469
|
-
return;
|
|
4470
|
-
}
|
|
4471
|
-
if (!branch ||
|
|
4472
|
-
branch.id !== retryDispatch.branch.id ||
|
|
4473
|
-
branch.generation !== retryDispatch.branch.generation) {
|
|
4474
|
-
await this.holdPrimarySummaryRequest(agent, retryDispatch.originalRequestId, 'primary_summary_fallback_branch_changed_before_retry', {
|
|
4475
|
-
expectedBranchId: retryDispatch.branch.id,
|
|
4476
|
-
expectedGeneration: retryDispatch.branch.generation,
|
|
4477
|
-
actualBranchId: branch?.id,
|
|
4478
|
-
actualGeneration: branch?.generation,
|
|
4479
|
-
});
|
|
4480
|
-
agent.reset();
|
|
4481
|
-
this.settleAgent(agent.name, { stopReason: 'completed', speech: '' });
|
|
4482
|
-
this.eventGate?.onInferenceEnded(agent.name);
|
|
4483
|
-
this.lastInferenceAt.set(agent.name, { ...this.lastInferenceAt.get(agent.name), endedAt: Date.now() });
|
|
4484
|
-
return;
|
|
4485
|
-
}
|
|
4486
|
-
const rebuilt = agent.buildPrimarySummaryRawExpansionRequest(retryDispatch.originalArtifacts, retryDispatch.candidateSummaries, tools);
|
|
4487
|
-
compiledRequest = rebuilt.request;
|
|
4488
|
-
artifacts = rebuilt.artifacts;
|
|
4489
|
-
const rebuiltHash = this.hashJson(compiledRequest);
|
|
4490
|
-
if (rebuiltHash !== retryDispatch.expectedRetryRequestHash ||
|
|
4491
|
-
this.primarySummaryProjectionKeys(artifacts.compileResult.primarySummaryProjection ?? null).join('\u0000') !==
|
|
4492
|
-
retryDispatch.originalProjectionKeys.join('\u0000')) {
|
|
4493
|
-
await this.holdPrimarySummaryRequest(agent, retryDispatch.originalRequestId, 'primary_summary_fallback_revalidation_failed_before_retry', {
|
|
4494
|
-
expectedRetryRequestHash: retryDispatch.expectedRetryRequestHash,
|
|
4495
|
-
actualRetryRequestHash: rebuiltHash,
|
|
4496
|
-
});
|
|
4497
|
-
agent.reset();
|
|
4498
|
-
this.settleAgent(agent.name, { stopReason: 'completed', speech: '' });
|
|
4499
|
-
this.eventGate?.onInferenceEnded(agent.name);
|
|
4500
|
-
this.lastInferenceAt.set(agent.name, { ...this.lastInferenceAt.get(agent.name), endedAt: Date.now() });
|
|
4501
|
-
return;
|
|
4502
|
-
}
|
|
4503
|
-
const retryRecord = this.buildPrimarySummaryRequestRecord(agent, requestId, 'primary_summary_fallback_retry', compiledRequest, artifacts, branch, undefined, false, 0, undefined, 'pending');
|
|
4504
|
-
await this.persistPrimarySummaryRequestRecord(retryRecord);
|
|
4505
|
-
const admittedTokens = this.primarySummaryAdmissionBoundTokens(retryDispatch.originalRequestInputBoundTokens, compiledRequest, retryDispatch.originalProviderInputTokens);
|
|
4506
|
-
if (admittedTokens > retryDispatch.requestBudgetTokens) {
|
|
4507
|
-
await this.holdPrimarySummaryRequest(agent, requestId, 'primary_summary_fallback_retry_over_budget', {
|
|
4508
|
-
admittedTokens,
|
|
4509
|
-
requestBudgetTokens: retryDispatch.requestBudgetTokens,
|
|
4510
|
-
retryRequestHash: rebuiltHash,
|
|
4511
|
-
});
|
|
4512
|
-
await this.updatePrimarySummaryRequestRecord(retryDispatch.originalRequestId, (record) => ({
|
|
4513
|
-
...record,
|
|
4514
|
-
fallbackStatus: 'held',
|
|
4515
|
-
fallbackHeldReason: 'primary_summary_fallback_retry_over_budget',
|
|
4516
|
-
}));
|
|
4517
|
-
agent.reset();
|
|
4518
|
-
this.settleAgent(agent.name, { stopReason: 'completed', speech: '' });
|
|
4519
|
-
this.eventGate?.onInferenceEnded(agent.name);
|
|
4520
|
-
this.lastInferenceAt.set(agent.name, { ...this.lastInferenceAt.get(agent.name), endedAt: Date.now() });
|
|
4521
|
-
return;
|
|
3296
|
+
// Gather context from modules (pull-based) and MCPL hooks (push-based)
|
|
3297
|
+
// Both produce ContextInjection[] that get merged before inference.
|
|
3298
|
+
let injections;
|
|
3299
|
+
// Module gatherContext (fail-open, per-module timeout — the module's
|
|
3300
|
+
// contextTimeoutMs, else the registry default). Injections are
|
|
3301
|
+
// channel-scoped via scopeInjectionsForAgent, matching the MCPL hook
|
|
3302
|
+
// injections below, so conversation forks don't see cross-channel
|
|
3303
|
+
// content.
|
|
3304
|
+
try {
|
|
3305
|
+
const moduleInjections = this.scopeInjectionsForAgent(agent.name, await this.moduleRegistry.gatherContext(agent.name));
|
|
3306
|
+
if (moduleInjections.length > 0) {
|
|
3307
|
+
injections = moduleInjections;
|
|
4522
3308
|
}
|
|
4523
3309
|
}
|
|
4524
|
-
|
|
4525
|
-
|
|
4526
|
-
|
|
4527
|
-
|
|
4528
|
-
|
|
4529
|
-
// contextTimeoutMs, else the registry default). Injections are
|
|
4530
|
-
// channel-scoped via scopeInjectionsForAgent, matching the MCPL hook
|
|
4531
|
-
// injections below, so conversation forks don't see cross-channel
|
|
4532
|
-
// content.
|
|
3310
|
+
catch (error) {
|
|
3311
|
+
console.error('Module gatherContext error:', error);
|
|
3312
|
+
}
|
|
3313
|
+
// MCPL beforeInference hooks (fail-open)
|
|
3314
|
+
if (this.hookOrchestrator) {
|
|
4533
3315
|
try {
|
|
4534
|
-
const
|
|
4535
|
-
|
|
4536
|
-
|
|
3316
|
+
const hookParams = this.buildBeforeInferenceParams(agent, trigger);
|
|
3317
|
+
const hookInjections = this.scopeInjectionsForAgent(agent.name, await this.hookOrchestrator.beforeInference(hookParams));
|
|
3318
|
+
if (hookInjections.length > 0) {
|
|
3319
|
+
injections = injections ? [...injections, ...hookInjections] : hookInjections;
|
|
4537
3320
|
}
|
|
4538
3321
|
}
|
|
4539
3322
|
catch (error) {
|
|
4540
|
-
console.error('
|
|
4541
|
-
}
|
|
4542
|
-
// MCPL beforeInference hooks (fail-open)
|
|
4543
|
-
if (this.hookOrchestrator) {
|
|
4544
|
-
try {
|
|
4545
|
-
const hookParams = this.buildBeforeInferenceParams(agent, trigger);
|
|
4546
|
-
const hookInjections = this.scopeInjectionsForAgent(agent.name, await this.hookOrchestrator.beforeInference(hookParams));
|
|
4547
|
-
if (hookInjections.length > 0) {
|
|
4548
|
-
injections = injections ? [...injections, ...hookInjections] : hookInjections;
|
|
4549
|
-
}
|
|
4550
|
-
}
|
|
4551
|
-
catch (error) {
|
|
4552
|
-
console.error('beforeInference hook error:', error);
|
|
4553
|
-
}
|
|
4554
|
-
}
|
|
4555
|
-
const prepared = await agent.prepareActivationRequest(tools, injections);
|
|
4556
|
-
compiledRequest = prepared.request;
|
|
4557
|
-
artifacts = prepared.artifacts;
|
|
4558
|
-
branch = agent.getCurrentBranchGeneration();
|
|
4559
|
-
if (fallbackConfig.enabled) {
|
|
4560
|
-
const record = this.buildPrimarySummaryRequestRecord(agent, requestId, 'primary', compiledRequest, artifacts, branch, undefined, false, 0, undefined, 'pending');
|
|
4561
|
-
await this.persistPrimarySummaryRequestRecord(record);
|
|
4562
|
-
const expandedByQuarantine = artifacts.compileResult.primarySummaryProjection?.selectedSummaries.some((selection) => selection.renderedAs === 'raw_expansion') ?? false;
|
|
4563
|
-
if (expandedByQuarantine) {
|
|
4564
|
-
const admittedTokens = this.estimateRequestCompleteBoundTokens(compiledRequest);
|
|
4565
|
-
if (admittedTokens > fallbackConfig.requestBudgetTokens) {
|
|
4566
|
-
await this.holdPrimarySummaryRequest(agent, requestId, 'primary_summary_quarantine_over_budget', {
|
|
4567
|
-
admittedTokens,
|
|
4568
|
-
requestBudgetTokens: fallbackConfig.requestBudgetTokens,
|
|
4569
|
-
});
|
|
4570
|
-
agent.reset();
|
|
4571
|
-
this.settleAgent(agent.name, { stopReason: 'completed', speech: '' });
|
|
4572
|
-
this.eventGate?.onInferenceEnded(agent.name);
|
|
4573
|
-
this.lastInferenceAt.set(agent.name, { ...this.lastInferenceAt.get(agent.name), endedAt: Date.now() });
|
|
4574
|
-
return;
|
|
4575
|
-
}
|
|
4576
|
-
}
|
|
3323
|
+
console.error('beforeInference hook error:', error);
|
|
4577
3324
|
}
|
|
4578
3325
|
}
|
|
4579
|
-
const { stream } = agent.
|
|
4580
|
-
this.
|
|
4581
|
-
requestId,
|
|
4582
|
-
trigger,
|
|
4583
|
-
compiledRequest,
|
|
4584
|
-
artifacts,
|
|
4585
|
-
});
|
|
4586
|
-
const handle = this.driveStream(agent, stream, requestSnapshot, requestId, trigger, attempt, compiledRequest, artifacts);
|
|
3326
|
+
const { stream, request: compiledRequest } = await agent.startStreamWithInjections(tools, injections);
|
|
3327
|
+
const handle = this.driveStream(agent, stream, requestSnapshot, trigger, attempt, compiledRequest);
|
|
4587
3328
|
this.activeStreams.set(agent.name, handle);
|
|
4588
3329
|
}
|
|
4589
3330
|
catch (error) {
|
|
4590
3331
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
4591
|
-
if (trigger?.primarySummaryFallbackRetry) {
|
|
4592
|
-
await this.updatePrimarySummaryRequestRecord(requestId, (record) => ({
|
|
4593
|
-
...record,
|
|
4594
|
-
finalStatus: 'held',
|
|
4595
|
-
fallbackHeldReason: err.message,
|
|
4596
|
-
}));
|
|
4597
|
-
await this.updatePrimarySummaryRequestRecord(trigger.primarySummaryFallbackRetry.originalRequestId, (record) => ({
|
|
4598
|
-
...record,
|
|
4599
|
-
finalStatus: 'held',
|
|
4600
|
-
fallbackStatus: 'held',
|
|
4601
|
-
fallbackHeldReason: err.message,
|
|
4602
|
-
}));
|
|
4603
|
-
this.opsAlert('refusal-held', agent.name, err.message, {
|
|
4604
|
-
data: { requestId, originalRequestId: trigger.primarySummaryFallbackRetry.originalRequestId },
|
|
4605
|
-
});
|
|
4606
|
-
agent.reset();
|
|
4607
|
-
this.settleAgent(agent.name, {
|
|
4608
|
-
stopReason: 'completed',
|
|
4609
|
-
speech: '',
|
|
4610
|
-
});
|
|
4611
|
-
this.eventGate?.onInferenceEnded(agent.name);
|
|
4612
|
-
this.lastInferenceAt.set(agent.name, { ...this.lastInferenceAt.get(agent.name), endedAt: Date.now() });
|
|
4613
|
-
return;
|
|
4614
|
-
}
|
|
4615
3332
|
this.emitTrace({
|
|
4616
3333
|
type: 'inference:failed',
|
|
4617
3334
|
agentName: agent.name,
|
|
@@ -4650,17 +3367,11 @@ export class AgentFramework {
|
|
|
4650
3367
|
}
|
|
4651
3368
|
}
|
|
4652
3369
|
}
|
|
4653
|
-
async driveStream(agent, stream, requestSnapshot,
|
|
3370
|
+
async driveStream(agent, stream, requestSnapshot, trigger, attempt = 0, compiledRequest) {
|
|
4654
3371
|
const startTime = Date.now();
|
|
3372
|
+
const requestId = `${agent.name}-${startTime}-${Math.random().toString(36).slice(2, 8)}`;
|
|
4655
3373
|
const myStreamId = agent.streamId;
|
|
4656
3374
|
let hadToolCalls = false;
|
|
4657
|
-
let executedToolCalls = 0;
|
|
4658
|
-
const primarySummaryConfig = this.primarySummaryFallbackConfig(agent);
|
|
4659
|
-
const primarySummaryRetry = trigger?.primarySummaryFallbackRetry;
|
|
4660
|
-
let primarySummaryQuarantinePersisted = false;
|
|
4661
|
-
const fallbackRetryRequestForLog = primarySummaryRetry && compiledRequest
|
|
4662
|
-
? this.summarizePrimarySummaryRetryRequest(compiledRequest)
|
|
4663
|
-
: compiledRequest ?? { note: 'streaming request' };
|
|
4664
3375
|
// ---- Present-while-acting turn state ---------------------------------
|
|
4665
3376
|
// Output locus for the current CONVERSATIONAL ROUND: resolved lazily and
|
|
4666
3377
|
// kept stable across tool-only rounds. A channel message injected at a
|
|
@@ -4668,7 +3379,7 @@ export class AgentFramework {
|
|
|
4668
3379
|
// single provider inference can now contain several conversations. The
|
|
4669
3380
|
// pin lives in `turnLocusPins` so it survives a context-budget restart. A
|
|
4670
3381
|
// null resolution is not pinned.
|
|
4671
|
-
if (trigger?.reason !== 'context_budget_restart'
|
|
3382
|
+
if (trigger?.reason !== 'context_budget_restart') {
|
|
4672
3383
|
this.turnLocusPins.delete(agent.name);
|
|
4673
3384
|
this.midTurnRoutingResets.delete(agent.name);
|
|
4674
3385
|
}
|
|
@@ -4736,61 +3447,6 @@ export class AgentFramework {
|
|
|
4736
3447
|
}
|
|
4737
3448
|
}
|
|
4738
3449
|
};
|
|
4739
|
-
const persistPrimarySummaryRetryQuarantine = async () => {
|
|
4740
|
-
if (!primarySummaryRetry || primarySummaryQuarantinePersisted || !artifacts)
|
|
4741
|
-
return true;
|
|
4742
|
-
const validation = await this.validatePrimarySummaryRetryMutation(agent, requestId, primarySummaryRetry, compiledRequest, artifacts, 'before_quarantine');
|
|
4743
|
-
if (!validation.ok) {
|
|
4744
|
-
await this.holdPrimarySummaryRetryFamily(agent, requestId, primarySummaryRetry, validation.reason, validation.data, { emitChronicleMarker: false });
|
|
4745
|
-
stream.cancel();
|
|
4746
|
-
agent.reset();
|
|
4747
|
-
this.settleAgent(agent.name, { stopReason: 'completed', speech: '' });
|
|
4748
|
-
return false;
|
|
4749
|
-
}
|
|
4750
|
-
try {
|
|
4751
|
-
const quarantinePrimarySummaryForPrimaryLane = agent.getContextManager().quarantinePrimarySummaryForPrimaryLane;
|
|
4752
|
-
if (typeof quarantinePrimarySummaryForPrimaryLane !== 'function') {
|
|
4753
|
-
throw new Error('Primary summary quarantine is unavailable in the active context strategy');
|
|
4754
|
-
}
|
|
4755
|
-
await quarantinePrimarySummaryForPrimaryLane.call(agent.getContextManager(), artifacts.contract, primarySummaryRetry.candidateSummaries);
|
|
4756
|
-
primarySummaryQuarantinePersisted = true;
|
|
4757
|
-
await this.recordPrimarySummaryRetryQuarantine(primarySummaryRetry.originalRequestId, primarySummaryRetry.intentId);
|
|
4758
|
-
return true;
|
|
4759
|
-
}
|
|
4760
|
-
catch (error) {
|
|
4761
|
-
const reason = 'primary_summary_fallback_quarantine_failed';
|
|
4762
|
-
await this.holdPrimarySummaryRetryFamily(agent, requestId, primarySummaryRetry, reason, {
|
|
4763
|
-
error: error instanceof Error ? error.message : String(error),
|
|
4764
|
-
});
|
|
4765
|
-
stream.cancel();
|
|
4766
|
-
agent.reset();
|
|
4767
|
-
this.settleAgent(agent.name, { stopReason: 'completed', speech: '' });
|
|
4768
|
-
return false;
|
|
4769
|
-
}
|
|
4770
|
-
};
|
|
4771
|
-
const persistPrimarySummaryRetryOutput = async (phase, blocks, participant = agent.name, metadata) => {
|
|
4772
|
-
if (!primarySummaryRetry || !artifacts)
|
|
4773
|
-
return true;
|
|
4774
|
-
if (blocks.length === 0)
|
|
4775
|
-
return true;
|
|
4776
|
-
const validation = await this.validatePrimarySummaryRetryMutation(agent, requestId, primarySummaryRetry, compiledRequest, artifacts, `before_${phase}`);
|
|
4777
|
-
if (!validation.ok) {
|
|
4778
|
-
await this.holdPrimarySummaryRetryFamily(agent, requestId, primarySummaryRetry, validation.reason, validation.data, { emitChronicleMarker: false });
|
|
4779
|
-
stream.cancel();
|
|
4780
|
-
agent.reset();
|
|
4781
|
-
this.settleAgent(agent.name, { stopReason: 'completed', speech: '' });
|
|
4782
|
-
return false;
|
|
4783
|
-
}
|
|
4784
|
-
const messageId = agent.getContextManager().addMessage(participant, blocks, metadata);
|
|
4785
|
-
await this.recordPrimarySummaryRetryOutput(primarySummaryRetry.originalRequestId, primarySummaryRetry.intentId, {
|
|
4786
|
-
phase,
|
|
4787
|
-
contentHash: this.primarySummaryFallbackOutputPhaseHash(blocks),
|
|
4788
|
-
messageId,
|
|
4789
|
-
blockTypes: this.primarySummaryFallbackBlockTypes(blocks),
|
|
4790
|
-
persistedAt: Date.now(),
|
|
4791
|
-
});
|
|
4792
|
-
return true;
|
|
4793
|
-
};
|
|
4794
3450
|
try {
|
|
4795
3451
|
for await (const event of stream) {
|
|
4796
3452
|
this.touchEphemeralRun(agent.name, true);
|
|
@@ -4818,6 +3474,7 @@ export class AgentFramework {
|
|
|
4818
3474
|
case 'tool-calls': {
|
|
4819
3475
|
adoptInjectedRound();
|
|
4820
3476
|
hadToolCalls = true;
|
|
3477
|
+
this.recordEphemeralToolCalls(agent.name, event.calls.length);
|
|
4821
3478
|
this.emitTrace({
|
|
4822
3479
|
type: 'inference:tool_calls_yielded',
|
|
4823
3480
|
agentName: agent.name,
|
|
@@ -4851,30 +3508,7 @@ export class AgentFramework {
|
|
|
4851
3508
|
// Truncated responses yield 'complete' with stop_reason 'max_tokens' directly.
|
|
4852
3509
|
// So all calls here have valid input — {} is legitimate for parameterless tools.
|
|
4853
3510
|
agent.enterWaitingForTools(event.calls, stream);
|
|
4854
|
-
if (!(await persistPrimarySummaryRetryQuarantine())) {
|
|
4855
|
-
return;
|
|
4856
|
-
}
|
|
4857
|
-
if (primarySummaryRetry) {
|
|
4858
|
-
const phase = `tool_round_${event.context.depth ?? 0}`;
|
|
4859
|
-
if (!(await persistPrimarySummaryRetryOutput(phase, assistantBlocks))) {
|
|
4860
|
-
return;
|
|
4861
|
-
}
|
|
4862
|
-
this.pendingAssistantBlocks.delete(agent.name);
|
|
4863
|
-
}
|
|
4864
|
-
executedToolCalls += event.calls.length;
|
|
4865
|
-
this.recordEphemeralToolCalls(agent.name, event.calls.length);
|
|
4866
3511
|
for (const call of event.calls) {
|
|
4867
|
-
if (primarySummaryRetry) {
|
|
4868
|
-
const validation = await this.validatePrimarySummaryRetryMutation(agent, requestId, primarySummaryRetry, compiledRequest, artifacts, `before_tool_dispatch_${call.id}`);
|
|
4869
|
-
if (!validation.ok) {
|
|
4870
|
-
await this.holdPrimarySummaryRetryFamily(agent, requestId, primarySummaryRetry, validation.reason, validation.data, { emitChronicleMarker: false });
|
|
4871
|
-
stream.cancel();
|
|
4872
|
-
agent.reset();
|
|
4873
|
-
this.settleAgent(agent.name, { stopReason: 'completed', speech: '' });
|
|
4874
|
-
return;
|
|
4875
|
-
}
|
|
4876
|
-
await this.recordPrimarySummaryRetryToolDispatch(primarySummaryRetry.originalRequestId, primarySummaryRetry.intentId, call);
|
|
4877
|
-
}
|
|
4878
3512
|
this.dispatchToolCall(agent.name, call);
|
|
4879
3513
|
}
|
|
4880
3514
|
// Speak-while-acting: route THIS round's prose to the locus NOW
|
|
@@ -4956,167 +3590,6 @@ export class AgentFramework {
|
|
|
4956
3590
|
agent.getContextManager().addMessage('user', toolResultContent);
|
|
4957
3591
|
}
|
|
4958
3592
|
}
|
|
4959
|
-
const lastToolIdx = response.content.reduce((last, b, i) => b.type === 'tool_use' || b.type === 'tool_result' ? i : last, -1);
|
|
4960
|
-
const terminalContent = lastToolIdx >= 0
|
|
4961
|
-
? response.content.slice(lastToolIdx + 1)
|
|
4962
|
-
: response.content;
|
|
4963
|
-
const heldAssistantContent = response.content;
|
|
4964
|
-
const visibleAssistantOutput = this.hasVisibleAssistantOutput(response.content);
|
|
4965
|
-
const providerInputTokens = response.usage?.inputTokens ??
|
|
4966
|
-
response.details?.usage?.inputTokens;
|
|
4967
|
-
if (primarySummaryConfig.enabled && compiledRequest && artifacts && response.stopReason === 'refusal') {
|
|
4968
|
-
const stopDetails = response.raw?.response?.stop_details;
|
|
4969
|
-
const category = stopDetails?.category ?? 'unknown';
|
|
4970
|
-
console.error(`[inference-refusal] agent=${agent.name} category=${category}` +
|
|
4971
|
-
(stopDetails?.explanation ? ` explanation=${stopDetails.explanation}` : ''));
|
|
4972
|
-
this.noteRefusal(agent.name, category, response.details?.usage
|
|
4973
|
-
? {
|
|
4974
|
-
input: response.details.usage.inputTokens,
|
|
4975
|
-
output: response.details.usage.outputTokens,
|
|
4976
|
-
}
|
|
4977
|
-
: undefined);
|
|
4978
|
-
const currentRecord = await this.updatePrimarySummaryRequestRecord(requestId, (record) => ({
|
|
4979
|
-
...record,
|
|
4980
|
-
stopReason: response.stopReason,
|
|
4981
|
-
providerInputTokens,
|
|
4982
|
-
visibleAssistantOutput,
|
|
4983
|
-
executedToolCalls,
|
|
4984
|
-
finalStatus: 'refusal',
|
|
4985
|
-
}));
|
|
4986
|
-
const holdReasonData = {
|
|
4987
|
-
category,
|
|
4988
|
-
visibleAssistantOutput,
|
|
4989
|
-
executedToolCalls,
|
|
4990
|
-
candidateLimit: primarySummaryConfig.maxNewSummaries,
|
|
4991
|
-
};
|
|
4992
|
-
if (primarySummaryRetry) {
|
|
4993
|
-
const reason = visibleAssistantOutput
|
|
4994
|
-
? 'primary_summary_fallback_retry_partial_output'
|
|
4995
|
-
: executedToolCalls > 0
|
|
4996
|
-
? 'primary_summary_fallback_retry_tool_already_executed'
|
|
4997
|
-
: 'primary_summary_fallback_retry_refusal';
|
|
4998
|
-
if (visibleAssistantOutput || executedToolCalls > 0) {
|
|
4999
|
-
await this.persistPrimarySummaryHeldOutput(agent, requestId, reason, {
|
|
5000
|
-
stopReason: response.stopReason,
|
|
5001
|
-
providerInputTokens,
|
|
5002
|
-
visibleAssistantOutput,
|
|
5003
|
-
executedToolCalls,
|
|
5004
|
-
assistantBlocks: heldAssistantContent,
|
|
5005
|
-
retryDispatch: primarySummaryRetry,
|
|
5006
|
-
retryPhase: 'refusal_visible_output',
|
|
5007
|
-
holdData: holdReasonData,
|
|
5008
|
-
});
|
|
5009
|
-
}
|
|
5010
|
-
else {
|
|
5011
|
-
await this.holdPrimarySummaryRetryFamily(agent, requestId, primarySummaryRetry, reason, holdReasonData);
|
|
5012
|
-
}
|
|
5013
|
-
}
|
|
5014
|
-
else if (visibleAssistantOutput || executedToolCalls > 0 || !currentRecord?.projection) {
|
|
5015
|
-
const reason = visibleAssistantOutput
|
|
5016
|
-
? 'primary_summary_refusal_partial_output'
|
|
5017
|
-
: executedToolCalls > 0
|
|
5018
|
-
? 'primary_summary_refusal_tool_already_executed'
|
|
5019
|
-
: 'primary_summary_refusal_no_projection';
|
|
5020
|
-
if (visibleAssistantOutput || executedToolCalls > 0) {
|
|
5021
|
-
await this.persistPrimarySummaryHeldOutput(agent, requestId, reason, {
|
|
5022
|
-
stopReason: response.stopReason,
|
|
5023
|
-
providerInputTokens,
|
|
5024
|
-
visibleAssistantOutput,
|
|
5025
|
-
executedToolCalls,
|
|
5026
|
-
assistantBlocks: heldAssistantContent,
|
|
5027
|
-
holdData: holdReasonData,
|
|
5028
|
-
});
|
|
5029
|
-
}
|
|
5030
|
-
else {
|
|
5031
|
-
await this.holdPrimarySummaryRequest(agent, requestId, reason, holdReasonData);
|
|
5032
|
-
}
|
|
5033
|
-
}
|
|
5034
|
-
else {
|
|
5035
|
-
const state = this.readPrimarySummaryFallbackState();
|
|
5036
|
-
const baseline = this.latestCompatibleHealthyPrimaryRequest(currentRecord, state);
|
|
5037
|
-
if (!baseline?.projection) {
|
|
5038
|
-
await this.holdPrimarySummaryRequest(agent, requestId, 'primary_summary_refusal_no_compatible_healthy_baseline', holdReasonData);
|
|
5039
|
-
}
|
|
5040
|
-
else {
|
|
5041
|
-
const baselineKeys = new Set(this.primarySummaryProjectionKeys(baseline.projection));
|
|
5042
|
-
const candidates = currentRecord.projection.selectedSummaries
|
|
5043
|
-
.filter((selection) => !baselineKeys.has(this.primarySummaryIdentityKey(selection.identity)))
|
|
5044
|
-
.map((selection) => selection.identity);
|
|
5045
|
-
if (candidates.length === 0) {
|
|
5046
|
-
await this.holdPrimarySummaryRequest(agent, requestId, 'primary_summary_refusal_no_newly_admitted_candidates', holdReasonData);
|
|
5047
|
-
}
|
|
5048
|
-
else if (candidates.length > primarySummaryConfig.maxNewSummaries) {
|
|
5049
|
-
await this.holdPrimarySummaryRequest(agent, requestId, 'primary_summary_refusal_too_many_candidates', { ...holdReasonData, candidateCount: candidates.length });
|
|
5050
|
-
}
|
|
5051
|
-
else {
|
|
5052
|
-
try {
|
|
5053
|
-
const retry = agent.buildPrimarySummaryRawExpansionRequest(artifacts, candidates, compiledRequest.tools ?? []);
|
|
5054
|
-
const retryRequestHash = this.hashJson(retry.request);
|
|
5055
|
-
const admittedTokens = this.primarySummaryAdmissionBoundTokens(currentRecord.requestInputBoundTokens, retry.request, providerInputTokens);
|
|
5056
|
-
if (admittedTokens > primarySummaryConfig.requestBudgetTokens) {
|
|
5057
|
-
await this.holdPrimarySummaryRequest(agent, requestId, 'primary_summary_refusal_retry_over_budget', {
|
|
5058
|
-
...holdReasonData,
|
|
5059
|
-
admittedTokens,
|
|
5060
|
-
requestBudgetTokens: primarySummaryConfig.requestBudgetTokens,
|
|
5061
|
-
});
|
|
5062
|
-
}
|
|
5063
|
-
else {
|
|
5064
|
-
const intentId = `${requestId}:fallback:${Math.random().toString(36).slice(2, 8)}`;
|
|
5065
|
-
await this.updatePrimarySummaryRequestRecord(requestId, (record) => ({
|
|
5066
|
-
...record,
|
|
5067
|
-
fallbackIntent: {
|
|
5068
|
-
intentId,
|
|
5069
|
-
createdAt: Date.now(),
|
|
5070
|
-
baselineRequestId: baseline.requestId,
|
|
5071
|
-
candidateSummaries: candidates,
|
|
5072
|
-
retryRequestHash,
|
|
5073
|
-
retryCompleteBoundTokens: admittedTokens,
|
|
5074
|
-
requestBudgetTokens: primarySummaryConfig.requestBudgetTokens,
|
|
5075
|
-
},
|
|
5076
|
-
fallbackStatus: 'pending',
|
|
5077
|
-
}));
|
|
5078
|
-
this.pendingRequests.push({
|
|
5079
|
-
agentName: agent.name,
|
|
5080
|
-
reason: 'primary-summary-fallback-retry',
|
|
5081
|
-
source: 'framework',
|
|
5082
|
-
timestamp: Date.now(),
|
|
5083
|
-
channelId: trigger?.channelId,
|
|
5084
|
-
primarySummaryFallbackRetry: {
|
|
5085
|
-
originalRequestId: requestId,
|
|
5086
|
-
baselineRequestId: baseline.requestId,
|
|
5087
|
-
intentId,
|
|
5088
|
-
branch: {
|
|
5089
|
-
id: currentRecord.branch.id,
|
|
5090
|
-
generation: currentRecord.branch.generation,
|
|
5091
|
-
},
|
|
5092
|
-
candidateSummaries: candidates,
|
|
5093
|
-
originalArtifacts: artifacts,
|
|
5094
|
-
originalProjectionKeys: this.primarySummaryProjectionKeys(currentRecord.projection),
|
|
5095
|
-
expectedRetryRequestHash: retryRequestHash,
|
|
5096
|
-
requestBudgetTokens: primarySummaryConfig.requestBudgetTokens,
|
|
5097
|
-
originalRequestInputBoundTokens: currentRecord.requestInputBoundTokens,
|
|
5098
|
-
originalProviderInputTokens: providerInputTokens,
|
|
5099
|
-
},
|
|
5100
|
-
});
|
|
5101
|
-
}
|
|
5102
|
-
}
|
|
5103
|
-
catch (error) {
|
|
5104
|
-
await this.holdPrimarySummaryRequest(agent, requestId, 'primary_summary_refusal_retry_build_failed', {
|
|
5105
|
-
...holdReasonData,
|
|
5106
|
-
error: error instanceof Error ? error.message : String(error),
|
|
5107
|
-
});
|
|
5108
|
-
}
|
|
5109
|
-
}
|
|
5110
|
-
}
|
|
5111
|
-
}
|
|
5112
|
-
agent.reset();
|
|
5113
|
-
this.eventGate?.onInferenceEnded(agent.name);
|
|
5114
|
-
this.settleAgent(agent.name, { stopReason: 'completed', speech: '' });
|
|
5115
|
-
continue;
|
|
5116
|
-
}
|
|
5117
|
-
if (!(await persistPrimarySummaryRetryQuarantine())) {
|
|
5118
|
-
return;
|
|
5119
|
-
}
|
|
5120
3593
|
// Add assistant response to context.
|
|
5121
3594
|
// If we had tool calls, each round's blocks (thinking + text +
|
|
5122
3595
|
// tool_use) were already stored as pendingAssistantBlocks and
|
|
@@ -5125,55 +3598,17 @@ export class AgentFramework {
|
|
|
5125
3598
|
// double-store earlier rounds' text/thinking AND detach signed
|
|
5126
3599
|
// thinking blocks from their tool_use turn (the API requires
|
|
5127
3600
|
// thinking to precede tool_use in the same assistant turn).
|
|
3601
|
+
const lastToolIdx = response.content.reduce((last, b, i) => b.type === 'tool_use' || b.type === 'tool_result' ? i : last, -1);
|
|
3602
|
+
const terminalContent = lastToolIdx >= 0
|
|
3603
|
+
? response.content.slice(lastToolIdx + 1)
|
|
3604
|
+
: response.content;
|
|
5128
3605
|
if (lastToolIdx >= 0) {
|
|
5129
3606
|
if (terminalContent.length > 0) {
|
|
5130
|
-
|
|
5131
|
-
const retryRecord = await this.getPrimarySummaryRequestRecord(requestId);
|
|
5132
|
-
const terminalMetadata = retryRecord
|
|
5133
|
-
? this.buildPrimarySummarySettlementMetadata(retryRecord, {
|
|
5134
|
-
outcome: 'success',
|
|
5135
|
-
stopReason: response.stopReason,
|
|
5136
|
-
providerInputTokens,
|
|
5137
|
-
visibleAssistantOutput,
|
|
5138
|
-
executedToolCalls,
|
|
5139
|
-
entryIndex: 0,
|
|
5140
|
-
entryCount: 1,
|
|
5141
|
-
role: 'assistant',
|
|
5142
|
-
kind: 'assistant_output',
|
|
5143
|
-
})
|
|
5144
|
-
: undefined;
|
|
5145
|
-
if (!(await persistPrimarySummaryRetryOutput('terminal', terminalContent, agent.name, terminalMetadata))) {
|
|
5146
|
-
return;
|
|
5147
|
-
}
|
|
5148
|
-
}
|
|
5149
|
-
else {
|
|
5150
|
-
agent.addAssistantResponse(terminalContent);
|
|
5151
|
-
}
|
|
3607
|
+
agent.addAssistantResponse(terminalContent);
|
|
5152
3608
|
}
|
|
5153
3609
|
}
|
|
5154
3610
|
else {
|
|
5155
|
-
|
|
5156
|
-
const retryRecord = await this.getPrimarySummaryRequestRecord(requestId);
|
|
5157
|
-
const terminalMetadata = retryRecord && terminalContent.length > 0
|
|
5158
|
-
? this.buildPrimarySummarySettlementMetadata(retryRecord, {
|
|
5159
|
-
outcome: 'success',
|
|
5160
|
-
stopReason: response.stopReason,
|
|
5161
|
-
providerInputTokens,
|
|
5162
|
-
visibleAssistantOutput,
|
|
5163
|
-
executedToolCalls,
|
|
5164
|
-
entryIndex: 0,
|
|
5165
|
-
entryCount: 1,
|
|
5166
|
-
role: 'assistant',
|
|
5167
|
-
kind: 'assistant_output',
|
|
5168
|
-
})
|
|
5169
|
-
: undefined;
|
|
5170
|
-
if (!(await persistPrimarySummaryRetryOutput('terminal', terminalContent, agent.name, terminalMetadata))) {
|
|
5171
|
-
return;
|
|
5172
|
-
}
|
|
5173
|
-
}
|
|
5174
|
-
else {
|
|
5175
|
-
agent.addAssistantResponse(terminalContent);
|
|
5176
|
-
}
|
|
3611
|
+
agent.addAssistantResponse(terminalContent);
|
|
5177
3612
|
}
|
|
5178
3613
|
// Run afterInference hooks (no-op if no MCPL servers)
|
|
5179
3614
|
if (this.hookOrchestrator) {
|
|
@@ -5228,28 +3663,6 @@ export class AgentFramework {
|
|
|
5228
3663
|
cacheRead: du.cacheReadTokens,
|
|
5229
3664
|
}
|
|
5230
3665
|
: undefined;
|
|
5231
|
-
if (primarySummaryRetry && artifacts) {
|
|
5232
|
-
const validation = await this.validatePrimarySummaryRetryMutation(agent, requestId, primarySummaryRetry, compiledRequest, artifacts, 'before_success_markers');
|
|
5233
|
-
if (!validation.ok) {
|
|
5234
|
-
await this.holdPrimarySummaryRetryFamily(agent, requestId, primarySummaryRetry, validation.reason, validation.data, { emitChronicleMarker: false });
|
|
5235
|
-
return;
|
|
5236
|
-
}
|
|
5237
|
-
}
|
|
5238
|
-
if (primarySummaryConfig.enabled) {
|
|
5239
|
-
if (primarySummaryRetry) {
|
|
5240
|
-
await this.markPrimarySummaryRetrySuccess(requestId, primarySummaryRetry, response.stopReason, providerInputTokens, visibleAssistantOutput, executedToolCalls);
|
|
5241
|
-
}
|
|
5242
|
-
else {
|
|
5243
|
-
await this.updatePrimarySummaryRequestRecord(requestId, (record) => ({
|
|
5244
|
-
...record,
|
|
5245
|
-
stopReason: response.stopReason,
|
|
5246
|
-
providerInputTokens,
|
|
5247
|
-
visibleAssistantOutput,
|
|
5248
|
-
executedToolCalls,
|
|
5249
|
-
finalStatus: 'success',
|
|
5250
|
-
}));
|
|
5251
|
-
}
|
|
5252
|
-
}
|
|
5253
3666
|
// Reset agent state before emitting inference:completed. Traces are
|
|
5254
3667
|
// observability-only, but external synchronous listeners should
|
|
5255
3668
|
// still see the terminal state at the terminal trace boundary.
|
|
@@ -5285,10 +3698,8 @@ export class AgentFramework {
|
|
|
5285
3698
|
agentName: agent.name,
|
|
5286
3699
|
requestId,
|
|
5287
3700
|
success: true,
|
|
5288
|
-
request:
|
|
5289
|
-
response:
|
|
5290
|
-
? this.summarizePrimarySummaryRetryResponse(response)
|
|
5291
|
-
: response.raw ?? { note: 'streaming response' },
|
|
3701
|
+
request: compiledRequest ?? { note: 'streaming request' },
|
|
3702
|
+
response: response.raw ?? { note: 'streaming response' },
|
|
5292
3703
|
durationMs,
|
|
5293
3704
|
tokenUsage,
|
|
5294
3705
|
stopReason: response.stopReason,
|
|
@@ -5481,22 +3892,12 @@ export class AgentFramework {
|
|
|
5481
3892
|
requestId,
|
|
5482
3893
|
success: false,
|
|
5483
3894
|
error: err.message,
|
|
5484
|
-
request:
|
|
3895
|
+
request: compiledRequest ?? { note: 'streaming request failed' },
|
|
5485
3896
|
durationMs,
|
|
5486
3897
|
});
|
|
5487
3898
|
// Only reset + retry if this is still the active stream
|
|
5488
3899
|
if (agent.streamId !== myStreamId)
|
|
5489
3900
|
break;
|
|
5490
|
-
if (primarySummaryRetry) {
|
|
5491
|
-
await this.holdPrimarySummaryRetryFamily(agent, requestId, primarySummaryRetry, 'primary_summary_fallback_retry_stream_error', { error: err.message });
|
|
5492
|
-
agent.reset();
|
|
5493
|
-
this.eventGate?.onInferenceEnded(agent.name);
|
|
5494
|
-
this.settleAgent(agent.name, {
|
|
5495
|
-
stopReason: 'completed',
|
|
5496
|
-
speech: '',
|
|
5497
|
-
});
|
|
5498
|
-
return;
|
|
5499
|
-
}
|
|
5500
3901
|
agent.reset();
|
|
5501
3902
|
const action = this.errorPolicy.onInferenceError(err, agent.name, attempt);
|
|
5502
3903
|
if (action.retry) {
|
|
@@ -5535,11 +3936,6 @@ export class AgentFramework {
|
|
|
5535
3936
|
// reject an ephemeral's promise mid-run and bump the failure
|
|
5536
3937
|
// streak). Gate release + stream teardown happen in `finally`.
|
|
5537
3938
|
if (this.frameworkCancelledStreams.delete(`${agent.name}:${myStreamId}`)) {
|
|
5538
|
-
if (primarySummaryRetry) {
|
|
5539
|
-
if (!(await this.primarySummaryRetryFamilyAlreadySettled(requestId, primarySummaryRetry))) {
|
|
5540
|
-
await this.holdPrimarySummaryRetryFamily(agent, requestId, primarySummaryRetry, 'primary_summary_fallback_retry_cancelled_before_success', { frameworkCancelled: true });
|
|
5541
|
-
}
|
|
5542
|
-
}
|
|
5543
3939
|
this.eventGate?.onInferenceEnded(agent.name);
|
|
5544
3940
|
return;
|
|
5545
3941
|
}
|
|
@@ -5548,33 +3944,6 @@ export class AgentFramework {
|
|
|
5548
3944
|
// may have already started a new stream, bumping streamId)
|
|
5549
3945
|
if (agent.streamId === myStreamId) {
|
|
5550
3946
|
const durationMs = Date.now() - startTime;
|
|
5551
|
-
if (primarySummaryRetry) {
|
|
5552
|
-
await this.holdPrimarySummaryRetryFamily(agent, requestId, primarySummaryRetry, `stream_aborted:${reason}`, { reason }, { stopReason: 'abort' });
|
|
5553
|
-
agent.reset();
|
|
5554
|
-
this.eventGate?.onInferenceEnded(agent.name);
|
|
5555
|
-
this.logInference({
|
|
5556
|
-
timestamp: startTime,
|
|
5557
|
-
agentName: agent.name,
|
|
5558
|
-
requestId,
|
|
5559
|
-
success: false,
|
|
5560
|
-
error: `Stream aborted: ${reason}`,
|
|
5561
|
-
request: fallbackRetryRequestForLog,
|
|
5562
|
-
durationMs,
|
|
5563
|
-
});
|
|
5564
|
-
this.settleAgent(agent.name, {
|
|
5565
|
-
stopReason: 'completed',
|
|
5566
|
-
speech: '',
|
|
5567
|
-
});
|
|
5568
|
-
return;
|
|
5569
|
-
}
|
|
5570
|
-
if (primarySummaryConfig.enabled) {
|
|
5571
|
-
await this.updatePrimarySummaryRequestRecord(requestId, (record) => ({
|
|
5572
|
-
...record,
|
|
5573
|
-
stopReason: 'abort',
|
|
5574
|
-
finalStatus: 'error',
|
|
5575
|
-
fallbackHeldReason: `stream_aborted:${reason}`,
|
|
5576
|
-
}));
|
|
5577
|
-
}
|
|
5578
3947
|
agent.reset();
|
|
5579
3948
|
this.settleAgent(agent.name, {
|
|
5580
3949
|
stopReason: 'exhausted',
|
|
@@ -5598,7 +3967,7 @@ export class AgentFramework {
|
|
|
5598
3967
|
requestId,
|
|
5599
3968
|
success: false,
|
|
5600
3969
|
error: `Stream aborted: ${reason}`,
|
|
5601
|
-
request:
|
|
3970
|
+
request: compiledRequest ?? { note: 'streaming request aborted' },
|
|
5602
3971
|
durationMs,
|
|
5603
3972
|
});
|
|
5604
3973
|
this.eventGate?.onInferenceEnded(agent.name);
|
|
@@ -5644,58 +4013,12 @@ export class AgentFramework {
|
|
|
5644
4013
|
// inference:exhausted so ephemeral agent promises can settle.
|
|
5645
4014
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
5646
4015
|
const durationMs = Date.now() - startTime;
|
|
5647
|
-
if (primarySummaryRetry) {
|
|
5648
|
-
if (await this.primarySummaryRetryAlreadySucceeded(requestId, primarySummaryRetry)) {
|
|
5649
|
-
this.logInference({
|
|
5650
|
-
timestamp: startTime,
|
|
5651
|
-
agentName: agent.name,
|
|
5652
|
-
requestId,
|
|
5653
|
-
success: true,
|
|
5654
|
-
request: fallbackRetryRequestForLog,
|
|
5655
|
-
response: { kind: 'primary_summary_fallback_retry', postSuccessError: err.message },
|
|
5656
|
-
durationMs,
|
|
5657
|
-
});
|
|
5658
|
-
agent.reset();
|
|
5659
|
-
this.eventGate?.onInferenceEnded(agent.name);
|
|
5660
|
-
this.settleAgent(agent.name, {
|
|
5661
|
-
stopReason: 'completed',
|
|
5662
|
-
speech: '',
|
|
5663
|
-
});
|
|
5664
|
-
return;
|
|
5665
|
-
}
|
|
5666
|
-
await this.holdPrimarySummaryRetryFamily(agent, requestId, primarySummaryRetry, 'primary_summary_fallback_retry_stream_threw', { error: err.message });
|
|
5667
|
-
}
|
|
5668
|
-
else if (primarySummaryConfig.enabled) {
|
|
5669
|
-
await this.updatePrimarySummaryRequestRecord(requestId, (record) => ({
|
|
5670
|
-
...record,
|
|
5671
|
-
finalStatus: 'error',
|
|
5672
|
-
fallbackHeldReason: err.message,
|
|
5673
|
-
}));
|
|
5674
|
-
}
|
|
5675
4016
|
this.emitTrace({
|
|
5676
4017
|
type: 'inference:failed',
|
|
5677
4018
|
agentName: agent.name,
|
|
5678
4019
|
error: err.message,
|
|
5679
4020
|
stack: err.stack,
|
|
5680
4021
|
});
|
|
5681
|
-
if (primarySummaryRetry) {
|
|
5682
|
-
this.logInference({
|
|
5683
|
-
timestamp: startTime,
|
|
5684
|
-
agentName: agent.name,
|
|
5685
|
-
requestId,
|
|
5686
|
-
success: false,
|
|
5687
|
-
error: `Stream threw: ${err.message}`,
|
|
5688
|
-
request: fallbackRetryRequestForLog,
|
|
5689
|
-
durationMs,
|
|
5690
|
-
});
|
|
5691
|
-
agent.reset();
|
|
5692
|
-
this.eventGate?.onInferenceEnded(agent.name);
|
|
5693
|
-
this.settleAgent(agent.name, {
|
|
5694
|
-
stopReason: 'completed',
|
|
5695
|
-
speech: '',
|
|
5696
|
-
});
|
|
5697
|
-
return;
|
|
5698
|
-
}
|
|
5699
4022
|
this.settleAgent(agent.name, {
|
|
5700
4023
|
stopReason: 'exhausted',
|
|
5701
4024
|
speech: '',
|
|
@@ -5716,7 +4039,7 @@ export class AgentFramework {
|
|
|
5716
4039
|
requestId,
|
|
5717
4040
|
success: false,
|
|
5718
4041
|
error: `Stream threw: ${err.message}`,
|
|
5719
|
-
request:
|
|
4042
|
+
request: compiledRequest ?? { note: 'streaming request threw' },
|
|
5720
4043
|
durationMs,
|
|
5721
4044
|
});
|
|
5722
4045
|
agent.reset();
|
|
@@ -5737,7 +4060,6 @@ export class AgentFramework {
|
|
|
5737
4060
|
this.channelRegistry?.stopTyping();
|
|
5738
4061
|
this.frameworkCancelledStreams.delete(`${agent.name}:${myStreamId}`);
|
|
5739
4062
|
this.activeStreams.delete(agent.name);
|
|
5740
|
-
this.activeStreamContexts.delete(agent.name);
|
|
5741
4063
|
this.pendingAssistantBlocks.delete(agent.name);
|
|
5742
4064
|
// A conversation fork whose TTL closure turn just finished is done for
|
|
5743
4065
|
// good — dispose it so the agent map doesn't grow monotonically.
|
|
@@ -6224,22 +4546,12 @@ export class AgentFramework {
|
|
|
6224
4546
|
* work, per-agent status + last inference activity. Cheap and read-only.
|
|
6225
4547
|
*/
|
|
6226
4548
|
healthSnapshot() {
|
|
6227
|
-
const primarySummary = this.readPrimarySummaryFallbackState();
|
|
6228
4549
|
return {
|
|
6229
4550
|
at: new Date().toISOString(),
|
|
6230
4551
|
uptimeSec: Math.round(process.uptime()),
|
|
6231
4552
|
gate: this.eventGate?.inferenceDiagnostics() ?? null,
|
|
6232
4553
|
pendingRequests: this.pendingRequests.length,
|
|
6233
4554
|
activeStreams: [...this.activeStreams.keys()],
|
|
6234
|
-
primarySummaryFallback: {
|
|
6235
|
-
requests: primarySummary.requests.length,
|
|
6236
|
-
pendingDispatches: primarySummary.requests.filter((record) => record.finalStatus === 'pending').length,
|
|
6237
|
-
unresolvedIntents: primarySummary.requests.filter((record) => record.fallbackStatus === 'pending').length,
|
|
6238
|
-
held: primarySummary.requests.filter((record) => record.finalStatus === 'held' ||
|
|
6239
|
-
record.finalStatus === 'timeout_unknown' ||
|
|
6240
|
-
record.fallbackStatus === 'held' ||
|
|
6241
|
-
record.fallbackStatus === 'timeout_unknown').length,
|
|
6242
|
-
},
|
|
6243
4555
|
agents: [...this.agents.entries()].map(([name, agent]) => ({
|
|
6244
4556
|
name,
|
|
6245
4557
|
status: agent.state.status,
|