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.
Files changed (73) hide show
  1. package/README.md +63 -8
  2. package/dist/a2a/index.d.ts +6 -4
  3. package/dist/acp/index.d.ts +7 -5
  4. package/dist/{agent-1nFVUP9E.d.ts → agent-DWE4_P5X.d.ts} +169 -485
  5. package/dist/{cache-DsRqxx6v.d.ts → cache-BlBwXXPS.d.ts} +1 -1
  6. package/dist/{chunk-4HW6LN6D.js → chunk-6MMYCGJQ.js} +366 -1227
  7. package/dist/chunk-6MMYCGJQ.js.map +1 -0
  8. package/dist/{chunk-5JN4SPI7.js → chunk-7IQCQI2G.js} +1 -1
  9. package/dist/{chunk-L3L3FG5T.js → chunk-CCM2AXZG.js} +1 -1
  10. package/dist/{chunk-L3L3FG5T.js.map → chunk-CCM2AXZG.js.map} +1 -1
  11. package/dist/{chunk-CS6WNDCF.js → chunk-I3JTUFPK.js} +2 -2
  12. package/dist/chunk-I3JTUFPK.js.map +1 -0
  13. package/dist/chunk-I5SBSOS6.js +40 -0
  14. package/dist/chunk-I5SBSOS6.js.map +1 -0
  15. package/dist/{chunk-HL6JCRZJ.js → chunk-XZN4QZLK.js} +4 -4
  16. package/dist/{chunk-EKOGVTBT.js → chunk-ZXSDKBYB.js} +4 -2
  17. package/dist/chunk-ZXSDKBYB.js.map +1 -0
  18. package/dist/cli/index.js +11 -11
  19. package/dist/client/index.d.ts +1 -1
  20. package/dist/computer-BPdxSo6X.d.ts +88 -0
  21. package/dist/docker.d.ts +129 -0
  22. package/dist/docker.js +401 -0
  23. package/dist/docker.js.map +1 -0
  24. package/dist/e2b.d.ts +157 -0
  25. package/dist/e2b.js +202 -0
  26. package/dist/e2b.js.map +1 -0
  27. package/dist/freestyle.d.ts +174 -0
  28. package/dist/freestyle.js +240 -0
  29. package/dist/freestyle.js.map +1 -0
  30. package/dist/index.d.ts +19 -204
  31. package/dist/index.js +38 -48
  32. package/dist/lsp/index.d.ts +4 -3
  33. package/dist/mcp/index.d.ts +5 -4
  34. package/dist/mcp/index.js +2 -2
  35. package/dist/{provider-factory-KCLIF34X.js → provider-factory-TUHU3DIG.js} +2 -2
  36. package/dist/providers/anthropic.d.ts +3 -3
  37. package/dist/providers/anthropic.js +4 -3
  38. package/dist/providers/anthropic.js.map +1 -1
  39. package/dist/providers/bedrock.d.ts +3 -3
  40. package/dist/providers/bedrock.js +2 -2
  41. package/dist/providers/bedrock.js.map +1 -1
  42. package/dist/providers/gemini.d.ts +2 -2
  43. package/dist/providers/gemini.js +1 -1
  44. package/dist/providers/gemini.js.map +1 -1
  45. package/dist/providers/ollama.d.ts +1 -1
  46. package/dist/providers/ollama.js +2 -2
  47. package/dist/providers/openai.d.ts +2 -2
  48. package/dist/providers/openai.js +2 -2
  49. package/dist/providers/openrouter.d.ts +1 -1
  50. package/dist/providers/openrouter.js +2 -2
  51. package/dist/providers/vertex.d.ts +3 -3
  52. package/dist/providers/vertex.js +4 -3
  53. package/dist/providers/vertex.js.map +1 -1
  54. package/dist/{resolve-4JA2BBDA.js → resolve-6KUZNEYW.js} +2 -2
  55. package/dist/sandbox-9qeMTNrD.d.ts +126 -0
  56. package/dist/server/index.d.ts +6 -4
  57. package/dist/{server-CHMxuWKq.d.ts → server-BzNGKTP6.d.ts} +1 -1
  58. package/dist/sprites.d.ts +136 -0
  59. package/dist/sprites.js +334 -0
  60. package/dist/sprites.js.map +1 -0
  61. package/dist/ssh.d.ts +187 -0
  62. package/dist/ssh.js +392 -0
  63. package/dist/ssh.js.map +1 -0
  64. package/dist/{types-RPKUTu1k.d.ts → types-DhXwOQwD.d.ts} +3 -89
  65. package/dist/{types-LrU4LRmX.d.ts → types-kiGBF35b.d.ts} +40 -2
  66. package/package.json +25 -1
  67. package/dist/chunk-4HW6LN6D.js.map +0 -1
  68. package/dist/chunk-CS6WNDCF.js.map +0 -1
  69. package/dist/chunk-EKOGVTBT.js.map +0 -1
  70. /package/dist/{chunk-5JN4SPI7.js.map → chunk-7IQCQI2G.js.map} +0 -0
  71. /package/dist/{chunk-HL6JCRZJ.js.map → chunk-XZN4QZLK.js.map} +0 -0
  72. /package/dist/{provider-factory-KCLIF34X.js.map → provider-factory-TUHU3DIG.js.map} +0 -0
  73. /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-L3L3FG5T.js";
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((resolve7) => {
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
- resolve7({
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((resolve7) => {
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(() => resolve7(result)).catch(() => resolve7(result));
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
- function SpritesSandbox(opts) {
1125
- const baseURL = (opts.baseURL ?? "https://api.sprites.dev").replace(/\/$/, "");
1126
- const userProvidedName = opts.spriteName;
1127
- if (userProvidedName) {
1128
- const fsOpts = { ...opts, spriteName: userProvidedName };
1129
- return {
1130
- fs: new SpritesFs(fsOpts),
1131
- computer: new SpritesComputer(fsOpts),
1132
- sandboxId: () => userProvidedName
1133
- };
1134
- }
1135
- const fsProxy = createFsProxy();
1136
- const computerProxy = createComputerProxy();
1137
- let resolvedName;
1138
- let autoCreated = false;
1139
- let initPromise = null;
1140
- async function doInit(reconnectId) {
1141
- let name = reconnectId ?? `${opts.namePrefix ?? "noumen-"}${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
1142
- let needsCreate = !reconnectId;
1143
- if (reconnectId) {
1144
- const check = await fetch(`${baseURL}/v1/sprites/${reconnectId}`, {
1145
- method: "GET",
1146
- headers: { Authorization: `Bearer ${opts.token}` }
1147
- });
1148
- if (!check.ok) {
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
- resolvedName = name;
1168
- const childOpts = { ...opts, spriteName: name };
1169
- fsProxy.setTarget(new SpritesFs(childOpts));
1170
- computerProxy.setTarget(new SpritesComputer(childOpts));
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
- if (!opts.image) {
1218
- throw new Error("DockerSandbox requires either `container` or `image`");
1219
- }
1220
- const fsProxy = createFsProxy();
1221
- const computerProxy = createComputerProxy();
1222
- let containerId;
1223
- let containerRef;
1224
- let autoCreated = false;
1225
- let initPromise = null;
1226
- async function doInit(reconnectId) {
1227
- const Docker = (await import("dockerode")).default;
1228
- const docker = new Docker();
1229
- let container;
1230
- if (reconnectId) {
1231
- container = docker.getContainer(reconnectId);
1232
- try {
1233
- await container.inspect();
1234
- } catch {
1235
- container = await docker.createContainer({
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 E2BSandbox(opts) {
1296
- if (opts.sandbox) {
1297
- const s = opts.sandbox;
1298
- return {
1299
- fs: new E2BFs({ sandbox: s, workingDir: opts.cwd }),
1300
- computer: new E2BComputer({
1301
- sandbox: s,
1302
- defaultCwd: opts.cwd,
1303
- defaultTimeout: opts.defaultTimeout
1304
- }),
1305
- sandboxId: () => s.sandboxId
1306
- };
1307
- }
1308
- const fsProxy = createFsProxy();
1309
- const computerProxy = createComputerProxy();
1310
- let resolvedId;
1311
- let sandboxRef;
1312
- let autoCreated = false;
1313
- let initPromise = null;
1314
- async function doInit(reconnectId) {
1315
- const e2b = await import("e2b");
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
- async dispose() {
1365
- if (initPromise) {
1366
- await initPromise.catch(() => {
1367
- });
1368
- }
1369
- if (!autoCreated || !sandboxRef) return;
1370
- if (typeof sandboxRef.kill === "function") {
1371
- await sandboxRef.kill();
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
- const fsProxy = createFsProxy();
1390
- const computerProxy = createComputerProxy();
1391
- let resolvedId;
1392
- let vmRef = null;
1393
- let autoCreated = false;
1394
- let initPromise = null;
1395
- async function doInit(reconnectId) {
1396
- const mod = await import("freestyle-sandboxes");
1397
- const freestyle = mod.freestyle ?? mod.default?.freestyle;
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
- return {
1442
- fs: fsProxy,
1443
- computer: computerProxy,
1444
- sandboxId: () => resolvedId,
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 path6 of Object.keys(snap.trackedFileBackups)) {
1708
- trackedFiles.add(path6);
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((resolve7, reject) => {
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
- resolve7(v);
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
- * Re-append custom-title and key metadata entries after a compact boundary
2967
- * so they remain discoverable in the active-entries window.
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
- await this.appendEntry(sessionId, {
1980
+ batch.push({
2983
1981
  type: "custom-title",
2984
1982
  sessionId,
2985
1983
  title: customTitle,
2986
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
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
- await this.appendMetadata(sessionId, key, value);
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 path6 = this.getTranscriptPath(sessionId);
2995
- const exists = await this.fs.exists(path6);
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(path6);
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 path6 = this.getTranscriptPath(sessionId);
3033
- const exists = await this.fs.exists(path6);
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(path6);
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
- const tailEntries = isSplit ? parseJSONL(tailSlice) : headEntries;
3080
- let title;
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") title = e.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") title = e.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, path6, scope, out, maxDepth, excludes) {
3406
- if (isExcluded(path6, excludes)) return;
3407
- const file = await loadContextFile(fs2, path6, scope, /* @__PURE__ */ new Set(), 0, maxDepth);
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, path6, scope, visited, depth, maxDepth) {
3411
- const normalized = normalizePath(path6);
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(path6);
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, path6, visited, depth, maxDepth);
2459
+ const includes = await resolveIncludes(fs2, content, path2, visited, depth, maxDepth);
3425
2460
  return {
3426
- path: path6,
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(path6, excludes) {
2527
+ function isExcluded(path2, excludes) {
3493
2528
  if (excludes.length === 0) return false;
3494
2529
  return excludes.some((pattern) => {
3495
- if (path6 === pattern) return true;
2530
+ if (path2 === pattern) return true;
3496
2531
  if (pattern.includes("*")) {
3497
2532
  const regex = simpleGlobToRegex(pattern);
3498
- return regex.test(path6);
2533
+ return regex.test(path2);
3499
2534
  }
3500
- return path6.includes(pattern);
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 normalize4 } from "path";
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(path6) {
6247
- return normalize4(path6);
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(path6, state) {
6253
- const k = this.key(path6);
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(path6) {
6270
- const k = this.key(path6);
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(path6) {
6278
- return this.entries.has(this.key(path6));
5312
+ has(path2) {
5313
+ return this.entries.has(this.key(path2));
6279
5314
  }
6280
- delete(path6) {
6281
- const k = this.key(path6);
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(path6, patterns) {
6324
- return patterns.some((pattern) => globMatch(pattern, path6));
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((resolve7) => {
6807
- this.progressResolve = resolve7;
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((resolve7, reject) => {
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
- resolve7();
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
- this.model = opts?.model ?? config.model ?? "gpt-5.4";
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-4JA2BBDA.js");
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(path6) {
10165
- const fullPath = path6.startsWith(this.dir) ? path6 : this.dir + path6;
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(path6),
9315
+ name: fm.name ?? pathToName(path2),
10172
9316
  description: fm.description ?? "",
10173
9317
  type: fm.type ?? "project",
10174
9318
  content: fm.rest,
10175
- path: path6.startsWith(this.dir) ? path6.slice(this.dir.length) : path6,
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(path6) {
10191
- const fullPath = path6.startsWith(this.dir) ? path6 : this.dir + path6;
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
- SpritesSandbox,
10251
- DockerSandbox,
10252
- E2BSandbox,
10253
- FreestyleSandbox,
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-4HW6LN6D.js.map
9504
+ //# sourceMappingURL=chunk-6MMYCGJQ.js.map