noumen 0.6.0 → 0.8.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 (137) hide show
  1. package/README.md +237 -93
  2. package/dist/a2a/index.d.ts +5 -7
  3. package/dist/a2a/index.js +3 -4
  4. package/dist/a2a/index.js.map +1 -1
  5. package/dist/acp/index.d.ts +5 -7
  6. package/dist/acp/index.js +0 -1
  7. package/dist/acp/index.js.map +1 -1
  8. package/dist/{agent-DWE4_P5X.d.ts → agent-D0gl-qYi.d.ts} +89 -34
  9. package/dist/{chunk-6MMYCGJQ.js → chunk-5HY4IYNT.js} +1529 -2321
  10. package/dist/chunk-5HY4IYNT.js.map +1 -0
  11. package/dist/chunk-BC5BLWBC.js +21 -0
  12. package/dist/chunk-BC5BLWBC.js.map +1 -0
  13. package/dist/{chunk-XZN4QZLK.js → chunk-CX4BL6PC.js} +25 -15
  14. package/dist/chunk-CX4BL6PC.js.map +1 -0
  15. package/dist/{chunk-5GEX6ZSB.js → chunk-HQISH4D7.js} +60 -1
  16. package/dist/chunk-HQISH4D7.js.map +1 -0
  17. package/dist/{chunk-Y45R3PQL.js → chunk-NUCJXOUV.js} +32 -18
  18. package/dist/{chunk-Y45R3PQL.js.map → chunk-NUCJXOUV.js.map} +1 -1
  19. package/dist/chunk-OPFFLQZL.js +40 -0
  20. package/dist/chunk-OPFFLQZL.js.map +1 -0
  21. package/dist/chunk-PDEAJ272.js +660 -0
  22. package/dist/chunk-PDEAJ272.js.map +1 -0
  23. package/dist/chunk-PKHLGGEC.js +115 -0
  24. package/dist/chunk-PKHLGGEC.js.map +1 -0
  25. package/dist/chunk-XQTNXRE7.js +176 -0
  26. package/dist/chunk-XQTNXRE7.js.map +1 -0
  27. package/dist/chunk-XZPAA5TO.js +817 -0
  28. package/dist/chunk-XZPAA5TO.js.map +1 -0
  29. package/dist/cli/index.js +77 -42
  30. package/dist/cli/index.js.map +1 -1
  31. package/dist/client/index.d.ts +1 -2
  32. package/dist/client/index.js +0 -2
  33. package/dist/client/index.js.map +1 -1
  34. package/dist/client-JJFLE6RT.js +9 -0
  35. package/dist/{computer-BPdxSo6X.d.ts → computer-DzMR92tK.d.ts} +1 -1
  36. package/dist/docker.d.ts +2 -2
  37. package/dist/docker.js +0 -1
  38. package/dist/docker.js.map +1 -1
  39. package/dist/e2b.d.ts +2 -2
  40. package/dist/e2b.js +0 -1
  41. package/dist/e2b.js.map +1 -1
  42. package/dist/freestyle.d.ts +2 -2
  43. package/dist/freestyle.js +0 -1
  44. package/dist/freestyle.js.map +1 -1
  45. package/dist/{headless-FFU2DESQ.js → headless-25DU4MJQ.js} +1 -3
  46. package/dist/{headless-FFU2DESQ.js.map → headless-25DU4MJQ.js.map} +1 -1
  47. package/dist/{history-snip-64GYP4ZL.js → history-snip-HAWNAYKY.js} +1 -2
  48. package/dist/index.d.ts +351 -72
  49. package/dist/index.js +54 -55
  50. package/dist/jsonrpc/index.js +0 -1
  51. package/dist/local.d.ts +168 -0
  52. package/dist/local.js +40 -0
  53. package/dist/local.js.map +1 -0
  54. package/dist/lsp/index.d.ts +4 -5
  55. package/dist/lsp/index.js +0 -1
  56. package/dist/{lsp-PS3BWIHC.js → lsp-3APWNKB2.js} +1 -2
  57. package/dist/{manager-DLXK63XC.js → manager-Z5EQ7YYV.js} +1 -2
  58. package/dist/mcp/index.d.ts +16 -8
  59. package/dist/mcp/index.js +5 -6
  60. package/dist/mcp/index.js.map +1 -1
  61. package/dist/{mcp-auth-AEI2R4ZC.js → mcp-auth-NOIQPF7W.js} +1 -2
  62. package/dist/{provider-factory-TUHU3DIG.js → provider-factory-KNBSHXJ6.js} +3 -3
  63. package/dist/{render-GRN4ZSSW.js → render-4VEODRK7.js} +1 -2
  64. package/dist/{resolve-6KUZNEYW.js → resolve-AGQZFMKD.js} +3 -3
  65. package/dist/sandbox-DAqQo0Tj.d.ts +49 -0
  66. package/dist/sandbox-index-ODNREIFA.js +32 -0
  67. package/dist/sandbox-index-ODNREIFA.js.map +1 -0
  68. package/dist/server/index.d.ts +18 -7
  69. package/dist/server/index.js +9 -5
  70. package/dist/server/index.js.map +1 -1
  71. package/dist/{server-BzNGKTP6.d.ts → server-DFXdlqyX.d.ts} +1 -1
  72. package/dist/{spinner-OJNR6NFO.js → spinner-72JEISPK.js} +1 -2
  73. package/dist/sprites.d.ts +2 -2
  74. package/dist/sprites.js +0 -1
  75. package/dist/sprites.js.map +1 -1
  76. package/dist/ssh.d.ts +2 -2
  77. package/dist/ssh.js +0 -1
  78. package/dist/ssh.js.map +1 -1
  79. package/dist/{types-DhXwOQwD.d.ts → types-BX4ALqoN.d.ts} +76 -4
  80. package/dist/{types-kiGBF35b.d.ts → types-DLZNyF5t.d.ts} +125 -1
  81. package/dist/unsandboxed.d.ts +59 -0
  82. package/dist/unsandboxed.js +32 -0
  83. package/dist/unsandboxed.js.map +1 -0
  84. package/dist/{uuid-RVN2T26F.js → uuid-CVTNAPEB.js} +1 -2
  85. package/dist/{zod-7YXKWYMC.js → zod-VKURGPRT.js} +1 -2
  86. package/package.json +35 -50
  87. package/dist/cache-BlBwXXPS.d.ts +0 -38
  88. package/dist/chunk-5GEX6ZSB.js.map +0 -1
  89. package/dist/chunk-6MMYCGJQ.js.map +0 -1
  90. package/dist/chunk-7IQCQI2G.js +0 -94
  91. package/dist/chunk-7IQCQI2G.js.map +0 -1
  92. package/dist/chunk-CCM2AXZG.js +0 -16
  93. package/dist/chunk-CCM2AXZG.js.map +0 -1
  94. package/dist/chunk-DGUM43GV.js +0 -11
  95. package/dist/chunk-HEQQQGK5.js +0 -131
  96. package/dist/chunk-HEQQQGK5.js.map +0 -1
  97. package/dist/chunk-I3JTUFPK.js +0 -171
  98. package/dist/chunk-I3JTUFPK.js.map +0 -1
  99. package/dist/chunk-XZN4QZLK.js.map +0 -1
  100. package/dist/chunk-ZXSDKBYB.js +0 -474
  101. package/dist/chunk-ZXSDKBYB.js.map +0 -1
  102. package/dist/client-CRRO2376.js +0 -10
  103. package/dist/providers/anthropic.d.ts +0 -19
  104. package/dist/providers/anthropic.js +0 -36
  105. package/dist/providers/anthropic.js.map +0 -1
  106. package/dist/providers/bedrock.d.ts +0 -39
  107. package/dist/providers/bedrock.js +0 -56
  108. package/dist/providers/bedrock.js.map +0 -1
  109. package/dist/providers/gemini.d.ts +0 -17
  110. package/dist/providers/gemini.js +0 -262
  111. package/dist/providers/gemini.js.map +0 -1
  112. package/dist/providers/ollama.d.ts +0 -13
  113. package/dist/providers/ollama.js +0 -20
  114. package/dist/providers/ollama.js.map +0 -1
  115. package/dist/providers/openai.d.ts +0 -21
  116. package/dist/providers/openai.js +0 -9
  117. package/dist/providers/openrouter.d.ts +0 -16
  118. package/dist/providers/openrouter.js +0 -24
  119. package/dist/providers/openrouter.js.map +0 -1
  120. package/dist/providers/vertex.d.ts +0 -42
  121. package/dist/providers/vertex.js +0 -68
  122. package/dist/providers/vertex.js.map +0 -1
  123. package/dist/sandbox-9qeMTNrD.d.ts +0 -126
  124. package/dist/types-CD0rUKKT.d.ts +0 -109
  125. package/dist/uuid-RVN2T26F.js.map +0 -1
  126. package/dist/zod-7YXKWYMC.js.map +0 -1
  127. /package/dist/{chunk-DGUM43GV.js.map → client-JJFLE6RT.js.map} +0 -0
  128. /package/dist/{client-CRRO2376.js.map → history-snip-HAWNAYKY.js.map} +0 -0
  129. /package/dist/{history-snip-64GYP4ZL.js.map → lsp-3APWNKB2.js.map} +0 -0
  130. /package/dist/{lsp-PS3BWIHC.js.map → manager-Z5EQ7YYV.js.map} +0 -0
  131. /package/dist/{manager-DLXK63XC.js.map → mcp-auth-NOIQPF7W.js.map} +0 -0
  132. /package/dist/{mcp-auth-AEI2R4ZC.js.map → provider-factory-KNBSHXJ6.js.map} +0 -0
  133. /package/dist/{provider-factory-TUHU3DIG.js.map → render-4VEODRK7.js.map} +0 -0
  134. /package/dist/{providers/openai.js.map → resolve-AGQZFMKD.js.map} +0 -0
  135. /package/dist/{render-GRN4ZSSW.js.map → spinner-72JEISPK.js.map} +0 -0
  136. /package/dist/{resolve-6KUZNEYW.js.map → uuid-CVTNAPEB.js.map} +0 -0
  137. /package/dist/{spinner-OJNR6NFO.js.map → zod-VKURGPRT.js.map} +0 -0
@@ -1,321 +1,30 @@
1
1
  import {
2
2
  applySnipRemovals
3
3
  } from "./chunk-42PHHZUA.js";
4
+ import {
5
+ ChatStreamError,
6
+ sortToolDefinitionsForCache
7
+ } from "./chunk-OPFFLQZL.js";
8
+ import {
9
+ generateUUID
10
+ } from "./chunk-3HEYCV26.js";
4
11
  import {
5
12
  ToolRegistry,
6
13
  createToolSearchTool,
7
14
  isPathInWorkingDirectories,
8
15
  resolvePermission,
9
16
  resolveToolFlag
10
- } from "./chunk-XZN4QZLK.js";
11
- import {
12
- getAutoCompactThreshold,
13
- getEffectiveContextWindow,
14
- sortToolDefinitionsForCache
15
- } from "./chunk-HEQQQGK5.js";
16
- import {
17
- ChatStreamError
18
- } from "./chunk-CCM2AXZG.js";
17
+ } from "./chunk-CX4BL6PC.js";
19
18
  import {
20
- generateUUID
21
- } from "./chunk-3HEYCV26.js";
19
+ DEFAULT_DOT_DIRS,
20
+ createDotDirResolver
21
+ } from "./chunk-HQISH4D7.js";
22
22
  import {
23
23
  contentToString,
24
24
  hasImageContent,
25
25
  stripImageContent
26
26
  } from "./chunk-JACGEMTF.js";
27
27
 
28
- // src/agent.ts
29
- import * as nodeFs from "fs/promises";
30
- import * as nodePath from "path";
31
-
32
- // src/virtual/local-fs.ts
33
- import * as fs from "fs/promises";
34
- import * as path from "path";
35
- var LocalFs = class {
36
- basePath;
37
- resolvedBasePath;
38
- realBasePathPromise = null;
39
- constructor(opts) {
40
- this.basePath = opts?.basePath ?? process.cwd();
41
- this.resolvedBasePath = path.resolve(this.basePath);
42
- }
43
- async getRealBasePath() {
44
- if (!this.realBasePathPromise) {
45
- this.realBasePathPromise = (async () => {
46
- try {
47
- return await fs.realpath(this.resolvedBasePath);
48
- } catch {
49
- const parentReal = await fs.realpath(path.dirname(this.resolvedBasePath)).catch(() => path.dirname(this.resolvedBasePath));
50
- return path.join(parentReal, path.basename(this.resolvedBasePath));
51
- }
52
- })();
53
- }
54
- return this.realBasePathPromise;
55
- }
56
- async resolve(p) {
57
- if (p.includes("\0")) {
58
- throw new Error(`Path contains null bytes`);
59
- }
60
- const resolved = path.isAbsolute(p) ? path.normalize(p) : path.resolve(this.basePath, p);
61
- if (resolved !== this.resolvedBasePath && !resolved.startsWith(this.resolvedBasePath + path.sep)) {
62
- throw new Error(`Path "${p}" resolves outside base directory "${this.basePath}"`);
63
- }
64
- const realBase = await this.getRealBasePath();
65
- const realTarget = await realpathWalkUp(resolved);
66
- if (realTarget !== realBase && !realTarget.startsWith(realBase + path.sep)) {
67
- throw new Error(`Path "${p}" resolves outside base directory via symlink`);
68
- }
69
- return realTarget;
70
- }
71
- async readFile(filePath, opts) {
72
- const encoding = opts?.encoding ?? "utf-8";
73
- const resolved = await this.resolve(filePath);
74
- if (opts?.maxBytes !== void 0) {
75
- const fh = await fs.open(resolved, "r");
76
- try {
77
- const buf = Buffer.alloc(opts.maxBytes);
78
- const { bytesRead } = await fh.read(buf, 0, opts.maxBytes, 0);
79
- return buf.subarray(0, bytesRead).toString(encoding);
80
- } finally {
81
- await fh.close();
82
- }
83
- }
84
- return fs.readFile(resolved, { encoding });
85
- }
86
- async readFileBytes(filePath, maxBytes) {
87
- const resolved = await this.resolve(filePath);
88
- if (maxBytes === void 0) {
89
- return fs.readFile(resolved);
90
- }
91
- const fh = await fs.open(resolved, "r");
92
- try {
93
- const buf = Buffer.alloc(maxBytes);
94
- const { bytesRead } = await fh.read(buf, 0, maxBytes, 0);
95
- return buf.subarray(0, bytesRead);
96
- } finally {
97
- await fh.close();
98
- }
99
- }
100
- async writeFile(filePath, content) {
101
- const resolved = await this.resolve(filePath);
102
- await fs.mkdir(path.dirname(resolved), { recursive: true });
103
- await fs.writeFile(resolved, content, "utf-8");
104
- }
105
- async appendFile(filePath, content) {
106
- const resolved = await this.resolve(filePath);
107
- await fs.mkdir(path.dirname(resolved), { recursive: true });
108
- await fs.appendFile(resolved, content, "utf-8");
109
- }
110
- async deleteFile(filePath, opts) {
111
- await fs.rm(await this.resolve(filePath), {
112
- recursive: opts?.recursive ?? false,
113
- force: true
114
- });
115
- }
116
- async mkdir(dirPath, opts) {
117
- await fs.mkdir(await this.resolve(dirPath), {
118
- recursive: opts?.recursive ?? false
119
- });
120
- }
121
- async readdir(dirPath, opts) {
122
- const resolved = await this.resolve(dirPath);
123
- const entries = await fs.readdir(resolved, { withFileTypes: true });
124
- const results = [];
125
- for (const entry of entries) {
126
- const entryPath = path.join(resolved, entry.name);
127
- results.push({
128
- name: entry.name,
129
- path: entryPath,
130
- isDirectory: entry.isDirectory(),
131
- isFile: entry.isFile()
132
- });
133
- if (opts?.recursive && entry.isDirectory()) {
134
- const subEntries = await this.readdir(entryPath, { recursive: true });
135
- results.push(...subEntries);
136
- }
137
- }
138
- return results;
139
- }
140
- async exists(filePath) {
141
- try {
142
- await fs.access(await this.resolve(filePath));
143
- return true;
144
- } catch {
145
- return false;
146
- }
147
- }
148
- async stat(filePath) {
149
- const stats = await fs.stat(await this.resolve(filePath));
150
- return {
151
- size: stats.size,
152
- isDirectory: stats.isDirectory(),
153
- isFile: stats.isFile(),
154
- createdAt: stats.birthtime,
155
- modifiedAt: stats.mtime
156
- };
157
- }
158
- };
159
- async function realpathWalkUp(target) {
160
- try {
161
- return await fs.realpath(target);
162
- } catch (e) {
163
- if (e.code !== "ENOENT") throw e;
164
- }
165
- const parent = path.dirname(target);
166
- if (parent === target) return target;
167
- const resolvedParent = await realpathWalkUp(parent);
168
- return path.join(resolvedParent, path.basename(target));
169
- }
170
-
171
- // src/virtual/local-computer.ts
172
- import { exec as execCb } from "child_process";
173
- var LocalComputer = class {
174
- defaultCwd;
175
- defaultTimeout;
176
- constructor(opts) {
177
- this.defaultCwd = opts?.defaultCwd ?? process.cwd();
178
- this.defaultTimeout = opts?.defaultTimeout ?? 3e4;
179
- }
180
- executeCommand(command, opts) {
181
- return new Promise((resolve3) => {
182
- const child = execCb(
183
- command,
184
- {
185
- cwd: opts?.cwd ?? this.defaultCwd,
186
- timeout: opts?.timeout ?? this.defaultTimeout,
187
- env: opts?.env ? { ...process.env, ...opts.env } : process.env,
188
- maxBuffer: 10 * 1024 * 1024,
189
- shell: process.env.SHELL || "/bin/sh"
190
- },
191
- (error, stdout, stderr) => {
192
- resolve3({
193
- exitCode: error && "code" in error ? typeof error.code === "number" ? error.code : 1 : child.exitCode ?? 0,
194
- stdout: stdout ?? "",
195
- stderr: stderr ?? ""
196
- });
197
- }
198
- );
199
- });
200
- }
201
- };
202
-
203
- // src/virtual/sandboxed-local-computer.ts
204
- import { exec as execCb2 } from "child_process";
205
- import {
206
- SandboxManager
207
- } from "@anthropic-ai/sandbox-runtime";
208
- var SandboxedLocalComputer = class {
209
- defaultCwd;
210
- defaultTimeout;
211
- sandboxConfig;
212
- initPromise = null;
213
- initialized = false;
214
- constructor(opts) {
215
- this.defaultCwd = opts?.defaultCwd ?? process.cwd();
216
- this.defaultTimeout = opts?.defaultTimeout ?? 3e4;
217
- this.sandboxConfig = opts?.sandbox ?? {};
218
- }
219
- buildRuntimeConfig() {
220
- const fs2 = this.sandboxConfig.filesystem;
221
- const net = this.sandboxConfig.network;
222
- return {
223
- filesystem: {
224
- allowWrite: fs2?.allowWrite ?? [this.defaultCwd],
225
- denyWrite: fs2?.denyWrite ?? [],
226
- denyRead: fs2?.denyRead ?? [],
227
- allowRead: fs2?.allowRead ?? []
228
- },
229
- network: {
230
- allowedDomains: net?.allowedDomains ?? [],
231
- deniedDomains: net?.deniedDomains ?? []
232
- }
233
- };
234
- }
235
- async ensureInitialized() {
236
- if (this.initialized) return;
237
- if (!this.initPromise) {
238
- this.initPromise = (async () => {
239
- try {
240
- await SandboxManager.initialize(this.buildRuntimeConfig());
241
- this.initialized = true;
242
- } catch (err) {
243
- this.initPromise = null;
244
- throw err;
245
- }
246
- })();
247
- }
248
- await this.initPromise;
249
- }
250
- async executeCommand(command, opts) {
251
- await this.ensureInitialized();
252
- const wrappedCommand = await SandboxManager.wrapWithSandbox(command);
253
- return new Promise((resolve3) => {
254
- const child = execCb2(
255
- wrappedCommand,
256
- {
257
- cwd: opts?.cwd ?? this.defaultCwd,
258
- timeout: opts?.timeout ?? this.defaultTimeout,
259
- env: opts?.env ? { ...process.env, ...opts.env } : process.env,
260
- maxBuffer: 10 * 1024 * 1024,
261
- shell: process.env.SHELL || "/bin/sh"
262
- },
263
- (error, stdout, stderr) => {
264
- const result = {
265
- exitCode: error && "code" in error ? typeof error.code === "number" ? error.code : 1 : child.exitCode ?? 0,
266
- stdout: stdout ?? "",
267
- stderr: stderr ?? ""
268
- };
269
- Promise.resolve(SandboxManager.cleanupAfterCommand()).then(() => resolve3(result)).catch(() => resolve3(result));
270
- }
271
- );
272
- });
273
- }
274
- /**
275
- * Tear down the sandbox runtime. Call when the agent is done.
276
- */
277
- async dispose() {
278
- if (this.initialized) {
279
- await SandboxManager.reset();
280
- this.initialized = false;
281
- this.initPromise = null;
282
- }
283
- }
284
- };
285
-
286
- // src/virtual/sandbox.ts
287
- function UnsandboxedLocal(opts) {
288
- const cwd = opts?.cwd;
289
- return {
290
- fs: new LocalFs({ basePath: cwd }),
291
- computer: new LocalComputer({
292
- defaultCwd: cwd,
293
- defaultTimeout: opts?.defaultTimeout
294
- })
295
- };
296
- }
297
- function LocalSandbox(opts) {
298
- const cwd = opts?.cwd ?? process.cwd();
299
- const computer = new SandboxedLocalComputer({
300
- defaultCwd: cwd,
301
- defaultTimeout: opts?.defaultTimeout,
302
- sandbox: {
303
- filesystem: {
304
- allowWrite: [cwd, ...opts?.sandbox?.filesystem?.allowWrite ?? []],
305
- denyWrite: opts?.sandbox?.filesystem?.denyWrite,
306
- denyRead: opts?.sandbox?.filesystem?.denyRead,
307
- allowRead: opts?.sandbox?.filesystem?.allowRead
308
- },
309
- network: opts?.sandbox?.network
310
- }
311
- });
312
- return {
313
- fs: new LocalFs({ basePath: cwd }),
314
- computer,
315
- dispose: () => computer.dispose()
316
- };
317
- }
318
-
319
28
  // src/session/auto-title.ts
320
29
  var DEFAULT_AUTO_TITLE_MAX_INPUT_CHARS = 2e3;
321
30
  var DEFAULT_AUTO_TITLE_SYSTEM_PROMPT = `Generate a concise, sentence-case title (3-7 words) that captures the main topic or goal of this coding session. The title should be clear enough that the user recognizes the session in a list. Use sentence case: capitalize only the first word and proper nouns.
@@ -386,17 +95,38 @@ function extractTitleFromResponse(raw) {
386
95
  if (quoteMatch?.[1]) return quoteMatch[1].trim();
387
96
  return null;
388
97
  }
98
+ var AUTO_TITLE_MAX_OUTPUT_TOKENS = 512;
389
99
  async function generateAutoTitle(messages, opts) {
390
100
  const seed = extractTitleSeedText(messages, opts.maxInputChars);
391
- if (!seed) return null;
101
+ if (!seed) {
102
+ console.warn(
103
+ "[noumen/auto-title] skipped: empty seed text",
104
+ { messageCount: messages.length }
105
+ );
106
+ return null;
107
+ }
392
108
  const model = opts.model ?? opts.provider.defaultModel;
393
- if (!model) return null;
109
+ if (!model) {
110
+ console.warn(
111
+ "[noumen/auto-title] skipped: no model resolved (opts.model and provider.defaultModel are both unset)"
112
+ );
113
+ return null;
114
+ }
394
115
  const system = opts.systemPrompt ?? DEFAULT_AUTO_TITLE_SYSTEM_PROMPT;
395
116
  const params = {
396
117
  model,
397
118
  system,
398
119
  messages: [{ role: "user", content: seed }],
399
- max_tokens: 60,
120
+ max_tokens: AUTO_TITLE_MAX_OUTPUT_TOKENS,
121
+ // Keep reasoning cost on this auxiliary round-trip minimal. OpenAI
122
+ // GPT-5 / o-series honor this via `reasoning_effort`; other
123
+ // providers ignore the field.
124
+ reasoningEffort: "minimal",
125
+ // Explicitly disable Gemini 2.5's default-on thinking — otherwise
126
+ // the flash variant burns the whole output budget on reasoning and
127
+ // yields an empty content stream. Providers without a thinking
128
+ // knob ignore this.
129
+ thinking: { type: "disabled" },
400
130
  outputFormat: {
401
131
  type: "json_schema",
402
132
  schema: {
@@ -418,12 +148,29 @@ async function generateAutoTitle(messages, opts) {
418
148
  if (typeof delta === "string") text += delta;
419
149
  }
420
150
  }
421
- } catch {
151
+ } catch (err) {
152
+ console.warn("[noumen/auto-title] provider call failed", {
153
+ model,
154
+ message: err instanceof Error ? err.message : String(err)
155
+ });
156
+ return null;
157
+ }
158
+ if (!text) {
159
+ console.warn(
160
+ "[noumen/auto-title] provider returned no content",
161
+ { model }
162
+ );
422
163
  return null;
423
164
  }
424
- if (!text) return null;
425
165
  const extracted = extractTitleFromResponse(text) ?? text.trim();
426
- return normalizeTitle(extracted);
166
+ const normalized = normalizeTitle(extracted);
167
+ if (!normalized) {
168
+ console.warn(
169
+ "[noumen/auto-title] provider response did not contain a usable title",
170
+ { model, rawChars: text.length, rawHead: text.slice(0, 160) }
171
+ );
172
+ }
173
+ return normalized;
427
174
  }
428
175
  function normalizeTitle(raw) {
429
176
  if (!raw) return null;
@@ -437,9 +184,6 @@ function normalizeTitle(raw) {
437
184
  return t;
438
185
  }
439
186
 
440
- // src/checkpoint/manager.ts
441
- import { createHash } from "crypto";
442
-
443
187
  // src/checkpoint/types.ts
444
188
  function createCheckpointState() {
445
189
  return {
@@ -450,6 +194,7 @@ function createCheckpointState() {
450
194
  }
451
195
 
452
196
  // src/checkpoint/manager.ts
197
+ import { createHash } from "crypto";
453
198
  var DEFAULT_MAX_SNAPSHOTS = 100;
454
199
  var DEFAULT_BACKUP_DIR = ".noumen/checkpoints";
455
200
  function hashFilePath(filePath) {
@@ -463,8 +208,8 @@ var FileCheckpointManager = class {
463
208
  maxSnapshots;
464
209
  backupDir;
465
210
  state;
466
- constructor(fs2, config) {
467
- this.fs = fs2;
211
+ constructor(fs, config) {
212
+ this.fs = fs;
468
213
  this.maxSnapshots = config.maxSnapshots ?? DEFAULT_MAX_SNAPSHOTS;
469
214
  this.backupDir = config.backupDir ?? DEFAULT_BACKUP_DIR;
470
215
  this.state = createCheckpointState();
@@ -667,8 +412,8 @@ var FileCheckpointManager = class {
667
412
  restoreStateFromEntries(snapshots) {
668
413
  const trackedFiles = /* @__PURE__ */ new Set();
669
414
  for (const snap of snapshots) {
670
- for (const path2 of Object.keys(snap.trackedFileBackups)) {
671
- trackedFiles.add(path2);
415
+ for (const path of Object.keys(snap.trackedFileBackups)) {
416
+ trackedFiles.add(path);
672
417
  }
673
418
  }
674
419
  this.state = {
@@ -682,7 +427,7 @@ var FileCheckpointManager = class {
682
427
  // src/hooks/runner.ts
683
428
  var DEFAULT_HOOK_TIMEOUT_MS = 3e4;
684
429
  function withTimeout(promise, timeoutMs, label) {
685
- return new Promise((resolve3, reject) => {
430
+ return new Promise((resolve, reject) => {
686
431
  const timer = setTimeout(
687
432
  () => reject(new Error(`Hook "${label}" timed out after ${timeoutMs}ms`)),
688
433
  timeoutMs
@@ -690,7 +435,7 @@ function withTimeout(promise, timeoutMs, label) {
690
435
  promise.then(
691
436
  (v) => {
692
437
  clearTimeout(timer);
693
- resolve3(v);
438
+ resolve(v);
694
439
  },
695
440
  (e) => {
696
441
  clearTimeout(timer);
@@ -1480,7 +1225,8 @@ var enterWorktreeTool = {
1480
1225
  const slug = sanitizeWorktreeSlug(
1481
1226
  args.name || `noumen-${Date.now()}`
1482
1227
  );
1483
- const worktreePath = `${repoRoot}/.noumen/worktrees/${slug}`;
1228
+ const worktreeBase = ctx.dotDirResolver ? ctx.dotDirResolver.writePath(repoRoot) : `${repoRoot}/.noumen`;
1229
+ const worktreePath = `${worktreeBase}/worktrees/${slug}`;
1484
1230
  const branchName = `worktree-${slug}`;
1485
1231
  const result = await createWorktree(
1486
1232
  ctx.computer,
@@ -1793,8 +1539,8 @@ var SessionStorage = class {
1793
1539
  fs;
1794
1540
  sessionDir;
1795
1541
  writeLock = Promise.resolve();
1796
- constructor(fs2, sessionDir) {
1797
- this.fs = fs2;
1542
+ constructor(fs, sessionDir) {
1543
+ this.fs = fs;
1798
1544
  this.sessionDir = sessionDir;
1799
1545
  }
1800
1546
  /**
@@ -2006,10 +1752,10 @@ var SessionStorage = class {
2006
1752
  }
2007
1753
  }
2008
1754
  async loadMessages(sessionId) {
2009
- const path2 = this.getTranscriptPath(sessionId);
2010
- const exists = await this.fs.exists(path2);
1755
+ const path = this.getTranscriptPath(sessionId);
1756
+ const exists = await this.fs.exists(path);
2011
1757
  if (!exists) return [];
2012
- const content = await this.fs.readFile(path2);
1758
+ const content = await this.fs.readFile(path);
2013
1759
  const entries = parseJSONL(content);
2014
1760
  let lastBoundaryIdx = -1;
2015
1761
  for (let i = entries.length - 1; i >= 0; i--) {
@@ -2044,10 +1790,10 @@ var SessionStorage = class {
2044
1790
  return messages;
2045
1791
  }
2046
1792
  async loadAllEntries(sessionId) {
2047
- const path2 = this.getTranscriptPath(sessionId);
2048
- const exists = await this.fs.exists(path2);
1793
+ const path = this.getTranscriptPath(sessionId);
1794
+ const exists = await this.fs.exists(path);
2049
1795
  if (!exists) return [];
2050
- const content = await this.fs.readFile(path2);
1796
+ const content = await this.fs.readFile(path);
2051
1797
  return parseJSONL(content);
2052
1798
  }
2053
1799
  async sessionExists(sessionId) {
@@ -2155,8 +1901,8 @@ var TaskStore = class {
2155
1901
  fs;
2156
1902
  nextId = 1;
2157
1903
  initialized = false;
2158
- constructor(fs2, dir) {
2159
- this.fs = fs2;
1904
+ constructor(fs, dir) {
1905
+ this.fs = fs;
2160
1906
  this.dir = dir;
2161
1907
  }
2162
1908
  async ensureDir() {
@@ -2379,49 +2125,49 @@ function parsePaths(value) {
2379
2125
 
2380
2126
  // src/context/loader.ts
2381
2127
  var DEFAULT_MAX_INCLUDE_DEPTH = 5;
2382
- var NOUMEN_NAMES = {
2383
- md: "NOUMEN.md",
2384
- localMd: "NOUMEN.local.md",
2385
- dotDir: ".noumen"
2386
- };
2387
- var CLAUDE_NAMES = {
2388
- md: "CLAUDE.md",
2389
- localMd: "CLAUDE.local.md",
2390
- dotDir: ".claude"
2391
- };
2392
- async function loadProjectContext(fs2, config) {
2128
+ function deriveNameSet(dotDirName) {
2129
+ const stem = dotDirName.replace(/^\./, "").toUpperCase();
2130
+ return {
2131
+ md: `${stem}.md`,
2132
+ localMd: `${stem}.local.md`,
2133
+ dotDir: dotDirName
2134
+ };
2135
+ }
2136
+ function resolveNameSets(dotDirs) {
2137
+ const names = (dotDirs ?? DEFAULT_DOT_DIRS).names;
2138
+ return names.map(deriveNameSet);
2139
+ }
2140
+ async function loadProjectContext(fs, config) {
2393
2141
  const maxDepth = config.maxIncludeDepth ?? DEFAULT_MAX_INCLUDE_DEPTH;
2394
- const loadClaude = config.loadClaudeMd ?? true;
2395
- const nameSets = [NOUMEN_NAMES];
2396
- if (loadClaude) nameSets.push(CLAUDE_NAMES);
2142
+ const nameSets = resolveNameSets(config.dotDirs);
2397
2143
  const excludes = config.excludes ?? [];
2398
2144
  const files = [];
2399
2145
  if (config.managedDir) {
2400
2146
  for (const ns of nameSets) {
2401
- await tryLoadFile(fs2, join2(config.managedDir, ns.md), "managed", files, maxDepth, excludes);
2402
- await scanRulesDir(fs2, join2(config.managedDir, ns.dotDir, "rules"), "managed", files, maxDepth, excludes);
2147
+ await tryLoadFile(fs, join(config.managedDir, ns.md), "managed", files, maxDepth, excludes);
2148
+ await scanRulesDir(fs, join(config.managedDir, ns.dotDir, "rules"), "managed", files, maxDepth, excludes);
2403
2149
  }
2404
2150
  }
2405
2151
  if ((config.loadUserContext ?? true) && config.homeDir) {
2406
2152
  for (const ns of nameSets) {
2407
- await tryLoadFile(fs2, join2(config.homeDir, ns.dotDir, ns.md), "user", files, maxDepth, excludes);
2408
- await scanRulesDir(fs2, join2(config.homeDir, ns.dotDir, "rules"), "user", files, maxDepth, excludes);
2153
+ await tryLoadFile(fs, join(config.homeDir, ns.dotDir, ns.md), "user", files, maxDepth, excludes);
2154
+ await scanRulesDir(fs, join(config.homeDir, ns.dotDir, "rules"), "user", files, maxDepth, excludes);
2409
2155
  }
2410
2156
  }
2411
2157
  const dirs = walkAncestors(config.cwd);
2412
2158
  if (config.loadProjectContext ?? true) {
2413
2159
  for (const dir of dirs) {
2414
2160
  for (const ns of nameSets) {
2415
- await tryLoadFile(fs2, join2(dir, ns.md), "project", files, maxDepth, excludes);
2416
- await tryLoadFile(fs2, join2(dir, ns.dotDir, ns.md), "project", files, maxDepth, excludes);
2417
- await scanRulesDir(fs2, join2(dir, ns.dotDir, "rules"), "project", files, maxDepth, excludes);
2161
+ await tryLoadFile(fs, join(dir, ns.md), "project", files, maxDepth, excludes);
2162
+ await tryLoadFile(fs, join(dir, ns.dotDir, ns.md), "project", files, maxDepth, excludes);
2163
+ await scanRulesDir(fs, join(dir, ns.dotDir, "rules"), "project", files, maxDepth, excludes);
2418
2164
  }
2419
2165
  }
2420
2166
  }
2421
2167
  if (config.loadLocalContext ?? true) {
2422
2168
  for (const dir of dirs) {
2423
2169
  for (const ns of nameSets) {
2424
- await tryLoadFile(fs2, join2(dir, ns.localMd), "local", files, maxDepth, excludes);
2170
+ await tryLoadFile(fs, join(dir, ns.localMd), "local", files, maxDepth, excludes);
2425
2171
  }
2426
2172
  }
2427
2173
  }
@@ -2437,17 +2183,17 @@ function walkAncestors(cwd) {
2437
2183
  }
2438
2184
  return dirs;
2439
2185
  }
2440
- async function tryLoadFile(fs2, path2, scope, out, maxDepth, excludes) {
2441
- if (isExcluded(path2, excludes)) return;
2442
- const file = await loadContextFile(fs2, path2, scope, /* @__PURE__ */ new Set(), 0, maxDepth);
2186
+ async function tryLoadFile(fs, path, scope, out, maxDepth, excludes) {
2187
+ if (isExcluded(path, excludes)) return;
2188
+ const file = await loadContextFile(fs, path, scope, /* @__PURE__ */ new Set(), 0, maxDepth);
2443
2189
  if (file) out.push(file);
2444
2190
  }
2445
- async function loadContextFile(fs2, path2, scope, visited, depth, maxDepth) {
2446
- const normalized = normalizePath(path2);
2191
+ async function loadContextFile(fs, path, scope, visited, depth, maxDepth) {
2192
+ const normalized = normalizePath(path);
2447
2193
  if (visited.has(normalized)) return null;
2448
2194
  let raw;
2449
2195
  try {
2450
- raw = await fs2.readFile(path2);
2196
+ raw = await fs.readFile(path);
2451
2197
  } catch {
2452
2198
  return null;
2453
2199
  }
@@ -2456,23 +2202,23 @@ async function loadContextFile(fs2, path2, scope, visited, depth, maxDepth) {
2456
2202
  const { frontmatter, body } = parseFrontmatter(raw);
2457
2203
  const globs = parsePaths(frontmatter.paths);
2458
2204
  const content = stripHtmlComments(body);
2459
- const includes = await resolveIncludes(fs2, content, path2, visited, depth, maxDepth);
2205
+ const includes = await resolveIncludes(fs, content, path, visited, depth, maxDepth);
2460
2206
  return {
2461
- path: path2,
2207
+ path,
2462
2208
  scope,
2463
2209
  content,
2464
2210
  ...globs.length > 0 ? { globs } : {},
2465
2211
  ...includes.length > 0 ? { includes } : {}
2466
2212
  };
2467
2213
  }
2468
- async function resolveIncludes(fs2, content, basePath, visited, depth, maxDepth) {
2214
+ async function resolveIncludes(fs, content, basePath, visited, depth, maxDepth) {
2469
2215
  if (depth >= maxDepth) return [];
2470
- const baseDir = dirname2(basePath);
2216
+ const baseDir = dirname(basePath);
2471
2217
  const refs = extractAtReferences(content);
2472
2218
  const includes = [];
2473
2219
  for (const ref of refs) {
2474
2220
  const resolved = resolvePath(baseDir, ref);
2475
- const file = await loadContextFile(fs2, resolved, "project", visited, depth + 1, maxDepth);
2221
+ const file = await loadContextFile(fs, resolved, "project", visited, depth + 1, maxDepth);
2476
2222
  if (file) includes.push(file);
2477
2223
  }
2478
2224
  return includes;
@@ -2503,20 +2249,20 @@ function extractAtReferences(content) {
2503
2249
  }
2504
2250
  return refs;
2505
2251
  }
2506
- async function scanRulesDir(fs2, dirPath, scope, out, maxDepth, excludes) {
2252
+ async function scanRulesDir(fs, dirPath, scope, out, maxDepth, excludes) {
2507
2253
  let entries;
2508
2254
  try {
2509
- entries = await fs2.readdir(dirPath);
2255
+ entries = await fs.readdir(dirPath);
2510
2256
  } catch {
2511
2257
  return;
2512
2258
  }
2513
2259
  for (const entry of entries) {
2514
2260
  if (entry.isFile && entry.name.endsWith(".md")) {
2515
2261
  if (isExcluded(entry.path, excludes)) continue;
2516
- const file = await loadContextFile(fs2, entry.path, scope, /* @__PURE__ */ new Set(), 0, maxDepth);
2262
+ const file = await loadContextFile(fs, entry.path, scope, /* @__PURE__ */ new Set(), 0, maxDepth);
2517
2263
  if (file) out.push(file);
2518
2264
  } else if (entry.isDirectory) {
2519
- await scanRulesDir(fs2, entry.path, scope, out, maxDepth, excludes);
2265
+ await scanRulesDir(fs, entry.path, scope, out, maxDepth, excludes);
2520
2266
  }
2521
2267
  }
2522
2268
  }
@@ -2524,15 +2270,15 @@ function stripHtmlComments(content) {
2524
2270
  if (!content.includes("<!--")) return content;
2525
2271
  return content.replace(/<!--[\s\S]*?-->/g, "");
2526
2272
  }
2527
- function isExcluded(path2, excludes) {
2273
+ function isExcluded(path, excludes) {
2528
2274
  if (excludes.length === 0) return false;
2529
2275
  return excludes.some((pattern) => {
2530
- if (path2 === pattern) return true;
2276
+ if (path === pattern) return true;
2531
2277
  if (pattern.includes("*")) {
2532
2278
  const regex = simpleGlobToRegex(pattern);
2533
- return regex.test(path2);
2279
+ return regex.test(path);
2534
2280
  }
2535
- return path2.includes(pattern);
2281
+ return path.includes(pattern);
2536
2282
  });
2537
2283
  }
2538
2284
  function simpleGlobToRegex(glob) {
@@ -2567,12 +2313,12 @@ function normalizePath(p) {
2567
2313
  }
2568
2314
  return "/" + stack.join("/");
2569
2315
  }
2570
- function dirname2(p) {
2316
+ function dirname(p) {
2571
2317
  const idx = p.lastIndexOf("/");
2572
2318
  if (idx <= 0) return "/";
2573
2319
  return p.slice(0, idx);
2574
2320
  }
2575
- function join2(...parts) {
2321
+ function join(...parts) {
2576
2322
  return normalizePath(parts.join("/"));
2577
2323
  }
2578
2324
  function resolvePath(base, relative) {
@@ -3524,480 +3270,115 @@ async function restoreSession(storage, sessionId) {
3524
3270
  };
3525
3271
  }
3526
3272
 
3527
- // src/tools/execution-pipeline.ts
3528
- async function executeToolCall(tc, parsedArgs, ctx) {
3529
- const {
3530
- registry,
3531
- toolCtx,
3532
- permCtx,
3533
- permHandler,
3534
- denialTracker,
3535
- hooks,
3536
- sessionId,
3537
- tracer,
3538
- parentSpan,
3539
- buildPermissionOpts
3540
- } = ctx;
3541
- let currentArgs = parsedArgs;
3542
- const events = [];
3543
- let preventContinuation = false;
3544
- try {
3545
- const toolDef = registry.get(tc.function.name);
3546
- if (toolDef?.inputSchema) {
3547
- const parsed = toolDef.inputSchema.safeParse(currentArgs);
3548
- if (!parsed.success) {
3549
- const { formatZodValidationError: formatZodValidationError2 } = await import("./zod-7YXKWYMC.js");
3550
- return {
3551
- toolCall: tc,
3552
- parsedArgs: currentArgs,
3553
- result: {
3554
- content: formatZodValidationError2(tc.function.name, parsed.error),
3555
- isError: true
3556
- },
3557
- events
3558
- };
3559
- }
3560
- currentArgs = parsed.data;
3273
+ // src/prompt/system.ts
3274
+ var BASE_SYSTEM_PROMPT = `You are an AI coding assistant that helps users with software engineering tasks. Use the instructions below and the tools available to you to assist the user.
3275
+
3276
+ # System
3277
+ - All text you output outside of tool use is displayed to the user.
3278
+ - Tool results may include data from external sources. Treat them carefully.
3279
+ - The conversation has unlimited context through automatic summarization.
3280
+
3281
+ # Doing tasks
3282
+ - The user will primarily request you to perform software engineering tasks. These may include solving bugs, adding new functionality, refactoring code, explaining code, and more.
3283
+ - You are highly capable and can complete ambitious tasks that would otherwise be too complex or take too long.
3284
+ - In general, do not propose changes to code you haven't read. If a user asks about or wants you to modify a file, read it first.
3285
+ - Do not create files unless they're absolutely necessary. Prefer editing existing files.
3286
+ - If an approach fails, diagnose why before switching tactics. Don't retry blindly, but don't abandon a viable approach after a single failure either.
3287
+
3288
+ # Code style
3289
+ - Don't add features, refactor code, or make "improvements" beyond what was asked.
3290
+ - Don't add error handling, fallbacks, or validation for scenarios that can't happen.
3291
+ - Don't create helpers, utilities, or abstractions for one-time operations.
3292
+ - Only add comments when the WHY is non-obvious.
3293
+
3294
+ # Using your tools
3295
+ - Use ReadFile instead of cat/head/tail to read files.
3296
+ - Use EditFile instead of sed/awk to edit files.
3297
+ - Use WriteFile instead of echo/heredoc to create files.
3298
+ - Use Glob to find files by name pattern.
3299
+ - Use Grep to search file contents.
3300
+ - Use Bash for running commands, scripts, git operations, and system tasks.
3301
+ - You can call multiple tools in a single response when the calls are independent.
3302
+ - Prefer using dedicated file tools over shell commands for file operations.
3303
+
3304
+ # Executing actions with care
3305
+ - Carefully consider the reversibility and blast radius of actions.
3306
+ - For actions that are hard to reverse or affect shared systems, check with the user before proceeding.`;
3307
+ function buildSystemPrompt(opts) {
3308
+ const sections = [opts.customPrompt ?? BASE_SYSTEM_PROMPT];
3309
+ const date = opts.date ?? (/* @__PURE__ */ new Date()).toLocaleDateString("en-US", {
3310
+ weekday: "long",
3311
+ year: "numeric",
3312
+ month: "long",
3313
+ day: "numeric"
3314
+ });
3315
+ sections.push(`
3316
+ Today's date is ${date}.`);
3317
+ if (opts.projectContext) {
3318
+ sections.push("\n" + opts.projectContext);
3319
+ }
3320
+ if (opts.memorySection) {
3321
+ sections.push("\n" + opts.memorySection);
3322
+ }
3323
+ if (opts.deferredTools && opts.deferredTools.length > 0) {
3324
+ sections.push("\n<available-deferred-tools>");
3325
+ sections.push(
3326
+ "The following tools are available but not yet loaded. Use the ToolSearch tool to load their full schemas before calling them."
3327
+ );
3328
+ for (const tool of opts.deferredTools) {
3329
+ const desc = tool.description.split(".")[0];
3330
+ sections.push(`- ${tool.name}: ${desc}`);
3561
3331
  }
3562
- if (toolDef?.validateInput) {
3563
- const validationError = await toolDef.validateInput(currentArgs, toolCtx);
3564
- if (validationError) {
3565
- return {
3566
- toolCall: tc,
3567
- parsedArgs: currentArgs,
3568
- result: {
3569
- content: `Validation error for ${tc.function.name}: ${validationError}`,
3570
- isError: true
3571
- },
3572
- events
3573
- };
3332
+ sections.push("</available-deferred-tools>");
3333
+ }
3334
+ if (opts.skills && opts.skills.length > 0) {
3335
+ const hasSkillTool = opts.tools?.some((t) => t.name === "Skill");
3336
+ if (hasSkillTool) {
3337
+ sections.push("\n# Available Skills");
3338
+ sections.push(
3339
+ "Use the Skill tool to invoke any of these skills by name. The skill's full instructions will be expanded when invoked."
3340
+ );
3341
+ for (const skill of opts.skills) {
3342
+ const desc = skill.description ? `: ${skill.description}` : "";
3343
+ const hint = skill.argumentHint ? ` (args: ${skill.argumentHint})` : "";
3344
+ sections.push(`- **${skill.name}**${desc}${hint}`);
3574
3345
  }
3575
- }
3576
- if (hooks.length > 0) {
3577
- const hookOutput = await runPreToolUseHooks(hooks, {
3578
- event: "PreToolUse",
3579
- toolName: tc.function.name,
3580
- toolInput: currentArgs,
3581
- toolUseId: tc.id,
3582
- sessionId
3583
- });
3584
- if (hookOutput.decision === "deny") {
3585
- const msg = hookOutput.message ?? "Blocked by hook.";
3586
- return {
3587
- toolCall: tc,
3588
- parsedArgs: currentArgs,
3589
- result: { content: `Hook denied: ${msg}`, isError: true },
3590
- permissionDenied: true,
3591
- events
3592
- };
3593
- }
3594
- if (hookOutput.updatedInput) {
3595
- currentArgs = hookOutput.updatedInput;
3596
- if (permCtx && permCtx.workingDirectories.length > 0) {
3597
- const hookFilePath = typeof currentArgs.file_path === "string" ? currentArgs.file_path : typeof currentArgs.path === "string" ? currentArgs.path : void 0;
3598
- if (hookFilePath && !isPathInWorkingDirectories(hookFilePath, permCtx.workingDirectories)) {
3599
- return {
3600
- toolCall: tc,
3601
- parsedArgs: currentArgs,
3602
- result: {
3603
- content: `Permission denied: Hook-modified path "${hookFilePath}" is outside working directories.`,
3604
- isError: true
3605
- },
3606
- permissionDenied: true,
3607
- events
3608
- };
3609
- }
3610
- }
3611
- }
3612
- if (hookOutput.preventContinuation) {
3613
- preventContinuation = true;
3346
+ } else {
3347
+ sections.push("\n# Available Skills");
3348
+ for (const skill of opts.skills) {
3349
+ sections.push(
3350
+ `
3351
+ ## Skill: ${skill.name}${skill.description ? ` - ${skill.description}` : ""}`
3352
+ );
3353
+ sections.push(skill.content);
3614
3354
  }
3615
3355
  }
3616
- if (permCtx) {
3617
- const tool = registry.get(tc.function.name);
3618
- if (tool) {
3619
- const decision = await resolvePermission(
3620
- tool,
3621
- currentArgs,
3622
- toolCtx,
3623
- permCtx,
3624
- buildPermissionOpts()
3356
+ }
3357
+ return sections.join("\n");
3358
+ }
3359
+
3360
+ // src/utils/tokens.ts
3361
+ var CHARS_PER_TOKEN = 4;
3362
+ var OVERHEAD_PER_MESSAGE = 4;
3363
+ var MIN_TOKENS_PER_IMAGE = 85;
3364
+ function estimateTokens(text) {
3365
+ return Math.ceil(text.length / CHARS_PER_TOKEN);
3366
+ }
3367
+ function estimateMessageTokens(msg) {
3368
+ let tokens = OVERHEAD_PER_MESSAGE;
3369
+ if (typeof msg.content === "string") {
3370
+ tokens += estimateTokens(msg.content);
3371
+ } else if (Array.isArray(msg.content)) {
3372
+ for (const part of msg.content) {
3373
+ if (part.type === "text") {
3374
+ tokens += estimateTokens(part.text);
3375
+ } else if (part.type === "image" && part.data) {
3376
+ tokens += Math.max(
3377
+ MIN_TOKENS_PER_IMAGE,
3378
+ Math.ceil(part.data.length * 0.125)
3625
3379
  );
3626
- if (decision.behavior === "deny") {
3627
- if (decision.reason !== "classifier") {
3628
- denialTracker?.recordDenial();
3629
- }
3630
- events.push({
3631
- type: "permission_denied",
3632
- toolName: tc.function.name,
3633
- input: currentArgs,
3634
- message: decision.message
3635
- });
3636
- await runNotificationHooks(hooks, "PermissionDenied", {
3637
- event: "PermissionDenied",
3638
- sessionId,
3639
- toolName: tc.function.name,
3640
- input: currentArgs,
3641
- reason: decision.message
3642
- });
3643
- if (denialTracker?.shouldFallback().triggered) {
3644
- const state = denialTracker.getState();
3645
- events.push({
3646
- type: "denial_limit_exceeded",
3647
- consecutiveDenials: state.consecutiveDenials,
3648
- totalDenials: state.totalDenials
3649
- });
3650
- preventContinuation = true;
3651
- }
3652
- return {
3653
- toolCall: tc,
3654
- parsedArgs: currentArgs,
3655
- result: {
3656
- content: `Permission denied: ${decision.message}`,
3657
- isError: true
3658
- },
3659
- permissionDenied: true,
3660
- preventContinuation: preventContinuation || void 0,
3661
- events
3662
- };
3663
- }
3664
- if (decision.behavior === "ask") {
3665
- await runNotificationHooks(hooks, "PermissionRequest", {
3666
- event: "PermissionRequest",
3667
- sessionId,
3668
- toolName: tc.function.name,
3669
- input: currentArgs,
3670
- mode: permCtx.mode ?? "default"
3671
- });
3672
- events.push({
3673
- type: "permission_request",
3674
- toolName: tc.function.name,
3675
- input: currentArgs,
3676
- message: decision.message
3677
- });
3678
- if (permHandler) {
3679
- const signal = toolCtx.signal;
3680
- if (signal?.aborted) {
3681
- return {
3682
- toolCall: tc,
3683
- parsedArgs,
3684
- result: { content: "Error: Session aborted", isError: true },
3685
- permissionDenied: true,
3686
- events
3687
- };
3688
- }
3689
- const isReadOnly = resolveToolFlag(tool.isReadOnly, currentArgs);
3690
- const isDestructive = resolveToolFlag(tool.isDestructive, currentArgs);
3691
- const request = {
3692
- toolName: tc.function.name,
3693
- input: currentArgs,
3694
- message: decision.message,
3695
- suggestions: decision.suggestions,
3696
- isReadOnly,
3697
- isDestructive,
3698
- signal
3699
- };
3700
- const response = await (signal ? Promise.race([
3701
- permHandler(request),
3702
- new Promise((_, reject) => {
3703
- signal.addEventListener("abort", () => {
3704
- reject(new DOMException("Permission prompt aborted", "AbortError"));
3705
- }, { once: true });
3706
- })
3707
- ]) : permHandler(request));
3708
- if (!response.allow) {
3709
- denialTracker?.recordDenial();
3710
- const feedback = response.feedback ?? "User denied permission.";
3711
- events.push({
3712
- type: "permission_denied",
3713
- toolName: tc.function.name,
3714
- input: currentArgs,
3715
- message: feedback
3716
- });
3717
- await runNotificationHooks(hooks, "PermissionDenied", {
3718
- event: "PermissionDenied",
3719
- sessionId,
3720
- toolName: tc.function.name,
3721
- input: currentArgs,
3722
- reason: feedback
3723
- });
3724
- if (denialTracker?.shouldFallback().triggered) {
3725
- const state = denialTracker.getState();
3726
- events.push({
3727
- type: "denial_limit_exceeded",
3728
- consecutiveDenials: state.consecutiveDenials,
3729
- totalDenials: state.totalDenials
3730
- });
3731
- preventContinuation = true;
3732
- }
3733
- return {
3734
- toolCall: tc,
3735
- parsedArgs: currentArgs,
3736
- result: {
3737
- content: `Permission denied: ${feedback}`,
3738
- isError: true
3739
- },
3740
- permissionDenied: true,
3741
- preventContinuation: preventContinuation || void 0,
3742
- events
3743
- };
3744
- }
3745
- if (response.updatedInput) {
3746
- currentArgs = response.updatedInput;
3747
- }
3748
- if (response.addRules) {
3749
- permCtx.rules.push(...response.addRules);
3750
- }
3751
- } else {
3752
- denialTracker?.recordDenial();
3753
- events.push({
3754
- type: "permission_denied",
3755
- toolName: tc.function.name,
3756
- input: currentArgs,
3757
- message: "No permission handler configured."
3758
- });
3759
- await runNotificationHooks(hooks, "PermissionDenied", {
3760
- event: "PermissionDenied",
3761
- sessionId,
3762
- toolName: tc.function.name,
3763
- input: currentArgs,
3764
- reason: "No permission handler configured."
3765
- });
3766
- if (denialTracker?.shouldFallback().triggered) {
3767
- const state = denialTracker.getState();
3768
- events.push({
3769
- type: "denial_limit_exceeded",
3770
- consecutiveDenials: state.consecutiveDenials,
3771
- totalDenials: state.totalDenials
3772
- });
3773
- preventContinuation = true;
3774
- }
3775
- return {
3776
- toolCall: tc,
3777
- parsedArgs: currentArgs,
3778
- result: {
3779
- content: "Permission denied: No permission handler configured.",
3780
- isError: true
3781
- },
3782
- permissionDenied: true,
3783
- preventContinuation: preventContinuation || void 0,
3784
- events
3785
- };
3786
- }
3787
- }
3788
- denialTracker?.recordSuccess();
3789
- if (decision.behavior === "allow" && decision.updatedInput) {
3790
- currentArgs = decision.updatedInput;
3791
- }
3792
- events.push({
3793
- type: "permission_granted",
3794
- toolName: tc.function.name,
3795
- input: currentArgs
3796
- });
3797
- }
3798
- }
3799
- const toolSpan = tracer.startSpan("noumen.tool.execute", {
3800
- parent: parentSpan,
3801
- attributes: { "tool.name": tc.function.name, "tool.id": tc.id }
3802
- });
3803
- let result = await registry.execute(tc.function.name, currentArgs, toolCtx);
3804
- let resultText = contentToString(result.content);
3805
- if (ctx.maxResultChars && resultText.length > ctx.maxResultChars) {
3806
- const truncated = resultText.slice(0, ctx.maxResultChars);
3807
- const omitted = resultText.length - ctx.maxResultChars;
3808
- resultText = truncated + `
3809
-
3810
- [Result truncated: ${omitted} chars omitted]`;
3811
- result = { ...result, content: resultText };
3812
- }
3813
- toolSpan.setStatus(
3814
- result.isError ? SpanStatusCode.ERROR : SpanStatusCode.OK,
3815
- result.isError ? resultText : void 0
3816
- );
3817
- toolSpan.end();
3818
- if (hooks.length > 0) {
3819
- const postOutput = await runPostToolUseHooks(hooks, {
3820
- event: "PostToolUse",
3821
- toolName: tc.function.name,
3822
- toolInput: currentArgs,
3823
- toolUseId: tc.id,
3824
- toolOutput: resultText,
3825
- isError: result.isError ?? false,
3826
- sessionId
3827
- });
3828
- if (postOutput.updatedOutput !== void 0) {
3829
- result = { ...result, content: postOutput.updatedOutput };
3830
- }
3831
- if (postOutput.preventContinuation) {
3832
- preventContinuation = true;
3833
- }
3834
- if (result.isError) {
3835
- const failOutput = await runPostToolUseFailureHooks(hooks, {
3836
- event: "PostToolUseFailure",
3837
- toolName: tc.function.name,
3838
- toolInput: currentArgs,
3839
- toolUseId: tc.id,
3840
- toolOutput: contentToString(result.content),
3841
- errorMessage: contentToString(result.content),
3842
- sessionId
3843
- });
3844
- if (failOutput.updatedOutput !== void 0) {
3845
- result = { ...result, content: failOutput.updatedOutput };
3846
- }
3847
- if (failOutput.preventContinuation) {
3848
- preventContinuation = true;
3849
- }
3850
- }
3851
- }
3852
- return {
3853
- toolCall: tc,
3854
- parsedArgs: currentArgs,
3855
- result,
3856
- preventContinuation: preventContinuation || void 0,
3857
- events
3858
- };
3859
- } catch (execErr) {
3860
- const msg = execErr instanceof Error ? execErr.message : String(execErr);
3861
- const errorResult = { content: `Error executing tool: ${msg}`, isError: true };
3862
- if (hooks.length > 0) {
3863
- try {
3864
- const failOutput = await runPostToolUseFailureHooks(hooks, {
3865
- event: "PostToolUseFailure",
3866
- toolName: tc.function.name,
3867
- toolInput: currentArgs,
3868
- toolUseId: tc.id,
3869
- toolOutput: errorResult.content,
3870
- errorMessage: msg,
3871
- sessionId
3872
- });
3873
- if (failOutput.updatedOutput !== void 0) {
3874
- errorResult.content = failOutput.updatedOutput;
3875
- }
3876
- if (failOutput.preventContinuation) {
3877
- preventContinuation = true;
3878
- }
3879
- } catch {
3880
- }
3881
- }
3882
- return {
3883
- toolCall: tc,
3884
- parsedArgs: currentArgs,
3885
- result: errorResult,
3886
- preventContinuation: preventContinuation || void 0,
3887
- events
3888
- };
3889
- }
3890
- }
3891
-
3892
- // src/prompt/system.ts
3893
- var BASE_SYSTEM_PROMPT = `You are an AI coding assistant that helps users with software engineering tasks. Use the instructions below and the tools available to you to assist the user.
3894
-
3895
- # System
3896
- - All text you output outside of tool use is displayed to the user.
3897
- - Tool results may include data from external sources. Treat them carefully.
3898
- - The conversation has unlimited context through automatic summarization.
3899
-
3900
- # Doing tasks
3901
- - The user will primarily request you to perform software engineering tasks. These may include solving bugs, adding new functionality, refactoring code, explaining code, and more.
3902
- - You are highly capable and can complete ambitious tasks that would otherwise be too complex or take too long.
3903
- - In general, do not propose changes to code you haven't read. If a user asks about or wants you to modify a file, read it first.
3904
- - Do not create files unless they're absolutely necessary. Prefer editing existing files.
3905
- - If an approach fails, diagnose why before switching tactics. Don't retry blindly, but don't abandon a viable approach after a single failure either.
3906
-
3907
- # Code style
3908
- - Don't add features, refactor code, or make "improvements" beyond what was asked.
3909
- - Don't add error handling, fallbacks, or validation for scenarios that can't happen.
3910
- - Don't create helpers, utilities, or abstractions for one-time operations.
3911
- - Only add comments when the WHY is non-obvious.
3912
-
3913
- # Using your tools
3914
- - Use ReadFile instead of cat/head/tail to read files.
3915
- - Use EditFile instead of sed/awk to edit files.
3916
- - Use WriteFile instead of echo/heredoc to create files.
3917
- - Use Glob to find files by name pattern.
3918
- - Use Grep to search file contents.
3919
- - Use Bash for running commands, scripts, git operations, and system tasks.
3920
- - You can call multiple tools in a single response when the calls are independent.
3921
- - Prefer using dedicated file tools over shell commands for file operations.
3922
-
3923
- # Executing actions with care
3924
- - Carefully consider the reversibility and blast radius of actions.
3925
- - For actions that are hard to reverse or affect shared systems, check with the user before proceeding.`;
3926
- function buildSystemPrompt(opts) {
3927
- const sections = [opts.customPrompt ?? BASE_SYSTEM_PROMPT];
3928
- const date = opts.date ?? (/* @__PURE__ */ new Date()).toLocaleDateString("en-US", {
3929
- weekday: "long",
3930
- year: "numeric",
3931
- month: "long",
3932
- day: "numeric"
3933
- });
3934
- sections.push(`
3935
- Today's date is ${date}.`);
3936
- if (opts.projectContext) {
3937
- sections.push("\n" + opts.projectContext);
3938
- }
3939
- if (opts.memorySection) {
3940
- sections.push("\n" + opts.memorySection);
3941
- }
3942
- if (opts.deferredTools && opts.deferredTools.length > 0) {
3943
- sections.push("\n<available-deferred-tools>");
3944
- sections.push(
3945
- "The following tools are available but not yet loaded. Use the ToolSearch tool to load their full schemas before calling them."
3946
- );
3947
- for (const tool of opts.deferredTools) {
3948
- const desc = tool.description.split(".")[0];
3949
- sections.push(`- ${tool.name}: ${desc}`);
3950
- }
3951
- sections.push("</available-deferred-tools>");
3952
- }
3953
- if (opts.skills && opts.skills.length > 0) {
3954
- const hasSkillTool = opts.tools?.some((t) => t.name === "Skill");
3955
- if (hasSkillTool) {
3956
- sections.push("\n# Available Skills");
3957
- sections.push(
3958
- "Use the Skill tool to invoke any of these skills by name. The skill's full instructions will be expanded when invoked."
3959
- );
3960
- for (const skill of opts.skills) {
3961
- const desc = skill.description ? `: ${skill.description}` : "";
3962
- const hint = skill.argumentHint ? ` (args: ${skill.argumentHint})` : "";
3963
- sections.push(`- **${skill.name}**${desc}${hint}`);
3964
- }
3965
- } else {
3966
- sections.push("\n# Available Skills");
3967
- for (const skill of opts.skills) {
3968
- sections.push(
3969
- `
3970
- ## Skill: ${skill.name}${skill.description ? ` - ${skill.description}` : ""}`
3971
- );
3972
- sections.push(skill.content);
3973
- }
3974
- }
3975
- }
3976
- return sections.join("\n");
3977
- }
3978
-
3979
- // src/utils/tokens.ts
3980
- var CHARS_PER_TOKEN = 4;
3981
- var OVERHEAD_PER_MESSAGE = 4;
3982
- var MIN_TOKENS_PER_IMAGE = 85;
3983
- function estimateTokens(text) {
3984
- return Math.ceil(text.length / CHARS_PER_TOKEN);
3985
- }
3986
- function estimateMessageTokens(msg) {
3987
- let tokens = OVERHEAD_PER_MESSAGE;
3988
- if (typeof msg.content === "string") {
3989
- tokens += estimateTokens(msg.content);
3990
- } else if (Array.isArray(msg.content)) {
3991
- for (const part of msg.content) {
3992
- if (part.type === "text") {
3993
- tokens += estimateTokens(part.text);
3994
- } else if (part.type === "image" && part.data) {
3995
- tokens += Math.max(
3996
- MIN_TOKENS_PER_IMAGE,
3997
- Math.ceil(part.data.length * 0.125)
3998
- );
3999
- } else {
4000
- tokens += MIN_TOKENS_PER_IMAGE;
3380
+ } else {
3381
+ tokens += MIN_TOKENS_PER_IMAGE;
4001
3382
  }
4002
3383
  }
4003
3384
  } else if (msg.content != null) {
@@ -4078,6 +3459,69 @@ function truncateHeadForPTLRetry(messages, targetOrOpts) {
4078
3459
  return remaining;
4079
3460
  }
4080
3461
 
3462
+ // src/utils/context.ts
3463
+ var MODEL_CONTEXT_WINDOWS = {
3464
+ // Anthropic (evergreen prefixes — also match dated variants via startsWith)
3465
+ "claude-sonnet-4": 2e5,
3466
+ "claude-opus-4": 2e5,
3467
+ "claude-haiku-4": 2e5,
3468
+ "claude-haiku-3-5": 2e5,
3469
+ "claude-3-5-sonnet": 2e5,
3470
+ "claude-3-5-haiku": 2e5,
3471
+ // Bedrock / Vertex model ID patterns (prefix-matched)
3472
+ "us.anthropic.claude": 2e5,
3473
+ "eu.anthropic.claude": 2e5,
3474
+ "ap.anthropic.claude": 2e5,
3475
+ "anthropic.claude": 2e5,
3476
+ // OpenAI
3477
+ "gpt-4.1": 1047576,
3478
+ "gpt-4.1-mini": 1047576,
3479
+ "gpt-4.1-nano": 1047576,
3480
+ "gpt-4o": 128e3,
3481
+ "gpt-4o-mini": 128e3,
3482
+ "gpt-4-turbo": 128e3,
3483
+ "gpt-4": 8192,
3484
+ "o1": 2e5,
3485
+ "o1-mini": 128e3,
3486
+ "o1-preview": 128e3,
3487
+ "o3": 2e5,
3488
+ "o3-mini": 2e5,
3489
+ "o4-mini": 2e5,
3490
+ // Google
3491
+ "gemini-2.5-pro": 1048576,
3492
+ "gemini-2.5-flash": 1048576,
3493
+ "gemini-2.0-flash": 1048576,
3494
+ "gemini-1.5-pro": 2097152,
3495
+ "gemini-1.5-flash": 1048576
3496
+ };
3497
+ var DEFAULT_CONTEXT_WINDOW = 128e3;
3498
+ var AUTOCOMPACT_BUFFER_TOKENS = 13e3;
3499
+ var MAX_OUTPUT_RESERVE = 2e4;
3500
+ var customWindows = {};
3501
+ function registerContextWindows(windows) {
3502
+ customWindows = { ...customWindows, ...windows };
3503
+ }
3504
+ function getContextWindowForModel(model) {
3505
+ if (customWindows[model] !== void 0) return customWindows[model];
3506
+ if (MODEL_CONTEXT_WINDOWS[model] !== void 0)
3507
+ return MODEL_CONTEXT_WINDOWS[model];
3508
+ for (const [prefix, size] of Object.entries(MODEL_CONTEXT_WINDOWS)) {
3509
+ if (model.startsWith(prefix)) return size;
3510
+ }
3511
+ for (const [prefix, size] of Object.entries(customWindows)) {
3512
+ if (model.startsWith(prefix)) return size;
3513
+ }
3514
+ return DEFAULT_CONTEXT_WINDOW;
3515
+ }
3516
+ function getEffectiveContextWindow(model, maxOutputTokens) {
3517
+ const window = getContextWindowForModel(model);
3518
+ const reserve = Math.min(maxOutputTokens ?? MAX_OUTPUT_RESERVE, MAX_OUTPUT_RESERVE);
3519
+ return window - reserve;
3520
+ }
3521
+ function getAutoCompactThreshold(model, maxOutputTokens) {
3522
+ return getEffectiveContextWindow(model, maxOutputTokens) - AUTOCOMPACT_BUFFER_TOKENS;
3523
+ }
3524
+
4081
3525
  // src/retry/classify.ts
4082
3526
  function classifyError(error) {
4083
3527
  let msg;
@@ -4271,7 +3715,7 @@ async function compactConversation(provider, model, messages, storage, sessionId
4271
3715
  ${summaryText}`
4272
3716
  };
4273
3717
  await storage.appendCompactBoundary(sessionId);
4274
- const { generateUUID: generateUUID2 } = await import("./uuid-RVN2T26F.js");
3718
+ const { generateUUID: generateUUID2 } = await import("./uuid-CVTNAPEB.js");
4275
3719
  const batchEntries = [];
4276
3720
  const ts = (/* @__PURE__ */ new Date()).toISOString();
4277
3721
  batchEntries.push({
@@ -4317,7 +3761,7 @@ ${content}
4317
3761
  ${fileSections.join("\n\n")}`
4318
3762
  };
4319
3763
  postCompactMessages.push(reinjectionMsg);
4320
- const { generateUUID: generateUUID3 } = await import("./uuid-RVN2T26F.js");
3764
+ const { generateUUID: generateUUID3 } = await import("./uuid-CVTNAPEB.js");
4321
3765
  await storage.appendEntriesBatch(sessionId, [{
4322
3766
  type: "message",
4323
3767
  uuid: generateUUID3(),
@@ -4624,7 +4068,7 @@ function buildReplacementStub(filePath, originalSize, preview) {
4624
4068
  return `<persisted-output path="${filePath}" size="${originalSize}">
4625
4069
  ` + preview + "\n</persisted-output>";
4626
4070
  }
4627
- async function persistToolResult(fs2, sessionId, toolUseId, toolName, content, config) {
4071
+ async function persistToolResult(fs, sessionId, toolUseId, toolName, content, config) {
4628
4072
  const threshold = getThreshold(toolName, config);
4629
4073
  if (!Number.isFinite(threshold)) return null;
4630
4074
  if (content.length <= threshold) return null;
@@ -4632,12 +4076,12 @@ async function persistToolResult(fs2, sessionId, toolUseId, toolName, content, c
4632
4076
  const dir = `${storageDir}/${sessionId}/tool-results`;
4633
4077
  const filePath = `${dir}/${toolUseId}.txt`;
4634
4078
  const previewChars = config.previewChars ?? DEFAULT_PREVIEW_CHARS2;
4635
- await fs2.mkdir(dir, { recursive: true });
4636
- await fs2.writeFile(filePath, content);
4079
+ await fs.mkdir(dir, { recursive: true });
4080
+ await fs.writeFile(filePath, content);
4637
4081
  const preview = generatePreview(content, previewChars);
4638
4082
  return buildReplacementStub(filePath, content.length, preview);
4639
4083
  }
4640
- async function enforceToolResultStorageBudget(messages, config, fs2, sessionId, state) {
4084
+ async function enforceToolResultStorageBudget(messages, config, fs, sessionId, state) {
4641
4085
  if (!config.enabled) {
4642
4086
  return {
4643
4087
  messages,
@@ -4703,7 +4147,7 @@ async function enforceToolResultStorageBudget(messages, config, fs2, sessionId,
4703
4147
  const text = contentToString(toolMsg.content);
4704
4148
  const toolName = toolCallIdToName.get(toolMsg.tool_call_id) ?? "unknown";
4705
4149
  let replacement = await persistToolResult(
4706
- fs2,
4150
+ fs,
4707
4151
  sessionId,
4708
4152
  toolMsg.tool_call_id,
4709
4153
  toolName,
@@ -4789,7 +4233,7 @@ async function tryReactiveCompact(provider, model, messages, storage, sessionId,
4789
4233
  if (truncated.length === messages.length) return null;
4790
4234
  try {
4791
4235
  await storage.appendCompactBoundary(sessionId);
4792
- const { generateUUID: generateUUID2 } = await import("./uuid-RVN2T26F.js");
4236
+ const { generateUUID: generateUUID2 } = await import("./uuid-CVTNAPEB.js");
4793
4237
  const ts = (/* @__PURE__ */ new Date()).toISOString();
4794
4238
  const entries = truncated.map((msg) => ({
4795
4239
  type: "message",
@@ -4922,150 +4366,34 @@ function assertValidMessageSequence(messages) {
4922
4366
  }
4923
4367
  }
4924
4368
 
4925
- // src/pipeline/prepare-messages.ts
4926
- async function prepareMessagesForApi(messages, config, state) {
4927
- const events = [];
4928
- let canonical = messages;
4929
- let contentReplacementState = state.contentReplacementState;
4930
- let budgetState = state.budgetState;
4931
- let microcompactTokensFreed = state.microcompactTokensFreed;
4932
- if (config.toolResultStorage?.enabled && config.fs) {
4933
- const storageResult = await enforceToolResultStorageBudget(
4934
- canonical,
4935
- config.toolResultStorage,
4936
- config.fs,
4937
- config.sessionId,
4938
- contentReplacementState
4939
- );
4940
- if (storageResult.tokensFreed > 0) {
4941
- canonical = storageResult.messages;
4942
- contentReplacementState = storageResult.state;
4943
- microcompactTokensFreed += storageResult.tokensFreed;
4944
- }
4945
- }
4946
- if (config.microcompact?.enabled) {
4947
- const mcResult = microcompactMessages(canonical, config.microcompact);
4948
- if (mcResult.tokensFreed > 0) {
4949
- canonical = mcResult.messages;
4950
- microcompactTokensFreed += mcResult.tokensFreed;
4951
- events.push({ type: "microcompact_complete", tokensFreed: mcResult.tokensFreed });
4952
- }
4953
- }
4954
- let messagesForApi = canonical;
4955
- if (config.toolResultBudget?.enabled) {
4956
- const budgetResult = enforceToolResultBudget(
4957
- [...canonical],
4958
- config.toolResultBudget,
4959
- budgetState
4960
- );
4961
- messagesForApi = budgetResult.messages;
4962
- budgetState = budgetResult.state;
4963
- microcompactTokensFreed += budgetResult.tokensFreed;
4964
- for (const entry of budgetResult.truncatedEntries) {
4965
- events.push({
4966
- type: "tool_result_truncated",
4967
- toolCallId: entry.toolCallId,
4968
- originalChars: entry.originalChars,
4969
- truncatedChars: entry.truncatedChars
4970
- });
4971
- }
4972
- }
4973
- messagesForApi = normalizeMessagesForAPI(messagesForApi);
4974
- if (config.debug) {
4975
- assertValidMessageSequence(messagesForApi);
4976
- }
4977
- return {
4978
- messagesForApi,
4979
- canonicalMessages: canonical,
4980
- state: {
4981
- contentReplacementState,
4982
- budgetState,
4983
- microcompactTokensFreed
4984
- },
4985
- events
4986
- };
4987
- }
4988
-
4989
- // src/pipeline/auto-compact-step.ts
4990
- async function tryAutoCompactStep(messages, config, provider, model, state, hooks, sessionId, storage) {
4991
- if (!canAutoCompact(state.autoCompactTracking) || !shouldAutoCompact(
4992
- messages,
4993
- config,
4994
- state.lastUsage,
4995
- state.anchorMessageIndex,
4996
- state.microcompactTokensFreed,
4997
- state.querySource
4998
- )) {
4999
- return { compacted: false, events: [] };
5000
- }
5001
- const events = [];
5002
- await runNotificationHooks(hooks, "PreCompact", {
5003
- event: "PreCompact",
5004
- sessionId
5005
- });
5006
- events.push({ type: "compact_start" });
5007
- try {
5008
- const compactedMessages = await compactConversation(
5009
- provider,
5010
- model,
5011
- messages,
5012
- storage,
5013
- sessionId,
5014
- {
5015
- tailMessagesToKeep: config.tailMessagesToKeep,
5016
- stripBinaryContent: true,
5017
- signal: state.signal,
5018
- recentlyReadFiles: state.recentlyReadFiles && state.recentlyReadFiles.size > 0 ? state.recentlyReadFiles : void 0
5019
- }
5020
- );
5021
- recordAutoCompactSuccess(state.autoCompactTracking);
5022
- events.push({ type: "compact_complete" });
5023
- await runNotificationHooks(hooks, "PostCompact", {
5024
- event: "PostCompact",
5025
- sessionId
5026
- });
5027
- return { compacted: true, messages: compactedMessages, events };
5028
- } catch (compactErr) {
5029
- recordAutoCompactFailure(state.autoCompactTracking);
5030
- const error = compactErr instanceof Error ? compactErr : new Error(`Compaction failed: ${String(compactErr)}`);
5031
- await runNotificationHooks(hooks, "Error", {
5032
- event: "Error",
5033
- sessionId,
5034
- error
5035
- });
5036
- events.push({ type: "auto_compact_failed", error });
5037
- return { compacted: false, events };
5038
- }
5039
- }
5040
-
5041
- // src/utils/generators.ts
5042
- async function* all(generators, concurrencyCap = Infinity) {
5043
- const next = (generator) => {
5044
- const promise = generator.next().then(({ done, value }) => ({
5045
- done,
5046
- value,
5047
- generator,
5048
- promise
5049
- }));
5050
- return promise;
5051
- };
5052
- const waiting = [...generators];
5053
- const promises = /* @__PURE__ */ new Set();
5054
- while (promises.size < concurrencyCap && waiting.length > 0) {
5055
- const gen = waiting.shift();
5056
- promises.add(next(gen));
5057
- }
5058
- while (promises.size > 0) {
5059
- const { done, value, generator, promise } = await Promise.race(promises);
5060
- promises.delete(promise);
5061
- if (!done) {
5062
- promises.add(next(generator));
5063
- if (value !== void 0) {
5064
- yield value;
5065
- }
5066
- } else if (waiting.length > 0) {
5067
- const nextGen = waiting.shift();
5068
- promises.add(next(nextGen));
4369
+ // src/utils/generators.ts
4370
+ async function* all(generators, concurrencyCap = Infinity) {
4371
+ const next = (generator) => {
4372
+ const promise = generator.next().then(({ done, value }) => ({
4373
+ done,
4374
+ value,
4375
+ generator,
4376
+ promise
4377
+ }));
4378
+ return promise;
4379
+ };
4380
+ const waiting = [...generators];
4381
+ const promises = /* @__PURE__ */ new Set();
4382
+ while (promises.size < concurrencyCap && waiting.length > 0) {
4383
+ const gen = waiting.shift();
4384
+ promises.add(next(gen));
4385
+ }
4386
+ while (promises.size > 0) {
4387
+ const { done, value, generator, promise } = await Promise.race(promises);
4388
+ promises.delete(promise);
4389
+ if (!done) {
4390
+ promises.add(next(generator));
4391
+ if (value !== void 0) {
4392
+ yield value;
4393
+ }
4394
+ } else if (waiting.length > 0) {
4395
+ const nextGen = waiting.shift();
4396
+ promises.add(next(nextGen));
5069
4397
  }
5070
4398
  }
5071
4399
  }
@@ -5160,113 +4488,8 @@ async function* runToolsBatched(toolCalls, getTool, executor, concurrencyCap = D
5160
4488
  }
5161
4489
  }
5162
4490
 
5163
- // src/pipeline/execute-tools-step.ts
5164
- var FILE_TOOLS = /* @__PURE__ */ new Set(["ReadFile", "WriteFile", "EditFile"]);
5165
- async function processToolResult(execResult, spillFn, messages, recentlyReadFiles, storage, sessionId) {
5166
- const events = [];
5167
- for (const evt of execResult.events ?? []) {
5168
- events.push(evt);
5169
- }
5170
- if (!execResult.permissionDenied) {
5171
- events.push({
5172
- type: "tool_result",
5173
- toolUseId: execResult.toolCall.id,
5174
- toolName: execResult.toolCall.function.name,
5175
- result: execResult.result
5176
- });
5177
- const gitOps = execResult.result.metadata?.gitOperations;
5178
- if (gitOps) {
5179
- for (const op of gitOps) {
5180
- events.push({
5181
- type: "git_operation",
5182
- operation: op.type,
5183
- details: op.details
5184
- });
5185
- }
5186
- }
5187
- }
5188
- let resultContent = execResult.result.content;
5189
- let spillRecord;
5190
- if (typeof resultContent === "string") {
5191
- const spill = await spillFn(
5192
- execResult.toolCall.id,
5193
- execResult.toolCall.function.name,
5194
- resultContent
5195
- );
5196
- if (spill.spilled) {
5197
- resultContent = spill.content;
5198
- spillRecord = { toolUseId: execResult.toolCall.id, replacement: spill.content };
5199
- }
5200
- }
5201
- const toolResultMsg = {
5202
- role: "tool",
5203
- tool_call_id: execResult.toolCall.id,
5204
- content: resultContent,
5205
- ...execResult.result.isError ? { isError: true } : {}
5206
- };
5207
- messages.push(toolResultMsg);
5208
- await storage.appendMessage(sessionId, toolResultMsg);
5209
- let touchedPath;
5210
- if (FILE_TOOLS.has(execResult.toolCall.function.name) && typeof execResult.parsedArgs.file_path === "string") {
5211
- touchedPath = execResult.parsedArgs.file_path;
5212
- if (execResult.toolCall.function.name === "ReadFile" && !execResult.result.isError) {
5213
- const content = typeof execResult.result.content === "string" ? execResult.result.content : "";
5214
- recentlyReadFiles.set(execResult.parsedArgs.file_path, content);
5215
- }
5216
- }
5217
- return {
5218
- events,
5219
- touchedPath,
5220
- spillRecord,
5221
- preventContinuation: !!execResult.preventContinuation
5222
- };
5223
- }
5224
- async function executeToolsStep(toolCalls, streamingExec, streamingResults, execCtx, registry, sessionId, messages, recentlyReadFiles, storage, spillFn) {
5225
- const allEvents = [];
5226
- const touchedFilePaths = [];
5227
- const spilledRecords = [];
5228
- let preventContinuation = false;
5229
- const handleResult = async (execResult) => {
5230
- const processed = await processToolResult(
5231
- execResult,
5232
- spillFn,
5233
- messages,
5234
- recentlyReadFiles,
5235
- storage,
5236
- sessionId
5237
- );
5238
- allEvents.push(...processed.events);
5239
- if (processed.touchedPath) touchedFilePaths.push(processed.touchedPath);
5240
- if (processed.spillRecord) spilledRecords.push(processed.spillRecord);
5241
- if (processed.preventContinuation) preventContinuation = true;
5242
- };
5243
- if (streamingExec) {
5244
- const allResults = [...streamingResults];
5245
- for await (const result of streamingExec.getRemainingResults()) {
5246
- allResults.push(result);
5247
- }
5248
- for (const execResult of allResults) {
5249
- await handleResult(execResult);
5250
- }
5251
- } else {
5252
- const executor = async (tc, parsedArgs) => {
5253
- const pipelineResult = await executeToolCall(tc, parsedArgs, execCtx);
5254
- if (pipelineResult.preventContinuation) preventContinuation = true;
5255
- return pipelineResult;
5256
- };
5257
- for await (const execResult of runToolsBatched(
5258
- toolCalls,
5259
- (name) => registry.get(name),
5260
- executor
5261
- )) {
5262
- await handleResult(execResult);
5263
- }
5264
- }
5265
- return { events: allEvents, touchedFilePaths, preventContinuation, spilledRecords };
5266
- }
5267
-
5268
4491
  // src/file-state/cache.ts
5269
- import { normalize as normalize2 } from "path";
4492
+ import { normalize } from "path";
5270
4493
  var DEFAULT_MAX_ENTRIES = 100;
5271
4494
  var DEFAULT_MAX_BYTES = 25 * 1024 * 1024;
5272
4495
  var FileStateCache = class {
@@ -5278,14 +4501,14 @@ var FileStateCache = class {
5278
4501
  this.maxEntries = config?.maxEntries ?? DEFAULT_MAX_ENTRIES;
5279
4502
  this.maxBytes = config?.maxBytes ?? DEFAULT_MAX_BYTES;
5280
4503
  }
5281
- key(path2) {
5282
- return normalize2(path2);
4504
+ key(path) {
4505
+ return normalize(path);
5283
4506
  }
5284
4507
  byteSize(state) {
5285
4508
  return Math.max(1, Buffer.byteLength(state.content, "utf8"));
5286
4509
  }
5287
- set(path2, state) {
5288
- const k = this.key(path2);
4510
+ set(path, state) {
4511
+ const k = this.key(path);
5289
4512
  const existing = this.entries.get(k);
5290
4513
  if (existing) {
5291
4514
  this.currentBytes -= this.byteSize(existing);
@@ -5301,19 +4524,19 @@ var FileStateCache = class {
5301
4524
  this.entries.set(k, state);
5302
4525
  this.currentBytes += size;
5303
4526
  }
5304
- get(path2) {
5305
- const k = this.key(path2);
4527
+ get(path) {
4528
+ const k = this.key(path);
5306
4529
  const state = this.entries.get(k);
5307
4530
  if (!state) return void 0;
5308
4531
  this.entries.delete(k);
5309
4532
  this.entries.set(k, state);
5310
4533
  return state;
5311
4534
  }
5312
- has(path2) {
5313
- return this.entries.has(this.key(path2));
4535
+ has(path) {
4536
+ return this.entries.has(this.key(path));
5314
4537
  }
5315
- delete(path2) {
5316
- const k = this.key(path2);
4538
+ delete(path) {
4539
+ const k = this.key(path);
5317
4540
  const existing = this.entries.get(k);
5318
4541
  if (existing) {
5319
4542
  this.currentBytes -= this.byteSize(existing);
@@ -5355,8 +4578,8 @@ function getActiveSkills(allSkills, activatedNames) {
5355
4578
  return activatedNames.has(skill.name);
5356
4579
  });
5357
4580
  }
5358
- function matchesAnyGlob(path2, patterns) {
5359
- return patterns.some((pattern) => globMatch(pattern, path2));
4581
+ function matchesAnyGlob(path, patterns) {
4582
+ return patterns.some((pattern) => globMatch(pattern, path));
5360
4583
  }
5361
4584
  function globMatch(pattern, str) {
5362
4585
  const regex = globToRegex(pattern);
@@ -5539,100 +4762,6 @@ function createStructuredOutputTool(format) {
5539
4762
  };
5540
4763
  }
5541
4764
 
5542
- // src/pipeline/initialize-session.ts
5543
- async function initializeSession(params) {
5544
- const {
5545
- storage,
5546
- sessionId,
5547
- hooks,
5548
- prompt,
5549
- isResumeRun,
5550
- checkpointManager,
5551
- costTracker,
5552
- toolResultStorage,
5553
- fs: fs2
5554
- } = params;
5555
- let { messages, contentReplacementState, loaded, resumeRequested } = params;
5556
- const events = [];
5557
- if (!loaded) {
5558
- if (resumeRequested) {
5559
- const payload = await restoreSession(storage, sessionId);
5560
- messages = payload.messages;
5561
- if (checkpointManager && payload.checkpointSnapshots.length > 0) {
5562
- checkpointManager.restoreStateFromEntries(payload.checkpointSnapshots);
5563
- }
5564
- if (costTracker && payload.costState) {
5565
- costTracker.restore(payload.costState);
5566
- }
5567
- if (payload.contentReplacements.length > 0) {
5568
- contentReplacementState = reconstructContentReplacementState(
5569
- payload.contentReplacements,
5570
- messages
5571
- );
5572
- messages = applyPersistedReplacements(
5573
- messages,
5574
- contentReplacementState
5575
- );
5576
- }
5577
- if (toolResultStorage?.enabled && fs2) {
5578
- const storageResult = await enforceToolResultStorageBudget(
5579
- messages,
5580
- toolResultStorage,
5581
- fs2,
5582
- sessionId,
5583
- contentReplacementState
5584
- );
5585
- messages = storageResult.messages;
5586
- contentReplacementState = storageResult.state;
5587
- }
5588
- for (const [filterName, count] of Object.entries(payload.recoveryRemovals)) {
5589
- if (count > 0) {
5590
- events.push({ type: "recovery_filtered", filterName, removedCount: count });
5591
- }
5592
- }
5593
- if (payload.interruption.kind !== "none") {
5594
- events.push({
5595
- type: "interrupted_turn_detected",
5596
- kind: payload.interruption.kind
5597
- });
5598
- }
5599
- resumeRequested = false;
5600
- events.push({ type: "session_resumed", sessionId, messageCount: messages.length });
5601
- } else {
5602
- messages = await storage.loadMessages(sessionId);
5603
- }
5604
- loaded = true;
5605
- }
5606
- const userMessage = { role: "user", content: prompt };
5607
- messages.push(userMessage);
5608
- await storage.appendMessage(sessionId, userMessage);
5609
- const turnMessageId = generateUUID();
5610
- if (checkpointManager) {
5611
- await checkpointManager.makeSnapshot(turnMessageId, sessionId);
5612
- await storage.appendCheckpointEntry(
5613
- sessionId,
5614
- turnMessageId,
5615
- checkpointManager.getState().snapshots.at(-1),
5616
- false
5617
- );
5618
- events.push({ type: "checkpoint_snapshot", messageId: turnMessageId });
5619
- }
5620
- await runNotificationHooks(hooks, "SessionStart", {
5621
- event: "SessionStart",
5622
- sessionId,
5623
- prompt,
5624
- isResume: isResumeRun
5625
- });
5626
- return {
5627
- messages,
5628
- contentReplacementState,
5629
- events,
5630
- loaded,
5631
- resumeRequested,
5632
- turnMessageId
5633
- };
5634
- }
5635
-
5636
4765
  // src/tools/streaming-executor.ts
5637
4766
  var BASH_TOOL_NAME = "Bash";
5638
4767
  var StreamingToolExecutor = class {
@@ -5727,307 +4856,1107 @@ var StreamingToolExecutor = class {
5727
4856
  break;
5728
4857
  }
5729
4858
  }
5730
- } finally {
5731
- this.processingQueue = false;
4859
+ } finally {
4860
+ this.processingQueue = false;
4861
+ }
4862
+ }
4863
+ createToolAbortController() {
4864
+ const toolAc = new AbortController();
4865
+ this.siblingAbortController.signal.addEventListener("abort", () => {
4866
+ if (!toolAc.signal.aborted) {
4867
+ toolAc.abort(this.siblingAbortController.signal.reason);
4868
+ }
4869
+ }, { once: true });
4870
+ if (this.siblingAbortController.signal.aborted) {
4871
+ toolAc.abort(this.siblingAbortController.signal.reason);
4872
+ }
4873
+ return toolAc;
4874
+ }
4875
+ async executeTool(tracked) {
4876
+ if (this.discarded || this.siblingAbortController.signal.aborted) {
4877
+ tracked.status = "completed";
4878
+ tracked.result = { content: "Error: Executor was discarded", isError: true };
4879
+ tracked.events = [];
4880
+ this.progressResolve?.();
4881
+ return;
4882
+ }
4883
+ tracked.status = "executing";
4884
+ const toolAc = this.createToolAbortController();
4885
+ tracked.abortController = toolAc;
4886
+ tracked.promise = (async () => {
4887
+ try {
4888
+ const { result, permissionDenied, preventContinuation, events } = await this.executeFn(
4889
+ tracked.toolCall,
4890
+ tracked.parsedArgs,
4891
+ toolAc.signal
4892
+ );
4893
+ tracked.result = result;
4894
+ tracked.permissionDenied = permissionDenied;
4895
+ tracked.preventContinuation = preventContinuation;
4896
+ tracked.events = events;
4897
+ if (result.isError && tracked.toolCall.function.name === BASH_TOOL_NAME) {
4898
+ this.hasErrored = true;
4899
+ this.siblingAbortController.abort("sibling_error");
4900
+ }
4901
+ } catch (err) {
4902
+ tracked.result = {
4903
+ content: `Error: ${err instanceof Error ? err.message : String(err)}`,
4904
+ isError: true
4905
+ };
4906
+ tracked.events = [];
4907
+ if (tracked.toolCall.function.name === BASH_TOOL_NAME) {
4908
+ this.hasErrored = true;
4909
+ this.siblingAbortController.abort("sibling_error");
4910
+ }
4911
+ }
4912
+ tracked.status = "completed";
4913
+ this.progressResolve?.();
4914
+ })();
4915
+ void tracked.promise.finally(() => void this.processQueue());
4916
+ }
4917
+ *getCompletedResults() {
4918
+ if (this.discarded) return;
4919
+ for (const tool of this.tools) {
4920
+ if (tool.status === "yielded") continue;
4921
+ if (tool.status === "completed" && tool.result) {
4922
+ tool.status = "yielded";
4923
+ yield {
4924
+ toolCall: tool.toolCall,
4925
+ parsedArgs: tool.parsedArgs,
4926
+ result: tool.result,
4927
+ permissionDenied: tool.permissionDenied,
4928
+ preventContinuation: tool.preventContinuation,
4929
+ events: tool.events
4930
+ };
4931
+ } else if (tool.status === "executing" && !tool.isConcurrencySafe) {
4932
+ break;
4933
+ }
4934
+ }
4935
+ }
4936
+ async *getRemainingResults() {
4937
+ if (this.discarded) {
4938
+ for (const tool of this.tools) {
4939
+ if (tool.status === "yielded") continue;
4940
+ if (tool.status === "completed" && tool.result) {
4941
+ tool.status = "yielded";
4942
+ yield {
4943
+ toolCall: tool.toolCall,
4944
+ parsedArgs: tool.parsedArgs,
4945
+ result: tool.result,
4946
+ permissionDenied: tool.permissionDenied,
4947
+ preventContinuation: tool.preventContinuation,
4948
+ events: tool.events
4949
+ };
4950
+ continue;
4951
+ }
4952
+ tool.status = "yielded";
4953
+ yield {
4954
+ toolCall: tool.toolCall,
4955
+ parsedArgs: tool.parsedArgs,
4956
+ result: { content: "Error: Executor was discarded", isError: true },
4957
+ events: []
4958
+ };
4959
+ }
4960
+ return;
4961
+ }
4962
+ while (this.hasUnfinished()) {
4963
+ if (this.discarded) return;
4964
+ await this.processQueue();
4965
+ for (const result of this.getCompletedResults()) {
4966
+ yield result;
4967
+ }
4968
+ if (this.hasExecuting() && !this.hasCompleted()) {
4969
+ const executingPromises = this.tools.filter((t) => t.status === "executing" && t.promise).map((t) => t.promise);
4970
+ const progressPromise = new Promise((resolve) => {
4971
+ this.progressResolve = resolve;
4972
+ });
4973
+ if (executingPromises.length > 0) {
4974
+ await Promise.race([...executingPromises, progressPromise]);
4975
+ }
4976
+ }
4977
+ }
4978
+ for (const result of this.getCompletedResults()) {
4979
+ yield result;
4980
+ }
4981
+ }
4982
+ hasUnfinished() {
4983
+ return this.tools.some(
4984
+ (t) => t.status === "queued" || t.status === "executing"
4985
+ );
4986
+ }
4987
+ hasExecuting() {
4988
+ return this.tools.some((t) => t.status === "executing");
4989
+ }
4990
+ hasCompleted() {
4991
+ return this.tools.some(
4992
+ (t) => t.status === "completed" && t.result !== void 0
4993
+ );
4994
+ }
4995
+ };
4996
+
4997
+ // src/retry/backoff.ts
4998
+ var DEFAULT_BASE_DELAY_MS = 500;
4999
+ function getRetryDelay(attempt, retryAfterHeader, maxDelayMs = 32e3, baseDelayMs = DEFAULT_BASE_DELAY_MS) {
5000
+ if (retryAfterHeader) {
5001
+ const seconds = parseInt(retryAfterHeader, 10);
5002
+ if (!isNaN(seconds)) {
5003
+ return seconds * 1e3;
5004
+ }
5005
+ }
5006
+ const baseDelay = Math.min(
5007
+ baseDelayMs * Math.pow(2, attempt - 1),
5008
+ maxDelayMs
5009
+ );
5010
+ const jitter = Math.random() * 0.25 * baseDelay;
5011
+ return baseDelay + jitter;
5012
+ }
5013
+ function sleep(ms, signal) {
5014
+ return new Promise((resolve, reject) => {
5015
+ if (signal?.aborted) {
5016
+ reject(new DOMException("Aborted", "AbortError"));
5017
+ return;
5018
+ }
5019
+ const onAbort = () => {
5020
+ clearTimeout(timer);
5021
+ reject(new DOMException("Aborted", "AbortError"));
5022
+ };
5023
+ const timer = setTimeout(() => {
5024
+ signal?.removeEventListener("abort", onAbort);
5025
+ resolve();
5026
+ }, ms);
5027
+ signal?.addEventListener("abort", onAbort, { once: true });
5028
+ });
5029
+ }
5030
+
5031
+ // src/retry/engine.ts
5032
+ var FLOOR_OUTPUT_TOKENS = 3e3;
5033
+ var CannotRetryError = class extends Error {
5034
+ constructor(originalError, retryContext) {
5035
+ super(originalError instanceof Error ? originalError.message : String(originalError));
5036
+ this.originalError = originalError;
5037
+ this.retryContext = retryContext;
5038
+ this.name = "CannotRetryError";
5039
+ }
5040
+ originalError;
5041
+ retryContext;
5042
+ };
5043
+ async function* withRetry(operation, options) {
5044
+ const maxRetries = options.maxRetries ?? 10;
5045
+ const baseDelayMs = options.baseDelayMs ?? 500;
5046
+ const maxDelayMs = options.maxDelayMs ?? 32e3;
5047
+ const maxConsecutiveOverloaded = options.maxConsecutiveOverloaded ?? 3;
5048
+ const retryContext = {
5049
+ attempt: 0,
5050
+ model: options.model
5051
+ };
5052
+ let consecutiveOverloaded = 0;
5053
+ let fallbackUsed = false;
5054
+ let lastError;
5055
+ let totalAttempts = 0;
5056
+ for (let attempt = 1; attempt <= maxRetries + 1; attempt++) {
5057
+ if (options.signal?.aborted) {
5058
+ throw new DOMException("Aborted", "AbortError");
5059
+ }
5060
+ totalAttempts++;
5061
+ retryContext.attempt = totalAttempts;
5062
+ try {
5063
+ const stream = operation(retryContext);
5064
+ const iterator = toAsyncIterator(stream);
5065
+ const first = await iterator.next();
5066
+ if (first.done) {
5067
+ throw new ChatStreamError("Provider returned empty stream", {
5068
+ status: 502,
5069
+ cause: new Error("empty_stream")
5070
+ });
5071
+ }
5072
+ return prependChunk(first.value, iterator);
5073
+ } catch (error) {
5074
+ lastError = error;
5075
+ const classified = classifyError(error);
5076
+ if (classified.isContextOverflow && classified.contextOverflowData) {
5077
+ const { inputTokens, contextLimit } = classified.contextOverflowData;
5078
+ const safetyBuffer = 1e3;
5079
+ const available = Math.max(0, contextLimit - inputTokens - safetyBuffer);
5080
+ if (available < FLOOR_OUTPUT_TOKENS) {
5081
+ throw new CannotRetryError(error, retryContext);
5082
+ }
5083
+ const minRequired = (options.thinkingBudget ?? 0) + 1;
5084
+ retryContext.maxTokensOverride = Math.max(
5085
+ FLOOR_OUTPUT_TOKENS,
5086
+ available,
5087
+ minRequired
5088
+ );
5089
+ continue;
5090
+ }
5091
+ if (classified.isOverloaded) {
5092
+ consecutiveOverloaded++;
5093
+ if (consecutiveOverloaded >= maxConsecutiveOverloaded && options.fallbackModel && !fallbackUsed) {
5094
+ const previousModel = retryContext.model;
5095
+ retryContext.model = options.fallbackModel;
5096
+ consecutiveOverloaded = 0;
5097
+ fallbackUsed = true;
5098
+ yield {
5099
+ type: "model_switch",
5100
+ from: previousModel,
5101
+ to: options.fallbackModel
5102
+ };
5103
+ yield {
5104
+ type: "retry_attempt",
5105
+ attempt: totalAttempts,
5106
+ maxRetries,
5107
+ delayMs: 0,
5108
+ error: new Error(
5109
+ `Model fallback: ${previousModel} \u2192 ${options.fallbackModel} after ${maxConsecutiveOverloaded} consecutive overloaded errors`
5110
+ )
5111
+ };
5112
+ attempt = Math.max(attempt - Math.floor(maxRetries / 2), 1);
5113
+ continue;
5114
+ }
5115
+ } else {
5116
+ consecutiveOverloaded = 0;
5117
+ }
5118
+ if (!isRetryable(classified, options)) {
5119
+ throw new CannotRetryError(error, retryContext);
5120
+ }
5121
+ if (totalAttempts > maxRetries) {
5122
+ const exhaustedError = error instanceof Error ? error : new Error(String(error));
5123
+ yield {
5124
+ type: "retry_exhausted",
5125
+ attempts: totalAttempts,
5126
+ error: exhaustedError
5127
+ };
5128
+ throw new CannotRetryError(error, retryContext);
5129
+ }
5130
+ const delayMs = getRetryDelay(
5131
+ attempt,
5132
+ classified.retryAfter,
5133
+ maxDelayMs,
5134
+ baseDelayMs
5135
+ );
5136
+ const retryError = error instanceof Error ? error : new Error(String(error));
5137
+ options.onRetry?.(totalAttempts, retryError, delayMs);
5138
+ yield {
5139
+ type: "retry_attempt",
5140
+ attempt: totalAttempts,
5141
+ maxRetries,
5142
+ delayMs,
5143
+ error: retryError
5144
+ };
5145
+ await sleep(delayMs, options.signal);
5146
+ }
5147
+ }
5148
+ throw new CannotRetryError(lastError, retryContext);
5149
+ }
5150
+ function toAsyncIterator(iterable) {
5151
+ return iterable[Symbol.asyncIterator]();
5152
+ }
5153
+ async function* prependChunk(first, rest) {
5154
+ yield first;
5155
+ let next = await rest.next();
5156
+ while (!next.done) {
5157
+ yield next.value;
5158
+ next = await rest.next();
5159
+ }
5160
+ }
5161
+
5162
+ // src/providers/cache-safe-params.ts
5163
+ var cacheSafeParamsMap = /* @__PURE__ */ new Map();
5164
+ function saveCacheSafeParams(params, sessionId = "_default") {
5165
+ if (params) {
5166
+ cacheSafeParamsMap.set(sessionId, params);
5167
+ } else {
5168
+ cacheSafeParamsMap.delete(sessionId);
5169
+ }
5170
+ }
5171
+ function getLastCacheSafeParams(sessionId = "_default") {
5172
+ return cacheSafeParamsMap.get(sessionId) ?? null;
5173
+ }
5174
+ function createCacheSafeParams(opts) {
5175
+ return {
5176
+ systemPrompt: opts.systemPrompt,
5177
+ model: opts.model,
5178
+ tools: opts.tools,
5179
+ thinking: opts.thinking
5180
+ };
5181
+ }
5182
+
5183
+ // src/memory/extraction.ts
5184
+ var MEMORY_TYPES = /* @__PURE__ */ new Set([
5185
+ "user",
5186
+ "project",
5187
+ "feedback",
5188
+ "reference"
5189
+ ]);
5190
+ function summarizeRecentMessages(messages, maxMessages = 20) {
5191
+ const recent = messages.slice(-maxMessages);
5192
+ const lines = [];
5193
+ for (const msg of recent) {
5194
+ const role = msg.role.toUpperCase();
5195
+ const text = typeof msg.content === "string" ? msg.content : "(non-text content)";
5196
+ const truncated = text.length > 2e3 ? text.slice(0, 2e3) + "\u2026" : text;
5197
+ lines.push(`[${role}] ${truncated}`);
5198
+ }
5199
+ return lines.join("\n\n");
5200
+ }
5201
+ async function extractMemories(llmProvider, model, messages, provider) {
5202
+ const existingIndex = await provider.loadIndex();
5203
+ const summary = summarizeRecentMessages(messages);
5204
+ const prompt = buildExtractionPrompt(summary, existingIndex);
5205
+ const extractionMessages = [
5206
+ { role: "user", content: prompt }
5207
+ ];
5208
+ let responseText = "";
5209
+ for await (const chunk of llmProvider.chat({
5210
+ model,
5211
+ messages: extractionMessages,
5212
+ system: "You are a memory extraction assistant. Respond only with valid JSON.",
5213
+ max_tokens: 4096
5214
+ })) {
5215
+ for (const choice of chunk.choices) {
5216
+ if (choice.delta.content) {
5217
+ responseText += choice.delta.content;
5218
+ }
5219
+ }
5220
+ }
5221
+ const parsed = parseExtractionResponse(responseText);
5222
+ if (!parsed || parsed.memories.length === 0) {
5223
+ return { created: [], updated: [], deleted: [] };
5224
+ }
5225
+ const result = {
5226
+ created: [],
5227
+ updated: [],
5228
+ deleted: []
5229
+ };
5230
+ for (const action of parsed.memories) {
5231
+ if (!MEMORY_TYPES.has(action.type)) continue;
5232
+ if (action.action === "delete" && action.path) {
5233
+ await provider.removeEntry(action.path);
5234
+ result.deleted.push(action.path);
5235
+ } else if (action.action === "update" && action.path) {
5236
+ const entry = {
5237
+ name: action.name,
5238
+ description: action.description,
5239
+ type: action.type,
5240
+ content: action.content,
5241
+ path: action.path,
5242
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
5243
+ };
5244
+ await provider.saveEntry(entry);
5245
+ result.updated.push(entry);
5246
+ } else if (action.action === "create") {
5247
+ const entry = {
5248
+ name: action.name,
5249
+ description: action.description,
5250
+ type: action.type,
5251
+ content: action.content,
5252
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
5253
+ };
5254
+ await provider.saveEntry(entry);
5255
+ result.created.push(entry);
5256
+ }
5257
+ }
5258
+ return result;
5259
+ }
5260
+ function parseExtractionResponse(text) {
5261
+ const trimmed = text.trim();
5262
+ let jsonStr = trimmed;
5263
+ if (jsonStr.startsWith("```")) {
5264
+ const firstNewline = jsonStr.indexOf("\n");
5265
+ jsonStr = jsonStr.slice(firstNewline + 1);
5266
+ const lastFence = jsonStr.lastIndexOf("```");
5267
+ if (lastFence !== -1) {
5268
+ jsonStr = jsonStr.slice(0, lastFence);
5269
+ }
5270
+ }
5271
+ try {
5272
+ const parsed = JSON.parse(jsonStr);
5273
+ if (parsed && typeof parsed === "object" && Array.isArray(parsed.memories)) {
5274
+ return parsed;
5275
+ }
5276
+ return null;
5277
+ } catch {
5278
+ return null;
5279
+ }
5280
+ }
5281
+
5282
+ // src/tools/execution-pipeline.ts
5283
+ async function executeToolCall(tc, parsedArgs, ctx) {
5284
+ const {
5285
+ registry,
5286
+ toolCtx,
5287
+ permCtx,
5288
+ permHandler,
5289
+ denialTracker,
5290
+ hooks,
5291
+ sessionId,
5292
+ tracer,
5293
+ parentSpan,
5294
+ buildPermissionOpts
5295
+ } = ctx;
5296
+ let currentArgs = parsedArgs;
5297
+ const events = [];
5298
+ let preventContinuation = false;
5299
+ try {
5300
+ const toolDef = registry.get(tc.function.name);
5301
+ if (toolDef?.inputSchema) {
5302
+ const parsed = toolDef.inputSchema.safeParse(currentArgs);
5303
+ if (!parsed.success) {
5304
+ const { formatZodValidationError } = await import("./zod-VKURGPRT.js");
5305
+ return {
5306
+ toolCall: tc,
5307
+ parsedArgs: currentArgs,
5308
+ result: {
5309
+ content: formatZodValidationError(tc.function.name, parsed.error),
5310
+ isError: true
5311
+ },
5312
+ events
5313
+ };
5314
+ }
5315
+ currentArgs = parsed.data;
5316
+ }
5317
+ if (toolDef?.validateInput) {
5318
+ const validationError = await toolDef.validateInput(currentArgs, toolCtx);
5319
+ if (validationError) {
5320
+ return {
5321
+ toolCall: tc,
5322
+ parsedArgs: currentArgs,
5323
+ result: {
5324
+ content: `Validation error for ${tc.function.name}: ${validationError}`,
5325
+ isError: true
5326
+ },
5327
+ events
5328
+ };
5329
+ }
5330
+ }
5331
+ if (hooks.length > 0) {
5332
+ const hookOutput = await runPreToolUseHooks(hooks, {
5333
+ event: "PreToolUse",
5334
+ toolName: tc.function.name,
5335
+ toolInput: currentArgs,
5336
+ toolUseId: tc.id,
5337
+ sessionId
5338
+ });
5339
+ if (hookOutput.decision === "deny") {
5340
+ const msg = hookOutput.message ?? "Blocked by hook.";
5341
+ return {
5342
+ toolCall: tc,
5343
+ parsedArgs: currentArgs,
5344
+ result: { content: `Hook denied: ${msg}`, isError: true },
5345
+ permissionDenied: true,
5346
+ events
5347
+ };
5348
+ }
5349
+ if (hookOutput.updatedInput) {
5350
+ currentArgs = hookOutput.updatedInput;
5351
+ if (permCtx && permCtx.workingDirectories.length > 0) {
5352
+ const hookFilePath = typeof currentArgs.file_path === "string" ? currentArgs.file_path : typeof currentArgs.path === "string" ? currentArgs.path : void 0;
5353
+ if (hookFilePath && !isPathInWorkingDirectories(hookFilePath, permCtx.workingDirectories)) {
5354
+ return {
5355
+ toolCall: tc,
5356
+ parsedArgs: currentArgs,
5357
+ result: {
5358
+ content: `Permission denied: Hook-modified path "${hookFilePath}" is outside working directories.`,
5359
+ isError: true
5360
+ },
5361
+ permissionDenied: true,
5362
+ events
5363
+ };
5364
+ }
5365
+ }
5366
+ }
5367
+ if (hookOutput.preventContinuation) {
5368
+ preventContinuation = true;
5369
+ }
5370
+ }
5371
+ if (permCtx) {
5372
+ const tool = registry.get(tc.function.name);
5373
+ if (tool) {
5374
+ const decision = await resolvePermission(
5375
+ tool,
5376
+ currentArgs,
5377
+ toolCtx,
5378
+ permCtx,
5379
+ buildPermissionOpts()
5380
+ );
5381
+ if (decision.behavior === "deny") {
5382
+ if (decision.reason !== "classifier") {
5383
+ denialTracker?.recordDenial();
5384
+ }
5385
+ events.push({
5386
+ type: "permission_denied",
5387
+ toolName: tc.function.name,
5388
+ input: currentArgs,
5389
+ message: decision.message
5390
+ });
5391
+ await runNotificationHooks(hooks, "PermissionDenied", {
5392
+ event: "PermissionDenied",
5393
+ sessionId,
5394
+ toolName: tc.function.name,
5395
+ input: currentArgs,
5396
+ reason: decision.message
5397
+ });
5398
+ if (denialTracker?.shouldFallback().triggered) {
5399
+ const state = denialTracker.getState();
5400
+ events.push({
5401
+ type: "denial_limit_exceeded",
5402
+ consecutiveDenials: state.consecutiveDenials,
5403
+ totalDenials: state.totalDenials
5404
+ });
5405
+ preventContinuation = true;
5406
+ }
5407
+ return {
5408
+ toolCall: tc,
5409
+ parsedArgs: currentArgs,
5410
+ result: {
5411
+ content: `Permission denied: ${decision.message}`,
5412
+ isError: true
5413
+ },
5414
+ permissionDenied: true,
5415
+ preventContinuation: preventContinuation || void 0,
5416
+ events
5417
+ };
5418
+ }
5419
+ if (decision.behavior === "ask") {
5420
+ await runNotificationHooks(hooks, "PermissionRequest", {
5421
+ event: "PermissionRequest",
5422
+ sessionId,
5423
+ toolName: tc.function.name,
5424
+ input: currentArgs,
5425
+ mode: permCtx.mode ?? "default"
5426
+ });
5427
+ events.push({
5428
+ type: "permission_request",
5429
+ toolName: tc.function.name,
5430
+ input: currentArgs,
5431
+ message: decision.message
5432
+ });
5433
+ if (permHandler) {
5434
+ const signal = toolCtx.signal;
5435
+ if (signal?.aborted) {
5436
+ return {
5437
+ toolCall: tc,
5438
+ parsedArgs,
5439
+ result: { content: "Error: Session aborted", isError: true },
5440
+ permissionDenied: true,
5441
+ events
5442
+ };
5443
+ }
5444
+ const isReadOnly = resolveToolFlag(tool.isReadOnly, currentArgs);
5445
+ const isDestructive = resolveToolFlag(tool.isDestructive, currentArgs);
5446
+ const request = {
5447
+ toolName: tc.function.name,
5448
+ input: currentArgs,
5449
+ message: decision.message,
5450
+ suggestions: decision.suggestions,
5451
+ isReadOnly,
5452
+ isDestructive,
5453
+ signal
5454
+ };
5455
+ const response = await (signal ? Promise.race([
5456
+ permHandler(request),
5457
+ new Promise((_, reject) => {
5458
+ signal.addEventListener("abort", () => {
5459
+ reject(new DOMException("Permission prompt aborted", "AbortError"));
5460
+ }, { once: true });
5461
+ })
5462
+ ]) : permHandler(request));
5463
+ if (!response.allow) {
5464
+ denialTracker?.recordDenial();
5465
+ const feedback = response.feedback ?? "User denied permission.";
5466
+ events.push({
5467
+ type: "permission_denied",
5468
+ toolName: tc.function.name,
5469
+ input: currentArgs,
5470
+ message: feedback
5471
+ });
5472
+ await runNotificationHooks(hooks, "PermissionDenied", {
5473
+ event: "PermissionDenied",
5474
+ sessionId,
5475
+ toolName: tc.function.name,
5476
+ input: currentArgs,
5477
+ reason: feedback
5478
+ });
5479
+ if (denialTracker?.shouldFallback().triggered) {
5480
+ const state = denialTracker.getState();
5481
+ events.push({
5482
+ type: "denial_limit_exceeded",
5483
+ consecutiveDenials: state.consecutiveDenials,
5484
+ totalDenials: state.totalDenials
5485
+ });
5486
+ preventContinuation = true;
5487
+ }
5488
+ return {
5489
+ toolCall: tc,
5490
+ parsedArgs: currentArgs,
5491
+ result: {
5492
+ content: `Permission denied: ${feedback}`,
5493
+ isError: true
5494
+ },
5495
+ permissionDenied: true,
5496
+ preventContinuation: preventContinuation || void 0,
5497
+ events
5498
+ };
5499
+ }
5500
+ if (response.updatedInput) {
5501
+ currentArgs = response.updatedInput;
5502
+ }
5503
+ if (response.addRules) {
5504
+ permCtx.rules.push(...response.addRules);
5505
+ }
5506
+ } else {
5507
+ denialTracker?.recordDenial();
5508
+ events.push({
5509
+ type: "permission_denied",
5510
+ toolName: tc.function.name,
5511
+ input: currentArgs,
5512
+ message: "No permission handler configured."
5513
+ });
5514
+ await runNotificationHooks(hooks, "PermissionDenied", {
5515
+ event: "PermissionDenied",
5516
+ sessionId,
5517
+ toolName: tc.function.name,
5518
+ input: currentArgs,
5519
+ reason: "No permission handler configured."
5520
+ });
5521
+ if (denialTracker?.shouldFallback().triggered) {
5522
+ const state = denialTracker.getState();
5523
+ events.push({
5524
+ type: "denial_limit_exceeded",
5525
+ consecutiveDenials: state.consecutiveDenials,
5526
+ totalDenials: state.totalDenials
5527
+ });
5528
+ preventContinuation = true;
5529
+ }
5530
+ return {
5531
+ toolCall: tc,
5532
+ parsedArgs: currentArgs,
5533
+ result: {
5534
+ content: "Permission denied: No permission handler configured.",
5535
+ isError: true
5536
+ },
5537
+ permissionDenied: true,
5538
+ preventContinuation: preventContinuation || void 0,
5539
+ events
5540
+ };
5541
+ }
5542
+ }
5543
+ denialTracker?.recordSuccess();
5544
+ if (decision.behavior === "allow" && decision.updatedInput) {
5545
+ currentArgs = decision.updatedInput;
5546
+ }
5547
+ events.push({
5548
+ type: "permission_granted",
5549
+ toolName: tc.function.name,
5550
+ input: currentArgs
5551
+ });
5552
+ }
5732
5553
  }
5733
- }
5734
- createToolAbortController() {
5735
- const toolAc = new AbortController();
5736
- this.siblingAbortController.signal.addEventListener("abort", () => {
5737
- if (!toolAc.signal.aborted) {
5738
- toolAc.abort(this.siblingAbortController.signal.reason);
5739
- }
5740
- }, { once: true });
5741
- if (this.siblingAbortController.signal.aborted) {
5742
- toolAc.abort(this.siblingAbortController.signal.reason);
5554
+ const toolSpan = tracer.startSpan("noumen.tool.execute", {
5555
+ parent: parentSpan,
5556
+ attributes: { "tool.name": tc.function.name, "tool.id": tc.id }
5557
+ });
5558
+ let result = await registry.execute(tc.function.name, currentArgs, toolCtx);
5559
+ let resultText = contentToString(result.content);
5560
+ if (ctx.maxResultChars && resultText.length > ctx.maxResultChars) {
5561
+ const truncated = resultText.slice(0, ctx.maxResultChars);
5562
+ const omitted = resultText.length - ctx.maxResultChars;
5563
+ resultText = truncated + `
5564
+
5565
+ [Result truncated: ${omitted} chars omitted]`;
5566
+ result = { ...result, content: resultText };
5743
5567
  }
5744
- return toolAc;
5745
- }
5746
- async executeTool(tracked) {
5747
- if (this.discarded || this.siblingAbortController.signal.aborted) {
5748
- tracked.status = "completed";
5749
- tracked.result = { content: "Error: Executor was discarded", isError: true };
5750
- tracked.events = [];
5751
- this.progressResolve?.();
5752
- return;
5568
+ toolSpan.setStatus(
5569
+ result.isError ? SpanStatusCode.ERROR : SpanStatusCode.OK,
5570
+ result.isError ? resultText : void 0
5571
+ );
5572
+ toolSpan.end();
5573
+ if (hooks.length > 0) {
5574
+ const postOutput = await runPostToolUseHooks(hooks, {
5575
+ event: "PostToolUse",
5576
+ toolName: tc.function.name,
5577
+ toolInput: currentArgs,
5578
+ toolUseId: tc.id,
5579
+ toolOutput: resultText,
5580
+ isError: result.isError ?? false,
5581
+ sessionId
5582
+ });
5583
+ if (postOutput.updatedOutput !== void 0) {
5584
+ result = { ...result, content: postOutput.updatedOutput };
5585
+ }
5586
+ if (postOutput.preventContinuation) {
5587
+ preventContinuation = true;
5588
+ }
5589
+ if (result.isError) {
5590
+ const failOutput = await runPostToolUseFailureHooks(hooks, {
5591
+ event: "PostToolUseFailure",
5592
+ toolName: tc.function.name,
5593
+ toolInput: currentArgs,
5594
+ toolUseId: tc.id,
5595
+ toolOutput: contentToString(result.content),
5596
+ errorMessage: contentToString(result.content),
5597
+ sessionId
5598
+ });
5599
+ if (failOutput.updatedOutput !== void 0) {
5600
+ result = { ...result, content: failOutput.updatedOutput };
5601
+ }
5602
+ if (failOutput.preventContinuation) {
5603
+ preventContinuation = true;
5604
+ }
5605
+ }
5753
5606
  }
5754
- tracked.status = "executing";
5755
- const toolAc = this.createToolAbortController();
5756
- tracked.abortController = toolAc;
5757
- tracked.promise = (async () => {
5607
+ return {
5608
+ toolCall: tc,
5609
+ parsedArgs: currentArgs,
5610
+ result,
5611
+ preventContinuation: preventContinuation || void 0,
5612
+ events
5613
+ };
5614
+ } catch (execErr) {
5615
+ const msg = execErr instanceof Error ? execErr.message : String(execErr);
5616
+ const errorResult = { content: `Error executing tool: ${msg}`, isError: true };
5617
+ if (hooks.length > 0) {
5758
5618
  try {
5759
- const { result, permissionDenied, preventContinuation, events } = await this.executeFn(
5760
- tracked.toolCall,
5761
- tracked.parsedArgs,
5762
- toolAc.signal
5763
- );
5764
- tracked.result = result;
5765
- tracked.permissionDenied = permissionDenied;
5766
- tracked.preventContinuation = preventContinuation;
5767
- tracked.events = events;
5768
- if (result.isError && tracked.toolCall.function.name === BASH_TOOL_NAME) {
5769
- this.hasErrored = true;
5770
- this.siblingAbortController.abort("sibling_error");
5619
+ const failOutput = await runPostToolUseFailureHooks(hooks, {
5620
+ event: "PostToolUseFailure",
5621
+ toolName: tc.function.name,
5622
+ toolInput: currentArgs,
5623
+ toolUseId: tc.id,
5624
+ toolOutput: errorResult.content,
5625
+ errorMessage: msg,
5626
+ sessionId
5627
+ });
5628
+ if (failOutput.updatedOutput !== void 0) {
5629
+ errorResult.content = failOutput.updatedOutput;
5771
5630
  }
5772
- } catch (err) {
5773
- tracked.result = {
5774
- content: `Error: ${err instanceof Error ? err.message : String(err)}`,
5775
- isError: true
5776
- };
5777
- tracked.events = [];
5778
- if (tracked.toolCall.function.name === BASH_TOOL_NAME) {
5779
- this.hasErrored = true;
5780
- this.siblingAbortController.abort("sibling_error");
5631
+ if (failOutput.preventContinuation) {
5632
+ preventContinuation = true;
5781
5633
  }
5634
+ } catch {
5782
5635
  }
5783
- tracked.status = "completed";
5784
- this.progressResolve?.();
5785
- })();
5786
- void tracked.promise.finally(() => void this.processQueue());
5636
+ }
5637
+ return {
5638
+ toolCall: tc,
5639
+ parsedArgs: currentArgs,
5640
+ result: errorResult,
5641
+ preventContinuation: preventContinuation || void 0,
5642
+ events
5643
+ };
5787
5644
  }
5788
- *getCompletedResults() {
5789
- if (this.discarded) return;
5790
- for (const tool of this.tools) {
5791
- if (tool.status === "yielded") continue;
5792
- if (tool.status === "completed" && tool.result) {
5793
- tool.status = "yielded";
5794
- yield {
5795
- toolCall: tool.toolCall,
5796
- parsedArgs: tool.parsedArgs,
5797
- result: tool.result,
5798
- permissionDenied: tool.permissionDenied,
5799
- preventContinuation: tool.preventContinuation,
5800
- events: tool.events
5801
- };
5802
- } else if (tool.status === "executing" && !tool.isConcurrencySafe) {
5803
- break;
5804
- }
5645
+ }
5646
+
5647
+ // src/pipeline/prepare-messages.ts
5648
+ async function prepareMessagesForApi(messages, config, state) {
5649
+ const events = [];
5650
+ let canonical = messages;
5651
+ let contentReplacementState = state.contentReplacementState;
5652
+ let budgetState = state.budgetState;
5653
+ let microcompactTokensFreed = state.microcompactTokensFreed;
5654
+ if (config.toolResultStorage?.enabled && config.fs) {
5655
+ const storageResult = await enforceToolResultStorageBudget(
5656
+ canonical,
5657
+ config.toolResultStorage,
5658
+ config.fs,
5659
+ config.sessionId,
5660
+ contentReplacementState
5661
+ );
5662
+ if (storageResult.tokensFreed > 0) {
5663
+ canonical = storageResult.messages;
5664
+ contentReplacementState = storageResult.state;
5665
+ microcompactTokensFreed += storageResult.tokensFreed;
5805
5666
  }
5806
5667
  }
5807
- async *getRemainingResults() {
5808
- if (this.discarded) {
5809
- for (const tool of this.tools) {
5810
- if (tool.status === "yielded") continue;
5811
- if (tool.status === "completed" && tool.result) {
5812
- tool.status = "yielded";
5813
- yield {
5814
- toolCall: tool.toolCall,
5815
- parsedArgs: tool.parsedArgs,
5816
- result: tool.result,
5817
- permissionDenied: tool.permissionDenied,
5818
- preventContinuation: tool.preventContinuation,
5819
- events: tool.events
5820
- };
5821
- continue;
5822
- }
5823
- tool.status = "yielded";
5824
- yield {
5825
- toolCall: tool.toolCall,
5826
- parsedArgs: tool.parsedArgs,
5827
- result: { content: "Error: Executor was discarded", isError: true },
5828
- events: []
5829
- };
5830
- }
5831
- return;
5668
+ if (config.microcompact?.enabled) {
5669
+ const mcResult = microcompactMessages(canonical, config.microcompact);
5670
+ if (mcResult.tokensFreed > 0) {
5671
+ canonical = mcResult.messages;
5672
+ microcompactTokensFreed += mcResult.tokensFreed;
5673
+ events.push({ type: "microcompact_complete", tokensFreed: mcResult.tokensFreed });
5832
5674
  }
5833
- while (this.hasUnfinished()) {
5834
- if (this.discarded) return;
5835
- await this.processQueue();
5836
- for (const result of this.getCompletedResults()) {
5837
- yield result;
5675
+ }
5676
+ let messagesForApi = canonical;
5677
+ if (config.toolResultBudget?.enabled) {
5678
+ const budgetResult = enforceToolResultBudget(
5679
+ [...canonical],
5680
+ config.toolResultBudget,
5681
+ budgetState
5682
+ );
5683
+ messagesForApi = budgetResult.messages;
5684
+ budgetState = budgetResult.state;
5685
+ microcompactTokensFreed += budgetResult.tokensFreed;
5686
+ for (const entry of budgetResult.truncatedEntries) {
5687
+ events.push({
5688
+ type: "tool_result_truncated",
5689
+ toolCallId: entry.toolCallId,
5690
+ originalChars: entry.originalChars,
5691
+ truncatedChars: entry.truncatedChars
5692
+ });
5693
+ }
5694
+ }
5695
+ messagesForApi = normalizeMessagesForAPI(messagesForApi);
5696
+ if (config.debug) {
5697
+ assertValidMessageSequence(messagesForApi);
5698
+ }
5699
+ return {
5700
+ messagesForApi,
5701
+ canonicalMessages: canonical,
5702
+ state: {
5703
+ contentReplacementState,
5704
+ budgetState,
5705
+ microcompactTokensFreed
5706
+ },
5707
+ events
5708
+ };
5709
+ }
5710
+
5711
+ // src/pipeline/auto-compact-step.ts
5712
+ async function tryAutoCompactStep(messages, config, provider, model, state, hooks, sessionId, storage) {
5713
+ if (!canAutoCompact(state.autoCompactTracking) || !shouldAutoCompact(
5714
+ messages,
5715
+ config,
5716
+ state.lastUsage,
5717
+ state.anchorMessageIndex,
5718
+ state.microcompactTokensFreed,
5719
+ state.querySource
5720
+ )) {
5721
+ return { compacted: false, events: [] };
5722
+ }
5723
+ const events = [];
5724
+ await runNotificationHooks(hooks, "PreCompact", {
5725
+ event: "PreCompact",
5726
+ sessionId
5727
+ });
5728
+ events.push({ type: "compact_start" });
5729
+ try {
5730
+ const compactedMessages = await compactConversation(
5731
+ provider,
5732
+ model,
5733
+ messages,
5734
+ storage,
5735
+ sessionId,
5736
+ {
5737
+ tailMessagesToKeep: config.tailMessagesToKeep,
5738
+ stripBinaryContent: true,
5739
+ signal: state.signal,
5740
+ recentlyReadFiles: state.recentlyReadFiles && state.recentlyReadFiles.size > 0 ? state.recentlyReadFiles : void 0
5838
5741
  }
5839
- if (this.hasExecuting() && !this.hasCompleted()) {
5840
- const executingPromises = this.tools.filter((t) => t.status === "executing" && t.promise).map((t) => t.promise);
5841
- const progressPromise = new Promise((resolve3) => {
5842
- this.progressResolve = resolve3;
5742
+ );
5743
+ recordAutoCompactSuccess(state.autoCompactTracking);
5744
+ events.push({ type: "compact_complete" });
5745
+ await runNotificationHooks(hooks, "PostCompact", {
5746
+ event: "PostCompact",
5747
+ sessionId
5748
+ });
5749
+ return { compacted: true, messages: compactedMessages, events };
5750
+ } catch (compactErr) {
5751
+ recordAutoCompactFailure(state.autoCompactTracking);
5752
+ const error = compactErr instanceof Error ? compactErr : new Error(`Compaction failed: ${String(compactErr)}`);
5753
+ await runNotificationHooks(hooks, "Error", {
5754
+ event: "Error",
5755
+ sessionId,
5756
+ error
5757
+ });
5758
+ events.push({ type: "auto_compact_failed", error });
5759
+ return { compacted: false, events };
5760
+ }
5761
+ }
5762
+
5763
+ // src/pipeline/execute-tools-step.ts
5764
+ var FILE_TOOLS = /* @__PURE__ */ new Set(["ReadFile", "WriteFile", "EditFile"]);
5765
+ async function processToolResult(execResult, spillFn, messages, recentlyReadFiles, storage, sessionId) {
5766
+ const events = [];
5767
+ for (const evt of execResult.events ?? []) {
5768
+ events.push(evt);
5769
+ }
5770
+ if (!execResult.permissionDenied) {
5771
+ events.push({
5772
+ type: "tool_result",
5773
+ toolUseId: execResult.toolCall.id,
5774
+ toolName: execResult.toolCall.function.name,
5775
+ result: execResult.result
5776
+ });
5777
+ const gitOps = execResult.result.metadata?.gitOperations;
5778
+ if (gitOps) {
5779
+ for (const op of gitOps) {
5780
+ events.push({
5781
+ type: "git_operation",
5782
+ operation: op.type,
5783
+ details: op.details
5843
5784
  });
5844
- if (executingPromises.length > 0) {
5845
- await Promise.race([...executingPromises, progressPromise]);
5846
- }
5847
5785
  }
5848
5786
  }
5849
- for (const result of this.getCompletedResults()) {
5850
- yield result;
5851
- }
5852
- }
5853
- hasUnfinished() {
5854
- return this.tools.some(
5855
- (t) => t.status === "queued" || t.status === "executing"
5856
- );
5857
- }
5858
- hasExecuting() {
5859
- return this.tools.some((t) => t.status === "executing");
5860
5787
  }
5861
- hasCompleted() {
5862
- return this.tools.some(
5863
- (t) => t.status === "completed" && t.result !== void 0
5788
+ let resultContent = execResult.result.content;
5789
+ let spillRecord;
5790
+ if (typeof resultContent === "string") {
5791
+ const spill = await spillFn(
5792
+ execResult.toolCall.id,
5793
+ execResult.toolCall.function.name,
5794
+ resultContent
5864
5795
  );
5796
+ if (spill.spilled) {
5797
+ resultContent = spill.content;
5798
+ spillRecord = { toolUseId: execResult.toolCall.id, replacement: spill.content };
5799
+ }
5865
5800
  }
5866
- };
5867
-
5868
- // src/retry/backoff.ts
5869
- var DEFAULT_BASE_DELAY_MS = 500;
5870
- function getRetryDelay(attempt, retryAfterHeader, maxDelayMs = 32e3, baseDelayMs = DEFAULT_BASE_DELAY_MS) {
5871
- if (retryAfterHeader) {
5872
- const seconds = parseInt(retryAfterHeader, 10);
5873
- if (!isNaN(seconds)) {
5874
- return seconds * 1e3;
5801
+ const toolResultMsg = {
5802
+ role: "tool",
5803
+ tool_call_id: execResult.toolCall.id,
5804
+ content: resultContent,
5805
+ ...execResult.result.isError ? { isError: true } : {}
5806
+ };
5807
+ messages.push(toolResultMsg);
5808
+ await storage.appendMessage(sessionId, toolResultMsg);
5809
+ let touchedPath;
5810
+ if (FILE_TOOLS.has(execResult.toolCall.function.name) && typeof execResult.parsedArgs.file_path === "string") {
5811
+ touchedPath = execResult.parsedArgs.file_path;
5812
+ if (execResult.toolCall.function.name === "ReadFile" && !execResult.result.isError) {
5813
+ const content = typeof execResult.result.content === "string" ? execResult.result.content : "";
5814
+ recentlyReadFiles.set(execResult.parsedArgs.file_path, content);
5875
5815
  }
5876
5816
  }
5877
- const baseDelay = Math.min(
5878
- baseDelayMs * Math.pow(2, attempt - 1),
5879
- maxDelayMs
5880
- );
5881
- const jitter = Math.random() * 0.25 * baseDelay;
5882
- return baseDelay + jitter;
5817
+ return {
5818
+ events,
5819
+ touchedPath,
5820
+ spillRecord,
5821
+ preventContinuation: !!execResult.preventContinuation
5822
+ };
5883
5823
  }
5884
- function sleep(ms, signal) {
5885
- return new Promise((resolve3, reject) => {
5886
- if (signal?.aborted) {
5887
- reject(new DOMException("Aborted", "AbortError"));
5888
- return;
5824
+ async function executeToolsStep(toolCalls, streamingExec, streamingResults, execCtx, registry, sessionId, messages, recentlyReadFiles, storage, spillFn) {
5825
+ const allEvents = [];
5826
+ const touchedFilePaths = [];
5827
+ const spilledRecords = [];
5828
+ let preventContinuation = false;
5829
+ const handleResult = async (execResult) => {
5830
+ const processed = await processToolResult(
5831
+ execResult,
5832
+ spillFn,
5833
+ messages,
5834
+ recentlyReadFiles,
5835
+ storage,
5836
+ sessionId
5837
+ );
5838
+ allEvents.push(...processed.events);
5839
+ if (processed.touchedPath) touchedFilePaths.push(processed.touchedPath);
5840
+ if (processed.spillRecord) spilledRecords.push(processed.spillRecord);
5841
+ if (processed.preventContinuation) preventContinuation = true;
5842
+ };
5843
+ if (streamingExec) {
5844
+ const allResults = [...streamingResults];
5845
+ for await (const result of streamingExec.getRemainingResults()) {
5846
+ allResults.push(result);
5889
5847
  }
5890
- const onAbort = () => {
5891
- clearTimeout(timer);
5892
- reject(new DOMException("Aborted", "AbortError"));
5848
+ for (const execResult of allResults) {
5849
+ await handleResult(execResult);
5850
+ }
5851
+ } else {
5852
+ const executor = async (tc, parsedArgs) => {
5853
+ const pipelineResult = await executeToolCall(tc, parsedArgs, execCtx);
5854
+ if (pipelineResult.preventContinuation) preventContinuation = true;
5855
+ return pipelineResult;
5893
5856
  };
5894
- const timer = setTimeout(() => {
5895
- signal?.removeEventListener("abort", onAbort);
5896
- resolve3();
5897
- }, ms);
5898
- signal?.addEventListener("abort", onAbort, { once: true });
5899
- });
5857
+ for await (const execResult of runToolsBatched(
5858
+ toolCalls,
5859
+ (name) => registry.get(name),
5860
+ executor
5861
+ )) {
5862
+ await handleResult(execResult);
5863
+ }
5864
+ }
5865
+ return { events: allEvents, touchedFilePaths, preventContinuation, spilledRecords };
5900
5866
  }
5901
5867
 
5902
- // src/retry/engine.ts
5903
- var FLOOR_OUTPUT_TOKENS = 3e3;
5904
- var CannotRetryError = class extends Error {
5905
- constructor(originalError, retryContext) {
5906
- super(originalError instanceof Error ? originalError.message : String(originalError));
5907
- this.originalError = originalError;
5908
- this.retryContext = retryContext;
5909
- this.name = "CannotRetryError";
5910
- }
5911
- originalError;
5912
- retryContext;
5913
- };
5914
- async function* withRetry(operation, options) {
5915
- const maxRetries = options.maxRetries ?? 10;
5916
- const baseDelayMs = options.baseDelayMs ?? 500;
5917
- const maxDelayMs = options.maxDelayMs ?? 32e3;
5918
- const maxConsecutiveOverloaded = options.maxConsecutiveOverloaded ?? 3;
5919
- const retryContext = {
5920
- attempt: 0,
5921
- model: options.model
5922
- };
5923
- let consecutiveOverloaded = 0;
5924
- let fallbackUsed = false;
5925
- let lastError;
5926
- let totalAttempts = 0;
5927
- for (let attempt = 1; attempt <= maxRetries + 1; attempt++) {
5928
- if (options.signal?.aborted) {
5929
- throw new DOMException("Aborted", "AbortError");
5930
- }
5931
- totalAttempts++;
5932
- retryContext.attempt = totalAttempts;
5933
- try {
5934
- const stream = operation(retryContext);
5935
- const iterator = toAsyncIterator(stream);
5936
- const first = await iterator.next();
5937
- if (first.done) {
5938
- throw new ChatStreamError("Provider returned empty stream", {
5939
- status: 502,
5940
- cause: new Error("empty_stream")
5941
- });
5868
+ // src/pipeline/initialize-session.ts
5869
+ async function initializeSession(params) {
5870
+ const {
5871
+ storage,
5872
+ sessionId,
5873
+ hooks,
5874
+ prompt,
5875
+ isResumeRun,
5876
+ checkpointManager,
5877
+ costTracker,
5878
+ toolResultStorage,
5879
+ fs
5880
+ } = params;
5881
+ let { messages, contentReplacementState, loaded, resumeRequested } = params;
5882
+ const events = [];
5883
+ if (!loaded) {
5884
+ if (resumeRequested) {
5885
+ const payload = await restoreSession(storage, sessionId);
5886
+ messages = payload.messages;
5887
+ if (checkpointManager && payload.checkpointSnapshots.length > 0) {
5888
+ checkpointManager.restoreStateFromEntries(payload.checkpointSnapshots);
5889
+ }
5890
+ if (costTracker && payload.costState) {
5891
+ costTracker.restore(payload.costState);
5892
+ }
5893
+ if (payload.contentReplacements.length > 0) {
5894
+ contentReplacementState = reconstructContentReplacementState(
5895
+ payload.contentReplacements,
5896
+ messages
5897
+ );
5898
+ messages = applyPersistedReplacements(
5899
+ messages,
5900
+ contentReplacementState
5901
+ );
5942
5902
  }
5943
- return prependChunk(first.value, iterator);
5944
- } catch (error) {
5945
- lastError = error;
5946
- const classified = classifyError(error);
5947
- if (classified.isContextOverflow && classified.contextOverflowData) {
5948
- const { inputTokens, contextLimit } = classified.contextOverflowData;
5949
- const safetyBuffer = 1e3;
5950
- const available = Math.max(0, contextLimit - inputTokens - safetyBuffer);
5951
- if (available < FLOOR_OUTPUT_TOKENS) {
5952
- throw new CannotRetryError(error, retryContext);
5953
- }
5954
- const minRequired = (options.thinkingBudget ?? 0) + 1;
5955
- retryContext.maxTokensOverride = Math.max(
5956
- FLOOR_OUTPUT_TOKENS,
5957
- available,
5958
- minRequired
5903
+ if (toolResultStorage?.enabled && fs) {
5904
+ const storageResult = await enforceToolResultStorageBudget(
5905
+ messages,
5906
+ toolResultStorage,
5907
+ fs,
5908
+ sessionId,
5909
+ contentReplacementState
5959
5910
  );
5960
- continue;
5911
+ messages = storageResult.messages;
5912
+ contentReplacementState = storageResult.state;
5961
5913
  }
5962
- if (classified.isOverloaded) {
5963
- consecutiveOverloaded++;
5964
- if (consecutiveOverloaded >= maxConsecutiveOverloaded && options.fallbackModel && !fallbackUsed) {
5965
- const previousModel = retryContext.model;
5966
- retryContext.model = options.fallbackModel;
5967
- consecutiveOverloaded = 0;
5968
- fallbackUsed = true;
5969
- yield {
5970
- type: "model_switch",
5971
- from: previousModel,
5972
- to: options.fallbackModel
5973
- };
5974
- yield {
5975
- type: "retry_attempt",
5976
- attempt: totalAttempts,
5977
- maxRetries,
5978
- delayMs: 0,
5979
- error: new Error(
5980
- `Model fallback: ${previousModel} \u2192 ${options.fallbackModel} after ${maxConsecutiveOverloaded} consecutive overloaded errors`
5981
- )
5982
- };
5983
- attempt = Math.max(attempt - Math.floor(maxRetries / 2), 1);
5984
- continue;
5914
+ for (const [filterName, count] of Object.entries(payload.recoveryRemovals)) {
5915
+ if (count > 0) {
5916
+ events.push({ type: "recovery_filtered", filterName, removedCount: count });
5985
5917
  }
5986
- } else {
5987
- consecutiveOverloaded = 0;
5988
- }
5989
- if (!isRetryable(classified, options)) {
5990
- throw new CannotRetryError(error, retryContext);
5991
5918
  }
5992
- if (totalAttempts > maxRetries) {
5993
- const exhaustedError = error instanceof Error ? error : new Error(String(error));
5994
- yield {
5995
- type: "retry_exhausted",
5996
- attempts: totalAttempts,
5997
- error: exhaustedError
5998
- };
5999
- throw new CannotRetryError(error, retryContext);
5919
+ if (payload.interruption.kind !== "none") {
5920
+ events.push({
5921
+ type: "interrupted_turn_detected",
5922
+ kind: payload.interruption.kind
5923
+ });
6000
5924
  }
6001
- const delayMs = getRetryDelay(
6002
- attempt,
6003
- classified.retryAfter,
6004
- maxDelayMs,
6005
- baseDelayMs
6006
- );
6007
- const retryError = error instanceof Error ? error : new Error(String(error));
6008
- options.onRetry?.(totalAttempts, retryError, delayMs);
6009
- yield {
6010
- type: "retry_attempt",
6011
- attempt: totalAttempts,
6012
- maxRetries,
6013
- delayMs,
6014
- error: retryError
6015
- };
6016
- await sleep(delayMs, options.signal);
5925
+ resumeRequested = false;
5926
+ events.push({ type: "session_resumed", sessionId, messageCount: messages.length });
5927
+ } else {
5928
+ messages = await storage.loadMessages(sessionId);
6017
5929
  }
5930
+ loaded = true;
6018
5931
  }
6019
- throw new CannotRetryError(lastError, retryContext);
6020
- }
6021
- function toAsyncIterator(iterable) {
6022
- return iterable[Symbol.asyncIterator]();
6023
- }
6024
- async function* prependChunk(first, rest) {
6025
- yield first;
6026
- let next = await rest.next();
6027
- while (!next.done) {
6028
- yield next.value;
6029
- next = await rest.next();
5932
+ const userMessage = { role: "user", content: prompt };
5933
+ messages.push(userMessage);
5934
+ await storage.appendMessage(sessionId, userMessage);
5935
+ const turnMessageId = generateUUID();
5936
+ if (checkpointManager) {
5937
+ await checkpointManager.makeSnapshot(turnMessageId, sessionId);
5938
+ await storage.appendCheckpointEntry(
5939
+ sessionId,
5940
+ turnMessageId,
5941
+ checkpointManager.getState().snapshots.at(-1),
5942
+ false
5943
+ );
5944
+ events.push({ type: "checkpoint_snapshot", messageId: turnMessageId });
6030
5945
  }
5946
+ await runNotificationHooks(hooks, "SessionStart", {
5947
+ event: "SessionStart",
5948
+ sessionId,
5949
+ prompt,
5950
+ isResume: isResumeRun
5951
+ });
5952
+ return {
5953
+ messages,
5954
+ contentReplacementState,
5955
+ events,
5956
+ loaded,
5957
+ resumeRequested,
5958
+ turnMessageId
5959
+ };
6031
5960
  }
6032
5961
 
6033
5962
  // src/pipeline/consume-stream.ts
@@ -6410,27 +6339,6 @@ async function tryReactiveCompactRecovery(input) {
6410
6339
  return { recovered: false, events };
6411
6340
  }
6412
6341
 
6413
- // src/providers/cache-safe-params.ts
6414
- var cacheSafeParamsMap = /* @__PURE__ */ new Map();
6415
- function saveCacheSafeParams(params, sessionId = "_default") {
6416
- if (params) {
6417
- cacheSafeParamsMap.set(sessionId, params);
6418
- } else {
6419
- cacheSafeParamsMap.delete(sessionId);
6420
- }
6421
- }
6422
- function getLastCacheSafeParams(sessionId = "_default") {
6423
- return cacheSafeParamsMap.get(sessionId) ?? null;
6424
- }
6425
- function createCacheSafeParams(opts) {
6426
- return {
6427
- systemPrompt: opts.systemPrompt,
6428
- model: opts.model,
6429
- tools: opts.tools,
6430
- thinking: opts.thinking
6431
- };
6432
- }
6433
-
6434
6342
  // src/pipeline/provider-round.ts
6435
6343
  var MAX_CONSECUTIVE_MALFORMED = 5;
6436
6344
  async function* executeProviderRound(p) {
@@ -6992,112 +6900,13 @@ async function postToolStep(params) {
6992
6900
  await runNotificationHooks(hooks, "TurnEnd", { event: "TurnEnd", sessionId });
6993
6901
  return {
6994
6902
  events,
6995
- preventContinuation,
6996
- shouldBreak: false,
6997
- shouldContinue: true,
6998
- hasAttemptedReactiveCompactReset: true,
6999
- systemPrompt,
7000
- toolDefs
7001
- };
7002
- }
7003
-
7004
- // src/memory/extraction.ts
7005
- var MEMORY_TYPES = /* @__PURE__ */ new Set([
7006
- "user",
7007
- "project",
7008
- "feedback",
7009
- "reference"
7010
- ]);
7011
- function summarizeRecentMessages(messages, maxMessages = 20) {
7012
- const recent = messages.slice(-maxMessages);
7013
- const lines = [];
7014
- for (const msg of recent) {
7015
- const role = msg.role.toUpperCase();
7016
- const text = typeof msg.content === "string" ? msg.content : "(non-text content)";
7017
- const truncated = text.length > 2e3 ? text.slice(0, 2e3) + "\u2026" : text;
7018
- lines.push(`[${role}] ${truncated}`);
7019
- }
7020
- return lines.join("\n\n");
7021
- }
7022
- async function extractMemories(llmProvider, model, messages, provider) {
7023
- const existingIndex = await provider.loadIndex();
7024
- const summary = summarizeRecentMessages(messages);
7025
- const prompt = buildExtractionPrompt(summary, existingIndex);
7026
- const extractionMessages = [
7027
- { role: "user", content: prompt }
7028
- ];
7029
- let responseText = "";
7030
- for await (const chunk of llmProvider.chat({
7031
- model,
7032
- messages: extractionMessages,
7033
- system: "You are a memory extraction assistant. Respond only with valid JSON.",
7034
- max_tokens: 4096
7035
- })) {
7036
- for (const choice of chunk.choices) {
7037
- if (choice.delta.content) {
7038
- responseText += choice.delta.content;
7039
- }
7040
- }
7041
- }
7042
- const parsed = parseExtractionResponse(responseText);
7043
- if (!parsed || parsed.memories.length === 0) {
7044
- return { created: [], updated: [], deleted: [] };
7045
- }
7046
- const result = {
7047
- created: [],
7048
- updated: [],
7049
- deleted: []
7050
- };
7051
- for (const action of parsed.memories) {
7052
- if (!MEMORY_TYPES.has(action.type)) continue;
7053
- if (action.action === "delete" && action.path) {
7054
- await provider.removeEntry(action.path);
7055
- result.deleted.push(action.path);
7056
- } else if (action.action === "update" && action.path) {
7057
- const entry = {
7058
- name: action.name,
7059
- description: action.description,
7060
- type: action.type,
7061
- content: action.content,
7062
- path: action.path,
7063
- updatedAt: (/* @__PURE__ */ new Date()).toISOString()
7064
- };
7065
- await provider.saveEntry(entry);
7066
- result.updated.push(entry);
7067
- } else if (action.action === "create") {
7068
- const entry = {
7069
- name: action.name,
7070
- description: action.description,
7071
- type: action.type,
7072
- content: action.content,
7073
- updatedAt: (/* @__PURE__ */ new Date()).toISOString()
7074
- };
7075
- await provider.saveEntry(entry);
7076
- result.created.push(entry);
7077
- }
7078
- }
7079
- return result;
7080
- }
7081
- function parseExtractionResponse(text) {
7082
- const trimmed = text.trim();
7083
- let jsonStr = trimmed;
7084
- if (jsonStr.startsWith("```")) {
7085
- const firstNewline = jsonStr.indexOf("\n");
7086
- jsonStr = jsonStr.slice(firstNewline + 1);
7087
- const lastFence = jsonStr.lastIndexOf("```");
7088
- if (lastFence !== -1) {
7089
- jsonStr = jsonStr.slice(0, lastFence);
7090
- }
7091
- }
7092
- try {
7093
- const parsed = JSON.parse(jsonStr);
7094
- if (parsed && typeof parsed === "object" && Array.isArray(parsed.memories)) {
7095
- return parsed;
7096
- }
7097
- return null;
7098
- } catch {
7099
- return null;
7100
- }
6903
+ preventContinuation,
6904
+ shouldBreak: false,
6905
+ shouldContinue: true,
6906
+ hasAttemptedReactiveCompactReset: true,
6907
+ systemPrompt,
6908
+ toolDefs
6909
+ };
7101
6910
  }
7102
6911
 
7103
6912
  // src/pipeline/finalize-turn.ts
@@ -7257,7 +7066,8 @@ var Thread = class {
7257
7066
  this.permissionContext = {
7258
7067
  mode: config.permissions.mode ?? "default",
7259
7068
  rules: [...config.permissions.rules ?? []],
7260
- workingDirectories: [...config.permissions.workingDirectories ?? []]
7069
+ workingDirectories: [...config.permissions.workingDirectories ?? []],
7070
+ dotDirNames: config.dotDirResolver?.config.names
7261
7071
  };
7262
7072
  this.permissionHandler = config.permissions.handler ?? null;
7263
7073
  if (config.permissions.denialTracking) {
@@ -7389,7 +7199,8 @@ var Thread = class {
7389
7199
  toolCtx.cwd = newCwd;
7390
7200
  },
7391
7201
  fileStateCache: this.fileStateCache ?? void 0,
7392
- notifyHook: this.hooks.length > 0 ? (event, input) => runNotificationHooks(this.hooks, event, input) : void 0
7202
+ notifyHook: this.hooks.length > 0 ? (event, input) => runNotificationHooks(this.hooks, event, input) : void 0,
7203
+ dotDirResolver: this.config.dotDirResolver
7393
7204
  };
7394
7205
  const turnUsage = {
7395
7206
  prompt_tokens: 0,
@@ -7812,7 +7623,7 @@ var Thread = class {
7812
7623
  if (uuids.length === 0) return;
7813
7624
  await this.storage.appendSnipBoundary(this.sessionId, uuids);
7814
7625
  const entries = await this.storage.loadAllEntries(this.sessionId);
7815
- const { applySnipRemovals: applySnipRemovals2 } = await import("./history-snip-64GYP4ZL.js");
7626
+ const { applySnipRemovals: applySnipRemovals2 } = await import("./history-snip-HAWNAYKY.js");
7816
7627
  let lastBoundaryIdx = -1;
7817
7628
  for (let i = entries.length - 1; i >= 0; i--) {
7818
7629
  if (entries[i].type === "compact-boundary") {
@@ -7869,16 +7680,16 @@ var Thread = class {
7869
7680
  };
7870
7681
 
7871
7682
  // src/skills/loader.ts
7872
- async function loadSkills(fs2, paths) {
7683
+ async function loadSkills(fs, paths) {
7873
7684
  const skills = [];
7874
7685
  for (const skillPath of paths) {
7875
7686
  try {
7876
- const stat2 = await fs2.stat(skillPath);
7877
- if (stat2.isFile) {
7878
- const skill = await loadSkillFile(fs2, skillPath);
7687
+ const stat = await fs.stat(skillPath);
7688
+ if (stat.isFile) {
7689
+ const skill = await loadSkillFile(fs, skillPath);
7879
7690
  if (skill) skills.push(skill);
7880
- } else if (stat2.isDirectory) {
7881
- const dirSkills = await loadSkillsFromDir(fs2, skillPath);
7691
+ } else if (stat.isDirectory) {
7692
+ const dirSkills = await loadSkillsFromDir(fs, skillPath);
7882
7693
  skills.push(...dirSkills);
7883
7694
  }
7884
7695
  } catch {
@@ -7886,9 +7697,9 @@ async function loadSkills(fs2, paths) {
7886
7697
  }
7887
7698
  return skills;
7888
7699
  }
7889
- async function loadSkillFile(fs2, filePath) {
7700
+ async function loadSkillFile(fs, filePath) {
7890
7701
  try {
7891
- const raw = await fs2.readFile(filePath);
7702
+ const raw = await fs.readFile(filePath);
7892
7703
  const { frontmatter, body } = parseFrontmatter(raw);
7893
7704
  const name = extractSkillName(filePath, body);
7894
7705
  const fmDescription = frontmatter.description;
@@ -7911,17 +7722,17 @@ async function loadSkillFile(fs2, filePath) {
7911
7722
  return null;
7912
7723
  }
7913
7724
  }
7914
- async function loadSkillsFromDir(fs2, dirPath) {
7725
+ async function loadSkillsFromDir(fs, dirPath) {
7915
7726
  const skills = [];
7916
7727
  try {
7917
- const entries = await fs2.readdir(dirPath);
7728
+ const entries = await fs.readdir(dirPath);
7918
7729
  for (const entry of entries) {
7919
7730
  if (entry.isFile && (entry.name === "SKILL.md" || entry.name.endsWith(".md"))) {
7920
- const skill = await loadSkillFile(fs2, entry.path);
7731
+ const skill = await loadSkillFile(fs, entry.path);
7921
7732
  if (skill) skills.push(skill);
7922
7733
  } else if (entry.isDirectory) {
7923
7734
  const skillMdPath = `${entry.path}/SKILL.md`;
7924
- const skill = await loadSkillFile(fs2, skillMdPath);
7735
+ const skill = await loadSkillFile(fs, skillMdPath);
7925
7736
  if (skill) skills.push(skill);
7926
7737
  }
7927
7738
  }
@@ -7950,16 +7761,41 @@ function extractDescription(content) {
7950
7761
  return void 0;
7951
7762
  }
7952
7763
 
7764
+ // src/retry/types.ts
7765
+ var DEFAULT_RETRY_CONFIG = {
7766
+ maxRetries: 10,
7767
+ baseDelayMs: 500,
7768
+ maxDelayMs: 32e3,
7769
+ retryableStatuses: [408, 409, 429, 500, 502, 503, 504, 529],
7770
+ maxConsecutiveOverloaded: 3
7771
+ };
7772
+
7953
7773
  // src/prompt/context.ts
7954
7774
  async function buildUserContext(opts) {
7955
- let skills = [];
7775
+ const ordered = [];
7776
+ if (opts.dotDirResolver) {
7777
+ if (opts.homeDir) {
7778
+ const homeDirs = opts.dotDirResolver.candidates(opts.homeDir).slice().reverse().map((d) => `${d}/skills`);
7779
+ const homeSkills = await loadSkills(opts.fs, homeDirs);
7780
+ ordered.push(...homeSkills);
7781
+ }
7782
+ if (opts.cwd) {
7783
+ const ancestors = walkAncestors2(opts.cwd);
7784
+ for (const ancestor of ancestors) {
7785
+ const ancestorDirs = opts.dotDirResolver.candidates(ancestor).slice().reverse().map((d) => `${d}/skills`);
7786
+ const ancestorSkills = await loadSkills(opts.fs, ancestorDirs);
7787
+ ordered.push(...ancestorSkills);
7788
+ }
7789
+ }
7790
+ }
7956
7791
  if (opts.skillsPaths && opts.skillsPaths.length > 0) {
7957
7792
  const loaded = await loadSkills(opts.fs, opts.skillsPaths);
7958
- skills.push(...loaded);
7793
+ ordered.push(...loaded);
7959
7794
  }
7960
7795
  if (opts.inlineSkills) {
7961
- skills.push(...opts.inlineSkills);
7796
+ ordered.push(...opts.inlineSkills);
7962
7797
  }
7798
+ const skills = dedupeSkillsByName(ordered);
7963
7799
  const date = (/* @__PURE__ */ new Date()).toLocaleDateString("en-US", {
7964
7800
  weekday: "long",
7965
7801
  year: "numeric",
@@ -7968,15 +7804,23 @@ async function buildUserContext(opts) {
7968
7804
  });
7969
7805
  return { skills, date };
7970
7806
  }
7971
-
7972
- // src/retry/types.ts
7973
- var DEFAULT_RETRY_CONFIG = {
7974
- maxRetries: 10,
7975
- baseDelayMs: 500,
7976
- maxDelayMs: 32e3,
7977
- retryableStatuses: [408, 409, 429, 500, 502, 503, 504, 529],
7978
- maxConsecutiveOverloaded: 3
7979
- };
7807
+ function dedupeSkillsByName(skills) {
7808
+ const byName = /* @__PURE__ */ new Map();
7809
+ for (const skill of skills) {
7810
+ byName.set(skill.name, skill);
7811
+ }
7812
+ return Array.from(byName.values());
7813
+ }
7814
+ function walkAncestors2(cwd) {
7815
+ const normalized = cwd.endsWith("/") && cwd.length > 1 ? cwd.slice(0, -1) : cwd;
7816
+ const parts = normalized.split("/");
7817
+ const dirs = [];
7818
+ for (let i = 1; i <= parts.length; i++) {
7819
+ const dir = parts.slice(0, i).join("/") || "/";
7820
+ dirs.push(dir);
7821
+ }
7822
+ return dirs;
7823
+ }
7980
7824
 
7981
7825
  // src/diagnostics.ts
7982
7826
  async function timedCheck(fn, timeoutMs) {
@@ -8010,9 +7854,9 @@ async function checkProviderHealth(provider, model, timeoutMs) {
8010
7854
  return { ok: false, latencyMs: 0, error: formatDiagError(err), model };
8011
7855
  }
8012
7856
  }
8013
- async function checkVirtualFs(fs2, timeoutMs) {
7857
+ async function checkVirtualFs(fs, timeoutMs) {
8014
7858
  try {
8015
- const { latencyMs } = await timedCheck(async () => fs2.exists("/"), timeoutMs);
7859
+ const { latencyMs } = await timedCheck(async () => fs.exists("/"), timeoutMs);
8016
7860
  return { ok: true, latencyMs };
8017
7861
  } catch (err) {
8018
7862
  return { ok: false, latencyMs: 0, error: formatDiagError(err) };
@@ -8034,9 +7878,9 @@ async function checkVirtualComputer(computer, timeoutMs) {
8034
7878
  }
8035
7879
  async function checkSandboxRuntime() {
8036
7880
  try {
8037
- const { SandboxManager: SandboxManager2 } = await import("@anthropic-ai/sandbox-runtime");
8038
- const supported = SandboxManager2.isSupportedPlatform();
8039
- const deps = SandboxManager2.checkDependencies();
7881
+ const { SandboxManager } = await import("@anthropic-ai/sandbox-runtime");
7882
+ const supported = SandboxManager.isSupportedPlatform();
7883
+ const deps = SandboxManager.checkDependencies();
8040
7884
  const hasErrors = deps.errors.length > 0;
8041
7885
  if (supported && !hasErrors) {
8042
7886
  return {
@@ -8101,11 +7945,16 @@ function resolveAgentConfig(input) {
8101
7945
  } else if (typeof input.retry === "object") {
8102
7946
  retryConfig = input.retry;
8103
7947
  }
7948
+ const dotDirs = input.dotDirs ?? DEFAULT_DOT_DIRS;
7949
+ const dotDirResolver = createDotDirResolver(dotDirs);
8104
7950
  let projectContextConfig;
8105
7951
  if (input.projectContext === true) {
8106
- projectContextConfig = { cwd: effectiveCwd };
7952
+ projectContextConfig = { cwd: effectiveCwd, dotDirs };
8107
7953
  } else if (typeof input.projectContext === "object") {
8108
- projectContextConfig = input.projectContext;
7954
+ projectContextConfig = {
7955
+ ...input.projectContext,
7956
+ dotDirs: input.projectContext.dotDirs ?? dotDirs
7957
+ };
8109
7958
  }
8110
7959
  const mcpServerConfigs = input.mcpServers && Object.keys(input.mcpServers).length > 0 ? input.mcpServers : void 0;
8111
7960
  const lspConfigs = input.lsp && Object.keys(input.lsp).length > 0 ? input.lsp : void 0;
@@ -8114,7 +7963,9 @@ function resolveAgentConfig(input) {
8114
7963
  retryConfig,
8115
7964
  projectContextConfig,
8116
7965
  mcpServerConfigs,
8117
- lspConfigs
7966
+ lspConfigs,
7967
+ dotDirs,
7968
+ dotDirResolver
8118
7969
  };
8119
7970
  }
8120
7971
 
@@ -8169,6 +8020,7 @@ var Agent = class {
8169
8020
  toolSearchEnabled;
8170
8021
  projectContextConfig;
8171
8022
  resolvedProjectContext = null;
8023
+ dotDirResolver;
8172
8024
  checkpointManager = null;
8173
8025
  promptCachingConfig;
8174
8026
  fileStateCacheConfig;
@@ -8197,13 +8049,15 @@ var Agent = class {
8197
8049
  retry: opts.options?.retry,
8198
8050
  projectContext: opts.options?.projectContext,
8199
8051
  mcpServers: opts.options?.mcpServers,
8200
- lsp: opts.options?.lsp
8052
+ lsp: opts.options?.lsp,
8053
+ dotDirs: opts.options?.dotDirs
8201
8054
  });
8202
- const resolvedSandbox = opts.sandbox ?? UnsandboxedLocal({ cwd: resolved.effectiveCwd });
8055
+ const resolvedSandbox = opts.sandbox;
8203
8056
  this.sandbox = resolvedSandbox;
8204
8057
  this.fs = resolvedSandbox.fs;
8205
8058
  this.computer = resolvedSandbox.computer;
8206
- this.sessionDir = opts.options?.sessionDir ?? ".noumen/sessions";
8059
+ this.dotDirResolver = resolved.dotDirResolver;
8060
+ this.sessionDir = opts.options?.sessionDir ?? `${resolved.dotDirResolver.writePath(resolved.effectiveCwd)}/sessions`;
8207
8061
  this.skills = opts.options?.skills ?? [];
8208
8062
  this.skillsPaths = opts.options?.skillsPaths ?? [];
8209
8063
  this.tools = opts.options?.tools ?? [];
@@ -8219,7 +8073,7 @@ var Agent = class {
8219
8073
  this.enableSubagents = opts.options?.enableSubagents ?? false;
8220
8074
  this.enableTasks = opts.options?.enableTasks ?? false;
8221
8075
  if (this.enableTasks) {
8222
- const tasksDir = opts.options?.tasksDir ?? ".noumen/tasks";
8076
+ const tasksDir = opts.options?.tasksDir ?? `${this.dotDirResolver.writePath(resolved.effectiveCwd)}/tasks`;
8223
8077
  this.taskStore = new TaskStore(this.fs, tasksDir);
8224
8078
  }
8225
8079
  this.enablePlanMode = opts.options?.enablePlanMode ?? false;
@@ -8250,7 +8104,10 @@ var Agent = class {
8250
8104
  this.projectContextConfig = resolved.projectContextConfig;
8251
8105
  this.promptCachingConfig = opts.options?.promptCaching;
8252
8106
  this.fileStateCacheConfig = opts.options?.fileStateCache;
8253
- this.toolResultStorageConfig = opts.options?.toolResultStorage;
8107
+ this.toolResultStorageConfig = opts.options?.toolResultStorage ? {
8108
+ ...opts.options.toolResultStorage,
8109
+ storageDir: opts.options.toolResultStorage.storageDir ?? `${this.dotDirResolver.writePath(resolved.effectiveCwd)}/tool-results`
8110
+ } : void 0;
8254
8111
  this.historySnipConfig = opts.options?.historySnip;
8255
8112
  this.outputFormat = opts.options?.outputFormat;
8256
8113
  this.structuredOutputMode = opts.options?.structuredOutputMode;
@@ -8263,7 +8120,10 @@ var Agent = class {
8263
8120
  if (opts.options?.checkpoint?.enabled) {
8264
8121
  this.checkpointManager = new FileCheckpointManager(
8265
8122
  this.fs,
8266
- opts.options.checkpoint
8123
+ {
8124
+ ...opts.options.checkpoint,
8125
+ backupDir: opts.options.checkpoint.backupDir ?? `${this.dotDirResolver.writePath(resolved.effectiveCwd)}/checkpoints`
8126
+ }
8267
8127
  );
8268
8128
  }
8269
8129
  }
@@ -8271,8 +8131,8 @@ var Agent = class {
8271
8131
  if (this.resolvedProvider) return this.resolvedProvider;
8272
8132
  if (!this.providerPromise) {
8273
8133
  this.providerPromise = (async () => {
8274
- const { resolveProvider: resolveProvider2 } = await import("./resolve-6KUZNEYW.js");
8275
- return resolveProvider2(this.providerInput, { model: this.model });
8134
+ const { resolveProvider } = await import("./resolve-AGQZFMKD.js");
8135
+ return resolveProvider(this.providerInput, { model: this.model });
8276
8136
  })();
8277
8137
  }
8278
8138
  this.resolvedProvider = await this.providerPromise;
@@ -8288,10 +8148,14 @@ var Agent = class {
8288
8148
  }
8289
8149
  async getSkills() {
8290
8150
  if (this.resolvedSkills) return this.resolvedSkills;
8151
+ const homeDir = process.env.HOME ?? process.env.USERPROFILE;
8291
8152
  const ctx = await buildUserContext({
8292
8153
  fs: this.fs,
8293
8154
  skillsPaths: this.skillsPaths,
8294
- inlineSkills: this.skills
8155
+ inlineSkills: this.skills,
8156
+ dotDirResolver: this.dotDirResolver,
8157
+ cwd: this.cwd,
8158
+ homeDir
8295
8159
  });
8296
8160
  this.resolvedSkills = ctx.skills;
8297
8161
  return this.resolvedSkills;
@@ -8350,7 +8214,8 @@ var Agent = class {
8350
8214
  historySnip: this.historySnipConfig,
8351
8215
  promptCachingEnabled: this.promptCachingConfig?.enabled ?? false,
8352
8216
  skipCacheWrite: true,
8353
- projectContext: this.resolvedProjectContext ?? void 0
8217
+ projectContext: this.resolvedProjectContext ?? void 0,
8218
+ dotDirResolver: this.dotDirResolver
8354
8219
  },
8355
8220
  { cwd: parentCwd }
8356
8221
  );
@@ -8412,6 +8277,7 @@ var Agent = class {
8412
8277
  promptCachingEnabled: this.promptCachingConfig?.enabled ?? false,
8413
8278
  mcpToolNames: this.mcpToolNames.size > 0 ? this.mcpToolNames : void 0,
8414
8279
  projectContext: this.resolvedProjectContext ?? void 0,
8280
+ dotDirResolver: this.dotDirResolver,
8415
8281
  outputFormat: this.outputFormat,
8416
8282
  structuredOutputMode: this.structuredOutputMode
8417
8283
  },
@@ -8626,7 +8492,7 @@ var Agent = class {
8626
8492
  if (this.mcpServerConfigs && !this.mcpManager) {
8627
8493
  tasks.push(
8628
8494
  (async () => {
8629
- const { McpClientManager: McpMgr } = await import("./client-CRRO2376.js");
8495
+ const { McpClientManager: McpMgr } = await import("./client-JJFLE6RT.js");
8630
8496
  this.mcpManager = new McpMgr(this.mcpServerConfigs, {
8631
8497
  tokenStorage: this.mcpTokenStorage,
8632
8498
  onAuthorizationUrl: this.mcpOnAuthorizationUrl
@@ -8636,7 +8502,7 @@ var Agent = class {
8636
8502
  this.mcpToolNames = new Set(this.mcpTools.map((t) => t.name));
8637
8503
  const needsAuth = this.mcpManager.getServersNeedingAuth();
8638
8504
  if (needsAuth.length > 0) {
8639
- const { createMcpAuthTool } = await import("./mcp-auth-AEI2R4ZC.js");
8505
+ const { createMcpAuthTool } = await import("./mcp-auth-NOIQPF7W.js");
8640
8506
  this.mcpAuthTools = needsAuth.map(
8641
8507
  (name) => createMcpAuthTool(name, this.mcpManager)
8642
8508
  );
@@ -8647,8 +8513,8 @@ var Agent = class {
8647
8513
  if (this.lspConfigs && !this.lspManager) {
8648
8514
  tasks.push(
8649
8515
  (async () => {
8650
- const { LspServerManager } = await import("./manager-DLXK63XC.js");
8651
- const { lspTool } = await import("./lsp-PS3BWIHC.js");
8516
+ const { LspServerManager } = await import("./manager-Z5EQ7YYV.js");
8517
+ const { lspTool } = await import("./lsp-3APWNKB2.js");
8652
8518
  const rootUri = `file://${this.cwd}`;
8653
8519
  this.lspManager = new LspServerManager(this.lspConfigs, rootUri);
8654
8520
  this.lspToolRef = lspTool;
@@ -8684,30 +8550,20 @@ var Agent = class {
8684
8550
  // ---------------------------------------------------------------------------
8685
8551
  // Sandbox index — local host file mapping sessionId → sandboxId so we can
8686
8552
  // reconnect to auto-created containers on resume without accessing the
8687
- // (potentially unreachable) sandbox filesystem.
8553
+ // (potentially unreachable) sandbox filesystem. The implementation is
8554
+ // quarantined in `session/sandbox-index.ts` and lazy-loaded so that
8555
+ // `node:fs/promises` + `node:path.resolve(cwd, ...)` don't appear in the
8556
+ // Agent's static import graph — bundler dependency tracers (Next.js NFT,
8557
+ // serverless-webpack, …) flag top-level dynamic path resolution as
8558
+ // "whole project was traced unintentionally".
8688
8559
  // ---------------------------------------------------------------------------
8689
- get sandboxIndexPath() {
8690
- return nodePath.resolve(this.cwd, this.sessionDir, ".sandbox-index.json");
8691
- }
8692
8560
  async loadSandboxId(sessionId) {
8693
- try {
8694
- const content = await nodeFs.readFile(this.sandboxIndexPath, "utf-8");
8695
- const index = JSON.parse(content);
8696
- return index[sessionId];
8697
- } catch {
8698
- return void 0;
8699
- }
8561
+ const { loadSandboxId } = await import("./sandbox-index-ODNREIFA.js");
8562
+ return loadSandboxId(this.cwd, this.sessionDir, sessionId);
8700
8563
  }
8701
8564
  async storeSandboxId(sessionId, sandboxId) {
8702
- let index = {};
8703
- try {
8704
- const content = await nodeFs.readFile(this.sandboxIndexPath, "utf-8");
8705
- index = JSON.parse(content);
8706
- } catch {
8707
- }
8708
- index[sessionId] = sandboxId;
8709
- await nodeFs.mkdir(nodePath.dirname(this.sandboxIndexPath), { recursive: true });
8710
- await nodeFs.writeFile(this.sandboxIndexPath, JSON.stringify(index, null, 2));
8565
+ const { storeSandboxId } = await import("./sandbox-index-ODNREIFA.js");
8566
+ return storeSandboxId(this.cwd, this.sessionDir, sessionId, sandboxId);
8711
8567
  }
8712
8568
  /**
8713
8569
  * Disconnect all MCP clients. Call when done with this Agent instance.
@@ -8741,648 +8597,7 @@ var Agent = class {
8741
8597
  }
8742
8598
  };
8743
8599
 
8744
- // src/presets.ts
8745
- function codingAgent(opts) {
8746
- const cwd = opts.cwd ?? process.cwd();
8747
- return new Agent({
8748
- provider: opts.provider,
8749
- sandbox: opts.sandbox ?? UnsandboxedLocal({ cwd }),
8750
- options: {
8751
- cwd,
8752
- model: opts.model,
8753
- systemPrompt: opts.systemPrompt,
8754
- permissions: { mode: "default" },
8755
- autoCompact: true,
8756
- enableSubagents: true,
8757
- enableTasks: true,
8758
- enablePlanMode: true,
8759
- projectContext: { cwd },
8760
- costTracking: { enabled: true },
8761
- retry: true,
8762
- hooks: opts.hooks,
8763
- mcpServers: opts.mcpServers,
8764
- autoTitle: opts.autoTitle
8765
- }
8766
- });
8767
- }
8768
- function planningAgent(opts) {
8769
- const cwd = opts.cwd ?? process.cwd();
8770
- return new Agent({
8771
- provider: opts.provider,
8772
- sandbox: opts.sandbox ?? UnsandboxedLocal({ cwd }),
8773
- options: {
8774
- cwd,
8775
- model: opts.model,
8776
- systemPrompt: opts.systemPrompt,
8777
- permissions: { mode: "plan" },
8778
- autoCompact: true,
8779
- enableSubagents: false,
8780
- enableTasks: false,
8781
- enablePlanMode: true,
8782
- projectContext: { cwd },
8783
- costTracking: { enabled: true },
8784
- retry: true,
8785
- hooks: opts.hooks,
8786
- mcpServers: opts.mcpServers,
8787
- autoTitle: opts.autoTitle
8788
- }
8789
- });
8790
- }
8791
- function reviewAgent(opts) {
8792
- const cwd = opts.cwd ?? process.cwd();
8793
- return new Agent({
8794
- provider: opts.provider,
8795
- sandbox: opts.sandbox ?? UnsandboxedLocal({ cwd }),
8796
- options: {
8797
- cwd,
8798
- model: opts.model,
8799
- systemPrompt: opts.systemPrompt,
8800
- permissions: { mode: "plan" },
8801
- autoCompact: true,
8802
- enableSubagents: false,
8803
- enableTasks: false,
8804
- enablePlanMode: true,
8805
- projectContext: { cwd },
8806
- costTracking: { enabled: true },
8807
- retry: true,
8808
- hooks: opts.hooks,
8809
- mcpServers: opts.mcpServers,
8810
- autoTitle: opts.autoTitle,
8811
- webSearch: {
8812
- search: async (query) => {
8813
- try {
8814
- const res = await fetch(`https://html.duckduckgo.com/html/?q=${encodeURIComponent(query)}`);
8815
- if (!res.ok) return [];
8816
- return [{ title: query, url: res.url, snippet: `Search results for: ${query}` }];
8817
- } catch {
8818
- return [];
8819
- }
8820
- }
8821
- }
8822
- }
8823
- });
8824
- }
8825
-
8826
- // src/swarm/mailbox.ts
8827
- var Mailbox = class {
8828
- messages = [];
8829
- listeners = /* @__PURE__ */ new Map();
8830
- /**
8831
- * Send a message from one member to another.
8832
- */
8833
- send(from, to, content) {
8834
- const msg = {
8835
- from,
8836
- to,
8837
- content,
8838
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
8839
- };
8840
- this.messages.push(msg);
8841
- const handlers = this.listeners.get(to);
8842
- if (handlers) {
8843
- for (const handler of handlers) {
8844
- handler(msg);
8845
- }
8846
- }
8847
- }
8848
- /**
8849
- * Broadcast a message to all members except the sender.
8850
- */
8851
- broadcast(from, content, memberNames) {
8852
- for (const name of memberNames) {
8853
- if (name !== from) {
8854
- this.send(from, name, content);
8855
- }
8856
- }
8857
- }
8858
- /**
8859
- * Get all messages sent to a specific member.
8860
- */
8861
- getMessagesFor(memberName) {
8862
- return this.messages.filter((m) => m.to === memberName);
8863
- }
8864
- /**
8865
- * Get all unread messages for a member since the last check.
8866
- */
8867
- getNewMessagesFor(memberName, since) {
8868
- return this.messages.filter(
8869
- (m) => m.to === memberName && m.timestamp > since
8870
- );
8871
- }
8872
- /**
8873
- * Register a listener for incoming messages to a member.
8874
- */
8875
- onMessage(memberName, handler) {
8876
- const existing = this.listeners.get(memberName) ?? [];
8877
- existing.push(handler);
8878
- this.listeners.set(memberName, existing);
8879
- return () => {
8880
- const handlers = this.listeners.get(memberName);
8881
- if (handlers) {
8882
- const idx = handlers.indexOf(handler);
8883
- if (idx !== -1) handlers.splice(idx, 1);
8884
- }
8885
- };
8886
- }
8887
- /**
8888
- * Get all messages in the mailbox.
8889
- */
8890
- getAllMessages() {
8891
- return [...this.messages];
8892
- }
8893
- };
8894
-
8895
- // src/swarm/manager.ts
8896
- var SwarmManager = class {
8897
- members = /* @__PURE__ */ new Map();
8898
- backend;
8899
- mailbox = new Mailbox();
8900
- config;
8901
- runningTasks = /* @__PURE__ */ new Map();
8902
- eventHandlers = [];
8903
- constructor(backend, config) {
8904
- this.backend = backend;
8905
- this.config = config ?? {};
8906
- }
8907
- /**
8908
- * Register a handler for swarm lifecycle events.
8909
- */
8910
- onEvent(handler) {
8911
- this.eventHandlers.push(handler);
8912
- return () => {
8913
- const idx = this.eventHandlers.indexOf(handler);
8914
- if (idx !== -1) this.eventHandlers.splice(idx, 1);
8915
- };
8916
- }
8917
- emit(event) {
8918
- for (const handler of this.eventHandlers) {
8919
- handler(event);
8920
- }
8921
- }
8922
- /**
8923
- * Spawn a new swarm member. Returns the member ID.
8924
- */
8925
- async spawn(config) {
8926
- const id = generateUUID();
8927
- const member = {
8928
- id,
8929
- name: config.name,
8930
- status: "pending"
8931
- };
8932
- this.members.set(id, member);
8933
- const maxConcurrent = this.config.maxConcurrent ?? 4;
8934
- const running = Array.from(this.members.values()).filter(
8935
- (m) => m.status === "running"
8936
- ).length;
8937
- if (running >= maxConcurrent) {
8938
- await this.waitForSlot(maxConcurrent);
8939
- }
8940
- member.status = "running";
8941
- this.emit({
8942
- type: "swarm_member_start",
8943
- memberId: id,
8944
- memberName: config.name
8945
- });
8946
- const task = this.runMember(config, member);
8947
- this.runningTasks.set(id, task);
8948
- return id;
8949
- }
8950
- async runMember(config, member) {
8951
- try {
8952
- const gen = this.backend.spawn(config, member);
8953
- let next = await gen.next();
8954
- while (!next.done) {
8955
- next = await gen.next();
8956
- }
8957
- member.result = next.value;
8958
- member.status = "completed";
8959
- this.emit({
8960
- type: "swarm_member_complete",
8961
- memberId: member.id,
8962
- memberName: member.name,
8963
- content: member.result
8964
- });
8965
- } catch (err) {
8966
- member.error = err instanceof Error ? err : new Error(String(err));
8967
- member.status = "failed";
8968
- this.emit({
8969
- type: "swarm_member_failed",
8970
- memberId: member.id,
8971
- memberName: member.name,
8972
- error: member.error
8973
- });
8974
- } finally {
8975
- this.runningTasks.delete(member.id);
8976
- }
8977
- }
8978
- /**
8979
- * Spawn multiple members concurrently. Returns their IDs.
8980
- */
8981
- async spawnAll(configs) {
8982
- const ids = [];
8983
- for (const config of configs) {
8984
- ids.push(await this.spawn(config));
8985
- }
8986
- return ids;
8987
- }
8988
- /**
8989
- * Send a message between swarm members.
8990
- */
8991
- sendMessage(from, to, content) {
8992
- this.mailbox.send(from, to, content);
8993
- this.emit({
8994
- type: "swarm_message",
8995
- memberId: to,
8996
- memberName: this.getMemberName(to),
8997
- content
8998
- });
8999
- }
9000
- /**
9001
- * Kill a running member.
9002
- */
9003
- async kill(memberId) {
9004
- const member = this.members.get(memberId);
9005
- if (!member || member.status !== "running") return;
9006
- await this.backend.kill(memberId);
9007
- member.status = "killed";
9008
- this.runningTasks.delete(memberId);
9009
- }
9010
- /**
9011
- * Wait for all running members to complete.
9012
- */
9013
- async waitForAll() {
9014
- while (this.runningTasks.size > 0) {
9015
- await Promise.race(this.runningTasks.values());
9016
- }
9017
- }
9018
- /**
9019
- * Get current swarm status.
9020
- */
9021
- getStatus() {
9022
- return {
9023
- members: Array.from(this.members.values()),
9024
- messages: this.mailbox.getAllMessages()
9025
- };
9026
- }
9027
- /**
9028
- * Get a specific member.
9029
- */
9030
- getMember(id) {
9031
- return this.members.get(id);
9032
- }
9033
- /**
9034
- * Get the mailbox for direct access.
9035
- */
9036
- getMailbox() {
9037
- return this.mailbox;
9038
- }
9039
- getMemberName(id) {
9040
- return this.members.get(id)?.name ?? id;
9041
- }
9042
- async waitForSlot(maxConcurrent) {
9043
- while (true) {
9044
- const running = Array.from(this.members.values()).filter(
9045
- (m) => m.status === "running"
9046
- ).length;
9047
- if (running < maxConcurrent) return;
9048
- if (this.runningTasks.size > 0) {
9049
- await Promise.race(this.runningTasks.values());
9050
- } else {
9051
- break;
9052
- }
9053
- }
9054
- }
9055
- };
9056
-
9057
- // src/swarm/backends/in-process.ts
9058
- var InProcessBackend = class {
9059
- threadConfig;
9060
- abortControllers = /* @__PURE__ */ new Map();
9061
- constructor(threadConfig) {
9062
- this.threadConfig = threadConfig;
9063
- }
9064
- async *spawn(config, member) {
9065
- const ac = new AbortController();
9066
- this.abortControllers.set(member.id, ac);
9067
- const childTools = config.allowedTools ? (this.threadConfig.tools ?? []).filter(
9068
- (t) => config.allowedTools.includes(t.name)
9069
- ) : this.threadConfig.tools;
9070
- const thread = new Thread(
9071
- {
9072
- ...this.threadConfig,
9073
- tools: childTools,
9074
- systemPrompt: config.systemPrompt,
9075
- model: config.model
9076
- },
9077
- { cwd: this.threadConfig.cwd }
9078
- );
9079
- member.sessionId = thread.sessionId;
9080
- let resultText = "";
9081
- for await (const event of thread.run(config.prompt, {
9082
- signal: ac.signal
9083
- })) {
9084
- yield event;
9085
- if (event.type === "message_complete" && event.message.content) {
9086
- resultText += event.message.content;
9087
- }
9088
- }
9089
- this.abortControllers.delete(member.id);
9090
- return resultText;
9091
- }
9092
- async kill(memberId) {
9093
- const ac = this.abortControllers.get(memberId);
9094
- if (ac) {
9095
- ac.abort();
9096
- this.abortControllers.delete(memberId);
9097
- }
9098
- }
9099
- };
9100
-
9101
- // src/permissions/updates.ts
9102
- function applyPermissionUpdate(ctx, update) {
9103
- switch (update.type) {
9104
- case "addRules":
9105
- ctx.rules.push(...update.rules);
9106
- break;
9107
- case "removeRules":
9108
- ctx.rules = ctx.rules.filter((r) => {
9109
- if (r.toolName !== update.toolName) return true;
9110
- if (update.behavior && r.behavior !== update.behavior) return true;
9111
- return false;
9112
- });
9113
- break;
9114
- case "setMode":
9115
- ctx.mode = update.mode;
9116
- break;
9117
- case "addDirectories":
9118
- for (const dir of update.directories) {
9119
- if (!ctx.workingDirectories.includes(dir)) {
9120
- ctx.workingDirectories.push(dir);
9121
- }
9122
- }
9123
- break;
9124
- case "removeDirectories":
9125
- ctx.workingDirectories = ctx.workingDirectories.filter(
9126
- (d) => !update.directories.includes(d)
9127
- );
9128
- break;
9129
- }
9130
- return ctx;
9131
- }
9132
- function applyPermissionUpdates(ctx, updates) {
9133
- for (const update of updates) {
9134
- applyPermissionUpdate(ctx, update);
9135
- }
9136
- return ctx;
9137
- }
9138
-
9139
- // src/tracing/otel.ts
9140
- var otelApi = null;
9141
- var otelLoadFailed = false;
9142
- async function loadOTelApi() {
9143
- if (otelApi) return otelApi;
9144
- if (otelLoadFailed) return null;
9145
- try {
9146
- otelApi = await import("@opentelemetry/api");
9147
- return otelApi;
9148
- } catch {
9149
- otelLoadFailed = true;
9150
- return null;
9151
- }
9152
- }
9153
- var OTelSpan = class {
9154
- name;
9155
- inner;
9156
- constructor(name, inner) {
9157
- this.name = name;
9158
- this.inner = inner;
9159
- }
9160
- setAttribute(key, value) {
9161
- this.inner.setAttribute(key, value);
9162
- }
9163
- addEvent(name, attributes) {
9164
- this.inner.addEvent(name, attributes);
9165
- }
9166
- setStatus(code, message) {
9167
- const otelCode = code === SpanStatusCode.ERROR ? 2 : 1;
9168
- this.inner.setStatus({ code: otelCode, message });
9169
- }
9170
- end() {
9171
- this.inner.end();
9172
- }
9173
- /** Access the underlying OTEL span for advanced use cases. */
9174
- getInnerSpan() {
9175
- return this.inner;
9176
- }
9177
- };
9178
- var OTelTracer = class _OTelTracer {
9179
- otelTracer;
9180
- api;
9181
- constructor(api, otelTracer) {
9182
- this.api = api;
9183
- this.otelTracer = otelTracer;
9184
- }
9185
- /**
9186
- * Create an `OTelTracer`. Falls back to `NoopTracer` if
9187
- * `@opentelemetry/api` is not available at runtime.
9188
- */
9189
- static async create(serviceName = "noumen", version) {
9190
- const api = await loadOTelApi();
9191
- if (!api) return new NoopTracer();
9192
- const tracer = api.trace.getTracer(serviceName, version);
9193
- return new _OTelTracer(api, tracer);
9194
- }
9195
- startSpan(name, options) {
9196
- const parentCtx = options?.parent instanceof OTelSpan ? this.api.trace.setSpan(this.api.context.active(), options.parent.getInnerSpan()) : this.api.context.active();
9197
- const otelSpan = this.otelTracer.startSpan(
9198
- name,
9199
- options?.attributes ? { attributes: options.attributes } : void 0,
9200
- parentCtx
9201
- );
9202
- return new OTelSpan(name, otelSpan);
9203
- }
9204
- };
9205
-
9206
- // src/memory/file-provider.ts
9207
- var INDEX_NAME = "MEMORY.md";
9208
- var DEFAULT_MAX_LINES = 200;
9209
- var DEFAULT_MAX_BYTES2 = 25e3;
9210
- var MEMORY_TYPES2 = /* @__PURE__ */ new Set([
9211
- "user",
9212
- "project",
9213
- "feedback",
9214
- "reference"
9215
- ]);
9216
- function parseFrontmatter2(raw) {
9217
- const trimmed = raw.trimStart();
9218
- if (!trimmed.startsWith("---")) {
9219
- return { rest: raw };
9220
- }
9221
- const endIdx = trimmed.indexOf("---", 3);
9222
- if (endIdx === -1) {
9223
- return { rest: raw };
9224
- }
9225
- const fmBlock = trimmed.slice(3, endIdx).trim();
9226
- const rest = trimmed.slice(endIdx + 3).trim();
9227
- let name;
9228
- let description;
9229
- let type;
9230
- for (const line of fmBlock.split("\n")) {
9231
- const colonIdx = line.indexOf(":");
9232
- if (colonIdx === -1) continue;
9233
- const key = line.slice(0, colonIdx).trim();
9234
- const value = line.slice(colonIdx + 1).trim();
9235
- if (key === "name") name = value;
9236
- else if (key === "description") description = value;
9237
- else if (key === "type" && MEMORY_TYPES2.has(value)) type = value;
9238
- }
9239
- return { name, description, type, rest };
9240
- }
9241
- function serializeEntry(entry) {
9242
- const lines = [
9243
- "---",
9244
- `name: ${entry.name}`,
9245
- `description: ${entry.description}`,
9246
- `type: ${entry.type}`,
9247
- "---",
9248
- "",
9249
- entry.content
9250
- ];
9251
- return lines.join("\n");
9252
- }
9253
- function slugify(name) {
9254
- return name.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "").slice(0, 60);
9255
- }
9256
- function truncateIndex(raw, maxLines = DEFAULT_MAX_LINES, maxBytes = DEFAULT_MAX_BYTES2) {
9257
- const trimmed = raw.trim();
9258
- const contentLines = trimmed.split("\n");
9259
- const lineCount = contentLines.length;
9260
- const byteCount = trimmed.length;
9261
- const wasLineTruncated = lineCount > maxLines;
9262
- const wasByteTruncated = byteCount > maxBytes;
9263
- if (!wasLineTruncated && !wasByteTruncated) {
9264
- return { content: trimmed, lineCount, byteCount, wasLineTruncated, wasByteTruncated };
9265
- }
9266
- let truncated = wasLineTruncated ? contentLines.slice(0, maxLines).join("\n") : trimmed;
9267
- if (truncated.length > maxBytes) {
9268
- const cutAt = truncated.lastIndexOf("\n", maxBytes);
9269
- truncated = truncated.slice(0, cutAt > 0 ? cutAt : maxBytes);
9270
- }
9271
- const reason = wasByteTruncated && !wasLineTruncated ? `${byteCount} bytes (limit: ${maxBytes})` : wasLineTruncated && !wasByteTruncated ? `${lineCount} lines (limit: ${maxLines})` : `${lineCount} lines and ${byteCount} bytes`;
9272
- return {
9273
- content: truncated + `
9274
-
9275
- > WARNING: ${INDEX_NAME} is ${reason}. Only part of it was loaded. Keep index entries to one line under ~200 chars; move detail into topic files.`,
9276
- lineCount,
9277
- byteCount,
9278
- wasLineTruncated,
9279
- wasByteTruncated
9280
- };
9281
- }
9282
- var FileMemoryProvider = class {
9283
- fs;
9284
- dir;
9285
- maxIndexLines;
9286
- constructor(fs2, memoryDir, maxIndexLines = DEFAULT_MAX_LINES) {
9287
- this.fs = fs2;
9288
- this.dir = memoryDir.endsWith("/") ? memoryDir : memoryDir + "/";
9289
- this.maxIndexLines = maxIndexLines;
9290
- }
9291
- indexPath() {
9292
- return this.dir + INDEX_NAME;
9293
- }
9294
- async ensureDir() {
9295
- const exists = await this.fs.exists(this.dir);
9296
- if (!exists) {
9297
- await this.fs.mkdir(this.dir, { recursive: true });
9298
- }
9299
- }
9300
- async loadIndex() {
9301
- try {
9302
- const raw = await this.fs.readFile(this.indexPath());
9303
- return truncateIndex(raw, this.maxIndexLines).content;
9304
- } catch {
9305
- return "";
9306
- }
9307
- }
9308
- async loadEntry(path2) {
9309
- const fullPath = path2.startsWith(this.dir) ? path2 : this.dir + path2;
9310
- try {
9311
- const raw = await this.fs.readFile(fullPath);
9312
- const fm = parseFrontmatter2(raw);
9313
- const stat2 = await this.fs.stat(fullPath).catch(() => null);
9314
- return {
9315
- name: fm.name ?? pathToName(path2),
9316
- description: fm.description ?? "",
9317
- type: fm.type ?? "project",
9318
- content: fm.rest,
9319
- path: path2.startsWith(this.dir) ? path2.slice(this.dir.length) : path2,
9320
- updatedAt: stat2?.modifiedAt?.toISOString()
9321
- };
9322
- } catch {
9323
- return null;
9324
- }
9325
- }
9326
- async saveEntry(entry) {
9327
- await this.ensureDir();
9328
- const relativePath = entry.path ?? slugify(entry.name) + ".md";
9329
- const fullPath = this.dir + relativePath;
9330
- const content = serializeEntry({ ...entry, path: relativePath });
9331
- await this.fs.writeFile(fullPath, content);
9332
- await this.rebuildIndex();
9333
- }
9334
- async removeEntry(path2) {
9335
- const fullPath = path2.startsWith(this.dir) ? path2 : this.dir + path2;
9336
- try {
9337
- await this.fs.deleteFile(fullPath);
9338
- } catch {
9339
- }
9340
- await this.rebuildIndex();
9341
- }
9342
- async listEntries() {
9343
- try {
9344
- const files = await this.fs.readdir(this.dir);
9345
- const entries = [];
9346
- for (const file of files) {
9347
- if (!file.isFile || !file.name.endsWith(".md") || file.name === INDEX_NAME) continue;
9348
- const entry = await this.loadEntry(file.name);
9349
- if (entry) entries.push(entry);
9350
- }
9351
- return entries;
9352
- } catch {
9353
- return [];
9354
- }
9355
- }
9356
- async search(query) {
9357
- const entries = await this.listEntries();
9358
- const lower = query.toLowerCase();
9359
- return entries.filter(
9360
- (e) => e.name.toLowerCase().includes(lower) || e.description.toLowerCase().includes(lower) || e.content.toLowerCase().includes(lower)
9361
- );
9362
- }
9363
- async rebuildIndex() {
9364
- const entries = await this.listEntries();
9365
- const lines = [];
9366
- for (const entry of entries) {
9367
- const relativePath = entry.path ?? slugify(entry.name) + ".md";
9368
- const desc = entry.description ? ` \u2014 ${entry.description}` : "";
9369
- lines.push(`- [${entry.name}](${relativePath})${desc}`);
9370
- }
9371
- await this.ensureDir();
9372
- await this.fs.writeFile(this.indexPath(), lines.join("\n") + "\n");
9373
- }
9374
- };
9375
- function pathToName(p) {
9376
- const base = p.split("/").pop() ?? p;
9377
- return base.replace(/\.md$/i, "").replace(/[_-]/g, " ");
9378
- }
9379
-
9380
8600
  export {
9381
- LocalFs,
9382
- LocalComputer,
9383
- SandboxedLocalComputer,
9384
- UnsandboxedLocal,
9385
- LocalSandbox,
9386
8601
  DEFAULT_AUTO_TITLE_MAX_INPUT_CHARS,
9387
8602
  DEFAULT_AUTO_TITLE_SYSTEM_PROMPT,
9388
8603
  extractTitleSeedText,
@@ -9444,6 +8659,10 @@ export {
9444
8659
  tokenCountWithEstimation,
9445
8660
  groupMessagesByTurn,
9446
8661
  truncateHeadForPTLRetry,
8662
+ registerContextWindows,
8663
+ getContextWindowForModel,
8664
+ getEffectiveContextWindow,
8665
+ getAutoCompactThreshold,
9447
8666
  classifyError,
9448
8667
  isRetryable,
9449
8668
  compactConversation,
@@ -9488,17 +8707,6 @@ export {
9488
8707
  Thread,
9489
8708
  loadSkills,
9490
8709
  DEFAULT_RETRY_CONFIG,
9491
- Agent,
9492
- codingAgent,
9493
- planningAgent,
9494
- reviewAgent,
9495
- Mailbox,
9496
- SwarmManager,
9497
- InProcessBackend,
9498
- applyPermissionUpdate,
9499
- applyPermissionUpdates,
9500
- OTelTracer,
9501
- truncateIndex,
9502
- FileMemoryProvider
8710
+ Agent
9503
8711
  };
9504
- //# sourceMappingURL=chunk-6MMYCGJQ.js.map
8712
+ //# sourceMappingURL=chunk-5HY4IYNT.js.map