stitchkit 0.114.2 → 0.116.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 (115) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/dist/agent-runtime/coding-tool-files.d.ts.map +1 -1
  3. package/dist/agent-runtime/coding-tool-listing.d.ts.map +1 -1
  4. package/dist/agent-runtime/coding-tool-paths.d.ts.map +1 -1
  5. package/dist/agent-runtime/coding-tool-refusals.d.ts +6 -0
  6. package/dist/agent-runtime/coding-tool-refusals.d.ts.map +1 -1
  7. package/dist/agent-runtime/coding-tool-search-patch.d.ts.map +1 -1
  8. package/dist/agent-runtime/contained-scan.d.ts +1 -1
  9. package/dist/agent-runtime/contained-scan.d.ts.map +1 -1
  10. package/dist/agent-runtime/sandbox-broker.d.ts.map +1 -1
  11. package/dist/agent-runtime/sandbox-bubblewrap-storage.d.ts.map +1 -1
  12. package/dist/application/directory-inbox-files.d.ts.map +1 -1
  13. package/dist/application/file-state-store.d.ts.map +1 -1
  14. package/dist/entrypoints/agent-runtime/coding-tools.js +6 -4
  15. package/dist/entrypoints/agent-runtime/harness.js +2 -1
  16. package/dist/entrypoints/agent-runtime/sandbox.js +13 -8
  17. package/dist/entrypoints/agent-runtime/sqlite/bun.js +8 -6
  18. package/dist/entrypoints/agent-runtime/sqlite/node.js +8 -6
  19. package/dist/entrypoints/agent-runtime/testing.js +8 -6
  20. package/dist/entrypoints/agent-runtime.js +8 -6
  21. package/dist/entrypoints/application/diagnostic-journal.js +2 -2
  22. package/dist/entrypoints/application/directory-inbox.js +10 -7
  23. package/dist/entrypoints/cli/publish.js +11 -8
  24. package/dist/entrypoints/cli.js +6 -3
  25. package/dist/entrypoints/files.js +10 -7
  26. package/dist/entrypoints/process/owner-loss.js +1 -1
  27. package/dist/entrypoints/process.js +3 -3
  28. package/dist/entrypoints/react/keyboard/testing.js +1 -1
  29. package/dist/entrypoints/react/keyboard.js +11 -11
  30. package/dist/entrypoints/server.js +4 -4
  31. package/dist/entrypoints/telegram/grammy.js +2 -2
  32. package/dist/entrypoints/telegram.d.ts +1 -1
  33. package/dist/entrypoints/telegram.d.ts.map +1 -1
  34. package/dist/entrypoints/telegram.js +30 -14
  35. package/dist/entrypoints/tools.js +5 -2
  36. package/dist/files/chunk-spool.d.ts.map +1 -1
  37. package/dist/{index-2f9r0jgr.js → index-364k2zwa.js} +4 -4
  38. package/dist/{index-dc9ere5p.js → index-4g6qmgdx.js} +1 -1
  39. package/dist/index-6f49krb5.js +107 -0
  40. package/dist/{index-912sza5h.js → index-90p34rts.js} +1 -1
  41. package/dist/{index-07dcf2yn.js → index-ac6vvm30.js} +5 -2
  42. package/dist/{index-zrtrjkbf.js → index-arjmb6de.js} +183 -172
  43. package/dist/{index-7r4knwf8.js → index-dr3t5n1v.js} +14 -4
  44. package/dist/{index-84bf5spd.js → index-f6m4c545.js} +5 -2
  45. package/dist/{index-qj1sy4ep.js → index-fa435yq2.js} +1 -1
  46. package/dist/{index-hy802jnr.js → index-fdk4r4r6.js} +6 -3
  47. package/dist/index-jj9228tt.js +6 -0
  48. package/dist/{index-a3462dqf.js → index-jq2ky8fw.js} +1 -1
  49. package/dist/{index-s47ac8zt.js → index-n5sv4z0w.js} +142 -156
  50. package/dist/{index-bh2ht9y5.js → index-n8z3rvzw.js} +4 -1
  51. package/dist/index-q968hy6y.js +100 -0
  52. package/dist/{index-j4zh647a.js → index-tt30h7mr.js} +4 -1
  53. package/dist/{index-tnxsq7cw.js → index-w7jq3fz9.js} +113 -38
  54. package/dist/{index-p02zgk8x.js → index-y4rxnvgp.js} +6 -0
  55. package/dist/{index-5v5a9k2n.js → index-z3d6smer.js} +1 -1
  56. package/dist/{index-6b3pcq5z.js → index-zqkz4af9.js} +1 -1
  57. package/dist/internal/atomic-file.d.ts.map +1 -1
  58. package/dist/internal/atomic-staging.d.ts.map +1 -1
  59. package/dist/internal/contained-directory.d.ts +20 -0
  60. package/dist/internal/contained-directory.d.ts.map +1 -0
  61. package/dist/{agent-runtime → internal}/contained-files.d.ts +24 -13
  62. package/dist/internal/contained-files.d.ts.map +1 -0
  63. package/dist/internal/contained-handle.d.ts +30 -0
  64. package/dist/internal/contained-handle.d.ts.map +1 -0
  65. package/dist/internal/contained-write.d.ts +12 -0
  66. package/dist/internal/contained-write.d.ts.map +1 -0
  67. package/dist/internal/darwin-binding.d.ts +1 -1
  68. package/dist/internal/darwin-binding.d.ts.map +1 -1
  69. package/dist/internal/errno.d.ts +3 -0
  70. package/dist/internal/errno.d.ts.map +1 -0
  71. package/dist/internal/process-instance.d.ts.map +1 -1
  72. package/dist/internal/write-download.d.ts +5 -14
  73. package/dist/internal/write-download.d.ts.map +1 -1
  74. package/dist/process/group-diagnostics.d.ts +1 -0
  75. package/dist/process/group-diagnostics.d.ts.map +1 -1
  76. package/dist/process/group-settlement.d.ts +2 -0
  77. package/dist/process/group-settlement.d.ts.map +1 -1
  78. package/dist/process/group.d.ts.map +1 -1
  79. package/dist/process/owner-loss-control.d.ts.map +1 -1
  80. package/dist/react/keyboard/hooks.d.ts +2 -2
  81. package/dist/react/keyboard/hooks.d.ts.map +1 -1
  82. package/dist/react/keyboard/layers.d.ts +2 -0
  83. package/dist/react/keyboard/layers.d.ts.map +1 -1
  84. package/dist/telegram/broadcast-state.d.ts.map +1 -1
  85. package/dist/telegram/budget-transformer.d.ts +5 -2
  86. package/dist/telegram/budget-transformer.d.ts.map +1 -1
  87. package/dist/telegram/outbound-budget.d.ts +26 -11
  88. package/dist/telegram/outbound-budget.d.ts.map +1 -1
  89. package/dist/tools/cli/download.d.ts.map +1 -1
  90. package/dist/tools/cli/publication-storage.d.ts.map +1 -1
  91. package/dist/tools/cli/publication.d.ts.map +1 -1
  92. package/dist/tools/cli/view.d.ts +7 -1
  93. package/dist/tools/cli/view.d.ts.map +1 -1
  94. package/llms/agent-runtime.txt +16 -6
  95. package/llms/cli.txt +37 -3
  96. package/llms/process.txt +2 -2
  97. package/llms/react-keyboard.txt +18 -2
  98. package/llms/stitchkit.txt +3 -3
  99. package/llms/telegram.txt +10 -3
  100. package/llms/{upgrading-0.104.0-0.114.0.txt → upgrading-0.105.0-0.116.0.txt} +85 -124
  101. package/llms/{upgrading-0.44.0-0.61.0.txt → upgrading-0.44.0-0.62.0.txt} +120 -3
  102. package/llms/{upgrading-0.62.0-0.78.0.txt → upgrading-0.63.0-0.83.0.txt} +181 -120
  103. package/llms/{upgrading-0.79.0-0.103.13.txt → upgrading-0.84.0-0.104.0.txt} +124 -181
  104. package/llms/upgrading.txt +4 -4
  105. package/llms-full.txt +168 -19
  106. package/llms.txt +9 -9
  107. package/native/darwin-arm64.node +0 -0
  108. package/native/darwin-x64.node +0 -0
  109. package/native-assets.json +4 -4
  110. package/package.json +1 -1
  111. package/dist/agent-runtime/contained-darwin.d.ts +0 -21
  112. package/dist/agent-runtime/contained-darwin.d.ts.map +0 -1
  113. package/dist/agent-runtime/contained-files.d.ts.map +0 -1
  114. package/dist/agent-runtime/contained-write.d.ts +0 -9
  115. package/dist/agent-runtime/contained-write.d.ts.map +0 -1
@@ -1,33 +1,36 @@
1
- import {
2
- loadDarwinBinding
3
- } from "./index-2kbysk9s.js";
4
1
  import {
5
2
  NativeCommandError2
6
- } from "./index-84bf5spd.js";
3
+ } from "./index-f6m4c545.js";
7
4
  import {
8
5
  CommandStopped,
9
6
  startNativeCommand
10
- } from "./index-7r4knwf8.js";
7
+ } from "./index-dr3t5n1v.js";
8
+ import {
9
+ scanContainedFiles
10
+ } from "./index-6f49krb5.js";
11
+ import {
12
+ writeContainedFile
13
+ } from "./index-q968hy6y.js";
11
14
  import {
12
- NumericFileHandle,
13
- descriptorPath,
14
15
  listContainedDirectory,
15
16
  missingContainedDirectories,
17
+ misspelledContainedSegment,
18
+ UnapprovedContainedDirectoryError,
16
19
  openContainedParent,
17
20
  openContainedFile,
21
+ ContainedPathChangedError,
18
22
  assertContainedFileCurrent,
19
23
  assertContainedParentCurrent,
20
24
  containedEntryMetadata,
21
25
  openContainedParentFile,
22
- readContainedUtf8Handle,
23
- scanContainedFiles
24
- } from "./index-zrtrjkbf.js";
26
+ readContainedUtf8Handle
27
+ } from "./index-arjmb6de.js";
25
28
  import {
26
29
  defineRuntimeTool2
27
30
  } from "./index-ega3yz4c.js";
28
31
  import {
29
- stagingName
30
- } from "./index-j4zh647a.js";
32
+ errnoOf
33
+ } from "./index-jj9228tt.js";
31
34
  import {
32
35
  AppError2,
33
36
  forbidden2,
@@ -250,7 +253,7 @@ async function recordAgentSandboxProbe2(input) {
250
253
  }
251
254
 
252
255
  // src/agent-runtime/coding-tools.ts
253
- import path5 from "node:path";
256
+ import path4 from "node:path";
254
257
  import { z as z5 } from "zod";
255
258
 
256
259
  // src/agent-runtime/coding-tool-files.ts
@@ -278,7 +281,8 @@ function codingPathRefusal(code, message, path, options = {}) {
278
281
  });
279
282
  }
280
283
  function refuseMissingCodingPath(error, relative) {
281
- const code = error?.code;
284
+ refuseChangedPath(error, relative);
285
+ const code = errnoOf(error);
282
286
  if (error instanceof Error && error.message.includes("is not a regular file")) {
283
287
  codingPathRefusal("BAD_REQUEST", "This path is not a regular file", relative, {
284
288
  hint: "Use list_directory to see what is here."
@@ -299,13 +303,55 @@ function refuseMissingCodingPath(error, relative) {
299
303
  hint: "Read or write the target directly, by its own workspace-relative path."
300
304
  });
301
305
  }
302
- if (code === "EISDIR") {
306
+ if (code === "EISDIR" || code === "ENXIO" || code === "EOPNOTSUPP") {
303
307
  codingPathRefusal("BAD_REQUEST", "This path is not a regular file", relative, {
304
308
  hint: "Use list_directory to see what is here."
305
309
  });
306
310
  }
307
311
  throw error;
308
312
  }
313
+ function refuseChangedPath(error, relative) {
314
+ if (error instanceof ContainedPathChangedError) {
315
+ codingPathRefusal("CONFLICT", "This path changed while it was being authorized", relative, {
316
+ hint: "Read it again and retry."
317
+ });
318
+ }
319
+ }
320
+ function refuseWriteConflict(error, relative, details = {}) {
321
+ refuseChangedPath(error, relative);
322
+ const code = errnoOf(error);
323
+ if (code === "ENOENT") {
324
+ codingPathRefusal("CONFLICT", "This path changed while it was being written", relative, {
325
+ details,
326
+ hint: "Read it again and retry."
327
+ });
328
+ }
329
+ if (code === "ENOTDIR") {
330
+ codingPathRefusal("CONFLICT", "A segment of this path is not a directory", relative, {
331
+ details,
332
+ hint: "Write under a different path, or remove the file blocking it."
333
+ });
334
+ }
335
+ if (code === "ELOOP") {
336
+ codingPathRefusal("FORBIDDEN", "This path is a symlink, which is not followed", relative, {
337
+ details,
338
+ hint: "Write to the target directly, by its own workspace-relative path."
339
+ });
340
+ }
341
+ if (code === "EISDIR") {
342
+ codingPathRefusal("CONFLICT", "This path is a directory", relative, {
343
+ details,
344
+ hint: "Write to a file path inside it, or choose another name."
345
+ });
346
+ }
347
+ if (code === "EEXIST") {
348
+ codingPathRefusal("CONFLICT", "This file already exists", relative, {
349
+ details,
350
+ hint: "Pass overwrite: true to replace it, or edit_file to change part of it."
351
+ });
352
+ }
353
+ throw error;
354
+ }
309
355
  function refuseUnknownSpillReference(reference) {
310
356
  codingRefusal("SPILL_REFERENCE_UNKNOWN", `No spilled output is held under \`${reference}\``, {
311
357
  details: { reference },
@@ -343,6 +389,12 @@ function inputPath(root, requested, maxPathBytes) {
343
389
  return resolved;
344
390
  }
345
391
  function boundedCodingRelativePath(root, requested, maxPathBytes) {
392
+ if (requested.includes("\x00")) {
393
+ codingRefusal("BAD_REQUEST", "Path segments cannot contain a NUL byte", {
394
+ details: { path: requested },
395
+ hint: "Pass the exact workspace-relative entry name without NUL bytes."
396
+ });
397
+ }
346
398
  if (path.isAbsolute(requested))
347
399
  inputPath(root, requested, maxPathBytes);
348
400
  if (requested.includes("\\")) {
@@ -387,6 +439,14 @@ async function authorizeCodingPath(config, path2) {
387
439
  }
388
440
  }
389
441
  async function authorizeCodingPathChain(config, relative) {
442
+ if (relative !== ".") {
443
+ const misspelled = await misspelledContainedSegment(await realpath(config.root), relative);
444
+ if (misspelled !== undefined) {
445
+ codingPathRefusal("BAD_REQUEST", "This path differs from the name stored on disk", relative, {
446
+ hint: "Use list_directory to see the stored spelling."
447
+ });
448
+ }
449
+ }
390
450
  if (!config.authorizePath)
391
451
  return;
392
452
  const segments = relative === "." ? ["."] : relative.split("/");
@@ -462,86 +522,6 @@ function utf8AlignedStart(data, start, end) {
462
522
  return safe;
463
523
  }
464
524
 
465
- // src/agent-runtime/contained-write.ts
466
- import { fchmod, write as writeDescriptor } from "node:fs";
467
- import { rename, unlink, writeFile } from "node:fs/promises";
468
- import path2 from "node:path";
469
- async function writeDescriptorFully(descriptor, content) {
470
- const bytes = Buffer.from(content);
471
- let offset = 0;
472
- while (offset < bytes.byteLength) {
473
- const written = await new Promise((resolve, reject) => {
474
- writeDescriptor(descriptor, bytes, offset, bytes.byteLength - offset, null, (error, bytesWritten) => error ? reject(error) : resolve(bytesWritten));
475
- });
476
- if (written === 0)
477
- throw new Error("Contained file write made no progress");
478
- offset += written;
479
- }
480
- }
481
- async function chmodDescriptor(descriptor, mode) {
482
- await new Promise((resolve, reject) => {
483
- fchmod(descriptor, mode & 4095, (error) => error ? reject(error) : resolve());
484
- });
485
- }
486
- async function createDarwinFile(parent, name, content, mode) {
487
- const binding = loadDarwinBinding();
488
- const descriptor = binding.createFileAt(parent.fd, name, mode & 4095);
489
- let failed;
490
- try {
491
- await writeDescriptorFully(descriptor, content);
492
- await chmodDescriptor(descriptor, mode);
493
- } catch (error) {
494
- failed = error;
495
- } finally {
496
- try {
497
- await new NumericFileHandle(descriptor).close();
498
- } catch (error) {
499
- failed ??= error;
500
- }
501
- }
502
- if (failed) {
503
- binding.unlinkAt(parent.fd, name);
504
- throw failed;
505
- }
506
- }
507
- async function writeContainedFile(input) {
508
- const mode = input.mode ?? 438;
509
- if (!input.replace) {
510
- if (process.platform === "darwin") {
511
- await createDarwinFile(input.parent.handle, input.parent.basename, input.content, mode);
512
- return;
513
- }
514
- await writeFile(path2.join(descriptorPath(input.parent.handle), input.parent.basename), input.content, { flag: "wx", mode });
515
- return;
516
- }
517
- const temporary = stagingName();
518
- if (process.platform === "darwin") {
519
- const binding = loadDarwinBinding();
520
- let created = false;
521
- try {
522
- await createDarwinFile(input.parent.handle, temporary, input.content, mode);
523
- created = true;
524
- binding.renameAt(input.parent.handle.fd, temporary, input.parent.basename);
525
- } catch (error) {
526
- if (created)
527
- binding.unlinkAt(input.parent.handle.fd, temporary);
528
- throw error;
529
- }
530
- return;
531
- }
532
- const directory = descriptorPath(input.parent.handle);
533
- const temporaryPath = path2.join(directory, temporary);
534
- try {
535
- await writeFile(temporaryPath, input.content, { flag: "wx", mode });
536
- await rename(temporaryPath, path2.join(directory, input.parent.basename));
537
- } catch (error) {
538
- await unlink(temporaryPath).catch(() => {
539
- return;
540
- });
541
- throw error;
542
- }
543
- }
544
-
545
525
  // src/agent-runtime/coding-tool-files.ts
546
526
  async function digestFile(handle, size) {
547
527
  const hash = createHash("sha256");
@@ -556,6 +536,17 @@ async function digestFile(handle, size) {
556
536
  }
557
537
  return hash.digest("hex");
558
538
  }
539
+ async function openAuthorizedWriteParent(root, relative, createsDirectories) {
540
+ return openContainedParent(root, relative, { createDirectories: createsDirectories }).catch((error) => {
541
+ if (error instanceof UnapprovedContainedDirectoryError) {
542
+ codingPathRefusal("CONFLICT", "The directory creation plan changed during authorization", relative, {
543
+ details: { createsDirectories },
544
+ hint: "Retry the write so the host can authorize the current directories."
545
+ });
546
+ }
547
+ refuseWriteConflict(error, relative, { createsDirectories });
548
+ });
549
+ }
559
550
  function createFileCodingTools(config, limits) {
560
551
  const read = defineRuntimeTool2({
561
552
  name: "read_file",
@@ -576,7 +567,7 @@ function createFileCodingTools(config, limits) {
576
567
  let sha256;
577
568
  try {
578
569
  await authorizeCodingTool(config, { operation: "read", path: relative });
579
- await assertContainedFileCurrent(root, relative, handle);
570
+ await assertContainedFileCurrent(root, relative, handle).catch((error) => refuseMissingCodingPath(error, relative));
580
571
  const metadata = await handle.stat();
581
572
  if (!metadata.isFile()) {
582
573
  codingPathRefusal("BAD_REQUEST", "This path is not a regular file", relative, {
@@ -639,14 +630,7 @@ function createFileCodingTools(config, limits) {
639
630
  hint: "Write a smaller file, or split it."
640
631
  });
641
632
  }
642
- const createsDirectories = await missingContainedDirectories(root, relative).catch((error) => {
643
- if (error.code === "ENOTDIR") {
644
- codingPathRefusal("CONFLICT", "A segment of this path is not a directory", relative, {
645
- hint: "Write under a different path, or remove the file blocking it."
646
- });
647
- }
648
- throw error;
649
- });
633
+ const createsDirectories = await missingContainedDirectories(root, relative).catch((error) => refuseWriteConflict(error, relative));
650
634
  await authorizeCodingTool(config, {
651
635
  operation: "write",
652
636
  path: relative,
@@ -654,42 +638,39 @@ function createFileCodingTools(config, limits) {
654
638
  overwrite: input.overwrite,
655
639
  createsDirectories
656
640
  });
657
- const parent = await openContainedParent(root, relative, { create: true }).catch((error) => {
658
- const code = error.code;
659
- if (code === "ENOTDIR" || error instanceof Error && error.message.includes("ancestor is not a directory")) {
660
- codingPathRefusal("CONFLICT", "A segment of this path is not a directory", relative, {
661
- details: { createsDirectories },
662
- hint: "Write under a different path, or remove the file blocking it."
663
- });
664
- }
665
- throw error;
666
- });
667
- try {
668
- await assertContainedParentCurrent(root, relative, parent.handle);
669
- const current = await containedEntryMetadata(parent);
670
- if (input.overwrite) {
671
- if (current?.isSymbolicLink()) {
672
- codingPathRefusal("FORBIDDEN", "This path is a symlink", relative, {
673
- hint: "Coding tools never write through symlinks; write to the target directly."
674
- });
675
- }
676
- await writeContainedFile({
677
- parent,
678
- content: input.content,
679
- replace: true,
680
- ...current && { mode: current.mode }
681
- });
682
- } else {
683
- if (current) {
684
- codingPathRefusal("CONFLICT", "This file already exists", relative, {
685
- hint: "Pass overwrite: true to replace it, or edit_file to change part of it."
686
- });
641
+ await withCodingPathLock(`${root}:${relative}`, async () => {
642
+ const parent = await openAuthorizedWriteParent(root, relative, createsDirectories);
643
+ try {
644
+ await assertContainedParentCurrent(root, relative, parent.handle).catch((error) => refuseWriteConflict(error, relative));
645
+ const current = await containedEntryMetadata(parent);
646
+ if (input.overwrite) {
647
+ if (current?.isSymbolicLink()) {
648
+ codingPathRefusal("FORBIDDEN", "This path is a symlink", relative, {
649
+ hint: "Coding tools never write through symlinks; write to the target directly."
650
+ });
651
+ }
652
+ await writeContainedFile({
653
+ parent,
654
+ content: input.content,
655
+ replace: true,
656
+ ...current && { mode: current.mode }
657
+ }).catch((error) => refuseWriteConflict(error, relative));
658
+ } else {
659
+ if (current) {
660
+ codingPathRefusal("CONFLICT", "This file already exists", relative, {
661
+ hint: "Pass overwrite: true to replace it, or edit_file to change part of it."
662
+ });
663
+ }
664
+ await writeContainedFile({
665
+ parent,
666
+ content: input.content,
667
+ replace: false
668
+ }).catch((error) => refuseWriteConflict(error, relative));
687
669
  }
688
- await writeContainedFile({ parent, content: input.content, replace: false });
670
+ } finally {
671
+ await parent.handle.close();
689
672
  }
690
- } finally {
691
- await parent.handle.close();
692
- }
673
+ });
693
674
  return { path: relative, bytes, createdDirectories: createsDirectories };
694
675
  }
695
676
  });
@@ -698,7 +679,7 @@ function createFileCodingTools(config, limits) {
698
679
 
699
680
  // src/agent-runtime/coding-tool-listing.ts
700
681
  import { realpath as realpath3 } from "node:fs/promises";
701
- import path3 from "node:path";
682
+ import path2 from "node:path";
702
683
  import { z as z3 } from "zod";
703
684
  var DEFAULT_EXCLUDED_DIRECTORIES = [
704
685
  ".git",
@@ -773,12 +754,7 @@ function createListingCodingTools(config, limits) {
773
754
  await authorizeCodingPathChain(config, relative);
774
755
  await authorizeCodingTool(config, { operation: "list", path: relative });
775
756
  const listing = await listContainedDirectory(root, relative, limits.maxListEntries, (candidate) => isCodingPathAuthorized(config, candidate)).catch((error) => {
776
- if (error instanceof Error && error.message.includes("ancestor is not a directory")) {
777
- codingPathRefusal("BAD_REQUEST", "This path is not a directory", relative, {
778
- hint: "Pass a directory; use read_file for a file."
779
- });
780
- }
781
- if (error.code === "ENOENT") {
757
+ if (errnoOf(error) === "ENOENT") {
782
758
  codingPathRefusal("NOT_FOUND", "This directory does not exist", relative, {
783
759
  hint: "List a parent directory to see what is there."
784
760
  });
@@ -794,7 +770,7 @@ function createListingCodingTools(config, limits) {
794
770
  truncated: listing.truncated,
795
771
  ...listing.denied.length > 0 && {
796
772
  denied: listing.denied.map((entry) => ({
797
- path: relative === "." ? entry.name : path3.join(relative, entry.name),
773
+ path: relative === "." ? entry.name : path2.join(relative, entry.name),
798
774
  kind: entry.kind
799
775
  })),
800
776
  deniedTruncated: listing.deniedTruncated
@@ -832,10 +808,10 @@ function createListingCodingTools(config, limits) {
832
808
  maxDepth: limits.maxSearchDepth,
833
809
  maxFiles: limits.maxSearchFiles,
834
810
  symlinks: "skip",
835
- excludeDirectory: (directory) => directory.split(path3.sep).some((segment) => excluded.has(segment)),
811
+ excludeDirectory: (directory) => directory.split(path2.sep).some((segment) => excluded.has(segment)),
836
812
  authorizePath: (candidate) => isCodingPathAuthorized(config, candidate)
837
813
  });
838
- const prefix = relative === "." ? "" : `${relative}${path3.sep}`;
814
+ const prefix = relative === "." ? "" : `${relative}${path2.sep}`;
839
815
  const matched = scan.files.filter((file) => file.relative.startsWith(prefix)).map((file) => file.relative).filter((candidate) => matcher.test(candidate.slice(prefix.length))).sort();
840
816
  const paths = matched.slice(0, limits.maxSearchResults);
841
817
  const scopedDenied = scan.denied.filter((entry) => entry.relative.startsWith(prefix));
@@ -862,7 +838,7 @@ function createListingCodingTools(config, limits) {
862
838
  // src/agent-runtime/coding-tool-search-patch.ts
863
839
  import { createHash as createHash2 } from "node:crypto";
864
840
  import { realpath as realpath4 } from "node:fs/promises";
865
- import path4 from "node:path";
841
+ import path3 from "node:path";
866
842
  import { z as z4 } from "zod";
867
843
  var SearchInputSchema = z4.object({
868
844
  query: z4.string().min(1),
@@ -943,7 +919,7 @@ async function searchFiles(config, limits, input) {
943
919
  readMaxBytes: limits.maxReadBytes,
944
920
  skipUnreadable: true
945
921
  },
946
- excludeDirectory: (relative) => relative.split(path4.sep).some((segment) => excluded.has(segment)),
922
+ excludeDirectory: (relative) => relative.split(path3.sep).some((segment) => excluded.has(segment)),
947
923
  includeFile: (candidate) => {
948
924
  if (included && !included.test(candidate)) {
949
925
  rejectedByInclude += 1;
@@ -1019,8 +995,12 @@ async function editFile(config, limits, input) {
1019
995
  const parent = await openContainedParent(root, relative).catch((error) => refuseMissingCodingPath(error, relative));
1020
996
  try {
1021
997
  return await withCodingPathLock(`${root}:${relative}`, async () => {
1022
- const handle = await openContainedParentFile(parent).catch((error) => refuseMissingCodingPath(error, relative));
1023
- const read = await readContainedUtf8Handle(handle, limits.maxWriteBytes).finally(() => handle.close());
998
+ const readCurrent = async () => {
999
+ const handle = await openContainedParentFile(parent).catch((error) => refuseMissingCodingPath(error, relative));
1000
+ return readContainedUtf8Handle(handle, limits.maxWriteBytes).finally(() => handle.close());
1001
+ };
1002
+ await authorizeCodingTool(config, { operation: "read", path: relative });
1003
+ const read = await readCurrent();
1024
1004
  const source = read.text;
1025
1005
  const baseSha256 = sha256(source);
1026
1006
  if (input.expectedSha256 !== undefined && baseSha256 !== input.expectedSha256) {
@@ -1064,14 +1044,20 @@ async function editFile(config, limits, input) {
1064
1044
  replacements,
1065
1045
  dryRun: input.dryRun
1066
1046
  });
1067
- await assertContainedParentCurrent(root, relative, parent.handle);
1047
+ await assertContainedParentCurrent(root, relative, parent.handle).catch((error) => refuseWriteConflict(error, relative));
1068
1048
  if (!input.dryRun) {
1049
+ if (sha256((await readCurrent()).text) !== baseSha256) {
1050
+ conflict2("The file changed since it was read", {
1051
+ message: "The file changed since it was read",
1052
+ path: relative
1053
+ });
1054
+ }
1069
1055
  await writeContainedFile({
1070
1056
  parent,
1071
1057
  content: changed,
1072
1058
  replace: true,
1073
1059
  mode: read.mode
1074
- });
1060
+ }).catch((error) => refuseWriteConflict(error, relative));
1075
1061
  }
1076
1062
  return {
1077
1063
  path: relative,
@@ -1383,10 +1369,10 @@ var AGENT_CODING_TOOL_NAMES = {
1383
1369
  writeFile: "write_file"
1384
1370
  };
1385
1371
  function createAgentCodingTools2(config) {
1386
- if (!path5.isAbsolute(config.root))
1372
+ if (!path4.isAbsolute(config.root))
1387
1373
  throw new Error("Coding tool root must be absolute");
1388
1374
  for (const executable of Object.values(config.executables ?? {})) {
1389
- if (!path5.isAbsolute(executable)) {
1375
+ if (!path4.isAbsolute(executable)) {
1390
1376
  throw new Error("Coding tool executables must be absolute paths");
1391
1377
  }
1392
1378
  }
@@ -1,6 +1,9 @@
1
1
  import {
2
2
  loadDarwinBinding
3
3
  } from "./index-2kbysk9s.js";
4
+ import {
5
+ errnoOf
6
+ } from "./index-jj9228tt.js";
4
7
  import {
5
8
  isRecord
6
9
  } from "./index-9jfdm5tg.js";
@@ -126,7 +129,7 @@ async function observeProcessInstanceAt(pid, procRoot) {
126
129
  readlink(`${procRoot}/self/ns/pid`),
127
130
  readFile2(`${procRoot}/self/status`, "utf8"),
128
131
  readlink(`${procRoot}/self/ns/time`).catch((error) => {
129
- if (error instanceof Error && "code" in error && error.code === "ENOENT")
132
+ if (errnoOf(error) === "ENOENT")
130
133
  return "";
131
134
  throw error;
132
135
  }),
@@ -0,0 +1,100 @@
1
+ import {
2
+ loadDarwinBinding
3
+ } from "./index-2kbysk9s.js";
4
+ import {
5
+ descriptorPath,
6
+ NumericFileHandle
7
+ } from "./index-arjmb6de.js";
8
+ import {
9
+ stagingName
10
+ } from "./index-tt30h7mr.js";
11
+
12
+ // src/internal/contained-write.ts
13
+ import { constants, fchmod, open as openDescriptor, write as writeDescriptor } from "node:fs";
14
+ import { rename, unlink } from "node:fs/promises";
15
+ import path from "node:path";
16
+ function entryPath(directory, name) {
17
+ return path.join(descriptorPath(directory), name);
18
+ }
19
+ async function openExclusive(directory, name, mode) {
20
+ if (process.platform === "darwin") {
21
+ return loadDarwinBinding().createFileAt(directory.fd, name, mode);
22
+ }
23
+ return new Promise((resolve, reject) => {
24
+ openDescriptor(entryPath(directory, name), constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, mode, (error, descriptor) => error ? reject(error) : resolve(descriptor));
25
+ });
26
+ }
27
+ async function renameEntry(directory, source, target) {
28
+ if (process.platform === "darwin") {
29
+ loadDarwinBinding().renameAt(directory.fd, source, target);
30
+ return;
31
+ }
32
+ await rename(entryPath(directory, source), entryPath(directory, target));
33
+ }
34
+ async function discardEntry(directory, name) {
35
+ try {
36
+ if (process.platform === "darwin")
37
+ loadDarwinBinding().unlinkAt(directory.fd, name);
38
+ else
39
+ await unlink(entryPath(directory, name));
40
+ } catch {}
41
+ }
42
+ async function writeDescriptorFully(descriptor, content) {
43
+ const bytes = typeof content === "string" ? Buffer.from(content) : content;
44
+ let offset = 0;
45
+ while (offset < bytes.byteLength) {
46
+ const written = await new Promise((resolve, reject) => {
47
+ writeDescriptor(descriptor, bytes, offset, bytes.byteLength - offset, null, (error, bytesWritten) => error ? reject(error) : resolve(bytesWritten));
48
+ });
49
+ if (written === 0)
50
+ throw new Error("Contained file write made no progress");
51
+ offset += written;
52
+ }
53
+ }
54
+ async function applyMode(file, mode) {
55
+ if (((await file.stat()).mode & 511) === mode)
56
+ return;
57
+ await new Promise((resolve, reject) => {
58
+ fchmod(file.fd, mode, (error) => error ? reject(error) : resolve());
59
+ });
60
+ }
61
+ async function createFile(directory, name, content, mode) {
62
+ const descriptor = await openExclusive(directory, name, mode ?? 438);
63
+ const file = new NumericFileHandle(descriptor);
64
+ let failed;
65
+ try {
66
+ await writeDescriptorFully(descriptor, content);
67
+ if (mode !== undefined)
68
+ await applyMode(file, mode);
69
+ } catch (error) {
70
+ failed = error;
71
+ } finally {
72
+ try {
73
+ await file.close();
74
+ } catch (error) {
75
+ failed ??= error;
76
+ }
77
+ }
78
+ if (failed) {
79
+ await discardEntry(directory, name);
80
+ throw failed;
81
+ }
82
+ }
83
+ async function writeContainedFile(input) {
84
+ const { handle, basename } = input.parent;
85
+ const mode = input.mode === undefined ? undefined : input.mode & 511;
86
+ if (!input.replace) {
87
+ await createFile(handle, basename, input.content, mode);
88
+ return;
89
+ }
90
+ const temporary = stagingName();
91
+ await createFile(handle, temporary, input.content, mode);
92
+ try {
93
+ await renameEntry(handle, temporary, basename);
94
+ } catch (error) {
95
+ await discardEntry(handle, temporary);
96
+ throw error;
97
+ }
98
+ }
99
+
100
+ export { writeContainedFile };
@@ -1,3 +1,6 @@
1
+ import {
2
+ errnoOf
3
+ } from "./index-jj9228tt.js";
1
4
  import {
2
5
  assertPositiveSafeInteger
3
6
  } from "./index-0s4xebbg.js";
@@ -21,7 +24,7 @@ function isAtomicStagingName2(name) {
21
24
  return NAME.test(name);
22
25
  }
23
26
  function isMissing(error) {
24
- return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
27
+ return errnoOf(error) === "ENOENT";
25
28
  }
26
29
  async function sweepAtomicStaging2(options) {
27
30
  const { directory, olderThanMs, signal } = options;