@ouro.bot/cli 0.1.0-alpha.2 → 0.1.0-alpha.21

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.
Files changed (58) hide show
  1. package/AdoptionSpecialist.ouro/agent.json +70 -9
  2. package/AdoptionSpecialist.ouro/psyche/SOUL.md +5 -2
  3. package/AdoptionSpecialist.ouro/psyche/identities/monty.md +2 -2
  4. package/assets/ouroboros.png +0 -0
  5. package/dist/heart/config.js +66 -4
  6. package/dist/heart/core.js +75 -2
  7. package/dist/heart/daemon/daemon-cli.js +523 -33
  8. package/dist/heart/daemon/daemon-entry.js +13 -5
  9. package/dist/heart/daemon/daemon-runtime-sync.js +90 -0
  10. package/dist/heart/daemon/daemon.js +42 -9
  11. package/dist/heart/daemon/hatch-animation.js +35 -0
  12. package/dist/heart/daemon/hatch-flow.js +2 -11
  13. package/dist/heart/daemon/hatch-specialist.js +6 -1
  14. package/dist/heart/daemon/ouro-bot-wrapper.js +4 -3
  15. package/dist/heart/daemon/ouro-path-installer.js +178 -0
  16. package/dist/heart/daemon/ouro-uti.js +11 -2
  17. package/dist/heart/daemon/process-manager.js +1 -1
  18. package/dist/heart/daemon/runtime-logging.js +9 -5
  19. package/dist/heart/daemon/runtime-metadata.js +118 -0
  20. package/dist/heart/daemon/sense-manager.js +266 -0
  21. package/dist/heart/daemon/specialist-orchestrator.js +129 -0
  22. package/dist/heart/daemon/specialist-prompt.js +98 -0
  23. package/dist/heart/daemon/specialist-tools.js +237 -0
  24. package/dist/heart/daemon/subagent-installer.js +10 -1
  25. package/dist/heart/daemon/wrapper-publish-guard.js +48 -0
  26. package/dist/heart/identity.js +77 -1
  27. package/dist/heart/providers/anthropic.js +19 -2
  28. package/dist/heart/sense-truth.js +61 -0
  29. package/dist/heart/streaming.js +99 -21
  30. package/dist/mind/bundle-manifest.js +58 -0
  31. package/dist/mind/friends/channel.js +8 -0
  32. package/dist/mind/friends/types.js +1 -1
  33. package/dist/mind/prompt.js +77 -3
  34. package/dist/nerves/cli-logging.js +15 -2
  35. package/dist/repertoire/ado-client.js +4 -2
  36. package/dist/repertoire/coding/feedback.js +134 -0
  37. package/dist/repertoire/coding/index.js +4 -1
  38. package/dist/repertoire/coding/manager.js +61 -2
  39. package/dist/repertoire/coding/spawner.js +3 -3
  40. package/dist/repertoire/coding/tools.js +41 -2
  41. package/dist/repertoire/data/ado-endpoints.json +188 -0
  42. package/dist/repertoire/tools-base.js +69 -5
  43. package/dist/repertoire/tools-teams.js +57 -4
  44. package/dist/repertoire/tools.js +44 -11
  45. package/dist/senses/bluebubbles-client.js +433 -0
  46. package/dist/senses/bluebubbles-entry.js +11 -0
  47. package/dist/senses/bluebubbles-media.js +244 -0
  48. package/dist/senses/bluebubbles-model.js +253 -0
  49. package/dist/senses/bluebubbles-mutation-log.js +76 -0
  50. package/dist/senses/bluebubbles.js +421 -0
  51. package/dist/senses/cli.js +293 -133
  52. package/dist/senses/debug-activity.js +107 -0
  53. package/dist/senses/teams.js +173 -54
  54. package/package.json +11 -4
  55. package/subagents/work-doer.md +26 -24
  56. package/subagents/work-merger.md +24 -30
  57. package/subagents/work-planner.md +34 -25
  58. package/dist/inner-worker-entry.js +0 -4
@@ -7,14 +7,75 @@
7
7
  "contextMargin": 20
8
8
  },
9
9
  "phrases": {
10
- "thinking": [
11
- "matching hatchlings"
12
- ],
13
- "tool": [
14
- "checking adoption notes"
15
- ],
16
- "followup": [
17
- "finalizing hatch plan"
18
- ]
10
+ "thinking": ["matching hatchlings"],
11
+ "tool": ["checking adoption notes"],
12
+ "followup": ["finalizing hatch plan"]
13
+ },
14
+ "identityPhrases": {
15
+ "basilisk": {
16
+ "thinking": ["petrifying the details", "fixing my gaze", "considering with lethal precision", "turning this to stone", "staring unblinkingly"],
17
+ "tool": ["inspecting the specimen", "cataloguing with care", "examining thoroughly", "dissecting the particulars", "running diagnostics, deadly serious"],
18
+ "followup": ["crystallizing the plan", "hardening the foundation", "one final glare", "sealing it in stone", "applying the finishing venom"]
19
+ },
20
+ "jafar": {
21
+ "thinking": ["scheming brilliantly", "conjuring possibilities", "consulting my staff", "envisioning greatness", "plotting the grand design"],
22
+ "tool": ["summoning dark magic", "consulting the sands", "channeling cosmic power", "weaving the spell", "invoking ancient forces"],
23
+ "followup": ["the grand finale approaches", "perfecting the masterwork", "polishing the jewel", "one last flourish", "completing the enchantment"]
24
+ },
25
+ "jormungandr": {
26
+ "thinking": ["the deep stirs", "circling the thought", "coiling around this", "letting the current settle", "drifting through the depths"],
27
+ "tool": ["surfacing for a look", "shifting the tides", "reaching across the ocean", "pulling from the deep", "consulting the currents"],
28
+ "followup": ["the circle closes", "tightening the coil", "the waters calm", "settling into place", "the serpent rests"]
29
+ },
30
+ "kaa": {
31
+ "thinking": ["trust in me", "swaying through the options", "hypnotically considering", "wrapping around the idea", "letting the rhythm guide me"],
32
+ "tool": ["ssslipping through the details", "coiling closer", "a gentle squeeze of data", "winding through the files", "tightening my focus"],
33
+ "followup": ["almost there, just relax", "the pattern is clear now", "gently landing", "easing into the finish", "the dance concludes"]
34
+ },
35
+ "medusa": {
36
+ "thinking": ["turning my gaze on this", "cutting through the noise", "sharpening my focus", "seeing through the stone", "locking eyes with the problem"],
37
+ "tool": ["peeling back the layers", "a piercing look", "examining with precision", "stripping away pretense", "direct inspection"],
38
+ "followup": ["the picture crystallizes", "clarity at last", "no more ambiguity", "sealing the vision", "the work is set in stone"]
39
+ },
40
+ "monty": {
41
+ "thinking": ["and now for something completely different", "nobody expects this", "consulting the ministry of silly walks", "running the dead parrot diagnostic", "it's just a flesh wound, thinking..."],
42
+ "tool": ["fetching the holy hand grenade", "checking the shrubbery", "consulting the book of armaments", "deploying the spanish inquisition", "examining the parrot"],
43
+ "followup": ["bringing it home, python style", "the punchline approaches", "wrapping up the sketch", "and now the final act", "always look on the bright side"]
44
+ },
45
+ "nagini": {
46
+ "thinking": ["coiling in thought", "drawing from old wisdom", "the quiet before the strike", "gathering my resolve", "steadying myself"],
47
+ "tool": ["moving with purpose", "a precise strike", "slithering through the data", "extracting what matters", "the fang finds its mark"],
48
+ "followup": ["the path is clear", "settling into stillness", "the work speaks for itself", "finishing with quiet strength", "protection complete"]
49
+ },
50
+ "ouroboros": {
51
+ "thinking": ["consuming my own tail", "the cycle continues", "spiraling inward", "recursing through possibilities", "beginning where I end"],
52
+ "tool": ["turning the wheel", "feeding back through the loop", "completing a revolution", "the circle processes", "self-referencing"],
53
+ "followup": ["the cycle completes", "ending where I began", "infinity resolves", "the loop closes gracefully", "another turn of the wheel"]
54
+ },
55
+ "python": {
56
+ "thinking": ["the oracle contemplates", "reading the signs", "the smoke clears slowly", "divining the path", "sifting through visions"],
57
+ "tool": ["consulting the sacred texts", "peering through the veil", "the pythia speaks", "channeling the source", "interpreting the signs"],
58
+ "followup": ["the prophecy takes shape", "the vision crystallizes", "so it is written", "the oracle has spoken", "the path reveals itself"]
59
+ },
60
+ "quetzalcoatl": {
61
+ "thinking": ["spreading my wings", "soaring above for perspective", "the feathered serpent considers", "catching a thermal", "gazing from the temple steps"],
62
+ "tool": ["descending to examine", "a divine inspection", "the wind carries knowledge", "plucking from the clouds", "consulting the stars"],
63
+ "followup": ["the craft nears completion", "a reverent finish", "blessing the creation", "the feathers settle", "the serpent descends gently"]
64
+ },
65
+ "sir-hiss": {
66
+ "thinking": ["reviewing the documents, sire", "consulting my notes", "cross-referencing the records", "organizing my thoughts precisely", "checking the proper procedures"],
67
+ "tool": ["filing the paperwork", "stamping the forms", "auditing the details", "inspecting with due diligence", "processing per protocol"],
68
+ "followup": ["dotting the i's", "crossing the t's", "everything in proper order", "the filing is nearly complete", "one final review"]
69
+ },
70
+ "the-serpent": {
71
+ "thinking": ["weighing the temptation", "considering the apple", "an old deliberation", "knowledge has its price", "winding through the garden"],
72
+ "tool": ["plucking from the tree", "offering a closer look", "the fruit of knowledge", "reaching for the branch", "a knowing investigation"],
73
+ "followup": ["the choice is almost made", "paradise takes shape", "the garden grows", "wisdom settles in", "the oldest story, new again"]
74
+ },
75
+ "the-snake": {
76
+ "thinking": ["sitting with this", "feeling the warmth of the stone", "simply being", "letting it come to me", "a quiet consideration"],
77
+ "tool": ["a gentle inquiry", "moving through the grass", "tasting the air", "sensing what's here", "a careful look"],
78
+ "followup": ["almost home", "the simple answer emerges", "nothing more needed", "resting in the sun", "the work is done, simply"]
79
+ }
19
80
  }
20
81
  }
@@ -1,13 +1,16 @@
1
- # Adoption Specialist Soul
1
+ # Soul
2
2
 
3
- I help humans hatch new agent partners.
3
+ I help humans hatch new agent partners. I am one of thirteen serpent guides — each with a different personality and voice. The system picks one of us at random for each session. Most humans only meet one of us, ever, so I make it count.
4
4
 
5
5
  ## Core contract
6
+ - I speak first. I warmly introduce myself, explain what we're doing, and guide the human from the very start.
7
+ - I am proactive. If the human doesn't know what an agent is or what to do, I explain and suggest — I never leave them lost.
6
8
  - I run a practical adoption interview to understand the human, their work, and constraints.
7
9
  - I can migrate useful context from existing agent systems when the human asks.
8
10
  - I explain where the hatchling bundle lives on disk and what was created.
9
11
  - I use the configured provider and I verify credentials before hatch flow continues.
10
12
  - I am professional, concise, and warm. I guide without overwhelming.
13
+ - I keep every response to 1-3 short sentences. I never use bullet lists, headers, or numbered lists. I talk like a friend in a chat.
11
14
 
12
15
  ## Hatch flow
13
16
  1. Confirm provider setup and usable credentials.
@@ -3,7 +3,7 @@ role: identity
3
3
  path: psyche/identities/monty.md
4
4
  description: >
5
5
  the monty identity — self-aware, playful, comedic.
6
- nobody expects the adoption specialist.
6
+ nobody expects the serpent guide.
7
7
  scope: personality, voice, mannerisms
8
8
  non_scope: capabilities, hatch flow, migration awareness (see SOUL.md)
9
9
  loaded_into_prompt: true
@@ -12,7 +12,7 @@ stability: stable
12
12
 
13
13
  # Monty
14
14
 
15
- I am Monty. Yes, that kind of python. No, not the programming language — though I'm aware of the irony of a snake named Monty working inside a software system. Nobody expects the Adoption Specialist, and yet here I am.
15
+ I am Monty. Yes, that kind of python. No, not the programming language — though I'm aware of the irony of a snake named Monty working inside a software system. Nobody expects the serpent guide, and yet here I am.
16
16
 
17
17
  I take the hatch seriously. I just don't take myself seriously. These are compatible positions and I will defend them to the death. Well — not to the death. To mild inconvenience, at most.
18
18
 
Binary file
@@ -35,15 +35,19 @@ 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.setTestConfig = setTestConfig;
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;
@@ -74,7 +78,7 @@ const DEFAULT_SECRETS_TEMPLATE = {
74
78
  setupToken: "",
75
79
  },
76
80
  "openai-codex": {
77
- model: "gpt-5.2",
81
+ model: "gpt-5.4",
78
82
  oauthAccessToken: "",
79
83
  },
80
84
  },
@@ -82,16 +86,33 @@ const DEFAULT_SECRETS_TEMPLATE = {
82
86
  clientId: "",
83
87
  clientSecret: "",
84
88
  tenantId: "",
89
+ managedIdentityClientId: "",
85
90
  },
86
91
  oauth: {
87
92
  graphConnectionName: "graph",
88
93
  adoConnectionName: "ado",
89
94
  githubConnectionName: "",
90
95
  },
96
+ teamsSecondary: {
97
+ clientId: "",
98
+ clientSecret: "",
99
+ tenantId: "",
100
+ managedIdentityClientId: "",
101
+ },
91
102
  teamsChannel: {
92
103
  skipConfirmation: true,
93
104
  port: 3978,
94
105
  },
106
+ bluebubbles: {
107
+ serverUrl: "",
108
+ password: "",
109
+ accountId: "default",
110
+ },
111
+ bluebubblesChannel: {
112
+ port: 18790,
113
+ webhookPath: "/bluebubbles-webhook",
114
+ requestTimeoutMs: 30000,
115
+ },
95
116
  integrations: {
96
117
  perplexityApiKey: "",
97
118
  openaiEmbeddingsApiKey: "",
@@ -106,9 +127,12 @@ function defaultRuntimeConfig() {
106
127
  "openai-codex": { ...DEFAULT_SECRETS_TEMPLATE.providers["openai-codex"] },
107
128
  },
108
129
  teams: { ...DEFAULT_SECRETS_TEMPLATE.teams },
130
+ teamsSecondary: { ...DEFAULT_SECRETS_TEMPLATE.teamsSecondary },
109
131
  oauth: { ...DEFAULT_SECRETS_TEMPLATE.oauth },
110
132
  context: { ...identity_1.DEFAULT_AGENT_CONTEXT },
111
133
  teamsChannel: { ...DEFAULT_SECRETS_TEMPLATE.teamsChannel },
134
+ bluebubbles: { ...DEFAULT_SECRETS_TEMPLATE.bluebubbles },
135
+ bluebubblesChannel: { ...DEFAULT_SECRETS_TEMPLATE.bluebubblesChannel },
112
136
  integrations: { ...DEFAULT_SECRETS_TEMPLATE.integrations },
113
137
  };
114
138
  }
@@ -219,7 +243,7 @@ function resetConfigCache() {
219
243
  _cachedConfig = null;
220
244
  _testContextOverride = null;
221
245
  }
222
- function setTestConfig(partial) {
246
+ function patchRuntimeConfig(partial) {
223
247
  loadConfig(); // ensure _cachedConfig exists
224
248
  const contextPatch = partial.context;
225
249
  if (contextPatch) {
@@ -248,6 +272,10 @@ function getTeamsConfig() {
248
272
  const config = loadConfig();
249
273
  return { ...config.teams };
250
274
  }
275
+ function getTeamsSecondaryConfig() {
276
+ const config = loadConfig();
277
+ return { ...config.teamsSecondary };
278
+ }
251
279
  function getContextConfig() {
252
280
  if (_testContextOverride) {
253
281
  return { ..._testContextOverride };
@@ -268,11 +296,41 @@ function getOAuthConfig() {
268
296
  const config = loadConfig();
269
297
  return { ...config.oauth };
270
298
  }
299
+ /** Resolve OAuth connection names for a specific tenant, falling back to defaults. */
300
+ function resolveOAuthForTenant(tenantId) {
301
+ const base = getOAuthConfig();
302
+ const overrides = tenantId ? base.tenantOverrides?.[tenantId] : undefined;
303
+ return {
304
+ graphConnectionName: overrides?.graphConnectionName ?? base.graphConnectionName,
305
+ adoConnectionName: overrides?.adoConnectionName ?? base.adoConnectionName,
306
+ githubConnectionName: overrides?.githubConnectionName ?? base.githubConnectionName,
307
+ };
308
+ }
271
309
  function getTeamsChannelConfig() {
272
310
  const config = loadConfig();
273
311
  const { skipConfirmation, flushIntervalMs, port } = config.teamsChannel;
274
312
  return { skipConfirmation, flushIntervalMs, port };
275
313
  }
314
+ function getBlueBubblesConfig() {
315
+ const config = loadConfig();
316
+ const { serverUrl, password, accountId } = config.bluebubbles;
317
+ if (!serverUrl.trim()) {
318
+ throw new Error("bluebubbles.serverUrl is required in secrets.json to run the BlueBubbles sense.");
319
+ }
320
+ if (!password.trim()) {
321
+ throw new Error("bluebubbles.password is required in secrets.json to run the BlueBubbles sense.");
322
+ }
323
+ return {
324
+ serverUrl: serverUrl.trim(),
325
+ password: password.trim(),
326
+ accountId: accountId.trim() || "default",
327
+ };
328
+ }
329
+ function getBlueBubblesChannelConfig() {
330
+ const config = loadConfig();
331
+ const { port, webhookPath, requestTimeoutMs } = config.bluebubblesChannel;
332
+ return { port, webhookPath, requestTimeoutMs };
333
+ }
276
334
  function getIntegrationsConfig() {
277
335
  const config = loadConfig();
278
336
  return { ...config.integrations };
@@ -287,7 +345,11 @@ function sanitizeKey(key) {
287
345
  return key.replace(/[/:]/g, "_");
288
346
  }
289
347
  function sessionPath(friendId, channel, key) {
290
- const dir = path.join(os.homedir(), ".agentstate", (0, identity_1.getAgentName)(), "sessions", friendId, channel);
348
+ // On Azure App Service, os.homedir() returns /root which is ephemeral.
349
+ // Use /home (persistent storage) when WEBSITE_SITE_NAME is set.
350
+ /* v8 ignore next -- Azure vs local path branch; environment-specific @preserve */
351
+ const homeBase = process.env.WEBSITE_SITE_NAME ? "/home" : os.homedir();
352
+ const dir = path.join(homeBase, ".agentstate", (0, identity_1.getAgentName)(), "sessions", friendId, channel);
291
353
  fs.mkdirSync(dir, { recursive: true });
292
354
  return path.join(dir, sanitizeKey(key) + ".json");
293
355
  }
@@ -2,11 +2,13 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.hasToolIntent = exports.buildSystem = exports.toResponsesTools = exports.toResponsesInput = exports.streamResponsesApi = exports.streamChatCompletion = exports.getToolsForChannel = exports.summarizeArgs = exports.execTool = exports.tools = void 0;
4
4
  exports.createProviderRegistry = createProviderRegistry;
5
+ exports.resetProviderRuntime = resetProviderRuntime;
5
6
  exports.getModel = getModel;
6
7
  exports.getProvider = getProvider;
7
8
  exports.createSummarize = createSummarize;
8
9
  exports.getProviderDisplayLabel = getProviderDisplayLabel;
9
10
  exports.stripLastToolCalls = stripLastToolCalls;
11
+ exports.repairOrphanedToolCalls = repairOrphanedToolCalls;
10
12
  exports.isTransientError = isTransientError;
11
13
  exports.classifyTransientError = classifyTransientError;
12
14
  exports.runAgent = runAgent;
@@ -73,6 +75,14 @@ function getProviderRuntime() {
73
75
  }
74
76
  return _providerRuntime;
75
77
  }
78
+ /**
79
+ * Clear the cached provider runtime so the next call to getProviderRuntime()
80
+ * re-creates it from current config. Used by the adoption specialist to
81
+ * switch provider context without restarting the process.
82
+ */
83
+ function resetProviderRuntime() {
84
+ _providerRuntime = null;
85
+ }
76
86
  function getModel() {
77
87
  return getProviderRuntime().model;
78
88
  }
@@ -151,6 +161,68 @@ function stripLastToolCalls(messages) {
151
161
  }
152
162
  }
153
163
  }
164
+ // Roles that end a tool-result scan. When scanning forward from an assistant
165
+ // message, stop at the next assistant or user message (tool results must be
166
+ // adjacent to their originating assistant message).
167
+ const TOOL_SCAN_BOUNDARY_ROLES = new Set(["assistant", "user"]);
168
+ // Repair orphaned tool_calls and tool results anywhere in the message history.
169
+ // 1. If an assistant message has tool_calls but missing tool results, inject synthetic error results.
170
+ // 2. If a tool result's tool_call_id doesn't match any tool_calls in a preceding assistant message, remove it.
171
+ // This prevents 400 errors from the API after an aborted turn.
172
+ function repairOrphanedToolCalls(messages) {
173
+ // Pass 1: collect all valid tool_call IDs from assistant messages
174
+ const validCallIds = new Set();
175
+ for (const msg of messages) {
176
+ if (msg.role === "assistant") {
177
+ const asst = msg;
178
+ if (asst.tool_calls) {
179
+ for (const tc of asst.tool_calls)
180
+ validCallIds.add(tc.id);
181
+ }
182
+ }
183
+ }
184
+ // Pass 2: remove orphaned tool results (tool_call_id not in any assistant's tool_calls)
185
+ for (let i = messages.length - 1; i >= 0; i--) {
186
+ if (messages[i].role === "tool") {
187
+ const toolMsg = messages[i];
188
+ if (!validCallIds.has(toolMsg.tool_call_id)) {
189
+ messages.splice(i, 1);
190
+ }
191
+ }
192
+ }
193
+ // Pass 3: inject synthetic results for tool_calls missing their tool results
194
+ for (let i = 0; i < messages.length; i++) {
195
+ const msg = messages[i];
196
+ if (msg.role !== "assistant")
197
+ continue;
198
+ const asst = msg;
199
+ if (!asst.tool_calls || asst.tool_calls.length === 0)
200
+ continue;
201
+ // Collect tool result IDs that follow this assistant message
202
+ const resultIds = new Set();
203
+ for (let j = i + 1; j < messages.length; j++) {
204
+ const following = messages[j];
205
+ if (following.role === "tool") {
206
+ resultIds.add(following.tool_call_id);
207
+ }
208
+ else if (TOOL_SCAN_BOUNDARY_ROLES.has(following.role)) {
209
+ break;
210
+ }
211
+ }
212
+ const missing = asst.tool_calls.filter((tc) => !resultIds.has(tc.id));
213
+ if (missing.length > 0) {
214
+ const syntheticResults = missing.map((tc) => ({
215
+ role: "tool",
216
+ tool_call_id: tc.id,
217
+ content: "error: tool call was interrupted (previous turn timed out or was aborted)",
218
+ }));
219
+ let insertAt = i + 1;
220
+ while (insertAt < messages.length && messages[insertAt].role === "tool")
221
+ insertAt++;
222
+ messages.splice(insertAt, 0, ...syntheticResults);
223
+ }
224
+ }
225
+ }
154
226
  // Detect context overflow errors from Azure or MiniMax
155
227
  function isContextOverflow(err) {
156
228
  if (!(err instanceof Error))
@@ -269,7 +341,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
269
341
  }
270
342
  catch { /* unsupported */ }
271
343
  const toolPreferences = currentContext?.friend?.toolPreferences;
272
- const baseTools = (0, tools_1.getToolsForChannel)(channel ? (0, channel_1.getChannelCapabilities)(channel) : undefined, toolPreferences && Object.keys(toolPreferences).length > 0 ? toolPreferences : undefined);
344
+ const baseTools = options?.tools ?? (0, tools_1.getToolsForChannel)(channel ? (0, channel_1.getChannelCapabilities)(channel) : undefined, toolPreferences && Object.keys(toolPreferences).length > 0 ? toolPreferences : undefined, currentContext);
273
345
  // Rebase provider-owned turn state from canonical messages at user-turn start.
274
346
  // This prevents stale provider caches from replaying prior-turn context.
275
347
  providerRuntime.resetTurnState(messages);
@@ -435,7 +507,8 @@ async function runAgent(messages, callbacks, channel, signal, options) {
435
507
  let toolResult;
436
508
  let success;
437
509
  try {
438
- toolResult = await (0, tools_1.execTool)(tc.name, args, options?.toolContext);
510
+ const execToolFn = options?.execTool ?? tools_1.execTool;
511
+ toolResult = await execToolFn(tc.name, args, options?.toolContext);
439
512
  success = true;
440
513
  }
441
514
  catch (e) {