noumen 0.4.0 → 0.6.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.
- package/README.md +63 -8
- package/dist/a2a/index.d.ts +6 -4
- package/dist/acp/index.d.ts +7 -5
- package/dist/{agent-1nFVUP9E.d.ts → agent-DWE4_P5X.d.ts} +169 -485
- package/dist/{cache-DsRqxx6v.d.ts → cache-BlBwXXPS.d.ts} +1 -1
- package/dist/{chunk-4HW6LN6D.js → chunk-6MMYCGJQ.js} +366 -1227
- package/dist/chunk-6MMYCGJQ.js.map +1 -0
- package/dist/{chunk-5JN4SPI7.js → chunk-7IQCQI2G.js} +1 -1
- package/dist/{chunk-L3L3FG5T.js → chunk-CCM2AXZG.js} +1 -1
- package/dist/{chunk-L3L3FG5T.js.map → chunk-CCM2AXZG.js.map} +1 -1
- package/dist/{chunk-CS6WNDCF.js → chunk-I3JTUFPK.js} +2 -2
- package/dist/chunk-I3JTUFPK.js.map +1 -0
- package/dist/chunk-I5SBSOS6.js +40 -0
- package/dist/chunk-I5SBSOS6.js.map +1 -0
- package/dist/{chunk-HL6JCRZJ.js → chunk-XZN4QZLK.js} +4 -4
- package/dist/{chunk-EKOGVTBT.js → chunk-ZXSDKBYB.js} +4 -2
- package/dist/chunk-ZXSDKBYB.js.map +1 -0
- package/dist/cli/index.js +11 -11
- package/dist/client/index.d.ts +1 -1
- package/dist/computer-BPdxSo6X.d.ts +88 -0
- package/dist/docker.d.ts +129 -0
- package/dist/docker.js +401 -0
- package/dist/docker.js.map +1 -0
- package/dist/e2b.d.ts +157 -0
- package/dist/e2b.js +202 -0
- package/dist/e2b.js.map +1 -0
- package/dist/freestyle.d.ts +174 -0
- package/dist/freestyle.js +240 -0
- package/dist/freestyle.js.map +1 -0
- package/dist/index.d.ts +19 -204
- package/dist/index.js +38 -48
- package/dist/lsp/index.d.ts +4 -3
- package/dist/mcp/index.d.ts +5 -4
- package/dist/mcp/index.js +2 -2
- package/dist/{provider-factory-KCLIF34X.js → provider-factory-TUHU3DIG.js} +2 -2
- package/dist/providers/anthropic.d.ts +3 -3
- package/dist/providers/anthropic.js +4 -3
- package/dist/providers/anthropic.js.map +1 -1
- package/dist/providers/bedrock.d.ts +3 -3
- package/dist/providers/bedrock.js +2 -2
- package/dist/providers/bedrock.js.map +1 -1
- package/dist/providers/gemini.d.ts +2 -2
- package/dist/providers/gemini.js +1 -1
- package/dist/providers/gemini.js.map +1 -1
- package/dist/providers/ollama.d.ts +1 -1
- package/dist/providers/ollama.js +2 -2
- package/dist/providers/openai.d.ts +2 -2
- package/dist/providers/openai.js +2 -2
- package/dist/providers/openrouter.d.ts +1 -1
- package/dist/providers/openrouter.js +2 -2
- package/dist/providers/vertex.d.ts +3 -3
- package/dist/providers/vertex.js +4 -3
- package/dist/providers/vertex.js.map +1 -1
- package/dist/{resolve-4JA2BBDA.js → resolve-6KUZNEYW.js} +2 -2
- package/dist/sandbox-9qeMTNrD.d.ts +126 -0
- package/dist/server/index.d.ts +6 -4
- package/dist/{server-CHMxuWKq.d.ts → server-BzNGKTP6.d.ts} +1 -1
- package/dist/sprites.d.ts +136 -0
- package/dist/sprites.js +334 -0
- package/dist/sprites.js.map +1 -0
- package/dist/ssh.d.ts +187 -0
- package/dist/ssh.js +392 -0
- package/dist/ssh.js.map +1 -0
- package/dist/{types-RPKUTu1k.d.ts → types-DhXwOQwD.d.ts} +3 -89
- package/dist/{types-LrU4LRmX.d.ts → types-kiGBF35b.d.ts} +40 -2
- package/package.json +25 -1
- package/dist/chunk-4HW6LN6D.js.map +0 -1
- package/dist/chunk-CS6WNDCF.js.map +0 -1
- package/dist/chunk-EKOGVTBT.js.map +0 -1
- /package/dist/{chunk-5JN4SPI7.js.map → chunk-7IQCQI2G.js.map} +0 -0
- /package/dist/{chunk-HL6JCRZJ.js.map → chunk-XZN4QZLK.js.map} +0 -0
- /package/dist/{provider-factory-KCLIF34X.js.map → provider-factory-TUHU3DIG.js.map} +0 -0
- /package/dist/{resolve-4JA2BBDA.js.map → resolve-6KUZNEYW.js.map} +0 -0
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
applySnipRemovals
|
|
3
3
|
} from "./chunk-42PHHZUA.js";
|
|
4
|
+
import {
|
|
5
|
+
ToolRegistry,
|
|
6
|
+
createToolSearchTool,
|
|
7
|
+
isPathInWorkingDirectories,
|
|
8
|
+
resolvePermission,
|
|
9
|
+
resolveToolFlag
|
|
10
|
+
} from "./chunk-XZN4QZLK.js";
|
|
4
11
|
import {
|
|
5
12
|
getAutoCompactThreshold,
|
|
6
13
|
getEffectiveContextWindow,
|
|
@@ -8,17 +15,10 @@ import {
|
|
|
8
15
|
} from "./chunk-HEQQQGK5.js";
|
|
9
16
|
import {
|
|
10
17
|
ChatStreamError
|
|
11
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-CCM2AXZG.js";
|
|
12
19
|
import {
|
|
13
20
|
generateUUID
|
|
14
21
|
} from "./chunk-3HEYCV26.js";
|
|
15
|
-
import {
|
|
16
|
-
ToolRegistry,
|
|
17
|
-
createToolSearchTool,
|
|
18
|
-
isPathInWorkingDirectories,
|
|
19
|
-
resolvePermission,
|
|
20
|
-
resolveToolFlag
|
|
21
|
-
} from "./chunk-HL6JCRZJ.js";
|
|
22
22
|
import {
|
|
23
23
|
contentToString,
|
|
24
24
|
hasImageContent,
|
|
@@ -178,7 +178,7 @@ var LocalComputer = class {
|
|
|
178
178
|
this.defaultTimeout = opts?.defaultTimeout ?? 3e4;
|
|
179
179
|
}
|
|
180
180
|
executeCommand(command, opts) {
|
|
181
|
-
return new Promise((
|
|
181
|
+
return new Promise((resolve3) => {
|
|
182
182
|
const child = execCb(
|
|
183
183
|
command,
|
|
184
184
|
{
|
|
@@ -189,7 +189,7 @@ var LocalComputer = class {
|
|
|
189
189
|
shell: process.env.SHELL || "/bin/sh"
|
|
190
190
|
},
|
|
191
191
|
(error, stdout, stderr) => {
|
|
192
|
-
|
|
192
|
+
resolve3({
|
|
193
193
|
exitCode: error && "code" in error ? typeof error.code === "number" ? error.code : 1 : child.exitCode ?? 0,
|
|
194
194
|
stdout: stdout ?? "",
|
|
195
195
|
stderr: stderr ?? ""
|
|
@@ -250,7 +250,7 @@ var SandboxedLocalComputer = class {
|
|
|
250
250
|
async executeCommand(command, opts) {
|
|
251
251
|
await this.ensureInitialized();
|
|
252
252
|
const wrappedCommand = await SandboxManager.wrapWithSandbox(command);
|
|
253
|
-
return new Promise((
|
|
253
|
+
return new Promise((resolve3) => {
|
|
254
254
|
const child = execCb2(
|
|
255
255
|
wrappedCommand,
|
|
256
256
|
{
|
|
@@ -266,7 +266,7 @@ var SandboxedLocalComputer = class {
|
|
|
266
266
|
stdout: stdout ?? "",
|
|
267
267
|
stderr: stderr ?? ""
|
|
268
268
|
};
|
|
269
|
-
Promise.resolve(SandboxManager.cleanupAfterCommand()).then(() =>
|
|
269
|
+
Promise.resolve(SandboxManager.cleanupAfterCommand()).then(() => resolve3(result)).catch(() => resolve3(result));
|
|
270
270
|
}
|
|
271
271
|
);
|
|
272
272
|
});
|
|
@@ -283,813 +283,7 @@ var SandboxedLocalComputer = class {
|
|
|
283
283
|
}
|
|
284
284
|
};
|
|
285
285
|
|
|
286
|
-
// src/virtual/sprites-fs.ts
|
|
287
|
-
import * as path2 from "path";
|
|
288
|
-
var SpritesFs = class {
|
|
289
|
-
token;
|
|
290
|
-
spriteName;
|
|
291
|
-
baseURL;
|
|
292
|
-
workingDir;
|
|
293
|
-
constructor(opts) {
|
|
294
|
-
this.token = opts.token;
|
|
295
|
-
this.spriteName = opts.spriteName;
|
|
296
|
-
this.baseURL = (opts.baseURL ?? "https://api.sprites.dev").replace(
|
|
297
|
-
/\/$/,
|
|
298
|
-
""
|
|
299
|
-
);
|
|
300
|
-
this.workingDir = opts.workingDir ?? "/home/sprite";
|
|
301
|
-
}
|
|
302
|
-
fsUrl(endpoint, params) {
|
|
303
|
-
const url = new URL(
|
|
304
|
-
`${this.baseURL}/v1/sprites/${this.spriteName}/fs${endpoint}`
|
|
305
|
-
);
|
|
306
|
-
if (params) {
|
|
307
|
-
for (const [k, v] of Object.entries(params)) {
|
|
308
|
-
url.searchParams.set(k, v);
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
return url.toString();
|
|
312
|
-
}
|
|
313
|
-
resolvePath(p) {
|
|
314
|
-
const normalizedBase = this.workingDir.endsWith("/") ? this.workingDir : this.workingDir + "/";
|
|
315
|
-
if (p.startsWith("/")) {
|
|
316
|
-
if (p !== this.workingDir && !p.startsWith(normalizedBase)) {
|
|
317
|
-
throw new Error(`Absolute path "${p}" is outside working directory "${this.workingDir}"`);
|
|
318
|
-
}
|
|
319
|
-
return p;
|
|
320
|
-
}
|
|
321
|
-
const resolved = path2.resolve(this.workingDir, p);
|
|
322
|
-
if (resolved !== this.workingDir && !resolved.startsWith(normalizedBase)) {
|
|
323
|
-
throw new Error(`Path "${p}" escapes working directory "${this.workingDir}"`);
|
|
324
|
-
}
|
|
325
|
-
return resolved;
|
|
326
|
-
}
|
|
327
|
-
headers() {
|
|
328
|
-
return {
|
|
329
|
-
Authorization: `Bearer ${this.token}`
|
|
330
|
-
};
|
|
331
|
-
}
|
|
332
|
-
async readFile(filePath, _opts) {
|
|
333
|
-
const url = this.fsUrl("/read", { path: this.resolvePath(filePath) });
|
|
334
|
-
const res = await fetch(url, { headers: this.headers() });
|
|
335
|
-
if (!res.ok) {
|
|
336
|
-
throw new Error(
|
|
337
|
-
`SpritesFs readFile failed (${res.status}): ${await res.text()}`
|
|
338
|
-
);
|
|
339
|
-
}
|
|
340
|
-
return res.text();
|
|
341
|
-
}
|
|
342
|
-
async readFileBytes(filePath, maxBytes) {
|
|
343
|
-
const url = this.fsUrl("/read", {
|
|
344
|
-
path: this.resolvePath(filePath),
|
|
345
|
-
binary: "true"
|
|
346
|
-
});
|
|
347
|
-
const res = await fetch(url, { headers: this.headers() });
|
|
348
|
-
if (!res.ok) {
|
|
349
|
-
throw new Error(
|
|
350
|
-
`SpritesFs readFileBytes failed (${res.status}): ${await res.text()}`
|
|
351
|
-
);
|
|
352
|
-
}
|
|
353
|
-
const arrayBuf = await res.arrayBuffer();
|
|
354
|
-
const buf = Buffer.from(arrayBuf);
|
|
355
|
-
if (maxBytes !== void 0 && buf.length > maxBytes) {
|
|
356
|
-
return buf.subarray(0, maxBytes);
|
|
357
|
-
}
|
|
358
|
-
return buf;
|
|
359
|
-
}
|
|
360
|
-
async writeFile(filePath, content) {
|
|
361
|
-
const url = this.fsUrl("/write");
|
|
362
|
-
const res = await fetch(url, {
|
|
363
|
-
method: "POST",
|
|
364
|
-
headers: {
|
|
365
|
-
...this.headers(),
|
|
366
|
-
"Content-Type": "application/json"
|
|
367
|
-
},
|
|
368
|
-
body: JSON.stringify({
|
|
369
|
-
path: this.resolvePath(filePath),
|
|
370
|
-
content
|
|
371
|
-
})
|
|
372
|
-
});
|
|
373
|
-
if (!res.ok) {
|
|
374
|
-
throw new Error(
|
|
375
|
-
`SpritesFs writeFile failed (${res.status}): ${await res.text()}`
|
|
376
|
-
);
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
/**
|
|
380
|
-
* @warning Not atomic. Concurrent appends may lose data due to
|
|
381
|
-
* read-then-write TOCTOU. The Sprites API does not expose an append primitive.
|
|
382
|
-
*/
|
|
383
|
-
async appendFile(filePath, content) {
|
|
384
|
-
let existing = "";
|
|
385
|
-
try {
|
|
386
|
-
existing = await this.readFile(filePath);
|
|
387
|
-
} catch {
|
|
388
|
-
}
|
|
389
|
-
await this.writeFile(filePath, existing + content);
|
|
390
|
-
}
|
|
391
|
-
async deleteFile(filePath, opts) {
|
|
392
|
-
const url = this.fsUrl("/remove");
|
|
393
|
-
const res = await fetch(url, {
|
|
394
|
-
method: "POST",
|
|
395
|
-
headers: {
|
|
396
|
-
...this.headers(),
|
|
397
|
-
"Content-Type": "application/json"
|
|
398
|
-
},
|
|
399
|
-
body: JSON.stringify({
|
|
400
|
-
path: this.resolvePath(filePath),
|
|
401
|
-
recursive: opts?.recursive ?? false
|
|
402
|
-
})
|
|
403
|
-
});
|
|
404
|
-
if (!res.ok) {
|
|
405
|
-
throw new Error(
|
|
406
|
-
`SpritesFs deleteFile failed (${res.status}): ${await res.text()}`
|
|
407
|
-
);
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
async mkdir(dirPath, opts) {
|
|
411
|
-
const url = this.fsUrl("/mkdir");
|
|
412
|
-
const res = await fetch(url, {
|
|
413
|
-
method: "POST",
|
|
414
|
-
headers: {
|
|
415
|
-
...this.headers(),
|
|
416
|
-
"Content-Type": "application/json"
|
|
417
|
-
},
|
|
418
|
-
body: JSON.stringify({
|
|
419
|
-
path: this.resolvePath(dirPath),
|
|
420
|
-
recursive: opts?.recursive ?? false
|
|
421
|
-
})
|
|
422
|
-
});
|
|
423
|
-
if (!res.ok) {
|
|
424
|
-
throw new Error(
|
|
425
|
-
`SpritesFs mkdir failed (${res.status}): ${await res.text()}`
|
|
426
|
-
);
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
async readdir(dirPath, _opts) {
|
|
430
|
-
const url = this.fsUrl("/readdir", { path: this.resolvePath(dirPath) });
|
|
431
|
-
const res = await fetch(url, { headers: this.headers() });
|
|
432
|
-
if (!res.ok) {
|
|
433
|
-
throw new Error(
|
|
434
|
-
`SpritesFs readdir failed (${res.status}): ${await res.text()}`
|
|
435
|
-
);
|
|
436
|
-
}
|
|
437
|
-
const data = await res.json();
|
|
438
|
-
return data.map((entry) => ({
|
|
439
|
-
name: entry.name,
|
|
440
|
-
path: entry.path,
|
|
441
|
-
isDirectory: entry.is_dir,
|
|
442
|
-
isFile: !entry.is_dir,
|
|
443
|
-
size: entry.size
|
|
444
|
-
}));
|
|
445
|
-
}
|
|
446
|
-
async exists(filePath) {
|
|
447
|
-
try {
|
|
448
|
-
await this.stat(filePath);
|
|
449
|
-
return true;
|
|
450
|
-
} catch {
|
|
451
|
-
return false;
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
async stat(filePath) {
|
|
455
|
-
const url = this.fsUrl("/stat", { path: this.resolvePath(filePath) });
|
|
456
|
-
const res = await fetch(url, { headers: this.headers() });
|
|
457
|
-
if (!res.ok) {
|
|
458
|
-
throw new Error(
|
|
459
|
-
`SpritesFs stat failed (${res.status}): ${await res.text()}`
|
|
460
|
-
);
|
|
461
|
-
}
|
|
462
|
-
const data = await res.json();
|
|
463
|
-
return {
|
|
464
|
-
size: data.size,
|
|
465
|
-
isDirectory: data.is_dir,
|
|
466
|
-
isFile: !data.is_dir,
|
|
467
|
-
createdAt: data.created_at ? new Date(data.created_at) : void 0,
|
|
468
|
-
modifiedAt: data.modified_at ? new Date(data.modified_at) : void 0
|
|
469
|
-
};
|
|
470
|
-
}
|
|
471
|
-
};
|
|
472
|
-
|
|
473
|
-
// src/virtual/sprites-computer.ts
|
|
474
|
-
var SpritesComputer = class {
|
|
475
|
-
token;
|
|
476
|
-
spriteName;
|
|
477
|
-
baseURL;
|
|
478
|
-
workingDir;
|
|
479
|
-
constructor(opts) {
|
|
480
|
-
this.token = opts.token;
|
|
481
|
-
this.spriteName = opts.spriteName;
|
|
482
|
-
this.baseURL = (opts.baseURL ?? "https://api.sprites.dev").replace(
|
|
483
|
-
/\/$/,
|
|
484
|
-
""
|
|
485
|
-
);
|
|
486
|
-
this.workingDir = opts.workingDir ?? "/home/sprite";
|
|
487
|
-
}
|
|
488
|
-
headers() {
|
|
489
|
-
return {
|
|
490
|
-
Authorization: `Bearer ${this.token}`,
|
|
491
|
-
"Content-Type": "application/json"
|
|
492
|
-
};
|
|
493
|
-
}
|
|
494
|
-
async executeCommand(command, opts) {
|
|
495
|
-
const cwd = opts?.cwd ?? this.workingDir;
|
|
496
|
-
const wrappedCommand = `cd ${this.shellEscape(cwd)} && ${command}`;
|
|
497
|
-
const url = `${this.baseURL}/v1/sprites/${this.spriteName}/exec`;
|
|
498
|
-
const res = await fetch(url, {
|
|
499
|
-
method: "POST",
|
|
500
|
-
headers: this.headers(),
|
|
501
|
-
body: JSON.stringify({
|
|
502
|
-
command: ["bash", "-c", wrappedCommand],
|
|
503
|
-
timeout: opts?.timeout ?? 3e4,
|
|
504
|
-
env: opts?.env
|
|
505
|
-
})
|
|
506
|
-
});
|
|
507
|
-
if (!res.ok) {
|
|
508
|
-
const text = await res.text();
|
|
509
|
-
return {
|
|
510
|
-
exitCode: 1,
|
|
511
|
-
stdout: "",
|
|
512
|
-
stderr: `Sprites exec failed (${res.status}): ${text}`
|
|
513
|
-
};
|
|
514
|
-
}
|
|
515
|
-
const data = await res.json();
|
|
516
|
-
return {
|
|
517
|
-
exitCode: data.exit_code,
|
|
518
|
-
stdout: data.stdout ?? "",
|
|
519
|
-
stderr: data.stderr ?? ""
|
|
520
|
-
};
|
|
521
|
-
}
|
|
522
|
-
shellEscape(s) {
|
|
523
|
-
return `'${s.replace(/'/g, "'\\''")}'`;
|
|
524
|
-
}
|
|
525
|
-
};
|
|
526
|
-
|
|
527
|
-
// src/virtual/docker-fs.ts
|
|
528
|
-
import * as path3 from "path";
|
|
529
|
-
var DockerFs = class {
|
|
530
|
-
container;
|
|
531
|
-
workingDir;
|
|
532
|
-
constructor(opts) {
|
|
533
|
-
this.container = opts.container;
|
|
534
|
-
this.workingDir = opts.workingDir ?? "/";
|
|
535
|
-
}
|
|
536
|
-
resolvePath(p) {
|
|
537
|
-
if (p.includes("\0")) {
|
|
538
|
-
throw new Error("Path contains null bytes");
|
|
539
|
-
}
|
|
540
|
-
const normalizedBase = this.workingDir.endsWith("/") ? this.workingDir : this.workingDir + "/";
|
|
541
|
-
if (p.startsWith("/")) {
|
|
542
|
-
const normalized = path3.normalize(p);
|
|
543
|
-
if (normalized !== this.workingDir && !normalized.startsWith(normalizedBase)) {
|
|
544
|
-
throw new Error(`Absolute path "${p}" is outside working directory "${this.workingDir}"`);
|
|
545
|
-
}
|
|
546
|
-
return normalized;
|
|
547
|
-
}
|
|
548
|
-
const resolved = path3.resolve(this.workingDir, p);
|
|
549
|
-
if (resolved !== this.workingDir && !resolved.startsWith(normalizedBase)) {
|
|
550
|
-
throw new Error(`Path "${p}" escapes working directory "${this.workingDir}"`);
|
|
551
|
-
}
|
|
552
|
-
return resolved;
|
|
553
|
-
}
|
|
554
|
-
async exec(cmd) {
|
|
555
|
-
const execInstance = await this.container.exec({
|
|
556
|
-
Cmd: cmd,
|
|
557
|
-
AttachStdout: true,
|
|
558
|
-
AttachStderr: true,
|
|
559
|
-
Tty: false
|
|
560
|
-
});
|
|
561
|
-
const stream = await execInstance.start({ hijack: true, stdin: false });
|
|
562
|
-
const result = await collectExecStream(stream);
|
|
563
|
-
const inspection = await execInstance.inspect();
|
|
564
|
-
return { exitCode: inspection.ExitCode, ...result };
|
|
565
|
-
}
|
|
566
|
-
async readFile(path6, _opts) {
|
|
567
|
-
const resolved = this.resolvePath(path6);
|
|
568
|
-
const { exitCode, stdout, stderr } = await this.exec([
|
|
569
|
-
"cat",
|
|
570
|
-
resolved
|
|
571
|
-
]);
|
|
572
|
-
if (exitCode !== 0) {
|
|
573
|
-
throw new Error(`DockerFs readFile failed: ${stderr.trim() || `exit code ${exitCode}`}`);
|
|
574
|
-
}
|
|
575
|
-
return stdout;
|
|
576
|
-
}
|
|
577
|
-
async readFileBytes(path6, maxBytes) {
|
|
578
|
-
const resolved = this.resolvePath(path6);
|
|
579
|
-
const cmd = maxBytes !== void 0 ? ["head", "-c", String(maxBytes), resolved] : ["cat", resolved];
|
|
580
|
-
const { exitCode, stdout, stderr } = await this.exec([
|
|
581
|
-
"bash",
|
|
582
|
-
"-c",
|
|
583
|
-
`${cmd.map(shellEscape).join(" ")} | base64`
|
|
584
|
-
]);
|
|
585
|
-
if (exitCode !== 0) {
|
|
586
|
-
throw new Error(`DockerFs readFileBytes failed: ${stderr.trim() || `exit code ${exitCode}`}`);
|
|
587
|
-
}
|
|
588
|
-
return Buffer.from(stdout.trim(), "base64");
|
|
589
|
-
}
|
|
590
|
-
async writeFile(path6, content) {
|
|
591
|
-
const resolved = this.resolvePath(path6);
|
|
592
|
-
const dir = resolved.substring(0, resolved.lastIndexOf("/"));
|
|
593
|
-
if (dir) {
|
|
594
|
-
await this.exec(["mkdir", "-p", dir]);
|
|
595
|
-
}
|
|
596
|
-
const encoded = Buffer.from(content, "utf-8").toString("base64");
|
|
597
|
-
const MAX_INLINE_LEN = 1e5;
|
|
598
|
-
if (encoded.length <= MAX_INLINE_LEN) {
|
|
599
|
-
const { exitCode, stderr } = await this.exec([
|
|
600
|
-
"bash",
|
|
601
|
-
"-c",
|
|
602
|
-
`echo ${shellEscape(encoded)} | base64 -d > ${shellEscape(resolved)}`
|
|
603
|
-
]);
|
|
604
|
-
if (exitCode !== 0) {
|
|
605
|
-
throw new Error(`DockerFs writeFile failed: ${stderr.trim()}`);
|
|
606
|
-
}
|
|
607
|
-
} else {
|
|
608
|
-
const execInstance = await this.container.exec({
|
|
609
|
-
Cmd: ["bash", "-c", `base64 -d > ${shellEscape(resolved)}`],
|
|
610
|
-
AttachStdout: true,
|
|
611
|
-
AttachStderr: true,
|
|
612
|
-
AttachStdin: true,
|
|
613
|
-
Tty: false
|
|
614
|
-
});
|
|
615
|
-
const stream = await execInstance.start({ hijack: true, stdin: true });
|
|
616
|
-
const writable = stream;
|
|
617
|
-
writable.write(encoded);
|
|
618
|
-
writable.end();
|
|
619
|
-
const result = await collectExecStream(stream);
|
|
620
|
-
const inspection = await execInstance.inspect();
|
|
621
|
-
if (inspection.ExitCode !== 0) {
|
|
622
|
-
throw new Error(`DockerFs writeFile failed: ${result.stderr.trim()}`);
|
|
623
|
-
}
|
|
624
|
-
}
|
|
625
|
-
}
|
|
626
|
-
async appendFile(path6, content) {
|
|
627
|
-
const resolved = this.resolvePath(path6);
|
|
628
|
-
const dir = resolved.substring(0, resolved.lastIndexOf("/"));
|
|
629
|
-
if (dir) {
|
|
630
|
-
await this.exec(["mkdir", "-p", dir]);
|
|
631
|
-
}
|
|
632
|
-
const encoded = Buffer.from(content, "utf-8").toString("base64");
|
|
633
|
-
const { exitCode, stderr } = await this.exec([
|
|
634
|
-
"bash",
|
|
635
|
-
"-c",
|
|
636
|
-
`echo ${shellEscape(encoded)} | base64 -d >> ${shellEscape(resolved)}`
|
|
637
|
-
]);
|
|
638
|
-
if (exitCode !== 0) {
|
|
639
|
-
throw new Error(`DockerFs appendFile failed: ${stderr.trim()}`);
|
|
640
|
-
}
|
|
641
|
-
}
|
|
642
|
-
async deleteFile(path6, opts) {
|
|
643
|
-
const resolved = this.resolvePath(path6);
|
|
644
|
-
const args = opts?.recursive ? ["rm", "-rf", resolved] : ["rm", "-f", resolved];
|
|
645
|
-
await this.exec(args);
|
|
646
|
-
}
|
|
647
|
-
async mkdir(path6, opts) {
|
|
648
|
-
const resolved = this.resolvePath(path6);
|
|
649
|
-
const args = opts?.recursive ? ["mkdir", "-p", resolved] : ["mkdir", resolved];
|
|
650
|
-
await this.exec(args);
|
|
651
|
-
}
|
|
652
|
-
async readdir(path6, _opts) {
|
|
653
|
-
const resolved = this.resolvePath(path6);
|
|
654
|
-
const { exitCode, stdout, stderr } = await this.exec([
|
|
655
|
-
"bash",
|
|
656
|
-
"-c",
|
|
657
|
-
`find ${shellEscape(resolved)} -maxdepth 1 -mindepth 1 -printf '%y %p\\n' 2>/dev/null`
|
|
658
|
-
]);
|
|
659
|
-
if (exitCode !== 0 && stderr.trim()) {
|
|
660
|
-
throw new Error(`DockerFs readdir failed: ${stderr.trim()}`);
|
|
661
|
-
}
|
|
662
|
-
const entries = [];
|
|
663
|
-
for (const line of stdout.trim().split("\n")) {
|
|
664
|
-
if (!line) continue;
|
|
665
|
-
const spaceIdx = line.indexOf(" ");
|
|
666
|
-
const type = line.substring(0, spaceIdx);
|
|
667
|
-
const fullPath = line.substring(spaceIdx + 1);
|
|
668
|
-
const name = fullPath.substring(fullPath.lastIndexOf("/") + 1);
|
|
669
|
-
entries.push({
|
|
670
|
-
name,
|
|
671
|
-
path: fullPath,
|
|
672
|
-
isDirectory: type === "d",
|
|
673
|
-
isFile: type === "f"
|
|
674
|
-
});
|
|
675
|
-
}
|
|
676
|
-
return entries;
|
|
677
|
-
}
|
|
678
|
-
async exists(path6) {
|
|
679
|
-
const resolved = this.resolvePath(path6);
|
|
680
|
-
const { exitCode } = await this.exec(["test", "-e", resolved]);
|
|
681
|
-
return exitCode === 0;
|
|
682
|
-
}
|
|
683
|
-
async stat(path6) {
|
|
684
|
-
const resolved = this.resolvePath(path6);
|
|
685
|
-
const { exitCode, stdout, stderr } = await this.exec([
|
|
686
|
-
"stat",
|
|
687
|
-
"-c",
|
|
688
|
-
"%s %F %W %Y",
|
|
689
|
-
resolved
|
|
690
|
-
]);
|
|
691
|
-
if (exitCode !== 0) {
|
|
692
|
-
throw new Error(`DockerFs stat failed: ${stderr.trim() || `exit code ${exitCode}`}`);
|
|
693
|
-
}
|
|
694
|
-
const parts = stdout.trim().split(" ");
|
|
695
|
-
const size = parseInt(parts[0], 10);
|
|
696
|
-
const fileType = parts[1];
|
|
697
|
-
const createdEpoch = parseInt(parts[2], 10);
|
|
698
|
-
const modifiedEpoch = parseInt(parts[3], 10);
|
|
699
|
-
return {
|
|
700
|
-
size,
|
|
701
|
-
isDirectory: fileType === "directory",
|
|
702
|
-
isFile: fileType.startsWith("regular"),
|
|
703
|
-
createdAt: createdEpoch > 0 ? new Date(createdEpoch * 1e3) : void 0,
|
|
704
|
-
modifiedAt: modifiedEpoch > 0 ? new Date(modifiedEpoch * 1e3) : void 0
|
|
705
|
-
};
|
|
706
|
-
}
|
|
707
|
-
};
|
|
708
|
-
function shellEscape(s) {
|
|
709
|
-
return `'${s.replace(/'/g, "'\\''")}'`;
|
|
710
|
-
}
|
|
711
|
-
function collectExecStream(stream) {
|
|
712
|
-
return new Promise((resolve7, reject) => {
|
|
713
|
-
const stdoutBufs = [];
|
|
714
|
-
const stderrBufs = [];
|
|
715
|
-
let pending = Buffer.alloc(0);
|
|
716
|
-
stream.on("data", (chunk) => {
|
|
717
|
-
let buf = pending.length > 0 ? Buffer.concat([pending, chunk]) : chunk;
|
|
718
|
-
let offset = 0;
|
|
719
|
-
while (offset + 8 <= buf.length) {
|
|
720
|
-
const payloadLen = buf.readUInt32BE(offset + 4);
|
|
721
|
-
if (offset + 8 + payloadLen > buf.length) break;
|
|
722
|
-
const streamType = buf[offset];
|
|
723
|
-
const payload = buf.subarray(offset + 8, offset + 8 + payloadLen);
|
|
724
|
-
if (streamType === 2) {
|
|
725
|
-
stderrBufs.push(payload);
|
|
726
|
-
} else {
|
|
727
|
-
stdoutBufs.push(payload);
|
|
728
|
-
}
|
|
729
|
-
offset += 8 + payloadLen;
|
|
730
|
-
}
|
|
731
|
-
pending = offset < buf.length ? buf.subarray(offset) : Buffer.alloc(0);
|
|
732
|
-
});
|
|
733
|
-
stream.on("end", () => {
|
|
734
|
-
resolve7({
|
|
735
|
-
stdout: Buffer.concat(stdoutBufs).toString("utf-8"),
|
|
736
|
-
stderr: Buffer.concat(stderrBufs).toString("utf-8")
|
|
737
|
-
});
|
|
738
|
-
});
|
|
739
|
-
stream.on("error", (err) => reject(err));
|
|
740
|
-
});
|
|
741
|
-
}
|
|
742
|
-
|
|
743
|
-
// src/virtual/docker-computer.ts
|
|
744
|
-
var DockerComputer = class {
|
|
745
|
-
container;
|
|
746
|
-
defaultCwd;
|
|
747
|
-
defaultTimeout;
|
|
748
|
-
constructor(opts) {
|
|
749
|
-
this.container = opts.container;
|
|
750
|
-
this.defaultCwd = opts.defaultCwd ?? "/";
|
|
751
|
-
this.defaultTimeout = opts.defaultTimeout ?? 3e4;
|
|
752
|
-
}
|
|
753
|
-
async executeCommand(command, opts) {
|
|
754
|
-
const cwd = opts?.cwd ?? this.defaultCwd;
|
|
755
|
-
const timeout = opts?.timeout ?? this.defaultTimeout;
|
|
756
|
-
const execOpts = {
|
|
757
|
-
Cmd: ["bash", "-c", `cd ${shellEscape2(cwd)} && ${command}`],
|
|
758
|
-
AttachStdout: true,
|
|
759
|
-
AttachStderr: true,
|
|
760
|
-
Tty: false
|
|
761
|
-
};
|
|
762
|
-
if (opts?.env) {
|
|
763
|
-
execOpts.Env = Object.entries(opts.env).map(
|
|
764
|
-
([k, v]) => `${k}=${v}`
|
|
765
|
-
);
|
|
766
|
-
}
|
|
767
|
-
const exec = await this.container.exec(execOpts);
|
|
768
|
-
const stream = await exec.start({ hijack: true, stdin: false });
|
|
769
|
-
const { stdout, stderr } = await collectStream(stream, timeout);
|
|
770
|
-
const inspection = await exec.inspect();
|
|
771
|
-
return {
|
|
772
|
-
exitCode: inspection.ExitCode,
|
|
773
|
-
stdout,
|
|
774
|
-
stderr
|
|
775
|
-
};
|
|
776
|
-
}
|
|
777
|
-
};
|
|
778
|
-
function shellEscape2(s) {
|
|
779
|
-
return `'${s.replace(/'/g, "'\\''")}'`;
|
|
780
|
-
}
|
|
781
|
-
function collectStream(stream, timeout) {
|
|
782
|
-
return new Promise((resolve7, reject) => {
|
|
783
|
-
const stdoutBufs = [];
|
|
784
|
-
const stderrBufs = [];
|
|
785
|
-
let pending = Buffer.alloc(0);
|
|
786
|
-
const timer = setTimeout(() => {
|
|
787
|
-
stream.destroy?.();
|
|
788
|
-
resolve7({
|
|
789
|
-
stdout: Buffer.concat(stdoutBufs).toString("utf-8"),
|
|
790
|
-
stderr: Buffer.concat(stderrBufs).toString("utf-8") + "\n[timeout after " + timeout + "ms]"
|
|
791
|
-
});
|
|
792
|
-
}, timeout);
|
|
793
|
-
stream.on("data", (chunk) => {
|
|
794
|
-
let buf = pending.length > 0 ? Buffer.concat([pending, chunk]) : chunk;
|
|
795
|
-
let offset = 0;
|
|
796
|
-
while (offset + 8 <= buf.length) {
|
|
797
|
-
const payloadLen = buf.readUInt32BE(offset + 4);
|
|
798
|
-
if (offset + 8 + payloadLen > buf.length) break;
|
|
799
|
-
const streamType = buf[offset];
|
|
800
|
-
const payload = buf.subarray(offset + 8, offset + 8 + payloadLen);
|
|
801
|
-
if (streamType === 2) {
|
|
802
|
-
stderrBufs.push(payload);
|
|
803
|
-
} else {
|
|
804
|
-
stdoutBufs.push(payload);
|
|
805
|
-
}
|
|
806
|
-
offset += 8 + payloadLen;
|
|
807
|
-
}
|
|
808
|
-
pending = offset < buf.length ? buf.subarray(offset) : Buffer.alloc(0);
|
|
809
|
-
});
|
|
810
|
-
stream.on("end", () => {
|
|
811
|
-
clearTimeout(timer);
|
|
812
|
-
resolve7({
|
|
813
|
-
stdout: Buffer.concat(stdoutBufs).toString("utf-8"),
|
|
814
|
-
stderr: Buffer.concat(stderrBufs).toString("utf-8")
|
|
815
|
-
});
|
|
816
|
-
});
|
|
817
|
-
stream.on("error", (err) => {
|
|
818
|
-
clearTimeout(timer);
|
|
819
|
-
reject(err);
|
|
820
|
-
});
|
|
821
|
-
});
|
|
822
|
-
}
|
|
823
|
-
|
|
824
|
-
// src/virtual/e2b-fs.ts
|
|
825
|
-
import * as path4 from "path";
|
|
826
|
-
var E2BFs = class {
|
|
827
|
-
sandbox;
|
|
828
|
-
workingDir;
|
|
829
|
-
constructor(opts) {
|
|
830
|
-
this.sandbox = opts.sandbox;
|
|
831
|
-
this.workingDir = opts.workingDir;
|
|
832
|
-
}
|
|
833
|
-
resolvePath(p) {
|
|
834
|
-
if (!this.workingDir) return p;
|
|
835
|
-
const normalizedBase = this.workingDir.endsWith("/") ? this.workingDir : this.workingDir + "/";
|
|
836
|
-
if (p.startsWith("/")) {
|
|
837
|
-
if (p !== this.workingDir && !p.startsWith(normalizedBase)) {
|
|
838
|
-
throw new Error(`Absolute path "${p}" is outside working directory "${this.workingDir}"`);
|
|
839
|
-
}
|
|
840
|
-
return p;
|
|
841
|
-
}
|
|
842
|
-
const resolved = path4.resolve(this.workingDir, p);
|
|
843
|
-
if (resolved !== this.workingDir && !resolved.startsWith(normalizedBase)) {
|
|
844
|
-
throw new Error(`Path "${p}" escapes working directory "${this.workingDir}"`);
|
|
845
|
-
}
|
|
846
|
-
return resolved;
|
|
847
|
-
}
|
|
848
|
-
async readFile(path6, _opts) {
|
|
849
|
-
return this.sandbox.files.read(this.resolvePath(path6), {
|
|
850
|
-
format: "text"
|
|
851
|
-
});
|
|
852
|
-
}
|
|
853
|
-
async readFileBytes(path6, maxBytes) {
|
|
854
|
-
const data = await this.sandbox.files.read(this.resolvePath(path6), {
|
|
855
|
-
format: "bytes"
|
|
856
|
-
});
|
|
857
|
-
const buf = Buffer.from(data);
|
|
858
|
-
if (maxBytes !== void 0 && buf.length > maxBytes) {
|
|
859
|
-
return buf.subarray(0, maxBytes);
|
|
860
|
-
}
|
|
861
|
-
return buf;
|
|
862
|
-
}
|
|
863
|
-
async writeFile(path6, content) {
|
|
864
|
-
await this.sandbox.files.write(this.resolvePath(path6), content);
|
|
865
|
-
}
|
|
866
|
-
/**
|
|
867
|
-
* @warning Not atomic. Concurrent appends may lose data due to
|
|
868
|
-
* read-then-write TOCTOU. The E2B SDK does not expose an append primitive.
|
|
869
|
-
*/
|
|
870
|
-
async appendFile(path6, content) {
|
|
871
|
-
let existing = "";
|
|
872
|
-
try {
|
|
873
|
-
existing = await this.readFile(path6);
|
|
874
|
-
} catch {
|
|
875
|
-
}
|
|
876
|
-
await this.writeFile(path6, existing + content);
|
|
877
|
-
}
|
|
878
|
-
async deleteFile(path6, _opts) {
|
|
879
|
-
await this.sandbox.files.remove(this.resolvePath(path6));
|
|
880
|
-
}
|
|
881
|
-
async mkdir(path6, _opts) {
|
|
882
|
-
await this.sandbox.files.makeDir(this.resolvePath(path6));
|
|
883
|
-
}
|
|
884
|
-
async readdir(path6, _opts) {
|
|
885
|
-
const entries = await this.sandbox.files.list(this.resolvePath(path6));
|
|
886
|
-
return entries.map((entry) => ({
|
|
887
|
-
name: entry.name,
|
|
888
|
-
path: entry.path,
|
|
889
|
-
isDirectory: entry.type === "dir",
|
|
890
|
-
isFile: entry.type === "file",
|
|
891
|
-
size: entry.size
|
|
892
|
-
}));
|
|
893
|
-
}
|
|
894
|
-
async exists(path6) {
|
|
895
|
-
return this.sandbox.files.exists(this.resolvePath(path6));
|
|
896
|
-
}
|
|
897
|
-
async stat(path6) {
|
|
898
|
-
const info = await this.sandbox.files.getInfo(this.resolvePath(path6));
|
|
899
|
-
return {
|
|
900
|
-
size: info.size ?? 0,
|
|
901
|
-
isDirectory: info.type === "dir",
|
|
902
|
-
isFile: info.type === "file",
|
|
903
|
-
modifiedAt: info.modifiedTime
|
|
904
|
-
};
|
|
905
|
-
}
|
|
906
|
-
};
|
|
907
|
-
|
|
908
|
-
// src/virtual/e2b-computer.ts
|
|
909
|
-
var E2BComputer = class {
|
|
910
|
-
sandbox;
|
|
911
|
-
defaultCwd;
|
|
912
|
-
defaultTimeout;
|
|
913
|
-
constructor(opts) {
|
|
914
|
-
this.sandbox = opts.sandbox;
|
|
915
|
-
this.defaultCwd = opts.defaultCwd;
|
|
916
|
-
this.defaultTimeout = opts.defaultTimeout ?? 3e4;
|
|
917
|
-
}
|
|
918
|
-
async executeCommand(command, opts) {
|
|
919
|
-
const result = await this.sandbox.commands.run(command, {
|
|
920
|
-
cwd: opts?.cwd ?? this.defaultCwd,
|
|
921
|
-
timeout: opts?.timeout ?? this.defaultTimeout,
|
|
922
|
-
envs: opts?.env
|
|
923
|
-
});
|
|
924
|
-
return {
|
|
925
|
-
exitCode: result.exitCode,
|
|
926
|
-
stdout: result.stdout ?? "",
|
|
927
|
-
stderr: result.stderr ?? ""
|
|
928
|
-
};
|
|
929
|
-
}
|
|
930
|
-
};
|
|
931
|
-
|
|
932
|
-
// src/virtual/freestyle-fs.ts
|
|
933
|
-
import * as path5 from "path";
|
|
934
|
-
var FreestyleFs = class {
|
|
935
|
-
vm;
|
|
936
|
-
workingDir;
|
|
937
|
-
constructor(opts) {
|
|
938
|
-
this.vm = opts.vm;
|
|
939
|
-
this.workingDir = opts.workingDir;
|
|
940
|
-
}
|
|
941
|
-
resolvePath(p) {
|
|
942
|
-
if (p.includes("\0")) {
|
|
943
|
-
throw new Error("Path contains null bytes");
|
|
944
|
-
}
|
|
945
|
-
if (!this.workingDir) return p;
|
|
946
|
-
const normalizedBase = this.workingDir.endsWith("/") ? this.workingDir : this.workingDir + "/";
|
|
947
|
-
if (p.startsWith("/")) {
|
|
948
|
-
const normalized = path5.normalize(p);
|
|
949
|
-
if (normalized !== this.workingDir && !normalized.startsWith(normalizedBase)) {
|
|
950
|
-
throw new Error(`Absolute path "${p}" is outside working directory "${this.workingDir}"`);
|
|
951
|
-
}
|
|
952
|
-
return normalized;
|
|
953
|
-
}
|
|
954
|
-
const resolved = path5.resolve(this.workingDir, p);
|
|
955
|
-
if (resolved !== this.workingDir && !resolved.startsWith(normalizedBase)) {
|
|
956
|
-
throw new Error(`Path "${p}" escapes working directory "${this.workingDir}"`);
|
|
957
|
-
}
|
|
958
|
-
return resolved;
|
|
959
|
-
}
|
|
960
|
-
async readFile(filePath, _opts) {
|
|
961
|
-
return this.vm.fs.readTextFile(this.resolvePath(filePath));
|
|
962
|
-
}
|
|
963
|
-
async readFileBytes(filePath, maxBytes) {
|
|
964
|
-
const resolved = this.resolvePath(filePath);
|
|
965
|
-
const cmd = maxBytes !== void 0 ? `head -c ${maxBytes} ${shellEscape3(resolved)} | base64` : `base64 ${shellEscape3(resolved)}`;
|
|
966
|
-
const { statusCode, stdout, stderr } = await this.vm.exec(cmd);
|
|
967
|
-
if (statusCode !== 0) {
|
|
968
|
-
throw new Error(`FreestyleFs readFileBytes failed: ${stderr?.trim() || `exit code ${statusCode}`}`);
|
|
969
|
-
}
|
|
970
|
-
return Buffer.from((stdout ?? "").trim(), "base64");
|
|
971
|
-
}
|
|
972
|
-
async writeFile(filePath, content) {
|
|
973
|
-
await this.vm.fs.writeTextFile(this.resolvePath(filePath), content);
|
|
974
|
-
}
|
|
975
|
-
async appendFile(filePath, content) {
|
|
976
|
-
const resolved = this.resolvePath(filePath);
|
|
977
|
-
const encoded = Buffer.from(content, "utf-8").toString("base64");
|
|
978
|
-
const { statusCode, stderr } = await this.vm.exec(
|
|
979
|
-
`echo ${shellEscape3(encoded)} | base64 -d >> ${shellEscape3(resolved)}`
|
|
980
|
-
);
|
|
981
|
-
if (statusCode !== 0) {
|
|
982
|
-
throw new Error(`FreestyleFs appendFile failed: ${stderr?.trim() || `exit code ${statusCode}`}`);
|
|
983
|
-
}
|
|
984
|
-
}
|
|
985
|
-
async deleteFile(filePath, opts) {
|
|
986
|
-
const resolved = this.resolvePath(filePath);
|
|
987
|
-
const flag = opts?.recursive ? "-rf" : "-f";
|
|
988
|
-
await this.vm.exec(`rm ${flag} ${shellEscape3(resolved)}`);
|
|
989
|
-
}
|
|
990
|
-
async mkdir(filePath, opts) {
|
|
991
|
-
const resolved = this.resolvePath(filePath);
|
|
992
|
-
const flag = opts?.recursive ? "-p " : "";
|
|
993
|
-
await this.vm.exec(`mkdir ${flag}${shellEscape3(resolved)}`);
|
|
994
|
-
}
|
|
995
|
-
async readdir(dirPath, _opts) {
|
|
996
|
-
const resolved = this.resolvePath(dirPath);
|
|
997
|
-
const items = await this.vm.fs.readDir(resolved);
|
|
998
|
-
return items.map((entry) => ({
|
|
999
|
-
name: entry.name,
|
|
1000
|
-
path: resolved === "/" ? `/${entry.name}` : `${resolved}/${entry.name}`,
|
|
1001
|
-
isDirectory: entry.kind === "dir" || entry.kind === "directory",
|
|
1002
|
-
isFile: entry.kind === "file"
|
|
1003
|
-
}));
|
|
1004
|
-
}
|
|
1005
|
-
async exists(filePath) {
|
|
1006
|
-
const resolved = this.resolvePath(filePath);
|
|
1007
|
-
const { statusCode } = await this.vm.exec(`test -e ${shellEscape3(resolved)}`);
|
|
1008
|
-
return statusCode === 0;
|
|
1009
|
-
}
|
|
1010
|
-
async stat(filePath) {
|
|
1011
|
-
const resolved = this.resolvePath(filePath);
|
|
1012
|
-
const { statusCode, stdout, stderr } = await this.vm.exec(
|
|
1013
|
-
`stat -c '%s %F %W %Y' ${shellEscape3(resolved)}`
|
|
1014
|
-
);
|
|
1015
|
-
if (statusCode !== 0) {
|
|
1016
|
-
throw new Error(`FreestyleFs stat failed: ${stderr?.trim() || `exit code ${statusCode}`}`);
|
|
1017
|
-
}
|
|
1018
|
-
const parts = (stdout ?? "").trim().split(" ");
|
|
1019
|
-
const size = parseInt(parts[0], 10);
|
|
1020
|
-
const fileType = parts[1];
|
|
1021
|
-
const createdEpoch = parseInt(parts[2], 10);
|
|
1022
|
-
const modifiedEpoch = parseInt(parts[3], 10);
|
|
1023
|
-
return {
|
|
1024
|
-
size,
|
|
1025
|
-
isDirectory: fileType === "directory",
|
|
1026
|
-
isFile: fileType.startsWith("regular"),
|
|
1027
|
-
createdAt: createdEpoch > 0 ? new Date(createdEpoch * 1e3) : void 0,
|
|
1028
|
-
modifiedAt: modifiedEpoch > 0 ? new Date(modifiedEpoch * 1e3) : void 0
|
|
1029
|
-
};
|
|
1030
|
-
}
|
|
1031
|
-
};
|
|
1032
|
-
function shellEscape3(s) {
|
|
1033
|
-
return `'${s.replace(/'/g, "'\\''")}'`;
|
|
1034
|
-
}
|
|
1035
|
-
|
|
1036
|
-
// src/virtual/freestyle-computer.ts
|
|
1037
|
-
var FreestyleComputer = class {
|
|
1038
|
-
vm;
|
|
1039
|
-
defaultCwd;
|
|
1040
|
-
defaultTimeout;
|
|
1041
|
-
constructor(opts) {
|
|
1042
|
-
this.vm = opts.vm;
|
|
1043
|
-
this.defaultCwd = opts.defaultCwd;
|
|
1044
|
-
this.defaultTimeout = opts.defaultTimeout ?? 3e4;
|
|
1045
|
-
}
|
|
1046
|
-
async executeCommand(command, opts) {
|
|
1047
|
-
const result = await this.vm.exec(command, {
|
|
1048
|
-
cwd: opts?.cwd ?? this.defaultCwd,
|
|
1049
|
-
timeout: opts?.timeout ?? this.defaultTimeout
|
|
1050
|
-
});
|
|
1051
|
-
return {
|
|
1052
|
-
exitCode: result.statusCode ?? 1,
|
|
1053
|
-
stdout: result.stdout ?? "",
|
|
1054
|
-
stderr: result.stderr ?? ""
|
|
1055
|
-
};
|
|
1056
|
-
}
|
|
1057
|
-
};
|
|
1058
|
-
|
|
1059
286
|
// src/virtual/sandbox.ts
|
|
1060
|
-
function uninitError() {
|
|
1061
|
-
throw new Error(
|
|
1062
|
-
"Sandbox not initialized \u2014 call init() or pass a pre-created resource"
|
|
1063
|
-
);
|
|
1064
|
-
}
|
|
1065
|
-
function createFsProxy() {
|
|
1066
|
-
let inner = null;
|
|
1067
|
-
const get = () => inner ?? uninitError();
|
|
1068
|
-
return {
|
|
1069
|
-
setTarget(target) {
|
|
1070
|
-
inner = target;
|
|
1071
|
-
},
|
|
1072
|
-
readFile: (...args) => get().readFile(...args),
|
|
1073
|
-
readFileBytes: (...args) => get().readFileBytes?.(...args),
|
|
1074
|
-
writeFile: (...args) => get().writeFile(...args),
|
|
1075
|
-
appendFile: (...args) => get().appendFile(...args),
|
|
1076
|
-
deleteFile: (...args) => get().deleteFile(...args),
|
|
1077
|
-
mkdir: (...args) => get().mkdir(...args),
|
|
1078
|
-
readdir: (...args) => get().readdir(...args),
|
|
1079
|
-
exists: (...args) => get().exists(...args),
|
|
1080
|
-
stat: (...args) => get().stat(...args)
|
|
1081
|
-
};
|
|
1082
|
-
}
|
|
1083
|
-
function createComputerProxy() {
|
|
1084
|
-
let inner = null;
|
|
1085
|
-
const get = () => inner ?? uninitError();
|
|
1086
|
-
return {
|
|
1087
|
-
setTarget(target) {
|
|
1088
|
-
inner = target;
|
|
1089
|
-
},
|
|
1090
|
-
executeCommand: (...args) => get().executeCommand(...args)
|
|
1091
|
-
};
|
|
1092
|
-
}
|
|
1093
287
|
function UnsandboxedLocal(opts) {
|
|
1094
288
|
const cwd = opts?.cwd;
|
|
1095
289
|
return {
|
|
@@ -1121,357 +315,126 @@ function LocalSandbox(opts) {
|
|
|
1121
315
|
dispose: () => computer.dispose()
|
|
1122
316
|
};
|
|
1123
317
|
}
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
if (
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
name = `${opts.namePrefix ?? "noumen-"}${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
1150
|
-
needsCreate = true;
|
|
1151
|
-
}
|
|
1152
|
-
}
|
|
1153
|
-
if (needsCreate) {
|
|
1154
|
-
const res = await fetch(`${baseURL}/v1/sprites`, {
|
|
1155
|
-
method: "POST",
|
|
1156
|
-
headers: {
|
|
1157
|
-
Authorization: `Bearer ${opts.token}`,
|
|
1158
|
-
"Content-Type": "application/json"
|
|
1159
|
-
},
|
|
1160
|
-
body: JSON.stringify({ name })
|
|
1161
|
-
});
|
|
1162
|
-
if (!res.ok) {
|
|
1163
|
-
throw new Error(`Sprites auto-create failed (${res.status}): ${await res.text()}`);
|
|
1164
|
-
}
|
|
1165
|
-
autoCreated = true;
|
|
318
|
+
|
|
319
|
+
// src/session/auto-title.ts
|
|
320
|
+
var DEFAULT_AUTO_TITLE_MAX_INPUT_CHARS = 2e3;
|
|
321
|
+
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.
|
|
322
|
+
|
|
323
|
+
Return JSON with a single "title" field.
|
|
324
|
+
|
|
325
|
+
Good examples:
|
|
326
|
+
{"title": "Fix login button on mobile"}
|
|
327
|
+
{"title": "Add OAuth authentication"}
|
|
328
|
+
{"title": "Debug failing CI tests"}
|
|
329
|
+
{"title": "Refactor API client error handling"}
|
|
330
|
+
|
|
331
|
+
Bad (too vague): {"title": "Code changes"}
|
|
332
|
+
Bad (too long): {"title": "Investigate and fix the issue where the login button does not respond on mobile devices"}
|
|
333
|
+
Bad (wrong case): {"title": "Fix Login Button On Mobile"}`;
|
|
334
|
+
function extractTitleSeedText(messages, maxChars = DEFAULT_AUTO_TITLE_MAX_INPUT_CHARS) {
|
|
335
|
+
const parts = [];
|
|
336
|
+
for (const msg of messages) {
|
|
337
|
+
if (msg.role !== "user" && msg.role !== "assistant") continue;
|
|
338
|
+
const content = msg.content;
|
|
339
|
+
if (content === null || content === void 0) continue;
|
|
340
|
+
if (typeof content === "string") {
|
|
341
|
+
if (content.trim()) parts.push(content);
|
|
342
|
+
continue;
|
|
1166
343
|
}
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
}
|
|
1172
|
-
return {
|
|
1173
|
-
fs: fsProxy,
|
|
1174
|
-
computer: computerProxy,
|
|
1175
|
-
sandboxId: () => resolvedName,
|
|
1176
|
-
init(sandboxId) {
|
|
1177
|
-
if (!initPromise) {
|
|
1178
|
-
initPromise = doInit(sandboxId).catch((err) => {
|
|
1179
|
-
initPromise = null;
|
|
1180
|
-
throw err;
|
|
1181
|
-
});
|
|
1182
|
-
}
|
|
1183
|
-
return initPromise;
|
|
1184
|
-
},
|
|
1185
|
-
async dispose() {
|
|
1186
|
-
if (initPromise) {
|
|
1187
|
-
await initPromise.catch(() => {
|
|
1188
|
-
});
|
|
1189
|
-
}
|
|
1190
|
-
if (!autoCreated || !resolvedName) return;
|
|
1191
|
-
try {
|
|
1192
|
-
const res = await fetch(`${baseURL}/v1/sprites/${resolvedName}`, {
|
|
1193
|
-
method: "DELETE",
|
|
1194
|
-
headers: { Authorization: `Bearer ${opts.token}` }
|
|
1195
|
-
});
|
|
1196
|
-
if (!res.ok && res.status !== 404) {
|
|
1197
|
-
throw new Error(`Sprites dispose failed (${res.status}): ${await res.text()}`);
|
|
344
|
+
if (Array.isArray(content)) {
|
|
345
|
+
for (const block of content) {
|
|
346
|
+
if (block.type === "text" && typeof block.text === "string") {
|
|
347
|
+
if (block.text.trim()) parts.push(block.text);
|
|
1198
348
|
}
|
|
1199
|
-
} catch {
|
|
1200
349
|
}
|
|
1201
350
|
}
|
|
1202
|
-
};
|
|
1203
|
-
}
|
|
1204
|
-
function DockerSandbox(opts) {
|
|
1205
|
-
if (opts.container) {
|
|
1206
|
-
const c = opts.container;
|
|
1207
|
-
return {
|
|
1208
|
-
fs: new DockerFs({ container: c, workingDir: opts.cwd }),
|
|
1209
|
-
computer: new DockerComputer({
|
|
1210
|
-
container: c,
|
|
1211
|
-
defaultCwd: opts.cwd,
|
|
1212
|
-
defaultTimeout: opts.defaultTimeout
|
|
1213
|
-
}),
|
|
1214
|
-
sandboxId: () => c.id
|
|
1215
|
-
};
|
|
1216
351
|
}
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
Image: opts.image,
|
|
1237
|
-
Cmd: opts.cmd ?? ["sleep", "infinity"],
|
|
1238
|
-
Env: opts.env,
|
|
1239
|
-
Tty: false,
|
|
1240
|
-
...opts.dockerOptions
|
|
1241
|
-
});
|
|
1242
|
-
await container.start();
|
|
1243
|
-
autoCreated = true;
|
|
1244
|
-
}
|
|
1245
|
-
} else {
|
|
1246
|
-
container = await docker.createContainer({
|
|
1247
|
-
Image: opts.image,
|
|
1248
|
-
Cmd: opts.cmd ?? ["sleep", "infinity"],
|
|
1249
|
-
Env: opts.env,
|
|
1250
|
-
Tty: false,
|
|
1251
|
-
...opts.dockerOptions
|
|
1252
|
-
});
|
|
1253
|
-
await container.start();
|
|
1254
|
-
autoCreated = true;
|
|
1255
|
-
}
|
|
1256
|
-
containerRef = container;
|
|
1257
|
-
containerId = container.id;
|
|
1258
|
-
fsProxy.setTarget(new DockerFs({ container, workingDir: opts.cwd }));
|
|
1259
|
-
computerProxy.setTarget(new DockerComputer({
|
|
1260
|
-
container,
|
|
1261
|
-
defaultCwd: opts.cwd,
|
|
1262
|
-
defaultTimeout: opts.defaultTimeout
|
|
1263
|
-
}));
|
|
1264
|
-
}
|
|
1265
|
-
return {
|
|
1266
|
-
fs: fsProxy,
|
|
1267
|
-
computer: computerProxy,
|
|
1268
|
-
sandboxId: () => containerId,
|
|
1269
|
-
init(sandboxId) {
|
|
1270
|
-
if (!initPromise) {
|
|
1271
|
-
initPromise = doInit(sandboxId).catch((err) => {
|
|
1272
|
-
initPromise = null;
|
|
1273
|
-
throw err;
|
|
1274
|
-
});
|
|
1275
|
-
}
|
|
1276
|
-
return initPromise;
|
|
1277
|
-
},
|
|
1278
|
-
async dispose() {
|
|
1279
|
-
if (initPromise) {
|
|
1280
|
-
await initPromise.catch(() => {
|
|
1281
|
-
});
|
|
1282
|
-
}
|
|
1283
|
-
if (!autoCreated || !containerRef) return;
|
|
1284
|
-
try {
|
|
1285
|
-
await containerRef.stop();
|
|
1286
|
-
} catch {
|
|
1287
|
-
}
|
|
1288
|
-
try {
|
|
1289
|
-
await containerRef.remove();
|
|
1290
|
-
} catch {
|
|
352
|
+
const text = parts.join("\n").trim();
|
|
353
|
+
if (text.length <= maxChars) return text;
|
|
354
|
+
return text.slice(-maxChars);
|
|
355
|
+
}
|
|
356
|
+
function extractTitleFromResponse(raw) {
|
|
357
|
+
if (!raw) return null;
|
|
358
|
+
const trimmed = raw.trim();
|
|
359
|
+
const parseTitle = (s) => {
|
|
360
|
+
let obj;
|
|
361
|
+
try {
|
|
362
|
+
obj = JSON.parse(s);
|
|
363
|
+
} catch {
|
|
364
|
+
return { kind: "none" };
|
|
365
|
+
}
|
|
366
|
+
if (obj && typeof obj === "object" && "title" in obj) {
|
|
367
|
+
const v = obj.title;
|
|
368
|
+
if (typeof v === "string") {
|
|
369
|
+
const t = v.trim();
|
|
370
|
+
if (t) return { kind: "ok", title: t };
|
|
1291
371
|
}
|
|
1292
372
|
}
|
|
373
|
+
return { kind: "empty" };
|
|
1293
374
|
};
|
|
375
|
+
const whole = parseTitle(trimmed);
|
|
376
|
+
if (whole.kind === "ok") return whole.title;
|
|
377
|
+
if (whole.kind === "empty") return null;
|
|
378
|
+
const start = trimmed.indexOf("{");
|
|
379
|
+
const end = trimmed.lastIndexOf("}");
|
|
380
|
+
if (start >= 0 && end > start) {
|
|
381
|
+
const sliced = parseTitle(trimmed.slice(start, end + 1));
|
|
382
|
+
if (sliced.kind === "ok") return sliced.title;
|
|
383
|
+
if (sliced.kind === "empty") return null;
|
|
384
|
+
}
|
|
385
|
+
const quoteMatch = trimmed.match(/"([^"\\]{2,120})"/);
|
|
386
|
+
if (quoteMatch?.[1]) return quoteMatch[1].trim();
|
|
387
|
+
return null;
|
|
1294
388
|
}
|
|
1295
|
-
function
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
const SandboxClass = e2b.Sandbox ?? e2b.default?.Sandbox;
|
|
1317
|
-
if (!SandboxClass) {
|
|
1318
|
-
throw new Error("Could not resolve Sandbox class from 'e2b' package");
|
|
1319
|
-
}
|
|
1320
|
-
let sandbox;
|
|
1321
|
-
if (reconnectId) {
|
|
1322
|
-
try {
|
|
1323
|
-
sandbox = await SandboxClass.connect(reconnectId, {
|
|
1324
|
-
apiKey: opts.apiKey
|
|
1325
|
-
});
|
|
1326
|
-
} catch {
|
|
1327
|
-
sandbox = await SandboxClass.create({
|
|
1328
|
-
template: opts.template ?? "base",
|
|
1329
|
-
apiKey: opts.apiKey,
|
|
1330
|
-
timeoutMs: opts.timeoutMs
|
|
1331
|
-
});
|
|
1332
|
-
autoCreated = true;
|
|
1333
|
-
}
|
|
1334
|
-
} else {
|
|
1335
|
-
sandbox = await SandboxClass.create({
|
|
1336
|
-
template: opts.template ?? "base",
|
|
1337
|
-
apiKey: opts.apiKey,
|
|
1338
|
-
timeoutMs: opts.timeoutMs
|
|
1339
|
-
});
|
|
1340
|
-
autoCreated = true;
|
|
1341
|
-
}
|
|
1342
|
-
sandboxRef = sandbox;
|
|
1343
|
-
resolvedId = sandbox.sandboxId ?? reconnectId;
|
|
1344
|
-
fsProxy.setTarget(new E2BFs({ sandbox, workingDir: opts.cwd }));
|
|
1345
|
-
computerProxy.setTarget(new E2BComputer({
|
|
1346
|
-
sandbox,
|
|
1347
|
-
defaultCwd: opts.cwd,
|
|
1348
|
-
defaultTimeout: opts.defaultTimeout
|
|
1349
|
-
}));
|
|
1350
|
-
}
|
|
1351
|
-
return {
|
|
1352
|
-
fs: fsProxy,
|
|
1353
|
-
computer: computerProxy,
|
|
1354
|
-
sandboxId: () => resolvedId,
|
|
1355
|
-
init(sandboxId) {
|
|
1356
|
-
if (!initPromise) {
|
|
1357
|
-
initPromise = doInit(sandboxId).catch((err) => {
|
|
1358
|
-
initPromise = null;
|
|
1359
|
-
throw err;
|
|
1360
|
-
});
|
|
1361
|
-
}
|
|
1362
|
-
return initPromise;
|
|
389
|
+
async function generateAutoTitle(messages, opts) {
|
|
390
|
+
const seed = extractTitleSeedText(messages, opts.maxInputChars);
|
|
391
|
+
if (!seed) return null;
|
|
392
|
+
const model = opts.model ?? opts.provider.defaultModel;
|
|
393
|
+
if (!model) return null;
|
|
394
|
+
const system = opts.systemPrompt ?? DEFAULT_AUTO_TITLE_SYSTEM_PROMPT;
|
|
395
|
+
const params = {
|
|
396
|
+
model,
|
|
397
|
+
system,
|
|
398
|
+
messages: [{ role: "user", content: seed }],
|
|
399
|
+
max_tokens: 60,
|
|
400
|
+
outputFormat: {
|
|
401
|
+
type: "json_schema",
|
|
402
|
+
schema: {
|
|
403
|
+
type: "object",
|
|
404
|
+
properties: { title: { type: "string" } },
|
|
405
|
+
required: ["title"],
|
|
406
|
+
additionalProperties: false
|
|
407
|
+
},
|
|
408
|
+
name: "session_title",
|
|
409
|
+
strict: true
|
|
1363
410
|
},
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
411
|
+
signal: opts.signal
|
|
412
|
+
};
|
|
413
|
+
let text = "";
|
|
414
|
+
try {
|
|
415
|
+
for await (const chunk of opts.provider.chat(params)) {
|
|
416
|
+
for (const choice of chunk.choices) {
|
|
417
|
+
const delta = choice.delta.content;
|
|
418
|
+
if (typeof delta === "string") text += delta;
|
|
1372
419
|
}
|
|
1373
420
|
}
|
|
1374
|
-
}
|
|
1375
|
-
|
|
1376
|
-
function FreestyleSandbox(opts) {
|
|
1377
|
-
if (opts.vm) {
|
|
1378
|
-
const v = opts.vm;
|
|
1379
|
-
return {
|
|
1380
|
-
fs: new FreestyleFs({ vm: v, workingDir: opts.cwd }),
|
|
1381
|
-
computer: new FreestyleComputer({
|
|
1382
|
-
vm: v,
|
|
1383
|
-
defaultCwd: opts.cwd,
|
|
1384
|
-
defaultTimeout: opts.defaultTimeout
|
|
1385
|
-
}),
|
|
1386
|
-
sandboxId: () => v.vmId
|
|
1387
|
-
};
|
|
421
|
+
} catch {
|
|
422
|
+
return null;
|
|
1388
423
|
}
|
|
1389
|
-
|
|
1390
|
-
const
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
if (!freestyle?.vms) {
|
|
1399
|
-
throw new Error("Could not resolve freestyle client from 'freestyle-sandboxes' package");
|
|
1400
|
-
}
|
|
1401
|
-
let vm;
|
|
1402
|
-
if (reconnectId) {
|
|
1403
|
-
try {
|
|
1404
|
-
const result = await freestyle.vms.get({ vmId: reconnectId });
|
|
1405
|
-
vm = result.vm;
|
|
1406
|
-
resolvedId = reconnectId;
|
|
1407
|
-
} catch {
|
|
1408
|
-
const result = await freestyle.vms.create({
|
|
1409
|
-
...opts.spec ? { spec: opts.spec } : {},
|
|
1410
|
-
snapshotId: opts.snapshotId,
|
|
1411
|
-
workdir: opts.cwd,
|
|
1412
|
-
idleTimeoutSeconds: opts.idleTimeoutSeconds ?? 600,
|
|
1413
|
-
additionalFiles: opts.additionalFiles,
|
|
1414
|
-
gitRepos: opts.gitRepos
|
|
1415
|
-
});
|
|
1416
|
-
vm = result.vm;
|
|
1417
|
-
resolvedId = result.vmId ?? result.id;
|
|
1418
|
-
autoCreated = true;
|
|
1419
|
-
}
|
|
1420
|
-
} else {
|
|
1421
|
-
const result = await freestyle.vms.create({
|
|
1422
|
-
...opts.spec ? { spec: opts.spec } : {},
|
|
1423
|
-
snapshotId: opts.snapshotId,
|
|
1424
|
-
workdir: opts.cwd,
|
|
1425
|
-
idleTimeoutSeconds: opts.idleTimeoutSeconds ?? 600,
|
|
1426
|
-
additionalFiles: opts.additionalFiles,
|
|
1427
|
-
gitRepos: opts.gitRepos
|
|
1428
|
-
});
|
|
1429
|
-
vm = result.vm;
|
|
1430
|
-
resolvedId = result.vmId ?? result.id;
|
|
1431
|
-
autoCreated = true;
|
|
1432
|
-
}
|
|
1433
|
-
vmRef = vm;
|
|
1434
|
-
fsProxy.setTarget(new FreestyleFs({ vm, workingDir: opts.cwd }));
|
|
1435
|
-
computerProxy.setTarget(new FreestyleComputer({
|
|
1436
|
-
vm,
|
|
1437
|
-
defaultCwd: opts.cwd,
|
|
1438
|
-
defaultTimeout: opts.defaultTimeout
|
|
1439
|
-
}));
|
|
424
|
+
if (!text) return null;
|
|
425
|
+
const extracted = extractTitleFromResponse(text) ?? text.trim();
|
|
426
|
+
return normalizeTitle(extracted);
|
|
427
|
+
}
|
|
428
|
+
function normalizeTitle(raw) {
|
|
429
|
+
if (!raw) return null;
|
|
430
|
+
let t = raw.replace(/\s+/g, " ").trim();
|
|
431
|
+
if (t.length >= 2 && (t.startsWith('"') && t.endsWith('"') || t.startsWith("'") && t.endsWith("'"))) {
|
|
432
|
+
t = t.slice(1, -1).trim();
|
|
1440
433
|
}
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
init(sandboxId) {
|
|
1446
|
-
if (!initPromise) {
|
|
1447
|
-
initPromise = doInit(sandboxId).catch((err) => {
|
|
1448
|
-
initPromise = null;
|
|
1449
|
-
throw err;
|
|
1450
|
-
});
|
|
1451
|
-
}
|
|
1452
|
-
return initPromise;
|
|
1453
|
-
},
|
|
1454
|
-
async dispose() {
|
|
1455
|
-
if (initPromise) {
|
|
1456
|
-
await initPromise.catch(() => {
|
|
1457
|
-
});
|
|
1458
|
-
}
|
|
1459
|
-
if (!autoCreated || !vmRef || !resolvedId) return;
|
|
1460
|
-
try {
|
|
1461
|
-
const strategy = opts.disposeStrategy ?? "suspend";
|
|
1462
|
-
if (strategy === "suspend") {
|
|
1463
|
-
await vmRef.suspend();
|
|
1464
|
-
} else {
|
|
1465
|
-
const mod = await import("freestyle-sandboxes");
|
|
1466
|
-
const freestyle = mod.freestyle ?? mod.default?.freestyle;
|
|
1467
|
-
if (freestyle?.vms) {
|
|
1468
|
-
await freestyle.vms.delete({ vmId: resolvedId });
|
|
1469
|
-
}
|
|
1470
|
-
}
|
|
1471
|
-
} catch {
|
|
1472
|
-
}
|
|
1473
|
-
}
|
|
1474
|
-
};
|
|
434
|
+
t = t.replace(/\.+$/, "").trim();
|
|
435
|
+
if (!t) return null;
|
|
436
|
+
if (t.length > 120) t = t.slice(0, 120).trim();
|
|
437
|
+
return t;
|
|
1475
438
|
}
|
|
1476
439
|
|
|
1477
440
|
// src/checkpoint/manager.ts
|
|
@@ -1704,8 +667,8 @@ var FileCheckpointManager = class {
|
|
|
1704
667
|
restoreStateFromEntries(snapshots) {
|
|
1705
668
|
const trackedFiles = /* @__PURE__ */ new Set();
|
|
1706
669
|
for (const snap of snapshots) {
|
|
1707
|
-
for (const
|
|
1708
|
-
trackedFiles.add(
|
|
670
|
+
for (const path2 of Object.keys(snap.trackedFileBackups)) {
|
|
671
|
+
trackedFiles.add(path2);
|
|
1709
672
|
}
|
|
1710
673
|
}
|
|
1711
674
|
this.state = {
|
|
@@ -1719,7 +682,7 @@ var FileCheckpointManager = class {
|
|
|
1719
682
|
// src/hooks/runner.ts
|
|
1720
683
|
var DEFAULT_HOOK_TIMEOUT_MS = 3e4;
|
|
1721
684
|
function withTimeout(promise, timeoutMs, label) {
|
|
1722
|
-
return new Promise((
|
|
685
|
+
return new Promise((resolve3, reject) => {
|
|
1723
686
|
const timer = setTimeout(
|
|
1724
687
|
() => reject(new Error(`Hook "${label}" timed out after ${timeoutMs}ms`)),
|
|
1725
688
|
timeoutMs
|
|
@@ -1727,7 +690,7 @@ function withTimeout(promise, timeoutMs, label) {
|
|
|
1727
690
|
promise.then(
|
|
1728
691
|
(v) => {
|
|
1729
692
|
clearTimeout(timer);
|
|
1730
|
-
|
|
693
|
+
resolve3(v);
|
|
1731
694
|
},
|
|
1732
695
|
(e) => {
|
|
1733
696
|
clearTimeout(timer);
|
|
@@ -2963,38 +1926,90 @@ var SessionStorage = class {
|
|
|
2963
1926
|
await this.appendEntry(sessionId, entry);
|
|
2964
1927
|
}
|
|
2965
1928
|
/**
|
|
2966
|
-
*
|
|
2967
|
-
*
|
|
1929
|
+
* Append a user-set session title. Wins over any `ai-title` when read.
|
|
1930
|
+
* Idempotent-ish: callers may append as many as they like; the last one
|
|
1931
|
+
* wins according to file order.
|
|
1932
|
+
*/
|
|
1933
|
+
async appendCustomTitle(sessionId, title) {
|
|
1934
|
+
const entry = {
|
|
1935
|
+
type: "custom-title",
|
|
1936
|
+
sessionId,
|
|
1937
|
+
title,
|
|
1938
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1939
|
+
};
|
|
1940
|
+
await this.appendEntry(sessionId, entry);
|
|
1941
|
+
}
|
|
1942
|
+
/**
|
|
1943
|
+
* Append an AI-generated session title. A `custom-title` (if present)
|
|
1944
|
+
* always takes precedence on read.
|
|
1945
|
+
*/
|
|
1946
|
+
async appendAiTitle(sessionId, title) {
|
|
1947
|
+
const entry = {
|
|
1948
|
+
type: "ai-title",
|
|
1949
|
+
sessionId,
|
|
1950
|
+
title,
|
|
1951
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1952
|
+
};
|
|
1953
|
+
await this.appendEntry(sessionId, entry);
|
|
1954
|
+
}
|
|
1955
|
+
/**
|
|
1956
|
+
* Re-append custom-title, ai-title, and key metadata entries after a
|
|
1957
|
+
* compact boundary so they remain discoverable in the active-entries
|
|
1958
|
+
* window. Written as a single batched append so a crash mid-write can't
|
|
1959
|
+
* leave the transcript with only a subset of the re-emitted entries.
|
|
2968
1960
|
*/
|
|
2969
1961
|
async reAppendMetadataAfterCompact(sessionId) {
|
|
2970
1962
|
const entries = await this.loadAllEntries(sessionId);
|
|
2971
1963
|
let customTitle;
|
|
1964
|
+
let aiTitle;
|
|
2972
1965
|
const metadataByKey = /* @__PURE__ */ new Map();
|
|
2973
1966
|
for (const entry of entries) {
|
|
2974
1967
|
if (entry.type === "custom-title") {
|
|
2975
1968
|
customTitle = entry.title;
|
|
2976
1969
|
}
|
|
1970
|
+
if (entry.type === "ai-title") {
|
|
1971
|
+
aiTitle = entry.title;
|
|
1972
|
+
}
|
|
2977
1973
|
if (entry.type === "metadata") {
|
|
2978
1974
|
metadataByKey.set(entry.key, entry.value);
|
|
2979
1975
|
}
|
|
2980
1976
|
}
|
|
1977
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
1978
|
+
const batch = [];
|
|
2981
1979
|
if (customTitle) {
|
|
2982
|
-
|
|
1980
|
+
batch.push({
|
|
2983
1981
|
type: "custom-title",
|
|
2984
1982
|
sessionId,
|
|
2985
1983
|
title: customTitle,
|
|
2986
|
-
timestamp
|
|
1984
|
+
timestamp
|
|
1985
|
+
});
|
|
1986
|
+
}
|
|
1987
|
+
if (aiTitle) {
|
|
1988
|
+
batch.push({
|
|
1989
|
+
type: "ai-title",
|
|
1990
|
+
sessionId,
|
|
1991
|
+
title: aiTitle,
|
|
1992
|
+
timestamp
|
|
2987
1993
|
});
|
|
2988
1994
|
}
|
|
2989
1995
|
for (const [key, value] of metadataByKey) {
|
|
2990
|
-
|
|
1996
|
+
batch.push({
|
|
1997
|
+
type: "metadata",
|
|
1998
|
+
sessionId,
|
|
1999
|
+
timestamp,
|
|
2000
|
+
key,
|
|
2001
|
+
value
|
|
2002
|
+
});
|
|
2003
|
+
}
|
|
2004
|
+
if (batch.length > 0) {
|
|
2005
|
+
await this.appendEntriesBatch(sessionId, batch);
|
|
2991
2006
|
}
|
|
2992
2007
|
}
|
|
2993
2008
|
async loadMessages(sessionId) {
|
|
2994
|
-
const
|
|
2995
|
-
const exists = await this.fs.exists(
|
|
2009
|
+
const path2 = this.getTranscriptPath(sessionId);
|
|
2010
|
+
const exists = await this.fs.exists(path2);
|
|
2996
2011
|
if (!exists) return [];
|
|
2997
|
-
const content = await this.fs.readFile(
|
|
2012
|
+
const content = await this.fs.readFile(path2);
|
|
2998
2013
|
const entries = parseJSONL(content);
|
|
2999
2014
|
let lastBoundaryIdx = -1;
|
|
3000
2015
|
for (let i = entries.length - 1; i >= 0; i--) {
|
|
@@ -3029,15 +2044,30 @@ var SessionStorage = class {
|
|
|
3029
2044
|
return messages;
|
|
3030
2045
|
}
|
|
3031
2046
|
async loadAllEntries(sessionId) {
|
|
3032
|
-
const
|
|
3033
|
-
const exists = await this.fs.exists(
|
|
2047
|
+
const path2 = this.getTranscriptPath(sessionId);
|
|
2048
|
+
const exists = await this.fs.exists(path2);
|
|
3034
2049
|
if (!exists) return [];
|
|
3035
|
-
const content = await this.fs.readFile(
|
|
2050
|
+
const content = await this.fs.readFile(path2);
|
|
3036
2051
|
return parseJSONL(content);
|
|
3037
2052
|
}
|
|
3038
2053
|
async sessionExists(sessionId) {
|
|
3039
2054
|
return this.fs.exists(this.getTranscriptPath(sessionId));
|
|
3040
2055
|
}
|
|
2056
|
+
/**
|
|
2057
|
+
* Return the currently persisted titles for a session. `title` reflects
|
|
2058
|
+
* the display preference (custom > ai). Returns all-undefined if the
|
|
2059
|
+
* session file doesn't exist.
|
|
2060
|
+
*/
|
|
2061
|
+
async getSessionTitles(sessionId) {
|
|
2062
|
+
const entries = await this.loadAllEntries(sessionId);
|
|
2063
|
+
let customTitle;
|
|
2064
|
+
let aiTitle;
|
|
2065
|
+
for (const entry of entries) {
|
|
2066
|
+
if (entry.type === "custom-title") customTitle = entry.title;
|
|
2067
|
+
if (entry.type === "ai-title") aiTitle = entry.title;
|
|
2068
|
+
}
|
|
2069
|
+
return { title: customTitle ?? aiTitle, customTitle, aiTitle };
|
|
2070
|
+
}
|
|
3041
2071
|
async deleteSession(sessionId) {
|
|
3042
2072
|
const filePath = this.getTranscriptPath(sessionId);
|
|
3043
2073
|
const exists = await this.fs.exists(filePath);
|
|
@@ -3076,8 +2106,8 @@ var SessionStorage = class {
|
|
|
3076
2106
|
tailSlice = content;
|
|
3077
2107
|
}
|
|
3078
2108
|
const headEntries = parseJSONL(headSlice);
|
|
3079
|
-
|
|
3080
|
-
let
|
|
2109
|
+
let customTitle;
|
|
2110
|
+
let aiTitle;
|
|
3081
2111
|
let firstTimestamp;
|
|
3082
2112
|
let lastTimestamp;
|
|
3083
2113
|
let messageCount = 0;
|
|
@@ -3087,22 +2117,27 @@ var SessionStorage = class {
|
|
|
3087
2117
|
if (!firstTimestamp) firstTimestamp = e.timestamp;
|
|
3088
2118
|
lastTimestamp = e.timestamp;
|
|
3089
2119
|
}
|
|
3090
|
-
if (e.type === "custom-title")
|
|
2120
|
+
if (e.type === "custom-title") customTitle = e.title;
|
|
2121
|
+
if (e.type === "ai-title") aiTitle = e.title;
|
|
3091
2122
|
}
|
|
3092
2123
|
if (isSplit) {
|
|
2124
|
+
const tailEntries = parseJSONL(tailSlice);
|
|
3093
2125
|
for (const e of tailEntries) {
|
|
3094
2126
|
if (e.type === "message" || e.type === "summary") {
|
|
3095
2127
|
messageCount++;
|
|
3096
2128
|
if (e.timestamp) lastTimestamp = e.timestamp;
|
|
3097
2129
|
}
|
|
3098
|
-
if (e.type === "custom-title")
|
|
2130
|
+
if (e.type === "custom-title") customTitle = e.title;
|
|
2131
|
+
if (e.type === "ai-title") aiTitle = e.title;
|
|
3099
2132
|
}
|
|
3100
2133
|
}
|
|
3101
2134
|
sessions.push({
|
|
3102
2135
|
sessionId,
|
|
3103
2136
|
createdAt: firstTimestamp ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
3104
2137
|
lastMessageAt: lastTimestamp ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
3105
|
-
title,
|
|
2138
|
+
title: customTitle ?? aiTitle,
|
|
2139
|
+
customTitle,
|
|
2140
|
+
aiTitle,
|
|
3106
2141
|
messageCount
|
|
3107
2142
|
});
|
|
3108
2143
|
} catch {
|
|
@@ -3402,17 +2437,17 @@ function walkAncestors(cwd) {
|
|
|
3402
2437
|
}
|
|
3403
2438
|
return dirs;
|
|
3404
2439
|
}
|
|
3405
|
-
async function tryLoadFile(fs2,
|
|
3406
|
-
if (isExcluded(
|
|
3407
|
-
const file = await loadContextFile(fs2,
|
|
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);
|
|
3408
2443
|
if (file) out.push(file);
|
|
3409
2444
|
}
|
|
3410
|
-
async function loadContextFile(fs2,
|
|
3411
|
-
const normalized = normalizePath(
|
|
2445
|
+
async function loadContextFile(fs2, path2, scope, visited, depth, maxDepth) {
|
|
2446
|
+
const normalized = normalizePath(path2);
|
|
3412
2447
|
if (visited.has(normalized)) return null;
|
|
3413
2448
|
let raw;
|
|
3414
2449
|
try {
|
|
3415
|
-
raw = await fs2.readFile(
|
|
2450
|
+
raw = await fs2.readFile(path2);
|
|
3416
2451
|
} catch {
|
|
3417
2452
|
return null;
|
|
3418
2453
|
}
|
|
@@ -3421,9 +2456,9 @@ async function loadContextFile(fs2, path6, scope, visited, depth, maxDepth) {
|
|
|
3421
2456
|
const { frontmatter, body } = parseFrontmatter(raw);
|
|
3422
2457
|
const globs = parsePaths(frontmatter.paths);
|
|
3423
2458
|
const content = stripHtmlComments(body);
|
|
3424
|
-
const includes = await resolveIncludes(fs2, content,
|
|
2459
|
+
const includes = await resolveIncludes(fs2, content, path2, visited, depth, maxDepth);
|
|
3425
2460
|
return {
|
|
3426
|
-
path:
|
|
2461
|
+
path: path2,
|
|
3427
2462
|
scope,
|
|
3428
2463
|
content,
|
|
3429
2464
|
...globs.length > 0 ? { globs } : {},
|
|
@@ -3489,15 +2524,15 @@ function stripHtmlComments(content) {
|
|
|
3489
2524
|
if (!content.includes("<!--")) return content;
|
|
3490
2525
|
return content.replace(/<!--[\s\S]*?-->/g, "");
|
|
3491
2526
|
}
|
|
3492
|
-
function isExcluded(
|
|
2527
|
+
function isExcluded(path2, excludes) {
|
|
3493
2528
|
if (excludes.length === 0) return false;
|
|
3494
2529
|
return excludes.some((pattern) => {
|
|
3495
|
-
if (
|
|
2530
|
+
if (path2 === pattern) return true;
|
|
3496
2531
|
if (pattern.includes("*")) {
|
|
3497
2532
|
const regex = simpleGlobToRegex(pattern);
|
|
3498
|
-
return regex.test(
|
|
2533
|
+
return regex.test(path2);
|
|
3499
2534
|
}
|
|
3500
|
-
return
|
|
2535
|
+
return path2.includes(pattern);
|
|
3501
2536
|
});
|
|
3502
2537
|
}
|
|
3503
2538
|
function simpleGlobToRegex(glob) {
|
|
@@ -6231,7 +5266,7 @@ async function executeToolsStep(toolCalls, streamingExec, streamingResults, exec
|
|
|
6231
5266
|
}
|
|
6232
5267
|
|
|
6233
5268
|
// src/file-state/cache.ts
|
|
6234
|
-
import { normalize as
|
|
5269
|
+
import { normalize as normalize2 } from "path";
|
|
6235
5270
|
var DEFAULT_MAX_ENTRIES = 100;
|
|
6236
5271
|
var DEFAULT_MAX_BYTES = 25 * 1024 * 1024;
|
|
6237
5272
|
var FileStateCache = class {
|
|
@@ -6243,14 +5278,14 @@ var FileStateCache = class {
|
|
|
6243
5278
|
this.maxEntries = config?.maxEntries ?? DEFAULT_MAX_ENTRIES;
|
|
6244
5279
|
this.maxBytes = config?.maxBytes ?? DEFAULT_MAX_BYTES;
|
|
6245
5280
|
}
|
|
6246
|
-
key(
|
|
6247
|
-
return
|
|
5281
|
+
key(path2) {
|
|
5282
|
+
return normalize2(path2);
|
|
6248
5283
|
}
|
|
6249
5284
|
byteSize(state) {
|
|
6250
5285
|
return Math.max(1, Buffer.byteLength(state.content, "utf8"));
|
|
6251
5286
|
}
|
|
6252
|
-
set(
|
|
6253
|
-
const k = this.key(
|
|
5287
|
+
set(path2, state) {
|
|
5288
|
+
const k = this.key(path2);
|
|
6254
5289
|
const existing = this.entries.get(k);
|
|
6255
5290
|
if (existing) {
|
|
6256
5291
|
this.currentBytes -= this.byteSize(existing);
|
|
@@ -6266,19 +5301,19 @@ var FileStateCache = class {
|
|
|
6266
5301
|
this.entries.set(k, state);
|
|
6267
5302
|
this.currentBytes += size;
|
|
6268
5303
|
}
|
|
6269
|
-
get(
|
|
6270
|
-
const k = this.key(
|
|
5304
|
+
get(path2) {
|
|
5305
|
+
const k = this.key(path2);
|
|
6271
5306
|
const state = this.entries.get(k);
|
|
6272
5307
|
if (!state) return void 0;
|
|
6273
5308
|
this.entries.delete(k);
|
|
6274
5309
|
this.entries.set(k, state);
|
|
6275
5310
|
return state;
|
|
6276
5311
|
}
|
|
6277
|
-
has(
|
|
6278
|
-
return this.entries.has(this.key(
|
|
5312
|
+
has(path2) {
|
|
5313
|
+
return this.entries.has(this.key(path2));
|
|
6279
5314
|
}
|
|
6280
|
-
delete(
|
|
6281
|
-
const k = this.key(
|
|
5315
|
+
delete(path2) {
|
|
5316
|
+
const k = this.key(path2);
|
|
6282
5317
|
const existing = this.entries.get(k);
|
|
6283
5318
|
if (existing) {
|
|
6284
5319
|
this.currentBytes -= this.byteSize(existing);
|
|
@@ -6320,8 +5355,8 @@ function getActiveSkills(allSkills, activatedNames) {
|
|
|
6320
5355
|
return activatedNames.has(skill.name);
|
|
6321
5356
|
});
|
|
6322
5357
|
}
|
|
6323
|
-
function matchesAnyGlob(
|
|
6324
|
-
return patterns.some((pattern) => globMatch(pattern,
|
|
5358
|
+
function matchesAnyGlob(path2, patterns) {
|
|
5359
|
+
return patterns.some((pattern) => globMatch(pattern, path2));
|
|
6325
5360
|
}
|
|
6326
5361
|
function globMatch(pattern, str) {
|
|
6327
5362
|
const regex = globToRegex(pattern);
|
|
@@ -6803,8 +5838,8 @@ var StreamingToolExecutor = class {
|
|
|
6803
5838
|
}
|
|
6804
5839
|
if (this.hasExecuting() && !this.hasCompleted()) {
|
|
6805
5840
|
const executingPromises = this.tools.filter((t) => t.status === "executing" && t.promise).map((t) => t.promise);
|
|
6806
|
-
const progressPromise = new Promise((
|
|
6807
|
-
this.progressResolve =
|
|
5841
|
+
const progressPromise = new Promise((resolve3) => {
|
|
5842
|
+
this.progressResolve = resolve3;
|
|
6808
5843
|
});
|
|
6809
5844
|
if (executingPromises.length > 0) {
|
|
6810
5845
|
await Promise.race([...executingPromises, progressPromise]);
|
|
@@ -6847,7 +5882,7 @@ function getRetryDelay(attempt, retryAfterHeader, maxDelayMs = 32e3, baseDelayMs
|
|
|
6847
5882
|
return baseDelay + jitter;
|
|
6848
5883
|
}
|
|
6849
5884
|
function sleep(ms, signal) {
|
|
6850
|
-
return new Promise((
|
|
5885
|
+
return new Promise((resolve3, reject) => {
|
|
6851
5886
|
if (signal?.aborted) {
|
|
6852
5887
|
reject(new DOMException("Aborted", "AbortError"));
|
|
6853
5888
|
return;
|
|
@@ -6858,7 +5893,7 @@ function sleep(ms, signal) {
|
|
|
6858
5893
|
};
|
|
6859
5894
|
const timer = setTimeout(() => {
|
|
6860
5895
|
signal?.removeEventListener("abort", onAbort);
|
|
6861
|
-
|
|
5896
|
+
resolve3();
|
|
6862
5897
|
}, ms);
|
|
6863
5898
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
6864
5899
|
});
|
|
@@ -8210,7 +7245,13 @@ var Thread = class {
|
|
|
8210
7245
|
this.config = config;
|
|
8211
7246
|
this.sessionId = opts?.sessionId ?? generateUUID();
|
|
8212
7247
|
this.cwd = opts?.cwd ?? "/";
|
|
8213
|
-
|
|
7248
|
+
const resolvedModel = opts?.model ?? config.model ?? config.provider.defaultModel;
|
|
7249
|
+
if (!resolvedModel) {
|
|
7250
|
+
throw new Error(
|
|
7251
|
+
"Thread: no model resolved. Pass `model` to Thread / Agent / preset options, set `config.model`, or provide a provider with a `defaultModel` (built-in providers expose one automatically)."
|
|
7252
|
+
);
|
|
7253
|
+
}
|
|
7254
|
+
this.model = resolvedModel;
|
|
8214
7255
|
this.storage = new SessionStorage(config.fs, config.sessionDir);
|
|
8215
7256
|
if (config.permissions) {
|
|
8216
7257
|
this.permissionContext = {
|
|
@@ -9135,6 +8176,14 @@ var Agent = class {
|
|
|
9135
8176
|
historySnipConfig;
|
|
9136
8177
|
outputFormat;
|
|
9137
8178
|
structuredOutputMode;
|
|
8179
|
+
autoTitleConfig;
|
|
8180
|
+
/**
|
|
8181
|
+
* Keyed by `${sessionId}:${force ? "force" : "normal"}`. Parallel calls
|
|
8182
|
+
* that pass the same `force` mode coalesce to one in-flight request; a
|
|
8183
|
+
* `force: true` call arriving during a normal in-flight request runs its
|
|
8184
|
+
* own pass so the caller's intent isn't silently dropped.
|
|
8185
|
+
*/
|
|
8186
|
+
autoTitleInFlight = /* @__PURE__ */ new Map();
|
|
9138
8187
|
providerPromise = null;
|
|
9139
8188
|
initPromise = null;
|
|
9140
8189
|
constructor(opts) {
|
|
@@ -9205,6 +8254,12 @@ var Agent = class {
|
|
|
9205
8254
|
this.historySnipConfig = opts.options?.historySnip;
|
|
9206
8255
|
this.outputFormat = opts.options?.outputFormat;
|
|
9207
8256
|
this.structuredOutputMode = opts.options?.structuredOutputMode;
|
|
8257
|
+
const autoTitleOpt = opts.options?.autoTitle;
|
|
8258
|
+
if (autoTitleOpt === true) {
|
|
8259
|
+
this.autoTitleConfig = { enabled: true };
|
|
8260
|
+
} else if (autoTitleOpt && typeof autoTitleOpt === "object") {
|
|
8261
|
+
this.autoTitleConfig = { enabled: true, ...autoTitleOpt };
|
|
8262
|
+
}
|
|
9208
8263
|
if (opts.options?.checkpoint?.enabled) {
|
|
9209
8264
|
this.checkpointManager = new FileCheckpointManager(
|
|
9210
8265
|
this.fs,
|
|
@@ -9216,7 +8271,7 @@ var Agent = class {
|
|
|
9216
8271
|
if (this.resolvedProvider) return this.resolvedProvider;
|
|
9217
8272
|
if (!this.providerPromise) {
|
|
9218
8273
|
this.providerPromise = (async () => {
|
|
9219
|
-
const { resolveProvider: resolveProvider2 } = await import("./resolve-
|
|
8274
|
+
const { resolveProvider: resolveProvider2 } = await import("./resolve-6KUZNEYW.js");
|
|
9220
8275
|
return resolveProvider2(this.providerInput, { model: this.model });
|
|
9221
8276
|
})();
|
|
9222
8277
|
}
|
|
@@ -9374,6 +8429,92 @@ var Agent = class {
|
|
|
9374
8429
|
async listSessions() {
|
|
9375
8430
|
return this.storage.listSessions();
|
|
9376
8431
|
}
|
|
8432
|
+
/**
|
|
8433
|
+
* Load the message history for a stored session, respecting compact
|
|
8434
|
+
* boundaries and history snips. Returns `[]` when the session does
|
|
8435
|
+
* not exist. Use this to rehydrate a UI — resuming a Thread for
|
|
8436
|
+
* further execution should go through `resumeThread()` instead.
|
|
8437
|
+
*/
|
|
8438
|
+
async getMessages(sessionId) {
|
|
8439
|
+
return this.storage.loadMessages(sessionId);
|
|
8440
|
+
}
|
|
8441
|
+
/**
|
|
8442
|
+
* Persist a user-set title for a session. Takes precedence over any
|
|
8443
|
+
* AI-generated title on read. No-op on empty / whitespace-only input.
|
|
8444
|
+
*/
|
|
8445
|
+
async setCustomTitle(sessionId, title) {
|
|
8446
|
+
const trimmed = title.trim();
|
|
8447
|
+
if (!trimmed) return;
|
|
8448
|
+
await this.storage.appendCustomTitle(sessionId, trimmed);
|
|
8449
|
+
}
|
|
8450
|
+
/**
|
|
8451
|
+
* Persist an AI-generated title for a session. A user-set title
|
|
8452
|
+
* (see `setCustomTitle`) always wins on read, so writing this after a
|
|
8453
|
+
* user has renamed the session is harmless.
|
|
8454
|
+
*/
|
|
8455
|
+
async setAiTitle(sessionId, title) {
|
|
8456
|
+
const trimmed = title.trim();
|
|
8457
|
+
if (!trimmed) return;
|
|
8458
|
+
await this.storage.appendAiTitle(sessionId, trimmed);
|
|
8459
|
+
}
|
|
8460
|
+
/**
|
|
8461
|
+
* Delete a session's persisted transcript. Does not affect any
|
|
8462
|
+
* currently-running `Thread` reading from the same session id.
|
|
8463
|
+
*/
|
|
8464
|
+
async deleteSession(sessionId) {
|
|
8465
|
+
await this.storage.deleteSession(sessionId);
|
|
8466
|
+
}
|
|
8467
|
+
/**
|
|
8468
|
+
* Return the currently persisted titles for a session.
|
|
8469
|
+
* `title` reflects the display preference (custom > ai).
|
|
8470
|
+
*/
|
|
8471
|
+
async getSessionTitles(sessionId) {
|
|
8472
|
+
return this.storage.getSessionTitles(sessionId);
|
|
8473
|
+
}
|
|
8474
|
+
/**
|
|
8475
|
+
* When `autoTitle` is enabled and the session has no title yet,
|
|
8476
|
+
* generate one via the configured provider + model and persist it
|
|
8477
|
+
* as an `ai-title` entry. Returns the new title on success, or `null`
|
|
8478
|
+
* when skipped (feature disabled, title already present, empty seed,
|
|
8479
|
+
* or provider error).
|
|
8480
|
+
*
|
|
8481
|
+
* Concurrency-safe: parallel calls for the same session coalesce to
|
|
8482
|
+
* a single in-flight request.
|
|
8483
|
+
*/
|
|
8484
|
+
async autoTitleIfMissing(sessionId, opts) {
|
|
8485
|
+
const cfg = this.autoTitleConfig;
|
|
8486
|
+
if (!cfg?.enabled) return null;
|
|
8487
|
+
const force = opts?.force === true;
|
|
8488
|
+
const key = `${sessionId}:${force ? "force" : "normal"}`;
|
|
8489
|
+
const existing = this.autoTitleInFlight.get(key);
|
|
8490
|
+
if (existing) return existing;
|
|
8491
|
+
const task = (async () => {
|
|
8492
|
+
try {
|
|
8493
|
+
if (!force) {
|
|
8494
|
+
const titles = await this.storage.getSessionTitles(sessionId);
|
|
8495
|
+
if (titles.customTitle || titles.aiTitle) return null;
|
|
8496
|
+
}
|
|
8497
|
+
await this.ensureProvider();
|
|
8498
|
+
const provider = cfg.provider ?? this.getProvider();
|
|
8499
|
+
const messages = await this.storage.loadMessages(sessionId);
|
|
8500
|
+
if (messages.length === 0) return null;
|
|
8501
|
+
const title = await generateAutoTitle(messages, {
|
|
8502
|
+
provider,
|
|
8503
|
+
model: cfg.model ?? provider.defaultModel ?? this.model,
|
|
8504
|
+
systemPrompt: cfg.systemPrompt ?? DEFAULT_AUTO_TITLE_SYSTEM_PROMPT,
|
|
8505
|
+
maxInputChars: cfg.maxInputChars ?? DEFAULT_AUTO_TITLE_MAX_INPUT_CHARS,
|
|
8506
|
+
signal: opts?.signal
|
|
8507
|
+
});
|
|
8508
|
+
if (!title) return null;
|
|
8509
|
+
await this.storage.appendAiTitle(sessionId, title);
|
|
8510
|
+
return title;
|
|
8511
|
+
} finally {
|
|
8512
|
+
this.autoTitleInFlight.delete(key);
|
|
8513
|
+
}
|
|
8514
|
+
})();
|
|
8515
|
+
this.autoTitleInFlight.set(key, task);
|
|
8516
|
+
return task;
|
|
8517
|
+
}
|
|
9377
8518
|
getCostSummary() {
|
|
9378
8519
|
return this.costTracker?.getSummary() ?? null;
|
|
9379
8520
|
}
|
|
@@ -9619,7 +8760,8 @@ function codingAgent(opts) {
|
|
|
9619
8760
|
costTracking: { enabled: true },
|
|
9620
8761
|
retry: true,
|
|
9621
8762
|
hooks: opts.hooks,
|
|
9622
|
-
mcpServers: opts.mcpServers
|
|
8763
|
+
mcpServers: opts.mcpServers,
|
|
8764
|
+
autoTitle: opts.autoTitle
|
|
9623
8765
|
}
|
|
9624
8766
|
});
|
|
9625
8767
|
}
|
|
@@ -9641,7 +8783,8 @@ function planningAgent(opts) {
|
|
|
9641
8783
|
costTracking: { enabled: true },
|
|
9642
8784
|
retry: true,
|
|
9643
8785
|
hooks: opts.hooks,
|
|
9644
|
-
mcpServers: opts.mcpServers
|
|
8786
|
+
mcpServers: opts.mcpServers,
|
|
8787
|
+
autoTitle: opts.autoTitle
|
|
9645
8788
|
}
|
|
9646
8789
|
});
|
|
9647
8790
|
}
|
|
@@ -9664,6 +8807,7 @@ function reviewAgent(opts) {
|
|
|
9664
8807
|
retry: true,
|
|
9665
8808
|
hooks: opts.hooks,
|
|
9666
8809
|
mcpServers: opts.mcpServers,
|
|
8810
|
+
autoTitle: opts.autoTitle,
|
|
9667
8811
|
webSearch: {
|
|
9668
8812
|
search: async (query) => {
|
|
9669
8813
|
try {
|
|
@@ -10161,18 +9305,18 @@ var FileMemoryProvider = class {
|
|
|
10161
9305
|
return "";
|
|
10162
9306
|
}
|
|
10163
9307
|
}
|
|
10164
|
-
async loadEntry(
|
|
10165
|
-
const fullPath =
|
|
9308
|
+
async loadEntry(path2) {
|
|
9309
|
+
const fullPath = path2.startsWith(this.dir) ? path2 : this.dir + path2;
|
|
10166
9310
|
try {
|
|
10167
9311
|
const raw = await this.fs.readFile(fullPath);
|
|
10168
9312
|
const fm = parseFrontmatter2(raw);
|
|
10169
9313
|
const stat2 = await this.fs.stat(fullPath).catch(() => null);
|
|
10170
9314
|
return {
|
|
10171
|
-
name: fm.name ?? pathToName(
|
|
9315
|
+
name: fm.name ?? pathToName(path2),
|
|
10172
9316
|
description: fm.description ?? "",
|
|
10173
9317
|
type: fm.type ?? "project",
|
|
10174
9318
|
content: fm.rest,
|
|
10175
|
-
path:
|
|
9319
|
+
path: path2.startsWith(this.dir) ? path2.slice(this.dir.length) : path2,
|
|
10176
9320
|
updatedAt: stat2?.modifiedAt?.toISOString()
|
|
10177
9321
|
};
|
|
10178
9322
|
} catch {
|
|
@@ -10187,8 +9331,8 @@ var FileMemoryProvider = class {
|
|
|
10187
9331
|
await this.fs.writeFile(fullPath, content);
|
|
10188
9332
|
await this.rebuildIndex();
|
|
10189
9333
|
}
|
|
10190
|
-
async removeEntry(
|
|
10191
|
-
const fullPath =
|
|
9334
|
+
async removeEntry(path2) {
|
|
9335
|
+
const fullPath = path2.startsWith(this.dir) ? path2 : this.dir + path2;
|
|
10192
9336
|
try {
|
|
10193
9337
|
await this.fs.deleteFile(fullPath);
|
|
10194
9338
|
} catch {
|
|
@@ -10237,20 +9381,14 @@ export {
|
|
|
10237
9381
|
LocalFs,
|
|
10238
9382
|
LocalComputer,
|
|
10239
9383
|
SandboxedLocalComputer,
|
|
10240
|
-
SpritesFs,
|
|
10241
|
-
SpritesComputer,
|
|
10242
|
-
DockerFs,
|
|
10243
|
-
DockerComputer,
|
|
10244
|
-
E2BFs,
|
|
10245
|
-
E2BComputer,
|
|
10246
|
-
FreestyleFs,
|
|
10247
|
-
FreestyleComputer,
|
|
10248
9384
|
UnsandboxedLocal,
|
|
10249
9385
|
LocalSandbox,
|
|
10250
|
-
|
|
10251
|
-
|
|
10252
|
-
|
|
10253
|
-
|
|
9386
|
+
DEFAULT_AUTO_TITLE_MAX_INPUT_CHARS,
|
|
9387
|
+
DEFAULT_AUTO_TITLE_SYSTEM_PROMPT,
|
|
9388
|
+
extractTitleSeedText,
|
|
9389
|
+
extractTitleFromResponse,
|
|
9390
|
+
generateAutoTitle,
|
|
9391
|
+
normalizeTitle,
|
|
10254
9392
|
createCheckpointState,
|
|
10255
9393
|
FileCheckpointManager,
|
|
10256
9394
|
runPreToolUseHooks,
|
|
@@ -10278,6 +9416,7 @@ export {
|
|
|
10278
9416
|
findModelPricing,
|
|
10279
9417
|
calculateCost,
|
|
10280
9418
|
CostTracker,
|
|
9419
|
+
SessionStorage,
|
|
10281
9420
|
TaskStore,
|
|
10282
9421
|
buildProjectContextSection,
|
|
10283
9422
|
parseFrontmatter,
|
|
@@ -10362,4 +9501,4 @@ export {
|
|
|
10362
9501
|
truncateIndex,
|
|
10363
9502
|
FileMemoryProvider
|
|
10364
9503
|
};
|
|
10365
|
-
//# sourceMappingURL=chunk-
|
|
9504
|
+
//# sourceMappingURL=chunk-6MMYCGJQ.js.map
|