maestro-agent-sdk 0.1.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 (196) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE +24 -0
  3. package/README.md +133 -0
  4. package/dist/agents/contracts.d.ts +49 -0
  5. package/dist/agents/contracts.d.ts.map +1 -0
  6. package/dist/agents/contracts.js +2 -0
  7. package/dist/agents/contracts.js.map +1 -0
  8. package/dist/agents/rollout/shared.d.ts +24 -0
  9. package/dist/agents/rollout/shared.d.ts.map +1 -0
  10. package/dist/agents/rollout/shared.js +105 -0
  11. package/dist/agents/rollout/shared.js.map +1 -0
  12. package/dist/core/agent.d.ts +71 -0
  13. package/dist/core/agent.d.ts.map +1 -0
  14. package/dist/core/agent.js +22 -0
  15. package/dist/core/agent.js.map +1 -0
  16. package/dist/core/loop.d.ts +26 -0
  17. package/dist/core/loop.d.ts.map +1 -0
  18. package/dist/core/loop.js +317 -0
  19. package/dist/core/loop.js.map +1 -0
  20. package/dist/index.d.ts +49 -0
  21. package/dist/index.d.ts.map +1 -0
  22. package/dist/index.js +53 -0
  23. package/dist/index.js.map +1 -0
  24. package/dist/mcp/client.d.ts +79 -0
  25. package/dist/mcp/client.d.ts.map +1 -0
  26. package/dist/mcp/client.js +176 -0
  27. package/dist/mcp/client.js.map +1 -0
  28. package/dist/mcp/pool-cache.d.ts +103 -0
  29. package/dist/mcp/pool-cache.d.ts.map +1 -0
  30. package/dist/mcp/pool-cache.js +249 -0
  31. package/dist/mcp/pool-cache.js.map +1 -0
  32. package/dist/mcp/pool.d.ts +65 -0
  33. package/dist/mcp/pool.d.ts.map +1 -0
  34. package/dist/mcp/pool.js +86 -0
  35. package/dist/mcp/pool.js.map +1 -0
  36. package/dist/media/file-events.d.ts +8 -0
  37. package/dist/media/file-events.d.ts.map +1 -0
  38. package/dist/media/file-events.js +15 -0
  39. package/dist/media/file-events.js.map +1 -0
  40. package/dist/memory/active-task-template.d.ts +34 -0
  41. package/dist/memory/active-task-template.d.ts.map +1 -0
  42. package/dist/memory/active-task-template.js +63 -0
  43. package/dist/memory/active-task-template.js.map +1 -0
  44. package/dist/memory/compressor.d.ts +87 -0
  45. package/dist/memory/compressor.d.ts.map +1 -0
  46. package/dist/memory/compressor.js +164 -0
  47. package/dist/memory/compressor.js.map +1 -0
  48. package/dist/memory/hash.d.ts +17 -0
  49. package/dist/memory/hash.d.ts.map +1 -0
  50. package/dist/memory/hash.js +20 -0
  51. package/dist/memory/hash.js.map +1 -0
  52. package/dist/memory/prune.d.ts +117 -0
  53. package/dist/memory/prune.d.ts.map +1 -0
  54. package/dist/memory/prune.js +416 -0
  55. package/dist/memory/prune.js.map +1 -0
  56. package/dist/memory/reminder.d.ts +57 -0
  57. package/dist/memory/reminder.d.ts.map +1 -0
  58. package/dist/memory/reminder.js +57 -0
  59. package/dist/memory/reminder.js.map +1 -0
  60. package/dist/memory/scrubber.d.ts +28 -0
  61. package/dist/memory/scrubber.d.ts.map +1 -0
  62. package/dist/memory/scrubber.js +147 -0
  63. package/dist/memory/scrubber.js.map +1 -0
  64. package/dist/memory/token-estimate.d.ts +10 -0
  65. package/dist/memory/token-estimate.d.ts.map +1 -0
  66. package/dist/memory/token-estimate.js +69 -0
  67. package/dist/memory/token-estimate.js.map +1 -0
  68. package/dist/platform/config.d.ts +12 -0
  69. package/dist/platform/config.d.ts.map +1 -0
  70. package/dist/platform/config.js +54 -0
  71. package/dist/platform/config.js.map +1 -0
  72. package/dist/platform/jsonl.d.ts +15 -0
  73. package/dist/platform/jsonl.d.ts.map +1 -0
  74. package/dist/platform/jsonl.js +80 -0
  75. package/dist/platform/jsonl.js.map +1 -0
  76. package/dist/platform/lifecycle.d.ts +22 -0
  77. package/dist/platform/lifecycle.d.ts.map +1 -0
  78. package/dist/platform/lifecycle.js +60 -0
  79. package/dist/platform/lifecycle.js.map +1 -0
  80. package/dist/platform/logger.d.ts +26 -0
  81. package/dist/platform/logger.d.ts.map +1 -0
  82. package/dist/platform/logger.js +41 -0
  83. package/dist/platform/logger.js.map +1 -0
  84. package/dist/platform/mcp-config.d.ts +15 -0
  85. package/dist/platform/mcp-config.d.ts.map +1 -0
  86. package/dist/platform/mcp-config.js +8 -0
  87. package/dist/platform/mcp-config.js.map +1 -0
  88. package/dist/provider.d.ts +81 -0
  89. package/dist/provider.d.ts.map +1 -0
  90. package/dist/provider.js +444 -0
  91. package/dist/provider.js.map +1 -0
  92. package/dist/providers/anthropic.d.ts +132 -0
  93. package/dist/providers/anthropic.d.ts.map +1 -0
  94. package/dist/providers/anthropic.js +518 -0
  95. package/dist/providers/anthropic.js.map +1 -0
  96. package/dist/providers/base.d.ts +140 -0
  97. package/dist/providers/base.d.ts.map +1 -0
  98. package/dist/providers/base.js +2 -0
  99. package/dist/providers/base.js.map +1 -0
  100. package/dist/providers/deepseek.d.ts +118 -0
  101. package/dist/providers/deepseek.d.ts.map +1 -0
  102. package/dist/providers/deepseek.js +467 -0
  103. package/dist/providers/deepseek.js.map +1 -0
  104. package/dist/registry.d.ts +3 -0
  105. package/dist/registry.d.ts.map +1 -0
  106. package/dist/registry.js +94 -0
  107. package/dist/registry.js.map +1 -0
  108. package/dist/session-store.d.ts +133 -0
  109. package/dist/session-store.d.ts.map +1 -0
  110. package/dist/session-store.js +277 -0
  111. package/dist/session-store.js.map +1 -0
  112. package/dist/skills/curator.d.ts +104 -0
  113. package/dist/skills/curator.d.ts.map +1 -0
  114. package/dist/skills/curator.js +162 -0
  115. package/dist/skills/curator.js.map +1 -0
  116. package/dist/skills/index-builder.d.ts +42 -0
  117. package/dist/skills/index-builder.d.ts.map +1 -0
  118. package/dist/skills/index-builder.js +94 -0
  119. package/dist/skills/index-builder.js.map +1 -0
  120. package/dist/skills/loader.d.ts +107 -0
  121. package/dist/skills/loader.d.ts.map +1 -0
  122. package/dist/skills/loader.js +286 -0
  123. package/dist/skills/loader.js.map +1 -0
  124. package/dist/skills/preprocess.d.ts +45 -0
  125. package/dist/skills/preprocess.d.ts.map +1 -0
  126. package/dist/skills/preprocess.js +126 -0
  127. package/dist/skills/preprocess.js.map +1 -0
  128. package/dist/skills/usage.d.ts +75 -0
  129. package/dist/skills/usage.d.ts.map +1 -0
  130. package/dist/skills/usage.js +147 -0
  131. package/dist/skills/usage.js.map +1 -0
  132. package/dist/state/todos.d.ts +95 -0
  133. package/dist/state/todos.d.ts.map +1 -0
  134. package/dist/state/todos.js +198 -0
  135. package/dist/state/todos.js.map +1 -0
  136. package/dist/storage/conversations.d.ts +28 -0
  137. package/dist/storage/conversations.d.ts.map +1 -0
  138. package/dist/storage/conversations.js +8 -0
  139. package/dist/storage/conversations.js.map +1 -0
  140. package/dist/sub-agent/runner.d.ts +78 -0
  141. package/dist/sub-agent/runner.d.ts.map +1 -0
  142. package/dist/sub-agent/runner.js +215 -0
  143. package/dist/sub-agent/runner.js.map +1 -0
  144. package/dist/tools/builtin/agent.d.ts +33 -0
  145. package/dist/tools/builtin/agent.d.ts.map +1 -0
  146. package/dist/tools/builtin/agent.js +76 -0
  147. package/dist/tools/builtin/agent.js.map +1 -0
  148. package/dist/tools/builtin/bash.d.ts +11 -0
  149. package/dist/tools/builtin/bash.d.ts.map +1 -0
  150. package/dist/tools/builtin/bash.js +91 -0
  151. package/dist/tools/builtin/bash.js.map +1 -0
  152. package/dist/tools/builtin/edit.d.ts +21 -0
  153. package/dist/tools/builtin/edit.d.ts.map +1 -0
  154. package/dist/tools/builtin/edit.js +238 -0
  155. package/dist/tools/builtin/edit.js.map +1 -0
  156. package/dist/tools/builtin/read.d.ts +17 -0
  157. package/dist/tools/builtin/read.d.ts.map +1 -0
  158. package/dist/tools/builtin/read.js +139 -0
  159. package/dist/tools/builtin/read.js.map +1 -0
  160. package/dist/tools/builtin/sandbox.d.ts +16 -0
  161. package/dist/tools/builtin/sandbox.d.ts.map +1 -0
  162. package/dist/tools/builtin/sandbox.js +58 -0
  163. package/dist/tools/builtin/sandbox.js.map +1 -0
  164. package/dist/tools/builtin/skill_view.d.ts +37 -0
  165. package/dist/tools/builtin/skill_view.d.ts.map +1 -0
  166. package/dist/tools/builtin/skill_view.js +82 -0
  167. package/dist/tools/builtin/skill_view.js.map +1 -0
  168. package/dist/tools/builtin/todo_write.d.ts +29 -0
  169. package/dist/tools/builtin/todo_write.d.ts.map +1 -0
  170. package/dist/tools/builtin/todo_write.js +96 -0
  171. package/dist/tools/builtin/todo_write.js.map +1 -0
  172. package/dist/tools/builtin/web_fetch.d.ts +10 -0
  173. package/dist/tools/builtin/web_fetch.d.ts.map +1 -0
  174. package/dist/tools/builtin/web_fetch.js +150 -0
  175. package/dist/tools/builtin/web_fetch.js.map +1 -0
  176. package/dist/tools/builtin/write.d.ts +35 -0
  177. package/dist/tools/builtin/write.d.ts.map +1 -0
  178. package/dist/tools/builtin/write.js +70 -0
  179. package/dist/tools/builtin/write.js.map +1 -0
  180. package/dist/tools/file-state.d.ts +99 -0
  181. package/dist/tools/file-state.d.ts.map +1 -0
  182. package/dist/tools/file-state.js +133 -0
  183. package/dist/tools/file-state.js.map +1 -0
  184. package/dist/tools/hooks/sandbox-fs.d.ts +25 -0
  185. package/dist/tools/hooks/sandbox-fs.d.ts.map +1 -0
  186. package/dist/tools/hooks/sandbox-fs.js +48 -0
  187. package/dist/tools/hooks/sandbox-fs.js.map +1 -0
  188. package/dist/tools/registry.d.ts +102 -0
  189. package/dist/tools/registry.d.ts.map +1 -0
  190. package/dist/tools/registry.js +93 -0
  191. package/dist/tools/registry.js.map +1 -0
  192. package/dist/types.d.ts +109 -0
  193. package/dist/types.d.ts.map +1 -0
  194. package/dist/types.js +20 -0
  195. package/dist/types.js.map +1 -0
  196. package/package.json +72 -0
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Pluggable logger for the SDK.
3
+ *
4
+ * Upstream clawgram used `pino`. The SDK ships with a minimal console-backed
5
+ * default and exposes `setLogger()` so hosts can plug their own structured
6
+ * logger (pino, winston, bunyan, …). All SDK call sites import `logger` from
7
+ * this module and never assume a particular implementation.
8
+ *
9
+ * The interface mirrors pino's call shape — `(meta, msg)` or just `(msg)` —
10
+ * so existing call sites copied from clawgram work unchanged.
11
+ */
12
+ export interface Logger {
13
+ trace: LogFn;
14
+ debug: LogFn;
15
+ info: LogFn;
16
+ warn: LogFn;
17
+ error: LogFn;
18
+ fatal: LogFn;
19
+ }
20
+ export interface LogFn {
21
+ (obj: Record<string, unknown>, msg?: string): void;
22
+ (msg: string): void;
23
+ }
24
+ export declare function setLogger(custom: Logger): void;
25
+ export declare const logger: Logger;
26
+ //# sourceMappingURL=logger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/platform/logger.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,MAAM,WAAW,MAAM;IACrB,KAAK,EAAE,KAAK,CAAC;IACb,KAAK,EAAE,KAAK,CAAC;IACb,IAAI,EAAE,KAAK,CAAC;IACZ,IAAI,EAAE,KAAK,CAAC;IACZ,KAAK,EAAE,KAAK,CAAC;IACb,KAAK,EAAE,KAAK,CAAC;CACd;AAED,MAAM,WAAW,KAAK;IACpB,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACnD,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;CACrB;AA0BD,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAE9C;AAED,eAAO,MAAM,MAAM,EAAE,MAInB,CAAC"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Pluggable logger for the SDK.
3
+ *
4
+ * Upstream clawgram used `pino`. The SDK ships with a minimal console-backed
5
+ * default and exposes `setLogger()` so hosts can plug their own structured
6
+ * logger (pino, winston, bunyan, …). All SDK call sites import `logger` from
7
+ * this module and never assume a particular implementation.
8
+ *
9
+ * The interface mirrors pino's call shape — `(meta, msg)` or just `(msg)` —
10
+ * so existing call sites copied from clawgram work unchanged.
11
+ */
12
+ function makeConsoleLogger() {
13
+ const emit = (level) => (objOrMsg, msg) => {
14
+ if (typeof objOrMsg === "string") {
15
+ // biome-ignore lint/suspicious/noConsole: SDK default logger
16
+ console[level](`[${level}] ${objOrMsg}`);
17
+ }
18
+ else {
19
+ // biome-ignore lint/suspicious/noConsole: SDK default logger
20
+ console[level](`[${level}] ${msg ?? ""}`, objOrMsg);
21
+ }
22
+ };
23
+ return {
24
+ trace: emit("log"),
25
+ debug: emit("log"),
26
+ info: emit("info"),
27
+ warn: emit("warn"),
28
+ error: emit("error"),
29
+ fatal: emit("error"),
30
+ };
31
+ }
32
+ let _logger = makeConsoleLogger();
33
+ export function setLogger(custom) {
34
+ _logger = custom;
35
+ }
36
+ export const logger = new Proxy({}, {
37
+ get(_target, prop) {
38
+ return _logger[prop];
39
+ },
40
+ });
41
+ //# sourceMappingURL=logger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/platform/logger.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAgBH,SAAS,iBAAiB;IACxB,MAAM,IAAI,GACR,CAAC,KAAwC,EAAE,EAAE,CAC7C,CAAC,QAA0C,EAAE,GAAY,EAAE,EAAE;QAC3D,IAAI,OAAO,QAAQ,KAAK,QAAQ,EAAE,CAAC;YACjC,6DAA6D;YAC7D,OAAO,CAAC,KAAK,CAAC,CAAC,IAAI,KAAK,KAAK,QAAQ,EAAE,CAAC,CAAC;QAC3C,CAAC;aAAM,CAAC;YACN,6DAA6D;YAC7D,OAAO,CAAC,KAAK,CAAC,CAAC,IAAI,KAAK,KAAK,GAAG,IAAI,EAAE,EAAE,EAAE,QAAQ,CAAC,CAAC;QACtD,CAAC;IACH,CAAC,CAAC;IACJ,OAAO;QACL,KAAK,EAAE,IAAI,CAAC,KAAK,CAAU;QAC3B,KAAK,EAAE,IAAI,CAAC,KAAK,CAAU;QAC3B,IAAI,EAAE,IAAI,CAAC,MAAM,CAAU;QAC3B,IAAI,EAAE,IAAI,CAAC,MAAM,CAAU;QAC3B,KAAK,EAAE,IAAI,CAAC,OAAO,CAAU;QAC7B,KAAK,EAAE,IAAI,CAAC,OAAO,CAAU;KAC9B,CAAC;AACJ,CAAC;AAED,IAAI,OAAO,GAAW,iBAAiB,EAAE,CAAC;AAE1C,MAAM,UAAU,SAAS,CAAC,MAAc;IACtC,OAAO,GAAG,MAAM,CAAC;AACnB,CAAC;AAED,MAAM,CAAC,MAAM,MAAM,GAAW,IAAI,KAAK,CAAC,EAAY,EAAE;IACpD,GAAG,CAAC,OAAO,EAAE,IAAkB;QAC7B,OAAO,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,CAAC;CACF,CAAC,CAAC"}
@@ -0,0 +1,15 @@
1
+ import type { MaestroMcpServerSpec } from "../mcp/client.js";
2
+ import type { AgentQueryOptions } from "../types.js";
3
+ /**
4
+ * Host-provided resolver: returns the MCP servers the loop should attach for
5
+ * a given query, keyed by server name. The SDK's MCP pool spawns them with
6
+ * the cache key derived from `(userId, session, groupId, agentKind)`.
7
+ *
8
+ * The SDK ships **no defaults** — hosts call `setMcpResolver()` to plug in
9
+ * their own, or skip it and run without MCP.
10
+ */
11
+ export type McpServerMap = Record<string, MaestroMcpServerSpec>;
12
+ export type McpResolver = (opts: AgentQueryOptions) => McpServerMap;
13
+ export declare function setMcpResolver(resolver: McpResolver): void;
14
+ export declare function getMcpServersForQuery(opts: AgentQueryOptions): McpServerMap;
15
+ //# sourceMappingURL=mcp-config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-config.d.ts","sourceRoot":"","sources":["../../src/platform/mcp-config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AACzD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAEjD;;;;;;;GAOG;AACH,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAAC;AAChE,MAAM,MAAM,WAAW,GAAG,CAAC,IAAI,EAAE,iBAAiB,KAAK,YAAY,CAAC;AAIpE,wBAAgB,cAAc,CAAC,QAAQ,EAAE,WAAW,GAAG,IAAI,CAE1D;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,iBAAiB,GAAG,YAAY,CAE3E"}
@@ -0,0 +1,8 @@
1
+ let _resolver = () => ({});
2
+ export function setMcpResolver(resolver) {
3
+ _resolver = resolver;
4
+ }
5
+ export function getMcpServersForQuery(opts) {
6
+ return _resolver(opts);
7
+ }
8
+ //# sourceMappingURL=mcp-config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-config.js","sourceRoot":"","sources":["../../src/platform/mcp-config.ts"],"names":[],"mappings":"AAcA,IAAI,SAAS,GAAgB,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;AAExC,MAAM,UAAU,cAAc,CAAC,QAAqB;IAClD,SAAS,GAAG,QAAQ,CAAC;AACvB,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,IAAuB;IAC3D,OAAO,SAAS,CAAC,IAAI,CAAC,CAAC;AACzB,CAAC"}
@@ -0,0 +1,81 @@
1
+ import type { Provider } from "./providers/base.js";
2
+ import type { AgentQueryOptions, UnifiedEvent } from "./types.js";
3
+ /**
4
+ * Maestro SDK provider (TS port of Maestro Agent v0.13.0).
5
+ *
6
+ * Multi-turn resume: when `opts.sessionId` is set we hydrate prior messages
7
+ * from `~/.maestro/sessions/<id>.jsonl` (written by the previous turn's
8
+ * persistence path or by a cross-agent rollout). Otherwise we mint a fresh
9
+ * UUIDv4 — matching the contract claude/codex providers expose, so the
10
+ * router stays agent-agnostic and `{type:"session", sessionId}` always comes
11
+ * back on the first iteration.
12
+ *
13
+ * After the loop drains we write the updated history back to disk so a
14
+ * subsequent call resumes correctly. Failures here are logged but never
15
+ * thrown — losing one persistence round is a degraded experience, not a
16
+ * stream-breaking one.
17
+ *
18
+ * MCP integration: every server in `getMcpServersForQuery(opts)` is leased
19
+ * from the process-wide cache (`mcp/pool-cache.ts`) and its tools are
20
+ * registered under `mcp__<server>__<tool>` — the same name convention Claude
21
+ * SDK uses, so the model sees consistent tool names across providers in the
22
+ * same topic. Unlike claudeProvider / codexProvider — which hand `mcpServers`
23
+ * to a vendor SDK that owns MCP lifecycle internally — Maestro hits Anthropic's
24
+ * Messages API via raw fetch, so we own startup + dispatch + cleanup here.
25
+ * Cache key includes (userId, session, groupId) so two users never share an
26
+ * MCP client; the cache evicts idle clients via TTL + LRU cap so stale slots
27
+ * don't accumulate.
28
+ *
29
+ * Snapshot pinned to upstream Maestro v0.13.0 (MIT, Nous Research). See
30
+ * docs/maestro-integration.md for the porting roadmap.
31
+ */
32
+ export declare function maestroProvider(opts: AgentQueryOptions): AsyncGenerator<UnifiedEvent>;
33
+ /**
34
+ * Compose the "Tool iterations remaining: N/M — <tone>" line that rides
35
+ * inside the per-iteration `<system-reminder>` block. Tone shifts with
36
+ * ABSOLUTE remaining count (not percentage of maxIter) — same threshold
37
+ * fires the same urgency regardless of effort level, so the model gets a
38
+ * consistent cue from a `low` run reaching 4-left as from a `xhigh` run
39
+ * reaching 4-left.
40
+ *
41
+ * Why absolute, not relative: at `low` (maxIter=5) the model crosses the
42
+ * wrap-up line almost immediately; at `xhigh` (maxIter=90) 75% left lasts
43
+ * ~22 turns. The user-visible behavior the threshold is targeting is "how
44
+ * many tool calls before I MUST stop" — that's an absolute count, not a
45
+ * fraction.
46
+ *
47
+ * Thresholds:
48
+ * - >= 10 → plenty of room. (no urgency)
49
+ * - 5..9 → pace yourself.
50
+ * - 2..4 → start wrapping up — consolidate, avoid new tool calls.
51
+ * - 0..1 → finalize NOW. Stop tooling, write the answer.
52
+ *
53
+ * Imperative phrasing matters: passing a bare count ("3 remaining") gets
54
+ * acknowledged but doesn't change behavior much. Pairing the count with a
55
+ * verb the model can act on ("start wrapping up", "finalize NOW") is what
56
+ * shifts the next-token distribution toward "emit final answer" instead
57
+ * of "call another tool". Exported so sub-agent / tests can share the
58
+ * exact phrasing if they need parity.
59
+ */
60
+ export declare function iterationBudgetLine(remaining: number, max: number): string;
61
+ /**
62
+ * Pick the right provider adapter for a resolved model id. DeepSeek's V4
63
+ * family uses `deepseek-*` ids; everything else (Anthropic claude-* + future
64
+ * direct full ids) falls through to the Anthropic adapter. Exported so tests
65
+ * can lock the dispatch shape independently of `maestroProvider`'s I/O.
66
+ */
67
+ export declare function providerForModel(resolvedModel: string): Provider;
68
+ /**
69
+ * Recognize the multiple shapes Node + WHATWG fetch use when a request is
70
+ * cancelled via `AbortController`:
71
+ * - DOMException with name "AbortError" (fetch / EventSource)
72
+ * - Error with name "AbortError" (older Node paths)
73
+ * - DOMException with code 20 (legacy ABORT_ERR code)
74
+ *
75
+ * Catches both so the abort detection isn't tied to a single runtime.
76
+ *
77
+ * Exported for unit coverage — used internally by `maestroProvider`'s catch
78
+ * branch to distinguish a user-initiated abort from a real provider crash.
79
+ */
80
+ export declare function isAbortError(err: unknown): boolean;
81
+ //# sourceMappingURL=provider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../src/provider.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,QAAQ,EAAyC,MAAM,kBAAkB,CAAC;AA0BxF,OAAO,KAAK,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAE/D;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAuB,eAAe,CAAC,IAAI,EAAE,iBAAiB,GAAG,cAAc,CAAC,YAAY,CAAC,CAsV5F;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAY1E;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,aAAa,EAAE,MAAM,GAAG,QAAQ,CAKhE;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAMlD"}
@@ -0,0 +1,444 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { AIAgent } from "./core/agent.js";
3
+ import { runConversation } from "./core/loop.js";
4
+ import { registerMcpTools, startMcpPool } from "./mcp/pool.js";
5
+ import { buildSystemReminder } from "./memory/reminder.js";
6
+ import { AnthropicProvider, effortToMaxIter, effortToThinkingBudget, } from "./providers/anthropic.js";
7
+ import { DeepseekProvider } from "./providers/deepseek.js";
8
+ import { maestroRegistry } from "./registry.js";
9
+ import { isWellFormedMessage, loadMaestroSession, saveMaestroSession, trimToSafePrefix, } from "./session-store.js";
10
+ import { curateSkills } from "./skills/curator.js";
11
+ import { buildSkillsIndex } from "./skills/index-builder.js";
12
+ import { loadSkillsCached } from "./skills/loader.js";
13
+ import { getTodoStore } from "./state/todos.js";
14
+ import { createAgentTool } from "./tools/builtin/agent.js";
15
+ import { bashTool } from "./tools/builtin/bash.js";
16
+ import { createEditTool } from "./tools/builtin/edit.js";
17
+ import { createReadTool } from "./tools/builtin/read.js";
18
+ import { createSkillViewTool } from "./tools/builtin/skill_view.js";
19
+ import { createTodoWriteTool } from "./tools/builtin/todo_write.js";
20
+ import { webFetchTool } from "./tools/builtin/web_fetch.js";
21
+ import { createWriteTool } from "./tools/builtin/write.js";
22
+ import { getFileStateTracker } from "./tools/file-state.js";
23
+ import { createSandboxFsHook } from "./tools/hooks/sandbox-fs.js";
24
+ import { ToolRegistry } from "./tools/registry.js";
25
+ import { logger } from "./platform/logger.js";
26
+ import { getMcpServersForQuery } from "./platform/mcp-config.js";
27
+ /**
28
+ * Maestro SDK provider (TS port of Maestro Agent v0.13.0).
29
+ *
30
+ * Multi-turn resume: when `opts.sessionId` is set we hydrate prior messages
31
+ * from `~/.maestro/sessions/<id>.jsonl` (written by the previous turn's
32
+ * persistence path or by a cross-agent rollout). Otherwise we mint a fresh
33
+ * UUIDv4 — matching the contract claude/codex providers expose, so the
34
+ * router stays agent-agnostic and `{type:"session", sessionId}` always comes
35
+ * back on the first iteration.
36
+ *
37
+ * After the loop drains we write the updated history back to disk so a
38
+ * subsequent call resumes correctly. Failures here are logged but never
39
+ * thrown — losing one persistence round is a degraded experience, not a
40
+ * stream-breaking one.
41
+ *
42
+ * MCP integration: every server in `getMcpServersForQuery(opts)` is leased
43
+ * from the process-wide cache (`mcp/pool-cache.ts`) and its tools are
44
+ * registered under `mcp__<server>__<tool>` — the same name convention Claude
45
+ * SDK uses, so the model sees consistent tool names across providers in the
46
+ * same topic. Unlike claudeProvider / codexProvider — which hand `mcpServers`
47
+ * to a vendor SDK that owns MCP lifecycle internally — Maestro hits Anthropic's
48
+ * Messages API via raw fetch, so we own startup + dispatch + cleanup here.
49
+ * Cache key includes (userId, session, groupId) so two users never share an
50
+ * MCP client; the cache evicts idle clients via TTL + LRU cap so stale slots
51
+ * don't accumulate.
52
+ *
53
+ * Snapshot pinned to upstream Maestro v0.13.0 (MIT, Nous Research). See
54
+ * docs/maestro-integration.md for the porting roadmap.
55
+ */
56
+ export async function* maestroProvider(opts) {
57
+ // Provider instantiation is deferred until after model resolution so the
58
+ // right adapter (Anthropic / DeepSeek) is chosen based on the resolved
59
+ // model id. The env-var check happens at fromEnv() time inside each
60
+ // adapter — we surface its error as a normal `error` UnifiedEvent so the
61
+ // dispatcher doesn't see a synthetic crash.
62
+ // Resolve sessionId up-front so per-session resources (file-state tracker,
63
+ // skill_view) can key off a stable id. Either supplied by the caller
64
+ // (resume / cross-agent bridge) or minted now for a fresh session.
65
+ const sessionId = opts.sessionId ?? randomUUID();
66
+ // Per-session file-state tracker drives the Read-before-Edit gate. Module-
67
+ // level registry (see tools/file-state.ts) keeps the tracker alive across
68
+ // turns so a Read in turn N is still recorded when an Edit fires in N+1.
69
+ const fileTracker = getFileStateTracker(sessionId);
70
+ // Per-session TodoWrite store. Same module-level cache pattern — the
71
+ // store hydrates from `~/.maestro/sessions/<sid>.todos.json` on first
72
+ // access so a multi-turn plan survives across calls.
73
+ const todoStore = getTodoStore(sessionId);
74
+ const tools = new ToolRegistry();
75
+ // Filesystem sandbox runs as a PreToolUse hook so every tool with a
76
+ // `file_path` argument (Read/Write/Edit + future FS-touching MCP tools)
77
+ // shares one gate. Registered first so it fires before any caller-added
78
+ // hook in this turn.
79
+ tools.use(createSandboxFsHook());
80
+ tools.register(bashTool);
81
+ // Read/Write/Edit/WebFetch — claude SDK parity builtins. Same name + schema
82
+ // so the model's pretrained instinct calls them with the right shape, and
83
+ // prompt cache keys line up across agents when a topic is bridged.
84
+ // Read/Write/Edit also gate on the workspace sandbox (inline today; future
85
+ // Phase 2.1 migrates to a hook) and on the per-session file-state tracker
86
+ // so Edit can't mutate a path that hasn't been Read in this session.
87
+ tools.register(createReadTool({ tracker: fileTracker }));
88
+ tools.register(createWriteTool({ tracker: fileTracker }));
89
+ tools.register(createEditTool({ tracker: fileTracker }));
90
+ tools.register(webFetchTool);
91
+ tools.register(createTodoWriteTool({ store: todoStore }));
92
+ // --- Skills: load SKILL.md catalog + register `skill_view` ---------------
93
+ //
94
+ // Domain accuracy lever (Phase 2). The model gets:
95
+ // - A `## Skills (mandatory)` block appended to the system prompt (60-char
96
+ // summary per skill) so prefix caching covers the catalog across turns.
97
+ // - A `skill_view(name)` builtin so it can pull the full SKILL.md body on
98
+ // demand (progressive disclosure — saves the per-turn cost of inlining
99
+ // every skill body).
100
+ //
101
+ // Source dir is picked from `MAESTRO_SKILL_DIR` first so power users can
102
+ // point at a Clawgram-local catalog later without code change; the v0.13.0
103
+ // upstream tree at `~/__KEEP_MAESTRO_AGENT__/skills/` is the current default.
104
+ //
105
+ // Failures (rootDir missing, unreadable, every file malformed) reduce to an
106
+ // empty catalog — the loop still runs with just bash + MCP tools.
107
+ const skillsDir = process.env.MAESTRO_SKILL_DIR ?? "/Users/maestrobot/__KEEP_MAESTRO_AGENT__/skills";
108
+ let skillsBlock = "";
109
+ // Hoisted to outer scope so the Agent tool (registered below, after
110
+ // model/effort resolve) can pass the same skill catalog to sub-agents.
111
+ let loadedSkills = [];
112
+ try {
113
+ const skills = loadSkillsCached(skillsDir);
114
+ loadedSkills = skills;
115
+ if (skills.length > 0) {
116
+ // Curator filters the catalog: archived skills (agent-created, never
117
+ // viewed, >60 days old) are dropped from the system-prompt index but
118
+ // stay reachable via skill_view by exact name. Bundled skills under
119
+ // the upstream snapshot directory are protected from archival.
120
+ // skill_view still sees the full set — model can resolve any name
121
+ // the user explicitly mentions even if it's been archived.
122
+ const curated = curateSkills(skills);
123
+ const visibleSkills = curated.map((c) => c.skill);
124
+ tools.register(createSkillViewTool({ skills, sessionId })); // full set
125
+ skillsBlock = buildSkillsIndex(visibleSkills);
126
+ logger.info({
127
+ agent: "maestro",
128
+ skillsDir,
129
+ skillCount: skills.length,
130
+ visibleCount: visibleSkills.length,
131
+ archivedCount: skills.length - visibleSkills.length,
132
+ }, "maestroProvider: skill catalog loaded (curated)");
133
+ }
134
+ }
135
+ catch (e) {
136
+ logger.warn({ err: e, skillsDir }, "maestroProvider: skill catalog load failed (degraded)");
137
+ }
138
+ // --- MCP pool: spawn every configured server, register their tools -------
139
+ //
140
+ // Failures here are logged but non-fatal: a turn can still serve from
141
+ // builtins alone if every server is unhealthy. This mirrors the partial-
142
+ // availability stance of the existing Playwright exit-propagation path,
143
+ // where one dead MCP doesn't take out the rest of the toolset.
144
+ let mcpPool = null;
145
+ try {
146
+ const servers = getMcpServersForQuery(opts);
147
+ // Scope cache to (userId, session, groupId, agentKind) so two users never
148
+ // share an MCP client (privacy) and so two sessions within one user keep
149
+ // their own playwright instance (forum/dm scope rules). The cache hashes
150
+ // the spec on top of this, so a server spec change inside the same scope
151
+ // also creates a fresh client.
152
+ mcpPool = await startMcpPool(servers, {
153
+ userId: opts.userId,
154
+ session: opts.session,
155
+ groupId: opts.groupId,
156
+ agentKind: "maestro",
157
+ });
158
+ registerMcpTools(tools, mcpPool, opts.abortController?.signal);
159
+ logger.info({
160
+ agent: "maestro",
161
+ mcpServerCount: mcpPool.clients.length,
162
+ mcpToolCount: mcpPool.tools.length,
163
+ }, "maestroProvider: MCP pool ready");
164
+ }
165
+ catch (e) {
166
+ logger.warn({ err: e }, "maestroProvider: MCP pool start failed — continuing without MCP");
167
+ }
168
+ const requestedModel = opts.model ?? maestroRegistry.defaultModel;
169
+ const resolvedModel = maestroRegistry.expandModelAlias(requestedModel);
170
+ // Resolve effort up-front (was previously deferred until after history
171
+ // hydration) so we can both build the initial system-reminder with the
172
+ // correct "iterations remaining: N/N" line and pass `maxIter` into
173
+ // `AIAgent` below. Default is the registry's `medium`, matching how
174
+ // claude-provider hands effort to its SDK when the caller doesn't pin one.
175
+ const resolvedEffort = opts.effort ?? maestroRegistry.defaultEffort;
176
+ const maxIter = effortToMaxIter(resolvedEffort);
177
+ // Pick provider by resolved model id prefix. DeepSeek models (`deepseek-*`)
178
+ // route to DeepseekProvider; anything else falls through to Anthropic. If
179
+ // the chosen adapter's env var is missing, close the MCP pool we already
180
+ // started before bailing so we don't leak subprocesses.
181
+ let provider;
182
+ try {
183
+ provider = providerForModel(resolvedModel);
184
+ }
185
+ catch (e) {
186
+ if (mcpPool) {
187
+ await mcpPool.close().catch((err) => {
188
+ logger.warn({ err }, "maestroProvider: mcp pool close after provider error failed");
189
+ });
190
+ }
191
+ yield {
192
+ type: "error",
193
+ content: e instanceof Error ? e.message : String(e),
194
+ };
195
+ return;
196
+ }
197
+ // --- Prior history hydration -------------------------------------------
198
+ //
199
+ // `sessionId` was already resolved at the top of this function so per-
200
+ // session resources (file-state tracker, skill_view) could be keyed off
201
+ // it. Three load cases for the persisted JSONL:
202
+ // 1. Caller supplied a sessionId AND file exists → resume.
203
+ // 2. Caller supplied a sessionId but file is missing/empty → keep the
204
+ // id (so cross-agent set_agent's pre-registered DB id stays valid)
205
+ // and start with empty history.
206
+ // 3. No sessionId → fresh UUIDv4 was minted above; no file to load.
207
+ const persisted = opts.sessionId ? loadMaestroSession(opts.sessionId) : null;
208
+ const priorMessages = (persisted ?? []).filter(isWellFormedMessage);
209
+ // Attach a `<system-reminder>` text block AFTER the user prompt on every
210
+ // new turn. Two reasons this lives at push time rather than on-wire:
211
+ // 1. Anthropic's automatic prompt cache breaks at the first byte
212
+ // divergence — past-turn user messages must be stable across future
213
+ // calls. Mutating wireMessages would make turn N's message differ
214
+ // between turn N's call (with reminder) and turn N+1's call (without
215
+ // reminder once canonical re-renders), nuking cache hits.
216
+ // 2. Compactor preserves the most-recent user message; the reminder
217
+ // rides along automatically. Historical turns keep the reminder
218
+ // that was true at THAT turn (sandbox could have flipped since) —
219
+ // drift across env-var changes is feature, not bug.
220
+ // Claude Code places `<system-reminder>` at the tail of user content; we
221
+ // match that order so the model's pretrained intuition treats the
222
+ // reminder as meta annotation, not as user intent.
223
+ // The reminder carries the iteration budget so the model can self-pace
224
+ // ("8 left → wrap up", "90 left → take your time"). Closure shared with
225
+ // the per-iteration builder below so first-turn and subsequent-turn
226
+ // reminders render with identical shape — the model sees the same fields
227
+ // in the same order, only the counts change.
228
+ const buildIterReminder = (iterationsRemaining) => buildSystemReminder({
229
+ sessionId,
230
+ todos: todoStore.list(),
231
+ extras: [iterationBudgetLine(iterationsRemaining, maxIter)],
232
+ });
233
+ const reminderText = buildIterReminder(maxIter);
234
+ const userBlocks = [
235
+ { type: "text", text: opts.prompt },
236
+ { type: "text", text: reminderText },
237
+ ];
238
+ const messages = [...priorMessages, { role: "user", content: userBlocks }];
239
+ // Emit the session event before the first provider call so the router's
240
+ // recorder captures it in the unified conversation log — same shape as
241
+ // claude/codex `init` and `thread.started` events.
242
+ yield { type: "session", sessionId };
243
+ // Skills index goes into the system prompt (NOT a user message) so
244
+ // Anthropic's prefix cache covers it across every turn — the catalog only
245
+ // changes when SKILL.md files on disk change, while user messages roll
246
+ // every turn. Append, don't prepend: caller-supplied systemPrompt is
247
+ // identity / instructions that should still anchor the prompt, and the
248
+ // skills block reads naturally as a final "and also, here's what tools
249
+ // you have access to" section.
250
+ const augmentedSystemPrompt = skillsBlock
251
+ ? `${opts.systemPrompt}\n\n${skillsBlock}`
252
+ : opts.systemPrompt;
253
+ // Register the `Agent` tool last — it captures the resolved model,
254
+ // effort, augmented system prompt (parent base for sub-agents), and the
255
+ // already-loaded skill catalog. Registered only on the PARENT call;
256
+ // sub-agents do NOT get an Agent tool because `runSubAgent` builds its
257
+ // own registry without registering one (advisor: depth=1 cap).
258
+ tools.register(createAgentTool({
259
+ parent: {
260
+ parentSessionId: sessionId,
261
+ parentSystemPrompt: augmentedSystemPrompt,
262
+ parentModel: resolvedModel,
263
+ ...(resolvedEffort ? { parentEffort: resolvedEffort } : {}),
264
+ ...(opts.abortController?.signal ? { parentAbortSignal: opts.abortController.signal } : {}),
265
+ skills: loadedSkills,
266
+ },
267
+ }));
268
+ const thinkingBudget = effortToThinkingBudget(resolvedEffort);
269
+ const agent = new AIAgent(provider, tools, {
270
+ model: resolvedModel,
271
+ systemPrompt: augmentedSystemPrompt,
272
+ // Effort-derived tool-iteration cap. The model sees the same number via
273
+ // the per-iteration `<system-reminder>` (see `buildIterReminder` above)
274
+ // so it can self-pace — low effort tells it to wrap up fast, xhigh
275
+ // gives it room to dig.
276
+ maxIterations: maxIter,
277
+ buildIterReminder,
278
+ ...(thinkingBudget ? { thinkingBudget } : {}),
279
+ ...(resolvedEffort ? { effort: resolvedEffort } : {}),
280
+ ...(opts.abortController?.signal ? { abortSignal: opts.abortController.signal } : {}),
281
+ });
282
+ // Wire abort → close MCP pool early. Without this, an aborted turn could
283
+ // leave Playwright / OCR subprocesses spinning until the finally block,
284
+ // which only runs after the loop's awaited operation completes.
285
+ const abortSignal = opts.abortController?.signal;
286
+ const onAbortClosePool = () => {
287
+ if (mcpPool) {
288
+ mcpPool.close().catch((err) => {
289
+ logger.warn({ err }, "maestroProvider: mcp pool close on abort failed");
290
+ });
291
+ }
292
+ };
293
+ abortSignal?.addEventListener("abort", onAbortClosePool, { once: true });
294
+ logger.info({
295
+ agent: "maestro",
296
+ model: resolvedModel,
297
+ effort: resolvedEffort,
298
+ maxIter,
299
+ thinkingBudget: thinkingBudget ?? null,
300
+ sessionId,
301
+ session: opts.session ?? null,
302
+ resumed: priorMessages.length > 0,
303
+ priorTurns: priorMessages.length,
304
+ }, "maestroProvider: starting run_conversation");
305
+ let drained = false;
306
+ let aborted = false;
307
+ try {
308
+ for await (const event of runConversation(agent, messages)) {
309
+ yield event;
310
+ }
311
+ drained = true;
312
+ }
313
+ catch (e) {
314
+ // Abort is a user-initiated signal, not a provider failure. claude/codex
315
+ // both silently return on AbortError (claude-provider relies on the SDK
316
+ // closing the stream, codex-provider catches `err.name === "AbortError"`
317
+ // and returns without yielding). Match that so the dispatcher doesn't
318
+ // see a synthetic "maestroProvider crashed: The operation was aborted"
319
+ // error event after the user simply moved on to a new prompt.
320
+ if (isAbortError(e) || abortSignal?.aborted) {
321
+ aborted = true;
322
+ }
323
+ else {
324
+ yield {
325
+ type: "error",
326
+ content: `maestroProvider crashed: ${e instanceof Error ? e.message : String(e)}`,
327
+ };
328
+ }
329
+ }
330
+ finally {
331
+ abortSignal?.removeEventListener("abort", onAbortClosePool);
332
+ if (mcpPool) {
333
+ await mcpPool.close();
334
+ }
335
+ // Always persist a safe prefix — even on partial drain (abort mid-tool,
336
+ // Anthropic API throw, MCP crash). Earlier versions gated on
337
+ // `drained === true` to avoid persisting half-finished tool rounds, but
338
+ // in practice that gate dropped the entire turn on every abort and
339
+ // users saw "maestro forgets everything I just said". The new contract:
340
+ // - clean drain → save messages verbatim (no change)
341
+ // - partial / crashed turn → `trimToSafePrefix` strips orphan
342
+ // user-prompt or assistant-tool_use trailing entries so the next
343
+ // resume passes Anthropic's tool_use/tool_result pairing check
344
+ // If the trim collapses everything (e.g. the only push before the
345
+ // crash was the new user prompt), we skip the write so the previous
346
+ // good checkpoint stays intact.
347
+ try {
348
+ const safePrefix = drained ? messages : trimToSafePrefix(messages);
349
+ if (safePrefix.length > 0) {
350
+ saveMaestroSession(sessionId, safePrefix);
351
+ if (!drained && safePrefix.length < messages.length) {
352
+ logger.info({
353
+ sessionId,
354
+ fullLength: messages.length,
355
+ savedLength: safePrefix.length,
356
+ dropped: messages.length - safePrefix.length,
357
+ aborted,
358
+ }, "maestroProvider: persisted trimmed prefix after partial turn");
359
+ }
360
+ }
361
+ }
362
+ catch (err) {
363
+ logger.warn({ err, sessionId, turns: messages.length }, "maestroProvider: persist failed (best-effort)");
364
+ }
365
+ }
366
+ }
367
+ /**
368
+ * Compose the "Tool iterations remaining: N/M — <tone>" line that rides
369
+ * inside the per-iteration `<system-reminder>` block. Tone shifts with
370
+ * ABSOLUTE remaining count (not percentage of maxIter) — same threshold
371
+ * fires the same urgency regardless of effort level, so the model gets a
372
+ * consistent cue from a `low` run reaching 4-left as from a `xhigh` run
373
+ * reaching 4-left.
374
+ *
375
+ * Why absolute, not relative: at `low` (maxIter=5) the model crosses the
376
+ * wrap-up line almost immediately; at `xhigh` (maxIter=90) 75% left lasts
377
+ * ~22 turns. The user-visible behavior the threshold is targeting is "how
378
+ * many tool calls before I MUST stop" — that's an absolute count, not a
379
+ * fraction.
380
+ *
381
+ * Thresholds:
382
+ * - >= 10 → plenty of room. (no urgency)
383
+ * - 5..9 → pace yourself.
384
+ * - 2..4 → start wrapping up — consolidate, avoid new tool calls.
385
+ * - 0..1 → finalize NOW. Stop tooling, write the answer.
386
+ *
387
+ * Imperative phrasing matters: passing a bare count ("3 remaining") gets
388
+ * acknowledged but doesn't change behavior much. Pairing the count with a
389
+ * verb the model can act on ("start wrapping up", "finalize NOW") is what
390
+ * shifts the next-token distribution toward "emit final answer" instead
391
+ * of "call another tool". Exported so sub-agent / tests can share the
392
+ * exact phrasing if they need parity.
393
+ */
394
+ export function iterationBudgetLine(remaining, max) {
395
+ let tone;
396
+ if (remaining >= 10) {
397
+ tone = "plenty of room.";
398
+ }
399
+ else if (remaining >= 5) {
400
+ tone = "pace yourself.";
401
+ }
402
+ else if (remaining >= 2) {
403
+ tone = "start wrapping up — consolidate findings, avoid new tool calls unless essential.";
404
+ }
405
+ else {
406
+ tone = "finalize NOW. Stop tooling and write the final answer.";
407
+ }
408
+ return `Tool iterations remaining: ${remaining}/${max} — ${tone}`;
409
+ }
410
+ /**
411
+ * Pick the right provider adapter for a resolved model id. DeepSeek's V4
412
+ * family uses `deepseek-*` ids; everything else (Anthropic claude-* + future
413
+ * direct full ids) falls through to the Anthropic adapter. Exported so tests
414
+ * can lock the dispatch shape independently of `maestroProvider`'s I/O.
415
+ */
416
+ export function providerForModel(resolvedModel) {
417
+ if (resolvedModel.startsWith("deepseek-")) {
418
+ return DeepseekProvider.fromEnv();
419
+ }
420
+ return AnthropicProvider.fromEnv();
421
+ }
422
+ /**
423
+ * Recognize the multiple shapes Node + WHATWG fetch use when a request is
424
+ * cancelled via `AbortController`:
425
+ * - DOMException with name "AbortError" (fetch / EventSource)
426
+ * - Error with name "AbortError" (older Node paths)
427
+ * - DOMException with code 20 (legacy ABORT_ERR code)
428
+ *
429
+ * Catches both so the abort detection isn't tied to a single runtime.
430
+ *
431
+ * Exported for unit coverage — used internally by `maestroProvider`'s catch
432
+ * branch to distinguish a user-initiated abort from a real provider crash.
433
+ */
434
+ export function isAbortError(err) {
435
+ if (!err || typeof err !== "object")
436
+ return false;
437
+ const e = err;
438
+ if (e.name === "AbortError")
439
+ return true;
440
+ if (e.code === 20 || e.code === "ABORT_ERR")
441
+ return true;
442
+ return false;
443
+ }
444
+ //# sourceMappingURL=provider.js.map