@ouro.bot/cli 0.1.0-alpha.5 → 0.1.0-alpha.51
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 +117 -188
- package/assets/ouroboros.png +0 -0
- package/changelog.json +252 -0
- package/dist/heart/active-work.js +157 -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 +81 -8
- package/dist/heart/core.js +172 -52
- package/dist/heart/daemon/agent-discovery.js +81 -0
- package/dist/heart/daemon/daemon-cli.js +1099 -164
- package/dist/heart/daemon/daemon-entry.js +14 -5
- package/dist/heart/daemon/daemon-runtime-sync.js +90 -0
- package/dist/heart/daemon/daemon.js +184 -9
- package/dist/heart/daemon/hatch-animation.js +10 -3
- package/dist/heart/daemon/hatch-flow.js +3 -20
- package/dist/heart/daemon/hooks/bundle-meta.js +92 -0
- package/dist/heart/daemon/launchd.js +151 -0
- package/dist/heart/daemon/message-router.js +15 -6
- 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 +1 -1
- package/dist/heart/daemon/run-hooks.js +37 -0
- package/dist/heart/daemon/runtime-metadata.js +118 -0
- package/dist/heart/daemon/sense-manager.js +290 -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 +82 -4
- 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 +72 -7
- package/dist/heart/providers/azure.js +8 -1
- package/dist/heart/providers/minimax.js +4 -0
- package/dist/heart/providers/openai-codex.js +10 -1
- 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/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 +43 -0
- package/dist/mind/friends/store-file.js +19 -0
- package/dist/mind/friends/types.js +9 -1
- package/dist/mind/memory.js +10 -3
- package/dist/mind/pending.js +72 -9
- package/dist/mind/phrases.js +1 -0
- package/dist/mind/prompt.js +275 -77
- package/dist/mind/token-estimate.js +8 -12
- package/dist/nerves/cli-logging.js +15 -2
- 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 +496 -245
- package/dist/repertoire/tools-bluebubbles.js +93 -0
- package/dist/repertoire/tools-teams.js +58 -25
- package/dist/repertoire/tools.js +93 -49
- package/dist/senses/bluebubbles-client.js +484 -0
- package/dist/senses/bluebubbles-entry.js +13 -0
- package/dist/senses/bluebubbles-inbound-log.js +109 -0
- package/dist/senses/bluebubbles-media.js +338 -0
- package/dist/senses/bluebubbles-model.js +261 -0
- package/dist/senses/bluebubbles-mutation-log.js +116 -0
- package/dist/senses/bluebubbles-runtime-state.js +109 -0
- package/dist/senses/bluebubbles-session-cleanup.js +72 -0
- package/dist/senses/bluebubbles.js +1142 -0
- package/dist/senses/cli.js +340 -138
- 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 +256 -0
- package/dist/senses/teams.js +541 -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/config.js
CHANGED
|
@@ -35,23 +35,29 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.loadConfig = loadConfig;
|
|
37
37
|
exports.resetConfigCache = resetConfigCache;
|
|
38
|
-
exports.
|
|
38
|
+
exports.patchRuntimeConfig = patchRuntimeConfig;
|
|
39
39
|
exports.getAzureConfig = getAzureConfig;
|
|
40
40
|
exports.getMinimaxConfig = getMinimaxConfig;
|
|
41
41
|
exports.getAnthropicConfig = getAnthropicConfig;
|
|
42
42
|
exports.getOpenAICodexConfig = getOpenAICodexConfig;
|
|
43
43
|
exports.getTeamsConfig = getTeamsConfig;
|
|
44
|
+
exports.getTeamsSecondaryConfig = getTeamsSecondaryConfig;
|
|
44
45
|
exports.getContextConfig = getContextConfig;
|
|
45
46
|
exports.getOAuthConfig = getOAuthConfig;
|
|
47
|
+
exports.resolveOAuthForTenant = resolveOAuthForTenant;
|
|
46
48
|
exports.getTeamsChannelConfig = getTeamsChannelConfig;
|
|
49
|
+
exports.getBlueBubblesConfig = getBlueBubblesConfig;
|
|
50
|
+
exports.getBlueBubblesChannelConfig = getBlueBubblesChannelConfig;
|
|
47
51
|
exports.getIntegrationsConfig = getIntegrationsConfig;
|
|
48
52
|
exports.getOpenAIEmbeddingsApiKey = getOpenAIEmbeddingsApiKey;
|
|
49
53
|
exports.getLogsDir = getLogsDir;
|
|
54
|
+
exports.sanitizeKey = sanitizeKey;
|
|
55
|
+
exports.slugify = slugify;
|
|
56
|
+
exports.resolveSessionPath = resolveSessionPath;
|
|
50
57
|
exports.sessionPath = sessionPath;
|
|
51
58
|
exports.logPath = logPath;
|
|
52
59
|
const fs = __importStar(require("fs"));
|
|
53
60
|
const path = __importStar(require("path"));
|
|
54
|
-
const os = __importStar(require("os"));
|
|
55
61
|
const identity_1 = require("./identity");
|
|
56
62
|
const runtime_1 = require("../nerves/runtime");
|
|
57
63
|
const DEFAULT_SECRETS_TEMPLATE = {
|
|
@@ -74,7 +80,7 @@ const DEFAULT_SECRETS_TEMPLATE = {
|
|
|
74
80
|
setupToken: "",
|
|
75
81
|
},
|
|
76
82
|
"openai-codex": {
|
|
77
|
-
model: "gpt-5.
|
|
83
|
+
model: "gpt-5.4",
|
|
78
84
|
oauthAccessToken: "",
|
|
79
85
|
},
|
|
80
86
|
},
|
|
@@ -82,16 +88,33 @@ const DEFAULT_SECRETS_TEMPLATE = {
|
|
|
82
88
|
clientId: "",
|
|
83
89
|
clientSecret: "",
|
|
84
90
|
tenantId: "",
|
|
91
|
+
managedIdentityClientId: "",
|
|
85
92
|
},
|
|
86
93
|
oauth: {
|
|
87
94
|
graphConnectionName: "graph",
|
|
88
95
|
adoConnectionName: "ado",
|
|
89
96
|
githubConnectionName: "",
|
|
90
97
|
},
|
|
98
|
+
teamsSecondary: {
|
|
99
|
+
clientId: "",
|
|
100
|
+
clientSecret: "",
|
|
101
|
+
tenantId: "",
|
|
102
|
+
managedIdentityClientId: "",
|
|
103
|
+
},
|
|
91
104
|
teamsChannel: {
|
|
92
105
|
skipConfirmation: true,
|
|
93
106
|
port: 3978,
|
|
94
107
|
},
|
|
108
|
+
bluebubbles: {
|
|
109
|
+
serverUrl: "",
|
|
110
|
+
password: "",
|
|
111
|
+
accountId: "default",
|
|
112
|
+
},
|
|
113
|
+
bluebubblesChannel: {
|
|
114
|
+
port: 18790,
|
|
115
|
+
webhookPath: "/bluebubbles-webhook",
|
|
116
|
+
requestTimeoutMs: 30000,
|
|
117
|
+
},
|
|
95
118
|
integrations: {
|
|
96
119
|
perplexityApiKey: "",
|
|
97
120
|
openaiEmbeddingsApiKey: "",
|
|
@@ -106,9 +129,12 @@ function defaultRuntimeConfig() {
|
|
|
106
129
|
"openai-codex": { ...DEFAULT_SECRETS_TEMPLATE.providers["openai-codex"] },
|
|
107
130
|
},
|
|
108
131
|
teams: { ...DEFAULT_SECRETS_TEMPLATE.teams },
|
|
132
|
+
teamsSecondary: { ...DEFAULT_SECRETS_TEMPLATE.teamsSecondary },
|
|
109
133
|
oauth: { ...DEFAULT_SECRETS_TEMPLATE.oauth },
|
|
110
134
|
context: { ...identity_1.DEFAULT_AGENT_CONTEXT },
|
|
111
135
|
teamsChannel: { ...DEFAULT_SECRETS_TEMPLATE.teamsChannel },
|
|
136
|
+
bluebubbles: { ...DEFAULT_SECRETS_TEMPLATE.bluebubbles },
|
|
137
|
+
bluebubblesChannel: { ...DEFAULT_SECRETS_TEMPLATE.bluebubblesChannel },
|
|
112
138
|
integrations: { ...DEFAULT_SECRETS_TEMPLATE.integrations },
|
|
113
139
|
};
|
|
114
140
|
}
|
|
@@ -219,7 +245,7 @@ function resetConfigCache() {
|
|
|
219
245
|
_cachedConfig = null;
|
|
220
246
|
_testContextOverride = null;
|
|
221
247
|
}
|
|
222
|
-
function
|
|
248
|
+
function patchRuntimeConfig(partial) {
|
|
223
249
|
loadConfig(); // ensure _cachedConfig exists
|
|
224
250
|
const contextPatch = partial.context;
|
|
225
251
|
if (contextPatch) {
|
|
@@ -248,6 +274,10 @@ function getTeamsConfig() {
|
|
|
248
274
|
const config = loadConfig();
|
|
249
275
|
return { ...config.teams };
|
|
250
276
|
}
|
|
277
|
+
function getTeamsSecondaryConfig() {
|
|
278
|
+
const config = loadConfig();
|
|
279
|
+
return { ...config.teamsSecondary };
|
|
280
|
+
}
|
|
251
281
|
function getContextConfig() {
|
|
252
282
|
if (_testContextOverride) {
|
|
253
283
|
return { ..._testContextOverride };
|
|
@@ -268,11 +298,41 @@ function getOAuthConfig() {
|
|
|
268
298
|
const config = loadConfig();
|
|
269
299
|
return { ...config.oauth };
|
|
270
300
|
}
|
|
301
|
+
/** Resolve OAuth connection names for a specific tenant, falling back to defaults. */
|
|
302
|
+
function resolveOAuthForTenant(tenantId) {
|
|
303
|
+
const base = getOAuthConfig();
|
|
304
|
+
const overrides = tenantId ? base.tenantOverrides?.[tenantId] : undefined;
|
|
305
|
+
return {
|
|
306
|
+
graphConnectionName: overrides?.graphConnectionName ?? base.graphConnectionName,
|
|
307
|
+
adoConnectionName: overrides?.adoConnectionName ?? base.adoConnectionName,
|
|
308
|
+
githubConnectionName: overrides?.githubConnectionName ?? base.githubConnectionName,
|
|
309
|
+
};
|
|
310
|
+
}
|
|
271
311
|
function getTeamsChannelConfig() {
|
|
272
312
|
const config = loadConfig();
|
|
273
313
|
const { skipConfirmation, flushIntervalMs, port } = config.teamsChannel;
|
|
274
314
|
return { skipConfirmation, flushIntervalMs, port };
|
|
275
315
|
}
|
|
316
|
+
function getBlueBubblesConfig() {
|
|
317
|
+
const config = loadConfig();
|
|
318
|
+
const { serverUrl, password, accountId } = config.bluebubbles;
|
|
319
|
+
if (!serverUrl.trim()) {
|
|
320
|
+
throw new Error("bluebubbles.serverUrl is required in secrets.json to run the BlueBubbles sense.");
|
|
321
|
+
}
|
|
322
|
+
if (!password.trim()) {
|
|
323
|
+
throw new Error("bluebubbles.password is required in secrets.json to run the BlueBubbles sense.");
|
|
324
|
+
}
|
|
325
|
+
return {
|
|
326
|
+
serverUrl: serverUrl.trim(),
|
|
327
|
+
password: password.trim(),
|
|
328
|
+
accountId: accountId.trim() || "default",
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
function getBlueBubblesChannelConfig() {
|
|
332
|
+
const config = loadConfig();
|
|
333
|
+
const { port, webhookPath, requestTimeoutMs } = config.bluebubblesChannel;
|
|
334
|
+
return { port, webhookPath, requestTimeoutMs };
|
|
335
|
+
}
|
|
276
336
|
function getIntegrationsConfig() {
|
|
277
337
|
const config = loadConfig();
|
|
278
338
|
return { ...config.integrations };
|
|
@@ -281,16 +341,29 @@ function getOpenAIEmbeddingsApiKey() {
|
|
|
281
341
|
return getIntegrationsConfig().openaiEmbeddingsApiKey;
|
|
282
342
|
}
|
|
283
343
|
function getLogsDir() {
|
|
284
|
-
return path.join(
|
|
344
|
+
return path.join((0, identity_1.getAgentRoot)(), "state", "logs");
|
|
285
345
|
}
|
|
286
346
|
function sanitizeKey(key) {
|
|
287
347
|
return key.replace(/[/:]/g, "_");
|
|
288
348
|
}
|
|
289
|
-
function
|
|
290
|
-
|
|
291
|
-
|
|
349
|
+
function slugify(value) {
|
|
350
|
+
return value
|
|
351
|
+
.trim()
|
|
352
|
+
.toLowerCase()
|
|
353
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
354
|
+
.replace(/^-+/, "")
|
|
355
|
+
.replace(/-+$/, "");
|
|
356
|
+
}
|
|
357
|
+
function resolveSessionPath(friendId, channel, key, options) {
|
|
358
|
+
const dir = path.join((0, identity_1.getAgentRoot)(), "state", "sessions", friendId, channel);
|
|
359
|
+
if (options?.ensureDir) {
|
|
360
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
361
|
+
}
|
|
292
362
|
return path.join(dir, sanitizeKey(key) + ".json");
|
|
293
363
|
}
|
|
364
|
+
function sessionPath(friendId, channel, key) {
|
|
365
|
+
return resolveSessionPath(friendId, channel, key, { ensureDir: true });
|
|
366
|
+
}
|
|
294
367
|
function logPath(channel, key) {
|
|
295
368
|
return path.join(getLogsDir(), channel, sanitizeKey(key) + ".ndjson");
|
|
296
369
|
}
|
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");
|
|
@@ -128,6 +126,35 @@ Object.defineProperty(exports, "toResponsesTools", { enumerable: true, get: func
|
|
|
128
126
|
// Re-export prompt functions for backward compat
|
|
129
127
|
var prompt_2 = require("../mind/prompt");
|
|
130
128
|
Object.defineProperty(exports, "buildSystem", { enumerable: true, get: function () { return prompt_2.buildSystem; } });
|
|
129
|
+
function parseFinalAnswerPayload(argumentsText) {
|
|
130
|
+
try {
|
|
131
|
+
const parsed = JSON.parse(argumentsText);
|
|
132
|
+
if (typeof parsed === "string") {
|
|
133
|
+
return { answer: parsed };
|
|
134
|
+
}
|
|
135
|
+
if (!parsed || typeof parsed !== "object") {
|
|
136
|
+
return {};
|
|
137
|
+
}
|
|
138
|
+
const answer = typeof parsed.answer === "string" ? parsed.answer : undefined;
|
|
139
|
+
const rawIntent = parsed.intent;
|
|
140
|
+
const intent = rawIntent === "complete" || rawIntent === "blocked" || rawIntent === "direct_reply"
|
|
141
|
+
? rawIntent
|
|
142
|
+
: undefined;
|
|
143
|
+
return { answer, intent };
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
return {};
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
function getFinalAnswerRetryError(mustResolveBeforeHandoff, intent, sawSteeringFollowUp) {
|
|
150
|
+
if (mustResolveBeforeHandoff && !intent) {
|
|
151
|
+
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.";
|
|
152
|
+
}
|
|
153
|
+
if (mustResolveBeforeHandoff && intent === "direct_reply" && !sawSteeringFollowUp) {
|
|
154
|
+
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.";
|
|
155
|
+
}
|
|
156
|
+
return "your final_answer was incomplete or malformed. call final_answer again with your complete response.";
|
|
157
|
+
}
|
|
131
158
|
// Re-export kick utilities for backward compat
|
|
132
159
|
var kicks_1 = require("./kicks");
|
|
133
160
|
Object.defineProperty(exports, "hasToolIntent", { enumerable: true, get: function () { return kicks_1.hasToolIntent; } });
|
|
@@ -160,6 +187,68 @@ function stripLastToolCalls(messages) {
|
|
|
160
187
|
}
|
|
161
188
|
}
|
|
162
189
|
}
|
|
190
|
+
// Roles that end a tool-result scan. When scanning forward from an assistant
|
|
191
|
+
// message, stop at the next assistant or user message (tool results must be
|
|
192
|
+
// adjacent to their originating assistant message).
|
|
193
|
+
const TOOL_SCAN_BOUNDARY_ROLES = new Set(["assistant", "user"]);
|
|
194
|
+
// Repair orphaned tool_calls and tool results anywhere in the message history.
|
|
195
|
+
// 1. If an assistant message has tool_calls but missing tool results, inject synthetic error results.
|
|
196
|
+
// 2. If a tool result's tool_call_id doesn't match any tool_calls in a preceding assistant message, remove it.
|
|
197
|
+
// This prevents 400 errors from the API after an aborted turn.
|
|
198
|
+
function repairOrphanedToolCalls(messages) {
|
|
199
|
+
// Pass 1: collect all valid tool_call IDs from assistant messages
|
|
200
|
+
const validCallIds = new Set();
|
|
201
|
+
for (const msg of messages) {
|
|
202
|
+
if (msg.role === "assistant") {
|
|
203
|
+
const asst = msg;
|
|
204
|
+
if (asst.tool_calls) {
|
|
205
|
+
for (const tc of asst.tool_calls)
|
|
206
|
+
validCallIds.add(tc.id);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
// Pass 2: remove orphaned tool results (tool_call_id not in any assistant's tool_calls)
|
|
211
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
212
|
+
if (messages[i].role === "tool") {
|
|
213
|
+
const toolMsg = messages[i];
|
|
214
|
+
if (!validCallIds.has(toolMsg.tool_call_id)) {
|
|
215
|
+
messages.splice(i, 1);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
// Pass 3: inject synthetic results for tool_calls missing their tool results
|
|
220
|
+
for (let i = 0; i < messages.length; i++) {
|
|
221
|
+
const msg = messages[i];
|
|
222
|
+
if (msg.role !== "assistant")
|
|
223
|
+
continue;
|
|
224
|
+
const asst = msg;
|
|
225
|
+
if (!asst.tool_calls || asst.tool_calls.length === 0)
|
|
226
|
+
continue;
|
|
227
|
+
// Collect tool result IDs that follow this assistant message
|
|
228
|
+
const resultIds = new Set();
|
|
229
|
+
for (let j = i + 1; j < messages.length; j++) {
|
|
230
|
+
const following = messages[j];
|
|
231
|
+
if (following.role === "tool") {
|
|
232
|
+
resultIds.add(following.tool_call_id);
|
|
233
|
+
}
|
|
234
|
+
else if (TOOL_SCAN_BOUNDARY_ROLES.has(following.role)) {
|
|
235
|
+
break;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
const missing = asst.tool_calls.filter((tc) => !resultIds.has(tc.id));
|
|
239
|
+
if (missing.length > 0) {
|
|
240
|
+
const syntheticResults = missing.map((tc) => ({
|
|
241
|
+
role: "tool",
|
|
242
|
+
tool_call_id: tc.id,
|
|
243
|
+
content: "error: tool call was interrupted (previous turn timed out or was aborted)",
|
|
244
|
+
}));
|
|
245
|
+
let insertAt = i + 1;
|
|
246
|
+
while (insertAt < messages.length && messages[insertAt].role === "tool")
|
|
247
|
+
insertAt++;
|
|
248
|
+
messages.splice(insertAt, 0, ...syntheticResults);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
163
252
|
// Detect context overflow errors from Azure or MiniMax
|
|
164
253
|
function isContextOverflow(err) {
|
|
165
254
|
if (!(err instanceof Error))
|
|
@@ -242,7 +331,12 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
242
331
|
// so turn execution remains consistent and non-fatal.
|
|
243
332
|
if (channel) {
|
|
244
333
|
try {
|
|
245
|
-
const
|
|
334
|
+
const buildSystemOptions = {
|
|
335
|
+
...options,
|
|
336
|
+
providerCapabilities: providerRuntime.capabilities,
|
|
337
|
+
supportedReasoningEfforts: providerRuntime.supportedReasoningEfforts,
|
|
338
|
+
};
|
|
339
|
+
const refreshed = await (0, prompt_1.buildSystem)(channel, buildSystemOptions, currentContext);
|
|
246
340
|
upsertSystemPrompt(messages, refreshed);
|
|
247
341
|
}
|
|
248
342
|
catch (error) {
|
|
@@ -265,20 +359,30 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
265
359
|
}
|
|
266
360
|
}
|
|
267
361
|
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
362
|
let done = false;
|
|
272
363
|
let lastUsage;
|
|
273
364
|
let overflowRetried = false;
|
|
274
365
|
let retryCount = 0;
|
|
366
|
+
let outcome = "complete";
|
|
367
|
+
let completion;
|
|
368
|
+
let sawSteeringFollowUp = false;
|
|
369
|
+
let mustResolveBeforeHandoffActive = options?.mustResolveBeforeHandoff === true;
|
|
370
|
+
let currentReasoningEffort = "medium";
|
|
275
371
|
// Prevent MaxListenersExceeded warning — each iteration adds a listener
|
|
276
372
|
try {
|
|
277
373
|
require("events").setMaxListeners(50, signal);
|
|
278
374
|
}
|
|
279
375
|
catch { /* unsupported */ }
|
|
280
376
|
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);
|
|
377
|
+
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);
|
|
378
|
+
// Augment tool context with reasoning effort controls from provider
|
|
379
|
+
const augmentedToolContext = options?.toolContext
|
|
380
|
+
? {
|
|
381
|
+
...options.toolContext,
|
|
382
|
+
supportedReasoningEfforts: providerRuntime.supportedReasoningEfforts,
|
|
383
|
+
setReasoningEffort: (level) => { currentReasoningEffort = level; },
|
|
384
|
+
}
|
|
385
|
+
: undefined;
|
|
282
386
|
// Rebase provider-owned turn state from canonical messages at user-turn start.
|
|
283
387
|
// This prevents stale provider caches from replaying prior-turn context.
|
|
284
388
|
providerRuntime.resetTurnState(messages);
|
|
@@ -290,6 +394,18 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
290
394
|
const activeTools = toolChoiceRequired ? [...baseTools, tools_1.finalAnswerTool] : baseTools;
|
|
291
395
|
const steeringFollowUps = options?.drainSteeringFollowUps?.() ?? [];
|
|
292
396
|
if (steeringFollowUps.length > 0) {
|
|
397
|
+
const hasSupersedingFollowUp = steeringFollowUps.some((followUp) => followUp.effect === "clear_and_supersede");
|
|
398
|
+
if (hasSupersedingFollowUp) {
|
|
399
|
+
mustResolveBeforeHandoffActive = false;
|
|
400
|
+
options?.setMustResolveBeforeHandoff?.(false);
|
|
401
|
+
outcome = "superseded";
|
|
402
|
+
break;
|
|
403
|
+
}
|
|
404
|
+
if (steeringFollowUps.some((followUp) => followUp.effect === "set_no_handoff")) {
|
|
405
|
+
mustResolveBeforeHandoffActive = true;
|
|
406
|
+
options?.setMustResolveBeforeHandoff?.(true);
|
|
407
|
+
}
|
|
408
|
+
sawSteeringFollowUp = true;
|
|
293
409
|
for (const followUp of steeringFollowUps) {
|
|
294
410
|
messages.push({ role: "user", content: followUp.text });
|
|
295
411
|
}
|
|
@@ -297,8 +413,10 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
297
413
|
}
|
|
298
414
|
// Yield so pending I/O (stdin Ctrl-C) can be processed between iterations
|
|
299
415
|
await new Promise((r) => setImmediate(r));
|
|
300
|
-
if (signal?.aborted)
|
|
416
|
+
if (signal?.aborted) {
|
|
417
|
+
outcome = "aborted";
|
|
301
418
|
break;
|
|
419
|
+
}
|
|
302
420
|
try {
|
|
303
421
|
callbacks.onModelStart();
|
|
304
422
|
const result = await providerRuntime.streamTurn({
|
|
@@ -308,6 +426,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
308
426
|
signal,
|
|
309
427
|
traceId,
|
|
310
428
|
toolChoiceRequired,
|
|
429
|
+
reasoningEffort: currentReasoningEffort,
|
|
311
430
|
});
|
|
312
431
|
// Track usage from the latest API call
|
|
313
432
|
if (result.usage)
|
|
@@ -331,49 +450,39 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
331
450
|
if (reasoningItems.length > 0) {
|
|
332
451
|
msg._reasoning_items = reasoningItems;
|
|
333
452
|
}
|
|
453
|
+
// Store thinking blocks (Anthropic) on the assistant message for round-tripping
|
|
454
|
+
const thinkingItems = result.outputItems.filter((item) => "type" in item && (item.type === "thinking" || item.type === "redacted_thinking"));
|
|
455
|
+
if (thinkingItems.length > 0) {
|
|
456
|
+
msg._thinking_blocks = thinkingItems;
|
|
457
|
+
}
|
|
458
|
+
// Phase annotation for Codex provider
|
|
459
|
+
const hasPhaseAnnotation = providerRuntime.capabilities.has("phase-annotation");
|
|
460
|
+
const isSoleFinalAnswer = result.toolCalls.length === 1 && result.toolCalls[0].name === "final_answer";
|
|
461
|
+
if (hasPhaseAnnotation) {
|
|
462
|
+
msg.phase = isSoleFinalAnswer ? "final_answer" : "commentary";
|
|
463
|
+
}
|
|
334
464
|
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
|
-
// }
|
|
465
|
+
// No tool calls — accept response as-is.
|
|
466
|
+
// (Kick detection disabled; tool_choice: required + final_answer
|
|
467
|
+
// is the primary loop control. See src/heart/kicks.ts to re-enable.)
|
|
353
468
|
messages.push(msg);
|
|
354
469
|
done = true;
|
|
355
470
|
}
|
|
356
471
|
else {
|
|
357
472
|
// Check for final_answer sole call: intercept before tool execution
|
|
358
|
-
const isSoleFinalAnswer = result.toolCalls.length === 1 && result.toolCalls[0].name === "final_answer";
|
|
359
473
|
if (isSoleFinalAnswer) {
|
|
360
474
|
// 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) {
|
|
475
|
+
// Supports: {"answer":"text","intent":"..."} or "text" (JSON string).
|
|
476
|
+
const { answer, intent } = parseFinalAnswerPayload(result.toolCalls[0].arguments);
|
|
477
|
+
const validDirectReply = mustResolveBeforeHandoffActive && intent === "direct_reply" && sawSteeringFollowUp;
|
|
478
|
+
const validTerminalIntent = intent === "complete" || intent === "blocked";
|
|
479
|
+
const validClosure = answer != null
|
|
480
|
+
&& (!mustResolveBeforeHandoffActive || validDirectReply || validTerminalIntent);
|
|
481
|
+
if (validClosure) {
|
|
482
|
+
completion = {
|
|
483
|
+
answer,
|
|
484
|
+
intent: validDirectReply ? "direct_reply" : intent === "blocked" ? "blocked" : "complete",
|
|
485
|
+
};
|
|
377
486
|
if (result.finalAnswerStreamed) {
|
|
378
487
|
// The streaming layer already parsed and emitted the answer
|
|
379
488
|
// progressively via FinalAnswerParser. Skip clearing and
|
|
@@ -386,19 +495,26 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
386
495
|
// Never truncate -- channel adapters handle splitting long messages.
|
|
387
496
|
callbacks.onTextChunk(answer);
|
|
388
497
|
}
|
|
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
498
|
messages.push(msg);
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
499
|
+
if (validDirectReply) {
|
|
500
|
+
const resumeWork = "direct reply delivered. resume the unresolved obligation now and keep working until you can finish or clearly report that you are blocked.";
|
|
501
|
+
messages.push({ role: "tool", tool_call_id: result.toolCalls[0].id, content: resumeWork });
|
|
502
|
+
providerRuntime.appendToolOutput(result.toolCalls[0].id, resumeWork);
|
|
503
|
+
}
|
|
504
|
+
else {
|
|
505
|
+
const delivered = "(delivered)";
|
|
506
|
+
messages.push({ role: "tool", tool_call_id: result.toolCalls[0].id, content: delivered });
|
|
507
|
+
providerRuntime.appendToolOutput(result.toolCalls[0].id, delivered);
|
|
508
|
+
outcome = intent === "blocked" ? "blocked" : "complete";
|
|
509
|
+
done = true;
|
|
510
|
+
}
|
|
395
511
|
}
|
|
396
512
|
else {
|
|
397
513
|
// Answer is undefined -- the model's final_answer was incomplete or
|
|
398
514
|
// malformed. Clear any partial streamed text or noise, then push the
|
|
399
515
|
// assistant msg + error tool result and let the model try again.
|
|
400
516
|
callbacks.onClearText?.();
|
|
401
|
-
const retryError =
|
|
517
|
+
const retryError = getFinalAnswerRetryError(mustResolveBeforeHandoffActive, intent, sawSteeringFollowUp);
|
|
402
518
|
messages.push(msg);
|
|
403
519
|
messages.push({ role: "tool", tool_call_id: result.toolCalls[0].id, content: retryError });
|
|
404
520
|
providerRuntime.appendToolOutput(result.toolCalls[0].id, retryError);
|
|
@@ -444,7 +560,8 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
444
560
|
let toolResult;
|
|
445
561
|
let success;
|
|
446
562
|
try {
|
|
447
|
-
|
|
563
|
+
const execToolFn = options?.execTool ?? tools_1.execTool;
|
|
564
|
+
toolResult = await execToolFn(tc.name, args, augmentedToolContext ?? options?.toolContext);
|
|
448
565
|
success = true;
|
|
449
566
|
}
|
|
450
567
|
catch (e) {
|
|
@@ -461,6 +578,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
461
578
|
// Abort is not an error — just stop cleanly
|
|
462
579
|
if (signal?.aborted) {
|
|
463
580
|
stripLastToolCalls(messages);
|
|
581
|
+
outcome = "aborted";
|
|
464
582
|
break;
|
|
465
583
|
}
|
|
466
584
|
// Context overflow: trim aggressively and retry once
|
|
@@ -495,6 +613,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
495
613
|
});
|
|
496
614
|
if (aborted) {
|
|
497
615
|
stripLastToolCalls(messages);
|
|
616
|
+
outcome = "aborted";
|
|
498
617
|
break;
|
|
499
618
|
}
|
|
500
619
|
providerRuntime.resetTurnState(messages);
|
|
@@ -510,6 +629,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
510
629
|
meta: {},
|
|
511
630
|
});
|
|
512
631
|
stripLastToolCalls(messages);
|
|
632
|
+
outcome = "errored";
|
|
513
633
|
done = true;
|
|
514
634
|
}
|
|
515
635
|
}
|
|
@@ -520,5 +640,5 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
520
640
|
message: "runAgent turn completed",
|
|
521
641
|
meta: { done },
|
|
522
642
|
});
|
|
523
|
-
return { usage: lastUsage };
|
|
643
|
+
return { usage: lastUsage, outcome, completion };
|
|
524
644
|
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.listEnabledBundleAgents = listEnabledBundleAgents;
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
const identity_1 = require("../identity");
|
|
40
|
+
const runtime_1 = require("../../nerves/runtime");
|
|
41
|
+
function listEnabledBundleAgents(options = {}) {
|
|
42
|
+
const bundlesRoot = options.bundlesRoot ?? (0, identity_1.getAgentBundlesRoot)();
|
|
43
|
+
const readdirSync = options.readdirSync ?? fs.readdirSync;
|
|
44
|
+
const readFileSync = options.readFileSync ?? fs.readFileSync;
|
|
45
|
+
let entries;
|
|
46
|
+
try {
|
|
47
|
+
entries = readdirSync(bundlesRoot, { withFileTypes: true });
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
(0, runtime_1.emitNervesEvent)({
|
|
51
|
+
level: "warn",
|
|
52
|
+
component: "daemon",
|
|
53
|
+
event: "daemon.agent_discovery_failed",
|
|
54
|
+
message: "failed to read bundle root for daemon agent discovery",
|
|
55
|
+
meta: { bundlesRoot },
|
|
56
|
+
});
|
|
57
|
+
return [];
|
|
58
|
+
}
|
|
59
|
+
const discovered = [];
|
|
60
|
+
for (const entry of entries) {
|
|
61
|
+
if (!entry.isDirectory() || !entry.name.endsWith(".ouro"))
|
|
62
|
+
continue;
|
|
63
|
+
const agentName = entry.name.slice(0, -5);
|
|
64
|
+
const configPath = path.join(bundlesRoot, entry.name, "agent.json");
|
|
65
|
+
let enabled = true;
|
|
66
|
+
try {
|
|
67
|
+
const raw = readFileSync(configPath, "utf-8");
|
|
68
|
+
const parsed = JSON.parse(raw);
|
|
69
|
+
if (typeof parsed.enabled === "boolean") {
|
|
70
|
+
enabled = parsed.enabled;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
if (enabled) {
|
|
77
|
+
discovered.push(agentName);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return discovered.sort((left, right) => left.localeCompare(right));
|
|
81
|
+
}
|