@ouro.bot/cli 0.1.0-alpha.793 → 0.1.0-alpha.794
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/changelog.json +7 -0
- package/deploy/unraid/sanctuary.ouro/bundle-meta.json +1 -1
- package/deploy/unraid/sanctuary.xml +1 -1
- package/dist/heart/core.js +1065 -1050
- package/dist/senses/sanctuary-media-catalog-contract.js +12 -3
- package/dist/senses/shared-turn.js +43 -17
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
package/dist/heart/core.js
CHANGED
|
@@ -1127,6 +1127,11 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
1127
1127
|
let providerIterations = 0;
|
|
1128
1128
|
const requiredToolCallNames = [...new Set(options?.requiredToolCalls?.names ?? [])];
|
|
1129
1129
|
const dispatchedRequiredToolCalls = new Set();
|
|
1130
|
+
const requiredCorrectionMarker = Symbol("requiredCorrection");
|
|
1131
|
+
const messagesWithoutRequiredCorrections = () => messages.filter((message) => message[requiredCorrectionMarker] !== true);
|
|
1132
|
+
const removeRequiredCorrections = () => {
|
|
1133
|
+
messages.splice(0, messages.length, ...messagesWithoutRequiredCorrections());
|
|
1134
|
+
};
|
|
1130
1135
|
const pendingRequiredToolCalls = () => {
|
|
1131
1136
|
const missing = requiredToolCallNames.filter((name) => !dispatchedRequiredToolCalls.has(name));
|
|
1132
1137
|
return missing.length > 0
|
|
@@ -1136,7 +1141,12 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
1136
1141
|
const queueRequiredCorrection = (message, limitContext) => {
|
|
1137
1142
|
if (providerIterations >= exports.MAX_PROVIDER_ITERATIONS)
|
|
1138
1143
|
throw new Error(`provider iteration limit exhausted at response ${exports.MAX_PROVIDER_ITERATIONS} ${limitContext}`);
|
|
1139
|
-
|
|
1144
|
+
const correction = {
|
|
1145
|
+
role: "user",
|
|
1146
|
+
content: message,
|
|
1147
|
+
[requiredCorrectionMarker]: true,
|
|
1148
|
+
};
|
|
1149
|
+
messages.push(correction);
|
|
1140
1150
|
providerRuntime.resetTurnState(messages);
|
|
1141
1151
|
};
|
|
1142
1152
|
const toolLoopState = (0, tool_loop_1.createToolLoopState)();
|
|
@@ -1211,307 +1221,398 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
1211
1221
|
// Rebase provider-owned turn state from canonical messages at user-turn start.
|
|
1212
1222
|
// This prevents stale provider caches from replaying prior-turn context.
|
|
1213
1223
|
providerRuntime.resetTurnState(messages);
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
&& habitSession.toolPolicy.
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
const forcedHistoricalToolNames = new Set(unresolvedHistoricalEffects
|
|
1261
|
-
.map((effect) => effect.name)
|
|
1262
|
-
.filter((name) => candidateToolNames.has(name)));
|
|
1263
|
-
const forcingHistoricalEffect = forcedHistoricalToolNames.size > 0;
|
|
1264
|
-
const activeTools = forcingHistoricalEffect
|
|
1265
|
-
? candidateActiveTools.filter((tool) => forcedHistoricalToolNames.has(tool.function.name))
|
|
1266
|
-
: candidateActiveTools;
|
|
1267
|
-
const activeToolNames = new Set(activeTools.map((tool) => tool.function.name));
|
|
1268
|
-
const steeringFollowUps = options?.drainSteeringFollowUps?.() ?? [];
|
|
1269
|
-
if (steeringFollowUps.length > 0) {
|
|
1270
|
-
const hasSupersedingFollowUp = steeringFollowUps.some((followUp) => followUp.effect === "clear_and_supersede");
|
|
1271
|
-
if (hasSupersedingFollowUp) {
|
|
1272
|
-
mustResolveBeforeHandoffActive = false;
|
|
1273
|
-
options?.setMustResolveBeforeHandoff?.(false);
|
|
1274
|
-
outcome = "superseded";
|
|
1275
|
-
break;
|
|
1224
|
+
try {
|
|
1225
|
+
while (!done) {
|
|
1226
|
+
// Channel-based tool filtering:
|
|
1227
|
+
// - Private runtime: exclude send_message (delivery via surface), observe (no one to observe)
|
|
1228
|
+
// - All outward channels (1:1, group, reaction): observe available
|
|
1229
|
+
//
|
|
1230
|
+
// ponder, settle/rest, surface, and observe are always assembled based on channel context.
|
|
1231
|
+
// ponder is available in ALL channels (outer: think privately, inner: keep turning).
|
|
1232
|
+
// Private runtime gets restTool instead of settleTool (rest = end turn, gated by attention queue).
|
|
1233
|
+
// toolChoiceRequired only controls whether tool_choice: "required" is set in the API call.
|
|
1234
|
+
const isPrivateRuntimeChannel = channel === "inner";
|
|
1235
|
+
const privateRuntimeHabitCanSendMessage = isPrivateRuntimeChannel
|
|
1236
|
+
&& habitSession?.toolPolicy.outwardMessagingAllowed === true
|
|
1237
|
+
&& habitSession.toolPolicy.grantedTools.includes("send_message");
|
|
1238
|
+
const privateRuntimeHabitCanSurface = isPrivateRuntimeChannel
|
|
1239
|
+
&& (!habitSession || (habitSession.toolPolicy.outwardMessagingAllowed === true
|
|
1240
|
+
&& habitSession.toolPolicy.grantedTools.includes("surface")));
|
|
1241
|
+
const filteredBaseTools = isPrivateRuntimeChannel
|
|
1242
|
+
? baseTools.filter((t) => privateRuntimeHabitCanSendMessage || t.function.name !== "send_message")
|
|
1243
|
+
: baseTools;
|
|
1244
|
+
const unscopedOrdinaryActiveTools = [
|
|
1245
|
+
...filteredBaseTools,
|
|
1246
|
+
...(augmentedToolContext?.noSend === true ? [] : [tools_1.ponderTool]),
|
|
1247
|
+
...(isPrivateRuntimeChannel && privateRuntimeHabitCanSurface ? [tools_2.surfaceToolDef] : []),
|
|
1248
|
+
...(isPrivateRuntimeChannel ? [tools_1.restTool] : []),
|
|
1249
|
+
...(!isPrivateRuntimeChannel ? [tools_1.observeTool] : []),
|
|
1250
|
+
...(!isPrivateRuntimeChannel ? [tools_1.settleTool] : []),
|
|
1251
|
+
...(isChatStyleChannel(channel ?? "") ? [tools_1.speakTool] : []),
|
|
1252
|
+
];
|
|
1253
|
+
const ordinaryActiveTools = relationshipToolNames
|
|
1254
|
+
? unscopedOrdinaryActiveTools.filter((tool) => relationshipToolNames.includes(tool.function.name))
|
|
1255
|
+
: unscopedOrdinaryActiveTools;
|
|
1256
|
+
const candidateActiveTools = options?.toolProfile === "sanctuary-health-private"
|
|
1257
|
+
? (() => {
|
|
1258
|
+
const sendTools = baseTools.filter((tool) => tool.function.name === "send_message");
|
|
1259
|
+
if (channel !== "inner" || sendTools.length !== 1 || baseTools.length !== 1) {
|
|
1260
|
+
throw new Error("sanctuary-health-private requires inner channel with exactly one canonical send_message definition");
|
|
1261
|
+
}
|
|
1262
|
+
return [sendTools[0], tools_1.restTool];
|
|
1263
|
+
})()
|
|
1264
|
+
: ordinaryActiveTools;
|
|
1265
|
+
const candidateToolNames = new Set(candidateActiveTools.map((tool) => tool.function.name));
|
|
1266
|
+
const unadvertisedRequiredTool = requiredToolCallNames.find((name) => !candidateToolNames.has(name));
|
|
1267
|
+
if (unadvertisedRequiredTool) {
|
|
1268
|
+
(0, runtime_1.emitNervesEvent)({ level: "error", component: "engine", event: "engine.required_tool_unadvertised", message: "required tool is not advertised for the active channel", meta: { toolName: unadvertisedRequiredTool, channel: String(channel) } });
|
|
1269
|
+
throw new Error(`required tool is not advertised for this channel: ${unadvertisedRequiredTool}`);
|
|
1276
1270
|
}
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1271
|
+
const forcedHistoricalToolNames = new Set(unresolvedHistoricalEffects
|
|
1272
|
+
.map((effect) => effect.name)
|
|
1273
|
+
.filter((name) => candidateToolNames.has(name)));
|
|
1274
|
+
const forcingHistoricalEffect = forcedHistoricalToolNames.size > 0;
|
|
1275
|
+
const activeTools = forcingHistoricalEffect
|
|
1276
|
+
? candidateActiveTools.filter((tool) => forcedHistoricalToolNames.has(tool.function.name))
|
|
1277
|
+
: candidateActiveTools;
|
|
1278
|
+
const activeToolNames = new Set(activeTools.map((tool) => tool.function.name));
|
|
1279
|
+
const steeringFollowUps = options?.drainSteeringFollowUps?.() ?? [];
|
|
1280
|
+
if (steeringFollowUps.length > 0) {
|
|
1281
|
+
const hasSupersedingFollowUp = steeringFollowUps.some((followUp) => followUp.effect === "clear_and_supersede");
|
|
1282
|
+
if (hasSupersedingFollowUp) {
|
|
1283
|
+
mustResolveBeforeHandoffActive = false;
|
|
1284
|
+
options?.setMustResolveBeforeHandoff?.(false);
|
|
1285
|
+
outcome = "superseded";
|
|
1286
|
+
break;
|
|
1287
|
+
}
|
|
1288
|
+
if (steeringFollowUps.some((followUp) => followUp.effect === "set_no_handoff")) {
|
|
1289
|
+
mustResolveBeforeHandoffActive = true;
|
|
1290
|
+
options?.setMustResolveBeforeHandoff?.(true);
|
|
1291
|
+
}
|
|
1292
|
+
sawSteeringFollowUp = true;
|
|
1293
|
+
for (const followUp of steeringFollowUps) {
|
|
1294
|
+
messages.push({ role: "user", content: followUp.text });
|
|
1295
|
+
}
|
|
1296
|
+
providerRuntime.resetTurnState(messages);
|
|
1280
1297
|
}
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1298
|
+
// Yield so pending I/O (stdin Ctrl-C) can be processed between iterations
|
|
1299
|
+
await new Promise((r) => setImmediate(r));
|
|
1300
|
+
if (signal?.aborted) {
|
|
1301
|
+
outcome = "aborted";
|
|
1302
|
+
break;
|
|
1284
1303
|
}
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1304
|
+
try {
|
|
1305
|
+
const turnCallbackBufferRef = { current: null };
|
|
1306
|
+
const callProviderTurn = async () => {
|
|
1307
|
+
callbacks.onModelStart();
|
|
1308
|
+
turnCallbackBufferRef.current = habitSession
|
|
1309
|
+
? createHabitCallbackBuffer(callbacks)
|
|
1310
|
+
: callbacks.settleOutputMode === "final_only"
|
|
1311
|
+
? createFinalOnlyTextBuffer(callbacks)
|
|
1312
|
+
: null;
|
|
1313
|
+
try {
|
|
1314
|
+
const promptBudget = (0, prompt_budget_1.applyPromptBudget)({
|
|
1315
|
+
messages,
|
|
1316
|
+
requiredPromptEvidence: options?.requiredPromptEvidence,
|
|
1317
|
+
provider: providerRuntime.id,
|
|
1318
|
+
model: providerRuntime.model,
|
|
1319
|
+
contextWindowTokens: (0, config_1.getContextConfig)().maxTokens,
|
|
1320
|
+
});
|
|
1321
|
+
if (promptBudget.status !== "within_budget") {
|
|
1322
|
+
messages.splice(0, messages.length, ...promptBudget.messages);
|
|
1323
|
+
providerRuntime.resetTurnState(messages);
|
|
1324
|
+
}
|
|
1325
|
+
return await providerRuntime.streamTurn({
|
|
1326
|
+
messages,
|
|
1327
|
+
activeTools,
|
|
1328
|
+
callbacks: turnCallbackBufferRef.current?.callbacks ?? callbacks,
|
|
1329
|
+
signal,
|
|
1330
|
+
traceId,
|
|
1331
|
+
toolChoiceRequired: forcingHistoricalEffect || toolChoiceRequired,
|
|
1332
|
+
reasoningEffort: currentReasoningEffort,
|
|
1333
|
+
eagerSettleStreaming: true,
|
|
1334
|
+
systemPrompt: structuredSystemPrompt,
|
|
1335
|
+
});
|
|
1336
|
+
}
|
|
1337
|
+
catch (error) {
|
|
1338
|
+
turnCallbackBufferRef.current?.discard();
|
|
1339
|
+
turnCallbackBufferRef.current = null;
|
|
1340
|
+
if (signal?.aborted)
|
|
1341
|
+
throw new provider_attempt_1.ProviderAttemptAbortError();
|
|
1342
|
+
throw error;
|
|
1313
1343
|
}
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1344
|
+
};
|
|
1345
|
+
const callProviderTurnWithOverflowRecovery = async () => {
|
|
1346
|
+
try {
|
|
1347
|
+
return await callProviderTurn();
|
|
1348
|
+
}
|
|
1349
|
+
catch (error) {
|
|
1350
|
+
if (error instanceof provider_attempt_1.ProviderAttemptAbortError)
|
|
1351
|
+
throw error;
|
|
1352
|
+
if (isContextOverflow(error) && !overflowRetried) {
|
|
1353
|
+
overflowRetried = true;
|
|
1354
|
+
stripLastToolCalls(messages);
|
|
1355
|
+
stripLastToolCalls(generatedMessages);
|
|
1356
|
+
const { maxTokens, contextMargin } = (0, config_1.getContextConfig)();
|
|
1357
|
+
const trimmed = (0, context_1.trimMessages)(messages, maxTokens, contextMargin, maxTokens * 2);
|
|
1358
|
+
const requiredEvidence = options?.requiredPromptEvidence;
|
|
1359
|
+
const requiredMessages = new Set([
|
|
1360
|
+
...(requiredEvidence?.verifiedPredecessorMessage ? [requiredEvidence.verifiedPredecessorMessage] : []),
|
|
1361
|
+
...(requiredEvidence?.currentUserMessage ? [requiredEvidence.currentUserMessage] : []),
|
|
1362
|
+
]);
|
|
1363
|
+
const trimmedMessages = new Set(trimmed);
|
|
1364
|
+
const overflowRetryMessages = requiredMessages.size === 0
|
|
1365
|
+
? trimmed
|
|
1366
|
+
: messages.filter((message) => trimmedMessages.has(message) || requiredMessages.has(message));
|
|
1367
|
+
messages.splice(0, messages.length, ...overflowRetryMessages);
|
|
1368
|
+
providerRuntime.resetTurnState(messages);
|
|
1369
|
+
callbacks.onError(new Error("context trimmed, retrying..."), "transient");
|
|
1370
|
+
return callProviderTurn();
|
|
1371
|
+
}
|
|
1372
|
+
throw error;
|
|
1373
|
+
}
|
|
1374
|
+
};
|
|
1375
|
+
const attempt = await (0, provider_attempt_1.runProviderAttempt)({
|
|
1376
|
+
operation: "turn",
|
|
1377
|
+
provider: providerRuntime.id,
|
|
1378
|
+
model: providerRuntime.model,
|
|
1379
|
+
run: callProviderTurnWithOverflowRecovery,
|
|
1380
|
+
classifyError: (error) => providerRuntime.classifyError(error),
|
|
1381
|
+
onRetry: async (record, maxAttempts) => {
|
|
1382
|
+
const delayMs = record.delayMs;
|
|
1383
|
+
const seconds = delayMs / 1000;
|
|
1384
|
+
const cause = RETRY_LABELS[record.classification];
|
|
1385
|
+
try {
|
|
1386
|
+
if (record.provider === "openai-codex" && record.classification === "auth-failure") {
|
|
1387
|
+
await (0, openai_codex_token_1.refreshOpenAICodexProviderCredentials)((0, identity_2.getAgentName)(), {
|
|
1388
|
+
force: true,
|
|
1389
|
+
reason: "turn-auth-failure",
|
|
1390
|
+
});
|
|
1391
|
+
}
|
|
1392
|
+
await (0, provider_credentials_1.refreshProviderCredentialPool)((0, identity_2.getAgentName)(), {
|
|
1393
|
+
preserveCachedOnFailure: true,
|
|
1394
|
+
providers: [record.provider],
|
|
1395
|
+
});
|
|
1396
|
+
_providerRuntimeFactories[facing] = null;
|
|
1397
|
+
providerRuntime = await getProviderRuntime(facing);
|
|
1398
|
+
providerRuntime.resetTurnState(messages);
|
|
1399
|
+
}
|
|
1400
|
+
catch (refreshError) {
|
|
1401
|
+
(0, runtime_1.emitNervesEvent)({
|
|
1402
|
+
level: "warn",
|
|
1403
|
+
component: "engine",
|
|
1404
|
+
event: "engine.provider_retry_refresh_failed",
|
|
1405
|
+
message: "provider credential refresh failed during retry",
|
|
1406
|
+
meta: { provider: record.provider, model: record.model, reason: refreshError instanceof Error ? refreshError.message : String(refreshError) },
|
|
1407
|
+
});
|
|
1408
|
+
}
|
|
1409
|
+
callbacks.onError(new Error(`${cause}, retrying in ${seconds}s (${record.attempt}/${maxAttempts})...`), "transient");
|
|
1410
|
+
},
|
|
1411
|
+
sleep: async (delayMs) => {
|
|
1412
|
+
await waitForProviderRetry(delayMs, signal);
|
|
1413
|
+
providerRuntime.resetTurnState(messages);
|
|
1414
|
+
},
|
|
1415
|
+
});
|
|
1416
|
+
if (!attempt.ok) {
|
|
1417
|
+
finishTerminalProviderError(attempt.error, attempt.classification);
|
|
1418
|
+
continue;
|
|
1325
1419
|
}
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
throw new provider_attempt_1.ProviderAttemptAbortError();
|
|
1331
|
-
throw error;
|
|
1420
|
+
const result = attempt.value;
|
|
1421
|
+
providerIterations += 1;
|
|
1422
|
+
if (providerIterations === exports.MAX_PROVIDER_ITERATIONS && result.toolCalls.length > 0) {
|
|
1423
|
+
throw new Error(`provider iteration limit exhausted at response ${exports.MAX_PROVIDER_ITERATIONS} before tool execution`);
|
|
1332
1424
|
}
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1425
|
+
const streamCallbackBuffer = turnCallbackBufferRef.current;
|
|
1426
|
+
turnCallbackBufferRef.current = null;
|
|
1427
|
+
if (result.settleFinalization && !result.settleFinalization.ok) {
|
|
1428
|
+
// A completed settle payload is terminal provider output, not a prompt
|
|
1429
|
+
// for another model turn. Retractable callbacks have already cleared;
|
|
1430
|
+
// core-owned callback buffers must be discarded before surfacing the exact
|
|
1431
|
+
// parser failure through the ordinary terminal-error path.
|
|
1432
|
+
streamCallbackBuffer?.discard();
|
|
1433
|
+
finishTerminalProviderError(new Error(result.settleFinalization.errorCode), "unknown");
|
|
1434
|
+
continue;
|
|
1337
1435
|
}
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1436
|
+
// Track usage from the latest API call
|
|
1437
|
+
if (result.usage)
|
|
1438
|
+
lastUsage = result.usage;
|
|
1439
|
+
// SHARED: build CC-format assistant message from TurnResult
|
|
1440
|
+
const msg = {
|
|
1441
|
+
role: "assistant",
|
|
1442
|
+
};
|
|
1443
|
+
// Persist assistant content WITHOUT inline <think>...</think> blocks.
|
|
1444
|
+
// Reasoning content already routed through onReasoningChunk for live
|
|
1445
|
+
// surfacing and persisted separately as `_reasoning_items` for
|
|
1446
|
+
// providers that support a reasoning channel; saving it inline AND
|
|
1447
|
+
// alongside tool_calls causes MiniMax to reject the replayed turn
|
|
1448
|
+
// with "tool result's tool id not found" (error code 2013) because
|
|
1449
|
+
// it can't reconcile reasoning-with-tools in the same assistant
|
|
1450
|
+
// message. Strip aggressively at persist so the next replay is
|
|
1451
|
+
// clean; preserve the original reasoning trace on the message via
|
|
1452
|
+
// `_inline_reasoning` so debug/audit paths can still see it.
|
|
1453
|
+
if (result.content) {
|
|
1454
|
+
const stripped = stripThinkBlocksForViolationCheck(result.content);
|
|
1455
|
+
if (stripped.length > 0)
|
|
1456
|
+
msg.content = stripped;
|
|
1457
|
+
if (stripped.length !== result.content.length) {
|
|
1458
|
+
msg._inline_reasoning = result.content;
|
|
1459
|
+
(0, runtime_1.emitNervesEvent)({
|
|
1460
|
+
level: "info",
|
|
1461
|
+
component: "engine",
|
|
1462
|
+
event: "engine.inline_reasoning_stripped",
|
|
1463
|
+
message: "stripped inline <think> blocks from persisted assistant message; preserved on _inline_reasoning",
|
|
1464
|
+
meta: {
|
|
1465
|
+
provider: providerRuntime.id,
|
|
1466
|
+
model: providerRuntime.model,
|
|
1467
|
+
originalLength: result.content.length,
|
|
1468
|
+
strippedLength: stripped.length,
|
|
1469
|
+
},
|
|
1470
|
+
});
|
|
1360
1471
|
}
|
|
1361
|
-
throw error;
|
|
1362
1472
|
}
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1473
|
+
if (result.toolCalls.length)
|
|
1474
|
+
msg.tool_calls = result.toolCalls.map((tc) => ({
|
|
1475
|
+
id: tc.id,
|
|
1476
|
+
type: "function",
|
|
1477
|
+
function: { name: tc.name, arguments: tc.arguments },
|
|
1478
|
+
}));
|
|
1479
|
+
// Store reasoning items from the API response on the assistant message
|
|
1480
|
+
// so they persist through session save/load and can be restored in toResponsesInput
|
|
1481
|
+
const reasoningItems = result.outputItems.filter((item) => "type" in item && item.type === "reasoning");
|
|
1482
|
+
if (reasoningItems.length > 0) {
|
|
1483
|
+
msg._reasoning_items = reasoningItems;
|
|
1484
|
+
}
|
|
1485
|
+
// Store thinking blocks (Anthropic) on the assistant message for round-tripping
|
|
1486
|
+
const thinkingItems = result.outputItems.filter((item) => "type" in item && (item.type === "thinking" || item.type === "redacted_thinking"));
|
|
1487
|
+
if (thinkingItems.length > 0) {
|
|
1488
|
+
msg._thinking_blocks = thinkingItems;
|
|
1489
|
+
}
|
|
1490
|
+
// Phase annotation for Codex provider
|
|
1491
|
+
const hasPhaseAnnotation = providerRuntime.capabilities.has("phase-annotation");
|
|
1492
|
+
const isSoleSettle = result.toolCalls.length === 1 && result.toolCalls[0].name === "settle";
|
|
1493
|
+
if (hasPhaseAnnotation) {
|
|
1494
|
+
msg.phase = isSoleSettle ? "settle" : "commentary";
|
|
1495
|
+
}
|
|
1496
|
+
// Detect the MiniMax "only-thinking, no tool call" violation: no tool
|
|
1497
|
+
// calls returned, and the content is empty after stripping
|
|
1498
|
+
// <think>...</think> blocks. This is a narrow check — legitimate
|
|
1499
|
+
// content-only responses (text without think tags, or text outside
|
|
1500
|
+
// think tags) still flow through the original "no tool calls →
|
|
1501
|
+
// accept as-is" path so existing channels and tests are unaffected.
|
|
1502
|
+
const onlyThinkContent = !result.toolCalls.length
|
|
1503
|
+
&& typeof result.content === "string"
|
|
1504
|
+
&& stripThinkBlocksForViolationCheck(result.content).length === 0
|
|
1505
|
+
&& result.content.length > 0;
|
|
1506
|
+
const privateReturnTextAckRetryError = !result.toolCalls.length
|
|
1507
|
+
? privateReturnMissingPonderError({
|
|
1508
|
+
latestUserRequest: latestUserMessageText(messages),
|
|
1509
|
+
answer: stripThinkBlocksForViolationCheck(result.content),
|
|
1510
|
+
sawPonder,
|
|
1511
|
+
})
|
|
1512
|
+
: null;
|
|
1513
|
+
if (!result.toolCalls.length) {
|
|
1514
|
+
if (forcingHistoricalEffect) {
|
|
1515
|
+
streamCallbackBuffer?.discard();
|
|
1516
|
+
callbacks.onClearText?.();
|
|
1517
|
+
const retryableHistoricalTools = [...forcedHistoricalToolNames];
|
|
1518
|
+
if (historicalToolFailureRetries >= NO_TOOL_CALL_MAX_RETRIES) {
|
|
1519
|
+
const blockedAnswer = `I could not complete the unresolved ${retryableHistoricalTools.join(", ")} effect because the provider repeatedly returned no tool call. I did not treat the requested change as complete.`;
|
|
1520
|
+
(0, runtime_1.emitNervesEvent)({
|
|
1521
|
+
level: "warn",
|
|
1522
|
+
component: "engine",
|
|
1523
|
+
event: "engine.historical_tool_failure_retry",
|
|
1524
|
+
message: "unresolved historical effect exhausted deterministic tool-call retries; failing closed",
|
|
1525
|
+
meta: { provider: providerRuntime.id, model: providerRuntime.model, toolNames: retryableHistoricalTools, cap: NO_TOOL_CALL_MAX_RETRIES },
|
|
1379
1526
|
});
|
|
1527
|
+
msg.content = blockedAnswer;
|
|
1528
|
+
pushGenerated(msg);
|
|
1529
|
+
callbacks.onTextChunk(blockedAnswer);
|
|
1530
|
+
completion = { answer: blockedAnswer, intent: "blocked" };
|
|
1531
|
+
outcome = "blocked";
|
|
1532
|
+
done = true;
|
|
1533
|
+
continue;
|
|
1380
1534
|
}
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1535
|
+
historicalToolFailureRetries += 1;
|
|
1536
|
+
(0, runtime_1.emitNervesEvent)({
|
|
1537
|
+
level: "warn",
|
|
1538
|
+
component: "engine",
|
|
1539
|
+
event: "engine.historical_tool_failure_retry",
|
|
1540
|
+
message: "unresolved historical effect returned no tool call; forcing another exact-effect attempt",
|
|
1541
|
+
meta: {
|
|
1542
|
+
provider: providerRuntime.id,
|
|
1543
|
+
model: providerRuntime.model,
|
|
1544
|
+
toolNames: retryableHistoricalTools,
|
|
1545
|
+
attempt: historicalToolFailureRetries,
|
|
1546
|
+
cap: NO_TOOL_CALL_MAX_RETRIES,
|
|
1547
|
+
},
|
|
1384
1548
|
});
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1549
|
+
pushGenerated(msg);
|
|
1550
|
+
messages.push({
|
|
1551
|
+
role: "user",
|
|
1552
|
+
content: `this exact request previously reached a tool that is advertised again now. Its unresolved historical effect is through ${retryableHistoricalTools.join(", ")}. Only that tool is available now. Read current state if needed, then retry the exact failed effect. Do not report completion until the effect succeeds.`,
|
|
1553
|
+
});
|
|
1554
|
+
continue;
|
|
1388
1555
|
}
|
|
1389
|
-
|
|
1556
|
+
const requiredToolCallsGate = pendingRequiredToolCalls();
|
|
1557
|
+
if (requiredToolCallsGate) {
|
|
1558
|
+
streamCallbackBuffer?.discard();
|
|
1559
|
+
callbacks.onClearText?.();
|
|
1560
|
+
queueRequiredCorrection(requiredToolCallsGate.message, "before required tool calls completed");
|
|
1390
1561
|
(0, runtime_1.emitNervesEvent)({
|
|
1391
1562
|
level: "warn",
|
|
1392
1563
|
component: "engine",
|
|
1393
|
-
event: "engine.
|
|
1394
|
-
message: "
|
|
1395
|
-
meta: {
|
|
1564
|
+
event: "engine.required_tool_calls_pending",
|
|
1565
|
+
message: "terminal response rejected until required tool handlers dispatch",
|
|
1566
|
+
meta: { missingToolNames: requiredToolCallsGate.missing },
|
|
1396
1567
|
});
|
|
1568
|
+
continue;
|
|
1397
1569
|
}
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
// Reasoning content already routed through onReasoningChunk for live
|
|
1434
|
-
// surfacing and persisted separately as `_reasoning_items` for
|
|
1435
|
-
// providers that support a reasoning channel; saving it inline AND
|
|
1436
|
-
// alongside tool_calls causes MiniMax to reject the replayed turn
|
|
1437
|
-
// with "tool result's tool id not found" (error code 2013) because
|
|
1438
|
-
// it can't reconcile reasoning-with-tools in the same assistant
|
|
1439
|
-
// message. Strip aggressively at persist so the next replay is
|
|
1440
|
-
// clean; preserve the original reasoning trace on the message via
|
|
1441
|
-
// `_inline_reasoning` so debug/audit paths can still see it.
|
|
1442
|
-
if (result.content) {
|
|
1443
|
-
const stripped = stripThinkBlocksForViolationCheck(result.content);
|
|
1444
|
-
if (stripped.length > 0)
|
|
1445
|
-
msg.content = stripped;
|
|
1446
|
-
if (stripped.length !== result.content.length) {
|
|
1447
|
-
msg._inline_reasoning = result.content;
|
|
1448
|
-
(0, runtime_1.emitNervesEvent)({
|
|
1449
|
-
level: "info",
|
|
1450
|
-
component: "engine",
|
|
1451
|
-
event: "engine.inline_reasoning_stripped",
|
|
1452
|
-
message: "stripped inline <think> blocks from persisted assistant message; preserved on _inline_reasoning",
|
|
1453
|
-
meta: {
|
|
1454
|
-
provider: providerRuntime.id,
|
|
1455
|
-
model: providerRuntime.model,
|
|
1456
|
-
originalLength: result.content.length,
|
|
1457
|
-
strippedLength: stripped.length,
|
|
1458
|
-
},
|
|
1459
|
-
});
|
|
1460
|
-
}
|
|
1461
|
-
}
|
|
1462
|
-
if (result.toolCalls.length)
|
|
1463
|
-
msg.tool_calls = result.toolCalls.map((tc) => ({
|
|
1464
|
-
id: tc.id,
|
|
1465
|
-
type: "function",
|
|
1466
|
-
function: { name: tc.name, arguments: tc.arguments },
|
|
1467
|
-
}));
|
|
1468
|
-
// Store reasoning items from the API response on the assistant message
|
|
1469
|
-
// so they persist through session save/load and can be restored in toResponsesInput
|
|
1470
|
-
const reasoningItems = result.outputItems.filter((item) => "type" in item && item.type === "reasoning");
|
|
1471
|
-
if (reasoningItems.length > 0) {
|
|
1472
|
-
msg._reasoning_items = reasoningItems;
|
|
1473
|
-
}
|
|
1474
|
-
// Store thinking blocks (Anthropic) on the assistant message for round-tripping
|
|
1475
|
-
const thinkingItems = result.outputItems.filter((item) => "type" in item && (item.type === "thinking" || item.type === "redacted_thinking"));
|
|
1476
|
-
if (thinkingItems.length > 0) {
|
|
1477
|
-
msg._thinking_blocks = thinkingItems;
|
|
1478
|
-
}
|
|
1479
|
-
// Phase annotation for Codex provider
|
|
1480
|
-
const hasPhaseAnnotation = providerRuntime.capabilities.has("phase-annotation");
|
|
1481
|
-
const isSoleSettle = result.toolCalls.length === 1 && result.toolCalls[0].name === "settle";
|
|
1482
|
-
if (hasPhaseAnnotation) {
|
|
1483
|
-
msg.phase = isSoleSettle ? "settle" : "commentary";
|
|
1484
|
-
}
|
|
1485
|
-
// Detect the MiniMax "only-thinking, no tool call" violation: no tool
|
|
1486
|
-
// calls returned, and the content is empty after stripping
|
|
1487
|
-
// <think>...</think> blocks. This is a narrow check — legitimate
|
|
1488
|
-
// content-only responses (text without think tags, or text outside
|
|
1489
|
-
// think tags) still flow through the original "no tool calls →
|
|
1490
|
-
// accept as-is" path so existing channels and tests are unaffected.
|
|
1491
|
-
const onlyThinkContent = !result.toolCalls.length
|
|
1492
|
-
&& typeof result.content === "string"
|
|
1493
|
-
&& stripThinkBlocksForViolationCheck(result.content).length === 0
|
|
1494
|
-
&& result.content.length > 0;
|
|
1495
|
-
const privateReturnTextAckRetryError = !result.toolCalls.length
|
|
1496
|
-
? privateReturnMissingPonderError({
|
|
1497
|
-
latestUserRequest: latestUserMessageText(messages),
|
|
1498
|
-
answer: stripThinkBlocksForViolationCheck(result.content),
|
|
1499
|
-
sawPonder,
|
|
1500
|
-
})
|
|
1501
|
-
: null;
|
|
1502
|
-
if (!result.toolCalls.length) {
|
|
1503
|
-
if (forcingHistoricalEffect) {
|
|
1504
|
-
streamCallbackBuffer?.discard();
|
|
1505
|
-
callbacks.onClearText?.();
|
|
1506
|
-
const retryableHistoricalTools = [...forcedHistoricalToolNames];
|
|
1507
|
-
if (historicalToolFailureRetries >= NO_TOOL_CALL_MAX_RETRIES) {
|
|
1508
|
-
const blockedAnswer = `I could not complete the unresolved ${retryableHistoricalTools.join(", ")} effect because the provider repeatedly returned no tool call. I did not treat the requested change as complete.`;
|
|
1570
|
+
const requiredAnswerRejection = options?.requiredToolCalls?.validateTerminalAnswer?.(String(msg.content ?? ""));
|
|
1571
|
+
if (requiredAnswerRejection) {
|
|
1572
|
+
streamCallbackBuffer?.discard();
|
|
1573
|
+
callbacks.onClearText?.();
|
|
1574
|
+
queueRequiredCorrection(requiredAnswerRejection, "before required terminal answer validation completed");
|
|
1575
|
+
(0, runtime_1.emitNervesEvent)({ level: "warn", component: "engine", event: "engine.required_tool_answer_rejected", message: "unsupported terminal answer rejected after required reads", meta: { answerLength: String(msg.content ?? "").length } });
|
|
1576
|
+
continue;
|
|
1577
|
+
}
|
|
1578
|
+
if (privateReturnTextAckRetryError) {
|
|
1579
|
+
streamCallbackBuffer?.discard();
|
|
1580
|
+
callbacks.onClearText?.();
|
|
1581
|
+
if (noToolCallRetries < NO_TOOL_CALL_MAX_RETRIES) {
|
|
1582
|
+
noToolCallRetries++;
|
|
1583
|
+
(0, runtime_1.emitNervesEvent)({
|
|
1584
|
+
level: "warn",
|
|
1585
|
+
component: "engine",
|
|
1586
|
+
event: "engine.no_tool_call_retry",
|
|
1587
|
+
message: "model returned a text-only private-return acknowledgement without ponder; retrying with corrective nudge",
|
|
1588
|
+
meta: {
|
|
1589
|
+
attempt: noToolCallRetries,
|
|
1590
|
+
cap: NO_TOOL_CALL_MAX_RETRIES,
|
|
1591
|
+
provider: providerRuntime.id,
|
|
1592
|
+
model: providerRuntime.model,
|
|
1593
|
+
reason: "private_return_missing_ponder",
|
|
1594
|
+
contentLength: result.content.length,
|
|
1595
|
+
},
|
|
1596
|
+
});
|
|
1597
|
+
pushGenerated(msg);
|
|
1598
|
+
messages.push({
|
|
1599
|
+
role: "user",
|
|
1600
|
+
content: `${privateReturnTextAckRetryError} Emit the ponder(action=create, ...) tool call now, or ask a blocking clarification without saying the private work is queued.`,
|
|
1601
|
+
});
|
|
1602
|
+
continue;
|
|
1603
|
+
}
|
|
1604
|
+
const blockedAnswer = "I could not start the private pass. No private-attention packet was created, so no return work was queued.";
|
|
1509
1605
|
(0, runtime_1.emitNervesEvent)({
|
|
1510
|
-
level: "
|
|
1606
|
+
level: "error",
|
|
1511
1607
|
component: "engine",
|
|
1512
|
-
event: "engine.
|
|
1513
|
-
message: "
|
|
1514
|
-
meta: {
|
|
1608
|
+
event: "engine.private_return_missing_ponder_blocked",
|
|
1609
|
+
message: "private-return text acknowledgement skipped ponder through the retry cap; failing closed",
|
|
1610
|
+
meta: {
|
|
1611
|
+
cap: NO_TOOL_CALL_MAX_RETRIES,
|
|
1612
|
+
provider: providerRuntime.id,
|
|
1613
|
+
model: providerRuntime.model,
|
|
1614
|
+
contentLength: result.content.length,
|
|
1615
|
+
},
|
|
1515
1616
|
});
|
|
1516
1617
|
msg.content = blockedAnswer;
|
|
1517
1618
|
pushGenerated(msg);
|
|
@@ -1521,895 +1622,809 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
1521
1622
|
done = true;
|
|
1522
1623
|
continue;
|
|
1523
1624
|
}
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
level:
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
provider: providerRuntime.id,
|
|
1532
|
-
model: providerRuntime.model,
|
|
1533
|
-
toolNames: retryableHistoricalTools,
|
|
1534
|
-
attempt: historicalToolFailureRetries,
|
|
1535
|
-
cap: NO_TOOL_CALL_MAX_RETRIES,
|
|
1536
|
-
},
|
|
1537
|
-
});
|
|
1538
|
-
pushGenerated(msg);
|
|
1539
|
-
messages.push({
|
|
1540
|
-
role: "user",
|
|
1541
|
-
content: `this exact request previously reached a tool that is advertised again now. Its unresolved historical effect is through ${retryableHistoricalTools.join(", ")}. Only that tool is available now. Read current state if needed, then retry the exact failed effect. Do not report completion until the effect succeeds.`,
|
|
1542
|
-
});
|
|
1543
|
-
continue;
|
|
1544
|
-
}
|
|
1545
|
-
const requiredToolCallsGate = pendingRequiredToolCalls();
|
|
1546
|
-
if (requiredToolCallsGate) {
|
|
1547
|
-
streamCallbackBuffer?.discard();
|
|
1548
|
-
callbacks.onClearText?.();
|
|
1549
|
-
queueRequiredCorrection(requiredToolCallsGate.message, "before required tool calls completed");
|
|
1550
|
-
(0, runtime_1.emitNervesEvent)({
|
|
1551
|
-
level: "warn",
|
|
1552
|
-
component: "engine",
|
|
1553
|
-
event: "engine.required_tool_calls_pending",
|
|
1554
|
-
message: "terminal response rejected until required tool handlers dispatch",
|
|
1555
|
-
meta: { missingToolNames: requiredToolCallsGate.missing },
|
|
1556
|
-
});
|
|
1557
|
-
continue;
|
|
1558
|
-
}
|
|
1559
|
-
const requiredAnswerRejection = options?.requiredToolCalls?.validateTerminalAnswer?.(String(msg.content ?? ""));
|
|
1560
|
-
if (requiredAnswerRejection) {
|
|
1561
|
-
streamCallbackBuffer?.discard();
|
|
1562
|
-
callbacks.onClearText?.();
|
|
1563
|
-
queueRequiredCorrection(requiredAnswerRejection, "before required terminal answer validation completed");
|
|
1564
|
-
(0, runtime_1.emitNervesEvent)({ level: "warn", component: "engine", event: "engine.required_tool_answer_rejected", message: "unsupported terminal answer rejected after required reads", meta: { answerLength: String(msg.content ?? "").length } });
|
|
1565
|
-
continue;
|
|
1566
|
-
}
|
|
1567
|
-
if (privateReturnTextAckRetryError) {
|
|
1568
|
-
streamCallbackBuffer?.discard();
|
|
1569
|
-
callbacks.onClearText?.();
|
|
1570
|
-
if (noToolCallRetries < NO_TOOL_CALL_MAX_RETRIES) {
|
|
1625
|
+
if (onlyThinkContent && toolChoiceRequired && noToolCallRetries < NO_TOOL_CALL_MAX_RETRIES) {
|
|
1626
|
+
streamCallbackBuffer?.discard();
|
|
1627
|
+
// Provider-level violation: tool_choice was required, model emitted
|
|
1628
|
+
// only a <think>...</think> block (or empty content) with no tool
|
|
1629
|
+
// call. Retry with a corrective nudge up to NO_TOOL_CALL_MAX_RETRIES
|
|
1630
|
+
// times. After cap, accept as-is (the readback path strips think
|
|
1631
|
+
// tags and surfaces a clear diagnostic).
|
|
1571
1632
|
noToolCallRetries++;
|
|
1572
1633
|
(0, runtime_1.emitNervesEvent)({
|
|
1573
1634
|
level: "warn",
|
|
1574
1635
|
component: "engine",
|
|
1575
1636
|
event: "engine.no_tool_call_retry",
|
|
1576
|
-
message: "model returned
|
|
1637
|
+
message: "model returned only <think> content with no tool call despite tool_choice=required; retrying with corrective nudge",
|
|
1577
1638
|
meta: {
|
|
1578
1639
|
attempt: noToolCallRetries,
|
|
1579
1640
|
cap: NO_TOOL_CALL_MAX_RETRIES,
|
|
1580
1641
|
provider: providerRuntime.id,
|
|
1581
1642
|
model: providerRuntime.model,
|
|
1582
|
-
reason: "private_return_missing_ponder",
|
|
1583
1643
|
contentLength: result.content.length,
|
|
1584
1644
|
},
|
|
1585
1645
|
});
|
|
1586
1646
|
pushGenerated(msg);
|
|
1587
1647
|
messages.push({
|
|
1588
1648
|
role: "user",
|
|
1589
|
-
content:
|
|
1649
|
+
content: isPrivateRuntimeChannel
|
|
1650
|
+
? augmentedToolContext?.noSend === true
|
|
1651
|
+
? "no tool was called this turn. this is an immutable no-send turn; call rest now without creating a continuation."
|
|
1652
|
+
: "no tool was called this turn. you must end every turn by calling rest (or surface, ponder, observe). emit the tool call now."
|
|
1653
|
+
: "no tool was called this turn. you must end every turn by calling settle with your answer (or ponder/observe). emit the tool call now.",
|
|
1590
1654
|
});
|
|
1591
1655
|
continue;
|
|
1592
1656
|
}
|
|
1593
|
-
|
|
1594
|
-
(0, runtime_1.emitNervesEvent)({
|
|
1595
|
-
level: "error",
|
|
1596
|
-
component: "engine",
|
|
1597
|
-
event: "engine.private_return_missing_ponder_blocked",
|
|
1598
|
-
message: "private-return text acknowledgement skipped ponder through the retry cap; failing closed",
|
|
1599
|
-
meta: {
|
|
1600
|
-
cap: NO_TOOL_CALL_MAX_RETRIES,
|
|
1601
|
-
provider: providerRuntime.id,
|
|
1602
|
-
model: providerRuntime.model,
|
|
1603
|
-
contentLength: result.content.length,
|
|
1604
|
-
},
|
|
1605
|
-
});
|
|
1606
|
-
msg.content = blockedAnswer;
|
|
1607
|
-
pushGenerated(msg);
|
|
1608
|
-
callbacks.onTextChunk(blockedAnswer);
|
|
1609
|
-
completion = { answer: blockedAnswer, intent: "blocked" };
|
|
1610
|
-
outcome = "blocked";
|
|
1611
|
-
done = true;
|
|
1612
|
-
continue;
|
|
1613
|
-
}
|
|
1614
|
-
if (onlyThinkContent && toolChoiceRequired && noToolCallRetries < NO_TOOL_CALL_MAX_RETRIES) {
|
|
1615
|
-
streamCallbackBuffer?.discard();
|
|
1616
|
-
// Provider-level violation: tool_choice was required, model emitted
|
|
1617
|
-
// only a <think>...</think> block (or empty content) with no tool
|
|
1618
|
-
// call. Retry with a corrective nudge up to NO_TOOL_CALL_MAX_RETRIES
|
|
1619
|
-
// times. After cap, accept as-is (the readback path strips think
|
|
1620
|
-
// tags and surfaces a clear diagnostic).
|
|
1621
|
-
noToolCallRetries++;
|
|
1622
|
-
(0, runtime_1.emitNervesEvent)({
|
|
1623
|
-
level: "warn",
|
|
1624
|
-
component: "engine",
|
|
1625
|
-
event: "engine.no_tool_call_retry",
|
|
1626
|
-
message: "model returned only <think> content with no tool call despite tool_choice=required; retrying with corrective nudge",
|
|
1627
|
-
meta: {
|
|
1628
|
-
attempt: noToolCallRetries,
|
|
1629
|
-
cap: NO_TOOL_CALL_MAX_RETRIES,
|
|
1630
|
-
provider: providerRuntime.id,
|
|
1631
|
-
model: providerRuntime.model,
|
|
1632
|
-
contentLength: result.content.length,
|
|
1633
|
-
},
|
|
1634
|
-
});
|
|
1635
|
-
pushGenerated(msg);
|
|
1636
|
-
messages.push({
|
|
1637
|
-
role: "user",
|
|
1638
|
-
content: isPrivateRuntimeChannel
|
|
1639
|
-
? augmentedToolContext?.noSend === true
|
|
1640
|
-
? "no tool was called this turn. this is an immutable no-send turn; call rest now without creating a continuation."
|
|
1641
|
-
: "no tool was called this turn. you must end every turn by calling rest (or surface, ponder, observe). emit the tool call now."
|
|
1642
|
-
: "no tool was called this turn. you must end every turn by calling settle with your answer (or ponder/observe). emit the tool call now.",
|
|
1643
|
-
});
|
|
1644
|
-
continue;
|
|
1645
|
-
}
|
|
1646
|
-
// Legitimate text-only response, or cap reached — accept as-is.
|
|
1647
|
-
await streamCallbackBuffer?.flush();
|
|
1648
|
-
pushGenerated(msg);
|
|
1649
|
-
done = true;
|
|
1650
|
-
}
|
|
1651
|
-
else {
|
|
1652
|
-
// Reset the retry counter on any successful tool call.
|
|
1653
|
-
noToolCallRetries = 0;
|
|
1654
|
-
const preCallMessages = structuredClone(messages.filter((message) => message.role !== "system"));
|
|
1655
|
-
const validatedCalls = validateToolCallBatchAtProductionBoundary(result.toolCalls, activeTools);
|
|
1656
|
-
const invalidCall = validatedCalls.find((entry) => "error" in entry);
|
|
1657
|
-
if (invalidCall) {
|
|
1657
|
+
// Legitimate text-only response, or cap reached — accept as-is.
|
|
1658
1658
|
await streamCallbackBuffer?.flush();
|
|
1659
1659
|
pushGenerated(msg);
|
|
1660
|
-
|
|
1661
|
-
for (const entry of validatedCalls) {
|
|
1662
|
-
const detail = "error" in entry ? entry.error : "another call in this batch had invalid arguments";
|
|
1663
|
-
const rejection = unadvertisedCall
|
|
1664
|
-
? `rejected: ${entry.call.name} was not advertised for this channel; no handler was executed.`
|
|
1665
|
-
: `invalid tool arguments: ${detail}`;
|
|
1666
|
-
pushGenerated({ role: "tool", tool_call_id: entry.call.id, content: rejection });
|
|
1667
|
-
providerRuntime.appendToolOutput(entry.call.id, rejection);
|
|
1668
|
-
options?.toolBoundaryObserver?.({
|
|
1669
|
-
name: entry.call.name,
|
|
1670
|
-
reason: activeToolNames.has(entry.call.name) ? "invalid_arguments" : "profile_excluded",
|
|
1671
|
-
globallyResolvable: typeof (0, tools_1.resolveToolDefinition)(entry.call.name)?.handler === "function",
|
|
1672
|
-
invoked: false,
|
|
1673
|
-
sideEffect: false,
|
|
1674
|
-
});
|
|
1675
|
-
}
|
|
1676
|
-
if (unadvertisedCall) {
|
|
1677
|
-
(0, runtime_1.emitNervesEvent)({
|
|
1678
|
-
level: "warn",
|
|
1679
|
-
component: "engine",
|
|
1680
|
-
event: "engine.unadvertised_tool_blocked",
|
|
1681
|
-
message: "blocked an unadvertised tool call before batch execution",
|
|
1682
|
-
meta: { channel: String(channel), toolName: unadvertisedCall.call.name },
|
|
1683
|
-
});
|
|
1684
|
-
}
|
|
1685
|
-
else {
|
|
1686
|
-
(0, runtime_1.emitNervesEvent)({
|
|
1687
|
-
level: "warn",
|
|
1688
|
-
component: "engine",
|
|
1689
|
-
event: "engine.tool_arguments_rejected",
|
|
1690
|
-
message: "tool batch rejected before execution because arguments were invalid",
|
|
1691
|
-
meta: { toolCallId: invalidCall.call.id, toolName: invalidCall.call.name },
|
|
1692
|
-
});
|
|
1693
|
-
}
|
|
1694
|
-
continue;
|
|
1695
|
-
}
|
|
1696
|
-
const validCalls = validatedCalls;
|
|
1697
|
-
const validatedCallArguments = new Map(validCalls.map((entry) => [
|
|
1698
|
-
entry.call,
|
|
1699
|
-
entry.validated.arguments,
|
|
1700
|
-
]));
|
|
1701
|
-
const habitBlockReason = await habitToolBatchBlockReason(habitSession, result.toolCalls, augmentedToolContext?.delegatedOrigins);
|
|
1702
|
-
if (habitBlockReason) {
|
|
1703
|
-
streamCallbackBuffer?.discard();
|
|
1704
|
-
recordBlockedHabitSurfaceAttempts(habitSession, result.toolCalls, habitBlockReason);
|
|
1705
|
-
pushGenerated(msg);
|
|
1706
|
-
const blockedOutput = `blocked: ${habitBlockReason}. No tool side effects from this assistant message were executed.`;
|
|
1707
|
-
for (const call of result.toolCalls) {
|
|
1708
|
-
pushGenerated({ role: "tool", tool_call_id: call.id, content: blockedOutput });
|
|
1709
|
-
providerRuntime.appendToolOutput(call.id, blockedOutput);
|
|
1710
|
-
}
|
|
1711
|
-
(0, runtime_1.emitNervesEvent)({
|
|
1712
|
-
level: "warn",
|
|
1713
|
-
component: "engine",
|
|
1714
|
-
event: "engine.habit_tool_batch_blocked",
|
|
1715
|
-
message: "habit tool batch blocked before side effects",
|
|
1716
|
-
meta: { reason: habitBlockReason, toolCalls: result.toolCalls.map((call) => call.name) },
|
|
1717
|
-
});
|
|
1718
|
-
continue;
|
|
1660
|
+
done = true;
|
|
1719
1661
|
}
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
const terminalArgs = validatedCallArguments.get(soleTerminalCall);
|
|
1728
|
-
if (soleTerminalProjection.clearBufferedText
|
|
1729
|
-
|| callbacks.settleOutputMode === "final_only") {
|
|
1730
|
-
streamCallbackBuffer?.discard();
|
|
1731
|
-
}
|
|
1732
|
-
else {
|
|
1662
|
+
else {
|
|
1663
|
+
// Reset the retry counter on any successful tool call.
|
|
1664
|
+
noToolCallRetries = 0;
|
|
1665
|
+
const preCallMessages = structuredClone(messagesWithoutRequiredCorrections().filter((message) => message.role !== "system"));
|
|
1666
|
+
const validatedCalls = validateToolCallBatchAtProductionBoundary(result.toolCalls, activeTools);
|
|
1667
|
+
const invalidCall = validatedCalls.find((entry) => "error" in entry);
|
|
1668
|
+
if (invalidCall) {
|
|
1733
1669
|
await streamCallbackBuffer?.flush();
|
|
1734
|
-
}
|
|
1735
|
-
if (soleTerminalProjection.clearBufferedText)
|
|
1736
|
-
callbacks.onClearText?.();
|
|
1737
|
-
callbacks.onToolStart(soleTerminalCall.name, terminalArgs);
|
|
1738
|
-
let terminalResult;
|
|
1739
|
-
try {
|
|
1740
|
-
const execToolFn = options?.execTool ?? tools_1.execTool;
|
|
1741
|
-
terminalResult = await execToolFn(soleTerminalCall.name, terminalArgs, augmentedToolContext);
|
|
1742
|
-
}
|
|
1743
|
-
catch (error) {
|
|
1744
|
-
callbacks.onToolEnd(soleTerminalCall.name, (0, tools_1.summarizeArgs)(soleTerminalCall.name, terminalArgs), false);
|
|
1745
1670
|
pushGenerated(msg);
|
|
1746
|
-
const
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1671
|
+
const unadvertisedCall = validatedCalls.find((entry) => !activeToolNames.has(entry.call.name));
|
|
1672
|
+
for (const entry of validatedCalls) {
|
|
1673
|
+
const detail = "error" in entry ? entry.error : "another call in this batch had invalid arguments";
|
|
1674
|
+
const rejection = unadvertisedCall
|
|
1675
|
+
? `rejected: ${entry.call.name} was not advertised for this channel; no handler was executed.`
|
|
1676
|
+
: `invalid tool arguments: ${detail}`;
|
|
1677
|
+
pushGenerated({ role: "tool", tool_call_id: entry.call.id, content: rejection });
|
|
1678
|
+
providerRuntime.appendToolOutput(entry.call.id, rejection);
|
|
1679
|
+
options?.toolBoundaryObserver?.({
|
|
1680
|
+
name: entry.call.name,
|
|
1681
|
+
reason: activeToolNames.has(entry.call.name) ? "invalid_arguments" : "profile_excluded",
|
|
1682
|
+
globallyResolvable: typeof (0, tools_1.resolveToolDefinition)(entry.call.name)?.handler === "function",
|
|
1683
|
+
invoked: false,
|
|
1684
|
+
sideEffect: false,
|
|
1685
|
+
});
|
|
1686
|
+
}
|
|
1687
|
+
if (unadvertisedCall) {
|
|
1688
|
+
(0, runtime_1.emitNervesEvent)({
|
|
1689
|
+
level: "warn",
|
|
1690
|
+
component: "engine",
|
|
1691
|
+
event: "engine.unadvertised_tool_blocked",
|
|
1692
|
+
message: "blocked an unadvertised tool call before batch execution",
|
|
1693
|
+
meta: { channel: String(channel), toolName: unadvertisedCall.call.name },
|
|
1694
|
+
});
|
|
1695
|
+
}
|
|
1696
|
+
else {
|
|
1697
|
+
(0, runtime_1.emitNervesEvent)({
|
|
1698
|
+
level: "warn",
|
|
1699
|
+
component: "engine",
|
|
1700
|
+
event: "engine.tool_arguments_rejected",
|
|
1701
|
+
message: "tool batch rejected before execution because arguments were invalid",
|
|
1702
|
+
meta: { toolCallId: invalidCall.call.id, toolName: invalidCall.call.name },
|
|
1703
|
+
});
|
|
1704
|
+
}
|
|
1753
1705
|
continue;
|
|
1754
1706
|
}
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
done = true;
|
|
1763
|
-
continue;
|
|
1764
|
-
}
|
|
1765
|
-
// Check for settle sole call: intercept before tool execution
|
|
1766
|
-
if (isSoleSettle) {
|
|
1767
|
-
const settleArgs = validatedCallArguments.get(result.toolCalls[0]);
|
|
1768
|
-
callbacks.onToolStart("settle", settleArgs);
|
|
1769
|
-
const requiredToolCallsGate = pendingRequiredToolCalls();
|
|
1770
|
-
if (requiredToolCallsGate) {
|
|
1707
|
+
const validCalls = validatedCalls;
|
|
1708
|
+
const validatedCallArguments = new Map(validCalls.map((entry) => [
|
|
1709
|
+
entry.call,
|
|
1710
|
+
entry.validated.arguments,
|
|
1711
|
+
]));
|
|
1712
|
+
const habitBlockReason = await habitToolBatchBlockReason(habitSession, result.toolCalls, augmentedToolContext?.delegatedOrigins);
|
|
1713
|
+
if (habitBlockReason) {
|
|
1771
1714
|
streamCallbackBuffer?.discard();
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1715
|
+
recordBlockedHabitSurfaceAttempts(habitSession, result.toolCalls, habitBlockReason);
|
|
1716
|
+
pushGenerated(msg);
|
|
1717
|
+
const blockedOutput = `blocked: ${habitBlockReason}. No tool side effects from this assistant message were executed.`;
|
|
1718
|
+
for (const call of result.toolCalls) {
|
|
1719
|
+
pushGenerated({ role: "tool", tool_call_id: call.id, content: blockedOutput });
|
|
1720
|
+
providerRuntime.appendToolOutput(call.id, blockedOutput);
|
|
1721
|
+
}
|
|
1775
1722
|
(0, runtime_1.emitNervesEvent)({
|
|
1776
1723
|
level: "warn",
|
|
1777
1724
|
component: "engine",
|
|
1778
|
-
event: "engine.
|
|
1779
|
-
message: "
|
|
1780
|
-
meta: {
|
|
1725
|
+
event: "engine.habit_tool_batch_blocked",
|
|
1726
|
+
message: "habit tool batch blocked before side effects",
|
|
1727
|
+
meta: { reason: habitBlockReason, toolCalls: result.toolCalls.map((call) => call.name) },
|
|
1781
1728
|
});
|
|
1782
1729
|
continue;
|
|
1783
1730
|
}
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
const
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1731
|
+
const soleTerminalCall = result.toolCalls.length === 1
|
|
1732
|
+
? result.toolCalls[0]
|
|
1733
|
+
: null;
|
|
1734
|
+
const soleTerminalProjection = soleTerminalCall
|
|
1735
|
+
? (0, tools_1.resolveToolDefinition)(soleTerminalCall.name)?.terminalProjection
|
|
1736
|
+
: undefined;
|
|
1737
|
+
if (soleTerminalCall && soleTerminalProjection?.mode === "verbatim") {
|
|
1738
|
+
const terminalArgs = validatedCallArguments.get(soleTerminalCall);
|
|
1739
|
+
if (soleTerminalProjection.clearBufferedText
|
|
1740
|
+
|| callbacks.settleOutputMode === "final_only") {
|
|
1741
|
+
streamCallbackBuffer?.discard();
|
|
1742
|
+
}
|
|
1743
|
+
else {
|
|
1744
|
+
await streamCallbackBuffer?.flush();
|
|
1745
|
+
}
|
|
1746
|
+
if (soleTerminalProjection.clearBufferedText)
|
|
1747
|
+
callbacks.onClearText?.();
|
|
1748
|
+
callbacks.onToolStart(soleTerminalCall.name, terminalArgs);
|
|
1749
|
+
let terminalResult;
|
|
1750
|
+
try {
|
|
1751
|
+
const execToolFn = options?.execTool ?? tools_1.execTool;
|
|
1752
|
+
terminalResult = await execToolFn(soleTerminalCall.name, terminalArgs, augmentedToolContext);
|
|
1753
|
+
}
|
|
1754
|
+
catch (error) {
|
|
1755
|
+
callbacks.onToolEnd(soleTerminalCall.name, (0, tools_1.summarizeArgs)(soleTerminalCall.name, terminalArgs), false);
|
|
1756
|
+
pushGenerated(msg);
|
|
1757
|
+
const failure = error instanceof Error ? `error: ${error.message}` : `error: ${String(error)}`;
|
|
1758
|
+
pushGenerated({ role: "tool", tool_call_id: soleTerminalCall.id, content: failure });
|
|
1759
|
+
providerRuntime.appendToolOutput(soleTerminalCall.id, failure);
|
|
1760
|
+
callbacks.onTextChunk(failure);
|
|
1761
|
+
completion = { answer: failure, intent: "blocked" };
|
|
1762
|
+
outcome = "blocked";
|
|
1763
|
+
done = true;
|
|
1764
|
+
continue;
|
|
1765
|
+
}
|
|
1766
|
+
callbacks.onToolEnd(soleTerminalCall.name, (0, tools_1.summarizeArgs)(soleTerminalCall.name, terminalArgs), true);
|
|
1812
1767
|
pushGenerated(msg);
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1768
|
+
pushGenerated({ role: "tool", tool_call_id: soleTerminalCall.id, content: terminalResult });
|
|
1769
|
+
providerRuntime.appendToolOutput(soleTerminalCall.id, terminalResult);
|
|
1770
|
+
callbacks.onTextChunk(terminalResult);
|
|
1771
|
+
completion = { answer: terminalResult, intent: "complete" };
|
|
1816
1772
|
outcome = "settled";
|
|
1817
1773
|
done = true;
|
|
1818
1774
|
continue;
|
|
1819
1775
|
}
|
|
1820
|
-
//
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
}
|
|
1837
|
-
|
|
1776
|
+
// Check for settle sole call: intercept before tool execution
|
|
1777
|
+
if (isSoleSettle) {
|
|
1778
|
+
const settleArgs = validatedCallArguments.get(result.toolCalls[0]);
|
|
1779
|
+
callbacks.onToolStart("settle", settleArgs);
|
|
1780
|
+
const requiredToolCallsGate = pendingRequiredToolCalls();
|
|
1781
|
+
if (requiredToolCallsGate) {
|
|
1782
|
+
streamCallbackBuffer?.discard();
|
|
1783
|
+
callbacks.onToolEnd("settle", (0, tools_1.summarizeArgs)("settle", settleArgs), false);
|
|
1784
|
+
callbacks.onClearText?.();
|
|
1785
|
+
queueRequiredCorrection(requiredToolCallsGate.message, "before required tool calls completed");
|
|
1786
|
+
(0, runtime_1.emitNervesEvent)({
|
|
1787
|
+
level: "warn",
|
|
1788
|
+
component: "engine",
|
|
1789
|
+
event: "engine.required_tool_calls_pending",
|
|
1790
|
+
message: "settle rejected until required tool handlers dispatch",
|
|
1791
|
+
meta: { missingToolNames: requiredToolCallsGate.missing },
|
|
1792
|
+
});
|
|
1793
|
+
continue;
|
|
1838
1794
|
}
|
|
1839
|
-
|
|
1795
|
+
// Private-runtime attention queue gate: reject settle if items remain
|
|
1796
|
+
const attentionQueue = augmentedToolContext?.delegatedOrigins;
|
|
1797
|
+
if (isPrivateRuntimeChannel && attentionQueue && attentionQueue.length > 0) {
|
|
1798
|
+
streamCallbackBuffer?.discard();
|
|
1840
1799
|
callbacks.onToolEnd("settle", (0, tools_1.summarizeArgs)("settle", settleArgs), false);
|
|
1800
|
+
callbacks.onClearText?.();
|
|
1801
|
+
pushGenerated(msg);
|
|
1802
|
+
const gateMessage = "current held-work frame still has unsurfaced items — return each listed item with surface(delegationId=...) before you settle. Older transcript claims are historical; only the current held-work frame is the gate.";
|
|
1803
|
+
pushGenerated({ role: "tool", tool_call_id: result.toolCalls[0].id, content: gateMessage });
|
|
1804
|
+
providerRuntime.appendToolOutput(result.toolCalls[0].id, gateMessage);
|
|
1805
|
+
continue;
|
|
1806
|
+
}
|
|
1807
|
+
// Extract answer from the tool call arguments.
|
|
1808
|
+
// Supports: {"answer":"text","intent":"..."} or "text" (JSON string).
|
|
1809
|
+
const { answer, intent } = parseSettlePayload(result.toolCalls[0].arguments);
|
|
1810
|
+
const requiredAnswerRejection = options?.requiredToolCalls?.validateTerminalAnswer?.(answer);
|
|
1811
|
+
if (requiredAnswerRejection) {
|
|
1841
1812
|
streamCallbackBuffer?.discard();
|
|
1842
|
-
|
|
1813
|
+
callbacks.onToolEnd("settle", (0, tools_1.summarizeArgs)("settle", settleArgs), false);
|
|
1814
|
+
callbacks.onClearText?.();
|
|
1815
|
+
queueRequiredCorrection(requiredAnswerRejection, "before required terminal answer validation completed");
|
|
1816
|
+
(0, runtime_1.emitNervesEvent)({ level: "warn", component: "engine", event: "engine.required_tool_answer_rejected", message: "unsupported settle answer rejected after required reads", meta: { answerLength: answer.length } });
|
|
1843
1817
|
continue;
|
|
1844
1818
|
}
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1819
|
+
// Private-runtime settle: no CompletionMetadata, "(settled)" ack
|
|
1820
|
+
if (isPrivateRuntimeChannel) {
|
|
1821
|
+
streamCallbackBuffer?.discard();
|
|
1822
|
+
callbacks.onToolEnd("settle", (0, tools_1.summarizeArgs)("settle", settleArgs), true);
|
|
1823
|
+
pushGenerated(msg);
|
|
1824
|
+
const settled = "(settled)";
|
|
1825
|
+
pushGenerated({ role: "tool", tool_call_id: result.toolCalls[0].id, content: settled });
|
|
1826
|
+
providerRuntime.appendToolOutput(result.toolCalls[0].id, settled);
|
|
1827
|
+
outcome = "settled";
|
|
1828
|
+
done = true;
|
|
1829
|
+
continue;
|
|
1830
|
+
}
|
|
1831
|
+
// The provider finalizer has already established a top-level string
|
|
1832
|
+
// answer before ordinary settle handling reaches this point.
|
|
1833
|
+
const deliveredAnswer = answer;
|
|
1834
|
+
const retryError = privateReturnAckLeakError(deliveredAnswer, privateReturnHeldTokens)
|
|
1835
|
+
?? privateReturnMissingPonderError({
|
|
1836
|
+
latestUserRequest: latestUserMessageText(messages),
|
|
1837
|
+
answer: deliveredAnswer,
|
|
1838
|
+
sawPonder,
|
|
1839
|
+
})
|
|
1840
|
+
?? getSettleRetryError(mustResolveBeforeHandoffActive, intent, sawSteeringFollowUp, options?.delegationDecision, sawSendMessageSelf, sawPonder, sawQuerySession, options?.currentObligation ?? null, options?.activeWorkFrame?.inner?.job, sawExternalStateQuery);
|
|
1841
|
+
const validDirectReply = mustResolveBeforeHandoffActive && intent === "direct_reply" && sawSteeringFollowUp;
|
|
1842
|
+
if (retryError === null) {
|
|
1843
|
+
try {
|
|
1844
|
+
if (!result.settleStreamed) {
|
|
1845
|
+
const acceptedOutputCallbacks = streamCallbackBuffer?.callbacks ?? callbacks;
|
|
1846
|
+
acceptedOutputCallbacks.onTextChunk(deliveredAnswer);
|
|
1847
|
+
}
|
|
1848
|
+
await streamCallbackBuffer?.flush();
|
|
1849
|
+
}
|
|
1850
|
+
catch (error) {
|
|
1851
|
+
callbacks.onToolEnd("settle", (0, tools_1.summarizeArgs)("settle", settleArgs), false);
|
|
1852
|
+
streamCallbackBuffer?.discard();
|
|
1853
|
+
finishTerminalProviderError(new streaming_1.SettleFinalizationCallbackError(error), "unknown");
|
|
1854
|
+
continue;
|
|
1855
|
+
}
|
|
1856
|
+
callbacks.onToolEnd("settle", (0, tools_1.summarizeArgs)("settle", settleArgs), true);
|
|
1857
|
+
completion = {
|
|
1858
|
+
answer: deliveredAnswer,
|
|
1859
|
+
intent: validDirectReply ? "direct_reply" : intent === "blocked" ? "blocked" : "complete",
|
|
1860
|
+
};
|
|
1861
|
+
// Retractable owners already hold the validated answer. Final-only
|
|
1862
|
+
// owners receive it here, after every semantic continuation gate.
|
|
1863
|
+
pushGenerated(msg);
|
|
1864
|
+
if (validDirectReply) {
|
|
1865
|
+
const resumeWork = "direct reply delivered. resume the unresolved obligation now and keep working until you can finish or clearly report that you are blocked.";
|
|
1866
|
+
pushGenerated({ role: "tool", tool_call_id: result.toolCalls[0].id, content: resumeWork });
|
|
1867
|
+
providerRuntime.appendToolOutput(result.toolCalls[0].id, resumeWork);
|
|
1868
|
+
}
|
|
1869
|
+
else {
|
|
1870
|
+
const delivered = "(delivered)";
|
|
1871
|
+
pushGenerated({ role: "tool", tool_call_id: result.toolCalls[0].id, content: delivered });
|
|
1872
|
+
providerRuntime.appendToolOutput(result.toolCalls[0].id, delivered);
|
|
1873
|
+
outcome = intent === "blocked" ? "blocked" : "settled";
|
|
1874
|
+
done = true;
|
|
1875
|
+
}
|
|
1857
1876
|
}
|
|
1858
1877
|
else {
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1878
|
+
// The payload is structurally final, but a semantic continuation
|
|
1879
|
+
// gate rejected it. Return that exact gate reason to the model.
|
|
1880
|
+
streamCallbackBuffer?.discard();
|
|
1881
|
+
callbacks.onToolEnd("settle", (0, tools_1.summarizeArgs)("settle", settleArgs), false);
|
|
1882
|
+
callbacks.onClearText?.();
|
|
1883
|
+
pushGenerated(msg);
|
|
1884
|
+
const toolRetryMessage = retryError;
|
|
1885
|
+
pushGenerated({ role: "tool", tool_call_id: result.toolCalls[0].id, content: toolRetryMessage });
|
|
1886
|
+
providerRuntime.appendToolOutput(result.toolCalls[0].id, toolRetryMessage);
|
|
1864
1887
|
}
|
|
1888
|
+
continue;
|
|
1865
1889
|
}
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1890
|
+
// Check for observe sole call: intercept before tool execution
|
|
1891
|
+
const isSoleObserve = result.toolCalls.length === 1 && result.toolCalls[0].name === "observe";
|
|
1892
|
+
if (isSoleObserve) {
|
|
1869
1893
|
streamCallbackBuffer?.discard();
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
streamCallbackBuffer?.discard();
|
|
1883
|
-
const observeArgs = validatedCallArguments.get(result.toolCalls[0]);
|
|
1884
|
-
let reason;
|
|
1885
|
-
if (typeof observeArgs?.reason === "string")
|
|
1886
|
-
reason = observeArgs.reason;
|
|
1887
|
-
callbacks.onToolStart("observe", observeArgs);
|
|
1888
|
-
(0, runtime_1.emitNervesEvent)({
|
|
1889
|
-
component: "engine",
|
|
1890
|
-
event: "engine.observe",
|
|
1891
|
-
message: "agent observed without responding",
|
|
1892
|
-
meta: { ...(reason ? { reason } : {}) },
|
|
1893
|
-
});
|
|
1894
|
-
callbacks.onToolEnd("observe", (0, tools_1.summarizeArgs)("observe", observeArgs), true);
|
|
1895
|
-
pushGenerated(msg);
|
|
1896
|
-
const silenced = "(silenced)";
|
|
1897
|
-
pushGenerated({ role: "tool", tool_call_id: result.toolCalls[0].id, content: silenced });
|
|
1898
|
-
providerRuntime.appendToolOutput(result.toolCalls[0].id, silenced);
|
|
1899
|
-
outcome = "observed";
|
|
1900
|
-
done = true;
|
|
1901
|
-
continue;
|
|
1902
|
-
}
|
|
1903
|
-
// Check for rest sole call: intercept before tool execution
|
|
1904
|
-
const isSoleRest = result.toolCalls.length === 1 && result.toolCalls[0].name === "rest";
|
|
1905
|
-
if (isSoleRest) {
|
|
1906
|
-
streamCallbackBuffer?.discard();
|
|
1907
|
-
const restArgs = validatedCallArguments.get(result.toolCalls[0]);
|
|
1908
|
-
callbacks.onToolStart("rest", restArgs);
|
|
1909
|
-
// Attention queue gate: reject rest if items remain
|
|
1910
|
-
const attentionQueue = augmentedToolContext?.delegatedOrigins;
|
|
1911
|
-
if (attentionQueue && attentionQueue.length > 0) {
|
|
1912
|
-
callbacks.onToolEnd("rest", (0, tools_1.summarizeArgs)("rest", restArgs), false);
|
|
1894
|
+
const observeArgs = validatedCallArguments.get(result.toolCalls[0]);
|
|
1895
|
+
let reason;
|
|
1896
|
+
if (typeof observeArgs?.reason === "string")
|
|
1897
|
+
reason = observeArgs.reason;
|
|
1898
|
+
callbacks.onToolStart("observe", observeArgs);
|
|
1899
|
+
(0, runtime_1.emitNervesEvent)({
|
|
1900
|
+
component: "engine",
|
|
1901
|
+
event: "engine.observe",
|
|
1902
|
+
message: "agent observed without responding",
|
|
1903
|
+
meta: { ...(reason ? { reason } : {}) },
|
|
1904
|
+
});
|
|
1905
|
+
callbacks.onToolEnd("observe", (0, tools_1.summarizeArgs)("observe", observeArgs), true);
|
|
1913
1906
|
pushGenerated(msg);
|
|
1914
|
-
const
|
|
1915
|
-
pushGenerated({ role: "tool", tool_call_id: result.toolCalls[0].id, content:
|
|
1916
|
-
providerRuntime.appendToolOutput(result.toolCalls[0].id,
|
|
1907
|
+
const silenced = "(silenced)";
|
|
1908
|
+
pushGenerated({ role: "tool", tool_call_id: result.toolCalls[0].id, content: silenced });
|
|
1909
|
+
providerRuntime.appendToolOutput(result.toolCalls[0].id, silenced);
|
|
1910
|
+
outcome = "observed";
|
|
1911
|
+
done = true;
|
|
1917
1912
|
continue;
|
|
1918
1913
|
}
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1914
|
+
// Check for rest sole call: intercept before tool execution
|
|
1915
|
+
const isSoleRest = result.toolCalls.length === 1 && result.toolCalls[0].name === "rest";
|
|
1916
|
+
if (isSoleRest) {
|
|
1917
|
+
streamCallbackBuffer?.discard();
|
|
1918
|
+
const restArgs = validatedCallArguments.get(result.toolCalls[0]);
|
|
1919
|
+
callbacks.onToolStart("rest", restArgs);
|
|
1920
|
+
// Attention queue gate: reject rest if items remain
|
|
1921
|
+
const attentionQueue = augmentedToolContext?.delegatedOrigins;
|
|
1922
|
+
if (attentionQueue && attentionQueue.length > 0) {
|
|
1923
|
+
callbacks.onToolEnd("rest", (0, tools_1.summarizeArgs)("rest", restArgs), false);
|
|
1924
|
+
pushGenerated(msg);
|
|
1925
|
+
const gateMessage = "current held-work frame still has unsurfaced items — return each listed item with surface(delegationId=...) before you rest. Older transcript claims are historical; only the current held-work frame is the gate.";
|
|
1926
|
+
pushGenerated({ role: "tool", tool_call_id: result.toolCalls[0].id, content: gateMessage });
|
|
1927
|
+
providerRuntime.appendToolOutput(result.toolCalls[0].id, gateMessage);
|
|
1928
|
+
continue;
|
|
1929
|
+
}
|
|
1930
|
+
if (hasFreshPendingWork(options) && !freshWorkGateFired) {
|
|
1931
|
+
freshWorkGateFired = true;
|
|
1932
|
+
callbacks.onToolEnd("rest", (0, tools_1.summarizeArgs)("rest", restArgs), false);
|
|
1933
|
+
pushGenerated(msg);
|
|
1934
|
+
const gateMessage = "fresh work arrived for me this turn — inspect the pending messages above and take the next concrete action before you rest.";
|
|
1935
|
+
pushGenerated({ role: "tool", tool_call_id: result.toolCalls[0].id, content: gateMessage });
|
|
1936
|
+
providerRuntime.appendToolOutput(result.toolCalls[0].id, gateMessage);
|
|
1937
|
+
(0, runtime_1.emitNervesEvent)({
|
|
1938
|
+
level: "info",
|
|
1939
|
+
component: "engine",
|
|
1940
|
+
event: "engine.fresh_work_gate_fired",
|
|
1941
|
+
message: "rest deferred once because pending work arrived this turn; agent has been notified",
|
|
1942
|
+
meta: { pendingCount: options.pendingMessages.length },
|
|
1943
|
+
});
|
|
1944
|
+
continue;
|
|
1945
|
+
}
|
|
1946
|
+
callbacks.onToolEnd("rest", (0, tools_1.summarizeArgs)("rest", restArgs), true);
|
|
1922
1947
|
pushGenerated(msg);
|
|
1923
|
-
const
|
|
1924
|
-
pushGenerated({ role: "tool", tool_call_id: result.toolCalls[0].id, content:
|
|
1925
|
-
providerRuntime.appendToolOutput(result.toolCalls[0].id,
|
|
1948
|
+
const ack = "(resting)";
|
|
1949
|
+
pushGenerated({ role: "tool", tool_call_id: result.toolCalls[0].id, content: ack });
|
|
1950
|
+
providerRuntime.appendToolOutput(result.toolCalls[0].id, ack);
|
|
1926
1951
|
(0, runtime_1.emitNervesEvent)({
|
|
1927
|
-
level: "info",
|
|
1928
1952
|
component: "engine",
|
|
1929
|
-
event: "engine.
|
|
1930
|
-
message: "
|
|
1931
|
-
meta: {
|
|
1953
|
+
event: "engine.rested",
|
|
1954
|
+
message: "resting until next heartbeat",
|
|
1955
|
+
meta: { ...(typeof restArgs?.status === "string" ? { status: restArgs.status } : {}) },
|
|
1932
1956
|
});
|
|
1957
|
+
outcome = "rested";
|
|
1958
|
+
done = true;
|
|
1933
1959
|
continue;
|
|
1934
1960
|
}
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
component: "engine",
|
|
1942
|
-
event: "engine.rested",
|
|
1943
|
-
message: "resting until next heartbeat",
|
|
1944
|
-
meta: { ...(typeof restArgs?.status === "string" ? { status: restArgs.status } : {}) },
|
|
1945
|
-
});
|
|
1946
|
-
outcome = "rested";
|
|
1947
|
-
done = true;
|
|
1948
|
-
continue;
|
|
1949
|
-
}
|
|
1950
|
-
const requiredDispatchRejections = new Map();
|
|
1951
|
-
for (const entry of validCalls) {
|
|
1952
|
-
const requiredArgs = entry.validated.arguments;
|
|
1953
|
-
const rejection = options?.requiredToolCalls?.validateToolCallBeforeDispatch?.(entry.call.name, requiredArgs);
|
|
1954
|
-
if (rejection)
|
|
1955
|
-
requiredDispatchRejections.set(entry.call.id, { name: entry.call.name, args: requiredArgs, message: rejection });
|
|
1956
|
-
}
|
|
1957
|
-
const approvalCalls = await Promise.all(validCalls.filter((entry) => !requiredDispatchRejections.has(entry.call.id)).map(async (entry) => {
|
|
1958
|
-
const classification = await (0, tools_1.classifyApprovalForInvocation)(entry.call.name, entry.validated.arguments, augmentedToolContext);
|
|
1959
|
-
return {
|
|
1960
|
-
...entry,
|
|
1961
|
-
...classification,
|
|
1962
|
-
};
|
|
1963
|
-
}));
|
|
1964
|
-
const protectedCall = approvalCalls.find((entry) => entry.policy.kind === "required");
|
|
1965
|
-
if (protectedCall && result.toolCalls.length !== 1) {
|
|
1966
|
-
streamCallbackBuffer?.discard();
|
|
1967
|
-
pushGenerated(msg);
|
|
1968
|
-
for (const call of result.toolCalls) {
|
|
1969
|
-
const rejection = "rejected: approval-eligible tool must be the sole call; no call in this batch was executed.";
|
|
1970
|
-
pushGenerated({ role: "tool", tool_call_id: call.id, content: rejection });
|
|
1971
|
-
providerRuntime.appendToolOutput(call.id, rejection);
|
|
1961
|
+
const requiredDispatchRejections = new Map();
|
|
1962
|
+
for (const entry of validCalls) {
|
|
1963
|
+
const requiredArgs = entry.validated.arguments;
|
|
1964
|
+
const rejection = options?.requiredToolCalls?.validateToolCallBeforeDispatch?.(entry.call.name, requiredArgs);
|
|
1965
|
+
if (rejection)
|
|
1966
|
+
requiredDispatchRejections.set(entry.call.id, { name: entry.call.name, args: requiredArgs, message: rejection });
|
|
1972
1967
|
}
|
|
1973
|
-
(
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
});
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
if (protectedCall && protectedCall.policy.kind === "required") {
|
|
1983
|
-
if (!options?.approvalCoordinator) {
|
|
1968
|
+
const approvalCalls = await Promise.all(validCalls.filter((entry) => !requiredDispatchRejections.has(entry.call.id)).map(async (entry) => {
|
|
1969
|
+
const classification = await (0, tools_1.classifyApprovalForInvocation)(entry.call.name, entry.validated.arguments, augmentedToolContext);
|
|
1970
|
+
return {
|
|
1971
|
+
...entry,
|
|
1972
|
+
...classification,
|
|
1973
|
+
};
|
|
1974
|
+
}));
|
|
1975
|
+
const protectedCall = approvalCalls.find((entry) => entry.policy.kind === "required");
|
|
1976
|
+
if (protectedCall && result.toolCalls.length !== 1) {
|
|
1984
1977
|
streamCallbackBuffer?.discard();
|
|
1985
1978
|
pushGenerated(msg);
|
|
1986
|
-
const
|
|
1987
|
-
|
|
1988
|
-
|
|
1979
|
+
for (const call of result.toolCalls) {
|
|
1980
|
+
const rejection = "rejected: approval-eligible tool must be the sole call; no call in this batch was executed.";
|
|
1981
|
+
pushGenerated({ role: "tool", tool_call_id: call.id, content: rejection });
|
|
1982
|
+
providerRuntime.appendToolOutput(call.id, rejection);
|
|
1983
|
+
}
|
|
1989
1984
|
(0, runtime_1.emitNervesEvent)({
|
|
1990
1985
|
level: "warn",
|
|
1991
1986
|
component: "engine",
|
|
1992
|
-
event: "engine.
|
|
1993
|
-
message: "protected tool
|
|
1994
|
-
meta: {
|
|
1987
|
+
event: "engine.approval_mixed_batch_rejected",
|
|
1988
|
+
message: "protected tool batch rejected before every handler",
|
|
1989
|
+
meta: { toolCallCount: result.toolCalls.length, protectedToolName: protectedCall.call.name },
|
|
1995
1990
|
});
|
|
1996
1991
|
continue;
|
|
1997
1992
|
}
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
toolDigest,
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
suspendedSessionRevision: committed.suspendedSessionRevision,
|
|
2026
|
-
};
|
|
2027
|
-
outcome = "suspended";
|
|
2028
|
-
done = true;
|
|
2029
|
-
(0, runtime_1.emitNervesEvent)({
|
|
2030
|
-
component: "engine",
|
|
2031
|
-
event: "engine.approval_turn_suspended",
|
|
2032
|
-
message: "agent turn suspended before protected tool execution",
|
|
2033
|
-
meta: { approvalId: committed.approvalId, toolCallId: protectedCall.call.id },
|
|
2034
|
-
});
|
|
2035
|
-
continue;
|
|
2036
|
-
}
|
|
2037
|
-
const containsSoleCallOnlyViolation = result.toolCalls.length > 1
|
|
2038
|
-
&& result.toolCalls.some((call) => {
|
|
2039
|
-
const terminalProjection = (0, tools_1.resolveToolDefinition)(call.name)?.terminalProjection;
|
|
2040
|
-
return SOLE_CALL_REJECTION[call.name] !== undefined
|
|
2041
|
-
|| terminalProjection?.requiresSoleCall === true;
|
|
2042
|
-
});
|
|
2043
|
-
if (callbacks.settleOutputMode === "final_only" && containsSoleCallOnlyViolation) {
|
|
2044
|
-
streamCallbackBuffer?.discard();
|
|
2045
|
-
}
|
|
2046
|
-
else {
|
|
2047
|
-
await streamCallbackBuffer?.flush();
|
|
2048
|
-
}
|
|
2049
|
-
pushGenerated(msg);
|
|
2050
|
-
// Execute tools (sole-call tools in mixed calls are rejected inline)
|
|
2051
|
-
for (const tc of result.toolCalls) {
|
|
2052
|
-
if (signal?.aborted)
|
|
2053
|
-
break;
|
|
2054
|
-
const requiredDispatchRejection = requiredDispatchRejections.get(tc.id);
|
|
2055
|
-
if (requiredDispatchRejection) {
|
|
2056
|
-
callbacks.onToolStart(tc.name, requiredDispatchRejection.args);
|
|
2057
|
-
callbacks.onToolEnd(tc.name, (0, tools_1.summarizeArgs)(tc.name, requiredDispatchRejection.args), false);
|
|
2058
|
-
pushGenerated({ role: "tool", tool_call_id: tc.id, content: requiredDispatchRejection.message });
|
|
2059
|
-
providerRuntime.appendToolOutput(tc.id, requiredDispatchRejection.message);
|
|
2060
|
-
options?.toolBoundaryObserver?.({
|
|
2061
|
-
name: tc.name,
|
|
2062
|
-
reason: "dependency_rejected",
|
|
2063
|
-
globallyResolvable: typeof (0, tools_1.resolveToolDefinition)(tc.name)?.handler === "function",
|
|
2064
|
-
invoked: false,
|
|
2065
|
-
sideEffect: false,
|
|
1993
|
+
if (protectedCall && protectedCall.policy.kind === "required") {
|
|
1994
|
+
if (!options?.approvalCoordinator) {
|
|
1995
|
+
streamCallbackBuffer?.discard();
|
|
1996
|
+
pushGenerated(msg);
|
|
1997
|
+
const rejection = "rejected: this protected tool requires approval, but the approval coordinator is unavailable; the handler was not invoked.";
|
|
1998
|
+
pushGenerated({ role: "tool", tool_call_id: protectedCall.call.id, content: rejection });
|
|
1999
|
+
providerRuntime.appendToolOutput(protectedCall.call.id, rejection);
|
|
2000
|
+
(0, runtime_1.emitNervesEvent)({
|
|
2001
|
+
level: "warn",
|
|
2002
|
+
component: "engine",
|
|
2003
|
+
event: "engine.approval_coordinator_unavailable",
|
|
2004
|
+
message: "protected tool failed closed before handler execution",
|
|
2005
|
+
meta: { toolName: protectedCall.call.name, toolCallId: protectedCall.call.id },
|
|
2006
|
+
});
|
|
2007
|
+
continue;
|
|
2008
|
+
}
|
|
2009
|
+
streamCallbackBuffer?.discard();
|
|
2010
|
+
pushGenerated(msg);
|
|
2011
|
+
const toolDigest = (0, tool_arguments_1.digestJson)({
|
|
2012
|
+
name: protectedCall.call.name,
|
|
2013
|
+
schemaDigest: protectedCall.validated.schemaDigest,
|
|
2014
|
+
policyId: protectedCall.policy.policyId,
|
|
2015
|
+
});
|
|
2016
|
+
const policyDigest = (0, tool_arguments_1.digestJson)({
|
|
2017
|
+
policyId: protectedCall.policy.policyId,
|
|
2018
|
+
actionClass: protectedCall.policy.actionClass,
|
|
2019
|
+
classification: "required",
|
|
2066
2020
|
});
|
|
2021
|
+
const committed = await options.approvalCoordinator.propose({
|
|
2022
|
+
toolCall: structuredClone(msg.tool_calls[0]),
|
|
2023
|
+
arguments: structuredClone(protectedCall.validated.arguments),
|
|
2024
|
+
preCallMessages,
|
|
2025
|
+
frozenAssistantMessage: structuredClone(msg),
|
|
2026
|
+
schemaDigest: protectedCall.validated.schemaDigest,
|
|
2027
|
+
toolDigest,
|
|
2028
|
+
policyDigest,
|
|
2029
|
+
policyId: protectedCall.policy.policyId,
|
|
2030
|
+
actionClass: protectedCall.policy.actionClass,
|
|
2031
|
+
});
|
|
2032
|
+
suspension = {
|
|
2033
|
+
approvalId: committed.approvalId,
|
|
2034
|
+
toolCallId: protectedCall.call.id,
|
|
2035
|
+
checkpointDigest: committed.checkpointDigest,
|
|
2036
|
+
suspendedSessionRevision: committed.suspendedSessionRevision,
|
|
2037
|
+
};
|
|
2038
|
+
outcome = "suspended";
|
|
2039
|
+
done = true;
|
|
2067
2040
|
(0, runtime_1.emitNervesEvent)({
|
|
2068
|
-
level: "warn",
|
|
2069
2041
|
component: "engine",
|
|
2070
|
-
event: "engine.
|
|
2071
|
-
message: "
|
|
2072
|
-
meta: {
|
|
2042
|
+
event: "engine.approval_turn_suspended",
|
|
2043
|
+
message: "agent turn suspended before protected tool execution",
|
|
2044
|
+
meta: { approvalId: committed.approvalId, toolCallId: protectedCall.call.id },
|
|
2073
2045
|
});
|
|
2074
2046
|
continue;
|
|
2075
2047
|
}
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
if (
|
|
2083
|
-
|
|
2084
|
-
providerRuntime.appendToolOutput(tc.id, soleCallRejection);
|
|
2085
|
-
continue;
|
|
2086
|
-
}
|
|
2087
|
-
const args = validatedCallArguments.get(tc);
|
|
2088
|
-
const currentEffectFingerprint = effectFingerprint(tc.name, tc.arguments);
|
|
2089
|
-
if (forcingHistoricalEffect
|
|
2090
|
-
&& currentEffectFingerprint
|
|
2091
|
-
&& !unresolvedHistoricalEffects.some((effect) => effect.fingerprint === currentEffectFingerprint)) {
|
|
2092
|
-
const rejection = "rejected: this turn is retrying an unresolved historical effect, and these mutation arguments do not match it. Read current state or retry the exact failed effect.";
|
|
2093
|
-
callbacks.onToolStart(tc.name, args);
|
|
2094
|
-
callbacks.onToolEnd(tc.name, (0, tools_1.summarizeArgs)(tc.name, args), false);
|
|
2095
|
-
pushGenerated({ role: "tool", tool_call_id: tc.id, content: rejection });
|
|
2096
|
-
providerRuntime.appendToolOutput(tc.id, rejection);
|
|
2097
|
-
continue;
|
|
2048
|
+
const containsSoleCallOnlyViolation = result.toolCalls.length > 1
|
|
2049
|
+
&& result.toolCalls.some((call) => {
|
|
2050
|
+
const terminalProjection = (0, tools_1.resolveToolDefinition)(call.name)?.terminalProjection;
|
|
2051
|
+
return SOLE_CALL_REJECTION[call.name] !== undefined
|
|
2052
|
+
|| terminalProjection?.requiresSoleCall === true;
|
|
2053
|
+
});
|
|
2054
|
+
if (callbacks.settleOutputMode === "final_only" && containsSoleCallOnlyViolation) {
|
|
2055
|
+
streamCallbackBuffer?.discard();
|
|
2098
2056
|
}
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
if (!isPrivateRuntimeChannel && looksLikePrivateReturnRequest(latestUserText)) {
|
|
2102
|
-
const argSummary = (0, tools_1.summarizeArgs)(tc.name, args);
|
|
2103
|
-
const rejection = "private-return requests must use ponder, not send_message(friendId=self). Create a typed ponder packet with the marker/source request preserved, then only acknowledge that the private pass is queued.";
|
|
2104
|
-
callbacks.onToolStart(tc.name, args);
|
|
2105
|
-
callbacks.onToolEnd(tc.name, argSummary, false);
|
|
2106
|
-
pushGenerated({ role: "tool", tool_call_id: tc.id, content: rejection });
|
|
2107
|
-
providerRuntime.appendToolOutput(tc.id, rejection);
|
|
2108
|
-
continue;
|
|
2109
|
-
}
|
|
2110
|
-
sawSendMessageSelf = true;
|
|
2057
|
+
else {
|
|
2058
|
+
await streamCallbackBuffer?.flush();
|
|
2111
2059
|
}
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
if (
|
|
2119
|
-
|
|
2120
|
-
callbacks.onToolEnd(
|
|
2121
|
-
pushGenerated({ role: "tool", tool_call_id: tc.id, content:
|
|
2122
|
-
providerRuntime.appendToolOutput(tc.id,
|
|
2060
|
+
pushGenerated(msg);
|
|
2061
|
+
// Execute tools (sole-call tools in mixed calls are rejected inline)
|
|
2062
|
+
for (const tc of result.toolCalls) {
|
|
2063
|
+
if (signal?.aborted)
|
|
2064
|
+
break;
|
|
2065
|
+
const requiredDispatchRejection = requiredDispatchRejections.get(tc.id);
|
|
2066
|
+
if (requiredDispatchRejection) {
|
|
2067
|
+
callbacks.onToolStart(tc.name, requiredDispatchRejection.args);
|
|
2068
|
+
callbacks.onToolEnd(tc.name, (0, tools_1.summarizeArgs)(tc.name, requiredDispatchRejection.args), false);
|
|
2069
|
+
pushGenerated({ role: "tool", tool_call_id: tc.id, content: requiredDispatchRejection.message });
|
|
2070
|
+
providerRuntime.appendToolOutput(tc.id, requiredDispatchRejection.message);
|
|
2071
|
+
options?.toolBoundaryObserver?.({
|
|
2072
|
+
name: tc.name,
|
|
2073
|
+
reason: "dependency_rejected",
|
|
2074
|
+
globallyResolvable: typeof (0, tools_1.resolveToolDefinition)(tc.name)?.handler === "function",
|
|
2075
|
+
invoked: false,
|
|
2076
|
+
sideEffect: false,
|
|
2077
|
+
});
|
|
2123
2078
|
(0, runtime_1.emitNervesEvent)({
|
|
2124
2079
|
level: "warn",
|
|
2125
2080
|
component: "engine",
|
|
2126
|
-
event: "engine.
|
|
2127
|
-
message: "
|
|
2128
|
-
meta: {},
|
|
2081
|
+
event: "engine.required_tool_dispatch_rejected",
|
|
2082
|
+
message: "required tool dependency rejected before approval and handler dispatch",
|
|
2083
|
+
meta: { toolName: tc.name },
|
|
2129
2084
|
});
|
|
2130
2085
|
continue;
|
|
2131
2086
|
}
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2087
|
+
// Reject sole-call tools when mixed with other tool calls
|
|
2088
|
+
const terminalProjection = (0, tools_1.resolveToolDefinition)(tc.name)?.terminalProjection;
|
|
2089
|
+
const soleCallRejection = SOLE_CALL_REJECTION[tc.name]
|
|
2090
|
+
?? (terminalProjection?.requiresSoleCall
|
|
2091
|
+
? `rejected: ${tc.name} must be the only tool call.`
|
|
2092
|
+
: undefined);
|
|
2093
|
+
if (soleCallRejection) {
|
|
2094
|
+
pushGenerated({ role: "tool", tool_call_id: tc.id, content: soleCallRejection });
|
|
2095
|
+
providerRuntime.appendToolOutput(tc.id, soleCallRejection);
|
|
2096
|
+
continue;
|
|
2136
2097
|
}
|
|
2137
|
-
|
|
2138
|
-
|
|
2098
|
+
const args = validatedCallArguments.get(tc);
|
|
2099
|
+
const currentEffectFingerprint = effectFingerprint(tc.name, tc.arguments);
|
|
2100
|
+
if (forcingHistoricalEffect
|
|
2101
|
+
&& currentEffectFingerprint
|
|
2102
|
+
&& !unresolvedHistoricalEffects.some((effect) => effect.fingerprint === currentEffectFingerprint)) {
|
|
2103
|
+
const rejection = "rejected: this turn is retrying an unresolved historical effect, and these mutation arguments do not match it. Read current state or retry the exact failed effect.";
|
|
2104
|
+
callbacks.onToolStart(tc.name, args);
|
|
2105
|
+
callbacks.onToolEnd(tc.name, (0, tools_1.summarizeArgs)(tc.name, args), false);
|
|
2106
|
+
pushGenerated({ role: "tool", tool_call_id: tc.id, content: rejection });
|
|
2107
|
+
providerRuntime.appendToolOutput(tc.id, rejection);
|
|
2108
|
+
continue;
|
|
2139
2109
|
}
|
|
2140
|
-
if (
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2110
|
+
if (tc.name === "send_message" && args.friendId === "self") {
|
|
2111
|
+
const latestUserText = latestUserMessageText(messages);
|
|
2112
|
+
if (!isPrivateRuntimeChannel && looksLikePrivateReturnRequest(latestUserText)) {
|
|
2113
|
+
const argSummary = (0, tools_1.summarizeArgs)(tc.name, args);
|
|
2114
|
+
const rejection = "private-return requests must use ponder, not send_message(friendId=self). Create a typed ponder packet with the marker/source request preserved, then only acknowledge that the private pass is queued.";
|
|
2115
|
+
callbacks.onToolStart(tc.name, args);
|
|
2116
|
+
callbacks.onToolEnd(tc.name, argSummary, false);
|
|
2117
|
+
pushGenerated({ role: "tool", tool_call_id: tc.id, content: rejection });
|
|
2118
|
+
providerRuntime.appendToolOutput(tc.id, rejection);
|
|
2119
|
+
continue;
|
|
2120
|
+
}
|
|
2121
|
+
sawSendMessageSelf = true;
|
|
2122
|
+
}
|
|
2123
|
+
if (tc.name === "speak") {
|
|
2124
|
+
// The canonical pre-batch schema gate guarantees a required string.
|
|
2125
|
+
const speakArgs = JSON.parse(tc.arguments);
|
|
2126
|
+
const speakMessage = speakArgs.message;
|
|
2127
|
+
const argSummary = (0, tools_1.summarizeArgs)("speak", { message: speakMessage });
|
|
2128
|
+
callbacks.onToolStart("speak", { message: speakMessage });
|
|
2129
|
+
if (speakMessage.trim().length === 0) {
|
|
2130
|
+
const err = "speak requires a non-empty `message` string.";
|
|
2131
|
+
callbacks.onToolEnd("speak", argSummary, false);
|
|
2132
|
+
pushGenerated({ role: "tool", tool_call_id: tc.id, content: err });
|
|
2133
|
+
providerRuntime.appendToolOutput(tc.id, err);
|
|
2134
|
+
(0, runtime_1.emitNervesEvent)({
|
|
2135
|
+
level: "warn",
|
|
2136
|
+
component: "engine",
|
|
2137
|
+
event: "engine.speak_invalid",
|
|
2138
|
+
message: "speak rejected: missing or empty message",
|
|
2139
|
+
meta: {},
|
|
2140
|
+
});
|
|
2141
|
+
continue;
|
|
2142
|
+
}
|
|
2143
|
+
callbacks.onTextChunk(speakMessage);
|
|
2144
|
+
let speakDeliveryError = null;
|
|
2145
|
+
try {
|
|
2146
|
+
await callbacks.flushNow?.();
|
|
2147
|
+
}
|
|
2148
|
+
catch (err) {
|
|
2149
|
+
speakDeliveryError = err instanceof Error ? err : new Error(String(err));
|
|
2150
|
+
}
|
|
2151
|
+
if (speakDeliveryError) {
|
|
2152
|
+
callbacks.onToolEnd("speak", argSummary, false);
|
|
2153
|
+
const failMsg = `speak delivery failed: ${speakDeliveryError.message}. the message did not reach your friend; do not assume they saw it.`;
|
|
2154
|
+
pushGenerated({ role: "tool", tool_call_id: tc.id, content: failMsg });
|
|
2155
|
+
providerRuntime.appendToolOutput(tc.id, failMsg);
|
|
2156
|
+
(0, runtime_1.emitNervesEvent)({
|
|
2157
|
+
level: "error",
|
|
2158
|
+
component: "engine",
|
|
2159
|
+
event: "engine.speak_delivery_failed",
|
|
2160
|
+
message: "speak delivery failed",
|
|
2161
|
+
meta: { error: speakDeliveryError.message, messageLength: speakMessage.length },
|
|
2162
|
+
});
|
|
2163
|
+
continue;
|
|
2164
|
+
}
|
|
2165
|
+
callbacks.onToolEnd("speak", argSummary, true);
|
|
2166
|
+
const ack = "(spoken)";
|
|
2167
|
+
pushGenerated({ role: "tool", tool_call_id: tc.id, content: ack });
|
|
2168
|
+
providerRuntime.appendToolOutput(tc.id, ack);
|
|
2145
2169
|
(0, runtime_1.emitNervesEvent)({
|
|
2146
|
-
level: "error",
|
|
2147
2170
|
component: "engine",
|
|
2148
|
-
event: "engine.
|
|
2149
|
-
message: "
|
|
2150
|
-
meta: {
|
|
2171
|
+
event: "engine.speak",
|
|
2172
|
+
message: "agent spoke mid-turn",
|
|
2173
|
+
meta: { messageLength: speakMessage.length },
|
|
2151
2174
|
});
|
|
2152
2175
|
continue;
|
|
2153
2176
|
}
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
const summary = typeof parsedArgs.summary === "string" ? parsedArgs.summary.trim() : "";
|
|
2194
|
-
const sourceRequest = currentOrigin && !isInnerChannel ? latestUserMessageText(messages) : "";
|
|
2195
|
-
privateReturnSourceRequest = sourceRequest;
|
|
2196
|
-
if (!kind || !objective || !successCriteria || !payload) {
|
|
2197
|
-
throw new Error("ponder create requires kind, objective, success_criteria, and valid payload_json.");
|
|
2198
|
-
}
|
|
2199
|
-
const packetPayload = sourceRequest
|
|
2200
|
-
? { ...payload, sourceRequest }
|
|
2201
|
-
: payload;
|
|
2202
|
-
const createLinkedReturnObligation = (id, packetId) => {
|
|
2203
|
-
(0, obligations_1.createReturnObligation)((0, identity_2.getAgentName)(), {
|
|
2204
|
-
id,
|
|
2205
|
-
origin: currentOrigin,
|
|
2206
|
-
status: "queued",
|
|
2207
|
-
delegatedContent: buildPonderDelegatedContent({ summary, objective, sourceRequest }),
|
|
2208
|
-
packetId,
|
|
2209
|
-
createdAt: Date.now(),
|
|
2210
|
-
});
|
|
2211
|
-
};
|
|
2212
|
-
const agentRoot = (0, identity_2.getAgentRoot)();
|
|
2213
|
-
let relatedObligationId;
|
|
2214
|
-
if (currentOrigin && !isInnerChannel) {
|
|
2215
|
-
try {
|
|
2216
|
-
const obligation = (0, obligations_1.createObligation)(agentRoot, {
|
|
2177
|
+
if (tc.name === "ponder") {
|
|
2178
|
+
const parsedArgs = normalizeLegacyPonderArgs(parsePonderPayload(tc.arguments));
|
|
2179
|
+
const argSummary = (0, tools_1.summarizeArgs)(tc.name, parsedArgs);
|
|
2180
|
+
callbacks.onToolStart(tc.name, parsedArgs);
|
|
2181
|
+
let toolResult;
|
|
2182
|
+
let success = false;
|
|
2183
|
+
try {
|
|
2184
|
+
const action = parsedArgs.action ?? "create";
|
|
2185
|
+
const currentSession = augmentedToolContext?.currentSession;
|
|
2186
|
+
const currentOrigin = currentSession
|
|
2187
|
+
? { friendId: currentSession.friendId, channel: currentSession.channel, key: currentSession.key }
|
|
2188
|
+
: undefined;
|
|
2189
|
+
const isInnerChannel = currentOrigin?.friendId === "self" && currentOrigin?.channel === "inner";
|
|
2190
|
+
const shouldCreateReturnObligation = !!currentOrigin && !isInnerChannel;
|
|
2191
|
+
const attentionQueue = augmentedToolContext?.delegatedOrigins ?? [];
|
|
2192
|
+
const successCriteria = parseSuccessCriteria(parsedArgs.success_criteria);
|
|
2193
|
+
const payload = parsePacketPayload(parsedArgs.payload_json);
|
|
2194
|
+
let packet;
|
|
2195
|
+
let returnObligationId = null;
|
|
2196
|
+
let resultAction = "created";
|
|
2197
|
+
let privateReturnSourceRequest = "";
|
|
2198
|
+
if (action === "create") {
|
|
2199
|
+
if (isInnerChannel && attentionQueue.length > 0) {
|
|
2200
|
+
throw new Error("private runtime already has held return work in the attention queue; surface the existing delegationId instead of creating a replacement ponder packet.");
|
|
2201
|
+
}
|
|
2202
|
+
const kind = parsedArgs.kind;
|
|
2203
|
+
const objective = typeof parsedArgs.objective === "string" ? parsedArgs.objective.trim() : "";
|
|
2204
|
+
const summary = typeof parsedArgs.summary === "string" ? parsedArgs.summary.trim() : "";
|
|
2205
|
+
const sourceRequest = currentOrigin && !isInnerChannel ? latestUserMessageText(messages) : "";
|
|
2206
|
+
privateReturnSourceRequest = sourceRequest;
|
|
2207
|
+
if (!kind || !objective || !successCriteria || !payload) {
|
|
2208
|
+
throw new Error("ponder create requires kind, objective, success_criteria, and valid payload_json.");
|
|
2209
|
+
}
|
|
2210
|
+
const packetPayload = sourceRequest
|
|
2211
|
+
? { ...payload, sourceRequest }
|
|
2212
|
+
: payload;
|
|
2213
|
+
const createLinkedReturnObligation = (id, packetId) => {
|
|
2214
|
+
(0, obligations_1.createReturnObligation)((0, identity_2.getAgentName)(), {
|
|
2215
|
+
id,
|
|
2217
2216
|
origin: currentOrigin,
|
|
2218
|
-
|
|
2217
|
+
status: "queued",
|
|
2218
|
+
delegatedContent: buildPonderDelegatedContent({ summary, objective, sourceRequest }),
|
|
2219
|
+
packetId,
|
|
2220
|
+
createdAt: Date.now(),
|
|
2219
2221
|
});
|
|
2220
|
-
|
|
2222
|
+
};
|
|
2223
|
+
const agentRoot = (0, identity_2.getAgentRoot)();
|
|
2224
|
+
let relatedObligationId;
|
|
2225
|
+
if (currentOrigin && !isInnerChannel) {
|
|
2226
|
+
try {
|
|
2227
|
+
const obligation = (0, obligations_1.createObligation)(agentRoot, {
|
|
2228
|
+
origin: currentOrigin,
|
|
2229
|
+
content: objective,
|
|
2230
|
+
});
|
|
2231
|
+
relatedObligationId = obligation.id;
|
|
2232
|
+
}
|
|
2233
|
+
catch {
|
|
2234
|
+
relatedObligationId = undefined;
|
|
2235
|
+
}
|
|
2221
2236
|
}
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
? packetPayload.frictionSignature
|
|
2228
|
-
: null;
|
|
2229
|
-
const existing = frictionSignature && currentOrigin
|
|
2230
|
-
? (0, packets_1.findHarnessFrictionPacket)(agentRoot, currentOrigin, frictionSignature)
|
|
2231
|
-
: null;
|
|
2232
|
-
if (existing) {
|
|
2233
|
-
resultAction = "revised";
|
|
2234
|
-
const existingActiveReturnId = shouldCreateReturnObligation
|
|
2235
|
-
? activeReturnObligationId((0, identity_2.getAgentName)(), existing.relatedReturnObligationId)
|
|
2237
|
+
const frictionSignature = kind === "harness_friction" && typeof packetPayload.frictionSignature === "string"
|
|
2238
|
+
? packetPayload.frictionSignature
|
|
2239
|
+
: null;
|
|
2240
|
+
const existing = frictionSignature && currentOrigin
|
|
2241
|
+
? (0, packets_1.findHarnessFrictionPacket)(agentRoot, currentOrigin, frictionSignature)
|
|
2236
2242
|
: null;
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2243
|
+
if (existing) {
|
|
2244
|
+
resultAction = "revised";
|
|
2245
|
+
const existingActiveReturnId = shouldCreateReturnObligation
|
|
2246
|
+
? activeReturnObligationId((0, identity_2.getAgentName)(), existing.relatedReturnObligationId)
|
|
2247
|
+
: null;
|
|
2248
|
+
returnObligationId = existingActiveReturnId
|
|
2249
|
+
?? (shouldCreateReturnObligation ? (0, obligations_1.generateObligationId)(Date.now()) : null);
|
|
2250
|
+
packet = existing.status === "drafting"
|
|
2251
|
+
? (0, packets_1.revisePonderPacket)(agentRoot, existing.id, {
|
|
2252
|
+
kind,
|
|
2253
|
+
objective,
|
|
2254
|
+
summary,
|
|
2255
|
+
successCriteria,
|
|
2256
|
+
payload: packetPayload,
|
|
2257
|
+
})
|
|
2258
|
+
: existing;
|
|
2259
|
+
if (returnObligationId && returnObligationId !== existing.relatedReturnObligationId) {
|
|
2260
|
+
packet = (0, packets_1.advancePonderPacket)(agentRoot, packet.id, { relatedReturnObligationId: returnObligationId });
|
|
2261
|
+
createLinkedReturnObligation(returnObligationId, packet.id);
|
|
2262
|
+
}
|
|
2263
|
+
}
|
|
2264
|
+
else {
|
|
2265
|
+
returnObligationId = shouldCreateReturnObligation ? (0, obligations_1.generateObligationId)(Date.now()) : null;
|
|
2266
|
+
packet = (0, packets_1.createPonderPacket)(agentRoot, {
|
|
2241
2267
|
kind,
|
|
2242
2268
|
objective,
|
|
2243
2269
|
summary,
|
|
2244
2270
|
successCriteria,
|
|
2271
|
+
...(currentOrigin ? { origin: currentOrigin } : {}),
|
|
2272
|
+
...(relatedObligationId ? { relatedObligationId } : {}),
|
|
2273
|
+
...(returnObligationId ? { relatedReturnObligationId: returnObligationId } : {}),
|
|
2274
|
+
...(parsedArgs.follows_packet_id ? { followsPacketId: parsedArgs.follows_packet_id } : {}),
|
|
2245
2275
|
payload: packetPayload,
|
|
2246
|
-
})
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
createLinkedReturnObligation(returnObligationId, packet.id);
|
|
2276
|
+
});
|
|
2277
|
+
if (returnObligationId) {
|
|
2278
|
+
createLinkedReturnObligation(returnObligationId, packet.id);
|
|
2279
|
+
}
|
|
2251
2280
|
}
|
|
2252
2281
|
}
|
|
2253
2282
|
else {
|
|
2254
|
-
|
|
2255
|
-
|
|
2283
|
+
const packetId = typeof parsedArgs.packet_id === "string" ? parsedArgs.packet_id.trim() : "";
|
|
2284
|
+
const kind = parsedArgs.kind;
|
|
2285
|
+
const objective = typeof parsedArgs.objective === "string" ? parsedArgs.objective.trim() : "";
|
|
2286
|
+
const summary = typeof parsedArgs.summary === "string" ? parsedArgs.summary.trim() : "";
|
|
2287
|
+
if (!packetId || !kind || !objective || !successCriteria || !payload) {
|
|
2288
|
+
throw new Error("ponder revise requires packet_id, kind, objective, success_criteria, and valid payload_json.");
|
|
2289
|
+
}
|
|
2290
|
+
packet = (0, packets_1.revisePonderPacket)((0, identity_2.getAgentRoot)(), packetId, {
|
|
2256
2291
|
kind,
|
|
2257
2292
|
objective,
|
|
2258
2293
|
summary,
|
|
2259
2294
|
successCriteria,
|
|
2260
|
-
|
|
2261
|
-
...(relatedObligationId ? { relatedObligationId } : {}),
|
|
2262
|
-
...(returnObligationId ? { relatedReturnObligationId: returnObligationId } : {}),
|
|
2263
|
-
...(parsedArgs.follows_packet_id ? { followsPacketId: parsedArgs.follows_packet_id } : {}),
|
|
2264
|
-
payload: packetPayload,
|
|
2295
|
+
payload,
|
|
2265
2296
|
});
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2297
|
+
returnObligationId = packet.relatedReturnObligationId
|
|
2298
|
+
&& !(packet.origin?.friendId === "self" && packet.origin.channel === "inner")
|
|
2299
|
+
? packet.relatedReturnObligationId
|
|
2300
|
+
: null;
|
|
2301
|
+
resultAction = "revised";
|
|
2269
2302
|
}
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
if (!packetId || !kind || !objective || !successCriteria || !payload) {
|
|
2277
|
-
throw new Error("ponder revise requires packet_id, kind, objective, success_criteria, and valid payload_json.");
|
|
2303
|
+
if (returnObligationId) {
|
|
2304
|
+
for (const token of extractPrivateReturnHeldTokens(privateReturnSourceRequest)) {
|
|
2305
|
+
privateReturnHeldTokens.add(token);
|
|
2306
|
+
}
|
|
2307
|
+
const agentName = (0, identity_2.getAgentName)();
|
|
2308
|
+
await (0, socket_client_1.requestPrivateWake)(agentName, augmentedToolContext?.daemonSocketPath, buildPonderReturnPrivateWakeOptions({ agentName, packet, returnObligationId })).catch(() => undefined);
|
|
2278
2309
|
}
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2310
|
+
sawPonder = true;
|
|
2311
|
+
toolResult = buildPonderResult(packet, resultAction, returnObligationId);
|
|
2312
|
+
success = true;
|
|
2313
|
+
(0, runtime_1.emitNervesEvent)({
|
|
2314
|
+
component: "engine",
|
|
2315
|
+
event: "engine.ponder_packet",
|
|
2316
|
+
message: "ponder packet touched",
|
|
2317
|
+
meta: {
|
|
2318
|
+
action: resultAction,
|
|
2319
|
+
packetId: packet.id,
|
|
2320
|
+
kind: packet.kind,
|
|
2321
|
+
status: packet.status,
|
|
2322
|
+
},
|
|
2285
2323
|
});
|
|
2286
|
-
returnObligationId = packet.relatedReturnObligationId
|
|
2287
|
-
&& !(packet.origin?.friendId === "self" && packet.origin.channel === "inner")
|
|
2288
|
-
? packet.relatedReturnObligationId
|
|
2289
|
-
: null;
|
|
2290
|
-
resultAction = "revised";
|
|
2291
2324
|
}
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
privateReturnHeldTokens.add(token);
|
|
2295
|
-
}
|
|
2296
|
-
const agentName = (0, identity_2.getAgentName)();
|
|
2297
|
-
await (0, socket_client_1.requestPrivateWake)(agentName, augmentedToolContext?.daemonSocketPath, buildPonderReturnPrivateWakeOptions({ agentName, packet, returnObligationId })).catch(() => undefined);
|
|
2325
|
+
catch (error) {
|
|
2326
|
+
toolResult = error instanceof Error ? error.message : String(error);
|
|
2298
2327
|
}
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
component: "engine",
|
|
2304
|
-
event: "engine.ponder_packet",
|
|
2305
|
-
message: "ponder packet touched",
|
|
2306
|
-
meta: {
|
|
2307
|
-
action: resultAction,
|
|
2308
|
-
packetId: packet.id,
|
|
2309
|
-
kind: packet.kind,
|
|
2310
|
-
status: packet.status,
|
|
2311
|
-
},
|
|
2312
|
-
});
|
|
2328
|
+
callbacks.onToolEnd(tc.name, argSummary, success);
|
|
2329
|
+
pushGenerated({ role: "tool", tool_call_id: tc.id, content: toolResult });
|
|
2330
|
+
providerRuntime.appendToolOutput(tc.id, toolResult);
|
|
2331
|
+
continue;
|
|
2313
2332
|
}
|
|
2314
|
-
|
|
2315
|
-
|
|
2333
|
+
/* v8 ignore next -- flag tested via truth-check integration tests @preserve */
|
|
2334
|
+
if (tc.name === "query_session")
|
|
2335
|
+
sawQuerySession = true;
|
|
2336
|
+
/* v8 ignore next -- flag tested via truth-check integration tests @preserve */
|
|
2337
|
+
if (tc.name === "bridge_manage")
|
|
2338
|
+
sawBridgeManage = true;
|
|
2339
|
+
/* v8 ignore next -- flag tested via truth-check integration tests @preserve */
|
|
2340
|
+
if (isExternalStateQuery(tc.name, args))
|
|
2341
|
+
sawExternalStateQuery = true;
|
|
2342
|
+
const argSummary = (0, tools_1.summarizeArgs)(tc.name, args);
|
|
2343
|
+
const toolLoop = (0, tool_loop_1.detectToolLoop)(toolLoopState, tc.name, args);
|
|
2344
|
+
if (toolLoop.stuck) {
|
|
2345
|
+
const rejection = `loop guard: ${toolLoop.message}`;
|
|
2346
|
+
callbacks.onToolStart(tc.name, args);
|
|
2347
|
+
callbacks.onToolEnd(tc.name, argSummary, false);
|
|
2348
|
+
pushGenerated({ role: "tool", tool_call_id: tc.id, content: rejection });
|
|
2349
|
+
providerRuntime.appendToolOutput(tc.id, rejection);
|
|
2350
|
+
continue;
|
|
2316
2351
|
}
|
|
2317
|
-
callbacks.onToolEnd(tc.name, argSummary, success);
|
|
2318
|
-
pushGenerated({ role: "tool", tool_call_id: tc.id, content: toolResult });
|
|
2319
|
-
providerRuntime.appendToolOutput(tc.id, toolResult);
|
|
2320
|
-
continue;
|
|
2321
|
-
}
|
|
2322
|
-
/* v8 ignore next -- flag tested via truth-check integration tests @preserve */
|
|
2323
|
-
if (tc.name === "query_session")
|
|
2324
|
-
sawQuerySession = true;
|
|
2325
|
-
/* v8 ignore next -- flag tested via truth-check integration tests @preserve */
|
|
2326
|
-
if (tc.name === "bridge_manage")
|
|
2327
|
-
sawBridgeManage = true;
|
|
2328
|
-
/* v8 ignore next -- flag tested via truth-check integration tests @preserve */
|
|
2329
|
-
if (isExternalStateQuery(tc.name, args))
|
|
2330
|
-
sawExternalStateQuery = true;
|
|
2331
|
-
const argSummary = (0, tools_1.summarizeArgs)(tc.name, args);
|
|
2332
|
-
const toolLoop = (0, tool_loop_1.detectToolLoop)(toolLoopState, tc.name, args);
|
|
2333
|
-
if (toolLoop.stuck) {
|
|
2334
|
-
const rejection = `loop guard: ${toolLoop.message}`;
|
|
2335
2352
|
callbacks.onToolStart(tc.name, args);
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2353
|
+
let toolResult;
|
|
2354
|
+
let success;
|
|
2355
|
+
try {
|
|
2356
|
+
const execToolFn = options?.execTool ?? tools_1.execTool;
|
|
2357
|
+
const routineActionSelection = approvalCalls.find((entry) => entry.call.id === tc.id)?.routineActionSelection;
|
|
2358
|
+
const executionToolContext = routineActionSelection && augmentedToolContext ? { ...augmentedToolContext, routineActionSelection } : augmentedToolContext;
|
|
2359
|
+
if (requiredToolCallNames.includes(tc.name) && !options?.requiredToolCalls?.requireSuccessfulResults)
|
|
2360
|
+
dispatchedRequiredToolCalls.add(tc.name);
|
|
2361
|
+
toolResult = await execToolFn(tc.name, args, executionToolContext);
|
|
2362
|
+
success = true;
|
|
2363
|
+
}
|
|
2364
|
+
catch (e) {
|
|
2365
|
+
toolResult = `error: ${e}`;
|
|
2366
|
+
success = false;
|
|
2367
|
+
augmentedToolContext?.habitSession?.recordError?.(toolResult);
|
|
2368
|
+
}
|
|
2369
|
+
const validatedRequiredResult = success && requiredToolCallNames.includes(tc.name) && options?.requiredToolCalls?.requireSuccessfulResults
|
|
2370
|
+
? requiredToolResultSucceeded(tc.name, toolResult, args, options.requiredToolCalls.validateRequiredToolResult)
|
|
2371
|
+
: false;
|
|
2372
|
+
if (validatedRequiredResult) {
|
|
2349
2373
|
dispatchedRequiredToolCalls.add(tc.name);
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
}
|
|
2358
|
-
const validatedRequiredResult = success && requiredToolCallNames.includes(tc.name) && options?.requiredToolCalls?.requireSuccessfulResults
|
|
2359
|
-
? requiredToolResultSucceeded(tc.name, toolResult, args, options.requiredToolCalls.validateRequiredToolResult)
|
|
2360
|
-
: false;
|
|
2361
|
-
if (validatedRequiredResult) {
|
|
2362
|
-
dispatchedRequiredToolCalls.add(tc.name);
|
|
2363
|
-
for (const requiredName of options?.requiredToolCalls?.requiredToolCallsAfterResult?.(tc.name, args, toolResult) ?? []) {
|
|
2364
|
-
if (!candidateToolNames.has(requiredName)) {
|
|
2365
|
-
(0, runtime_1.emitNervesEvent)({ level: "error", component: "engine", event: "engine.required_tool_unadvertised", message: "dependent required tool is not advertised for the active channel", meta: { toolName: requiredName, channel: String(channel) } });
|
|
2366
|
-
throw new Error(`dependent required tool is not advertised for this channel: ${requiredName}`);
|
|
2374
|
+
for (const requiredName of options?.requiredToolCalls?.requiredToolCallsAfterResult?.(tc.name, args, toolResult) ?? []) {
|
|
2375
|
+
if (!candidateToolNames.has(requiredName)) {
|
|
2376
|
+
(0, runtime_1.emitNervesEvent)({ level: "error", component: "engine", event: "engine.required_tool_unadvertised", message: "dependent required tool is not advertised for the active channel", meta: { toolName: requiredName, channel: String(channel) } });
|
|
2377
|
+
throw new Error(`dependent required tool is not advertised for this channel: ${requiredName}`);
|
|
2378
|
+
}
|
|
2379
|
+
if (!requiredToolCallNames.includes(requiredName))
|
|
2380
|
+
requiredToolCallNames.push(requiredName);
|
|
2367
2381
|
}
|
|
2368
|
-
if (!requiredToolCallNames.includes(requiredName))
|
|
2369
|
-
requiredToolCallNames.push(requiredName);
|
|
2370
2382
|
}
|
|
2383
|
+
if (success && currentEffectFingerprint && !toolResultIndicatesFailure(toolResult)) {
|
|
2384
|
+
unresolvedHistoricalEffects = unresolvedHistoricalEffects.filter((effect) => effect.fingerprint !== currentEffectFingerprint);
|
|
2385
|
+
}
|
|
2386
|
+
const resolvedRiskProfile = (0, tools_1.resolveToolDefinition)(tc.name)?.riskProfile;
|
|
2387
|
+
const toolRiskProfile = typeof resolvedRiskProfile === "function" ? resolvedRiskProfile(args) : resolvedRiskProfile;
|
|
2388
|
+
options?.toolBoundaryObserver?.({
|
|
2389
|
+
name: tc.name,
|
|
2390
|
+
reason: "dispatched",
|
|
2391
|
+
globallyResolvable: typeof (0, tools_1.resolveToolDefinition)(tc.name)?.handler === "function",
|
|
2392
|
+
invoked: true,
|
|
2393
|
+
sideEffect: success && toolRiskProfile?.mutates !== "none",
|
|
2394
|
+
});
|
|
2395
|
+
toolResult = (0, tool_friction_1.rewriteToolResultForModel)(tc.name, toolResult, toolFrictionLedger);
|
|
2396
|
+
(0, tool_loop_1.recordToolOutcome)(toolLoopState, tc.name, args, toolResult, success);
|
|
2397
|
+
callbacks.onToolEnd(tc.name, (0, tools_1.buildToolResultSummary)(tc.name, args, toolResult, success), success);
|
|
2398
|
+
pushGenerated({ role: "tool", tool_call_id: tc.id, content: toolResult });
|
|
2399
|
+
providerRuntime.appendToolOutput(tc.id, toolResult);
|
|
2400
|
+
callbacks.onToolResult?.(messagesWithoutRequiredCorrections());
|
|
2371
2401
|
}
|
|
2372
|
-
if (success && currentEffectFingerprint && !toolResultIndicatesFailure(toolResult)) {
|
|
2373
|
-
unresolvedHistoricalEffects = unresolvedHistoricalEffects.filter((effect) => effect.fingerprint !== currentEffectFingerprint);
|
|
2374
|
-
}
|
|
2375
|
-
const resolvedRiskProfile = (0, tools_1.resolveToolDefinition)(tc.name)?.riskProfile;
|
|
2376
|
-
const toolRiskProfile = typeof resolvedRiskProfile === "function" ? resolvedRiskProfile(args) : resolvedRiskProfile;
|
|
2377
|
-
options?.toolBoundaryObserver?.({
|
|
2378
|
-
name: tc.name,
|
|
2379
|
-
reason: "dispatched",
|
|
2380
|
-
globallyResolvable: typeof (0, tools_1.resolveToolDefinition)(tc.name)?.handler === "function",
|
|
2381
|
-
invoked: true,
|
|
2382
|
-
sideEffect: success && toolRiskProfile?.mutates !== "none",
|
|
2383
|
-
});
|
|
2384
|
-
toolResult = (0, tool_friction_1.rewriteToolResultForModel)(tc.name, toolResult, toolFrictionLedger);
|
|
2385
|
-
(0, tool_loop_1.recordToolOutcome)(toolLoopState, tc.name, args, toolResult, success);
|
|
2386
|
-
callbacks.onToolEnd(tc.name, (0, tools_1.buildToolResultSummary)(tc.name, args, toolResult, success), success);
|
|
2387
|
-
pushGenerated({ role: "tool", tool_call_id: tc.id, content: toolResult });
|
|
2388
|
-
providerRuntime.appendToolOutput(tc.id, toolResult);
|
|
2389
|
-
callbacks.onToolResult?.(messages);
|
|
2390
2402
|
}
|
|
2391
2403
|
}
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2404
|
+
catch (e) {
|
|
2405
|
+
// Abort is not an error — just stop cleanly
|
|
2406
|
+
if (e instanceof provider_attempt_1.ProviderAttemptAbortError || signal?.aborted) {
|
|
2407
|
+
stripLastToolCalls(messages);
|
|
2408
|
+
stripLastToolCalls(generatedMessages);
|
|
2409
|
+
outcome = "aborted";
|
|
2410
|
+
break;
|
|
2411
|
+
}
|
|
2412
|
+
const errorForClassification = e instanceof Error ? e : /* v8 ignore next -- defensive @preserve */ new Error(String(e));
|
|
2413
|
+
let providerClassification;
|
|
2414
|
+
try {
|
|
2415
|
+
providerClassification = providerRuntime.classifyError(errorForClassification);
|
|
2416
|
+
}
|
|
2417
|
+
catch {
|
|
2418
|
+
/* v8 ignore next -- defensive: classifyError should not throw @preserve */
|
|
2419
|
+
providerClassification = "unknown";
|
|
2420
|
+
}
|
|
2421
|
+
finishTerminalProviderError(errorForClassification, providerClassification);
|
|
2409
2422
|
}
|
|
2410
|
-
finishTerminalProviderError(errorForClassification, providerClassification);
|
|
2411
2423
|
}
|
|
2412
2424
|
}
|
|
2425
|
+
finally {
|
|
2426
|
+
removeRequiredCorrections();
|
|
2427
|
+
}
|
|
2413
2428
|
options?.captureGeneratedMessages?.(structuredClone(generatedMessages));
|
|
2414
2429
|
(0, runtime_1.emitNervesEvent)({
|
|
2415
2430
|
event: "engine.turn_end",
|