@ouro.bot/cli 0.1.0-alpha.726 → 0.1.0-alpha.727
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 +9 -0
- package/dist/heart/core.js +181 -209
- package/dist/heart/daemon/daemon.js +79 -14
- package/dist/heart/daemon/process-manager.js +100 -18
- package/dist/heart/providers/anthropic.js +17 -12
- package/dist/heart/streaming.js +6 -6
- package/dist/mind/prompt-budget.js +104 -5
- package/dist/repertoire/tools.js +0 -10
- package/dist/senses/bluebubbles/client.js +43 -57
- package/dist/senses/bluebubbles/context-packet.js +104 -19
- package/dist/senses/bluebubbles/entry.js +14 -1
- package/dist/senses/bluebubbles/index.js +1382 -719
- package/dist/senses/bluebubbles/latest-turn.js +311 -0
- package/dist/senses/bluebubbles/outbound-state.js +4 -0
- package/dist/senses/bluebubbles/reaction-policy.js +4 -12
- package/dist/senses/bluebubbles/semantic-receipts.js +178 -10
- package/package.json +1 -1
package/dist/heart/core.js
CHANGED
|
@@ -43,7 +43,10 @@ const provider_credentials_1 = require("./provider-credentials");
|
|
|
43
43
|
const habit_session_1 = require("./habits/habit-session");
|
|
44
44
|
const provider_attempt_1 = require("./provider-attempt");
|
|
45
45
|
const openai_codex_token_1 = require("./providers/openai-codex-token");
|
|
46
|
-
|
|
46
|
+
// Cache only immutable provider construction inputs. ProviderRuntime owns
|
|
47
|
+
// mutable per-turn state (for example Responses nativeInput), so every caller
|
|
48
|
+
// must receive a fresh instance even when its binding fingerprint is unchanged.
|
|
49
|
+
const _providerRuntimeFactories = {
|
|
47
50
|
human: null,
|
|
48
51
|
agent: null,
|
|
49
52
|
};
|
|
@@ -108,12 +111,17 @@ function createProviderRegistry() {
|
|
|
108
111
|
};
|
|
109
112
|
}
|
|
110
113
|
async function getProviderRuntime(facing = "human") {
|
|
114
|
+
let runtime = null;
|
|
111
115
|
try {
|
|
112
116
|
const { binding, fingerprint, credential } = await getProviderRuntimeFingerprint(facing);
|
|
113
|
-
const cached =
|
|
117
|
+
const cached = _providerRuntimeFactories[facing];
|
|
114
118
|
if (!cached || cached.fingerprint !== fingerprint) {
|
|
115
|
-
const
|
|
116
|
-
|
|
119
|
+
const create = () => createProviderRegistry().resolve(binding.provider, binding.model, credential);
|
|
120
|
+
runtime = create();
|
|
121
|
+
_providerRuntimeFactories[facing] = runtime ? { fingerprint, create } : null;
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
runtime = cached.create();
|
|
117
125
|
}
|
|
118
126
|
}
|
|
119
127
|
catch (error) {
|
|
@@ -129,7 +137,7 @@ async function getProviderRuntime(facing = "human") {
|
|
|
129
137
|
console.error(`\n[fatal] ${msg}\n`);
|
|
130
138
|
throw error instanceof Error ? error : new Error(msg);
|
|
131
139
|
}
|
|
132
|
-
if (!
|
|
140
|
+
if (!runtime) {
|
|
133
141
|
const msg = "provider runtime could not be initialized.";
|
|
134
142
|
(0, runtime_1.emitNervesEvent)({
|
|
135
143
|
level: "error",
|
|
@@ -142,16 +150,16 @@ async function getProviderRuntime(facing = "human") {
|
|
|
142
150
|
console.error(`\n[fatal] ${msg}\n`);
|
|
143
151
|
throw new Error(msg);
|
|
144
152
|
}
|
|
145
|
-
return
|
|
153
|
+
return runtime;
|
|
146
154
|
}
|
|
147
155
|
/**
|
|
148
|
-
* Clear
|
|
149
|
-
* current config. Runtime
|
|
150
|
-
*
|
|
156
|
+
* Clear cached provider construction inputs so the next access re-reads them
|
|
157
|
+
* from current config. Runtime instances are always per-caller and are never
|
|
158
|
+
* shared across turns.
|
|
151
159
|
*/
|
|
152
160
|
function resetProviderRuntime() {
|
|
153
|
-
|
|
154
|
-
|
|
161
|
+
_providerRuntimeFactories.human = null;
|
|
162
|
+
_providerRuntimeFactories.agent = null;
|
|
155
163
|
}
|
|
156
164
|
function getModel(facing = "human") {
|
|
157
165
|
return resolveRuntimeProviderBinding(facing).model;
|
|
@@ -348,11 +356,11 @@ async function habitToolBatchBlockReason(habitSession, toolCalls, delegatedOrigi
|
|
|
348
356
|
}
|
|
349
357
|
return null;
|
|
350
358
|
}
|
|
351
|
-
/**
|
|
352
|
-
*
|
|
353
|
-
*
|
|
359
|
+
/** Channels that deliberately support mid-turn delivery expose `speak`.
|
|
360
|
+
* BlueBubbles is final-only: its adapter owns one accepted visibility boundary.
|
|
361
|
+
* The private runtime has `ponder`; MCP returns synchronously; mail is batch. */
|
|
354
362
|
function isChatStyleChannel(channel) {
|
|
355
|
-
return channel === "cli" || channel === "teams" || channel === "
|
|
363
|
+
return channel === "cli" || channel === "teams" || channel === "voice";
|
|
356
364
|
}
|
|
357
365
|
function bindCurrentIngressEvidenceLocator(toolList, evidence) {
|
|
358
366
|
if (!evidence
|
|
@@ -392,35 +400,16 @@ const SOLE_CALL_REJECTION = {
|
|
|
392
400
|
rest: "rejected: rest must be the only tool call. finish your work first, then call rest alone.",
|
|
393
401
|
};
|
|
394
402
|
function parseSettlePayload(argumentsText) {
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
return {};
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
function matchesRegisteredToolArgumentSchema(tool, args) {
|
|
409
|
-
const parameters = tool.function.parameters;
|
|
410
|
-
for (const requiredKey of parameters.required ?? []) {
|
|
411
|
-
if (!(requiredKey in args))
|
|
412
|
-
return false;
|
|
413
|
-
}
|
|
414
|
-
for (const [key, property] of Object.entries(parameters.properties)) {
|
|
415
|
-
const value = args[key];
|
|
416
|
-
if (value === undefined)
|
|
417
|
-
continue;
|
|
418
|
-
if (property.type === "string" && typeof value !== "string")
|
|
419
|
-
return false;
|
|
420
|
-
if (property.enum && !property.enum.includes(value))
|
|
421
|
-
return false;
|
|
422
|
-
}
|
|
423
|
-
return true;
|
|
403
|
+
const parsed = JSON.parse(argumentsText);
|
|
404
|
+
// Provider finalization validates settle.answer as a string before this
|
|
405
|
+
// terminal handling path. Keep this parser focused on projection instead
|
|
406
|
+
// of carrying an unreachable second shape check.
|
|
407
|
+
const answer = parsed.answer;
|
|
408
|
+
const rawIntent = parsed.intent;
|
|
409
|
+
const intent = rawIntent === "complete" || rawIntent === "blocked" || rawIntent === "direct_reply"
|
|
410
|
+
? rawIntent
|
|
411
|
+
: undefined;
|
|
412
|
+
return { answer, intent };
|
|
424
413
|
}
|
|
425
414
|
function parsePonderPayload(argumentsText) {
|
|
426
415
|
try {
|
|
@@ -620,9 +609,10 @@ function getSettleRetryError(mustResolveBeforeHandoff, intent, sawSteeringFollow
|
|
|
620
609
|
}
|
|
621
610
|
return null;
|
|
622
611
|
}
|
|
623
|
-
function upsertSystemPrompt(messages, systemText) {
|
|
612
|
+
function upsertSystemPrompt(messages, systemText, protectedSystemMessages = []) {
|
|
624
613
|
const systemMessage = { role: "system", content: systemText };
|
|
625
|
-
|
|
614
|
+
const protectedMessages = new Set(protectedSystemMessages.filter((message) => message.role === "system"));
|
|
615
|
+
if (messages[0]?.role === "system" && !protectedMessages.has(messages[0])) {
|
|
626
616
|
messages[0] = systemMessage;
|
|
627
617
|
}
|
|
628
618
|
else {
|
|
@@ -808,11 +798,15 @@ function buildAuthFailureGuidance(provider, model, agentName, detail) {
|
|
|
808
798
|
return lines.join("\n");
|
|
809
799
|
}
|
|
810
800
|
async function runAgent(messages, callbacks, channel, signal, options) {
|
|
801
|
+
const generatedMessages = [];
|
|
802
|
+
const pushGenerated = (...next) => {
|
|
803
|
+
messages.push(...next);
|
|
804
|
+
generatedMessages.push(...structuredClone(next));
|
|
805
|
+
};
|
|
811
806
|
const facing = (0, friends_1.channelToFacing)(channel);
|
|
812
807
|
let providerRuntime = await getProviderRuntime(facing);
|
|
813
808
|
const provider = providerRuntime.id;
|
|
814
|
-
const
|
|
815
|
-
const toolChoiceRequired = restrictedReactionFeedback ? true : options?.toolChoiceRequired ?? true;
|
|
809
|
+
const toolChoiceRequired = options?.toolChoiceRequired ?? true;
|
|
816
810
|
const traceId = options?.traceId;
|
|
817
811
|
(0, runtime_1.emitNervesEvent)({
|
|
818
812
|
event: "engine.turn_start",
|
|
@@ -833,6 +827,52 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
833
827
|
}
|
|
834
828
|
const turnOrientationFrame = options?.orientationFrame
|
|
835
829
|
?? (channel ? (0, orientation_frame_1.buildOrientationFrame)({ channel, messages }) : undefined);
|
|
830
|
+
if (options?.requiredPromptEvidence) {
|
|
831
|
+
let structuralFloor;
|
|
832
|
+
try {
|
|
833
|
+
structuralFloor = (0, prompt_budget_1.assessRequiredPromptEvidenceBudget)({
|
|
834
|
+
messages,
|
|
835
|
+
requiredPromptEvidence: options.requiredPromptEvidence,
|
|
836
|
+
provider: providerRuntime.id,
|
|
837
|
+
model: providerRuntime.model,
|
|
838
|
+
contextWindowTokens: (0, config_1.getContextConfig)().maxTokens,
|
|
839
|
+
});
|
|
840
|
+
}
|
|
841
|
+
catch (error) {
|
|
842
|
+
const invalidEvidenceError = error instanceof Error ? error : new Error(String(error));
|
|
843
|
+
callbacks.onError(invalidEvidenceError, "terminal");
|
|
844
|
+
options.captureGeneratedMessages?.([]);
|
|
845
|
+
(0, runtime_1.emitNervesEvent)({
|
|
846
|
+
event: "engine.turn_end",
|
|
847
|
+
trace_id: traceId,
|
|
848
|
+
component: "engine",
|
|
849
|
+
message: "runAgent turn completed",
|
|
850
|
+
meta: { done: true, sawPonder: false, sawQuerySession: false, sawBridgeManage: false },
|
|
851
|
+
});
|
|
852
|
+
return {
|
|
853
|
+
outcome: "errored",
|
|
854
|
+
error: invalidEvidenceError,
|
|
855
|
+
errorClassification: "unknown",
|
|
856
|
+
};
|
|
857
|
+
}
|
|
858
|
+
if (structuralFloor.status === "required_evidence_over_budget") {
|
|
859
|
+
const budgetError = new Error(`required_evidence_over_budget: required current-turn evidence needs ${structuralFloor.estimatedTokens} tokens but the provider input limit is ${structuralFloor.budget.inputTokenLimit}`);
|
|
860
|
+
callbacks.onError(budgetError, "terminal");
|
|
861
|
+
options.captureGeneratedMessages?.([]);
|
|
862
|
+
(0, runtime_1.emitNervesEvent)({
|
|
863
|
+
event: "engine.turn_end",
|
|
864
|
+
trace_id: traceId,
|
|
865
|
+
component: "engine",
|
|
866
|
+
message: "runAgent turn completed",
|
|
867
|
+
meta: { done: true, sawPonder: false, sawQuerySession: false, sawBridgeManage: false },
|
|
868
|
+
});
|
|
869
|
+
return {
|
|
870
|
+
outcome: "errored",
|
|
871
|
+
error: budgetError,
|
|
872
|
+
errorClassification: "unknown",
|
|
873
|
+
};
|
|
874
|
+
}
|
|
875
|
+
}
|
|
836
876
|
// Refresh system prompt at start of each turn when channel is provided.
|
|
837
877
|
// If refresh fails, retain only a recognised stable prefix (or inject a
|
|
838
878
|
// minimal safe fallback) so stale dynamic state cannot regain authority.
|
|
@@ -848,12 +888,22 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
848
888
|
};
|
|
849
889
|
const refreshed = await (0, prompt_1.buildSystem)(channel, buildSystemOptions, currentContext);
|
|
850
890
|
structuredSystemPrompt = refreshed;
|
|
851
|
-
upsertSystemPrompt(messages, (0, prompt_1.flattenSystemPrompt)(refreshed)
|
|
891
|
+
upsertSystemPrompt(messages, (0, prompt_1.flattenSystemPrompt)(refreshed), [
|
|
892
|
+
...(options?.promptOnlyEvidenceMessages ?? []),
|
|
893
|
+
...(options?.requiredPromptEvidence?.verifiedPredecessorMessage
|
|
894
|
+
? [options.requiredPromptEvidence.verifiedPredecessorMessage]
|
|
895
|
+
: []),
|
|
896
|
+
]);
|
|
852
897
|
}
|
|
853
898
|
catch (error) {
|
|
854
899
|
const hadExistingSystemPrompt = messages[0]?.role === "system" && typeof messages[0].content === "string";
|
|
855
900
|
const fallback = repairFallbackSystemPrompt(turnOrientationFrame);
|
|
856
|
-
upsertSystemPrompt(messages, fallback
|
|
901
|
+
upsertSystemPrompt(messages, fallback, [
|
|
902
|
+
...(options?.promptOnlyEvidenceMessages ?? []),
|
|
903
|
+
...(options?.requiredPromptEvidence?.verifiedPredecessorMessage
|
|
904
|
+
? [options.requiredPromptEvidence.verifiedPredecessorMessage]
|
|
905
|
+
: []),
|
|
906
|
+
]);
|
|
857
907
|
(0, runtime_1.emitNervesEvent)({
|
|
858
908
|
level: "warn",
|
|
859
909
|
event: "mind.step_error",
|
|
@@ -939,25 +989,18 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
939
989
|
},
|
|
940
990
|
});
|
|
941
991
|
stripLastToolCalls(messages);
|
|
992
|
+
stripLastToolCalls(generatedMessages);
|
|
942
993
|
outcome = "errored";
|
|
943
994
|
done = true;
|
|
944
995
|
};
|
|
945
|
-
const finishRestrictedReactionFeedbackViolation = (reason) => {
|
|
946
|
-
callbacks.onClearText?.();
|
|
947
|
-
finishTerminalProviderError(new Error(`restricted reaction feedback failed closed: ${reason}`), "unknown");
|
|
948
|
-
};
|
|
949
996
|
// Prevent MaxListenersExceeded warning — each iteration adds a listener
|
|
950
997
|
try {
|
|
951
998
|
require("events").setMaxListeners(50, signal);
|
|
952
999
|
}
|
|
953
1000
|
catch { /* unsupported */ }
|
|
954
1001
|
const toolPreferences = currentContext?.friend?.toolPreferences;
|
|
955
|
-
const unboundBaseTools =
|
|
956
|
-
|
|
957
|
-
: options?.tools ?? (0, tools_1.getToolsForChannel)(channel ? (0, friends_1.getChannelCapabilities)(channel) : undefined, toolPreferences && Object.keys(toolPreferences).length > 0 ? toolPreferences : undefined, currentContext, providerRuntime.capabilities, options?.mcpManager, providerRuntime.model);
|
|
958
|
-
const baseTools = restrictedReactionFeedback
|
|
959
|
-
? unboundBaseTools
|
|
960
|
-
: bindCurrentIngressEvidenceLocator(unboundBaseTools, options?.toolContext?.currentIngressEvidence);
|
|
1002
|
+
const unboundBaseTools = options?.tools ?? (0, tools_1.getToolsForChannel)(channel ? (0, friends_1.getChannelCapabilities)(channel) : undefined, toolPreferences && Object.keys(toolPreferences).length > 0 ? toolPreferences : undefined, currentContext, providerRuntime.capabilities, options?.mcpManager, providerRuntime.model);
|
|
1003
|
+
const baseTools = bindCurrentIngressEvidenceLocator(unboundBaseTools, options?.toolContext?.currentIngressEvidence);
|
|
961
1004
|
// Augment tool context with reasoning effort controls from provider
|
|
962
1005
|
const baseToolContext = options?.toolContext
|
|
963
1006
|
?? (turnOrientationFrame ? { signin: async () => undefined, orientationFrame: turnOrientationFrame } : undefined);
|
|
@@ -1001,21 +1044,17 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
1001
1044
|
const filteredBaseTools = isPrivateRuntimeChannel
|
|
1002
1045
|
? baseTools.filter((t) => privateRuntimeHabitCanSendMessage || t.function.name !== "send_message")
|
|
1003
1046
|
: baseTools;
|
|
1004
|
-
const activeTools =
|
|
1005
|
-
|
|
1006
|
-
: [
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
...(isChatStyleChannel(channel ?? "") ? [tools_1.speakTool] : []),
|
|
1014
|
-
];
|
|
1047
|
+
const activeTools = [
|
|
1048
|
+
...filteredBaseTools,
|
|
1049
|
+
...(augmentedToolContext?.noSend === true ? [] : [tools_1.ponderTool]),
|
|
1050
|
+
...(isPrivateRuntimeChannel && privateRuntimeHabitCanSurface ? [tools_2.surfaceToolDef] : []),
|
|
1051
|
+
...(isPrivateRuntimeChannel ? [tools_1.restTool] : []),
|
|
1052
|
+
...(!isPrivateRuntimeChannel ? [tools_1.observeTool] : []),
|
|
1053
|
+
...(!isPrivateRuntimeChannel ? [tools_1.settleTool] : []),
|
|
1054
|
+
...(isChatStyleChannel(channel ?? "") ? [tools_1.speakTool] : []),
|
|
1055
|
+
];
|
|
1015
1056
|
const activeToolNames = new Set(activeTools.map((tool) => tool.function.name));
|
|
1016
|
-
const steeringFollowUps =
|
|
1017
|
-
? []
|
|
1018
|
-
: options?.drainSteeringFollowUps?.() ?? [];
|
|
1057
|
+
const steeringFollowUps = options?.drainSteeringFollowUps?.() ?? [];
|
|
1019
1058
|
if (steeringFollowUps.length > 0) {
|
|
1020
1059
|
const hasSupersedingFollowUp = steeringFollowUps.some((followUp) => followUp.effect === "clear_and_supersede");
|
|
1021
1060
|
if (hasSupersedingFollowUp) {
|
|
@@ -1052,6 +1091,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
1052
1091
|
try {
|
|
1053
1092
|
const promptBudget = (0, prompt_budget_1.applyPromptBudget)({
|
|
1054
1093
|
messages,
|
|
1094
|
+
requiredPromptEvidence: options?.requiredPromptEvidence,
|
|
1055
1095
|
provider: providerRuntime.id,
|
|
1056
1096
|
model: providerRuntime.model,
|
|
1057
1097
|
contextWindowTokens: (0, config_1.getContextConfig)().maxTokens,
|
|
@@ -1068,7 +1108,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
1068
1108
|
traceId,
|
|
1069
1109
|
toolChoiceRequired,
|
|
1070
1110
|
reasoningEffort: currentReasoningEffort,
|
|
1071
|
-
eagerSettleStreaming:
|
|
1111
|
+
eagerSettleStreaming: true,
|
|
1072
1112
|
systemPrompt: structuredSystemPrompt,
|
|
1073
1113
|
});
|
|
1074
1114
|
}
|
|
@@ -1090,9 +1130,19 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
1090
1130
|
if (isContextOverflow(error) && !overflowRetried) {
|
|
1091
1131
|
overflowRetried = true;
|
|
1092
1132
|
stripLastToolCalls(messages);
|
|
1133
|
+
stripLastToolCalls(generatedMessages);
|
|
1093
1134
|
const { maxTokens, contextMargin } = (0, config_1.getContextConfig)();
|
|
1094
1135
|
const trimmed = (0, context_1.trimMessages)(messages, maxTokens, contextMargin, maxTokens * 2);
|
|
1095
|
-
|
|
1136
|
+
const requiredEvidence = options?.requiredPromptEvidence;
|
|
1137
|
+
const requiredMessages = new Set([
|
|
1138
|
+
...(requiredEvidence?.verifiedPredecessorMessage ? [requiredEvidence.verifiedPredecessorMessage] : []),
|
|
1139
|
+
...(requiredEvidence?.currentUserMessage ? [requiredEvidence.currentUserMessage] : []),
|
|
1140
|
+
]);
|
|
1141
|
+
const trimmedMessages = new Set(trimmed);
|
|
1142
|
+
const overflowRetryMessages = requiredMessages.size === 0
|
|
1143
|
+
? trimmed
|
|
1144
|
+
: messages.filter((message) => trimmedMessages.has(message) || requiredMessages.has(message));
|
|
1145
|
+
messages.splice(0, messages.length, ...overflowRetryMessages);
|
|
1096
1146
|
providerRuntime.resetTurnState(messages);
|
|
1097
1147
|
callbacks.onError(new Error("context trimmed, retrying..."), "transient");
|
|
1098
1148
|
return callProviderTurn();
|
|
@@ -1104,9 +1154,8 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
1104
1154
|
operation: "turn",
|
|
1105
1155
|
provider: providerRuntime.id,
|
|
1106
1156
|
model: providerRuntime.model,
|
|
1107
|
-
run:
|
|
1157
|
+
run: callProviderTurnWithOverflowRecovery,
|
|
1108
1158
|
classifyError: (error) => providerRuntime.classifyError(error),
|
|
1109
|
-
...(restrictedReactionFeedback ? { policy: { maxAttempts: 1 } } : {}),
|
|
1110
1159
|
onRetry: async (record, maxAttempts) => {
|
|
1111
1160
|
const delayMs = record.delayMs;
|
|
1112
1161
|
const seconds = delayMs / 1000;
|
|
@@ -1122,7 +1171,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
1122
1171
|
preserveCachedOnFailure: true,
|
|
1123
1172
|
providers: [record.provider],
|
|
1124
1173
|
});
|
|
1125
|
-
|
|
1174
|
+
_providerRuntimeFactories[facing] = null;
|
|
1126
1175
|
providerRuntime = await getProviderRuntime(facing);
|
|
1127
1176
|
providerRuntime.resetTurnState(messages);
|
|
1128
1177
|
}
|
|
@@ -1218,100 +1267,6 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
1218
1267
|
if (hasPhaseAnnotation) {
|
|
1219
1268
|
msg.phase = isSoleSettle ? "settle" : "commentary";
|
|
1220
1269
|
}
|
|
1221
|
-
if (restrictedReactionFeedback) {
|
|
1222
|
-
if (result.toolCalls.length !== 1) {
|
|
1223
|
-
streamCallbackBuffer?.discard();
|
|
1224
|
-
finishRestrictedReactionFeedbackViolation(result.toolCalls.length === 0
|
|
1225
|
-
? "the provider returned no terminal tool"
|
|
1226
|
-
: "the provider returned a mixed or multiple-tool batch");
|
|
1227
|
-
continue;
|
|
1228
|
-
}
|
|
1229
|
-
const restrictedCall = result.toolCalls[0];
|
|
1230
|
-
if (!activeToolNames.has(restrictedCall.name)) {
|
|
1231
|
-
streamCallbackBuffer?.discard();
|
|
1232
|
-
finishRestrictedReactionFeedbackViolation(`the provider returned unregistered tool ${JSON.stringify(restrictedCall.name)}`);
|
|
1233
|
-
continue;
|
|
1234
|
-
}
|
|
1235
|
-
let restrictedArgs = null;
|
|
1236
|
-
try {
|
|
1237
|
-
const parsed = JSON.parse(restrictedCall.arguments);
|
|
1238
|
-
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
1239
|
-
restrictedArgs = parsed;
|
|
1240
|
-
}
|
|
1241
|
-
}
|
|
1242
|
-
catch {
|
|
1243
|
-
restrictedArgs = null;
|
|
1244
|
-
}
|
|
1245
|
-
const registeredTool = activeTools.find((tool) => tool.function.name === restrictedCall.name);
|
|
1246
|
-
const argumentsMatchSchema = restrictedArgs !== null
|
|
1247
|
-
&& matchesRegisteredToolArgumentSchema(registeredTool, restrictedArgs);
|
|
1248
|
-
const callbackArgs = (restrictedArgs ?? {});
|
|
1249
|
-
if (restrictedCall.name === "settle") {
|
|
1250
|
-
const { answer, intent } = parseSettlePayload(restrictedCall.arguments);
|
|
1251
|
-
callbacks.onToolStart("settle", callbackArgs);
|
|
1252
|
-
if (!argumentsMatchSchema || answer === undefined || intent === "direct_reply") {
|
|
1253
|
-
callbacks.onToolEnd("settle", (0, tools_1.summarizeArgs)("settle", callbackArgs), false);
|
|
1254
|
-
streamCallbackBuffer?.discard();
|
|
1255
|
-
finishRestrictedReactionFeedbackViolation("settle arguments were malformed or requested continuation");
|
|
1256
|
-
continue;
|
|
1257
|
-
}
|
|
1258
|
-
streamCallbackBuffer?.discard();
|
|
1259
|
-
callbacks.onToolEnd("settle", (0, tools_1.summarizeArgs)("settle", callbackArgs), true);
|
|
1260
|
-
const completionIntent = intent === "blocked" ? "blocked" : "complete";
|
|
1261
|
-
completion = { answer, intent: completionIntent };
|
|
1262
|
-
callbacks.onClearText?.();
|
|
1263
|
-
callbacks.onTextChunk(answer);
|
|
1264
|
-
messages.push(msg);
|
|
1265
|
-
const delivered = "(delivered)";
|
|
1266
|
-
messages.push({ role: "tool", tool_call_id: restrictedCall.id, content: delivered });
|
|
1267
|
-
providerRuntime.appendToolOutput(restrictedCall.id, delivered);
|
|
1268
|
-
outcome = completionIntent === "blocked" ? "blocked" : "settled";
|
|
1269
|
-
done = true;
|
|
1270
|
-
continue;
|
|
1271
|
-
}
|
|
1272
|
-
if (!argumentsMatchSchema) {
|
|
1273
|
-
streamCallbackBuffer?.discard();
|
|
1274
|
-
finishRestrictedReactionFeedbackViolation(`${restrictedCall.name} arguments were malformed`);
|
|
1275
|
-
continue;
|
|
1276
|
-
}
|
|
1277
|
-
if (restrictedCall.name === "observe") {
|
|
1278
|
-
streamCallbackBuffer?.discard();
|
|
1279
|
-
callbacks.onClearText?.();
|
|
1280
|
-
callbacks.onToolStart("observe", callbackArgs);
|
|
1281
|
-
const reason = typeof callbackArgs.reason === "string" ? callbackArgs.reason : undefined;
|
|
1282
|
-
(0, runtime_1.emitNervesEvent)({
|
|
1283
|
-
component: "engine",
|
|
1284
|
-
event: "engine.observe",
|
|
1285
|
-
message: "agent observed without responding",
|
|
1286
|
-
meta: { ...(reason ? { reason } : {}) },
|
|
1287
|
-
});
|
|
1288
|
-
callbacks.onToolEnd("observe", (0, tools_1.summarizeArgs)("observe", callbackArgs), true);
|
|
1289
|
-
messages.push(msg);
|
|
1290
|
-
const silenced = "(silenced)";
|
|
1291
|
-
messages.push({ role: "tool", tool_call_id: restrictedCall.id, content: silenced });
|
|
1292
|
-
providerRuntime.appendToolOutput(restrictedCall.id, silenced);
|
|
1293
|
-
outcome = "observed";
|
|
1294
|
-
done = true;
|
|
1295
|
-
continue;
|
|
1296
|
-
}
|
|
1297
|
-
callbacks.onClearText?.();
|
|
1298
|
-
callbacks.onToolStart("orientation_get", callbackArgs);
|
|
1299
|
-
let orientationReadSucceeded = false;
|
|
1300
|
-
try {
|
|
1301
|
-
const execToolFn = options?.execTool ?? tools_1.execTool;
|
|
1302
|
-
await execToolFn("orientation_get", callbackArgs, augmentedToolContext);
|
|
1303
|
-
orientationReadSucceeded = true;
|
|
1304
|
-
}
|
|
1305
|
-
catch {
|
|
1306
|
-
orientationReadSucceeded = false;
|
|
1307
|
-
}
|
|
1308
|
-
callbacks.onToolEnd("orientation_get", (0, tools_1.summarizeArgs)("orientation_get", callbackArgs), orientationReadSucceeded);
|
|
1309
|
-
streamCallbackBuffer?.discard();
|
|
1310
|
-
finishRestrictedReactionFeedbackViolation(orientationReadSucceeded
|
|
1311
|
-
? "orientation_get completed but no terminal response can follow the single invocation"
|
|
1312
|
-
: "orientation_get failed");
|
|
1313
|
-
continue;
|
|
1314
|
-
}
|
|
1315
1270
|
// Detect the MiniMax "only-thinking, no tool call" violation: no tool
|
|
1316
1271
|
// calls returned, and the content is empty after stripping
|
|
1317
1272
|
// <think>...</think> blocks. This is a narrow check — legitimate
|
|
@@ -1349,7 +1304,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
1349
1304
|
contentLength: result.content.length,
|
|
1350
1305
|
},
|
|
1351
1306
|
});
|
|
1352
|
-
|
|
1307
|
+
pushGenerated(msg);
|
|
1353
1308
|
messages.push({
|
|
1354
1309
|
role: "user",
|
|
1355
1310
|
content: `${privateReturnTextAckRetryError} Emit the ponder(action=create, ...) tool call now, or ask a blocking clarification without saying the private work is queued.`,
|
|
@@ -1370,7 +1325,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
1370
1325
|
},
|
|
1371
1326
|
});
|
|
1372
1327
|
msg.content = blockedAnswer;
|
|
1373
|
-
|
|
1328
|
+
pushGenerated(msg);
|
|
1374
1329
|
callbacks.onTextChunk(blockedAnswer);
|
|
1375
1330
|
completion = { answer: blockedAnswer, intent: "blocked" };
|
|
1376
1331
|
outcome = "blocked";
|
|
@@ -1398,7 +1353,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
1398
1353
|
contentLength: result.content.length,
|
|
1399
1354
|
},
|
|
1400
1355
|
});
|
|
1401
|
-
|
|
1356
|
+
pushGenerated(msg);
|
|
1402
1357
|
messages.push({
|
|
1403
1358
|
role: "user",
|
|
1404
1359
|
content: isPrivateRuntimeChannel
|
|
@@ -1411,7 +1366,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
1411
1366
|
}
|
|
1412
1367
|
// Legitimate text-only response, or cap reached — accept as-is.
|
|
1413
1368
|
await streamCallbackBuffer?.flush();
|
|
1414
|
-
|
|
1369
|
+
pushGenerated(msg);
|
|
1415
1370
|
done = true;
|
|
1416
1371
|
}
|
|
1417
1372
|
else {
|
|
@@ -1421,10 +1376,10 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
1421
1376
|
if (habitBlockReason) {
|
|
1422
1377
|
streamCallbackBuffer?.discard();
|
|
1423
1378
|
recordBlockedHabitSurfaceAttempts(habitSession, result.toolCalls, habitBlockReason);
|
|
1424
|
-
|
|
1379
|
+
pushGenerated(msg);
|
|
1425
1380
|
const blockedOutput = `blocked: ${habitBlockReason}. No tool side effects from this assistant message were executed.`;
|
|
1426
1381
|
for (const call of result.toolCalls) {
|
|
1427
|
-
|
|
1382
|
+
pushGenerated({ role: "tool", tool_call_id: call.id, content: blockedOutput });
|
|
1428
1383
|
providerRuntime.appendToolOutput(call.id, blockedOutput);
|
|
1429
1384
|
}
|
|
1430
1385
|
(0, runtime_1.emitNervesEvent)({
|
|
@@ -1470,9 +1425,9 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
1470
1425
|
}
|
|
1471
1426
|
catch (error) {
|
|
1472
1427
|
callbacks.onToolEnd(soleTerminalCall.name, (0, tools_1.summarizeArgs)(soleTerminalCall.name, terminalArgs), false);
|
|
1473
|
-
|
|
1428
|
+
pushGenerated(msg);
|
|
1474
1429
|
const failure = error instanceof Error ? `error: ${error.message}` : `error: ${String(error)}`;
|
|
1475
|
-
|
|
1430
|
+
pushGenerated({ role: "tool", tool_call_id: soleTerminalCall.id, content: failure });
|
|
1476
1431
|
providerRuntime.appendToolOutput(soleTerminalCall.id, failure);
|
|
1477
1432
|
callbacks.onTextChunk(failure);
|
|
1478
1433
|
completion = { answer: failure, intent: "blocked" };
|
|
@@ -1481,8 +1436,8 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
1481
1436
|
continue;
|
|
1482
1437
|
}
|
|
1483
1438
|
callbacks.onToolEnd(soleTerminalCall.name, (0, tools_1.summarizeArgs)(soleTerminalCall.name, terminalArgs), true);
|
|
1484
|
-
|
|
1485
|
-
|
|
1439
|
+
pushGenerated(msg);
|
|
1440
|
+
pushGenerated({ role: "tool", tool_call_id: soleTerminalCall.id, content: terminalResult });
|
|
1486
1441
|
providerRuntime.appendToolOutput(soleTerminalCall.id, terminalResult);
|
|
1487
1442
|
callbacks.onTextChunk(terminalResult);
|
|
1488
1443
|
completion = { answer: terminalResult, intent: "complete" };
|
|
@@ -1506,9 +1461,9 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
1506
1461
|
streamCallbackBuffer?.discard();
|
|
1507
1462
|
callbacks.onToolEnd("settle", (0, tools_1.summarizeArgs)("settle", settleArgs), false);
|
|
1508
1463
|
callbacks.onClearText?.();
|
|
1509
|
-
|
|
1464
|
+
pushGenerated(msg);
|
|
1510
1465
|
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.";
|
|
1511
|
-
|
|
1466
|
+
pushGenerated({ role: "tool", tool_call_id: result.toolCalls[0].id, content: gateMessage });
|
|
1512
1467
|
providerRuntime.appendToolOutput(result.toolCalls[0].id, gateMessage);
|
|
1513
1468
|
continue;
|
|
1514
1469
|
}
|
|
@@ -1519,9 +1474,9 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
1519
1474
|
if (isPrivateRuntimeChannel) {
|
|
1520
1475
|
streamCallbackBuffer?.discard();
|
|
1521
1476
|
callbacks.onToolEnd("settle", (0, tools_1.summarizeArgs)("settle", settleArgs), true);
|
|
1522
|
-
|
|
1477
|
+
pushGenerated(msg);
|
|
1523
1478
|
const settled = "(settled)";
|
|
1524
|
-
|
|
1479
|
+
pushGenerated({ role: "tool", tool_call_id: result.toolCalls[0].id, content: settled });
|
|
1525
1480
|
providerRuntime.appendToolOutput(result.toolCalls[0].id, settled);
|
|
1526
1481
|
outcome = "settled";
|
|
1527
1482
|
done = true;
|
|
@@ -1559,15 +1514,15 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
1559
1514
|
};
|
|
1560
1515
|
// Retractable owners already hold the validated answer. Final-only
|
|
1561
1516
|
// owners receive it here, after every semantic continuation gate.
|
|
1562
|
-
|
|
1517
|
+
pushGenerated(msg);
|
|
1563
1518
|
if (validDirectReply) {
|
|
1564
1519
|
const resumeWork = "direct reply delivered. resume the unresolved obligation now and keep working until you can finish or clearly report that you are blocked.";
|
|
1565
|
-
|
|
1520
|
+
pushGenerated({ role: "tool", tool_call_id: result.toolCalls[0].id, content: resumeWork });
|
|
1566
1521
|
providerRuntime.appendToolOutput(result.toolCalls[0].id, resumeWork);
|
|
1567
1522
|
}
|
|
1568
1523
|
else {
|
|
1569
1524
|
const delivered = "(delivered)";
|
|
1570
|
-
|
|
1525
|
+
pushGenerated({ role: "tool", tool_call_id: result.toolCalls[0].id, content: delivered });
|
|
1571
1526
|
providerRuntime.appendToolOutput(result.toolCalls[0].id, delivered);
|
|
1572
1527
|
outcome = intent === "blocked" ? "blocked" : "settled";
|
|
1573
1528
|
done = true;
|
|
@@ -1579,9 +1534,9 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
1579
1534
|
streamCallbackBuffer?.discard();
|
|
1580
1535
|
callbacks.onToolEnd("settle", (0, tools_1.summarizeArgs)("settle", settleArgs), false);
|
|
1581
1536
|
callbacks.onClearText?.();
|
|
1582
|
-
|
|
1537
|
+
pushGenerated(msg);
|
|
1583
1538
|
const toolRetryMessage = retryError;
|
|
1584
|
-
|
|
1539
|
+
pushGenerated({ role: "tool", tool_call_id: result.toolCalls[0].id, content: toolRetryMessage });
|
|
1585
1540
|
providerRuntime.appendToolOutput(result.toolCalls[0].id, toolRetryMessage);
|
|
1586
1541
|
}
|
|
1587
1542
|
continue;
|
|
@@ -1608,9 +1563,9 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
1608
1563
|
meta: { ...(reason ? { reason } : {}) },
|
|
1609
1564
|
});
|
|
1610
1565
|
callbacks.onToolEnd("observe", (0, tools_1.summarizeArgs)("observe", observeArgs), true);
|
|
1611
|
-
|
|
1566
|
+
pushGenerated(msg);
|
|
1612
1567
|
const silenced = "(silenced)";
|
|
1613
|
-
|
|
1568
|
+
pushGenerated({ role: "tool", tool_call_id: result.toolCalls[0].id, content: silenced });
|
|
1614
1569
|
providerRuntime.appendToolOutput(result.toolCalls[0].id, silenced);
|
|
1615
1570
|
outcome = "observed";
|
|
1616
1571
|
done = true;
|
|
@@ -1631,18 +1586,18 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
1631
1586
|
const attentionQueue = augmentedToolContext?.delegatedOrigins;
|
|
1632
1587
|
if (attentionQueue && attentionQueue.length > 0) {
|
|
1633
1588
|
callbacks.onToolEnd("rest", (0, tools_1.summarizeArgs)("rest", restArgs), false);
|
|
1634
|
-
|
|
1589
|
+
pushGenerated(msg);
|
|
1635
1590
|
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.";
|
|
1636
|
-
|
|
1591
|
+
pushGenerated({ role: "tool", tool_call_id: result.toolCalls[0].id, content: gateMessage });
|
|
1637
1592
|
providerRuntime.appendToolOutput(result.toolCalls[0].id, gateMessage);
|
|
1638
1593
|
continue;
|
|
1639
1594
|
}
|
|
1640
1595
|
if (hasFreshPendingWork(options) && !freshWorkGateFired) {
|
|
1641
1596
|
freshWorkGateFired = true;
|
|
1642
1597
|
callbacks.onToolEnd("rest", (0, tools_1.summarizeArgs)("rest", restArgs), false);
|
|
1643
|
-
|
|
1598
|
+
pushGenerated(msg);
|
|
1644
1599
|
const gateMessage = "fresh work arrived for me this turn — inspect the pending messages above and take the next concrete action before you rest.";
|
|
1645
|
-
|
|
1600
|
+
pushGenerated({ role: "tool", tool_call_id: result.toolCalls[0].id, content: gateMessage });
|
|
1646
1601
|
providerRuntime.appendToolOutput(result.toolCalls[0].id, gateMessage);
|
|
1647
1602
|
(0, runtime_1.emitNervesEvent)({
|
|
1648
1603
|
level: "info",
|
|
@@ -1654,9 +1609,9 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
1654
1609
|
continue;
|
|
1655
1610
|
}
|
|
1656
1611
|
callbacks.onToolEnd("rest", (0, tools_1.summarizeArgs)("rest", restArgs), true);
|
|
1657
|
-
|
|
1612
|
+
pushGenerated(msg);
|
|
1658
1613
|
const ack = "(resting)";
|
|
1659
|
-
|
|
1614
|
+
pushGenerated({ role: "tool", tool_call_id: result.toolCalls[0].id, content: ack });
|
|
1660
1615
|
providerRuntime.appendToolOutput(result.toolCalls[0].id, ack);
|
|
1661
1616
|
(0, runtime_1.emitNervesEvent)({
|
|
1662
1617
|
component: "engine",
|
|
@@ -1680,11 +1635,26 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
1680
1635
|
else {
|
|
1681
1636
|
await streamCallbackBuffer?.flush();
|
|
1682
1637
|
}
|
|
1683
|
-
|
|
1638
|
+
pushGenerated(msg);
|
|
1684
1639
|
// Execute tools (sole-call tools in mixed calls are rejected inline)
|
|
1685
1640
|
for (const tc of result.toolCalls) {
|
|
1686
1641
|
if (signal?.aborted)
|
|
1687
1642
|
break;
|
|
1643
|
+
if (tc.name === "speak" && !activeToolNames.has("speak")) {
|
|
1644
|
+
const rejection = "rejected: speak was not advertised for this channel; no outward message was sent.";
|
|
1645
|
+
callbacks.onToolStart(tc.name, {});
|
|
1646
|
+
callbacks.onToolEnd(tc.name, "", false);
|
|
1647
|
+
pushGenerated({ role: "tool", tool_call_id: tc.id, content: rejection });
|
|
1648
|
+
providerRuntime.appendToolOutput(tc.id, rejection);
|
|
1649
|
+
(0, runtime_1.emitNervesEvent)({
|
|
1650
|
+
level: "warn",
|
|
1651
|
+
component: "engine",
|
|
1652
|
+
event: "engine.unadvertised_speak_blocked",
|
|
1653
|
+
message: "blocked an unadvertised speak tool call",
|
|
1654
|
+
meta: { channel: String(channel) },
|
|
1655
|
+
});
|
|
1656
|
+
continue;
|
|
1657
|
+
}
|
|
1688
1658
|
// Reject sole-call tools when mixed with other tool calls
|
|
1689
1659
|
const terminalProjection = (0, tools_1.resolveToolDefinition)(tc.name)?.terminalProjection;
|
|
1690
1660
|
const soleCallRejection = SOLE_CALL_REJECTION[tc.name]
|
|
@@ -1692,7 +1662,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
1692
1662
|
? `rejected: ${tc.name} must be the only tool call.`
|
|
1693
1663
|
: undefined);
|
|
1694
1664
|
if (soleCallRejection) {
|
|
1695
|
-
|
|
1665
|
+
pushGenerated({ role: "tool", tool_call_id: tc.id, content: soleCallRejection });
|
|
1696
1666
|
providerRuntime.appendToolOutput(tc.id, soleCallRejection);
|
|
1697
1667
|
continue;
|
|
1698
1668
|
}
|
|
@@ -1710,7 +1680,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
1710
1680
|
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.";
|
|
1711
1681
|
callbacks.onToolStart(tc.name, args);
|
|
1712
1682
|
callbacks.onToolEnd(tc.name, argSummary, false);
|
|
1713
|
-
|
|
1683
|
+
pushGenerated({ role: "tool", tool_call_id: tc.id, content: rejection });
|
|
1714
1684
|
providerRuntime.appendToolOutput(tc.id, rejection);
|
|
1715
1685
|
continue;
|
|
1716
1686
|
}
|
|
@@ -1728,7 +1698,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
1728
1698
|
if (speakMessage.trim().length === 0) {
|
|
1729
1699
|
const err = "speak requires a non-empty `message` string.";
|
|
1730
1700
|
callbacks.onToolEnd("speak", argSummary, false);
|
|
1731
|
-
|
|
1701
|
+
pushGenerated({ role: "tool", tool_call_id: tc.id, content: err });
|
|
1732
1702
|
providerRuntime.appendToolOutput(tc.id, err);
|
|
1733
1703
|
(0, runtime_1.emitNervesEvent)({
|
|
1734
1704
|
level: "warn",
|
|
@@ -1750,7 +1720,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
1750
1720
|
if (speakDeliveryError) {
|
|
1751
1721
|
callbacks.onToolEnd("speak", argSummary, false);
|
|
1752
1722
|
const failMsg = `speak delivery failed: ${speakDeliveryError.message}. the message did not reach your friend; do not assume they saw it.`;
|
|
1753
|
-
|
|
1723
|
+
pushGenerated({ role: "tool", tool_call_id: tc.id, content: failMsg });
|
|
1754
1724
|
providerRuntime.appendToolOutput(tc.id, failMsg);
|
|
1755
1725
|
(0, runtime_1.emitNervesEvent)({
|
|
1756
1726
|
level: "error",
|
|
@@ -1763,7 +1733,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
1763
1733
|
}
|
|
1764
1734
|
callbacks.onToolEnd("speak", argSummary, true);
|
|
1765
1735
|
const ack = "(spoken)";
|
|
1766
|
-
|
|
1736
|
+
pushGenerated({ role: "tool", tool_call_id: tc.id, content: ack });
|
|
1767
1737
|
providerRuntime.appendToolOutput(tc.id, ack);
|
|
1768
1738
|
(0, runtime_1.emitNervesEvent)({
|
|
1769
1739
|
component: "engine",
|
|
@@ -1928,7 +1898,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
1928
1898
|
toolResult = error instanceof Error ? error.message : String(error);
|
|
1929
1899
|
}
|
|
1930
1900
|
callbacks.onToolEnd(tc.name, argSummary, success);
|
|
1931
|
-
|
|
1901
|
+
pushGenerated({ role: "tool", tool_call_id: tc.id, content: toolResult });
|
|
1932
1902
|
providerRuntime.appendToolOutput(tc.id, toolResult);
|
|
1933
1903
|
continue;
|
|
1934
1904
|
}
|
|
@@ -1947,7 +1917,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
1947
1917
|
const rejection = `loop guard: ${toolLoop.message}`;
|
|
1948
1918
|
callbacks.onToolStart(tc.name, args);
|
|
1949
1919
|
callbacks.onToolEnd(tc.name, argSummary, false);
|
|
1950
|
-
|
|
1920
|
+
pushGenerated({ role: "tool", tool_call_id: tc.id, content: rejection });
|
|
1951
1921
|
providerRuntime.appendToolOutput(tc.id, rejection);
|
|
1952
1922
|
continue;
|
|
1953
1923
|
}
|
|
@@ -1967,7 +1937,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
1967
1937
|
toolResult = (0, tool_friction_1.rewriteToolResultForModel)(tc.name, toolResult, toolFrictionLedger);
|
|
1968
1938
|
(0, tool_loop_1.recordToolOutcome)(toolLoopState, tc.name, args, toolResult, success);
|
|
1969
1939
|
callbacks.onToolEnd(tc.name, (0, tools_1.buildToolResultSummary)(tc.name, args, toolResult, success), success);
|
|
1970
|
-
|
|
1940
|
+
pushGenerated({ role: "tool", tool_call_id: tc.id, content: toolResult });
|
|
1971
1941
|
providerRuntime.appendToolOutput(tc.id, toolResult);
|
|
1972
1942
|
callbacks.onToolResult?.(messages);
|
|
1973
1943
|
}
|
|
@@ -1977,6 +1947,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
1977
1947
|
// Abort is not an error — just stop cleanly
|
|
1978
1948
|
if (e instanceof provider_attempt_1.ProviderAttemptAbortError || signal?.aborted) {
|
|
1979
1949
|
stripLastToolCalls(messages);
|
|
1950
|
+
stripLastToolCalls(generatedMessages);
|
|
1980
1951
|
outcome = "aborted";
|
|
1981
1952
|
break;
|
|
1982
1953
|
}
|
|
@@ -1992,6 +1963,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
1992
1963
|
finishTerminalProviderError(errorForClassification, providerClassification);
|
|
1993
1964
|
}
|
|
1994
1965
|
}
|
|
1966
|
+
options?.captureGeneratedMessages?.(structuredClone(generatedMessages));
|
|
1995
1967
|
(0, runtime_1.emitNervesEvent)({
|
|
1996
1968
|
event: "engine.turn_end",
|
|
1997
1969
|
trace_id: traceId,
|