@ouro.bot/cli 0.1.0-alpha.724 → 0.1.0-alpha.725
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 +12 -0
- package/dist/heart/active-work.js +10 -3
- package/dist/heart/core.js +343 -77
- package/dist/heart/daemon/cli-exec.js +72 -14
- package/dist/heart/daemon/cli-help.js +13 -2
- package/dist/heart/daemon/cli-parse.js +82 -1
- package/dist/heart/daemon/daemon.js +286 -33
- package/dist/heart/daemon/doctor.js +21 -6
- package/dist/heart/daemon/habit-private-wake.js +6 -1
- package/dist/heart/habits/habit-cancel.js +810 -0
- package/dist/heart/habits/habit-lifecycle.js +1327 -0
- package/dist/heart/habits/habit-migration.js +28 -23
- package/dist/heart/habits/habit-parser.js +72 -8
- package/dist/heart/habits/habit-runtime-state.js +3 -24
- package/dist/heart/habits/habit-scheduler.js +268 -50
- package/dist/heart/habits/habit-session.js +16 -0
- package/dist/heart/hatch/hatch-flow.js +6 -4
- package/dist/heart/mailbox/readers/runtime-readers.js +26 -27
- package/dist/heart/orientation-frame.js +58 -1
- package/dist/heart/provider-attempt.js +4 -1
- package/dist/heart/providers/anthropic.js +23 -2
- package/dist/heart/start-of-turn-packet.js +14 -11
- package/dist/heart/streaming.js +565 -185
- package/dist/mind/obligation-steering.js +15 -9
- package/dist/mind/prompt.js +58 -26
- package/dist/nerves/coverage/run-artifacts.js +55 -35
- package/dist/nerves/review/cli.js +2 -2
- package/dist/nerves/review/core.js +14 -7
- package/dist/repertoire/tools-base.js +2 -0
- package/dist/repertoire/tools-habits.js +103 -0
- package/dist/repertoire/tools.js +18 -3
- package/dist/rsvp/cli.js +126 -20
- package/dist/rsvp/diagnostics.js +12 -2
- package/dist/rsvp/habit-policy.js +7 -0
- package/dist/rsvp/habit-stage.js +19 -4
- package/dist/rsvp/native-habit-runner.js +151 -11
- package/dist/rsvp/outbound-state.js +360 -0
- package/dist/senses/bluebubbles/client.js +90 -17
- package/dist/senses/bluebubbles/inbound-log.js +1 -0
- package/dist/senses/bluebubbles/index.js +662 -425
- package/dist/senses/bluebubbles/model.js +57 -13
- package/dist/senses/bluebubbles/mutation-log.js +21 -10
- package/dist/senses/bluebubbles/reaction-policy.js +55 -0
- package/dist/senses/bluebubbles/semantic-receipts.js +1818 -0
- package/dist/senses/cli/tui-store.js +1 -0
- package/dist/senses/cli.js +1 -0
- package/dist/senses/habit-lifecycle-guard.js +24 -0
- package/dist/senses/ingress-evidence.js +2 -0
- package/dist/senses/pipeline.js +90 -24
- package/dist/senses/private-runtime-worker.js +21 -20
- package/dist/senses/private-runtime.js +79 -29
- package/dist/senses/shared-turn.js +1 -0
- package/dist/senses/teams.js +1 -0
- package/package.json +6 -1
package/dist/heart/core.js
CHANGED
|
@@ -19,6 +19,7 @@ const identity_1 = require("./identity");
|
|
|
19
19
|
const tools_1 = require("../repertoire/tools");
|
|
20
20
|
const friends_1 = require("@ouro.bot/friends");
|
|
21
21
|
const tools_2 = require("../repertoire/tools");
|
|
22
|
+
const streaming_1 = require("./streaming");
|
|
22
23
|
const runtime_1 = require("../nerves/runtime");
|
|
23
24
|
const context_1 = require("../mind/context");
|
|
24
25
|
const prompt_budget_1 = require("../mind/prompt-budget");
|
|
@@ -30,6 +31,7 @@ const azure_1 = require("./providers/azure");
|
|
|
30
31
|
const minimax_1 = require("./providers/minimax");
|
|
31
32
|
const openai_codex_1 = require("./providers/openai-codex");
|
|
32
33
|
const github_copilot_1 = require("./providers/github-copilot");
|
|
34
|
+
const orientation_frame_1 = require("./orientation-frame");
|
|
33
35
|
const identity_2 = require("./identity");
|
|
34
36
|
const socket_client_1 = require("./daemon/socket-client");
|
|
35
37
|
const obligations_1 = require("../arc/obligations");
|
|
@@ -188,17 +190,20 @@ function getProviderDisplayLabel(facing = "human") {
|
|
|
188
190
|
};
|
|
189
191
|
return providerLabelBuilders[provider]();
|
|
190
192
|
}
|
|
191
|
-
function
|
|
193
|
+
function createCallbackBuffer(callbacks, mode) {
|
|
192
194
|
const events = [];
|
|
195
|
+
const bufferedCallbacks = {
|
|
196
|
+
...callbacks,
|
|
197
|
+
onTextChunk: (text) => { events.push({ kind: "text", text }); },
|
|
198
|
+
onClearText: () => { events.push({ kind: "clear" }); },
|
|
199
|
+
flushNow: () => { events.push({ kind: "flush" }); },
|
|
200
|
+
};
|
|
201
|
+
if (mode === "all") {
|
|
202
|
+
bufferedCallbacks.onModelStreamStart = () => { events.push({ kind: "stream-start" }); };
|
|
203
|
+
bufferedCallbacks.onReasoningChunk = (text) => { events.push({ kind: "reasoning", text }); };
|
|
204
|
+
}
|
|
193
205
|
return {
|
|
194
|
-
callbacks:
|
|
195
|
-
...callbacks,
|
|
196
|
-
onModelStreamStart: () => { events.push({ kind: "stream-start" }); },
|
|
197
|
-
onTextChunk: (text) => { events.push({ kind: "text", text }); },
|
|
198
|
-
onReasoningChunk: (text) => { events.push({ kind: "reasoning", text }); },
|
|
199
|
-
onClearText: () => { events.push({ kind: "clear" }); },
|
|
200
|
-
flushNow: () => { events.push({ kind: "flush" }); },
|
|
201
|
-
},
|
|
206
|
+
callbacks: bufferedCallbacks,
|
|
202
207
|
async flush() {
|
|
203
208
|
for (const event of events.splice(0)) {
|
|
204
209
|
switch (event.kind) {
|
|
@@ -225,6 +230,12 @@ function createHabitCallbackBuffer(callbacks) {
|
|
|
225
230
|
},
|
|
226
231
|
};
|
|
227
232
|
}
|
|
233
|
+
function createHabitCallbackBuffer(callbacks) {
|
|
234
|
+
return createCallbackBuffer(callbacks, "all");
|
|
235
|
+
}
|
|
236
|
+
function createFinalOnlyTextBuffer(callbacks) {
|
|
237
|
+
return createCallbackBuffer(callbacks, "text-only");
|
|
238
|
+
}
|
|
228
239
|
/**
|
|
229
240
|
* Strip <think>...</think> blocks for the violation-detection check at the
|
|
230
241
|
* end of a streaming turn. Used to tell legitimate text-only responses
|
|
@@ -295,7 +306,10 @@ function recordBlockedHabitSurfaceAttempts(habitSession, toolCalls, reason) {
|
|
|
295
306
|
});
|
|
296
307
|
}
|
|
297
308
|
}
|
|
298
|
-
async function habitToolBatchBlockReason(habitSession, toolCalls, delegatedOrigins, activeToolNames) {
|
|
309
|
+
async function habitToolBatchBlockReason(habitSession, toolCalls, delegatedOrigins, activeToolNames, noSend) {
|
|
310
|
+
if (noSend && toolCalls.some((call) => call.name === "ponder")) {
|
|
311
|
+
return "habit tool 'ponder' cannot create continuations under immutable no-send authority";
|
|
312
|
+
}
|
|
299
313
|
if (!habitSession)
|
|
300
314
|
return null;
|
|
301
315
|
const granted = new Set(habitSession.toolPolicy.grantedTools);
|
|
@@ -340,6 +354,36 @@ async function habitToolBatchBlockReason(habitSession, toolCalls, delegatedOrigi
|
|
|
340
354
|
function isChatStyleChannel(channel) {
|
|
341
355
|
return channel === "cli" || channel === "teams" || channel === "bluebubbles" || channel === "voice";
|
|
342
356
|
}
|
|
357
|
+
function bindCurrentIngressEvidenceLocator(toolList, evidence) {
|
|
358
|
+
if (!evidence
|
|
359
|
+
|| evidence.schemaVersion !== 1
|
|
360
|
+
|| evidence.provider !== "bluebubbles"
|
|
361
|
+
|| !/^[a-f0-9]{64}$/.test(evidence.captureKeyHash))
|
|
362
|
+
return toolList;
|
|
363
|
+
const locator = `capture:${evidence.captureKeyHash}`;
|
|
364
|
+
return toolList.map((tool) => {
|
|
365
|
+
if (tool.function.name !== "habit_cancel")
|
|
366
|
+
return tool;
|
|
367
|
+
const parameters = tool.function.parameters;
|
|
368
|
+
const properties = parameters.properties;
|
|
369
|
+
return {
|
|
370
|
+
...tool,
|
|
371
|
+
function: {
|
|
372
|
+
...tool.function,
|
|
373
|
+
parameters: {
|
|
374
|
+
...parameters,
|
|
375
|
+
properties: {
|
|
376
|
+
...properties,
|
|
377
|
+
evidence: {
|
|
378
|
+
...properties.evidence,
|
|
379
|
+
enum: [locator],
|
|
380
|
+
},
|
|
381
|
+
},
|
|
382
|
+
},
|
|
383
|
+
},
|
|
384
|
+
};
|
|
385
|
+
});
|
|
386
|
+
}
|
|
343
387
|
// Sole-call tools must be the only tool call in a turn. When they appear
|
|
344
388
|
// alongside other tools, the sole-call tool is rejected with this message.
|
|
345
389
|
const SOLE_CALL_REJECTION = {
|
|
@@ -350,14 +394,8 @@ const SOLE_CALL_REJECTION = {
|
|
|
350
394
|
function parseSettlePayload(argumentsText) {
|
|
351
395
|
try {
|
|
352
396
|
const parsed = JSON.parse(argumentsText);
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
}
|
|
356
|
-
if (!parsed || typeof parsed !== "object") {
|
|
357
|
-
return {};
|
|
358
|
-
}
|
|
359
|
-
const answer = typeof parsed.answer === "string" ? parsed.answer : undefined;
|
|
360
|
-
const rawIntent = parsed.intent;
|
|
397
|
+
const answer = typeof parsed?.answer === "string" ? parsed.answer : undefined;
|
|
398
|
+
const rawIntent = parsed?.intent;
|
|
361
399
|
const intent = rawIntent === "complete" || rawIntent === "blocked" || rawIntent === "direct_reply"
|
|
362
400
|
? rawIntent
|
|
363
401
|
: undefined;
|
|
@@ -367,6 +405,23 @@ function parseSettlePayload(argumentsText) {
|
|
|
367
405
|
return {};
|
|
368
406
|
}
|
|
369
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;
|
|
424
|
+
}
|
|
370
425
|
function parsePonderPayload(argumentsText) {
|
|
371
426
|
try {
|
|
372
427
|
const parsed = JSON.parse(argumentsText);
|
|
@@ -574,6 +629,15 @@ function upsertSystemPrompt(messages, systemText) {
|
|
|
574
629
|
messages.unshift(systemMessage);
|
|
575
630
|
}
|
|
576
631
|
}
|
|
632
|
+
/**
|
|
633
|
+
* A prompt refresh failure must not revive any generated text from an older
|
|
634
|
+
* turn or release. Even the nominally stable region contains actor-scoped trust,
|
|
635
|
+
* tool, and channel context, so it is not safe to reconstruct. Fail closed to a
|
|
636
|
+
* neutral base and put the freshly derived trigger first.
|
|
637
|
+
*/
|
|
638
|
+
function repairFallbackSystemPrompt(orientationFrame) {
|
|
639
|
+
return `${(0, orientation_frame_1.renderOrientationFrame)(orientationFrame)}\n\nYou are a helpful assistant.`;
|
|
640
|
+
}
|
|
577
641
|
// Remove orphan tool_calls from the last assistant message and any
|
|
578
642
|
// trailing tool-result messages that lack a matching tool_call.
|
|
579
643
|
// This keeps the conversation valid after an abort or tool-loop limit.
|
|
@@ -747,7 +811,8 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
747
811
|
const facing = (0, friends_1.channelToFacing)(channel);
|
|
748
812
|
let providerRuntime = await getProviderRuntime(facing);
|
|
749
813
|
const provider = providerRuntime.id;
|
|
750
|
-
const
|
|
814
|
+
const restrictedReactionFeedback = options?.restrictedReactionFeedback === true;
|
|
815
|
+
const toolChoiceRequired = restrictedReactionFeedback ? true : options?.toolChoiceRequired ?? true;
|
|
751
816
|
const traceId = options?.traceId;
|
|
752
817
|
(0, runtime_1.emitNervesEvent)({
|
|
753
818
|
event: "engine.turn_start",
|
|
@@ -766,14 +831,18 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
766
831
|
currentContext = { ...currentContext, friend: freshFriend };
|
|
767
832
|
}
|
|
768
833
|
}
|
|
834
|
+
const turnOrientationFrame = options?.orientationFrame
|
|
835
|
+
?? (channel ? (0, orientation_frame_1.buildOrientationFrame)({ channel, messages }) : undefined);
|
|
769
836
|
// Refresh system prompt at start of each turn when channel is provided.
|
|
770
|
-
// If refresh fails,
|
|
771
|
-
// so
|
|
837
|
+
// If refresh fails, retain only a recognised stable prefix (or inject a
|
|
838
|
+
// minimal safe fallback) so stale dynamic state cannot regain authority.
|
|
772
839
|
let structuredSystemPrompt;
|
|
773
840
|
if (channel) {
|
|
774
841
|
try {
|
|
775
842
|
const buildSystemOptions = {
|
|
776
843
|
...options,
|
|
844
|
+
orientationFrame: turnOrientationFrame,
|
|
845
|
+
resumePriorWork: options?.resumePriorWork === true,
|
|
777
846
|
providerCapabilities: providerRuntime.capabilities,
|
|
778
847
|
supportedReasoningEfforts: providerRuntime.supportedReasoningEfforts,
|
|
779
848
|
};
|
|
@@ -783,8 +852,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
783
852
|
}
|
|
784
853
|
catch (error) {
|
|
785
854
|
const hadExistingSystemPrompt = messages[0]?.role === "system" && typeof messages[0].content === "string";
|
|
786
|
-
const
|
|
787
|
-
const fallback = existingSystemText ?? "You are a helpful assistant.";
|
|
855
|
+
const fallback = repairFallbackSystemPrompt(turnOrientationFrame);
|
|
788
856
|
upsertSystemPrompt(messages, fallback);
|
|
789
857
|
(0, runtime_1.emitNervesEvent)({
|
|
790
858
|
level: "warn",
|
|
@@ -874,16 +942,25 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
874
942
|
outcome = "errored";
|
|
875
943
|
done = true;
|
|
876
944
|
};
|
|
945
|
+
const finishRestrictedReactionFeedbackViolation = (reason) => {
|
|
946
|
+
callbacks.onClearText?.();
|
|
947
|
+
finishTerminalProviderError(new Error(`restricted reaction feedback failed closed: ${reason}`), "unknown");
|
|
948
|
+
};
|
|
877
949
|
// Prevent MaxListenersExceeded warning — each iteration adds a listener
|
|
878
950
|
try {
|
|
879
951
|
require("events").setMaxListeners(50, signal);
|
|
880
952
|
}
|
|
881
953
|
catch { /* unsupported */ }
|
|
882
954
|
const toolPreferences = currentContext?.friend?.toolPreferences;
|
|
883
|
-
const
|
|
955
|
+
const unboundBaseTools = restrictedReactionFeedback
|
|
956
|
+
? (0, tools_1.getRestrictedReactionFeedbackTools)()
|
|
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);
|
|
884
961
|
// Augment tool context with reasoning effort controls from provider
|
|
885
962
|
const baseToolContext = options?.toolContext
|
|
886
|
-
?? (
|
|
963
|
+
?? (turnOrientationFrame ? { signin: async () => undefined, orientationFrame: turnOrientationFrame } : undefined);
|
|
887
964
|
const habitSession = options?.habitSession ?? baseToolContext?.habitSession;
|
|
888
965
|
const augmentedToolContext = baseToolContext
|
|
889
966
|
? {
|
|
@@ -891,7 +968,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
891
968
|
supportedReasoningEfforts: providerRuntime.supportedReasoningEfforts,
|
|
892
969
|
setReasoningEffort: (level) => { currentReasoningEffort = level; },
|
|
893
970
|
activeWorkFrame: options?.activeWorkFrame,
|
|
894
|
-
orientationFrame:
|
|
971
|
+
orientationFrame: turnOrientationFrame ?? baseToolContext.orientationFrame,
|
|
895
972
|
...(habitSession ? { habitSession } : {}),
|
|
896
973
|
}
|
|
897
974
|
: habitSession
|
|
@@ -924,17 +1001,21 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
924
1001
|
const filteredBaseTools = isPrivateRuntimeChannel
|
|
925
1002
|
? baseTools.filter((t) => privateRuntimeHabitCanSendMessage || t.function.name !== "send_message")
|
|
926
1003
|
: baseTools;
|
|
927
|
-
const activeTools =
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
1004
|
+
const activeTools = restrictedReactionFeedback
|
|
1005
|
+
? filteredBaseTools
|
|
1006
|
+
: [
|
|
1007
|
+
...filteredBaseTools,
|
|
1008
|
+
...(augmentedToolContext?.noSend === true ? [] : [tools_1.ponderTool]),
|
|
1009
|
+
...(isPrivateRuntimeChannel && privateRuntimeHabitCanSurface ? [tools_2.surfaceToolDef] : []),
|
|
1010
|
+
...(isPrivateRuntimeChannel ? [tools_1.restTool] : []),
|
|
1011
|
+
...(!isPrivateRuntimeChannel ? [tools_1.observeTool] : []),
|
|
1012
|
+
...(!isPrivateRuntimeChannel ? [tools_1.settleTool] : []),
|
|
1013
|
+
...(isChatStyleChannel(channel ?? "") ? [tools_1.speakTool] : []),
|
|
1014
|
+
];
|
|
936
1015
|
const activeToolNames = new Set(activeTools.map((tool) => tool.function.name));
|
|
937
|
-
const steeringFollowUps =
|
|
1016
|
+
const steeringFollowUps = restrictedReactionFeedback
|
|
1017
|
+
? []
|
|
1018
|
+
: options?.drainSteeringFollowUps?.() ?? [];
|
|
938
1019
|
if (steeringFollowUps.length > 0) {
|
|
939
1020
|
const hasSupersedingFollowUp = steeringFollowUps.some((followUp) => followUp.effect === "clear_and_supersede");
|
|
940
1021
|
if (hasSupersedingFollowUp) {
|
|
@@ -960,10 +1041,14 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
960
1041
|
break;
|
|
961
1042
|
}
|
|
962
1043
|
try {
|
|
963
|
-
const
|
|
1044
|
+
const turnCallbackBufferRef = { current: null };
|
|
964
1045
|
const callProviderTurn = async () => {
|
|
965
1046
|
callbacks.onModelStart();
|
|
966
|
-
|
|
1047
|
+
turnCallbackBufferRef.current = habitSession
|
|
1048
|
+
? createHabitCallbackBuffer(callbacks)
|
|
1049
|
+
: callbacks.settleOutputMode === "final_only"
|
|
1050
|
+
? createFinalOnlyTextBuffer(callbacks)
|
|
1051
|
+
: null;
|
|
967
1052
|
try {
|
|
968
1053
|
const promptBudget = (0, prompt_budget_1.applyPromptBudget)({
|
|
969
1054
|
messages,
|
|
@@ -978,18 +1063,18 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
978
1063
|
return await providerRuntime.streamTurn({
|
|
979
1064
|
messages,
|
|
980
1065
|
activeTools,
|
|
981
|
-
callbacks:
|
|
1066
|
+
callbacks: turnCallbackBufferRef.current?.callbacks ?? callbacks,
|
|
982
1067
|
signal,
|
|
983
1068
|
traceId,
|
|
984
1069
|
toolChoiceRequired,
|
|
985
1070
|
reasoningEffort: currentReasoningEffort,
|
|
986
|
-
eagerSettleStreaming:
|
|
1071
|
+
eagerSettleStreaming: !restrictedReactionFeedback,
|
|
987
1072
|
systemPrompt: structuredSystemPrompt,
|
|
988
1073
|
});
|
|
989
1074
|
}
|
|
990
1075
|
catch (error) {
|
|
991
|
-
|
|
992
|
-
|
|
1076
|
+
turnCallbackBufferRef.current?.discard();
|
|
1077
|
+
turnCallbackBufferRef.current = null;
|
|
993
1078
|
if (signal?.aborted)
|
|
994
1079
|
throw new provider_attempt_1.ProviderAttemptAbortError();
|
|
995
1080
|
throw error;
|
|
@@ -1019,8 +1104,9 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
1019
1104
|
operation: "turn",
|
|
1020
1105
|
provider: providerRuntime.id,
|
|
1021
1106
|
model: providerRuntime.model,
|
|
1022
|
-
run: callProviderTurnWithOverflowRecovery,
|
|
1107
|
+
run: restrictedReactionFeedback ? callProviderTurn : callProviderTurnWithOverflowRecovery,
|
|
1023
1108
|
classifyError: (error) => providerRuntime.classifyError(error),
|
|
1109
|
+
...(restrictedReactionFeedback ? { policy: { maxAttempts: 1 } } : {}),
|
|
1024
1110
|
onRetry: async (record, maxAttempts) => {
|
|
1025
1111
|
const delayMs = record.delayMs;
|
|
1026
1112
|
const seconds = delayMs / 1000;
|
|
@@ -1061,8 +1147,17 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
1061
1147
|
continue;
|
|
1062
1148
|
}
|
|
1063
1149
|
const result = attempt.value;
|
|
1064
|
-
const streamCallbackBuffer =
|
|
1065
|
-
|
|
1150
|
+
const streamCallbackBuffer = turnCallbackBufferRef.current;
|
|
1151
|
+
turnCallbackBufferRef.current = null;
|
|
1152
|
+
if (result.settleFinalization && !result.settleFinalization.ok) {
|
|
1153
|
+
// A completed settle payload is terminal provider output, not a prompt
|
|
1154
|
+
// for another model turn. Retractable callbacks have already cleared;
|
|
1155
|
+
// core-owned callback buffers must be discarded before surfacing the exact
|
|
1156
|
+
// parser failure through the ordinary terminal-error path.
|
|
1157
|
+
streamCallbackBuffer?.discard();
|
|
1158
|
+
finishTerminalProviderError(new Error(result.settleFinalization.errorCode), "unknown");
|
|
1159
|
+
continue;
|
|
1160
|
+
}
|
|
1066
1161
|
// Track usage from the latest API call
|
|
1067
1162
|
if (result.usage)
|
|
1068
1163
|
lastUsage = result.usage;
|
|
@@ -1123,6 +1218,100 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
1123
1218
|
if (hasPhaseAnnotation) {
|
|
1124
1219
|
msg.phase = isSoleSettle ? "settle" : "commentary";
|
|
1125
1220
|
}
|
|
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
|
+
}
|
|
1126
1315
|
// Detect the MiniMax "only-thinking, no tool call" violation: no tool
|
|
1127
1316
|
// calls returned, and the content is empty after stripping
|
|
1128
1317
|
// <think>...</think> blocks. This is a narrow check — legitimate
|
|
@@ -1141,8 +1330,8 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
1141
1330
|
})
|
|
1142
1331
|
: null;
|
|
1143
1332
|
if (!result.toolCalls.length) {
|
|
1144
|
-
await streamCallbackBuffer?.flush();
|
|
1145
1333
|
if (privateReturnTextAckRetryError) {
|
|
1334
|
+
streamCallbackBuffer?.discard();
|
|
1146
1335
|
callbacks.onClearText?.();
|
|
1147
1336
|
if (noToolCallRetries < NO_TOOL_CALL_MAX_RETRIES) {
|
|
1148
1337
|
noToolCallRetries++;
|
|
@@ -1189,6 +1378,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
1189
1378
|
continue;
|
|
1190
1379
|
}
|
|
1191
1380
|
if (onlyThinkContent && toolChoiceRequired && noToolCallRetries < NO_TOOL_CALL_MAX_RETRIES) {
|
|
1381
|
+
streamCallbackBuffer?.discard();
|
|
1192
1382
|
// Provider-level violation: tool_choice was required, model emitted
|
|
1193
1383
|
// only a <think>...</think> block (or empty content) with no tool
|
|
1194
1384
|
// call. Retry with a corrective nudge up to NO_TOOL_CALL_MAX_RETRIES
|
|
@@ -1212,19 +1402,22 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
1212
1402
|
messages.push({
|
|
1213
1403
|
role: "user",
|
|
1214
1404
|
content: isPrivateRuntimeChannel
|
|
1215
|
-
?
|
|
1405
|
+
? augmentedToolContext?.noSend === true
|
|
1406
|
+
? "no tool was called this turn. this is an immutable no-send turn; call rest now without creating a continuation."
|
|
1407
|
+
: "no tool was called this turn. you must end every turn by calling rest (or surface, ponder, observe). emit the tool call now."
|
|
1216
1408
|
: "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.",
|
|
1217
1409
|
});
|
|
1218
1410
|
continue;
|
|
1219
1411
|
}
|
|
1220
1412
|
// Legitimate text-only response, or cap reached — accept as-is.
|
|
1413
|
+
await streamCallbackBuffer?.flush();
|
|
1221
1414
|
messages.push(msg);
|
|
1222
1415
|
done = true;
|
|
1223
1416
|
}
|
|
1224
1417
|
else {
|
|
1225
1418
|
// Reset the retry counter on any successful tool call.
|
|
1226
1419
|
noToolCallRetries = 0;
|
|
1227
|
-
const habitBlockReason = await habitToolBatchBlockReason(habitSession, result.toolCalls, augmentedToolContext?.delegatedOrigins, activeToolNames);
|
|
1420
|
+
const habitBlockReason = await habitToolBatchBlockReason(habitSession, result.toolCalls, augmentedToolContext?.delegatedOrigins, activeToolNames, augmentedToolContext?.noSend === true);
|
|
1228
1421
|
if (habitBlockReason) {
|
|
1229
1422
|
streamCallbackBuffer?.discard();
|
|
1230
1423
|
recordBlockedHabitSurfaceAttempts(habitSession, result.toolCalls, habitBlockReason);
|
|
@@ -1243,7 +1436,60 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
1243
1436
|
});
|
|
1244
1437
|
continue;
|
|
1245
1438
|
}
|
|
1246
|
-
|
|
1439
|
+
const soleTerminalCall = result.toolCalls.length === 1
|
|
1440
|
+
? result.toolCalls[0]
|
|
1441
|
+
: null;
|
|
1442
|
+
const soleTerminalProjection = soleTerminalCall
|
|
1443
|
+
? (0, tools_1.resolveToolDefinition)(soleTerminalCall.name)?.terminalProjection
|
|
1444
|
+
: undefined;
|
|
1445
|
+
if (soleTerminalCall && soleTerminalProjection?.mode === "verbatim") {
|
|
1446
|
+
let terminalArgs = {};
|
|
1447
|
+
try {
|
|
1448
|
+
const parsed = JSON.parse(soleTerminalCall.arguments);
|
|
1449
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
1450
|
+
terminalArgs = parsed;
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
catch {
|
|
1454
|
+
// The registered handler owns exact argument validation.
|
|
1455
|
+
}
|
|
1456
|
+
if (soleTerminalProjection.clearBufferedText
|
|
1457
|
+
|| callbacks.settleOutputMode === "final_only") {
|
|
1458
|
+
streamCallbackBuffer?.discard();
|
|
1459
|
+
}
|
|
1460
|
+
else {
|
|
1461
|
+
await streamCallbackBuffer?.flush();
|
|
1462
|
+
}
|
|
1463
|
+
if (soleTerminalProjection.clearBufferedText)
|
|
1464
|
+
callbacks.onClearText?.();
|
|
1465
|
+
callbacks.onToolStart(soleTerminalCall.name, terminalArgs);
|
|
1466
|
+
let terminalResult;
|
|
1467
|
+
try {
|
|
1468
|
+
const execToolFn = options?.execTool ?? tools_1.execTool;
|
|
1469
|
+
terminalResult = await execToolFn(soleTerminalCall.name, terminalArgs, augmentedToolContext);
|
|
1470
|
+
}
|
|
1471
|
+
catch (error) {
|
|
1472
|
+
callbacks.onToolEnd(soleTerminalCall.name, (0, tools_1.summarizeArgs)(soleTerminalCall.name, terminalArgs), false);
|
|
1473
|
+
messages.push(msg);
|
|
1474
|
+
const failure = error instanceof Error ? `error: ${error.message}` : `error: ${String(error)}`;
|
|
1475
|
+
messages.push({ role: "tool", tool_call_id: soleTerminalCall.id, content: failure });
|
|
1476
|
+
providerRuntime.appendToolOutput(soleTerminalCall.id, failure);
|
|
1477
|
+
callbacks.onTextChunk(failure);
|
|
1478
|
+
completion = { answer: failure, intent: "blocked" };
|
|
1479
|
+
outcome = "blocked";
|
|
1480
|
+
done = true;
|
|
1481
|
+
continue;
|
|
1482
|
+
}
|
|
1483
|
+
callbacks.onToolEnd(soleTerminalCall.name, (0, tools_1.summarizeArgs)(soleTerminalCall.name, terminalArgs), true);
|
|
1484
|
+
messages.push(msg);
|
|
1485
|
+
messages.push({ role: "tool", tool_call_id: soleTerminalCall.id, content: terminalResult });
|
|
1486
|
+
providerRuntime.appendToolOutput(soleTerminalCall.id, terminalResult);
|
|
1487
|
+
callbacks.onTextChunk(terminalResult);
|
|
1488
|
+
completion = { answer: terminalResult, intent: "complete" };
|
|
1489
|
+
outcome = "settled";
|
|
1490
|
+
done = true;
|
|
1491
|
+
continue;
|
|
1492
|
+
}
|
|
1247
1493
|
// Check for settle sole call: intercept before tool execution
|
|
1248
1494
|
if (isSoleSettle) {
|
|
1249
1495
|
/* v8 ignore next -- defensive: JSON.parse catch for malformed settle args @preserve */
|
|
@@ -1255,8 +1501,9 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
1255
1501
|
} })();
|
|
1256
1502
|
callbacks.onToolStart("settle", settleArgs);
|
|
1257
1503
|
// Private-runtime attention queue gate: reject settle if items remain
|
|
1258
|
-
const attentionQueue =
|
|
1504
|
+
const attentionQueue = augmentedToolContext?.delegatedOrigins;
|
|
1259
1505
|
if (isPrivateRuntimeChannel && attentionQueue && attentionQueue.length > 0) {
|
|
1506
|
+
streamCallbackBuffer?.discard();
|
|
1260
1507
|
callbacks.onToolEnd("settle", (0, tools_1.summarizeArgs)("settle", settleArgs), false);
|
|
1261
1508
|
callbacks.onClearText?.();
|
|
1262
1509
|
messages.push(msg);
|
|
@@ -1270,6 +1517,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
1270
1517
|
const { answer, intent } = parseSettlePayload(result.toolCalls[0].arguments);
|
|
1271
1518
|
// Private-runtime settle: no CompletionMetadata, "(settled)" ack
|
|
1272
1519
|
if (isPrivateRuntimeChannel) {
|
|
1520
|
+
streamCallbackBuffer?.discard();
|
|
1273
1521
|
callbacks.onToolEnd("settle", (0, tools_1.summarizeArgs)("settle", settleArgs), true);
|
|
1274
1522
|
messages.push(msg);
|
|
1275
1523
|
const settled = "(settled)";
|
|
@@ -1279,6 +1527,8 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
1279
1527
|
done = true;
|
|
1280
1528
|
continue;
|
|
1281
1529
|
}
|
|
1530
|
+
// The provider finalizer has already established a top-level string
|
|
1531
|
+
// answer before ordinary settle handling reaches this point.
|
|
1282
1532
|
const deliveredAnswer = answer;
|
|
1283
1533
|
const retryError = privateReturnAckLeakError(deliveredAnswer, privateReturnHeldTokens)
|
|
1284
1534
|
?? privateReturnMissingPonderError({
|
|
@@ -1288,28 +1538,27 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
1288
1538
|
})
|
|
1289
1539
|
?? getSettleRetryError(mustResolveBeforeHandoffActive, intent, sawSteeringFollowUp, options?.delegationDecision, sawSendMessageSelf, sawPonder, sawQuerySession, options?.currentObligation ?? null, options?.activeWorkFrame?.inner?.job, sawExternalStateQuery);
|
|
1290
1540
|
const validDirectReply = mustResolveBeforeHandoffActive && intent === "direct_reply" && sawSteeringFollowUp;
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1541
|
+
if (retryError === null) {
|
|
1542
|
+
try {
|
|
1543
|
+
if (!result.settleStreamed) {
|
|
1544
|
+
const acceptedOutputCallbacks = streamCallbackBuffer?.callbacks ?? callbacks;
|
|
1545
|
+
acceptedOutputCallbacks.onTextChunk(deliveredAnswer);
|
|
1546
|
+
}
|
|
1547
|
+
await streamCallbackBuffer?.flush();
|
|
1548
|
+
}
|
|
1549
|
+
catch (error) {
|
|
1550
|
+
callbacks.onToolEnd("settle", (0, tools_1.summarizeArgs)("settle", settleArgs), false);
|
|
1551
|
+
streamCallbackBuffer?.discard();
|
|
1552
|
+
finishTerminalProviderError(new streaming_1.SettleFinalizationCallbackError(error), "unknown");
|
|
1553
|
+
continue;
|
|
1554
|
+
}
|
|
1296
1555
|
callbacks.onToolEnd("settle", (0, tools_1.summarizeArgs)("settle", settleArgs), true);
|
|
1297
1556
|
completion = {
|
|
1298
1557
|
answer: deliveredAnswer,
|
|
1299
1558
|
intent: validDirectReply ? "direct_reply" : intent === "blocked" ? "blocked" : "complete",
|
|
1300
1559
|
};
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
// progressively via SettleParser. Skip clearing and
|
|
1304
|
-
// re-emitting to avoid double-delivery.
|
|
1305
|
-
}
|
|
1306
|
-
else {
|
|
1307
|
-
// Clear any streamed noise (e.g. refusal text) before emitting.
|
|
1308
|
-
callbacks.onClearText?.();
|
|
1309
|
-
// Emit the answer through the callback pipeline so channels receive it.
|
|
1310
|
-
// Never truncate -- channel adapters handle splitting long messages.
|
|
1311
|
-
callbacks.onTextChunk(deliveredAnswer);
|
|
1312
|
-
}
|
|
1560
|
+
// Retractable owners already hold the validated answer. Final-only
|
|
1561
|
+
// owners receive it here, after every semantic continuation gate.
|
|
1313
1562
|
messages.push(msg);
|
|
1314
1563
|
if (validDirectReply) {
|
|
1315
1564
|
const resumeWork = "direct reply delivered. resume the unresolved obligation now and keep working until you can finish or clearly report that you are blocked.";
|
|
@@ -1325,14 +1574,13 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
1325
1574
|
}
|
|
1326
1575
|
}
|
|
1327
1576
|
else {
|
|
1328
|
-
//
|
|
1329
|
-
//
|
|
1330
|
-
|
|
1577
|
+
// The payload is structurally final, but a semantic continuation
|
|
1578
|
+
// gate rejected it. Return that exact gate reason to the model.
|
|
1579
|
+
streamCallbackBuffer?.discard();
|
|
1331
1580
|
callbacks.onToolEnd("settle", (0, tools_1.summarizeArgs)("settle", settleArgs), false);
|
|
1332
1581
|
callbacks.onClearText?.();
|
|
1333
1582
|
messages.push(msg);
|
|
1334
|
-
const toolRetryMessage = retryError
|
|
1335
|
-
?? "your settle was incomplete or malformed. call settle again with your complete response.";
|
|
1583
|
+
const toolRetryMessage = retryError;
|
|
1336
1584
|
messages.push({ role: "tool", tool_call_id: result.toolCalls[0].id, content: toolRetryMessage });
|
|
1337
1585
|
providerRuntime.appendToolOutput(result.toolCalls[0].id, toolRetryMessage);
|
|
1338
1586
|
}
|
|
@@ -1341,6 +1589,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
1341
1589
|
// Check for observe sole call: intercept before tool execution
|
|
1342
1590
|
const isSoleObserve = result.toolCalls.length === 1 && result.toolCalls[0].name === "observe";
|
|
1343
1591
|
if (isSoleObserve) {
|
|
1592
|
+
streamCallbackBuffer?.discard();
|
|
1344
1593
|
/* v8 ignore next -- defensive: JSON.parse catch for malformed observe args @preserve */
|
|
1345
1594
|
const observeArgs = (() => { try {
|
|
1346
1595
|
return JSON.parse(result.toolCalls[0].arguments);
|
|
@@ -1370,6 +1619,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
1370
1619
|
// Check for rest sole call: intercept before tool execution
|
|
1371
1620
|
const isSoleRest = result.toolCalls.length === 1 && result.toolCalls[0].name === "rest";
|
|
1372
1621
|
if (isSoleRest) {
|
|
1622
|
+
streamCallbackBuffer?.discard();
|
|
1373
1623
|
const restArgs = (() => { try {
|
|
1374
1624
|
return JSON.parse(result.toolCalls[0].arguments);
|
|
1375
1625
|
}
|
|
@@ -1378,7 +1628,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
1378
1628
|
} })();
|
|
1379
1629
|
callbacks.onToolStart("rest", restArgs);
|
|
1380
1630
|
// Attention queue gate: reject rest if items remain
|
|
1381
|
-
const attentionQueue =
|
|
1631
|
+
const attentionQueue = augmentedToolContext?.delegatedOrigins;
|
|
1382
1632
|
if (attentionQueue && attentionQueue.length > 0) {
|
|
1383
1633
|
callbacks.onToolEnd("rest", (0, tools_1.summarizeArgs)("rest", restArgs), false);
|
|
1384
1634
|
messages.push(msg);
|
|
@@ -1418,13 +1668,29 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
1418
1668
|
done = true;
|
|
1419
1669
|
continue;
|
|
1420
1670
|
}
|
|
1671
|
+
const containsSoleCallOnlyViolation = result.toolCalls.length > 1
|
|
1672
|
+
&& result.toolCalls.some((call) => {
|
|
1673
|
+
const terminalProjection = (0, tools_1.resolveToolDefinition)(call.name)?.terminalProjection;
|
|
1674
|
+
return SOLE_CALL_REJECTION[call.name] !== undefined
|
|
1675
|
+
|| terminalProjection?.requiresSoleCall === true;
|
|
1676
|
+
});
|
|
1677
|
+
if (callbacks.settleOutputMode === "final_only" && containsSoleCallOnlyViolation) {
|
|
1678
|
+
streamCallbackBuffer?.discard();
|
|
1679
|
+
}
|
|
1680
|
+
else {
|
|
1681
|
+
await streamCallbackBuffer?.flush();
|
|
1682
|
+
}
|
|
1421
1683
|
messages.push(msg);
|
|
1422
1684
|
// Execute tools (sole-call tools in mixed calls are rejected inline)
|
|
1423
1685
|
for (const tc of result.toolCalls) {
|
|
1424
1686
|
if (signal?.aborted)
|
|
1425
1687
|
break;
|
|
1426
1688
|
// Reject sole-call tools when mixed with other tool calls
|
|
1427
|
-
const
|
|
1689
|
+
const terminalProjection = (0, tools_1.resolveToolDefinition)(tc.name)?.terminalProjection;
|
|
1690
|
+
const soleCallRejection = SOLE_CALL_REJECTION[tc.name]
|
|
1691
|
+
?? (terminalProjection?.requiresSoleCall
|
|
1692
|
+
? `rejected: ${tc.name} must be the only tool call.`
|
|
1693
|
+
: undefined);
|
|
1428
1694
|
if (soleCallRejection) {
|
|
1429
1695
|
messages.push({ role: "tool", tool_call_id: tc.id, content: soleCallRejection });
|
|
1430
1696
|
providerRuntime.appendToolOutput(tc.id, soleCallRejection);
|
|
@@ -1515,13 +1781,13 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
1515
1781
|
let success = false;
|
|
1516
1782
|
try {
|
|
1517
1783
|
const action = parsedArgs.action ?? "create";
|
|
1518
|
-
const currentSession =
|
|
1784
|
+
const currentSession = augmentedToolContext?.currentSession;
|
|
1519
1785
|
const currentOrigin = currentSession
|
|
1520
1786
|
? { friendId: currentSession.friendId, channel: currentSession.channel, key: currentSession.key }
|
|
1521
1787
|
: undefined;
|
|
1522
1788
|
const isInnerChannel = currentOrigin?.friendId === "self" && currentOrigin?.channel === "inner";
|
|
1523
1789
|
const shouldCreateReturnObligation = !!currentOrigin && !isInnerChannel;
|
|
1524
|
-
const attentionQueue =
|
|
1790
|
+
const attentionQueue = augmentedToolContext?.delegatedOrigins ?? [];
|
|
1525
1791
|
const successCriteria = parseSuccessCriteria(parsedArgs.success_criteria);
|
|
1526
1792
|
const payload = parsePacketPayload(parsedArgs.payload_json);
|
|
1527
1793
|
let packet;
|
|
@@ -1690,7 +1956,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
1690
1956
|
let success;
|
|
1691
1957
|
try {
|
|
1692
1958
|
const execToolFn = options?.execTool ?? tools_1.execTool;
|
|
1693
|
-
toolResult = await execToolFn(tc.name, args, augmentedToolContext
|
|
1959
|
+
toolResult = await execToolFn(tc.name, args, augmentedToolContext);
|
|
1694
1960
|
success = true;
|
|
1695
1961
|
}
|
|
1696
1962
|
catch (e) {
|