zidane 5.3.2 → 5.4.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 (43) hide show
  1. package/README.md +16 -1
  2. package/dist/{agent-BXRCCHeq.d.ts → agent-DHQAsdj6.d.ts} +47 -15
  3. package/dist/agent-DHQAsdj6.d.ts.map +1 -0
  4. package/dist/chat.d.ts +4 -4
  5. package/dist/chat.d.ts.map +1 -1
  6. package/dist/chat.js +1 -1
  7. package/dist/{index-CT5_p-3P.d.ts → index-CHSaLab5.d.ts} +2 -2
  8. package/dist/{index-CT5_p-3P.d.ts.map → index-CHSaLab5.d.ts.map} +1 -1
  9. package/dist/{index-BPk8-Slm.d.ts → index-CrqFoaQA.d.ts} +12 -11
  10. package/dist/index-CrqFoaQA.d.ts.map +1 -0
  11. package/dist/index.d.ts +4 -4
  12. package/dist/index.js +4 -4
  13. package/dist/{login-DrBZ15G7.js → login-bK0EP8La.js} +2 -2
  14. package/dist/{login-DrBZ15G7.js.map → login-bK0EP8La.js.map} +1 -1
  15. package/dist/mcp.d.ts +1 -1
  16. package/dist/{presets-0_IRJAYF.js → presets-M8f6lDnW.js} +2 -2
  17. package/dist/{presets-0_IRJAYF.js.map → presets-M8f6lDnW.js.map} +1 -1
  18. package/dist/presets.d.ts +2 -2
  19. package/dist/presets.js +1 -1
  20. package/dist/providers.d.ts +1 -1
  21. package/dist/session/sqlite.d.ts +1 -1
  22. package/dist/session.d.ts +1 -1
  23. package/dist/skills.d.ts +2 -2
  24. package/dist/{tools-CCsL5SCO.js → tools-DKdyPoUf.js} +279 -63
  25. package/dist/tools-DKdyPoUf.js.map +1 -0
  26. package/dist/tools.d.ts +2 -2
  27. package/dist/tools.js +1 -1
  28. package/dist/{transcript-anchors-DSk8LlWt.d.ts → transcript-anchors-Fgh_rZ04.d.ts} +3 -3
  29. package/dist/{transcript-anchors-DSk8LlWt.d.ts.map → transcript-anchors-Fgh_rZ04.d.ts.map} +1 -1
  30. package/dist/tui.d.ts +2 -2
  31. package/dist/tui.js +3 -3
  32. package/dist/tui.js.map +1 -1
  33. package/dist/{turn-operations-CutZin8X.js → turn-operations-DDokWR8p.js} +4 -3
  34. package/dist/turn-operations-DDokWR8p.js.map +1 -0
  35. package/dist/types-IcokUOyC.js.map +1 -1
  36. package/dist/types.d.ts +3 -3
  37. package/docs/ARCHITECTURE.md +17 -6
  38. package/docs/SKILL.md +33 -2
  39. package/package.json +1 -1
  40. package/dist/agent-BXRCCHeq.d.ts.map +0 -1
  41. package/dist/index-BPk8-Slm.d.ts.map +0 -1
  42. package/dist/tools-CCsL5SCO.js.map +0 -1
  43. package/dist/turn-operations-CutZin8X.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"presets-0_IRJAYF.js","names":[],"sources":["../src/presets/basic.ts","../src/presets/index.ts"],"sourcesContent":["import { definePreset } from '.'\nimport { edit, listFiles, multiEdit, readFile, shell, writeFile } from '../tools'\nimport { createSpawnTool } from '../tools/spawn'\n\n/**\n * Core tools available in every basic preset (without spawn).\n *\n * `edit` and `multi_edit` ship in the basic set because surgical edits are the\n * default modality for production agents — `write_file` is for full overwrites.\n * `glob` and `grep` are exported but opt-in: not every agent needs codebase\n * search, and shipping them by default would force `tool:gate` work onto\n * consumers that prefer the model to use `shell` + classic Unix tools.\n */\nexport const basicTools = { shell, readFile, writeFile, listFiles, edit, multiEdit }\n\nexport default definePreset({\n name: 'basic',\n system: 'You are a helpful assistant with access to shell, file reading, file writing, surgical and multi-edit tools, directory listing, and sub-agent spawning. Prefer `edit` / `multi_edit` for in-place changes and `write_file` for full file overwrites. Use them to accomplish tasks in the project directory.',\n // `persist: true` shares the parent's session with every child agent — child\n // turns land in `session.turns` tagged with their own `runId`, and the run\n // itself is recorded in `session.runs` with `parentRunId` + `depth`. That's\n // what lets a reloaded TUI session reconstruct the full subagent tree (see\n // `eventsFromTurns` in `tui/store.ts`). Hosts that want children in-memory\n // only can construct their own preset with `createSpawnTool()`.\n tools: { ...basicTools, spawn: createSpawnTool({ persist: true }) },\n})\n","import type { AgentHooks, AgentOptions } from '../agent'\n\nexport type { AgentHookMap } from '../agent'\n\n/**\n * A preset is a reusable slice of `AgentOptions` — spread it into `createAgent()`\n * to configure tools, a default system prompt, aliases, behavior defaults, and\n * agent-lifetime hooks.\n *\n * `provider`, `execution`, `session`, and internal fields are excluded so presets\n * remain shareable and composable.\n *\n * ```ts\n * import { basic } from 'zidane/presets'\n * createAgent({ ...basic, provider })\n * ```\n *\n * ### Composing multiple presets\n *\n * Bare `...spread` is shallow — `{ ...a, ...b }` overwrites every key `b`\n * defines, including `hooks`. Use {@link composePresets} when you want\n * field-aware merging (per-event hook concat, tools shallow-merge, etc.):\n *\n * ```ts\n * createAgent({ ...composePresets(basic, telemetry, mine), provider })\n * ```\n */\nexport type Preset = Omit<Partial<AgentOptions>, 'provider' | 'execution' | 'session' | 'mcpConnector'>\n\n/**\n * Identity helper for type inference when defining a preset.\n */\nexport function definePreset(config: Preset): Preset {\n return config\n}\n\n/**\n * Field-aware composition of presets. Right-most preset wins for scalar fields;\n * objects shallow-merge; arrays and hook handler lists concatenate. Designed so\n * stacking presets does the obvious thing without the spread-collision footgun:\n *\n * - `name`, `system`, `eager`, `skills` → last-defined wins\n * - `tools`, `toolAliases`, `behavior` → shallow-merge (later keys override)\n * - `behavior.dedupTools`, `behavior.toolBudgets` → **deep-merge** (per-tool-name; later wins on collision)\n * - `mcpServers` → concat with last-wins on `name` collision\n * - `hooks` → per-event concat; every handler fires\n *\n * `hooks` always emerges as `event → handler[]` so downstream registration\n * (in `createAgent`) sees a uniform shape. Order of handlers within an event\n * follows preset order: earlier presets register first.\n *\n * `mcpServers` is deduped by `name` because shipping two servers with the same\n * name would trip the connector at runtime — a later preset overriding an\n * earlier preset's `github` server is the practical intent.\n *\n * `behavior.dedupTools` and `behavior.toolBudgets` get the same per-key deep-merge\n * because they are tool-name-keyed records — a preset that ships a dedup hasher\n * for one tool should not erase a hasher another preset ships for a different\n * tool. Last-wins still applies on a per-tool collision so a downstream preset\n * can override an upstream preset's policy for one specific tool. Other\n * `behavior` fields keep last-wins semantics.\n */\nexport function composePresets(...presets: Preset[]): Preset {\n const out: Preset = {}\n const hooksByEvent: { [K in keyof AgentHooks]?: AgentHooks[K][] } = {}\n // Keep mcpServers in source-order on first sight, but allow later\n // declarations to override earlier ones with the same `name`. A `Map`\n // keyed by name gives O(1) override + stable iteration.\n const mcpByName = new Map<string, NonNullable<Preset['mcpServers']>[number]>()\n\n for (const p of presets) {\n if (p.name !== undefined)\n out.name = p.name\n if (p.system !== undefined)\n out.system = p.system\n if (p.eager !== undefined)\n out.eager = p.eager\n if (p.skills !== undefined)\n out.skills = p.skills\n if (p.tools)\n out.tools = { ...out.tools, ...p.tools }\n if (p.toolAliases)\n out.toolAliases = { ...out.toolAliases, ...p.toolAliases }\n if (p.behavior) {\n // Top-level shallow-merge first; then deep-merge the two tool-name-keyed\n // sub-records so per-tool entries from earlier presets aren't clobbered.\n const merged: NonNullable<Preset['behavior']> = { ...out.behavior, ...p.behavior }\n if (out.behavior?.dedupTools || p.behavior.dedupTools) {\n merged.dedupTools = { ...out.behavior?.dedupTools, ...p.behavior.dedupTools }\n }\n if (out.behavior?.toolBudgets || p.behavior.toolBudgets) {\n merged.toolBudgets = { ...out.behavior?.toolBudgets, ...p.behavior.toolBudgets }\n }\n out.behavior = merged\n }\n if (p.mcpServers) {\n for (const server of p.mcpServers)\n mcpByName.set(server.name, server)\n }\n if (p.hooks) {\n for (const [event, handler] of Object.entries(p.hooks)) {\n if (handler === undefined)\n continue\n const list = Array.isArray(handler) ? handler : [handler]\n const key = event as keyof AgentHooks\n // Safe cast: we read the loose `AgentHookMap` shape (handler-or-array)\n // and re-emit only as arrays. Each `list` element matches the event's\n // handler signature by construction (the input was typed `AgentHookMap`).\n const bucket = (hooksByEvent[key] ??= []) as unknown[]\n bucket.push(...(list as unknown[]))\n }\n }\n }\n\n if (mcpByName.size > 0)\n out.mcpServers = [...mcpByName.values()]\n\n if (Object.keys(hooksByEvent).length > 0)\n out.hooks = hooksByEvent\n\n return out\n}\n\nexport { default as basic, basicTools } from './basic'\n"],"mappings":";;;;;;;;;;;AAaA,MAAa,aAAa;CAAE;CAAO;CAAU;CAAW;CAAW;CAAM;CAAW;AAEpF,IAAA,gBAAe,aAAa;CAC1B,MAAM;CACN,QAAQ;CAOR,OAAO;EAAE,GAAG;EAAY,OAAO,gBAAgB,EAAE,SAAS,MAAM,CAAC;EAAE;CACpE,CAAC;;;;;;ACOF,SAAgB,aAAa,QAAwB;CACnD,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6BT,SAAgB,eAAe,GAAG,SAA2B;CAC3D,MAAM,MAAc,EAAE;CACtB,MAAM,eAA8D,EAAE;CAItE,MAAM,4BAAY,IAAI,KAAwD;CAE9E,KAAK,MAAM,KAAK,SAAS;EACvB,IAAI,EAAE,SAAS,KAAA,GACb,IAAI,OAAO,EAAE;EACf,IAAI,EAAE,WAAW,KAAA,GACf,IAAI,SAAS,EAAE;EACjB,IAAI,EAAE,UAAU,KAAA,GACd,IAAI,QAAQ,EAAE;EAChB,IAAI,EAAE,WAAW,KAAA,GACf,IAAI,SAAS,EAAE;EACjB,IAAI,EAAE,OACJ,IAAI,QAAQ;GAAE,GAAG,IAAI;GAAO,GAAG,EAAE;GAAO;EAC1C,IAAI,EAAE,aACJ,IAAI,cAAc;GAAE,GAAG,IAAI;GAAa,GAAG,EAAE;GAAa;EAC5D,IAAI,EAAE,UAAU;GAGd,MAAM,SAA0C;IAAE,GAAG,IAAI;IAAU,GAAG,EAAE;IAAU;GAClF,IAAI,IAAI,UAAU,cAAc,EAAE,SAAS,YACzC,OAAO,aAAa;IAAE,GAAG,IAAI,UAAU;IAAY,GAAG,EAAE,SAAS;IAAY;GAE/E,IAAI,IAAI,UAAU,eAAe,EAAE,SAAS,aAC1C,OAAO,cAAc;IAAE,GAAG,IAAI,UAAU;IAAa,GAAG,EAAE,SAAS;IAAa;GAElF,IAAI,WAAW;;EAEjB,IAAI,EAAE,YACJ,KAAK,MAAM,UAAU,EAAE,YACrB,UAAU,IAAI,OAAO,MAAM,OAAO;EAEtC,IAAI,EAAE,OACJ,KAAK,MAAM,CAAC,OAAO,YAAY,OAAO,QAAQ,EAAE,MAAM,EAAE;GACtD,IAAI,YAAY,KAAA,GACd;GACF,MAAM,OAAO,MAAM,QAAQ,QAAQ,GAAG,UAAU,CAAC,QAAQ;GACzD,MAAM,MAAM;GAKZ,CADgB,aAAa,SAAS,EAAE,EACjC,KAAK,GAAI,KAAmB;;;CAKzC,IAAI,UAAU,OAAO,GACnB,IAAI,aAAa,CAAC,GAAG,UAAU,QAAQ,CAAC;CAE1C,IAAI,OAAO,KAAK,aAAa,CAAC,SAAS,GACrC,IAAI,QAAQ;CAEd,OAAO"}
1
+ {"version":3,"file":"presets-M8f6lDnW.js","names":[],"sources":["../src/presets/basic.ts","../src/presets/index.ts"],"sourcesContent":["import { definePreset } from '.'\nimport { edit, listFiles, multiEdit, readFile, shell, writeFile } from '../tools'\nimport { createSpawnTool } from '../tools/spawn'\n\n/**\n * Core tools available in every basic preset (without spawn).\n *\n * `edit` and `multi_edit` ship in the basic set because surgical edits are the\n * default modality for production agents — `write_file` is for full overwrites.\n * `glob` and `grep` are exported but opt-in: not every agent needs codebase\n * search, and shipping them by default would force `tool:gate` work onto\n * consumers that prefer the model to use `shell` + classic Unix tools.\n */\nexport const basicTools = { shell, readFile, writeFile, listFiles, edit, multiEdit }\n\nexport default definePreset({\n name: 'basic',\n system: 'You are a helpful assistant with access to shell, file reading, file writing, surgical and multi-edit tools, directory listing, and sub-agent spawning. Prefer `edit` / `multi_edit` for in-place changes and `write_file` for full file overwrites. Use them to accomplish tasks in the project directory.',\n // `persist: true` shares the parent's session with every child agent — child\n // turns land in `session.turns` tagged with their own `runId`, and the run\n // itself is recorded in `session.runs` with `parentRunId` + `depth`. That's\n // what lets a reloaded TUI session reconstruct the full subagent tree (see\n // `eventsFromTurns` in `tui/store.ts`). Hosts that want children in-memory\n // only can construct their own preset with `createSpawnTool()`.\n tools: { ...basicTools, spawn: createSpawnTool({ persist: true }) },\n})\n","import type { AgentHooks, AgentOptions } from '../agent'\n\nexport type { AgentHookMap } from '../agent'\n\n/**\n * A preset is a reusable slice of `AgentOptions` — spread it into `createAgent()`\n * to configure tools, a default system prompt, aliases, behavior defaults, and\n * agent-lifetime hooks.\n *\n * `provider`, `execution`, `session`, and internal fields are excluded so presets\n * remain shareable and composable.\n *\n * ```ts\n * import { basic } from 'zidane/presets'\n * createAgent({ ...basic, provider })\n * ```\n *\n * ### Composing multiple presets\n *\n * Bare `...spread` is shallow — `{ ...a, ...b }` overwrites every key `b`\n * defines, including `hooks`. Use {@link composePresets} when you want\n * field-aware merging (per-event hook concat, tools shallow-merge, etc.):\n *\n * ```ts\n * createAgent({ ...composePresets(basic, telemetry, mine), provider })\n * ```\n */\nexport type Preset = Omit<Partial<AgentOptions>, 'provider' | 'execution' | 'session' | 'mcpConnector'>\n\n/**\n * Identity helper for type inference when defining a preset.\n */\nexport function definePreset(config: Preset): Preset {\n return config\n}\n\n/**\n * Field-aware composition of presets. Right-most preset wins for scalar fields;\n * objects shallow-merge; arrays and hook handler lists concatenate. Designed so\n * stacking presets does the obvious thing without the spread-collision footgun:\n *\n * - `name`, `system`, `eager`, `skills` → last-defined wins\n * - `tools`, `toolAliases`, `behavior` → shallow-merge (later keys override)\n * - `behavior.dedupTools`, `behavior.toolBudgets` → **deep-merge** (per-tool-name; later wins on collision)\n * - `mcpServers` → concat with last-wins on `name` collision\n * - `hooks` → per-event concat; every handler fires\n *\n * `hooks` always emerges as `event → handler[]` so downstream registration\n * (in `createAgent`) sees a uniform shape. Order of handlers within an event\n * follows preset order: earlier presets register first.\n *\n * `mcpServers` is deduped by `name` because shipping two servers with the same\n * name would trip the connector at runtime — a later preset overriding an\n * earlier preset's `github` server is the practical intent.\n *\n * `behavior.dedupTools` and `behavior.toolBudgets` get the same per-key deep-merge\n * because they are tool-name-keyed records — a preset that ships a dedup hasher\n * for one tool should not erase a hasher another preset ships for a different\n * tool. Last-wins still applies on a per-tool collision so a downstream preset\n * can override an upstream preset's policy for one specific tool. Other\n * `behavior` fields keep last-wins semantics.\n */\nexport function composePresets(...presets: Preset[]): Preset {\n const out: Preset = {}\n const hooksByEvent: { [K in keyof AgentHooks]?: AgentHooks[K][] } = {}\n // Keep mcpServers in source-order on first sight, but allow later\n // declarations to override earlier ones with the same `name`. A `Map`\n // keyed by name gives O(1) override + stable iteration.\n const mcpByName = new Map<string, NonNullable<Preset['mcpServers']>[number]>()\n\n for (const p of presets) {\n if (p.name !== undefined)\n out.name = p.name\n if (p.system !== undefined)\n out.system = p.system\n if (p.eager !== undefined)\n out.eager = p.eager\n if (p.skills !== undefined)\n out.skills = p.skills\n if (p.tools)\n out.tools = { ...out.tools, ...p.tools }\n if (p.toolAliases)\n out.toolAliases = { ...out.toolAliases, ...p.toolAliases }\n if (p.behavior) {\n // Top-level shallow-merge first; then deep-merge the two tool-name-keyed\n // sub-records so per-tool entries from earlier presets aren't clobbered.\n const merged: NonNullable<Preset['behavior']> = { ...out.behavior, ...p.behavior }\n if (out.behavior?.dedupTools || p.behavior.dedupTools) {\n merged.dedupTools = { ...out.behavior?.dedupTools, ...p.behavior.dedupTools }\n }\n if (out.behavior?.toolBudgets || p.behavior.toolBudgets) {\n merged.toolBudgets = { ...out.behavior?.toolBudgets, ...p.behavior.toolBudgets }\n }\n out.behavior = merged\n }\n if (p.mcpServers) {\n for (const server of p.mcpServers)\n mcpByName.set(server.name, server)\n }\n if (p.hooks) {\n for (const [event, handler] of Object.entries(p.hooks)) {\n if (handler === undefined)\n continue\n const list = Array.isArray(handler) ? handler : [handler]\n const key = event as keyof AgentHooks\n // Safe cast: we read the loose `AgentHookMap` shape (handler-or-array)\n // and re-emit only as arrays. Each `list` element matches the event's\n // handler signature by construction (the input was typed `AgentHookMap`).\n const bucket = (hooksByEvent[key] ??= []) as unknown[]\n bucket.push(...(list as unknown[]))\n }\n }\n }\n\n if (mcpByName.size > 0)\n out.mcpServers = [...mcpByName.values()]\n\n if (Object.keys(hooksByEvent).length > 0)\n out.hooks = hooksByEvent\n\n return out\n}\n\nexport { default as basic, basicTools } from './basic'\n"],"mappings":";;;;;;;;;;;AAaA,MAAa,aAAa;CAAE;CAAO;CAAU;CAAW;CAAW;CAAM;CAAW;AAEpF,IAAA,gBAAe,aAAa;CAC1B,MAAM;CACN,QAAQ;CAOR,OAAO;EAAE,GAAG;EAAY,OAAO,gBAAgB,EAAE,SAAS,MAAM,CAAC;EAAE;CACpE,CAAC;;;;;;ACOF,SAAgB,aAAa,QAAwB;CACnD,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6BT,SAAgB,eAAe,GAAG,SAA2B;CAC3D,MAAM,MAAc,EAAE;CACtB,MAAM,eAA8D,EAAE;CAItE,MAAM,4BAAY,IAAI,KAAwD;CAE9E,KAAK,MAAM,KAAK,SAAS;EACvB,IAAI,EAAE,SAAS,KAAA,GACb,IAAI,OAAO,EAAE;EACf,IAAI,EAAE,WAAW,KAAA,GACf,IAAI,SAAS,EAAE;EACjB,IAAI,EAAE,UAAU,KAAA,GACd,IAAI,QAAQ,EAAE;EAChB,IAAI,EAAE,WAAW,KAAA,GACf,IAAI,SAAS,EAAE;EACjB,IAAI,EAAE,OACJ,IAAI,QAAQ;GAAE,GAAG,IAAI;GAAO,GAAG,EAAE;GAAO;EAC1C,IAAI,EAAE,aACJ,IAAI,cAAc;GAAE,GAAG,IAAI;GAAa,GAAG,EAAE;GAAa;EAC5D,IAAI,EAAE,UAAU;GAGd,MAAM,SAA0C;IAAE,GAAG,IAAI;IAAU,GAAG,EAAE;IAAU;GAClF,IAAI,IAAI,UAAU,cAAc,EAAE,SAAS,YACzC,OAAO,aAAa;IAAE,GAAG,IAAI,UAAU;IAAY,GAAG,EAAE,SAAS;IAAY;GAE/E,IAAI,IAAI,UAAU,eAAe,EAAE,SAAS,aAC1C,OAAO,cAAc;IAAE,GAAG,IAAI,UAAU;IAAa,GAAG,EAAE,SAAS;IAAa;GAElF,IAAI,WAAW;;EAEjB,IAAI,EAAE,YACJ,KAAK,MAAM,UAAU,EAAE,YACrB,UAAU,IAAI,OAAO,MAAM,OAAO;EAEtC,IAAI,EAAE,OACJ,KAAK,MAAM,CAAC,OAAO,YAAY,OAAO,QAAQ,EAAE,MAAM,EAAE;GACtD,IAAI,YAAY,KAAA,GACd;GACF,MAAM,OAAO,MAAM,QAAQ,QAAQ,GAAG,UAAU,CAAC,QAAQ;GACzD,MAAM,MAAM;GAKZ,CADgB,aAAa,SAAS,EAAE,EACjC,KAAK,GAAI,KAAmB;;;CAKzC,IAAI,UAAU,OAAO,GACnB,IAAI,aAAa,CAAC,GAAG,UAAU,QAAQ,CAAC;CAE1C,IAAI,OAAO,KAAK,aAAa,CAAC,SAAS,GACrC,IAAI,QAAQ;CAEd,OAAO"}
package/dist/presets.d.ts CHANGED
@@ -1,3 +1,3 @@
1
- import { n as AgentHookMap } from "./agent-BXRCCHeq.js";
2
- import { a as basicTools, i as _default, n as composePresets, r as definePreset, t as Preset } from "./index-BPk8-Slm.js";
1
+ import { n as AgentHookMap } from "./agent-DHQAsdj6.js";
2
+ import { a as basicTools, i as _default, n as composePresets, r as definePreset, t as Preset } from "./index-CrqFoaQA.js";
3
3
  export { AgentHookMap, Preset, _default as basic, basicTools, composePresets, definePreset };
package/dist/presets.js CHANGED
@@ -1,2 +1,2 @@
1
- import { i as basic_default, n as definePreset, r as basicTools, t as composePresets } from "./presets-0_IRJAYF.js";
1
+ import { i as basic_default, n as definePreset, r as basicTools, t as composePresets } from "./presets-M8f6lDnW.js";
2
2
  export { basic_default as basic, basicTools, composePresets, definePreset };
@@ -1,2 +1,2 @@
1
- import { At as cerebras, Ct as OpenAICompatParams, Dt as OpenAIParams, Et as openaiCompat, Mt as anthropic, Ot as openai, St as OpenAICompatHttpError, Tt as mapOAIFinishReason, _t as sanitizeToolSchema, bt as openrouter, ct as StreamCallbacks, dt as ToolResult, ft as ToolSpec, gt as SchemaSanitizeResult, ht as SchemaSanitizeProfile, jt as AnthropicParams, kt as CerebrasParams, lt as StreamOptions, mt as SchemaSanitizeOptions, ot as Provider, pt as TurnResult, st as ProviderCapabilities, ut as ToolCall, vt as sanitizeToolSpecs, wt as classifyOpenAICompatError, xt as OpenAICompatAuthHeader, yt as OpenRouterParams } from "./agent-BXRCCHeq.js";
1
+ import { At as cerebras, Ct as OpenAICompatParams, Dt as OpenAIParams, Et as openaiCompat, Mt as anthropic, Ot as openai, St as OpenAICompatHttpError, Tt as mapOAIFinishReason, _t as sanitizeToolSchema, bt as openrouter, ct as StreamCallbacks, dt as ToolResult, ft as ToolSpec, gt as SchemaSanitizeResult, ht as SchemaSanitizeProfile, jt as AnthropicParams, kt as CerebrasParams, lt as StreamOptions, mt as SchemaSanitizeOptions, ot as Provider, pt as TurnResult, st as ProviderCapabilities, ut as ToolCall, vt as sanitizeToolSpecs, wt as classifyOpenAICompatError, xt as OpenAICompatAuthHeader, yt as OpenRouterParams } from "./agent-DHQAsdj6.js";
2
2
  export { AnthropicParams, CerebrasParams, OpenAICompatAuthHeader, OpenAICompatHttpError, OpenAICompatParams, OpenAIParams, OpenRouterParams, Provider, ProviderCapabilities, SchemaSanitizeOptions, SchemaSanitizeProfile, SchemaSanitizeResult, StreamCallbacks, StreamOptions, ToolCall, ToolResult, ToolSpec, TurnResult, anthropic, cerebras, classifyOpenAICompatError, mapOAIFinishReason, openai, openaiCompat, openrouter, sanitizeToolSchema, sanitizeToolSpecs };
@@ -1,4 +1,4 @@
1
- import { I as SessionStore } from "../agent-BXRCCHeq.js";
1
+ import { I as SessionStore } from "../agent-DHQAsdj6.js";
2
2
 
3
3
  //#region src/session/sqlite.d.ts
4
4
  interface SqliteStoreOptions {
package/dist/session.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- import { $ as fromOpenAI, B as createRemoteStore, F as SessionRun, I as SessionStore, J as autoDetectAndConvert, Kt as SessionContentBlock, L as createSession, M as CreateSessionOptions, N as Session, P as SessionData, Q as fromAnthropic, R as loadSession, Xt as SessionTurn, Yt as SessionMessage, at as createFileMapStore, et as toAnthropic, it as FileMapStoreOptions, nt as createMemoryStore, rt as FileMapAdapter, tt as toOpenAI, z as RemoteStoreOptions } from "./agent-BXRCCHeq.js";
1
+ import { $ as fromOpenAI, B as createRemoteStore, F as SessionRun, I as SessionStore, J as autoDetectAndConvert, Kt as SessionContentBlock, L as createSession, M as CreateSessionOptions, N as Session, P as SessionData, Q as fromAnthropic, R as loadSession, Xt as SessionTurn, Yt as SessionMessage, at as createFileMapStore, et as toAnthropic, it as FileMapStoreOptions, nt as createMemoryStore, rt as FileMapAdapter, tt as toOpenAI, z as RemoteStoreOptions } from "./agent-DHQAsdj6.js";
2
2
  export { CreateSessionOptions, FileMapAdapter, FileMapStoreOptions, RemoteStoreOptions, Session, SessionContentBlock, SessionData, SessionMessage, SessionRun, SessionStore, SessionTurn, autoDetectAndConvert, createFileMapStore, createMemoryStore, createRemoteStore, createSession, fromAnthropic, fromOpenAI, loadSession, toAnthropic, toOpenAI };
package/dist/skills.d.ts CHANGED
@@ -1,3 +1,3 @@
1
- import { A as SkillSource, D as SkillConfig, O as SkillDiagnostic, c as DeactivationReason, d as createSkillActivationState, j as SkillsConfig, k as SkillResource, l as SkillActivationState, o as ActivationVia, s as ActiveSkill, u as SkillActivationStateOptions } from "./agent-BXRCCHeq.js";
2
- import { S as installAllowedToolsGate, _ as inferSource, a as SkillValidationResult, b as buildCatalog, c as parseAllowedToolPattern, d as validateSkillName, f as resolveSkills, g as getDefaultScanPaths, h as discoverSkills, i as SkillValidationIssue, l as validateResourcePath, m as SourcedScanPath, n as writeSkillToDisk, o as isToolAllowedByUnion, p as interpolateShellCommands, r as writeSkillsToDisk, s as matchesAllowedTool, t as defineSkill, u as validateSkillForWrite, v as parseFrontmatter, x as IMPLICITLY_ALLOWED_SKILL_TOOLS, y as parseSkillFile } from "./index-CT5_p-3P.js";
1
+ import { A as SkillSource, D as SkillConfig, O as SkillDiagnostic, c as DeactivationReason, d as createSkillActivationState, j as SkillsConfig, k as SkillResource, l as SkillActivationState, o as ActivationVia, s as ActiveSkill, u as SkillActivationStateOptions } from "./agent-DHQAsdj6.js";
2
+ import { S as installAllowedToolsGate, _ as inferSource, a as SkillValidationResult, b as buildCatalog, c as parseAllowedToolPattern, d as validateSkillName, f as resolveSkills, g as getDefaultScanPaths, h as discoverSkills, i as SkillValidationIssue, l as validateResourcePath, m as SourcedScanPath, n as writeSkillToDisk, o as isToolAllowedByUnion, p as interpolateShellCommands, r as writeSkillsToDisk, s as matchesAllowedTool, t as defineSkill, u as validateSkillForWrite, v as parseFrontmatter, x as IMPLICITLY_ALLOWED_SKILL_TOOLS, y as parseSkillFile } from "./index-CHSaLab5.js";
3
3
  export { ActivationVia, ActiveSkill, DeactivationReason, IMPLICITLY_ALLOWED_SKILL_TOOLS, SkillActivationState, SkillActivationStateOptions, SkillConfig, SkillDiagnostic, SkillResource, SkillSource, SkillValidationIssue, SkillValidationResult, SkillsConfig, SourcedScanPath, buildCatalog, createSkillActivationState, defineSkill, discoverSkills, getDefaultScanPaths, inferSource, installAllowedToolsGate, interpolateShellCommands, isToolAllowedByUnion, matchesAllowedTool, parseAllowedToolPattern, parseFrontmatter, parseSkillFile, resolveSkills, validateResourcePath, validateSkillForWrite, validateSkillName, writeSkillToDisk, writeSkillsToDisk };
@@ -805,20 +805,13 @@ const IMAGE_OMITTED_MARKER = "[image omitted — model does not support vision]"
805
805
  const INTERRUPT_MESSAGE_FOR_TOOL_USE = "[Request interrupted by user for tool use]";
806
806
  /**
807
807
  * Canonical tool_result text emitted when a tool call is skipped because a
808
- * sibling sequential call errored or a steering message arrived between
809
- * iterations of {@link executeToolsSequential}. Distinguished from
810
- * {@link INTERRUPT_MESSAGE_FOR_TOOL_USE} so consumers can distinguish "user
808
+ * steering message arrived between dispatches inside
809
+ * {@link executeToolBatch}. Distinguished from
810
+ * {@link INTERRUPT_MESSAGE_FOR_TOOL_USE} so consumers can split "user
811
811
  * cancelled" from "framework superseded".
812
812
  */
813
813
  const TOOL_USE_SKIPPED_MESSAGE = "[Tool use skipped — superseded by user message]";
814
814
  /**
815
- * Canonical tool_result text emitted when the loop catches a sequential
816
- * sibling that threw and synthesizes follow-up results for the remaining
817
- * queued calls. Distinct from {@link TOOL_USE_SKIPPED_MESSAGE} so telemetry
818
- * can split "skipped by user steering" from "skipped after error".
819
- */
820
- const TOOL_USE_AFTER_ERROR_MESSAGE = "[Tool use skipped — previous tool call in batch threw]";
821
- /**
822
815
  * Compute the effective thinking budget for a given run-relative turn, given
823
816
  * the configured decay schedule. Pure helper — exported for tests and so
824
817
  * downstream tooling can preview decay curves without spinning up the loop.
@@ -1518,7 +1511,7 @@ async function executeTurn(ctx, turn) {
1518
1511
  usage: result.usage
1519
1512
  };
1520
1513
  }
1521
- const toolResults = ctx.toolExecution === "parallel" ? await executeToolsParallel(ctx, canonicalToolCalls, turnId) : await executeToolsSequential(ctx, canonicalToolCalls, turnId);
1514
+ const toolResults = await executeToolBatch(ctx, canonicalToolCalls, turnId);
1522
1515
  const toolResultMsg = ctx.provider.toolResultsMessage(toolResults);
1523
1516
  const toolResultsTurn = {
1524
1517
  id: await ctx.generateTurnId(),
@@ -1859,57 +1852,170 @@ async function emitToolResult(ctx, params) {
1859
1852
  isError
1860
1853
  };
1861
1854
  }
1862
- async function executeToolsSequential(ctx, toolCalls, turnId) {
1863
- const results = [];
1864
- for (let i = 0; i < toolCalls.length; i++) {
1865
- const call = toolCalls[i];
1866
- if (ctx.signal.aborted) {
1867
- for (let j = i; j < toolCalls.length; j++) results.push({
1868
- id: toolCalls[j].id,
1869
- content: INTERRUPT_MESSAGE_FOR_TOOL_USE,
1870
- isError: true
1871
- });
1872
- return results;
1873
- }
1874
- if (ctx.steeringQueue.length > 0) {
1875
- for (let j = i; j < toolCalls.length; j++) results.push({
1876
- id: toolCalls[j].id,
1877
- content: TOOL_USE_SKIPPED_MESSAGE,
1878
- isError: true
1879
- });
1880
- return results;
1881
- }
1882
- try {
1883
- const { result } = await executeSingleTool(ctx, call, turnId);
1884
- results.push(result);
1885
- } catch (err) {
1886
- results.push({
1887
- id: call.id,
1888
- content: `Error: ${errorMessage(err)}`,
1889
- isError: true
1890
- });
1891
- for (let j = i + 1; j < toolCalls.length; j++) results.push({
1892
- id: toolCalls[j].id,
1893
- content: TOOL_USE_AFTER_ERROR_MESSAGE,
1894
- isError: true
1895
- });
1896
- return results;
1897
- }
1855
+ /** Default cap on in-flight tools per turn. Mirrors Claude Code's `CLAUDE_CODE_MAX_TOOL_USE_CONCURRENCY`. */
1856
+ const DEFAULT_MAX_CONCURRENT_TOOLS = 10;
1857
+ /** Canonical name of the shell tool — referenced for cascade-cancel semantics. */
1858
+ const SHELL_TOOL_NAME = "shell";
1859
+ /** Reason surfaced on `siblingAbort.signal` when a shell error cancels its fleet. */
1860
+ const SHELL_CASCADE_REASON = "sibling-shell-error";
1861
+ /**
1862
+ * Canonical `tool_result.content` text emitted to siblings that were
1863
+ * cancelled by a `shell` error in the same batch. Distinct from
1864
+ * {@link INTERRUPT_MESSAGE_FOR_TOOL_USE} (user-issued abort) and
1865
+ * {@link TOOL_USE_SKIPPED_MESSAGE} (steered) so consumers can split
1866
+ * the three causes by string-match.
1867
+ */
1868
+ const SHELL_CASCADE_CANCEL_MESSAGE = "Cancelled: a sibling `shell` call in the same batch errored; re-run independently if still needed.";
1869
+ /**
1870
+ * Resolve a tool's concurrency-safety verdict for a specific call.
1871
+ *
1872
+ * - Missing toolDef (unknown tool) → `false`. `executeSingleTool` handles
1873
+ * the unknown-tool path itself; barriering it keeps the unknown-tool
1874
+ * error from racing with siblings.
1875
+ * - Static `true` / `false` → use as-is.
1876
+ * - Function invoke; any throw is treated as `false` (fail-closed) so a
1877
+ * buggy predicate can't accidentally widen the safety window.
1878
+ *
1879
+ * Pure / sync — pre-computed once per call before dispatch begins, so the
1880
+ * scheduler's hot path stays branch-light.
1881
+ */
1882
+ function resolveConcurrencySafe(def, input) {
1883
+ if (!def) return false;
1884
+ const flag = def.isConcurrencySafe;
1885
+ if (flag === void 0) return false;
1886
+ if (typeof flag === "boolean") return flag;
1887
+ try {
1888
+ return flag(input) === true;
1889
+ } catch {
1890
+ return false;
1898
1891
  }
1899
- return results;
1900
1892
  }
1901
- async function executeToolsParallel(ctx, toolCalls, turnId) {
1902
- const executions = toolCalls.map((call) => executeSingleTool(ctx, call, turnId));
1903
- return (await Promise.allSettled(executions)).map((s, i) => {
1904
- if (s.status === "fulfilled") return s.value.result;
1905
- const reason = s.reason;
1906
- const isAbort = ctx.signal.aborted || reason instanceof Error && reason.name === "AbortError";
1907
- return {
1908
- id: toolCalls[i].id,
1909
- content: isAbort ? INTERRUPT_MESSAGE_FOR_TOOL_USE : `Error: ${reason instanceof Error ? reason.message : String(reason)}`,
1893
+ /**
1894
+ * Unified per-turn tool dispatcher.
1895
+ *
1896
+ * Walks `toolCalls` in submission order. For each call:
1897
+ *
1898
+ * - **Concurrency-safe + fleet is all safe + room under the cap** → fires
1899
+ * asynchronously and the loop advances to the next call. The fleet runs
1900
+ * in parallel up to `behavior.maxConcurrentTools` (default {@link
1901
+ * DEFAULT_MAX_CONCURRENT_TOOLS}).
1902
+ * - **Unsafe** (or the in-flight fleet contains anything unsafe) → acts
1903
+ * as a barrier: waits for the fleet to drain, then runs alone, then
1904
+ * unblocks the queue.
1905
+ *
1906
+ * Results are written into a fixed `results[index]` array on completion
1907
+ * and yielded back in submission order, so the model sees deterministic
1908
+ * adjacency regardless of which call finished first.
1909
+ *
1910
+ * **Failure modes:**
1911
+ *
1912
+ * - **Hook throws / tool body throws** — captured per-call into a
1913
+ * `tool_result` so the assistant turn's `tool_use` IDs always have
1914
+ * matching `tool_result` IDs (providers reject orphan IDs).
1915
+ * - **Parent abort** mid-batch — drains the in-flight fleet (their
1916
+ * `AbortError` becomes `INTERRUPT_MESSAGE_FOR_TOOL_USE`), then synthesizes
1917
+ * interrupt results for any unstarted calls so the turn closes cleanly.
1918
+ * - **Steering queue populated** between dispatches — same drain + a
1919
+ * `TOOL_USE_SKIPPED_MESSAGE` result for unstarted calls. The outer loop
1920
+ * picks up the steer at the next checkpoint.
1921
+ * - **Shell error in a fleet** — `siblingAbort.abort('sibling-shell-error')`
1922
+ * tears down concurrently-running siblings. Mirrors the convention that
1923
+ * shell commands often chain (`mkdir foo && cd foo`); one failing
1924
+ * sibling commonly invalidates the rest. Non-shell errors are isolated.
1925
+ *
1926
+ * A child `AbortController` (`siblingAbort`) forwards the parent abort
1927
+ * AND carries the shell-cascade signal — siblings see one signal source.
1928
+ */
1929
+ async function executeToolBatch(ctx, toolCalls, turnId) {
1930
+ if (toolCalls.length === 0) return [];
1931
+ const N = toolCalls.length;
1932
+ const maxConcurrent = Math.max(1, ctx.maxConcurrentTools ?? DEFAULT_MAX_CONCURRENT_TOOLS);
1933
+ const results = Array.from({ length: N });
1934
+ const safe = Array.from({ length: N });
1935
+ for (let i = 0; i < N; i++) safe[i] = resolveConcurrencySafe(ctx.tools[toolCalls[i].name], toolCalls[i].input);
1936
+ const siblingAbort = new AbortController();
1937
+ let parentAbortListener;
1938
+ if (ctx.signal.aborted) siblingAbort.abort(ctx.signal.reason ?? "parent-aborted");
1939
+ else {
1940
+ parentAbortListener = () => siblingAbort.abort(ctx.signal.reason ?? "parent-aborted");
1941
+ ctx.signal.addEventListener("abort", parentAbortListener, { once: true });
1942
+ }
1943
+ const childCtx = {
1944
+ ...ctx,
1945
+ signal: siblingAbort.signal
1946
+ };
1947
+ /** Indices currently in flight. Tracked for fleet-safety + cap checks. */
1948
+ const inFlight = /* @__PURE__ */ new Map();
1949
+ /**
1950
+ * Distinguish a shell-cascade kill from a user-issued abort so the
1951
+ * model sees actionable text. When BOTH the parent signal and the
1952
+ * sibling signal are aborted, the parent wins — user-issued aborts
1953
+ * take precedence (the model is being interrupted by the human, not
1954
+ * by a sibling's failure).
1955
+ */
1956
+ const cancelMessage = () => {
1957
+ if (ctx.signal.aborted) return INTERRUPT_MESSAGE_FOR_TOOL_USE;
1958
+ if (siblingAbort.signal.reason === SHELL_CASCADE_REASON) return SHELL_CASCADE_CANCEL_MESSAGE;
1959
+ return INTERRUPT_MESSAGE_FOR_TOOL_USE;
1960
+ };
1961
+ const dispatch = (index) => {
1962
+ const call = toolCalls[index];
1963
+ return executeSingleTool(childCtx, call, turnId).then(({ result }) => {
1964
+ results[index] = result;
1965
+ if (result.isError && call.name === SHELL_TOOL_NAME && !siblingAbort.signal.aborted) siblingAbort.abort(SHELL_CASCADE_REASON);
1966
+ }, (err) => {
1967
+ const isAbort = siblingAbort.signal.aborted || ctx.signal.aborted || err instanceof Error && err.name === "AbortError";
1968
+ results[index] = {
1969
+ id: call.id,
1970
+ content: isAbort ? cancelMessage() : `Error: ${errorMessage(err)}`,
1971
+ isError: true
1972
+ };
1973
+ }).finally(() => {
1974
+ inFlight.delete(index);
1975
+ });
1976
+ };
1977
+ const drain = async () => {
1978
+ if (inFlight.size > 0) await Promise.all([...inFlight.values()]);
1979
+ };
1980
+ /** Whether every in-flight call is concurrency-safe. */
1981
+ const fleetAllSafe = () => {
1982
+ for (const idx of inFlight.keys()) if (!safe[idx]) return false;
1983
+ return true;
1984
+ };
1985
+ /**
1986
+ * Fill all unstarted slots (`results[j]` still undefined) with the
1987
+ * canonical text + `isError: true`. Used at every short-circuit
1988
+ * branch (abort / steer) so the assistant turn's `tool_use` IDs
1989
+ * always have matching `tool_result` IDs — providers reject orphan
1990
+ * IDs loudly.
1991
+ */
1992
+ const fillUnstarted = (from, content) => {
1993
+ for (let j = from; j < N; j++) if (!results[j]) results[j] = {
1994
+ id: toolCalls[j].id,
1995
+ content,
1910
1996
  isError: true
1911
1997
  };
1912
- });
1998
+ };
1999
+ try {
2000
+ for (let i = 0; i < N; i++) {
2001
+ if (!safe[i] || !fleetAllSafe() || inFlight.size >= maxConcurrent) await drain();
2002
+ if (ctx.signal.aborted) {
2003
+ await drain();
2004
+ fillUnstarted(i, INTERRUPT_MESSAGE_FOR_TOOL_USE);
2005
+ return results;
2006
+ }
2007
+ if (ctx.steeringQueue.length > 0) {
2008
+ await drain();
2009
+ fillUnstarted(i, TOOL_USE_SKIPPED_MESSAGE);
2010
+ return results;
2011
+ }
2012
+ inFlight.set(i, dispatch(i));
2013
+ }
2014
+ await drain();
2015
+ return results;
2016
+ } finally {
2017
+ if (parentAbortListener) ctx.signal.removeEventListener("abort", parentAbortListener);
2018
+ }
1913
2019
  }
1914
2020
  //#endregion
1915
2021
  //#region src/prompt.ts
@@ -2129,6 +2235,7 @@ function looksBinary(text, sniffBytes = SNIFF_BYTES) {
2129
2235
  function createSkillsReadTool(options) {
2130
2236
  const byName = new Map(options.catalog.map((s) => [s.name, s]));
2131
2237
  return {
2238
+ isConcurrencySafe: true,
2132
2239
  spec: {
2133
2240
  name: "skills_read",
2134
2241
  description: "Read a bundled resource file from an active skill. The skill must have been activated via skills_use first. Path is relative to the skill's directory (e.g. \"references/REFERENCE.md\", \"assets/template.txt\").",
@@ -2392,6 +2499,7 @@ function createToolSearchTool(options) {
2392
2499
  }
2393
2500
  const maxLimit = Math.max(options.catalog.length, 1);
2394
2501
  return {
2502
+ isConcurrencySafe: true,
2395
2503
  spec: {
2396
2504
  name: "tool_search",
2397
2505
  description: "Discover and load schemas for additional tools listed in <searchable_tools>. Tools listed there are advertised by name + description only — their input schemas are not loaded into context until you surface them through this tool. Pass `query` for a substring search, `names` to load specific tools, or `server` to load every tool from one MCP server. Returned tools become callable for the rest of this run.",
@@ -2607,7 +2715,7 @@ async function synthesizeMissingToolResults(turns, syntheticTurnId, runId, provi
2607
2715
  }
2608
2716
  function resolveBehavior(agentBehavior, runBehavior) {
2609
2717
  return {
2610
- toolExecution: runBehavior?.toolExecution ?? agentBehavior?.toolExecution ?? "parallel",
2718
+ maxConcurrentTools: runBehavior?.maxConcurrentTools ?? agentBehavior?.maxConcurrentTools,
2611
2719
  maxTurns: runBehavior?.maxTurns ?? agentBehavior?.maxTurns,
2612
2720
  maxTokens: runBehavior?.maxTokens ?? agentBehavior?.maxTokens,
2613
2721
  thinkingBudget: runBehavior?.thinkingBudget ?? agentBehavior?.thinkingBudget,
@@ -2917,7 +3025,7 @@ function createAgent({ provider, name: agentName, system: agentSystem, tools: ag
2917
3025
  const thinking = options.thinking ?? "off";
2918
3026
  const model = options.model ?? provider.meta.defaultModel;
2919
3027
  const resolvedBehavior = resolveBehavior(agentBehavior, options.behavior);
2920
- const { toolExecution, maxTurns, maxTokens, thinkingBudget, schema, cache, toolOutputBudget, compactStrategy, compactThreshold, compactKeepTurns, thinkingDecay, dedupTools, toolBudgets, elideStaleReads, toolDisclosure, toolSearch, persistThreshold, persistExcludeTools, persistDir, strictToolPairing } = resolvedBehavior;
3028
+ const { maxConcurrentTools, maxTurns, maxTokens, thinkingBudget, schema, cache, toolOutputBudget, compactStrategy, compactThreshold, compactKeepTurns, thinkingDecay, dedupTools, toolBudgets, elideStaleReads, toolDisclosure, toolSearch, persistThreshold, persistExcludeTools, persistDir, strictToolPairing } = resolvedBehavior;
2921
3029
  let system = options.system || agentSystem || "You are a helpful assistant.";
2922
3030
  if (skillsCatalog) system = `${system}\n\n${skillsCatalog}`;
2923
3031
  const runBaseTools = options.tools !== void 0 ? options.tools : mcpConnection ? {
@@ -3080,7 +3188,7 @@ function createAgent({ provider, name: agentName, system: agentSystem, tools: ag
3080
3188
  model,
3081
3189
  system,
3082
3190
  thinking,
3083
- toolExecution,
3191
+ ...maxConcurrentTools !== void 0 ? { maxConcurrentTools } : {},
3084
3192
  signal: abortController.signal,
3085
3193
  execution: executionContext,
3086
3194
  handle: executionHandle,
@@ -3760,6 +3868,7 @@ async function globViaShell(pattern, ctx, limit) {
3760
3868
  return result.stdout.split("\n").filter((line) => line.length > 0);
3761
3869
  }
3762
3870
  const glob = {
3871
+ isConcurrencySafe: true,
3763
3872
  spec: {
3764
3873
  name: "glob",
3765
3874
  description: "Match files by glob pattern (supports **, *, ?). Relative to the execution context cwd. By default each row is `<path>\\t<size-bytes>\\t<mtime-iso>`; set `metadata: false` for a plain newline-separated list of paths. Always sorted.",
@@ -3825,6 +3934,7 @@ const glob = {
3825
3934
  const DEFAULT_HEAD_LIMIT = 250;
3826
3935
  const DEFAULT_OUTPUT_MODE = "files_with_matches";
3827
3936
  const grep = {
3937
+ isConcurrencySafe: true,
3828
3938
  spec: {
3829
3939
  name: "grep",
3830
3940
  description: "Search file contents by regex. Returns matching paths (default), match content, or per-file counts. Backed by ripgrep when available with a Bun.Glob fallback for in-process runs.",
@@ -4060,6 +4170,7 @@ function createInteractionTool(options) {
4060
4170
  //#endregion
4061
4171
  //#region src/tools/list-files.ts
4062
4172
  const listFiles = {
4173
+ isConcurrencySafe: true,
4063
4174
  spec: {
4064
4175
  name: "list_files",
4065
4176
  description: "List files and directories at the given path (relative to project root).",
@@ -4244,6 +4355,7 @@ const DEFAULT_BYTE_CAP = 262144;
4244
4355
  */
4245
4356
  const DEFAULT_IMAGE_BYTE_CAP = 5 * 1024 * 1024;
4246
4357
  const readFile$1 = {
4358
+ isConcurrencySafe: true,
4247
4359
  spec: {
4248
4360
  name: "read_file",
4249
4361
  description: "Read a file by path. Returns lines [offset..offset+limit). Default offset=1, limit=2000. Each line is prefixed with its 1-indexed line number followed by a tab (e.g. `42\\tconst foo = bar`); the prefix is metadata, not part of the file. Mirrors Claude Code's `cat -n`-style compact output for token efficiency. A trailing footer explains how to read the rest when truncated. Binary files return a short marker rather than mojibake.",
@@ -4438,7 +4550,110 @@ function extractTrailingCommand(command) {
4438
4550
  * context's own default (30 s for in-process).
4439
4551
  */
4440
4552
  const DEFAULT_MAX_OUTPUT_BYTES = 32768;
4553
+ /**
4554
+ * Best-effort read-only allow-list for the leading command token. Members
4555
+ * are commands whose stock behavior cannot mutate the workspace under any
4556
+ * argument combination — `ls`, `cat`, `pwd`, etc. Commands that *can*
4557
+ * mutate depending on flags (`find -delete`, `git tag <name>`, `tar -x`)
4558
+ * are intentionally excluded; the input-aware {@link isReadOnlyShellCommand}
4559
+ * predicate falls back to the conservative "not safe" answer for them, so
4560
+ * the scheduler barriers them.
4561
+ */
4562
+ const SHELL_READ_ONLY_COMMANDS = new Set([
4563
+ "ls",
4564
+ "cat",
4565
+ "head",
4566
+ "tail",
4567
+ "wc",
4568
+ "pwd",
4569
+ "whoami",
4570
+ "id",
4571
+ "date",
4572
+ "uname",
4573
+ "hostname",
4574
+ "tty",
4575
+ "echo",
4576
+ "printf",
4577
+ "env",
4578
+ "printenv",
4579
+ "which",
4580
+ "type",
4581
+ "command",
4582
+ "file",
4583
+ "stat",
4584
+ "grep",
4585
+ "rg",
4586
+ "ag",
4587
+ "true",
4588
+ "false",
4589
+ "test"
4590
+ ]);
4591
+ /**
4592
+ * `git` subcommands that are pure reads regardless of arguments. Excludes
4593
+ * `branch`/`tag`/`remote` (which can mutate when given a name) and
4594
+ * `config` (which writes when given a value).
4595
+ */
4596
+ const GIT_READ_ONLY_SUBCOMMANDS = new Set([
4597
+ "status",
4598
+ "log",
4599
+ "diff",
4600
+ "show",
4601
+ "blame",
4602
+ "rev-parse",
4603
+ "ls-files",
4604
+ "ls-tree",
4605
+ "cat-file",
4606
+ "reflog",
4607
+ "shortlog",
4608
+ "describe",
4609
+ "rev-list",
4610
+ "name-rev",
4611
+ "whatchanged",
4612
+ "merge-base",
4613
+ "symbolic-ref"
4614
+ ]);
4615
+ /**
4616
+ * Conservative read-only verdict for a shell command — used to opt a
4617
+ * `shell` invocation into the scheduler's concurrent fleet. Returns
4618
+ * `false` (fail-closed) on anything ambiguous so the scheduler barriers
4619
+ * it. Specifically:
4620
+ *
4621
+ * - Rejects compound commands (`;`, `&&`, `||`, `|`) and redirects (`>`,
4622
+ * `>>`, `<`) — even a pipe to a read-only sink is treated as too
4623
+ * complex to analyze.
4624
+ * - Rejects subshell / process substitution (`$(...)`, `` `...` ``,
4625
+ * `<(...)`, `>(...)`).
4626
+ * - Skips leading `VAR=value` env assignments to find the real
4627
+ * command token.
4628
+ * - Strips a possible absolute path on the command (`/usr/bin/ls` → `ls`).
4629
+ * - Allows the command iff its base name is in
4630
+ * {@link SHELL_READ_ONLY_COMMANDS} OR it's `git <subcmd>` where
4631
+ * `<subcmd>` is in {@link GIT_READ_ONLY_SUBCOMMANDS}.
4632
+ *
4633
+ * Cheap (no spawned process; regex + token scan). Safe to call from the
4634
+ * hot scheduler path.
4635
+ */
4636
+ function isReadOnlyShellCommand(command) {
4637
+ if (typeof command !== "string") return false;
4638
+ const trimmed = command.trim();
4639
+ if (trimmed === "") return false;
4640
+ if (/[<>;&|`\n]/.test(trimmed)) return false;
4641
+ if (trimmed.includes("$(") || trimmed.includes("<(") || trimmed.includes(">(")) return false;
4642
+ const tokens = trimmed.split(/\s+/);
4643
+ let i = 0;
4644
+ while (i < tokens.length && /^[A-Z_]\w*=/i.test(tokens[i])) i++;
4645
+ const head = tokens[i];
4646
+ if (!head) return false;
4647
+ const base = head.split("/").pop() ?? head;
4648
+ if (SHELL_READ_ONLY_COMMANDS.has(base)) return true;
4649
+ if (base === "git") {
4650
+ const sub = tokens[i + 1];
4651
+ return typeof sub === "string" && GIT_READ_ONLY_SUBCOMMANDS.has(sub);
4652
+ }
4653
+ return false;
4654
+ }
4441
4655
  const shell = {
4656
+ isConcurrencySafe: (input) => isReadOnlyShellCommand(input.command),
4442
4657
  spec: {
4443
4658
  name: "shell",
4444
4659
  description: "Execute a shell command in the project root and return its combined stdout/stderr. Output is tail-priority truncated at 32 KiB by default; errors and exit-code summaries live in the tail. By default each call appends a `(exit N, Nms)` footer and surfaces non-empty stderr in a separate section even on success — set `metadata: false` to return only stdout. Set maxOutputBytes=0 to disable truncation.",
@@ -4715,6 +4930,7 @@ function createSpawnTool(options = {}) {
4715
4930
  get totalChildStats() {
4716
4931
  return { ...localStats };
4717
4932
  },
4933
+ isConcurrencySafe: true,
4718
4934
  spec: {
4719
4935
  name: "spawn",
4720
4936
  description: "Spawn a sub-agent for a self-contained task that benefits from isolation (separate context window, separate retries) — for example, a deep research dive or a long codegen pass on a specific file. The sub-agent runs independently with its own tool access and returns its final response. Do NOT spawn for sequential steps you could do yourself.",
@@ -4947,6 +5163,6 @@ const writeFile$1 = {
4947
5163
  }
4948
5164
  };
4949
5165
  //#endregion
4950
- export { readStateKey as A, PERSISTENCE_PREVIEW_BYTES as C, resolvePersistDir as D, maybePersistToolResult as E, getReadState as O, PERSISTED_STUB_PREFIX as S, cleanupPersistedSession as T, createSkillsReadTool as _, multiEdit as a, TOOL_USE_SKIPPED_MESSAGE as b, grep as c, resolveOldString as d, styleReplacementForVia as f, createSkillsRunScriptTool as g, createSkillsUseTool as h, readFile$1 as i, resolveReadStateMap as j, hashContent as k, glob as l, createToolSearchTool as m, createSpawnTool as n, listFiles as o, createAgent as p, shell as r, createInteractionTool as s, writeFile$1 as t, edit as u, INTERRUPT_MESSAGE_FOR_TOOL_USE as v, buildPersistedStub as w, validateToolArgs as x, TOOL_USE_AFTER_ERROR_MESSAGE as y };
5166
+ export { readStateKey as A, PERSISTENCE_PREVIEW_BYTES as C, resolvePersistDir as D, maybePersistToolResult as E, getReadState as O, PERSISTED_STUB_PREFIX as S, cleanupPersistedSession as T, createSkillsReadTool as _, multiEdit as a, TOOL_USE_SKIPPED_MESSAGE as b, grep as c, resolveOldString as d, styleReplacementForVia as f, createSkillsRunScriptTool as g, createSkillsUseTool as h, readFile$1 as i, resolveReadStateMap as j, hashContent as k, glob as l, createToolSearchTool as m, createSpawnTool as n, listFiles as o, createAgent as p, shell as r, createInteractionTool as s, writeFile$1 as t, edit as u, INTERRUPT_MESSAGE_FOR_TOOL_USE as v, buildPersistedStub as w, validateToolArgs as x, SHELL_CASCADE_CANCEL_MESSAGE as y };
4951
5167
 
4952
- //# sourceMappingURL=tools-CCsL5SCO.js.map
5168
+ //# sourceMappingURL=tools-DKdyPoUf.js.map