maka-agent 0.2.0-dev.30.20260912 → 0.2.0-dev.31.20260913

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 (194) hide show
  1. package/THIRD_PARTY_NOTICES.txt +392 -0
  2. package/dist/acp/maka-acp-agent.js +8 -2
  3. package/dist/acp/prompt-content.js +225 -0
  4. package/dist/acp/session-event-mapper.js +103 -0
  5. package/dist/acp/session-registry.js +654 -17
  6. package/dist/acp/stdio-server.js +10 -1
  7. package/dist/cli-core.js +1 -1
  8. package/dist/pi-transcript-tools.js +3 -2
  9. package/dist/pi-transcript.js +12 -2
  10. package/dist/runtime-host-session-channel.js +63 -9
  11. package/native/runtime-host-windows-task-launcher/prebuilds/win32-x64/maka-runtime-host-task-launcher.exe +0 -0
  12. package/node_modules/@maka/core/dist/artifacts.js +6 -0
  13. package/node_modules/@maka/core/dist/attachments.js +7 -2
  14. package/node_modules/@maka/core/dist/durable-tool-result-projection.js +3 -1
  15. package/node_modules/@maka/core/dist/events.js +21 -0
  16. package/node_modules/@maka/core/dist/goal.js +44 -2
  17. package/node_modules/@maka/core/dist/permission.js +0 -1
  18. package/node_modules/@maka/core/dist/runtime-boundary.js +73 -29
  19. package/node_modules/@maka/core/dist/runtime-event.js +6 -3
  20. package/node_modules/@maka/core/dist/runtime-handoff.js +10 -2
  21. package/node_modules/@maka/core/dist/runtime-logical-execution.js +39 -14
  22. package/node_modules/@maka/core/dist/tool-result-archive-evidence.js +3 -1
  23. package/node_modules/@maka/core/dist/tool-result-record-schema.js +2 -1
  24. package/node_modules/@maka/core/dist/unified-diff.js +33 -26
  25. package/node_modules/@maka/runtime/dist/ai-sdk-compaction.js +49 -178
  26. package/node_modules/@maka/runtime/dist/ai-sdk-message-projection.js +7 -2
  27. package/node_modules/@maka/runtime/dist/ai-sdk-turn.js +15 -53
  28. package/node_modules/@maka/runtime/dist/bash-model-output.js +7 -1
  29. package/node_modules/@maka/runtime/dist/bash-tail-buffer.js +13 -52
  30. package/node_modules/@maka/runtime/dist/builtin-tools.js +59 -111
  31. package/node_modules/@maka/runtime/dist/child-fd-input.js +3 -2
  32. package/node_modules/@maka/runtime/dist/context-budget-policy.js +1 -11
  33. package/node_modules/@maka/runtime/dist/context-budget.js +12 -1
  34. package/node_modules/@maka/runtime/dist/conversation-copy.js +104 -9
  35. package/node_modules/@maka/runtime/dist/filesystem-executor.js +10 -6
  36. package/node_modules/@maka/runtime/dist/filesystem-worker/client.js +77 -19
  37. package/node_modules/@maka/runtime/dist/filesystem-worker/launch-spec.js +66 -55
  38. package/node_modules/@maka/runtime/dist/filesystem-worker/operations.js +34 -97
  39. package/node_modules/@maka/runtime/dist/filesystem-worker/protocol.js +25 -10
  40. package/node_modules/@maka/runtime/dist/filesystem-worker/worker-entry.js +2 -0
  41. package/node_modules/@maka/runtime/dist/glob-search.js +74 -0
  42. package/node_modules/@maka/runtime/dist/goal-continuation.js +78 -8
  43. package/node_modules/@maka/runtime/dist/goal-state.js +7 -0
  44. package/node_modules/@maka/runtime/dist/grep-search.js +155 -0
  45. package/node_modules/@maka/runtime/dist/ledger-tool-result-archive-reader.js +84 -94
  46. package/node_modules/@maka/runtime/dist/model-history.js +25 -3
  47. package/node_modules/@maka/runtime/dist/pipe-tail-collector.js +6 -9
  48. package/node_modules/@maka/runtime/dist/read-page.js +231 -0
  49. package/node_modules/@maka/runtime/dist/request-projection.js +2 -15
  50. package/node_modules/@maka/runtime/dist/ripgrep-executable.js +62 -0
  51. package/node_modules/@maka/runtime/dist/ripgrep-guidance.js +81 -0
  52. package/node_modules/@maka/runtime/dist/runtime-event-read-model.js +187 -20
  53. package/node_modules/@maka/runtime/dist/runtime-read-model.js +2 -1
  54. package/node_modules/@maka/runtime/dist/sandbox/linux-profile-path.js +7 -1
  55. package/node_modules/@maka/runtime/dist/sandbox/linux-sandbox.js +20 -5
  56. package/node_modules/@maka/runtime/dist/session-manager.js +6 -6
  57. package/node_modules/@maka/runtime/dist/session-recap.js +9 -5
  58. package/node_modules/@maka/runtime/dist/shell-exec.js +7 -25
  59. package/node_modules/@maka/runtime/dist/shell-run-manager.js +1 -1
  60. package/node_modules/@maka/runtime/dist/shell-run-tool-result.js +1 -1
  61. package/node_modules/@maka/runtime/dist/shell-tools.js +6 -12
  62. package/node_modules/@maka/runtime/dist/tool-availability.js +0 -1
  63. package/node_modules/@maka/runtime/dist/tool-output.js +1 -1
  64. package/node_modules/@maka/runtime/dist/tool-result-archive-capability.js +24 -36
  65. package/node_modules/@maka/runtime/dist/tool-result-archive-resource.js +32 -527
  66. package/node_modules/@maka/runtime/dist/tool-result-archive-transition.js +17 -119
  67. package/node_modules/@maka/runtime/dist/tool-result-archive.js +7 -25
  68. package/node_modules/@maka/runtime/dist/tool-runtime.js +7 -5
  69. package/node_modules/@maka/runtime/dist/workers/filesystem-worker.js +6933 -3681
  70. package/node_modules/@maka/runtime/dist/workspace-executor.js +20 -24
  71. package/node_modules/@maka/runtime/package.json +2 -0
  72. package/node_modules/@maka/runtime-host/dist/client/host-handoff-copy.js +185 -191
  73. package/node_modules/@maka/runtime-host/dist/client/reconnecting-connection.js +3 -0
  74. package/node_modules/@maka/runtime-host/dist/protocol/index.js +5 -1
  75. package/node_modules/@maka/runtime-host/dist/protocol/message.js +2 -2
  76. package/node_modules/@maka/runtime-host/dist/protocol/operations.js +1 -0
  77. package/node_modules/@maka/runtime-host/dist/protocol/session-continuity.js +2 -2
  78. package/node_modules/@maka/runtime-host/dist/protocol/turn.js +10 -2
  79. package/node_modules/@maka/runtime-host/dist/protocol/workhub-coordination.js +38 -0
  80. package/node_modules/@maka/runtime-host/dist/server/execution-artifacts.js +2 -8
  81. package/node_modules/@maka/runtime-host/dist/server/execution-composition.js +2 -0
  82. package/node_modules/@maka/runtime-host/dist/server/goal-coordinator.js +81 -7
  83. package/node_modules/@maka/runtime-host/dist/server/hosted-execution-tool-profile.js +13 -13
  84. package/node_modules/@maka/runtime-host/dist/server/interaction-coordinator.js +65 -0
  85. package/node_modules/@maka/runtime-host/dist/server/root-turn-coordinator.js +1 -0
  86. package/node_modules/@maka/runtime-host/dist/server/session-continuity-coordinator.js +5 -2
  87. package/node_modules/@maka/runtime-host/dist/server/session-transcript-reader.js +88 -72
  88. package/node_modules/@maka/runtime-host/dist/server/workhub-coordination-action-gate.js +21 -4
  89. package/node_modules/@maka/runtime-host/dist/server/workhub-coordination-coordinator.js +145 -1
  90. package/node_modules/@maka/storage/dist/agent-run-store.js +5 -2
  91. package/node_modules/@maka/storage/dist/execution-stores.js +2 -1
  92. package/node_modules/@maka/storage/dist/runtime-transcript-query.js +27 -14
  93. package/node_modules/@maka/storage/dist/sqlite-runtime-store.js +69 -3
  94. package/node_modules/balanced-match/LICENSE.md +23 -0
  95. package/node_modules/balanced-match/README.md +57 -0
  96. package/node_modules/balanced-match/dist/commonjs/index.js +59 -0
  97. package/node_modules/balanced-match/dist/commonjs/package.json +3 -0
  98. package/node_modules/balanced-match/dist/esm/index.js +54 -0
  99. package/node_modules/balanced-match/dist/esm/package.json +3 -0
  100. package/node_modules/balanced-match/package.json +68 -0
  101. package/node_modules/brace-expansion/LICENSE +23 -0
  102. package/node_modules/brace-expansion/README.md +105 -0
  103. package/node_modules/brace-expansion/dist/commonjs/index.js +289 -0
  104. package/node_modules/brace-expansion/dist/commonjs/package.json +3 -0
  105. package/node_modules/brace-expansion/dist/esm/index.js +285 -0
  106. package/node_modules/brace-expansion/dist/esm/package.json +3 -0
  107. package/node_modules/brace-expansion/package.json +64 -0
  108. package/node_modules/glob/LICENSE.md +63 -0
  109. package/node_modules/glob/README.md +1203 -0
  110. package/node_modules/glob/dist/commonjs/glob.js +248 -0
  111. package/node_modules/glob/dist/commonjs/has-magic.js +27 -0
  112. package/node_modules/glob/dist/commonjs/ignore.js +119 -0
  113. package/node_modules/glob/dist/commonjs/index.js +68 -0
  114. package/node_modules/glob/dist/commonjs/index.min.js +4 -0
  115. package/node_modules/glob/dist/commonjs/package.json +3 -0
  116. package/node_modules/glob/dist/commonjs/pattern.js +223 -0
  117. package/node_modules/glob/dist/commonjs/processor.js +301 -0
  118. package/node_modules/glob/dist/commonjs/walker.js +387 -0
  119. package/node_modules/glob/dist/esm/glob.js +244 -0
  120. package/node_modules/glob/dist/esm/has-magic.js +23 -0
  121. package/node_modules/glob/dist/esm/ignore.js +115 -0
  122. package/node_modules/glob/dist/esm/index.js +55 -0
  123. package/node_modules/glob/dist/esm/index.min.js +4 -0
  124. package/node_modules/glob/dist/esm/package.json +3 -0
  125. package/node_modules/glob/dist/esm/pattern.js +219 -0
  126. package/node_modules/glob/dist/esm/processor.js +294 -0
  127. package/node_modules/glob/dist/esm/walker.js +381 -0
  128. package/node_modules/glob/package.json +98 -0
  129. package/node_modules/minimatch/LICENSE.md +55 -0
  130. package/node_modules/minimatch/README.md +528 -0
  131. package/node_modules/minimatch/dist/commonjs/assert-valid-pattern.js +14 -0
  132. package/node_modules/minimatch/dist/commonjs/ast.js +845 -0
  133. package/node_modules/minimatch/dist/commonjs/brace-expressions.js +150 -0
  134. package/node_modules/minimatch/dist/commonjs/escape.js +30 -0
  135. package/node_modules/minimatch/dist/commonjs/index.js +1127 -0
  136. package/node_modules/minimatch/dist/commonjs/package.json +3 -0
  137. package/node_modules/minimatch/dist/commonjs/unescape.js +38 -0
  138. package/node_modules/minimatch/dist/esm/assert-valid-pattern.js +10 -0
  139. package/node_modules/minimatch/dist/esm/ast.js +841 -0
  140. package/node_modules/minimatch/dist/esm/brace-expressions.js +146 -0
  141. package/node_modules/minimatch/dist/esm/escape.js +26 -0
  142. package/node_modules/minimatch/dist/esm/index.js +1114 -0
  143. package/node_modules/minimatch/dist/esm/package.json +3 -0
  144. package/node_modules/minimatch/dist/esm/unescape.js +34 -0
  145. package/node_modules/minimatch/package.json +73 -0
  146. package/node_modules/minipass/LICENSE.md +55 -0
  147. package/node_modules/minipass/README.md +825 -0
  148. package/node_modules/minipass/dist/commonjs/index.js +1038 -0
  149. package/node_modules/minipass/dist/commonjs/package.json +3 -0
  150. package/node_modules/minipass/dist/esm/index.js +1020 -0
  151. package/node_modules/minipass/dist/esm/package.json +3 -0
  152. package/node_modules/minipass/package.json +77 -0
  153. package/node_modules/path-scurry/LICENSE.md +55 -0
  154. package/node_modules/path-scurry/README.md +636 -0
  155. package/node_modules/path-scurry/dist/commonjs/index.js +2018 -0
  156. package/node_modules/path-scurry/dist/commonjs/package.json +3 -0
  157. package/node_modules/path-scurry/dist/esm/index.js +1983 -0
  158. package/node_modules/path-scurry/dist/esm/package.json +3 -0
  159. package/node_modules/path-scurry/node_modules/lru-cache/LICENSE.md +55 -0
  160. package/node_modules/path-scurry/node_modules/lru-cache/README.md +469 -0
  161. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/diagnostics-channel.js +7 -0
  162. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/index.js +1726 -0
  163. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/index.min.js +2 -0
  164. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/perf.js +10 -0
  165. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/diagnostics-channel-cjs.d.cts.map +1 -0
  166. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/diagnostics-channel.js +7 -0
  167. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/index.js +1726 -0
  168. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/index.min.js +2 -0
  169. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/diagnostics-channel.js +9 -0
  170. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/index.js +1726 -0
  171. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/index.min.js +2 -0
  172. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/perf.js +10 -0
  173. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/package.json +3 -0
  174. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/perf.js +10 -0
  175. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/diagnostics-channel.js +4 -0
  176. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/index.js +1722 -0
  177. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/index.min.js +2 -0
  178. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/perf.js +7 -0
  179. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/diagnostics-channel-esm.d.mts.map +1 -0
  180. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/diagnostics-channel.js +19 -0
  181. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/index.js +1722 -0
  182. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/index.min.js +2 -0
  183. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/diagnostics-channel.js +6 -0
  184. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/index.js +1722 -0
  185. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/index.min.js +2 -0
  186. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/perf.js +7 -0
  187. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/package.json +3 -0
  188. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/perf.js +7 -0
  189. package/node_modules/path-scurry/node_modules/lru-cache/package.json +154 -0
  190. package/node_modules/path-scurry/package.json +72 -0
  191. package/package.json +1 -1
  192. package/node_modules/@maka/runtime/dist/active-tool-result-prune.js +0 -248
  193. package/node_modules/@maka/runtime/dist/active-tool-result-working-set.js +0 -218
  194. package/node_modules/@maka/runtime/dist/archive-read-tool.js +0 -115
@@ -16,16 +16,17 @@
16
16
  * specific language governing permissions and limitations
17
17
  * under the License.
18
18
  */
19
- import { spawn } from 'node:child_process';
19
+ import { searchFiles, GrepSearchError } from '../grep-search.js';
20
20
  import { promises as fs } from 'node:fs';
21
- import { glob as nodeGlob } from 'node:fs/promises';
21
+ import { globFiles } from '../glob-search.js';
22
22
  import { dirname, isAbsolute, parse, resolve } from 'node:path';
23
23
  import { isPathInside } from '../path-containment.js';
24
+ import { ripgrepMissingMessage, ripgrepVanishedMessage, } from '../ripgrep-guidance.js';
24
25
  import { sandboxPathApi } from './sandbox-paths.js';
25
26
  import { sandboxBoundaryExpansionAllowsPath } from '@maka/core/sandbox-boundary';
26
27
  import { ApplyPatchRejectedError, applyUpdateToContent, createPatchedFile, } from '../apply-patch-file.js';
27
28
  import { computeEditedSource } from '../edit-replace.js';
28
- import { readTextLineWindow } from '../text-line-window.js';
29
+ import { readPage } from '../read-page.js';
29
30
  import { createEditUnifiedDiff, createUnifiedDiff } from '../unified-diff.js';
30
31
  import { compareAndDeleteEntry, hostVisibilityAfterWrite, openStableTarget, readModifyWriteThroughHandle, StableWriteFailure, writeThroughHandle, } from '../file-stable-write.js';
31
32
  import { isSupportedImagePath, readWorkspaceImage } from '../image-file.js';
@@ -35,9 +36,6 @@ import { isLikelySandboxDenial } from '../sandbox/detect.js';
35
36
  // on POSIX, lexical + reparse-rejecting inside the Windows AppContainer where
36
37
  // realpath is denied. See sandbox-paths.ts.
37
38
  const { realpath, realpathAllowMissing, resolveCanonicalDirectoryEntryTarget } = sandboxPathApi();
38
- const DEFAULT_GLOB_LIMIT = 200;
39
- const MAX_GREP_OUTPUT_BYTES = 8 * 1024 * 1024;
40
- const MAX_GREP_STDERR_BYTES = 16 * 1024;
41
39
  export async function executeFilesystemWorkerRequest(request, dependencies = {}) {
42
40
  try {
43
41
  await assertTargetUnchanged(request.operation.cwd, request.operation.path, request.expectedTarget, operationUsesDirectoryEntry(request.operation), operationAccess(request.operation.kind));
@@ -76,10 +74,14 @@ export async function executeFilesystemOperation(operation, operationBoundary, d
76
74
  }
77
75
  }
78
76
  const content = await fs.readFile(path, 'utf8');
79
- return {
80
- kind: 'read',
81
- content: readTextLineWindow(content, operation.offset, operation.limit),
82
- };
77
+ try {
78
+ return { kind: 'read', ...readPage(content, operation, undefined, operation.continuation) };
79
+ }
80
+ catch (error) {
81
+ throw operationError('invalid_request', error instanceof Error
82
+ ? error.message
83
+ : 'Read pagination failed. Use the original path again.');
84
+ }
83
85
  }
84
86
  case 'write': {
85
87
  const path = await resolveWritableAllowed(operation.cwd, operation.path, 'Write', operationBoundary);
@@ -258,14 +260,7 @@ export async function executeFilesystemOperation(operation, operationBoundary, d
258
260
  case 'glob': {
259
261
  assertContainedGlobPattern(operation.pattern);
260
262
  const path = await resolveExistingAllowed(operation.cwd, operation.path, 'Glob cwd', 'read', operationBoundary);
261
- const files = [];
262
- const limit = operation.limit ?? DEFAULT_GLOB_LIMIT;
263
- for await (const file of nodeGlob(operation.pattern, { cwd: path })) {
264
- files.push(typeof file === 'string' ? file : file.name);
265
- if (files.length >= limit)
266
- break;
267
- }
268
- return { kind: 'glob', files };
263
+ return { kind: 'glob', ...(await globFiles({ ...operation, cwd: path })) };
269
264
  }
270
265
  case 'grep': {
271
266
  const path = await resolveExistingAllowed(operation.cwd, operation.path, 'Grep', 'read', operationBoundary);
@@ -281,34 +276,32 @@ export async function executeFilesystemOperation(operation, operationBoundary, d
281
276
  if (dependencies.windowsSandboxed) {
282
277
  throw operationError('grep_unavailable', 'Grep is not available inside the Windows sandbox preview; use Glob and Read instead.');
283
278
  }
284
- if (!dependencies.grepExecutable)
285
- throw operationError('grep_unavailable', 'Grep is unavailable in this runtime.');
286
- const args = ['-n', '--no-heading', `--max-count=${operation.maxCountPerFile}`];
287
- if (operation.glob)
288
- args.push('--glob', operation.glob);
289
- args.push('--', operation.pattern, path);
290
- const result = await (dependencies.runGrep ?? runRipgrep)({
291
- executable: dependencies.grepExecutable,
292
- args,
279
+ const grepExecutable = dependencies.grepExecutable;
280
+ if (!grepExecutable)
281
+ throw operationError('grep_unavailable', ripgrepMissingMessage(dependencies.ripgrepEnvironment));
282
+ const result = await searchFiles({
283
+ ...operation,
284
+ path,
285
+ executable: grepExecutable,
293
286
  // The target is canonical and absolute. Running from its filesystem root avoids
294
287
  // requiring operation-scoped workers to read the broader session workspace.
295
288
  cwd: parse(path).root,
296
289
  timeoutMs: operation.timeoutMs,
290
+ }, dependencies.runGrep).catch((error) => {
291
+ // The cwd is a filesystem root, which always exists, so a spawn ENOENT
292
+ // means the executable this worker was launched with is gone — removed
293
+ // after the launch configuration checked it. Left alone it would be
294
+ // normalized to `not_found` and read as a missing search path.
295
+ if (nodeErrorCode(error) === 'ENOENT')
296
+ throw operationError('grep_unavailable', ripgrepVanishedMessage(grepExecutable, dependencies.ripgrepEnvironment));
297
+ if (error instanceof GrepSearchError) {
298
+ throw operationError(isLikelySandboxDenial({ stdout: '', stderr: error.message, sandboxed: true })
299
+ ? 'sandbox_denied'
300
+ : 'filesystem_error', error.message);
301
+ }
302
+ throw error;
297
303
  });
298
- if (result.exitCode === 1)
299
- return { kind: 'grep', matches: [] };
300
- if (result.exitCode !== 0) {
301
- const detail = result.stderrTail.trim();
302
- throw operationError(isLikelySandboxDenial({ stdout: result.stdout, stderr: detail, sandboxed: true })
303
- ? 'sandbox_denied'
304
- : 'filesystem_error', detail
305
- ? `Grep failed while searching files.\n${detail}`
306
- : 'Grep failed while searching files.');
307
- }
308
- return {
309
- kind: 'grep',
310
- matches: result.stdout.split('\n').filter(Boolean).slice(0, operation.limit),
311
- };
304
+ return { kind: 'grep', ...result };
312
305
  }
313
306
  }
314
307
  }
@@ -483,59 +476,3 @@ function nodeErrorCode(error) {
483
476
  return undefined;
484
477
  return typeof error.code === 'string' ? error.code : undefined;
485
478
  }
486
- async function runRipgrep(input) {
487
- return await new Promise((resolvePromise, reject) => {
488
- const child = spawn(input.executable, [...input.args], {
489
- cwd: input.cwd,
490
- shell: false,
491
- stdio: ['ignore', 'pipe', 'pipe'],
492
- });
493
- const chunks = [];
494
- let bytes = 0;
495
- let stderrTail = Buffer.alloc(0);
496
- let settled = false;
497
- const timer = setTimeout(() => {
498
- child.kill('SIGKILL');
499
- rejectOnce(operationError('filesystem_error', 'Grep timed out.'));
500
- }, input.timeoutMs);
501
- child.stdout.on('data', (chunk) => {
502
- bytes += chunk.length;
503
- if (bytes > MAX_GREP_OUTPUT_BYTES) {
504
- child.kill('SIGKILL');
505
- rejectOnce(operationError('filesystem_error', 'Grep output exceeded the worker limit.'));
506
- }
507
- else {
508
- chunks.push(chunk);
509
- }
510
- });
511
- child.stderr.on('data', (chunk) => {
512
- stderrTail = appendBoundedTail(stderrTail, chunk, MAX_GREP_STDERR_BYTES);
513
- });
514
- child.once('error', (error) => rejectOnce(error));
515
- child.once('close', (exitCode) => {
516
- if (settled)
517
- return;
518
- settled = true;
519
- clearTimeout(timer);
520
- resolvePromise({
521
- exitCode: exitCode ?? 2,
522
- stdout: Buffer.concat(chunks).toString('utf8'),
523
- stderrTail: stderrTail.toString('utf8'),
524
- });
525
- });
526
- function rejectOnce(error) {
527
- if (settled)
528
- return;
529
- settled = true;
530
- clearTimeout(timer);
531
- reject(error);
532
- }
533
- });
534
- }
535
- function appendBoundedTail(current, chunk, limit) {
536
- if (chunk.length >= limit)
537
- return chunk.subarray(chunk.length - limit);
538
- if (current.length + chunk.length <= limit)
539
- return Buffer.concat([current, chunk]);
540
- return Buffer.concat([current.subarray(current.length - (limit - chunk.length)), chunk]);
541
- }
@@ -17,13 +17,12 @@
17
17
  * under the License.
18
18
  */
19
19
  import { z } from 'zod';
20
+ import { readContinuationSchema, readPageSchema } from '../read-page.js';
20
21
  import { validateSandboxBoundaryExpansion } from '@maka/core/sandbox-boundary';
21
- // v6 adds the captured target identity (opaque decimal-string dev/ino) to
22
- // FilesystemWorkerTarget, so the worker can compare-and-swap against the
23
- // inode that was authorised at lock acquisition instead of only the path
24
- // string. The identity is carried as strings because bigint cannot cross the
25
- // JSON protocol boundary.
26
- export const FILESYSTEM_WORKER_PROTOCOL_VERSION = 7;
22
+ import { GREP_MAX_LINES, GREP_MAX_LINES_PER_FILE, GREP_MAX_MATCH_BYTES } from '../grep-search.js';
23
+ // v10 adds bounded Read pages to v9's exact Grep counts. Older workers cannot
24
+ // satisfy the combined result contract and must be rejected at the handshake.
25
+ export const FILESYSTEM_WORKER_PROTOCOL_VERSION = 10;
27
26
  /** The single authority on which operation kinds are writes. Shared by the
28
27
  * client (permission/identity decisions) and the worker (operation guards) so
29
28
  * the set cannot drift. */
@@ -100,6 +99,7 @@ export const FilesystemWorkerOperationSchema = z.union([
100
99
  path,
101
100
  offset: z.number().int().nonnegative().optional(),
102
101
  limit: z.number().int().positive().optional(),
102
+ continuation: readContinuationSchema.optional(),
103
103
  })
104
104
  .strict(),
105
105
  z.object({ kind: z.literal('write'), cwd, path, content: z.string() }).strict(),
@@ -146,8 +146,8 @@ export const FilesystemWorkerOperationSchema = z.union([
146
146
  path,
147
147
  pattern: z.string(),
148
148
  glob: z.string().min(1).optional(),
149
- maxCountPerFile: z.number().int().positive(),
150
- limit: z.number().int().positive(),
149
+ maxCountPerFile: z.number().int().positive().max(GREP_MAX_LINES_PER_FILE),
150
+ limit: z.number().int().positive().max(GREP_MAX_LINES),
151
151
  timeoutMs: z.number().int().positive(),
152
152
  })
153
153
  .strict(),
@@ -162,7 +162,7 @@ export const FilesystemWorkerRequestSchema = z
162
162
  })
163
163
  .strict();
164
164
  export const FilesystemWorkerResultSchema = z.discriminatedUnion('kind', [
165
- z.object({ kind: z.literal('read'), content: z.string() }).strict(),
165
+ readPageSchema.extend({ kind: z.literal('read') }).strict(),
166
166
  z
167
167
  .object({
168
168
  kind: z.literal('read_image'),
@@ -207,7 +207,22 @@ export const FilesystemWorkerResultSchema = z.discriminatedUnion('kind', [
207
207
  })
208
208
  .strict(),
209
209
  z.object({ kind: z.literal('glob'), files: z.array(z.string()) }).strict(),
210
- z.object({ kind: z.literal('grep'), matches: z.array(z.string()) }).strict(),
210
+ z
211
+ .object({
212
+ kind: z.literal('grep'),
213
+ matchedLines: z.number().int().nonnegative(),
214
+ returnedLines: z.number().int().nonnegative(),
215
+ omittedLines: z.number().int().nonnegative(),
216
+ truncated: z.boolean(),
217
+ matches: z
218
+ .array(z.string())
219
+ .max(GREP_MAX_LINES)
220
+ .refine((matches) => Buffer.byteLength(JSON.stringify(matches)) <= GREP_MAX_MATCH_BYTES),
221
+ })
222
+ .strict()
223
+ .refine((result) => result.returnedLines === result.matches.length &&
224
+ result.matchedLines === result.returnedLines + result.omittedLines &&
225
+ result.truncated === result.omittedLines > 0),
211
226
  ]);
212
227
  export const FilesystemWorkerErrorCodeSchema = z.enum([
213
228
  'invalid_request',
@@ -18,6 +18,7 @@
18
18
  */
19
19
  import { stdin, stdout } from 'node:process';
20
20
  import { executeFilesystemWorkerRequest } from './operations.js';
21
+ import { parseRipgrepEnvironmentArg } from '../ripgrep-guidance.js';
21
22
  import { FILESYSTEM_WORKER_PROTOCOL_VERSION, FilesystemWorkerRequestSchema, FilesystemWorkerResponseSchema, } from './protocol.js';
22
23
  const MAX_REQUEST_BYTES = 16 * 1024 * 1024;
23
24
  async function main() {
@@ -47,6 +48,7 @@ async function main() {
47
48
  }
48
49
  writeResponse(await executeFilesystemWorkerRequest(parsed.data, {
49
50
  grepExecutable: readOption('--grep-executable'),
51
+ ripgrepEnvironment: parseRipgrepEnvironmentArg(readOption('--ripgrep-environment')),
50
52
  windowsSandboxed: process.env.MAKA_WINDOWS_SANDBOX === '1',
51
53
  }));
52
54
  }
@@ -0,0 +1,74 @@
1
+ /*
2
+ * Licensed to the Apache Software Foundation (ASF) under one
3
+ * or more contributor license agreements. See the NOTICE file
4
+ * distributed with this work for additional information
5
+ * regarding copyright ownership. The ASF licenses this file
6
+ * to you under the Apache License, Version 2.0 (the
7
+ * "License"); you may not use this file except in compliance
8
+ * with the License. You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing,
13
+ * software distributed under the License is distributed on an
14
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15
+ * KIND, either express or implied. See the License for the
16
+ * specific language governing permissions and limitations
17
+ * under the License.
18
+ */
19
+ import { promises as fs, readdir } from 'node:fs';
20
+ import { resolve } from 'node:path';
21
+ import { globIterate } from 'glob';
22
+ export async function globFiles(input) {
23
+ let failure;
24
+ const directories = new Set([resolve(input.cwd)]);
25
+ function record(error, path) {
26
+ // Speculative literal components may miss. A directory already admitted by
27
+ // cwd, stat, or enumeration disappearing instead makes this walk incomplete.
28
+ if (directories.has(resolve(path)) || (error.code !== 'ENOENT' && error.code !== 'ENOTDIR')) {
29
+ failure ??= error;
30
+ }
31
+ }
32
+ const files = [];
33
+ for await (const file of globIterate(input.pattern, {
34
+ cwd: input.cwd,
35
+ ignore: { childrenIgnored: (entry) => entry.isSymbolicLink() },
36
+ fs: {
37
+ readdir(path, options, callback) {
38
+ readdir(path, options, (error, entries) => {
39
+ if (error)
40
+ record(error, path);
41
+ else
42
+ for (const entry of entries) {
43
+ if (entry.isDirectory())
44
+ directories.add(resolve(path, entry.name));
45
+ }
46
+ callback(error, entries);
47
+ });
48
+ },
49
+ promises: {
50
+ async lstat(path) {
51
+ try {
52
+ const stat = await fs.lstat(path);
53
+ if (stat.isDirectory())
54
+ directories.add(resolve(path));
55
+ return stat;
56
+ }
57
+ catch (error) {
58
+ record(error, path);
59
+ throw error;
60
+ }
61
+ },
62
+ },
63
+ },
64
+ })) {
65
+ if (failure)
66
+ throw failure;
67
+ files.push(file);
68
+ if (files.length >= (input.limit ?? 200))
69
+ break;
70
+ }
71
+ if (failure)
72
+ throw failure;
73
+ return { files };
74
+ }
@@ -24,12 +24,14 @@
24
24
  * an in-memory backoff instead of immediately spending another turn.
25
25
  */
26
26
  import { evaluateGoal, } from './goal-evaluator.js';
27
- import { goalCheckpoint, } from './goal-state.js';
27
+ import { goalCheckpoint, truncateGoalText, } from './goal-state.js';
28
28
  import { GoalSessionCloseFence, } from './goal-session-close-fence.js';
29
29
  import { GoalTaskGatePolicy } from './goal-task-gate-policy.js';
30
- import { isDrivingGoal, sameGoalControlLease, } from '@maka/core/goal';
30
+ import { GOAL_PENDING_PROMPT_TEXT_LIMIT, isDrivingGoal, sameGoalControlLease, } from '@maka/core/goal';
31
31
  export const volatileGoalDurability = Object.freeze({
32
32
  flush: async () => { },
33
+ recordPendingContinuation: async () => { },
34
+ clearPendingContinuation: async () => { },
33
35
  recordCurrentExecution: async () => { },
34
36
  settleCurrentExecution: async () => { },
35
37
  });
@@ -287,6 +289,39 @@ export class GoalContinuationCoordinator {
287
289
  reason: goal.lastReason ?? 'Goal continuation recovered after Host restart.',
288
290
  });
289
291
  }
292
+ /** Restore the durable outbox entry before synthesizing generic recovery. */
293
+ recoverPendingContinuation(pending) {
294
+ if (this.disposed)
295
+ return;
296
+ const sessionId = this.deps.goalManager.getSessionIdByGoalId(pending.checkpoint.goalId);
297
+ if (!sessionId || this.sessionCloseFence.isClosed(sessionId))
298
+ return;
299
+ const goal = this.deps.goalManager.get(sessionId);
300
+ if (!goal ||
301
+ (goal.status !== 'active' && goal.status !== 'waiting') ||
302
+ !this.deps.goalManager.matches(sessionId, pending.checkpoint) ||
303
+ !this.deps.goalManager.matchesControlLease(sessionId, pending.controlLease)) {
304
+ return;
305
+ }
306
+ const lane = this.laneFor(sessionId);
307
+ if (lane.intent || lane.turns.size > 0)
308
+ return;
309
+ lane.intent = {
310
+ checkpoint: pending.checkpoint,
311
+ controlLease: pending.controlLease,
312
+ ...(pending.triggeringTurnId ? { triggeringTurnId: pending.triggeringTurnId } : {}),
313
+ recoveredPrompt: pending.prompt,
314
+ evaluation: {
315
+ met: false,
316
+ impossible: false,
317
+ progress: false,
318
+ waiting: goal.status === 'waiting',
319
+ evaluatorFailed: true,
320
+ reason: 'Durable Goal continuation recovered after Host restart.',
321
+ },
322
+ };
323
+ this.scheduleDrain(lane);
324
+ }
290
325
  /** Resume an exact paused Goal generation from Host control without a model-owned Turn. */
291
326
  resumeFromControl(sessionId, checkpoint) {
292
327
  if (this.disposed || this.sessionCloseFence.isClosed(sessionId))
@@ -353,9 +388,6 @@ export class GoalContinuationCoordinator {
353
388
  }
354
389
  enqueueTurn(outcome, controlLease, registration) {
355
390
  const lane = registration.lane;
356
- // New evidence always outranks an older, not-yet-admitted continuation.
357
- lane.intent = undefined;
358
- this.clearWaitingTimer(lane);
359
391
  return new Promise((resolve) => {
360
392
  lane.queue.push({
361
393
  turnId: registration.turnId,
@@ -484,10 +516,21 @@ export class GoalContinuationCoordinator {
484
516
  if (!this.deps.goalManager.matchesControlLease(lane.sessionId, item.controlLease)) {
485
517
  return;
486
518
  }
487
- const goal = this.deps.goalManager.get(lane.sessionId);
488
519
  if (item.checkpoint && !this.deps.goalManager.matchesActive(lane.sessionId, item.checkpoint)) {
489
520
  return;
490
521
  }
522
+ if (sameGoalControlLease(lane.intent?.controlLease, item.controlLease)) {
523
+ // New evidence always outranks an older, not-yet-admitted continuation.
524
+ lane.intent = undefined;
525
+ this.clearWaitingTimer(lane);
526
+ await this.deps.durability.clearPendingContinuation(lane.sessionId, item.controlLease);
527
+ if (!this.isCurrent(lane) ||
528
+ !this.deps.goalManager.matchesControlLease(lane.sessionId, item.controlLease) ||
529
+ (item.checkpoint && !this.deps.goalManager.matchesActive(lane.sessionId, item.checkpoint))) {
530
+ return;
531
+ }
532
+ }
533
+ const goal = this.deps.goalManager.get(lane.sessionId);
491
534
  if (item.outcome.kind !== 'completed') {
492
535
  if (goal.status !== 'active' && goal.status !== 'waiting')
493
536
  return;
@@ -592,6 +635,13 @@ export class GoalContinuationCoordinator {
592
635
  triggeringTurnId: turnId,
593
636
  evaluation,
594
637
  };
638
+ const pendingPrompt = truncateGoalText(buildContinuationPrompt(settled, evaluation, undefined), GOAL_PENDING_PROMPT_TEXT_LIMIT);
639
+ await this.deps.durability.recordPendingContinuation({
640
+ checkpoint: lane.intent.checkpoint,
641
+ controlLease,
642
+ prompt: pendingPrompt,
643
+ triggeringTurnId: turnId,
644
+ });
595
645
  if (settled.status === 'waiting') {
596
646
  lane.consecutiveWaits++;
597
647
  return;
@@ -621,7 +671,9 @@ export class GoalContinuationCoordinator {
621
671
  lane.intent = undefined;
622
672
  return;
623
673
  }
624
- const prompt = buildContinuationPrompt(goal, intent.evaluation, taskPlan.reminder);
674
+ const prompt = intent.recoveredPrompt
675
+ ? attachTaskReminder(intent.recoveredPrompt, taskPlan.reminder)
676
+ : buildContinuationPrompt(goal, intent.evaluation, taskPlan.reminder);
625
677
  const admission = this.deps.admitTurn(lane.sessionId, prompt, intent.checkpoint, intent.controlLease);
626
678
  if (admission.kind === 'busy') {
627
679
  this.watchBusyLane(lane, admission.whenIdle);
@@ -720,7 +772,16 @@ export class GoalContinuationCoordinator {
720
772
  return;
721
773
  lane.intent = { ...lane.intent, checkpoint: goalCheckpoint(woken) };
722
774
  void this.deps.durability
723
- .flush(lane.sessionId)
775
+ .recordPendingContinuation({
776
+ checkpoint: lane.intent.checkpoint,
777
+ controlLease: lane.intent.controlLease,
778
+ prompt: lane.intent.recoveredPrompt ??
779
+ buildContinuationPrompt(woken, lane.intent.evaluation, undefined),
780
+ ...(lane.intent.triggeringTurnId
781
+ ? { triggeringTurnId: lane.intent.triggeringTurnId }
782
+ : {}),
783
+ })
784
+ .then(() => this.deps.durability.flush(lane.sessionId))
724
785
  .then(() => this.scheduleDrain(lane))
725
786
  .catch(() => undefined);
726
787
  }, delayMs);
@@ -778,6 +839,15 @@ function buildContinuationPrompt(goal, evaluation, taskReminder) {
778
839
  `\n\nEvaluation: ${evaluation.reason}${evaluation.waiting ? ' (scheduled external-event re-check)' : ''}\n` +
779
840
  `Goal: "${goal.condition}" (turn ${goal.iterations}/${goal.maxIterations}${noProgress})`);
780
841
  }
842
+ function attachTaskReminder(prompt, taskReminder) {
843
+ if (!taskReminder)
844
+ return prompt;
845
+ const evaluationMarker = '\n\nEvaluation:';
846
+ const evaluationAt = prompt.indexOf(evaluationMarker);
847
+ if (evaluationAt === -1)
848
+ return `${prompt}\n\n${taskReminder}`;
849
+ return `${prompt.slice(0, evaluationAt)}\n\n${taskReminder}${prompt.slice(evaluationAt)}`;
850
+ }
781
851
  function waitBackoffMs(consecutiveWaits) {
782
852
  return Math.min(GOAL_WAIT_BACKOFF_MAX_MS, GOAL_WAIT_BACKOFF_BASE_MS * 2 ** Math.max(0, consecutiveWaits - 1));
783
853
  }
@@ -158,6 +158,13 @@ export class GoalManager {
158
158
  get(sessionId) {
159
159
  return this.goals.get(sessionId)?.state;
160
160
  }
161
+ getSessionIdByGoalId(goalId) {
162
+ for (const [sessionId, record] of this.goals) {
163
+ if (record.state.id === goalId)
164
+ return sessionId;
165
+ }
166
+ return undefined;
167
+ }
161
168
  getActive(sessionId) {
162
169
  const goal = this.goals.get(sessionId)?.state;
163
170
  return goal?.status === 'active' ? goal : undefined;
@@ -0,0 +1,155 @@
1
+ /*
2
+ * Licensed to the Apache Software Foundation (ASF) under one
3
+ * or more contributor license agreements. See the NOTICE file
4
+ * distributed with this work for additional information
5
+ * regarding copyright ownership. The ASF licenses this file
6
+ * to you under the Apache License, Version 2.0 (the
7
+ * "License"); you may not use this file except in compliance
8
+ * with the License. You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing,
13
+ * software distributed under the License is distributed on an
14
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15
+ * KIND, either express or implied. See the License for the
16
+ * specific language governing permissions and limitations
17
+ * under the License.
18
+ */
19
+ import { spawn } from 'node:child_process';
20
+ export const GREP_MAX_LINES_PER_FILE = 50;
21
+ export const GREP_MAX_LINES = 200;
22
+ export const GREP_MAX_MATCH_BYTES = 24 * 1024;
23
+ const MAX_RECORD_BYTES = 256 * 1024;
24
+ const MAX_STDERR_BYTES = 16 * 1024;
25
+ export class GrepSearchError extends Error {
26
+ }
27
+ export async function searchFiles(input, run = runRipgrep) {
28
+ const matches = [];
29
+ let matchBytes = 2;
30
+ let returnedInFile = 0;
31
+ let matchedLines;
32
+ let pending = Buffer.alloc(0);
33
+ let oversized = false;
34
+ const perFile = Math.min(input.maxCountPerFile, GREP_MAX_LINES_PER_FILE);
35
+ const limit = Math.min(input.limit, GREP_MAX_LINES);
36
+ // Text mode prevents binary detection from stopping a file early. Single-threaded
37
+ // output keeps each file's begin/match/end records together without a growing map.
38
+ const args = ['--no-config', '--json', '--text', '--threads=1', '--line-buffered'];
39
+ if (input.glob)
40
+ args.push('--glob', input.glob);
41
+ args.push('--', input.pattern, input.path);
42
+ const result = await run({ ...input, args, onStdout });
43
+ if (result.exitCode !== 0 && result.exitCode !== 1) {
44
+ throw new GrepSearchError(`Grep failed; search totals are unknown. Check the regex and search path, then retry.\n${result.stderrTail.trim()}`);
45
+ }
46
+ if (pending.length || oversized || matchedLines === undefined || matchedLines < matches.length) {
47
+ throw new GrepSearchError('Grep did not produce a complete search summary; search totals are unknown. Retry with a narrower path or glob.');
48
+ }
49
+ return {
50
+ matchedLines,
51
+ returnedLines: matches.length,
52
+ omittedLines: matchedLines - matches.length,
53
+ truncated: matchedLines > matches.length,
54
+ matches,
55
+ };
56
+ function onStdout(chunk) {
57
+ let start = 0;
58
+ while (start < chunk.length) {
59
+ const newline = chunk.indexOf(10, start);
60
+ const end = newline === -1 ? chunk.length : newline;
61
+ if (!oversized) {
62
+ const part = chunk.subarray(start, end);
63
+ if (pending.length + part.length > MAX_RECORD_BYTES) {
64
+ oversized = true;
65
+ pending = Buffer.alloc(0);
66
+ }
67
+ else {
68
+ pending = Buffer.concat([pending, part]);
69
+ }
70
+ }
71
+ if (newline === -1)
72
+ break;
73
+ // Oversized match records are omitted, not shortened. The final engine
74
+ // summary still counts their lines without retaining their content.
75
+ if (!oversized)
76
+ consumeRecord(pending.toString('utf8'));
77
+ pending = Buffer.alloc(0);
78
+ oversized = false;
79
+ start = newline + 1;
80
+ }
81
+ }
82
+ function consumeRecord(line) {
83
+ const event = JSON.parse(line);
84
+ if (matchedLines !== undefined)
85
+ throw new GrepSearchError('Grep emitted data after its summary; search totals are unknown.');
86
+ if (event.type === 'begin')
87
+ returnedInFile = 0;
88
+ if (event.type === 'summary') {
89
+ const count = event.data.stats.matched_lines;
90
+ if (!Number.isSafeInteger(count) || count < 0)
91
+ throw new GrepSearchError('Grep returned an invalid line count; search totals are unknown.');
92
+ matchedLines = count;
93
+ }
94
+ if (event.type !== 'match' || matches.length >= limit || returnedInFile >= perFile)
95
+ return;
96
+ const { path, lines, line_number: lineNumber } = event.data;
97
+ // ripgrep uses base64 for non-UTF8 paths/content; do not pass a lossy decoded
98
+ // filename or pretend that a replacement-character preview is the original.
99
+ if (typeof path.text !== 'string' || typeof lines.text !== 'string')
100
+ return;
101
+ const match = `${path.text}:${lineNumber}:${lines.text.replace(/\r?\n$/, '')}`;
102
+ const bytes = Buffer.byteLength(JSON.stringify(match)) + (matches.length ? 1 : 0);
103
+ if (matchBytes + bytes > GREP_MAX_MATCH_BYTES)
104
+ return;
105
+ matchBytes += bytes;
106
+ matches.push(match);
107
+ returnedInFile++;
108
+ }
109
+ }
110
+ async function runRipgrep(input) {
111
+ if (input.abortSignal?.aborted)
112
+ throw new GrepSearchError('Grep was cancelled; search totals are unknown.');
113
+ return await new Promise((resolve, reject) => {
114
+ const child = spawn(input.executable, [...input.args], {
115
+ cwd: input.cwd,
116
+ shell: false,
117
+ stdio: ['ignore', 'pipe', 'pipe'],
118
+ });
119
+ let stderrTail = Buffer.alloc(0);
120
+ let failure;
121
+ const timer = setTimeout(() => fail(new GrepSearchError('Grep timed out; search totals are unknown. Retry with a narrower path or glob.')), input.timeoutMs);
122
+ const abort = () => fail(new GrepSearchError('Grep was cancelled; search totals are unknown.'));
123
+ input.abortSignal?.addEventListener('abort', abort, { once: true });
124
+ if (input.abortSignal?.aborted)
125
+ abort();
126
+ child.stdout.on('data', (chunk) => {
127
+ if (failure)
128
+ return;
129
+ try {
130
+ input.onStdout(chunk);
131
+ }
132
+ catch {
133
+ fail(new GrepSearchError('Grep returned invalid search output; search totals are unknown.'));
134
+ }
135
+ });
136
+ child.stderr.on('data', (chunk) => {
137
+ stderrTail = Buffer.concat([stderrTail, chunk]).subarray(-MAX_STDERR_BYTES);
138
+ });
139
+ child.once('error', (error) => {
140
+ failure ??= error;
141
+ });
142
+ child.once('close', (exitCode) => {
143
+ clearTimeout(timer);
144
+ input.abortSignal?.removeEventListener('abort', abort);
145
+ if (failure)
146
+ reject(failure);
147
+ else
148
+ resolve({ exitCode: exitCode ?? 2, stderrTail: stderrTail.toString('utf8') });
149
+ });
150
+ function fail(error) {
151
+ failure ??= error;
152
+ child.kill('SIGKILL');
153
+ }
154
+ });
155
+ }