@ouro.bot/cli 0.1.0-alpha.6 → 0.1.0-alpha.60
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/AdoptionSpecialist.ouro/agent.json +70 -9
- package/AdoptionSpecialist.ouro/psyche/SOUL.md +5 -2
- package/AdoptionSpecialist.ouro/psyche/identities/monty.md +2 -2
- package/README.md +147 -205
- package/assets/ouroboros.png +0 -0
- package/changelog.json +325 -0
- package/dist/heart/active-work.js +178 -0
- package/dist/heart/bridges/manager.js +358 -0
- package/dist/heart/bridges/state-machine.js +135 -0
- package/dist/heart/bridges/store.js +123 -0
- package/dist/heart/config.js +57 -23
- package/dist/heart/core.js +236 -90
- package/dist/heart/cross-chat-delivery.js +146 -0
- package/dist/heart/daemon/agent-discovery.js +81 -0
- package/dist/heart/daemon/auth-flow.js +351 -0
- package/dist/heart/daemon/daemon-cli.js +1173 -227
- package/dist/heart/daemon/daemon-entry.js +55 -6
- package/dist/heart/daemon/daemon-runtime-sync.js +212 -0
- package/dist/heart/daemon/daemon.js +189 -10
- package/dist/heart/daemon/hatch-animation.js +10 -3
- package/dist/heart/daemon/hatch-flow.js +4 -82
- package/dist/heart/daemon/hooks/bundle-meta.js +92 -0
- package/dist/heart/daemon/launchd.js +159 -0
- package/dist/heart/daemon/log-tailer.js +4 -3
- package/dist/heart/daemon/message-router.js +17 -8
- package/dist/heart/daemon/ouro-bot-entry.js +0 -0
- package/dist/heart/daemon/ouro-bot-global-installer.js +128 -0
- package/dist/heart/daemon/ouro-entry.js +0 -0
- package/dist/heart/daemon/ouro-path-installer.js +178 -0
- package/dist/heart/daemon/ouro-uti.js +11 -2
- package/dist/heart/daemon/process-manager.js +14 -1
- package/dist/heart/daemon/run-hooks.js +37 -0
- package/dist/heart/daemon/runtime-logging.js +58 -15
- package/dist/heart/daemon/runtime-metadata.js +219 -0
- package/dist/heart/daemon/runtime-mode.js +67 -0
- package/dist/heart/daemon/sense-manager.js +307 -0
- package/dist/heart/daemon/socket-client.js +202 -0
- package/dist/heart/daemon/specialist-orchestrator.js +53 -84
- package/dist/heart/daemon/specialist-prompt.js +64 -5
- package/dist/heart/daemon/specialist-tools.js +213 -58
- package/dist/heart/daemon/staged-restart.js +114 -0
- package/dist/heart/daemon/subagent-installer.js +48 -7
- package/dist/heart/daemon/thoughts.js +379 -0
- package/dist/heart/daemon/update-checker.js +111 -0
- package/dist/heart/daemon/update-hooks.js +138 -0
- package/dist/heart/daemon/wrapper-publish-guard.js +86 -0
- package/dist/heart/delegation.js +62 -0
- package/dist/heart/identity.js +122 -19
- package/dist/heart/kicks.js +1 -19
- package/dist/heart/model-capabilities.js +40 -0
- package/dist/heart/progress-story.js +42 -0
- package/dist/heart/providers/anthropic.js +74 -9
- package/dist/heart/providers/azure.js +86 -7
- package/dist/heart/providers/minimax.js +4 -0
- package/dist/heart/providers/openai-codex.js +12 -3
- package/dist/heart/safe-workspace.js +228 -0
- package/dist/heart/sense-truth.js +61 -0
- package/dist/heart/session-activity.js +169 -0
- package/dist/heart/session-recall.js +116 -0
- package/dist/heart/streaming.js +100 -22
- package/dist/heart/target-resolution.js +123 -0
- package/dist/heart/turn-coordinator.js +28 -0
- package/dist/mind/associative-recall.js +14 -2
- package/dist/mind/bundle-manifest.js +70 -0
- package/dist/mind/context.js +27 -11
- package/dist/mind/first-impressions.js +16 -2
- package/dist/mind/friends/channel.js +35 -0
- package/dist/mind/friends/group-context.js +144 -0
- package/dist/mind/friends/store-file.js +19 -0
- package/dist/mind/friends/trust-explanation.js +74 -0
- package/dist/mind/friends/types.js +8 -0
- package/dist/mind/memory.js +27 -26
- package/dist/mind/pending.js +72 -9
- package/dist/mind/phrases.js +1 -0
- package/dist/mind/prompt.js +299 -77
- package/dist/mind/token-estimate.js +8 -12
- package/dist/nerves/cli-logging.js +15 -2
- package/dist/nerves/coverage/run-artifacts.js +1 -1
- package/dist/repertoire/ado-client.js +4 -2
- package/dist/repertoire/coding/feedback.js +134 -0
- package/dist/repertoire/coding/index.js +4 -1
- package/dist/repertoire/coding/manager.js +62 -4
- package/dist/repertoire/coding/spawner.js +3 -3
- package/dist/repertoire/coding/tools.js +41 -2
- package/dist/repertoire/data/ado-endpoints.json +188 -0
- package/dist/repertoire/tasks/board.js +12 -0
- package/dist/repertoire/tasks/index.js +23 -9
- package/dist/repertoire/tasks/transitions.js +1 -2
- package/dist/repertoire/tools-base.js +629 -251
- package/dist/repertoire/tools-bluebubbles.js +93 -0
- package/dist/repertoire/tools-teams.js +58 -25
- package/dist/repertoire/tools.js +92 -48
- package/dist/senses/bluebubbles-client.js +210 -5
- package/dist/senses/bluebubbles-entry.js +2 -0
- package/dist/senses/bluebubbles-inbound-log.js +109 -0
- package/dist/senses/bluebubbles-media.js +339 -0
- package/dist/senses/bluebubbles-model.js +12 -4
- package/dist/senses/bluebubbles-mutation-log.js +45 -5
- package/dist/senses/bluebubbles-runtime-state.js +109 -0
- package/dist/senses/bluebubbles-session-cleanup.js +72 -0
- package/dist/senses/bluebubbles.js +890 -45
- package/dist/senses/cli-layout.js +87 -0
- package/dist/senses/cli.js +345 -144
- package/dist/senses/continuity.js +94 -0
- package/dist/senses/debug-activity.js +148 -0
- package/dist/senses/inner-dialog-worker.js +47 -18
- package/dist/senses/inner-dialog.js +330 -84
- package/dist/senses/pipeline.js +278 -0
- package/dist/senses/teams.js +570 -129
- package/dist/senses/trust-gate.js +112 -2
- package/package.json +14 -3
- package/subagents/README.md +46 -33
- package/subagents/work-doer.md +28 -24
- package/subagents/work-merger.md +24 -30
- package/subagents/work-planner.md +44 -27
- package/dist/heart/daemon/specialist-session.js +0 -142
- package/dist/inner-worker-entry.js +0 -4
package/dist/heart/core.js
CHANGED
|
@@ -8,6 +8,7 @@ exports.getProvider = getProvider;
|
|
|
8
8
|
exports.createSummarize = createSummarize;
|
|
9
9
|
exports.getProviderDisplayLabel = getProviderDisplayLabel;
|
|
10
10
|
exports.stripLastToolCalls = stripLastToolCalls;
|
|
11
|
+
exports.repairOrphanedToolCalls = repairOrphanedToolCalls;
|
|
11
12
|
exports.isTransientError = isTransientError;
|
|
12
13
|
exports.classifyTransientError = classifyTransientError;
|
|
13
14
|
exports.runAgent = runAgent;
|
|
@@ -15,9 +16,6 @@ const config_1 = require("./config");
|
|
|
15
16
|
const identity_1 = require("./identity");
|
|
16
17
|
const tools_1 = require("../repertoire/tools");
|
|
17
18
|
const channel_1 = require("../mind/friends/channel");
|
|
18
|
-
// Kick detection preserved but disabled — see comment in agent loop below.
|
|
19
|
-
// import { detectKick } from "./kicks";
|
|
20
|
-
// import type { KickReason } from "./kicks";
|
|
21
19
|
const runtime_1 = require("../nerves/runtime");
|
|
22
20
|
const context_1 = require("../mind/context");
|
|
23
21
|
const prompt_1 = require("../mind/prompt");
|
|
@@ -27,6 +25,27 @@ const azure_1 = require("./providers/azure");
|
|
|
27
25
|
const minimax_1 = require("./providers/minimax");
|
|
28
26
|
const openai_codex_1 = require("./providers/openai-codex");
|
|
29
27
|
let _providerRuntime = null;
|
|
28
|
+
function getProviderRuntimeFingerprint() {
|
|
29
|
+
const provider = (0, identity_1.loadAgentConfig)().provider;
|
|
30
|
+
switch (provider) {
|
|
31
|
+
case "azure": {
|
|
32
|
+
const { apiKey, endpoint, deployment, modelName, apiVersion, managedIdentityClientId } = (0, config_1.getAzureConfig)();
|
|
33
|
+
return JSON.stringify({ provider, apiKey, endpoint, deployment, modelName, apiVersion, managedIdentityClientId });
|
|
34
|
+
}
|
|
35
|
+
case "anthropic": {
|
|
36
|
+
const { model, setupToken } = (0, config_1.getAnthropicConfig)();
|
|
37
|
+
return JSON.stringify({ provider, model, setupToken });
|
|
38
|
+
}
|
|
39
|
+
case "minimax": {
|
|
40
|
+
const { apiKey, model } = (0, config_1.getMinimaxConfig)();
|
|
41
|
+
return JSON.stringify({ provider, apiKey, model });
|
|
42
|
+
}
|
|
43
|
+
case "openai-codex": {
|
|
44
|
+
const { model, oauthAccessToken } = (0, config_1.getOpenAICodexConfig)();
|
|
45
|
+
return JSON.stringify({ provider, model, oauthAccessToken });
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
30
49
|
function createProviderRegistry() {
|
|
31
50
|
const factories = {
|
|
32
51
|
azure: azure_1.createAzureProviderRuntime,
|
|
@@ -42,42 +61,44 @@ function createProviderRegistry() {
|
|
|
42
61
|
};
|
|
43
62
|
}
|
|
44
63
|
function getProviderRuntime() {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
const msg = error instanceof Error ? error.message : String(error);
|
|
51
|
-
(0, runtime_1.emitNervesEvent)({
|
|
52
|
-
level: "error",
|
|
53
|
-
event: "engine.provider_init_error",
|
|
54
|
-
component: "engine",
|
|
55
|
-
message: msg,
|
|
56
|
-
meta: {},
|
|
57
|
-
});
|
|
58
|
-
// eslint-disable-next-line no-console -- pre-boot guard: provider init failure
|
|
59
|
-
console.error(`\n[fatal] ${msg}\n`);
|
|
60
|
-
process.exit(1);
|
|
61
|
-
throw new Error("unreachable");
|
|
62
|
-
}
|
|
63
|
-
if (!_providerRuntime) {
|
|
64
|
-
(0, runtime_1.emitNervesEvent)({
|
|
65
|
-
level: "error",
|
|
66
|
-
event: "engine.provider_init_error",
|
|
67
|
-
component: "engine",
|
|
68
|
-
message: "provider runtime could not be initialized.",
|
|
69
|
-
meta: {},
|
|
70
|
-
});
|
|
71
|
-
process.exit(1);
|
|
72
|
-
throw new Error("unreachable");
|
|
64
|
+
try {
|
|
65
|
+
const fingerprint = getProviderRuntimeFingerprint();
|
|
66
|
+
if (!_providerRuntime || _providerRuntime.fingerprint !== fingerprint) {
|
|
67
|
+
const runtime = createProviderRegistry().resolve();
|
|
68
|
+
_providerRuntime = runtime ? { fingerprint, runtime } : null;
|
|
73
69
|
}
|
|
74
70
|
}
|
|
75
|
-
|
|
71
|
+
catch (error) {
|
|
72
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
73
|
+
(0, runtime_1.emitNervesEvent)({
|
|
74
|
+
level: "error",
|
|
75
|
+
event: "engine.provider_init_error",
|
|
76
|
+
component: "engine",
|
|
77
|
+
message: msg,
|
|
78
|
+
meta: {},
|
|
79
|
+
});
|
|
80
|
+
// eslint-disable-next-line no-console -- pre-boot guard: provider init failure
|
|
81
|
+
console.error(`\n[fatal] ${msg}\n`);
|
|
82
|
+
process.exit(1);
|
|
83
|
+
throw new Error("unreachable");
|
|
84
|
+
}
|
|
85
|
+
if (!_providerRuntime) {
|
|
86
|
+
(0, runtime_1.emitNervesEvent)({
|
|
87
|
+
level: "error",
|
|
88
|
+
event: "engine.provider_init_error",
|
|
89
|
+
component: "engine",
|
|
90
|
+
message: "provider runtime could not be initialized.",
|
|
91
|
+
meta: {},
|
|
92
|
+
});
|
|
93
|
+
process.exit(1);
|
|
94
|
+
throw new Error("unreachable");
|
|
95
|
+
}
|
|
96
|
+
return _providerRuntime.runtime;
|
|
76
97
|
}
|
|
77
98
|
/**
|
|
78
|
-
* Clear the cached provider runtime so the next
|
|
79
|
-
*
|
|
80
|
-
*
|
|
99
|
+
* Clear the cached provider runtime so the next access re-creates it from
|
|
100
|
+
* current config. Runtime access also auto-refreshes when the selected
|
|
101
|
+
* provider fingerprint changes on disk.
|
|
81
102
|
*/
|
|
82
103
|
function resetProviderRuntime() {
|
|
83
104
|
_providerRuntime = null;
|
|
@@ -104,14 +125,17 @@ function createSummarize() {
|
|
|
104
125
|
};
|
|
105
126
|
}
|
|
106
127
|
function getProviderDisplayLabel() {
|
|
107
|
-
const
|
|
128
|
+
const provider = (0, identity_1.loadAgentConfig)().provider;
|
|
108
129
|
const providerLabelBuilders = {
|
|
109
|
-
azure: () =>
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
130
|
+
azure: () => {
|
|
131
|
+
const config = (0, config_1.getAzureConfig)();
|
|
132
|
+
return `azure openai (${config.deployment || "default"}, model: ${config.modelName || "unknown"})`;
|
|
133
|
+
},
|
|
134
|
+
anthropic: () => `anthropic (${(0, config_1.getAnthropicConfig)().model || "unknown"})`,
|
|
135
|
+
minimax: () => `minimax (${(0, config_1.getMinimaxConfig)().model || "unknown"})`,
|
|
136
|
+
"openai-codex": () => `openai codex (${(0, config_1.getOpenAICodexConfig)().model || "unknown"})`,
|
|
113
137
|
};
|
|
114
|
-
return providerLabelBuilders[
|
|
138
|
+
return providerLabelBuilders[provider]();
|
|
115
139
|
}
|
|
116
140
|
// Re-export tools, execTool, summarizeArgs from ./tools for backward compat
|
|
117
141
|
var tools_2 = require("../repertoire/tools");
|
|
@@ -128,6 +152,35 @@ Object.defineProperty(exports, "toResponsesTools", { enumerable: true, get: func
|
|
|
128
152
|
// Re-export prompt functions for backward compat
|
|
129
153
|
var prompt_2 = require("../mind/prompt");
|
|
130
154
|
Object.defineProperty(exports, "buildSystem", { enumerable: true, get: function () { return prompt_2.buildSystem; } });
|
|
155
|
+
function parseFinalAnswerPayload(argumentsText) {
|
|
156
|
+
try {
|
|
157
|
+
const parsed = JSON.parse(argumentsText);
|
|
158
|
+
if (typeof parsed === "string") {
|
|
159
|
+
return { answer: parsed };
|
|
160
|
+
}
|
|
161
|
+
if (!parsed || typeof parsed !== "object") {
|
|
162
|
+
return {};
|
|
163
|
+
}
|
|
164
|
+
const answer = typeof parsed.answer === "string" ? parsed.answer : undefined;
|
|
165
|
+
const rawIntent = parsed.intent;
|
|
166
|
+
const intent = rawIntent === "complete" || rawIntent === "blocked" || rawIntent === "direct_reply"
|
|
167
|
+
? rawIntent
|
|
168
|
+
: undefined;
|
|
169
|
+
return { answer, intent };
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
return {};
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
function getFinalAnswerRetryError(mustResolveBeforeHandoff, intent, sawSteeringFollowUp) {
|
|
176
|
+
if (mustResolveBeforeHandoff && !intent) {
|
|
177
|
+
return "your final_answer is missing required intent. when you must keep going until done or blocked, call final_answer again with answer plus intent=complete, blocked, or direct_reply.";
|
|
178
|
+
}
|
|
179
|
+
if (mustResolveBeforeHandoff && intent === "direct_reply" && !sawSteeringFollowUp) {
|
|
180
|
+
return "your final_answer used intent=direct_reply without a newer steering follow-up. continue the unresolved work, or call final_answer again with intent=complete or blocked when appropriate.";
|
|
181
|
+
}
|
|
182
|
+
return "your final_answer was incomplete or malformed. call final_answer again with your complete response.";
|
|
183
|
+
}
|
|
131
184
|
// Re-export kick utilities for backward compat
|
|
132
185
|
var kicks_1 = require("./kicks");
|
|
133
186
|
Object.defineProperty(exports, "hasToolIntent", { enumerable: true, get: function () { return kicks_1.hasToolIntent; } });
|
|
@@ -160,6 +213,68 @@ function stripLastToolCalls(messages) {
|
|
|
160
213
|
}
|
|
161
214
|
}
|
|
162
215
|
}
|
|
216
|
+
// Roles that end a tool-result scan. When scanning forward from an assistant
|
|
217
|
+
// message, stop at the next assistant or user message (tool results must be
|
|
218
|
+
// adjacent to their originating assistant message).
|
|
219
|
+
const TOOL_SCAN_BOUNDARY_ROLES = new Set(["assistant", "user"]);
|
|
220
|
+
// Repair orphaned tool_calls and tool results anywhere in the message history.
|
|
221
|
+
// 1. If an assistant message has tool_calls but missing tool results, inject synthetic error results.
|
|
222
|
+
// 2. If a tool result's tool_call_id doesn't match any tool_calls in a preceding assistant message, remove it.
|
|
223
|
+
// This prevents 400 errors from the API after an aborted turn.
|
|
224
|
+
function repairOrphanedToolCalls(messages) {
|
|
225
|
+
// Pass 1: collect all valid tool_call IDs from assistant messages
|
|
226
|
+
const validCallIds = new Set();
|
|
227
|
+
for (const msg of messages) {
|
|
228
|
+
if (msg.role === "assistant") {
|
|
229
|
+
const asst = msg;
|
|
230
|
+
if (asst.tool_calls) {
|
|
231
|
+
for (const tc of asst.tool_calls)
|
|
232
|
+
validCallIds.add(tc.id);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
// Pass 2: remove orphaned tool results (tool_call_id not in any assistant's tool_calls)
|
|
237
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
238
|
+
if (messages[i].role === "tool") {
|
|
239
|
+
const toolMsg = messages[i];
|
|
240
|
+
if (!validCallIds.has(toolMsg.tool_call_id)) {
|
|
241
|
+
messages.splice(i, 1);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
// Pass 3: inject synthetic results for tool_calls missing their tool results
|
|
246
|
+
for (let i = 0; i < messages.length; i++) {
|
|
247
|
+
const msg = messages[i];
|
|
248
|
+
if (msg.role !== "assistant")
|
|
249
|
+
continue;
|
|
250
|
+
const asst = msg;
|
|
251
|
+
if (!asst.tool_calls || asst.tool_calls.length === 0)
|
|
252
|
+
continue;
|
|
253
|
+
// Collect tool result IDs that follow this assistant message
|
|
254
|
+
const resultIds = new Set();
|
|
255
|
+
for (let j = i + 1; j < messages.length; j++) {
|
|
256
|
+
const following = messages[j];
|
|
257
|
+
if (following.role === "tool") {
|
|
258
|
+
resultIds.add(following.tool_call_id);
|
|
259
|
+
}
|
|
260
|
+
else if (TOOL_SCAN_BOUNDARY_ROLES.has(following.role)) {
|
|
261
|
+
break;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
const missing = asst.tool_calls.filter((tc) => !resultIds.has(tc.id));
|
|
265
|
+
if (missing.length > 0) {
|
|
266
|
+
const syntheticResults = missing.map((tc) => ({
|
|
267
|
+
role: "tool",
|
|
268
|
+
tool_call_id: tc.id,
|
|
269
|
+
content: "error: tool call was interrupted (previous turn timed out or was aborted)",
|
|
270
|
+
}));
|
|
271
|
+
let insertAt = i + 1;
|
|
272
|
+
while (insertAt < messages.length && messages[insertAt].role === "tool")
|
|
273
|
+
insertAt++;
|
|
274
|
+
messages.splice(insertAt, 0, ...syntheticResults);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
163
278
|
// Detect context overflow errors from Azure or MiniMax
|
|
164
279
|
function isContextOverflow(err) {
|
|
165
280
|
if (!(err instanceof Error))
|
|
@@ -242,7 +357,12 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
242
357
|
// so turn execution remains consistent and non-fatal.
|
|
243
358
|
if (channel) {
|
|
244
359
|
try {
|
|
245
|
-
const
|
|
360
|
+
const buildSystemOptions = {
|
|
361
|
+
...options,
|
|
362
|
+
providerCapabilities: providerRuntime.capabilities,
|
|
363
|
+
supportedReasoningEfforts: providerRuntime.supportedReasoningEfforts,
|
|
364
|
+
};
|
|
365
|
+
const refreshed = await (0, prompt_1.buildSystem)(channel, buildSystemOptions, currentContext);
|
|
246
366
|
upsertSystemPrompt(messages, refreshed);
|
|
247
367
|
}
|
|
248
368
|
catch (error) {
|
|
@@ -265,20 +385,30 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
265
385
|
}
|
|
266
386
|
}
|
|
267
387
|
await (0, associative_recall_1.injectAssociativeRecall)(messages);
|
|
268
|
-
// kickCount and lastKickReason preserved but unused while kick detection is disabled.
|
|
269
|
-
// let kickCount = 0;
|
|
270
|
-
// let lastKickReason: KickReason | null = null;
|
|
271
388
|
let done = false;
|
|
272
389
|
let lastUsage;
|
|
273
390
|
let overflowRetried = false;
|
|
274
391
|
let retryCount = 0;
|
|
392
|
+
let outcome = "complete";
|
|
393
|
+
let completion;
|
|
394
|
+
let sawSteeringFollowUp = false;
|
|
395
|
+
let mustResolveBeforeHandoffActive = options?.mustResolveBeforeHandoff === true;
|
|
396
|
+
let currentReasoningEffort = "medium";
|
|
275
397
|
// Prevent MaxListenersExceeded warning — each iteration adds a listener
|
|
276
398
|
try {
|
|
277
399
|
require("events").setMaxListeners(50, signal);
|
|
278
400
|
}
|
|
279
401
|
catch { /* unsupported */ }
|
|
280
402
|
const toolPreferences = currentContext?.friend?.toolPreferences;
|
|
281
|
-
const baseTools = (0, tools_1.getToolsForChannel)(channel ? (0, channel_1.getChannelCapabilities)(channel) : undefined, toolPreferences && Object.keys(toolPreferences).length > 0 ? toolPreferences : undefined);
|
|
403
|
+
const baseTools = options?.tools ?? (0, tools_1.getToolsForChannel)(channel ? (0, channel_1.getChannelCapabilities)(channel) : undefined, toolPreferences && Object.keys(toolPreferences).length > 0 ? toolPreferences : undefined, currentContext, providerRuntime.capabilities);
|
|
404
|
+
// Augment tool context with reasoning effort controls from provider
|
|
405
|
+
const augmentedToolContext = options?.toolContext
|
|
406
|
+
? {
|
|
407
|
+
...options.toolContext,
|
|
408
|
+
supportedReasoningEfforts: providerRuntime.supportedReasoningEfforts,
|
|
409
|
+
setReasoningEffort: (level) => { currentReasoningEffort = level; },
|
|
410
|
+
}
|
|
411
|
+
: undefined;
|
|
282
412
|
// Rebase provider-owned turn state from canonical messages at user-turn start.
|
|
283
413
|
// This prevents stale provider caches from replaying prior-turn context.
|
|
284
414
|
providerRuntime.resetTurnState(messages);
|
|
@@ -290,6 +420,18 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
290
420
|
const activeTools = toolChoiceRequired ? [...baseTools, tools_1.finalAnswerTool] : baseTools;
|
|
291
421
|
const steeringFollowUps = options?.drainSteeringFollowUps?.() ?? [];
|
|
292
422
|
if (steeringFollowUps.length > 0) {
|
|
423
|
+
const hasSupersedingFollowUp = steeringFollowUps.some((followUp) => followUp.effect === "clear_and_supersede");
|
|
424
|
+
if (hasSupersedingFollowUp) {
|
|
425
|
+
mustResolveBeforeHandoffActive = false;
|
|
426
|
+
options?.setMustResolveBeforeHandoff?.(false);
|
|
427
|
+
outcome = "superseded";
|
|
428
|
+
break;
|
|
429
|
+
}
|
|
430
|
+
if (steeringFollowUps.some((followUp) => followUp.effect === "set_no_handoff")) {
|
|
431
|
+
mustResolveBeforeHandoffActive = true;
|
|
432
|
+
options?.setMustResolveBeforeHandoff?.(true);
|
|
433
|
+
}
|
|
434
|
+
sawSteeringFollowUp = true;
|
|
293
435
|
for (const followUp of steeringFollowUps) {
|
|
294
436
|
messages.push({ role: "user", content: followUp.text });
|
|
295
437
|
}
|
|
@@ -297,8 +439,10 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
297
439
|
}
|
|
298
440
|
// Yield so pending I/O (stdin Ctrl-C) can be processed between iterations
|
|
299
441
|
await new Promise((r) => setImmediate(r));
|
|
300
|
-
if (signal?.aborted)
|
|
442
|
+
if (signal?.aborted) {
|
|
443
|
+
outcome = "aborted";
|
|
301
444
|
break;
|
|
445
|
+
}
|
|
302
446
|
try {
|
|
303
447
|
callbacks.onModelStart();
|
|
304
448
|
const result = await providerRuntime.streamTurn({
|
|
@@ -308,6 +452,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
308
452
|
signal,
|
|
309
453
|
traceId,
|
|
310
454
|
toolChoiceRequired,
|
|
455
|
+
reasoningEffort: currentReasoningEffort,
|
|
311
456
|
});
|
|
312
457
|
// Track usage from the latest API call
|
|
313
458
|
if (result.usage)
|
|
@@ -331,49 +476,39 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
331
476
|
if (reasoningItems.length > 0) {
|
|
332
477
|
msg._reasoning_items = reasoningItems;
|
|
333
478
|
}
|
|
479
|
+
// Store thinking blocks (Anthropic) on the assistant message for round-tripping
|
|
480
|
+
const thinkingItems = result.outputItems.filter((item) => "type" in item && (item.type === "thinking" || item.type === "redacted_thinking"));
|
|
481
|
+
if (thinkingItems.length > 0) {
|
|
482
|
+
msg._thinking_blocks = thinkingItems;
|
|
483
|
+
}
|
|
484
|
+
// Phase annotation for Codex provider
|
|
485
|
+
const hasPhaseAnnotation = providerRuntime.capabilities.has("phase-annotation");
|
|
486
|
+
const isSoleFinalAnswer = result.toolCalls.length === 1 && result.toolCalls[0].name === "final_answer";
|
|
487
|
+
if (hasPhaseAnnotation) {
|
|
488
|
+
msg.phase = isSoleFinalAnswer ? "final_answer" : "commentary";
|
|
489
|
+
}
|
|
334
490
|
if (!result.toolCalls.length) {
|
|
335
|
-
//
|
|
336
|
-
//
|
|
337
|
-
//
|
|
338
|
-
// accept the response as-is rather than risk false-positive kicks.
|
|
339
|
-
//
|
|
340
|
-
// Preserved for future use — re-enable by uncommenting:
|
|
341
|
-
// const kick = detectKick(result.content, options);
|
|
342
|
-
// if (kick) {
|
|
343
|
-
// kickCount++;
|
|
344
|
-
// lastKickReason = kick.reason;
|
|
345
|
-
// callbacks.onKick?.();
|
|
346
|
-
// const kickContent = result.content
|
|
347
|
-
// ? result.content + "\n\n" + kick.message
|
|
348
|
-
// : kick.message;
|
|
349
|
-
// messages.push({ role: "assistant", content: kickContent });
|
|
350
|
-
// providerRuntime.resetTurnState(messages);
|
|
351
|
-
// continue;
|
|
352
|
-
// }
|
|
491
|
+
// No tool calls — accept response as-is.
|
|
492
|
+
// (Kick detection disabled; tool_choice: required + final_answer
|
|
493
|
+
// is the primary loop control. See src/heart/kicks.ts to re-enable.)
|
|
353
494
|
messages.push(msg);
|
|
354
495
|
done = true;
|
|
355
496
|
}
|
|
356
497
|
else {
|
|
357
498
|
// Check for final_answer sole call: intercept before tool execution
|
|
358
|
-
const isSoleFinalAnswer = result.toolCalls.length === 1 && result.toolCalls[0].name === "final_answer";
|
|
359
499
|
if (isSoleFinalAnswer) {
|
|
360
500
|
// Extract answer from the tool call arguments.
|
|
361
|
-
// Supports: {"answer":"text"}
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
answer
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
}
|
|
373
|
-
catch {
|
|
374
|
-
// JSON parsing failed (e.g. truncated output) — answer stays undefined (retry)
|
|
375
|
-
}
|
|
376
|
-
if (answer != null) {
|
|
501
|
+
// Supports: {"answer":"text","intent":"..."} or "text" (JSON string).
|
|
502
|
+
const { answer, intent } = parseFinalAnswerPayload(result.toolCalls[0].arguments);
|
|
503
|
+
const validDirectReply = mustResolveBeforeHandoffActive && intent === "direct_reply" && sawSteeringFollowUp;
|
|
504
|
+
const validTerminalIntent = intent === "complete" || intent === "blocked";
|
|
505
|
+
const validClosure = answer != null
|
|
506
|
+
&& (!mustResolveBeforeHandoffActive || validDirectReply || validTerminalIntent);
|
|
507
|
+
if (validClosure) {
|
|
508
|
+
completion = {
|
|
509
|
+
answer,
|
|
510
|
+
intent: validDirectReply ? "direct_reply" : intent === "blocked" ? "blocked" : "complete",
|
|
511
|
+
};
|
|
377
512
|
if (result.finalAnswerStreamed) {
|
|
378
513
|
// The streaming layer already parsed and emitted the answer
|
|
379
514
|
// progressively via FinalAnswerParser. Skip clearing and
|
|
@@ -386,19 +521,26 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
386
521
|
// Never truncate -- channel adapters handle splitting long messages.
|
|
387
522
|
callbacks.onTextChunk(answer);
|
|
388
523
|
}
|
|
389
|
-
// Keep the full assistant message (with tool_calls) for debuggability,
|
|
390
|
-
// plus a synthetic tool response so the conversation stays valid on resume.
|
|
391
524
|
messages.push(msg);
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
525
|
+
if (validDirectReply) {
|
|
526
|
+
const resumeWork = "direct reply delivered. resume the unresolved obligation now and keep working until you can finish or clearly report that you are blocked.";
|
|
527
|
+
messages.push({ role: "tool", tool_call_id: result.toolCalls[0].id, content: resumeWork });
|
|
528
|
+
providerRuntime.appendToolOutput(result.toolCalls[0].id, resumeWork);
|
|
529
|
+
}
|
|
530
|
+
else {
|
|
531
|
+
const delivered = "(delivered)";
|
|
532
|
+
messages.push({ role: "tool", tool_call_id: result.toolCalls[0].id, content: delivered });
|
|
533
|
+
providerRuntime.appendToolOutput(result.toolCalls[0].id, delivered);
|
|
534
|
+
outcome = intent === "blocked" ? "blocked" : "complete";
|
|
535
|
+
done = true;
|
|
536
|
+
}
|
|
395
537
|
}
|
|
396
538
|
else {
|
|
397
539
|
// Answer is undefined -- the model's final_answer was incomplete or
|
|
398
540
|
// malformed. Clear any partial streamed text or noise, then push the
|
|
399
541
|
// assistant msg + error tool result and let the model try again.
|
|
400
542
|
callbacks.onClearText?.();
|
|
401
|
-
const retryError =
|
|
543
|
+
const retryError = getFinalAnswerRetryError(mustResolveBeforeHandoffActive, intent, sawSteeringFollowUp);
|
|
402
544
|
messages.push(msg);
|
|
403
545
|
messages.push({ role: "tool", tool_call_id: result.toolCalls[0].id, content: retryError });
|
|
404
546
|
providerRuntime.appendToolOutput(result.toolCalls[0].id, retryError);
|
|
@@ -444,7 +586,8 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
444
586
|
let toolResult;
|
|
445
587
|
let success;
|
|
446
588
|
try {
|
|
447
|
-
|
|
589
|
+
const execToolFn = options?.execTool ?? tools_1.execTool;
|
|
590
|
+
toolResult = await execToolFn(tc.name, args, augmentedToolContext ?? options?.toolContext);
|
|
448
591
|
success = true;
|
|
449
592
|
}
|
|
450
593
|
catch (e) {
|
|
@@ -461,6 +604,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
461
604
|
// Abort is not an error — just stop cleanly
|
|
462
605
|
if (signal?.aborted) {
|
|
463
606
|
stripLastToolCalls(messages);
|
|
607
|
+
outcome = "aborted";
|
|
464
608
|
break;
|
|
465
609
|
}
|
|
466
610
|
// Context overflow: trim aggressively and retry once
|
|
@@ -495,6 +639,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
495
639
|
});
|
|
496
640
|
if (aborted) {
|
|
497
641
|
stripLastToolCalls(messages);
|
|
642
|
+
outcome = "aborted";
|
|
498
643
|
break;
|
|
499
644
|
}
|
|
500
645
|
providerRuntime.resetTurnState(messages);
|
|
@@ -510,6 +655,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
510
655
|
meta: {},
|
|
511
656
|
});
|
|
512
657
|
stripLastToolCalls(messages);
|
|
658
|
+
outcome = "errored";
|
|
513
659
|
done = true;
|
|
514
660
|
}
|
|
515
661
|
}
|
|
@@ -520,5 +666,5 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
520
666
|
message: "runAgent turn completed",
|
|
521
667
|
meta: { done },
|
|
522
668
|
});
|
|
523
|
-
return { usage: lastUsage };
|
|
669
|
+
return { usage: lastUsage, outcome, completion };
|
|
524
670
|
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.deliverCrossChatMessage = deliverCrossChatMessage;
|
|
4
|
+
const types_1 = require("../mind/friends/types");
|
|
5
|
+
const runtime_1 = require("../nerves/runtime");
|
|
6
|
+
function buildPendingEnvelope(request, agentName, now) {
|
|
7
|
+
return {
|
|
8
|
+
from: agentName,
|
|
9
|
+
friendId: request.friendId,
|
|
10
|
+
channel: request.channel,
|
|
11
|
+
key: request.key,
|
|
12
|
+
content: request.content,
|
|
13
|
+
timestamp: now,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
function queueForLater(request, deps, detail) {
|
|
17
|
+
deps.queuePending(buildPendingEnvelope(request, deps.agentName, (deps.now ?? Date.now)()));
|
|
18
|
+
return {
|
|
19
|
+
status: "queued_for_later",
|
|
20
|
+
detail,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
function isExplicitlyAuthorized(request) {
|
|
24
|
+
return request.intent === "explicit_cross_chat"
|
|
25
|
+
&& Boolean(request.authorizingSession)
|
|
26
|
+
&& (0, types_1.isTrustedLevel)(request.authorizingSession?.trustLevel);
|
|
27
|
+
}
|
|
28
|
+
async function deliverCrossChatMessage(request, deps) {
|
|
29
|
+
(0, runtime_1.emitNervesEvent)({
|
|
30
|
+
component: "engine",
|
|
31
|
+
event: "engine.cross_chat_delivery_start",
|
|
32
|
+
message: "resolving cross-chat delivery",
|
|
33
|
+
meta: {
|
|
34
|
+
friendId: request.friendId,
|
|
35
|
+
channel: request.channel,
|
|
36
|
+
key: request.key,
|
|
37
|
+
intent: request.intent,
|
|
38
|
+
authorizingTrustLevel: request.authorizingSession?.trustLevel ?? null,
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
if (request.intent === "generic_outreach") {
|
|
42
|
+
const result = queueForLater(request, deps, "generic outreach stays queued until the target session is next active");
|
|
43
|
+
(0, runtime_1.emitNervesEvent)({
|
|
44
|
+
component: "engine",
|
|
45
|
+
event: "engine.cross_chat_delivery_end",
|
|
46
|
+
message: "queued generic outreach for later delivery",
|
|
47
|
+
meta: {
|
|
48
|
+
friendId: request.friendId,
|
|
49
|
+
channel: request.channel,
|
|
50
|
+
key: request.key,
|
|
51
|
+
status: result.status,
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
return result;
|
|
55
|
+
}
|
|
56
|
+
if (!isExplicitlyAuthorized(request)) {
|
|
57
|
+
const result = {
|
|
58
|
+
status: "blocked",
|
|
59
|
+
detail: "explicit cross-chat delivery requires a trusted asking session",
|
|
60
|
+
};
|
|
61
|
+
(0, runtime_1.emitNervesEvent)({
|
|
62
|
+
level: "warn",
|
|
63
|
+
component: "engine",
|
|
64
|
+
event: "engine.cross_chat_delivery_end",
|
|
65
|
+
message: "blocked explicit cross-chat delivery",
|
|
66
|
+
meta: {
|
|
67
|
+
friendId: request.friendId,
|
|
68
|
+
channel: request.channel,
|
|
69
|
+
key: request.key,
|
|
70
|
+
status: result.status,
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
return result;
|
|
74
|
+
}
|
|
75
|
+
const deliverer = deps.deliverers?.[request.channel];
|
|
76
|
+
if (!deliverer) {
|
|
77
|
+
const result = queueForLater(request, deps, "live delivery unavailable right now; queued for the next active turn");
|
|
78
|
+
(0, runtime_1.emitNervesEvent)({
|
|
79
|
+
component: "engine",
|
|
80
|
+
event: "engine.cross_chat_delivery_end",
|
|
81
|
+
message: "queued explicit cross-chat delivery because no live deliverer was available",
|
|
82
|
+
meta: {
|
|
83
|
+
friendId: request.friendId,
|
|
84
|
+
channel: request.channel,
|
|
85
|
+
key: request.key,
|
|
86
|
+
status: result.status,
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
return result;
|
|
90
|
+
}
|
|
91
|
+
try {
|
|
92
|
+
const direct = await deliverer(request);
|
|
93
|
+
if (direct.status === "delivered_now" || direct.status === "blocked" || direct.status === "failed") {
|
|
94
|
+
const result = {
|
|
95
|
+
status: direct.status,
|
|
96
|
+
detail: direct.detail,
|
|
97
|
+
};
|
|
98
|
+
(0, runtime_1.emitNervesEvent)({
|
|
99
|
+
level: result.status === "failed" ? "error" : result.status === "blocked" ? "warn" : "info",
|
|
100
|
+
component: "engine",
|
|
101
|
+
event: "engine.cross_chat_delivery_end",
|
|
102
|
+
message: "completed direct cross-chat delivery resolution",
|
|
103
|
+
meta: {
|
|
104
|
+
friendId: request.friendId,
|
|
105
|
+
channel: request.channel,
|
|
106
|
+
key: request.key,
|
|
107
|
+
status: result.status,
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
return result;
|
|
111
|
+
}
|
|
112
|
+
const result = queueForLater(request, deps, direct.detail.trim() || "live delivery unavailable right now; queued for the next active turn");
|
|
113
|
+
(0, runtime_1.emitNervesEvent)({
|
|
114
|
+
component: "engine",
|
|
115
|
+
event: "engine.cross_chat_delivery_end",
|
|
116
|
+
message: "queued explicit cross-chat delivery after adapter reported unavailability",
|
|
117
|
+
meta: {
|
|
118
|
+
friendId: request.friendId,
|
|
119
|
+
channel: request.channel,
|
|
120
|
+
key: request.key,
|
|
121
|
+
status: result.status,
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
return result;
|
|
125
|
+
}
|
|
126
|
+
catch (error) {
|
|
127
|
+
const result = {
|
|
128
|
+
status: "failed",
|
|
129
|
+
detail: error instanceof Error ? error.message : String(error),
|
|
130
|
+
};
|
|
131
|
+
(0, runtime_1.emitNervesEvent)({
|
|
132
|
+
level: "error",
|
|
133
|
+
component: "engine",
|
|
134
|
+
event: "engine.cross_chat_delivery_end",
|
|
135
|
+
message: "cross-chat delivery threw unexpectedly",
|
|
136
|
+
meta: {
|
|
137
|
+
friendId: request.friendId,
|
|
138
|
+
channel: request.channel,
|
|
139
|
+
key: request.key,
|
|
140
|
+
status: result.status,
|
|
141
|
+
reason: result.detail,
|
|
142
|
+
},
|
|
143
|
+
});
|
|
144
|
+
return result;
|
|
145
|
+
}
|
|
146
|
+
}
|