zidane 4.1.8 → 5.0.0

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 (66) hide show
  1. package/README.md +11 -2
  2. package/dist/{index-bgh-k8Mv.d.ts → agent-JhicgLOV.d.ts} +2082 -1969
  3. package/dist/agent-JhicgLOV.d.ts.map +1 -0
  4. package/dist/chat.d.ts +340 -9
  5. package/dist/chat.d.ts.map +1 -1
  6. package/dist/chat.js +2 -2
  7. package/dist/contexts.d.ts +1 -1
  8. package/dist/{index-DRoG_udt.d.ts → index-2yLUyTbc.d.ts} +34 -4
  9. package/dist/{index-DRoG_udt.d.ts.map → index-2yLUyTbc.d.ts.map} +1 -1
  10. package/dist/{index-BB4kuRh3.d.ts → index-CXVvqTQj.d.ts} +1 -1
  11. package/dist/{index-BB4kuRh3.d.ts.map → index-CXVvqTQj.d.ts.map} +1 -1
  12. package/dist/{index-Ds5YpvfZ.d.ts → index-t_W9i7Ql.d.ts} +9 -4
  13. package/dist/index-t_W9i7Ql.d.ts.map +1 -0
  14. package/dist/index.d.ts +4 -4
  15. package/dist/index.js +6 -6
  16. package/dist/{interpolate-CukJwP2G.js → interpolate-Ck970-61.js} +11 -2
  17. package/dist/interpolate-Ck970-61.js.map +1 -0
  18. package/dist/{mcp-8wClKY-3.js → mcp-Dw-fRPVk.js} +61 -65
  19. package/dist/mcp-Dw-fRPVk.js.map +1 -0
  20. package/dist/mcp.d.ts +1 -1
  21. package/dist/mcp.js +1 -1
  22. package/dist/presets-BRFH2qsQ.js +90 -0
  23. package/dist/presets-BRFH2qsQ.js.map +1 -0
  24. package/dist/presets.d.ts +3 -2
  25. package/dist/presets.js +2 -2
  26. package/dist/providers.d.ts +1 -1
  27. package/dist/session/sqlite.d.ts +13 -2
  28. package/dist/session/sqlite.d.ts.map +1 -1
  29. package/dist/session/sqlite.js +96 -38
  30. package/dist/session/sqlite.js.map +1 -1
  31. package/dist/{session-Cn68UASv.js → session-791hhrFa.js} +65 -30
  32. package/dist/session-791hhrFa.js.map +1 -0
  33. package/dist/session.d.ts +1 -1
  34. package/dist/session.js +1 -1
  35. package/dist/skills.d.ts +2 -2
  36. package/dist/skills.js +1 -1
  37. package/dist/{stats-BT9l57RS.js → stats-DZIsGqzu.js} +15 -5
  38. package/dist/stats-DZIsGqzu.js.map +1 -0
  39. package/dist/theme-pJv47erq.d.ts +1202 -0
  40. package/dist/theme-pJv47erq.d.ts.map +1 -0
  41. package/dist/{tools-C8kDot0H.js → tools-CLazLRb4.js} +475 -318
  42. package/dist/tools-CLazLRb4.js.map +1 -0
  43. package/dist/tools.d.ts +2 -2
  44. package/dist/tools.js +1 -1
  45. package/dist/tui.d.ts +303 -18
  46. package/dist/tui.d.ts.map +1 -1
  47. package/dist/tui.js +3305 -509
  48. package/dist/tui.js.map +1 -1
  49. package/dist/turn-operations-5aQu4dJg.js +3587 -0
  50. package/dist/turn-operations-5aQu4dJg.js.map +1 -0
  51. package/dist/types.d.ts +3 -3
  52. package/dist/types.js +1 -1
  53. package/package.json +6 -1
  54. package/dist/index-Ds5YpvfZ.d.ts.map +0 -1
  55. package/dist/index-bgh-k8Mv.d.ts.map +0 -1
  56. package/dist/interpolate-CukJwP2G.js.map +0 -1
  57. package/dist/mcp-8wClKY-3.js.map +0 -1
  58. package/dist/presets-BzkJDW1K.js +0 -39
  59. package/dist/presets-BzkJDW1K.js.map +0 -1
  60. package/dist/session-Cn68UASv.js.map +0 -1
  61. package/dist/stats-BT9l57RS.js.map +0 -1
  62. package/dist/theme-BlXO6yHe.d.ts +0 -503
  63. package/dist/theme-BlXO6yHe.d.ts.map +0 -1
  64. package/dist/theme-context-MungM3SY.js +0 -1713
  65. package/dist/theme-context-MungM3SY.js.map +0 -1
  66. package/dist/tools-C8kDot0H.js.map +0 -1
@@ -1,9 +1,9 @@
1
1
  import { n as createProcessContext } from "./contexts-3Arvn7yR.js";
2
2
  import { r as AgentProviderError, s as toTypedError, t as AgentAbortedError } from "./errors-D1lhd6mX.js";
3
3
  import { t as toolOutputByteLength } from "./types-Bx_F8jet.js";
4
- import { t as connectMcpServers } from "./mcp-8wClKY-3.js";
5
- import { _ as validateResourcePath, b as createSkillActivationState, d as escapeXml, n as resolveSkills, p as installAllowedToolsGate, t as interpolateShellCommands, u as buildCatalog } from "./interpolate-CukJwP2G.js";
6
- import { n as formatTokenUsage, t as flattenTurns } from "./stats-BT9l57RS.js";
4
+ import { t as connectMcpServers } from "./mcp-Dw-fRPVk.js";
5
+ import { _ as validateResourcePath, b as createSkillActivationState, d as escapeXml, n as resolveSkills, p as installAllowedToolsGate, t as interpolateShellCommands, u as buildCatalog } from "./interpolate-Ck970-61.js";
6
+ import { n as formatTokenUsage, t as flattenTurns } from "./stats-DZIsGqzu.js";
7
7
  import { createHooks } from "hookable";
8
8
  import { stat } from "node:fs/promises";
9
9
  import { resolve } from "node:path";
@@ -892,12 +892,19 @@ async function executeTurn(ctx, turn) {
892
892
  }
893
893
  const toolResults = ctx.toolExecution === "parallel" ? await executeToolsParallel(ctx, canonicalToolCalls, turnId) : await executeToolsSequential(ctx, canonicalToolCalls, turnId);
894
894
  const toolResultMsg = ctx.provider.toolResultsMessage(toolResults);
895
- ctx.turns.push({
895
+ const toolResultsTurn = {
896
896
  id: await ctx.generateTurnId(),
897
897
  runId: ctx.runId,
898
898
  role: toolResultMsg.role,
899
899
  content: toolResultMsg.content,
900
900
  createdAt: Date.now()
901
+ };
902
+ ctx.turns.push(toolResultsTurn);
903
+ await ctx.hooks.callHook("tool-results:after", {
904
+ turn,
905
+ turnId,
906
+ message: toolResultsTurn,
907
+ results: toolResults
901
908
  });
902
909
  if (typeof ctx.toolOutputBudget === "number" && ctx.toolOutputBudget > 0) {
903
910
  const totalBytes = toolResults.reduce((sum, r) => sum + toolOutputByteLength(r.content), 0);
@@ -1151,8 +1158,21 @@ async function executeToolsSequential(ctx, toolCalls, turnId) {
1151
1158
  });
1152
1159
  return results;
1153
1160
  }
1154
- const { result } = await executeSingleTool(ctx, call, turnId);
1155
- results.push(result);
1161
+ try {
1162
+ const { result } = await executeSingleTool(ctx, call, turnId);
1163
+ results.push(result);
1164
+ } catch (err) {
1165
+ const message = err instanceof Error ? err.message : String(err);
1166
+ results.push({
1167
+ id: call.id,
1168
+ content: `Error: ${message}`
1169
+ });
1170
+ for (let j = i + 1; j < toolCalls.length; j++) results.push({
1171
+ id: toolCalls[j].id,
1172
+ content: "Skipped: previous tool call threw"
1173
+ });
1174
+ return results;
1175
+ }
1156
1176
  }
1157
1177
  return results;
1158
1178
  }
@@ -1737,6 +1757,7 @@ const HOOK_EVENT_SET = new Set([
1737
1757
  "system:before",
1738
1758
  "turn:before",
1739
1759
  "turn:after",
1760
+ "tool-results:after",
1740
1761
  "stream:text",
1741
1762
  "stream:end",
1742
1763
  "stream:thinking",
@@ -1794,6 +1815,45 @@ const HOOK_EVENT_SET = new Set([
1794
1815
  function isKnownHookEvent(event) {
1795
1816
  return HOOK_EVENT_SET.has(event);
1796
1817
  }
1818
+ /**
1819
+ * If the trailing assistant turn in `turns` carries `tool_call` blocks with
1820
+ * no matching `tool_result` in a following user turn, mutate `turns` in
1821
+ * place to append a synthetic tool-results turn that closes every dangling
1822
+ * tool_use id. Used by the error path of `agent.run` to prevent a thrown
1823
+ * loop from leaving the persisted session with an orphan tool_use — which
1824
+ * Anthropic rejects on resume.
1825
+ *
1826
+ * No-op when:
1827
+ * - The trailing turn isn't an assistant turn (already closed, or session
1828
+ * ends with the seeded user prompt).
1829
+ * - The assistant turn has no `tool_call` blocks.
1830
+ * - All tool_use ids are already answered by a tool_result somewhere later
1831
+ * in the conversation (defensive — shouldn't happen but cheap to check).
1832
+ */
1833
+ function synthesizeMissingToolResults(turns, syntheticTurnId, runId, provider) {
1834
+ if (turns.length === 0) return;
1835
+ const last = turns[turns.length - 1];
1836
+ if (last.role !== "assistant") return;
1837
+ const pendingIds = [];
1838
+ for (const block of last.content) if (block.type === "tool_call") pendingIds.push(block.id);
1839
+ if (pendingIds.length === 0) return;
1840
+ const answered = /* @__PURE__ */ new Set();
1841
+ for (const turn of turns.slice(0, -1)) for (const block of turn.content) if (block.type === "tool_result") answered.add(block.callId);
1842
+ const dangling = pendingIds.filter((id) => !answered.has(id));
1843
+ if (dangling.length === 0) return;
1844
+ const results = dangling.map((id) => ({
1845
+ id,
1846
+ content: "Aborted: run failed before tool execution completed"
1847
+ }));
1848
+ const msg = provider.toolResultsMessage(results);
1849
+ turns.push({
1850
+ id: syntheticTurnId,
1851
+ runId,
1852
+ role: msg.role,
1853
+ content: msg.content,
1854
+ createdAt: Date.now()
1855
+ });
1856
+ }
1797
1857
  function resolveBehavior(agentBehavior, runBehavior) {
1798
1858
  return {
1799
1859
  toolExecution: runBehavior?.toolExecution ?? agentBehavior?.toolExecution ?? "parallel",
@@ -1936,10 +1996,43 @@ function installLazyDisclosureGate(hooks, lazyCanonicalNames, unlocked, discover
1936
1996
  ctx.reason = discoveryToolName ? `Tool "${ctx.name}" is listed in <searchable_tools> but its schema has not been loaded. Call the \`${discoveryToolName}\` tool with names: ["${ctx.name}"] first, then re-issue the call.` : `Tool "${ctx.name}" is listed in <searchable_tools> but its schema has not been loaded.`;
1937
1997
  });
1938
1998
  }
1939
- function createAgent({ provider, name: agentName, system: agentSystem, tools: agentTools, toolAliases, behavior: agentBehavior, execution, mcpServers, session, skills: agentSkills, mcpConnector, eager }) {
1999
+ /**
2000
+ * Pick the next safe value for `runCounter` so `run_${++counter}` mints
2001
+ * an id that doesn't collide with any runId already referenced by the
2002
+ * session — whether the runId lives in `session.runs` or only in
2003
+ * `session.turns[].runId`. The regex matches the canonical `run_<int>`
2004
+ * shape minted by this module; any caller-supplied custom id schemes
2005
+ * are ignored (they don't conflict with `run_N`).
2006
+ *
2007
+ * Returning 0 for a sessionless / clean session preserves the original
2008
+ * "first run is run_1" semantics.
2009
+ */
2010
+ function initialRunCounter(session) {
2011
+ if (!session) return 0;
2012
+ let max = 0;
2013
+ const consider = (id) => {
2014
+ if (!id) return;
2015
+ const m = /^run_(\d+)$/.exec(id);
2016
+ if (!m) return;
2017
+ const n = Number.parseInt(m[1], 10);
2018
+ if (Number.isFinite(n) && n > max) max = n;
2019
+ };
2020
+ for (const r of session.runs) consider(r.id);
2021
+ for (const t of session.turns) consider(t.runId);
2022
+ return max;
2023
+ }
2024
+ function createAgent({ provider, name: agentName, system: agentSystem, tools: agentTools, toolAliases, behavior: agentBehavior, execution, mcpServers, session, skills: agentSkills, mcpConnector, eager, hooks: initialHooks }) {
1940
2025
  const hooks = createHooks();
1941
2026
  const executionContext = execution ?? createProcessContext();
1942
2027
  const sourceTools = agentTools ?? {};
2028
+ if (initialHooks) for (const [event, handler] of Object.entries(initialHooks)) {
2029
+ if (!isKnownHookEvent(event)) throw new Error(`Unknown hook event "${event}" passed to createAgent(). See AgentHooks for valid events.`);
2030
+ const handlerList = Array.isArray(handler) ? handler : [handler];
2031
+ for (const fn of handlerList) {
2032
+ if (typeof fn !== "function") continue;
2033
+ hooks.hook(event, fn);
2034
+ }
2035
+ }
1943
2036
  let abortController;
1944
2037
  let running = false;
1945
2038
  let idleResolve;
@@ -1951,354 +2044,397 @@ function createAgent({ provider, name: agentName, system: agentSystem, tools: ag
1951
2044
  const steeringQueue = [];
1952
2045
  const followUpQueue = [];
1953
2046
  let conversationTurns = session?.turns.slice() ?? [];
1954
- let runCounter = session?.runs.length ?? 0;
2047
+ let runCounter = initialRunCounter(session);
1955
2048
  const skillsConfig = agentSkills;
1956
2049
  const skillsEnabledValue = skillsConfig?.enabled;
1957
2050
  const skillsDisabled = skillsEnabledValue === false || Array.isArray(skillsEnabledValue) && skillsEnabledValue.length === 0;
1958
2051
  let resolvedSkills = null;
1959
2052
  let skillsCatalog = null;
2053
+ let skillsResolvePromise = null;
1960
2054
  let skillsCleanup = () => {};
1961
- const skillActivationState = createSkillActivationState({ maxActive: skillsConfig?.maxActive });
1962
- async function run(options) {
1963
- if (running) throw new Error("Agent is already running. Use steer() or followUp() to queue messages, or waitForIdle().");
1964
- const hasSessionTurns = session && session.turns.length > 0;
1965
- if (!options.prompt && !hasSessionTurns) throw new Error("prompt is required when no session with existing turns is provided");
1966
- if (!options.prompt && hasSessionTurns) {
1967
- const lastTurn = session.turns.at(-1);
1968
- if (lastTurn && lastTurn.role !== "user") throw new Error("cannot resume without prompt: last session turn must be a user message");
1969
- }
1970
- running = true;
1971
- abortController = new AbortController();
1972
- const runId = `run_${++runCounter}`;
1973
- const promptLabel = typeof options.prompt === "string" ? options.prompt : Array.isArray(options.prompt) ? options.prompt.filter((p) => p.type === "text").map((p) => p.text).join("\n") : "";
1974
- session?.startRun(runId, promptLabel, {
1975
- ...options.parentRunId ? { parentRunId: options.parentRunId } : {},
1976
- depth: typeof options.depth === "number" ? options.depth : 0
1977
- });
1978
- if (session) {
1979
- await session.updateStatus("running");
1980
- await hooks.callHook("session:start", {
1981
- sessionId: session.id,
1982
- runId,
1983
- prompt: promptLabel
1984
- });
1985
- }
1986
- if (options.signal) if (options.signal.aborted) abortController.abort();
1987
- else {
1988
- const onExternalAbort = () => abortController?.abort();
1989
- options.signal.addEventListener("abort", onExternalAbort, { once: true });
1990
- }
1991
- idlePromise = new Promise((resolve) => {
1992
- idleResolve = resolve;
1993
- });
1994
- const childrenStats = [];
1995
- const unregisterSpawnHook = hooks.hook("spawn:complete", (ctx) => {
1996
- childrenStats.push(ctx);
1997
- });
1998
- const perRunUnregisters = [];
1999
- if (options.hooks) for (const [event, handler] of Object.entries(options.hooks)) {
2000
- if (!isKnownHookEvent(event)) throw new Error(`Unknown hook event "${event}" passed to run(). See AgentHooks for valid events.`);
2001
- const handlerList = Array.isArray(handler) ? handler : [handler];
2002
- for (const fn of handlerList) {
2003
- if (typeof fn !== "function") continue;
2004
- perRunUnregisters.push(hooks.hook(event, fn));
2005
- }
2006
- }
2007
- if (!executionHandle) executionHandle = await executionContext.spawn();
2008
- if (allMcpServers.length > 0 && !mcpConnection) await warmup();
2009
- if (!skillsDisabled && skillsConfig && !resolvedSkills) {
2055
+ /**
2056
+ * Resolve skills once for the lifetime of the agent. Idempotent and
2057
+ * concurrency-safe; no-op when `skills` is disabled or omitted.
2058
+ *
2059
+ * Used by `run()` (lazy bootstrap), `warmup()` (eager bootstrap), and
2060
+ * `activateSkill()` (so the public API doesn't leak the timing of `run()`).
2061
+ * Fires `skills:resolve` (mutable `skills` array) and `skills:catalog`
2062
+ * (mutable `catalog`) in that order exactly once per agent.
2063
+ */
2064
+ async function ensureSkillsResolved() {
2065
+ if (skillsDisabled || !skillsConfig) return;
2066
+ if (resolvedSkills) return;
2067
+ if (skillsResolvePromise) return skillsResolvePromise;
2068
+ skillsResolvePromise = (async () => {
2010
2069
  const bundle = await resolveSkills(skillsConfig);
2011
2070
  resolvedSkills = bundle.skills;
2012
2071
  skillsCleanup = bundle.cleanup;
2013
2072
  await hooks.callHook("skills:resolve", { skills: resolvedSkills });
2014
- const skillsToolRegistered = skillsConfig?.tool !== false && resolvedSkills.length > 0;
2073
+ const skillsToolRegistered = skillsConfig.tool !== false && resolvedSkills.length > 0;
2015
2074
  const catalogCtx = {
2016
2075
  catalog: buildCatalog(resolvedSkills, { skillsToolRegistered }),
2017
2076
  skills: resolvedSkills
2018
2077
  };
2019
2078
  await hooks.callHook("skills:catalog", catalogCtx);
2020
2079
  skillsCatalog = catalogCtx.catalog;
2080
+ })();
2081
+ try {
2082
+ await skillsResolvePromise;
2083
+ } catch (err) {
2084
+ skillsResolvePromise = null;
2085
+ throw err;
2021
2086
  }
2022
- if (resolvedSkills && session && session.turns.length > 0 && skillActivationState.active().length === 0) {
2023
- const skillsByName = new Map(resolvedSkills.map((s) => [s.name, s]));
2024
- for (const turn of session.turns) {
2025
- if (turn.role !== "assistant") continue;
2026
- for (const block of turn.content) {
2027
- if (block.type !== "tool_call" || block.name !== "skills_use") continue;
2028
- const skillName = block.input?.name;
2029
- if (!skillName) continue;
2030
- const skill = skillsByName.get(skillName);
2031
- if (!skill) continue;
2032
- if (skillActivationState.activate(skill, "resume") === "ok") await hooks.callHook("skills:activate", {
2033
- skill,
2034
- via: "resume"
2035
- });
2036
- }
2037
- }
2087
+ }
2088
+ const skillActivationState = createSkillActivationState({ maxActive: skillsConfig?.maxActive });
2089
+ async function run(options) {
2090
+ if (running) throw new Error("Agent is already running. Use steer() or followUp() to queue messages, or waitForIdle().");
2091
+ const hasSessionTurns = session && session.turns.length > 0;
2092
+ if (!options.prompt && !hasSessionTurns) throw new Error("prompt is required when no session with existing turns is provided");
2093
+ if (!options.prompt && hasSessionTurns) {
2094
+ const lastTurn = session.turns.at(-1);
2095
+ if (lastTurn && lastTurn.role !== "user") throw new Error("cannot resume without prompt: last session turn must be a user message");
2038
2096
  }
2039
- const thinking = options.thinking ?? "off";
2040
- const model = options.model ?? provider.meta.defaultModel;
2041
- const resolvedBehavior = resolveBehavior(agentBehavior, options.behavior);
2042
- const { toolExecution, maxTurns, maxTokens, thinkingBudget, schema, cache, toolOutputBudget, compactStrategy, compactThreshold, compactKeepTurns, thinkingDecay, dedupTools, toolBudgets, elideStaleReads, toolDisclosure, toolSearch } = resolvedBehavior;
2043
- let system = options.system || agentSystem || "You are a helpful assistant.";
2044
- if (skillsCatalog) system = `${system}\n\n${skillsCatalog}`;
2045
- const runBaseTools = options.tools !== void 0 ? options.tools : mcpConnection ? {
2046
- ...sourceTools,
2047
- ...mcpConnection.tools
2048
- } : sourceTools;
2049
- const mcpToolNames = options.tools === void 0 && mcpConnection ? new Set(Object.keys(mcpConnection.tools)) : /* @__PURE__ */ new Set();
2050
- const mergedWithSkills = options.tools === void 0 && !!resolvedSkills && resolvedSkills.length > 0 && skillsConfig?.tool !== false ? {
2051
- skills_use: createSkillsUseTool({
2052
- catalog: resolvedSkills,
2053
- state: skillActivationState,
2054
- hooks
2055
- }),
2056
- skills_read: createSkillsReadTool({
2057
- catalog: resolvedSkills,
2058
- state: skillActivationState
2059
- }),
2060
- skills_run_script: createSkillsRunScriptTool({
2061
- catalog: resolvedSkills,
2062
- state: skillActivationState,
2063
- scriptTimeoutMs: skillsConfig?.scriptTimeoutMs
2064
- }),
2065
- ...runBaseTools
2066
- } : runBaseTools;
2067
- const toolsPreSearch = {};
2068
- for (const tool of Object.values(mergedWithSkills)) toolsPreSearch[tool.spec.name] = tool;
2069
- const disclosure = partitionToolDisclosure(toolsPreSearch, mcpToolNames, mcpServers, toolDisclosure, toolAliases);
2070
- const unlocked = new Set(disclosure.eagerCanonicalNames);
2071
- const hostDefinedToolSearch = !!toolsPreSearch.tool_search;
2072
- const shouldInjectToolSearch = disclosure.lazyEntries.length > 0 && toolSearch?.tool !== false && !hostDefinedToolSearch;
2073
- let tools = toolsPreSearch;
2074
- if (shouldInjectToolSearch) {
2075
- const toolSearchTool = createToolSearchTool({
2076
- catalog: disclosure.lazyEntries,
2077
- unlocked,
2078
- ...toolSearch?.limit !== void 0 ? { defaultLimit: toolSearch.limit } : {}
2097
+ let externalAbortListener;
2098
+ const externalSignal = options.signal;
2099
+ running = true;
2100
+ try {
2101
+ abortController = new AbortController();
2102
+ const runId = `run_${++runCounter}`;
2103
+ const promptLabel = typeof options.prompt === "string" ? options.prompt : Array.isArray(options.prompt) ? options.prompt.filter((p) => p.type === "text").map((p) => p.text).join("\n") : "";
2104
+ session?.startRun(runId, promptLabel, {
2105
+ ...options.parentRunId ? { parentRunId: options.parentRunId } : {},
2106
+ depth: typeof options.depth === "number" ? options.depth : 0
2079
2107
  });
2080
- tools = {
2081
- ...toolsPreSearch,
2082
- [toolSearchTool.spec.name]: toolSearchTool
2083
- };
2084
- unlocked.add(toolSearchTool.spec.name);
2085
- }
2086
- const discoveryToolName = shouldInjectToolSearch ? "tool_search" : hostDefinedToolSearch ? toolAliases?.tool_search ?? "tool_search" : null;
2087
- if (disclosure.lazyEntries.length > 0) system = `${system}\n\n${buildSearchableCatalog(disclosure.lazyEntries, { discoveryToolName })}`;
2088
- const aliasMaps = buildAliasMaps(toolAliases, Object.keys(tools));
2089
- const uninstallLazyDisclosureGate = installLazyDisclosureGate(hooks, disclosure.lazyCanonicalNames, unlocked, discoveryToolName);
2090
- function buildFormattedTools() {
2091
- const specs = [];
2092
- for (const t of Object.values(tools)) {
2093
- if (!unlocked.has(t.spec.name)) continue;
2094
- specs.push({
2095
- name: aliasMaps.aliasByCanonical.get(t.spec.name) ?? t.spec.name,
2096
- description: t.spec.description || "",
2097
- inputSchema: t.spec.inputSchema
2108
+ if (session) {
2109
+ await session.updateStatus("running");
2110
+ await hooks.callHook("session:start", {
2111
+ sessionId: session.id,
2112
+ runId,
2113
+ prompt: promptLabel
2098
2114
  });
2099
2115
  }
2100
- return specs.length > 0 ? provider.formatTools(specs) : [];
2101
- }
2102
- const formattedTools = buildFormattedTools();
2103
- const turns = [];
2104
- const isResume = session && session.turns.length > 0 && (session.runs.length > 0 || !options.prompt) && !options.parentRunId;
2105
- if (isResume) {
2106
- const childRunIds = new Set(session.runs.filter((r) => (r.depth ?? 0) > 0).map((r) => r.id));
2107
- const resumed = childRunIds.size === 0 ? session.turns : session.turns.filter((t) => !t.runId || !childRunIds.has(t.runId));
2108
- turns.push(...resumed);
2109
- }
2110
- const runTurnStart = turns.length;
2111
- if (options.system) await hooks.callHook("system:before", { system: options.system });
2112
- const promptParts = canonicalizePrompt(options.prompt);
2113
- if (promptParts) {
2114
- const promptMsg = buildPromptMessage(provider, promptParts);
2115
- turns.push({
2116
- id: crypto.randomUUID(),
2117
- runId,
2118
- role: promptMsg.role,
2119
- content: promptMsg.content,
2120
- createdAt: Date.now()
2116
+ if (externalSignal) if (externalSignal.aborted) abortController.abort();
2117
+ else {
2118
+ externalAbortListener = () => abortController?.abort();
2119
+ externalSignal.addEventListener("abort", externalAbortListener, { once: true });
2120
+ }
2121
+ idlePromise = new Promise((resolve) => {
2122
+ idleResolve = resolve;
2121
2123
  });
2122
- }
2123
- conversationTurns = turns;
2124
- let lastPersistedTurnCount = isResume ? session.turns.length : 0;
2125
- if (session && turns.length > lastPersistedTurnCount) {
2126
- const seededTurns = turns.slice(lastPersistedTurnCount);
2127
- await session.appendTurns(seededTurns);
2128
- lastPersistedTurnCount = turns.length;
2129
- await hooks.callHook("session:turns", {
2130
- sessionId: session.id,
2131
- turns: seededTurns,
2132
- count: turns.length
2124
+ const childrenStats = [];
2125
+ const unregisterSpawnHook = hooks.hook("spawn:complete", (ctx) => {
2126
+ childrenStats.push(ctx);
2133
2127
  });
2134
- }
2135
- const unregisterSessionSync = session ? hooks.hook("turn:after", async () => {
2136
- const newTurns = turns.slice(lastPersistedTurnCount);
2137
- if (newTurns.length > 0) {
2138
- await session.appendTurns(newTurns);
2128
+ const perRunUnregisters = [];
2129
+ if (options.hooks) for (const [event, handler] of Object.entries(options.hooks)) {
2130
+ if (!isKnownHookEvent(event)) throw new Error(`Unknown hook event "${event}" passed to run(). See AgentHooks for valid events.`);
2131
+ const handlerList = Array.isArray(handler) ? handler : [handler];
2132
+ for (const fn of handlerList) {
2133
+ if (typeof fn !== "function") continue;
2134
+ perRunUnregisters.push(hooks.hook(event, fn));
2135
+ }
2136
+ }
2137
+ if (!executionHandle) executionHandle = await executionContext.spawn();
2138
+ if (allMcpServers.length > 0 && !mcpConnection) await warmup();
2139
+ await ensureSkillsResolved();
2140
+ if (resolvedSkills && session && session.turns.length > 0 && skillActivationState.active().length === 0) {
2141
+ const skillsByName = new Map(resolvedSkills.map((s) => [s.name, s]));
2142
+ for (const turn of session.turns) {
2143
+ if (turn.role !== "assistant") continue;
2144
+ for (const block of turn.content) {
2145
+ if (block.type !== "tool_call" || block.name !== "skills_use") continue;
2146
+ const skillName = block.input?.name;
2147
+ if (!skillName) continue;
2148
+ const skill = skillsByName.get(skillName);
2149
+ if (!skill) continue;
2150
+ if (skillActivationState.activate(skill, "resume") === "ok") await hooks.callHook("skills:activate", {
2151
+ skill,
2152
+ via: "resume"
2153
+ });
2154
+ }
2155
+ }
2156
+ }
2157
+ const thinking = options.thinking ?? "off";
2158
+ const model = options.model ?? provider.meta.defaultModel;
2159
+ const resolvedBehavior = resolveBehavior(agentBehavior, options.behavior);
2160
+ const { toolExecution, maxTurns, maxTokens, thinkingBudget, schema, cache, toolOutputBudget, compactStrategy, compactThreshold, compactKeepTurns, thinkingDecay, dedupTools, toolBudgets, elideStaleReads, toolDisclosure, toolSearch } = resolvedBehavior;
2161
+ let system = options.system || agentSystem || "You are a helpful assistant.";
2162
+ if (skillsCatalog) system = `${system}\n\n${skillsCatalog}`;
2163
+ const runBaseTools = options.tools !== void 0 ? options.tools : mcpConnection ? {
2164
+ ...sourceTools,
2165
+ ...mcpConnection.tools
2166
+ } : sourceTools;
2167
+ const mcpToolNames = options.tools === void 0 && mcpConnection ? new Set(Object.keys(mcpConnection.tools)) : /* @__PURE__ */ new Set();
2168
+ const mergedWithSkills = options.tools === void 0 && !!resolvedSkills && resolvedSkills.length > 0 && skillsConfig?.tool !== false ? {
2169
+ skills_use: createSkillsUseTool({
2170
+ catalog: resolvedSkills,
2171
+ state: skillActivationState,
2172
+ hooks
2173
+ }),
2174
+ skills_read: createSkillsReadTool({
2175
+ catalog: resolvedSkills,
2176
+ state: skillActivationState
2177
+ }),
2178
+ skills_run_script: createSkillsRunScriptTool({
2179
+ catalog: resolvedSkills,
2180
+ state: skillActivationState,
2181
+ scriptTimeoutMs: skillsConfig?.scriptTimeoutMs
2182
+ }),
2183
+ ...runBaseTools
2184
+ } : runBaseTools;
2185
+ const toolsPreSearch = {};
2186
+ for (const tool of Object.values(mergedWithSkills)) toolsPreSearch[tool.spec.name] = tool;
2187
+ const disclosure = partitionToolDisclosure(toolsPreSearch, mcpToolNames, mcpServers, toolDisclosure, toolAliases);
2188
+ const unlocked = new Set(disclosure.eagerCanonicalNames);
2189
+ const hostDefinedToolSearch = !!toolsPreSearch.tool_search;
2190
+ const shouldInjectToolSearch = disclosure.lazyEntries.length > 0 && toolSearch?.tool !== false && !hostDefinedToolSearch;
2191
+ let tools = toolsPreSearch;
2192
+ if (shouldInjectToolSearch) {
2193
+ const toolSearchTool = createToolSearchTool({
2194
+ catalog: disclosure.lazyEntries,
2195
+ unlocked,
2196
+ ...toolSearch?.limit !== void 0 ? { defaultLimit: toolSearch.limit } : {}
2197
+ });
2198
+ tools = {
2199
+ ...toolsPreSearch,
2200
+ [toolSearchTool.spec.name]: toolSearchTool
2201
+ };
2202
+ unlocked.add(toolSearchTool.spec.name);
2203
+ }
2204
+ const discoveryToolName = shouldInjectToolSearch ? "tool_search" : hostDefinedToolSearch ? toolAliases?.tool_search ?? "tool_search" : null;
2205
+ if (disclosure.lazyEntries.length > 0) system = `${system}\n\n${buildSearchableCatalog(disclosure.lazyEntries, { discoveryToolName })}`;
2206
+ const aliasMaps = buildAliasMaps(toolAliases, Object.keys(tools));
2207
+ function buildFormattedTools() {
2208
+ const specs = [];
2209
+ for (const t of Object.values(tools)) {
2210
+ if (!unlocked.has(t.spec.name)) continue;
2211
+ specs.push({
2212
+ name: aliasMaps.aliasByCanonical.get(t.spec.name) ?? t.spec.name,
2213
+ description: t.spec.description || "",
2214
+ inputSchema: t.spec.inputSchema
2215
+ });
2216
+ }
2217
+ return specs.length > 0 ? provider.formatTools(specs) : [];
2218
+ }
2219
+ const formattedTools = buildFormattedTools();
2220
+ const turns = [];
2221
+ const isResume = session && session.turns.length > 0 && (session.runs.length > 0 || !options.prompt) && !options.parentRunId;
2222
+ if (isResume) {
2223
+ const childRunIds = new Set(session.runs.filter((r) => (r.depth ?? 0) > 0).map((r) => r.id));
2224
+ const resumed = childRunIds.size === 0 ? session.turns : session.turns.filter((t) => !t.runId || !childRunIds.has(t.runId));
2225
+ turns.push(...resumed);
2226
+ }
2227
+ const runTurnStart = turns.length;
2228
+ if (options.system) await hooks.callHook("system:before", { system: options.system });
2229
+ const promptParts = canonicalizePrompt(options.prompt);
2230
+ if (promptParts) {
2231
+ const promptMsg = buildPromptMessage(provider, promptParts);
2232
+ turns.push({
2233
+ id: crypto.randomUUID(),
2234
+ runId,
2235
+ role: promptMsg.role,
2236
+ content: promptMsg.content,
2237
+ createdAt: Date.now()
2238
+ });
2239
+ }
2240
+ conversationTurns = turns;
2241
+ let lastPersistedTurnCount = isResume ? session.turns.length : 0;
2242
+ if (session && turns.length > lastPersistedTurnCount) {
2243
+ const seededTurns = turns.slice(lastPersistedTurnCount);
2244
+ await session.appendTurns(seededTurns);
2139
2245
  lastPersistedTurnCount = turns.length;
2140
2246
  await hooks.callHook("session:turns", {
2141
2247
  sessionId: session.id,
2142
- turns: newTurns,
2248
+ turns: seededTurns,
2143
2249
  count: turns.length
2144
2250
  });
2145
2251
  }
2146
- }) : void 0;
2147
- async function flushTurns() {
2148
- if (!session) return;
2149
- const remaining = turns.slice(lastPersistedTurnCount);
2150
- if (remaining.length > 0) {
2151
- await session.appendTurns(remaining);
2252
+ const persistPendingTurns = async () => {
2253
+ if (!session) return;
2254
+ const newTurns = turns.slice(lastPersistedTurnCount);
2255
+ if (newTurns.length === 0) return;
2256
+ await session.appendTurns(newTurns);
2152
2257
  lastPersistedTurnCount = turns.length;
2153
2258
  await hooks.callHook("session:turns", {
2154
2259
  sessionId: session.id,
2155
- turns: remaining,
2260
+ turns: newTurns,
2156
2261
  count: turns.length
2157
2262
  });
2263
+ };
2264
+ const unregisterTurnSync = session ? hooks.hook("turn:after", persistPendingTurns) : void 0;
2265
+ const unregisterToolResultsSync = session ? hooks.hook("tool-results:after", persistPendingTurns) : void 0;
2266
+ async function flushTurns(opts = {}) {
2267
+ if (!session) return;
2268
+ if (opts.failureFallback) synthesizeMissingToolResults(turns, await session.generateTurnId?.() ?? crypto.randomUUID(), runId, provider);
2269
+ const remaining = turns.slice(lastPersistedTurnCount);
2270
+ if (remaining.length > 0) {
2271
+ await session.appendTurns(remaining);
2272
+ lastPersistedTurnCount = turns.length;
2273
+ await hooks.callHook("session:turns", {
2274
+ sessionId: session.id,
2275
+ turns: remaining,
2276
+ count: turns.length
2277
+ });
2278
+ }
2158
2279
  }
2159
- }
2160
- async function deactivateAllSkills() {
2161
- for (const record of skillActivationState.clear()) await hooks.callHook("skills:deactivate", {
2162
- skill: record.skill,
2163
- reason: "run-end"
2164
- });
2165
- }
2166
- async function finalizeSession(status) {
2167
- if (!session) return;
2168
- const run = session.runs.find((r) => r.id === runId);
2169
- if (run) await session.updateRun(run);
2170
- await session.updateStatus(status === "aborted" ? "idle" : status);
2171
- await hooks.callHook("session:end", {
2172
- sessionId: session.id,
2173
- runId,
2174
- status,
2175
- turnRange: [runTurnStart, turns.length - 1]
2176
- });
2177
- }
2178
- const uninstallAllowedToolsGate = installAllowedToolsGate(hooks, skillActivationState);
2179
- const uninstallToolBudgets = installToolBudgetsGate(hooks, () => toolBudgets, (msg) => steeringQueue.push(msg));
2180
- const uninstallDedupTools = installDedupToolsGate(hooks, () => dedupTools, () => session ?? void 0);
2181
- const runStartMs = Date.now();
2182
- const runDepth = typeof options.depth === "number" ? options.depth : 0;
2183
- try {
2184
- const stats = await runLoop({
2185
- provider,
2186
- hooks,
2187
- agentName,
2188
- agentSystem,
2189
- agentTools: sourceTools,
2190
- agentToolAliases: toolAliases,
2191
- agentMcpServers: mcpServers,
2192
- agentSkills,
2193
- agentBehavior: resolvedBehavior,
2194
- tools,
2195
- formattedTools,
2196
- rebuildFormattedTools: disclosure.lazyEntries.length > 0 ? buildFormattedTools : void 0,
2197
- aliasMaps,
2198
- model,
2199
- system,
2200
- thinking,
2201
- toolExecution,
2202
- signal: abortController.signal,
2203
- execution: executionContext,
2204
- handle: executionHandle,
2205
- steeringQueue,
2206
- followUpQueue,
2207
- turns,
2208
- runId,
2209
- generateTurnId: () => session?.generateTurnId() ?? crypto.randomUUID(),
2210
- maxTurns,
2211
- maxTokens,
2212
- ...session ? { session } : {},
2213
- depth: runDepth,
2214
- thinkingBudget,
2215
- schema,
2216
- cache,
2217
- toolOutputBudget,
2218
- compactStrategy,
2219
- compactThreshold,
2220
- compactKeepTurns,
2221
- ...elideStaleReads !== void 0 ? { elideStaleReads } : {},
2222
- ...thinkingDecay !== void 0 ? { thinkingDecay } : {},
2223
- runStartMs,
2224
- runToolCounts: {}
2225
- });
2226
- const parentTurnCost = stats.turnUsage?.reduce((sum, t) => sum + (t.cost ?? 0), 0) ?? 0;
2227
- let childrenIn = 0;
2228
- let childrenOut = 0;
2229
- let childrenCost = 0;
2230
- let childrenCacheRead = 0;
2231
- let childrenCacheCreation = 0;
2232
- for (const c of childrenStats) {
2233
- childrenIn += c.stats.totalIn;
2234
- childrenOut += c.stats.totalOut;
2235
- childrenCost += c.stats.cost ?? 0;
2236
- childrenCacheRead += c.stats.totalCacheRead;
2237
- childrenCacheCreation += c.stats.totalCacheCreation;
2280
+ async function deactivateAllSkills() {
2281
+ for (const record of skillActivationState.clear()) await hooks.callHook("skills:deactivate", {
2282
+ skill: record.skill,
2283
+ reason: "run-end"
2284
+ });
2238
2285
  }
2239
- const cumulativeCost = parentTurnCost + childrenCost;
2240
- const finalStats = {
2241
- ...stats,
2242
- totalIn: stats.totalIn + childrenIn,
2243
- totalOut: stats.totalOut + childrenOut,
2244
- totalCacheRead: stats.totalCacheRead + childrenCacheRead,
2245
- totalCacheCreation: stats.totalCacheCreation + childrenCacheCreation,
2246
- ...cumulativeCost > 0 ? { cost: cumulativeCost } : {},
2247
- children: childrenStats.length > 0 ? childrenStats : void 0
2248
- };
2249
- await flushTurns();
2250
- if (abortController.signal.aborted) {
2251
- session?.abortRun(runId);
2252
- await finalizeSession("aborted");
2253
- await hooks.callHook("agent:done", finalStats);
2254
- return finalStats;
2286
+ async function finalizeSession(status) {
2287
+ if (!session) return;
2288
+ const run = session.runs.find((r) => r.id === runId);
2289
+ if (run) await session.updateRun(run);
2290
+ await session.updateStatus(status === "aborted" ? "idle" : status);
2291
+ await hooks.callHook("session:end", {
2292
+ sessionId: session.id,
2293
+ runId,
2294
+ status,
2295
+ turnRange: [runTurnStart, turns.length - 1]
2296
+ });
2255
2297
  }
2256
- session?.completeRun(runId, {
2257
- turns: stats.turns,
2258
- tokensIn: stats.totalIn,
2259
- tokensOut: stats.totalOut,
2260
- turnUsage: stats.turnUsage,
2261
- cost: parentTurnCost > 0 ? parentTurnCost : void 0
2262
- });
2263
- await finalizeSession("completed");
2264
- await hooks.callHook("agent:done", finalStats);
2265
- return finalStats;
2266
- } catch (err) {
2267
- await flushTurns();
2268
- if (abortController.signal.aborted) {
2269
- session?.abortRun(runId);
2270
- await finalizeSession("aborted");
2271
- const stats = {
2272
- totalIn: 0,
2273
- totalOut: 0,
2274
- totalCacheRead: 0,
2275
- totalCacheCreation: 0,
2276
- turns: 0,
2277
- elapsed: 0
2298
+ const uninstallAllowedToolsGate = installAllowedToolsGate(hooks, skillActivationState);
2299
+ const uninstallToolBudgets = installToolBudgetsGate(hooks, () => toolBudgets, (msg) => steeringQueue.push(msg));
2300
+ const uninstallDedupTools = installDedupToolsGate(hooks, () => dedupTools, () => session ?? void 0);
2301
+ const uninstallLazyDisclosureGate = installLazyDisclosureGate(hooks, disclosure.lazyCanonicalNames, unlocked, discoveryToolName);
2302
+ const runStartMs = Date.now();
2303
+ const runDepth = typeof options.depth === "number" ? options.depth : 0;
2304
+ try {
2305
+ const stats = await runLoop({
2306
+ provider,
2307
+ hooks,
2308
+ agentName,
2309
+ agentSystem,
2310
+ agentTools: sourceTools,
2311
+ agentToolAliases: toolAliases,
2312
+ agentMcpServers: mcpServers,
2313
+ agentSkills,
2314
+ agentBehavior: resolvedBehavior,
2315
+ tools,
2316
+ formattedTools,
2317
+ rebuildFormattedTools: disclosure.lazyEntries.length > 0 ? buildFormattedTools : void 0,
2318
+ aliasMaps,
2319
+ model,
2320
+ system,
2321
+ thinking,
2322
+ toolExecution,
2323
+ signal: abortController.signal,
2324
+ execution: executionContext,
2325
+ handle: executionHandle,
2326
+ steeringQueue,
2327
+ followUpQueue,
2328
+ turns,
2329
+ runId,
2330
+ generateTurnId: () => session?.generateTurnId() ?? crypto.randomUUID(),
2331
+ maxTurns,
2332
+ maxTokens,
2333
+ ...session ? { session } : {},
2334
+ depth: runDepth,
2335
+ thinkingBudget,
2336
+ schema,
2337
+ cache,
2338
+ toolOutputBudget,
2339
+ compactStrategy,
2340
+ compactThreshold,
2341
+ compactKeepTurns,
2342
+ ...elideStaleReads !== void 0 ? { elideStaleReads } : {},
2343
+ ...thinkingDecay !== void 0 ? { thinkingDecay } : {},
2344
+ runStartMs,
2345
+ runToolCounts: {}
2346
+ });
2347
+ const parentTurnCost = stats.turnUsage?.reduce((sum, t) => sum + (t.cost ?? 0), 0) ?? 0;
2348
+ let childrenIn = 0;
2349
+ let childrenOut = 0;
2350
+ let childrenCost = 0;
2351
+ let childrenCacheRead = 0;
2352
+ let childrenCacheCreation = 0;
2353
+ for (const c of childrenStats) {
2354
+ childrenIn += c.stats.totalIn;
2355
+ childrenOut += c.stats.totalOut;
2356
+ childrenCost += c.stats.cost ?? 0;
2357
+ childrenCacheRead += c.stats.totalCacheRead;
2358
+ childrenCacheCreation += c.stats.totalCacheCreation;
2359
+ }
2360
+ const cumulativeCost = parentTurnCost + childrenCost;
2361
+ const finalStats = {
2362
+ ...stats,
2363
+ totalIn: stats.totalIn + childrenIn,
2364
+ totalOut: stats.totalOut + childrenOut,
2365
+ totalCacheRead: stats.totalCacheRead + childrenCacheRead,
2366
+ totalCacheCreation: stats.totalCacheCreation + childrenCacheCreation,
2367
+ ...cumulativeCost > 0 ? { cost: cumulativeCost } : {},
2368
+ children: childrenStats.length > 0 ? childrenStats : void 0
2278
2369
  };
2279
- await hooks.callHook("agent:done", stats);
2280
- return stats;
2370
+ await flushTurns();
2371
+ if (abortController.signal.aborted) {
2372
+ session?.abortRun(runId, {
2373
+ turns: stats.turns,
2374
+ tokensIn: stats.totalIn,
2375
+ tokensOut: stats.totalOut,
2376
+ turnUsage: stats.turnUsage,
2377
+ cost: parentTurnCost > 0 ? parentTurnCost : void 0
2378
+ });
2379
+ await finalizeSession("aborted");
2380
+ await hooks.callHook("agent:done", finalStats);
2381
+ return finalStats;
2382
+ }
2383
+ session?.completeRun(runId, {
2384
+ turns: stats.turns,
2385
+ tokensIn: stats.totalIn,
2386
+ tokensOut: stats.totalOut,
2387
+ turnUsage: stats.turnUsage,
2388
+ cost: parentTurnCost > 0 ? parentTurnCost : void 0
2389
+ });
2390
+ await finalizeSession("completed");
2391
+ await hooks.callHook("agent:done", finalStats);
2392
+ return finalStats;
2393
+ } catch (err) {
2394
+ await flushTurns({ failureFallback: true });
2395
+ if (abortController.signal.aborted) {
2396
+ session?.abortRun(runId);
2397
+ await finalizeSession("aborted");
2398
+ const stats = {
2399
+ totalIn: 0,
2400
+ totalOut: 0,
2401
+ totalCacheRead: 0,
2402
+ totalCacheCreation: 0,
2403
+ turns: 0,
2404
+ elapsed: 0
2405
+ };
2406
+ await hooks.callHook("agent:done", stats);
2407
+ return stats;
2408
+ }
2409
+ const message = err instanceof Error ? err.message : String(err);
2410
+ session?.errorRun(runId, message);
2411
+ await finalizeSession("error");
2412
+ throw err;
2413
+ } finally {
2414
+ await deactivateAllSkills();
2415
+ uninstallAllowedToolsGate();
2416
+ uninstallDedupTools();
2417
+ uninstallToolBudgets();
2418
+ uninstallLazyDisclosureGate();
2419
+ unregisterSpawnHook();
2420
+ unregisterTurnSync?.();
2421
+ unregisterToolResultsSync?.();
2422
+ for (const unregister of perRunUnregisters) unregister();
2423
+ running = false;
2424
+ abortController = void 0;
2425
+ steeringQueue.length = 0;
2426
+ followUpQueue.length = 0;
2427
+ idleResolve?.();
2428
+ idlePromise = void 0;
2429
+ idleResolve = void 0;
2281
2430
  }
2282
- const message = err instanceof Error ? err.message : String(err);
2283
- session?.errorRun(runId, message);
2284
- await finalizeSession("error");
2285
- throw err;
2286
2431
  } finally {
2287
- await deactivateAllSkills();
2288
- uninstallAllowedToolsGate();
2289
- uninstallDedupTools();
2290
- uninstallToolBudgets();
2291
- uninstallLazyDisclosureGate();
2292
- unregisterSpawnHook();
2293
- unregisterSessionSync?.();
2294
- for (const unregister of perRunUnregisters) unregister();
2295
2432
  running = false;
2296
2433
  abortController = void 0;
2297
- steeringQueue.length = 0;
2298
- followUpQueue.length = 0;
2299
2434
  idleResolve?.();
2300
2435
  idlePromise = void 0;
2301
2436
  idleResolve = void 0;
2437
+ if (externalSignal && externalAbortListener) externalSignal.removeEventListener("abort", externalAbortListener);
2302
2438
  }
2303
2439
  }
2304
2440
  function abort() {
@@ -2325,7 +2461,8 @@ function createAgent({ provider, name: agentName, system: agentSystem, tools: ag
2325
2461
  });
2326
2462
  }
2327
2463
  async function activateSkill(name) {
2328
- if (!resolvedSkills) throw new Error(`Cannot activate skill "${name}" — skills have not been resolved yet. Call activateSkill after the first \`run()\`, or pass a skills config that resolves at agent-creation time.`);
2464
+ await ensureSkillsResolved();
2465
+ if (!resolvedSkills) throw new Error(`Cannot activate skill "${name}" — skills are disabled for this agent.`);
2329
2466
  const skill = resolvedSkills.find((s) => s.name === name);
2330
2467
  if (!skill) {
2331
2468
  const available = resolvedSkills.map((s) => s.name).join(", ") || "<none>";
@@ -2371,8 +2508,7 @@ function createAgent({ provider, name: agentName, system: agentSystem, tools: ag
2371
2508
  * retry — leaving a rejected promise cached would permanently poison future
2372
2509
  * runs on the same agent.
2373
2510
  */
2374
- async function warmup() {
2375
- if (destroyed) return;
2511
+ async function ensureMcpConnected() {
2376
2512
  if (mcpConnection || allMcpServers.length === 0) return;
2377
2513
  if (mcpWarmupPromise) return mcpWarmupPromise;
2378
2514
  mcpWarmupPromise = (async () => {
@@ -2390,6 +2526,10 @@ function createAgent({ provider, name: agentName, system: agentSystem, tools: ag
2390
2526
  throw err;
2391
2527
  }
2392
2528
  }
2529
+ async function warmup() {
2530
+ if (destroyed) return;
2531
+ await Promise.all([ensureMcpConnected(), ensureSkillsResolved()]);
2532
+ }
2393
2533
  async function destroy() {
2394
2534
  if (destroyed) return;
2395
2535
  destroyed = true;
@@ -2407,7 +2547,8 @@ function createAgent({ provider, name: agentName, system: agentSystem, tools: ag
2407
2547
  skillsCleanup();
2408
2548
  skillsCleanup = () => {};
2409
2549
  }
2410
- if (eager && allMcpServers.length > 0) warmup().catch(() => {});
2550
+ const eagerHasWork = allMcpServers.length > 0 || !skillsDisabled && !!skillsConfig;
2551
+ if (eager && eagerHasWork) warmup().catch(() => {});
2411
2552
  return {
2412
2553
  hooks,
2413
2554
  run,
@@ -3701,17 +3842,33 @@ var SpawnTimeoutError = class extends Error {
3701
3842
  * Called before `agent.run()` starts, torn down in a finally block — so
3702
3843
  * nothing leaks even if the child throws mid-run.
3703
3844
  */
3845
+ /**
3846
+ * Surface a thrown observational-bubble listener without killing the
3847
+ * process. Observational events (`child:stream:text`, `child:tool:after`,
3848
+ * `child:turn:after`, …) are fire-and-forget — a rejected promise here
3849
+ * has no caller. Without this handler, a buggy parent listener bubbles
3850
+ * up to an unhandled-rejection, and Node/Bun under
3851
+ * `--unhandled-rejections=strict` terminates the host.
3852
+ *
3853
+ * Gated on `ZIDANE_DEBUG` so production logs stay quiet; debug builds
3854
+ * still get the tagged line.
3855
+ */
3856
+ function swallowBubbleError(eventName, err) {
3857
+ if (!process.env.ZIDANE_DEBUG) return;
3858
+ const message = err instanceof Error ? err.stack ?? err.message : String(err);
3859
+ process.stderr.write(`[zidane/spawn] parent listener for "${eventName}" rejected: ${message}\n`);
3860
+ }
3704
3861
  function bubbleHooks(childHooks, parentHooks, childId, depth) {
3705
3862
  const unregisters = [];
3706
3863
  const fire = parentHooks.callHook;
3707
3864
  for (const evt of BUBBLED_EVENTS) {
3708
3865
  const parentEvt = CHILD_EVENT_NAME[evt];
3709
3866
  const unregister = childHooks.hook(evt, (ctx) => {
3710
- fire(parentEvt, {
3867
+ Promise.resolve(fire(parentEvt, {
3711
3868
  ...ctx,
3712
3869
  childId,
3713
3870
  depth
3714
- });
3871
+ })).catch((err) => swallowBubbleError(parentEvt, err));
3715
3872
  });
3716
3873
  unregisters.push(unregister);
3717
3874
  }
@@ -3731,7 +3888,7 @@ function bubbleHooks(childHooks, parentHooks, childId, depth) {
3731
3888
  for (const evt of BUBBLED_EVENTS) {
3732
3889
  const parentEvt = CHILD_EVENT_NAME[evt];
3733
3890
  unregisters.push(chainHook(parentEvt, (ctx) => {
3734
- fire(parentEvt, ctx);
3891
+ Promise.resolve(fire(parentEvt, ctx)).catch((err) => swallowBubbleError(parentEvt, err));
3735
3892
  }));
3736
3893
  }
3737
3894
  for (const evt of BUBBLED_GATE_EVENTS) {
@@ -3988,4 +4145,4 @@ const writeFile$1 = {
3988
4145
  //#endregion
3989
4146
  export { multiEdit as a, grep as c, createAgent as d, createToolSearchTool as f, validateToolArgs as g, createSkillsReadTool as h, readFile$1 as i, glob as l, createSkillsRunScriptTool as m, createSpawnTool as n, listFiles as o, createSkillsUseTool as p, shell as r, createInteractionTool as s, writeFile$1 as t, edit as u };
3990
4147
 
3991
- //# sourceMappingURL=tools-C8kDot0H.js.map
4148
+ //# sourceMappingURL=tools-CLazLRb4.js.map