@microck/canonfig 2.0.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 (68) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +263 -0
  3. package/dist/agent/agent-resolution.errors.js +42 -0
  4. package/dist/agent/agent-resolution.layer.js +204 -0
  5. package/dist/agent/agent-resolution.service.js +2259 -0
  6. package/dist/agent/agent-resolution.types.js +1 -0
  7. package/dist/agent/controlled-executor.js +704 -0
  8. package/dist/agent/harness-adapters.js +85 -0
  9. package/dist/cli/cli.js +618 -0
  10. package/dist/cli/exit-codes.js +28 -0
  11. package/dist/cli/follower-commands.js +3 -0
  12. package/dist/cli/render.js +56 -0
  13. package/dist/cli/source-commands.js +5 -0
  14. package/dist/domain/brand.js +29 -0
  15. package/dist/domain/identity.js +31 -0
  16. package/dist/domain/npm-package-spec.js +186 -0
  17. package/dist/domain/profile.js +950 -0
  18. package/dist/domain/recipe-versions.js +297 -0
  19. package/dist/domain/resource.js +259 -0
  20. package/dist/domain/synchronization.js +346 -0
  21. package/dist/enrollment/enrollment.errors.js +43 -0
  22. package/dist/enrollment/enrollment.layer.js +724 -0
  23. package/dist/enrollment/enrollment.service.js +3 -0
  24. package/dist/enrollment/enrollment.types.js +59 -0
  25. package/dist/enrollment/follower-client.js +585 -0
  26. package/dist/enrollment/source-server.js +313 -0
  27. package/dist/machine/linux.layer.js +1183 -0
  28. package/dist/machine/machine-state.errors.js +52 -0
  29. package/dist/machine/machine-state.service.js +3 -0
  30. package/dist/machine/machine-state.types.js +1 -0
  31. package/dist/machine/macos.layer.js +470 -0
  32. package/dist/machine/windows.layer.js +879 -0
  33. package/dist/profile/discovery.js +740 -0
  34. package/dist/profile/profile-catalog.errors.js +50 -0
  35. package/dist/profile/profile-catalog.layer.js +20 -0
  36. package/dist/profile/profile-catalog.service.js +7 -0
  37. package/dist/profile/profile-codec.js +153 -0
  38. package/dist/profile/publication.js +298 -0
  39. package/dist/profile/tool-catalog.js +384 -0
  40. package/dist/runtime/doctor.js +306 -0
  41. package/dist/runtime/layers.js +706 -0
  42. package/dist/runtime/main.js +38 -0
  43. package/dist/schedule/linux-schedule.js +24 -0
  44. package/dist/schedule/macos-schedule.js +25 -0
  45. package/dist/schedule/schedule-manager.errors.js +17 -0
  46. package/dist/schedule/schedule-manager.layer.js +205 -0
  47. package/dist/schedule/schedule-manager.service.js +3 -0
  48. package/dist/schedule/schedule-manager.types.js +114 -0
  49. package/dist/schedule/windows-schedule.js +25 -0
  50. package/dist/state/state-repository.errors.js +55 -0
  51. package/dist/state/state-repository.layer.js +1507 -0
  52. package/dist/state/state-repository.service.js +3 -0
  53. package/dist/state/state-repository.types.js +1 -0
  54. package/dist/state/state-schema.js +298 -0
  55. package/dist/synchronization/config-codec.js +97 -0
  56. package/dist/synchronization/executor.js +700 -0
  57. package/dist/synchronization/follower-orchestration.js +939 -0
  58. package/dist/synchronization/follower-sync-config.js +81 -0
  59. package/dist/synchronization/npm-artifact.js +670 -0
  60. package/dist/synchronization/planner.js +378 -0
  61. package/dist/synchronization/recovery.js +397 -0
  62. package/dist/synchronization/resource-executors.js +1198 -0
  63. package/dist/synchronization/resource-plans.js +645 -0
  64. package/dist/synchronization/synchronization.errors.js +102 -0
  65. package/dist/synchronization/synchronization.layer.js +97 -0
  66. package/dist/synchronization/synchronization.service.js +11 -0
  67. package/dist/synchronization/synchronization.types.js +1 -0
  68. package/package.json +66 -0
@@ -0,0 +1,704 @@
1
+ import { createHash } from "node:crypto";
2
+ import { spawn } from "node:child_process";
3
+ import { lstat, open, realpath } from "node:fs/promises";
4
+ import { win32 } from "node:path";
5
+ import { Effect } from "effect";
6
+ import { AgentExecutionCancelledError, AgentExecutionTimeoutError, AgentInputLimitError, AgentOutputLimitError, AgentProcessError, } from "./agent-resolution.errors.js";
7
+ const decoder = new TextDecoder();
8
+ const terminateProcessTree = (child) => {
9
+ const processId = child.pid;
10
+ if (processId === undefined) {
11
+ child.kill("SIGKILL");
12
+ return Promise.resolve();
13
+ }
14
+ if (process.platform === "win32") {
15
+ return new Promise((resolveTermination) => {
16
+ let finished = false;
17
+ let timer;
18
+ const finish = () => {
19
+ if (finished)
20
+ return;
21
+ finished = true;
22
+ if (timer !== undefined)
23
+ clearTimeout(timer);
24
+ child.kill("SIGKILL");
25
+ resolveTermination();
26
+ };
27
+ const killer = spawn(win32.join(process.env.SystemRoot ?? "C:\\Windows", "System32", "taskkill.exe"), ["/pid", String(processId), "/t", "/f"], {
28
+ shell: false,
29
+ stdio: "ignore",
30
+ windowsHide: true,
31
+ });
32
+ killer.once("error", finish);
33
+ killer.once("close", finish);
34
+ timer = setTimeout(() => {
35
+ killer.kill();
36
+ finish();
37
+ }, 5_000);
38
+ });
39
+ }
40
+ try {
41
+ process.kill(-processId, "SIGKILL");
42
+ }
43
+ catch {
44
+ child.kill("SIGKILL");
45
+ }
46
+ return Promise.resolve();
47
+ };
48
+ const sameRequirementIdentity = (left, right) => left.dev === right.dev
49
+ && left.ino === right.ino
50
+ && left.size === right.size;
51
+ const pipRequirementFilesUnchanged = async (files) => {
52
+ for (const expected of files) {
53
+ let handle;
54
+ try {
55
+ const before = await lstat(expected.path);
56
+ if (!before.isFile() || !sameRequirementIdentity(before, expected.identity)) {
57
+ return false;
58
+ }
59
+ if (await realpath(expected.path) !== expected.canonicalPath)
60
+ return false;
61
+ handle = await open(expected.path, "r");
62
+ const opened = await handle.stat();
63
+ if (!sameRequirementIdentity(opened, expected.identity))
64
+ return false;
65
+ const buffer = Buffer.alloc(expected.identity.size + 1);
66
+ const { bytesRead } = await handle.read(buffer, 0, buffer.byteLength, 0);
67
+ if (bytesRead !== expected.identity.size)
68
+ return false;
69
+ const after = await lstat(expected.path);
70
+ if (!sameRequirementIdentity(after, expected.identity))
71
+ return false;
72
+ if (createHash("sha256")
73
+ .update(buffer.subarray(0, bytesRead))
74
+ .digest("hex") !== expected.digest)
75
+ return false;
76
+ }
77
+ catch {
78
+ return false;
79
+ }
80
+ finally {
81
+ if (handle !== undefined)
82
+ await handle.close().catch(() => undefined);
83
+ }
84
+ }
85
+ return true;
86
+ };
87
+ export const redactText = (value, secrets) => {
88
+ let redacted = value;
89
+ const ordered = [...new Set(secrets)]
90
+ .filter((secret) => secret.length > 0)
91
+ .sort((left, right) => right.length - left.length);
92
+ for (const secret of ordered)
93
+ redacted = redacted.replaceAll(secret, "[REDACTED]");
94
+ return redacted;
95
+ };
96
+ const packageManagerName = (value) => {
97
+ const name = value
98
+ .replaceAll("\\", "/")
99
+ .split("/")
100
+ .at(-1)
101
+ ?.toLowerCase()
102
+ .replace(/\.(?:cmd|exe|bat|com|ps1)$/u, "")
103
+ .replace(/^(?:npm)-cli\.js$/u, "npm")
104
+ .replace(/^(?:pnpm)\.(?:cjs|js)$/u, "pnpm")
105
+ .replace(/^(?:yarn)\.js$/u, "yarn")
106
+ ?? "";
107
+ return /^pip(?:3(?:\.\d+(?:\.\d+)*)?|-3(?:\.\d+(?:\.\d+)*)?)?$/u.test(name)
108
+ ? "pip"
109
+ : name;
110
+ };
111
+ const isRegistryPackageManager = (manager) => manager === "npm"
112
+ || manager === "pnpm"
113
+ || manager === "yarn"
114
+ || manager === "bun"
115
+ || manager === "pip"
116
+ || manager === "uv";
117
+ const canonicalRegistryOrigin = (value) => {
118
+ return canonicalRegistryUrl(value)?.origin;
119
+ };
120
+ const hasUnsafeUrlCharacter = (value) => [...value].some((character) => {
121
+ const code = character.codePointAt(0) ?? 0;
122
+ return code <= 0x20
123
+ || (code >= 0x7f && code <= 0x9f)
124
+ || "\"'<>\\ ".includes(character);
125
+ });
126
+ const hasExplicitUrlCredentialOrFragment = (value) => {
127
+ if (value.includes("#"))
128
+ return true;
129
+ const authority = /^https?:\/\/([^/?#]*)/iu.exec(value)?.[1];
130
+ return authority?.includes("@") ?? false;
131
+ };
132
+ const canonicalRegistryUrl = (value) => {
133
+ if (value.trim() !== value
134
+ || hasUnsafeUrlCharacter(value)
135
+ || hasExplicitUrlCredentialOrFragment(value))
136
+ return undefined;
137
+ try {
138
+ const url = new URL(value);
139
+ if (url.protocol !== "https:"
140
+ || url.username.length > 0
141
+ || url.password.length > 0
142
+ || url.hash.length > 0
143
+ || url.hostname.length === 0)
144
+ return undefined;
145
+ return {
146
+ url: url.pathname === "/" && url.search.length === 0
147
+ ? url.origin
148
+ : url.href,
149
+ origin: url.origin,
150
+ };
151
+ }
152
+ catch {
153
+ return undefined;
154
+ }
155
+ };
156
+ const packageOperationRequiresRegistry = (executable, arguments_) => {
157
+ const manager = packageManagerName(executable);
158
+ if (!isRegistryPackageManager(manager))
159
+ return false;
160
+ const optionsWithValues = manager === "uv"
161
+ ? uvCommandOptionsWithValues
162
+ : manager === "pip"
163
+ ? new Set([
164
+ "--cache-dir",
165
+ "--cert",
166
+ "--client-cert",
167
+ "--config-settings",
168
+ "--config-setting",
169
+ "--constraint",
170
+ "-c",
171
+ "--extra-index-url",
172
+ "--find-links",
173
+ "-f",
174
+ "-i",
175
+ "--index-url",
176
+ "--proxy",
177
+ "--requirement",
178
+ "-r",
179
+ "--trusted-host",
180
+ ])
181
+ : manager === "bun"
182
+ ? new Set(["--config", "--cwd", "--filter", "--registry"])
183
+ : new Set([
184
+ "-C",
185
+ "--cache",
186
+ "--config-dir",
187
+ "--dir",
188
+ "--global-bin-dir",
189
+ "--global-dir",
190
+ "--prefix",
191
+ "--registry",
192
+ "--store-dir",
193
+ "--userconfig",
194
+ "--virtual-store-dir",
195
+ "--workspace-dir",
196
+ ]);
197
+ let command;
198
+ let commandIndex = -1;
199
+ for (let index = 0; index < arguments_.length; index += 1) {
200
+ const argument = arguments_[index];
201
+ if (argument === "--")
202
+ return false;
203
+ if (!argument.startsWith("-") || argument === "-") {
204
+ command = argument.toLowerCase();
205
+ commandIndex = index;
206
+ break;
207
+ }
208
+ if (!argument.includes("=")
209
+ && optionsWithValues.has(argument.split("=", 1)[0].toLowerCase()))
210
+ index += 1;
211
+ }
212
+ if (manager === "uv") {
213
+ return ((command === "tool" || command === "pip")
214
+ && arguments_[commandIndex + 1]?.toLowerCase() === "install");
215
+ }
216
+ if (manager === "pip")
217
+ return command === "install";
218
+ if (manager === "bun") {
219
+ return command !== undefined && ["add", "i", "install", "update"].includes(command);
220
+ }
221
+ return command !== undefined && [
222
+ "add",
223
+ "ci",
224
+ "i",
225
+ "in",
226
+ "ins",
227
+ "inst",
228
+ "insta",
229
+ "instal",
230
+ "install",
231
+ "info",
232
+ "list",
233
+ "ls",
234
+ "outdated",
235
+ "prefix",
236
+ "root",
237
+ "search",
238
+ "view",
239
+ "why",
240
+ ].includes(command);
241
+ };
242
+ const isUvFindLinksOption = (argument) => {
243
+ const lower = argument.toLowerCase();
244
+ return lower === "--find-links"
245
+ || lower.startsWith("--find-links=")
246
+ || (lower.startsWith("-")
247
+ && !lower.startsWith("--")
248
+ && lower.slice(1).includes("f"));
249
+ };
250
+ const uvRequirementFileOptions = new Set([
251
+ "--build-constraints",
252
+ "--constraint",
253
+ "--constraints",
254
+ "--excludes",
255
+ "--overrides",
256
+ "--requirement",
257
+ "--requirements",
258
+ "--with-requirements",
259
+ ]);
260
+ const uvCommandOptionsWithValues = new Set([
261
+ "--cache-dir",
262
+ "--color",
263
+ "--config-file",
264
+ "--default-index",
265
+ "--directory",
266
+ "--extra-index-url",
267
+ "--find-links",
268
+ "--index",
269
+ "--index-url",
270
+ "--project",
271
+ "-f",
272
+ ]);
273
+ const uvInstallCommand = (arguments_) => {
274
+ let command;
275
+ for (let index = 0; index < arguments_.length; index += 1) {
276
+ const argument = arguments_[index];
277
+ if (!argument.startsWith("-") || argument === "-") {
278
+ command = argument.toLowerCase();
279
+ break;
280
+ }
281
+ if (!argument.includes("=")
282
+ && uvCommandOptionsWithValues.has(argument.split("=", 1)[0].toLowerCase()))
283
+ index += 1;
284
+ }
285
+ return command === "pip" || command === "tool" ? command : undefined;
286
+ };
287
+ const hasUvRequirementFileShortOption = (argument, command) => {
288
+ if (!argument.startsWith("-") || argument.startsWith("--"))
289
+ return false;
290
+ const optionsWithoutValues = new Set([
291
+ "h",
292
+ "n",
293
+ "q",
294
+ "U",
295
+ "v",
296
+ ...(command === "tool" ? ["e"] : []),
297
+ ]);
298
+ const requirementOptions = new Set([
299
+ "b",
300
+ "c",
301
+ ...(command === "pip" ? ["r"] : []),
302
+ ]);
303
+ for (const option of argument.slice(1)) {
304
+ if (requirementOptions.has(option))
305
+ return true;
306
+ if (!optionsWithoutValues.has(option))
307
+ return false;
308
+ }
309
+ return false;
310
+ };
311
+ const isUvRequirementFileOption = (argument, command) => {
312
+ const name = argument.split("=", 1)[0].toLowerCase();
313
+ return uvRequirementFileOptions.has(name)
314
+ || hasUvRequirementFileShortOption(argument, command);
315
+ };
316
+ const hasUvRequirementFileOption = (arguments_) => {
317
+ const command = uvInstallCommand(arguments_);
318
+ return arguments_.some((argument) => isUvRequirementFileOption(argument, command));
319
+ };
320
+ const uvInsecureHostOptions = new Set([
321
+ "--allow-insecure-host",
322
+ "--trusted-host",
323
+ ]);
324
+ const isUvInsecureHostOption = (argument) => uvInsecureHostOptions.has(argument.split("=", 1)[0].toLowerCase());
325
+ const packageRegistryInvocationIsSafe = (executable, arguments_, packageRegistryOrigin) => {
326
+ const manager = packageManagerName(executable);
327
+ const registry = canonicalRegistryOrigin(packageRegistryOrigin);
328
+ if (registry === undefined)
329
+ return false;
330
+ if (arguments_.includes("--"))
331
+ return false;
332
+ if (manager === "uv" && hasUvRequirementFileOption(arguments_))
333
+ return false;
334
+ const indexOptions = manager === "uv"
335
+ ? new Set([
336
+ "--default-index",
337
+ "--index-url",
338
+ "--extra-index-url",
339
+ "-f",
340
+ "--find-links",
341
+ "--index",
342
+ ])
343
+ : manager === "pip"
344
+ ? new Set(["-i", "--index-url", "--extra-index-url", "-f", "--find-links"])
345
+ : new Set();
346
+ const unsafeOptions = manager === "uv"
347
+ ? new Set(["--config-file"])
348
+ : manager === "pip"
349
+ ? new Set([
350
+ "--cert",
351
+ "--client-cert",
352
+ "--config-setting",
353
+ "--config-settings",
354
+ "--proxy",
355
+ "--trusted-host",
356
+ ])
357
+ : new Set();
358
+ for (let index = 0; index < arguments_.length; index += 1) {
359
+ const argument = arguments_[index];
360
+ const separator = argument.indexOf("=");
361
+ const name = (separator > 0 ? argument.slice(0, separator) : argument)
362
+ .toLowerCase();
363
+ if (unsafeOptions.has(name))
364
+ return false;
365
+ if (manager === "uv"
366
+ && isUvInsecureHostOption(argument))
367
+ return false;
368
+ if (manager === "uv"
369
+ && (isUvFindLinksOption(argument)
370
+ || ["--extra-index-url", "--index"].includes(name)))
371
+ return false;
372
+ if ((manager === "uv" && name === "--no-config")
373
+ || (manager === "pip" && name === "--isolated")) {
374
+ if (separator > 0 && argument.slice(separator + 1).toLowerCase() !== "true") {
375
+ return false;
376
+ }
377
+ }
378
+ if (!indexOptions.has(name))
379
+ continue;
380
+ const value = separator > 0
381
+ ? argument.slice(separator + 1)
382
+ : arguments_[index + 1];
383
+ if (separator === -1)
384
+ index += 1;
385
+ if (value === undefined || canonicalRegistryOrigin(value) !== registry) {
386
+ return false;
387
+ }
388
+ }
389
+ return true;
390
+ };
391
+ const hasPipRequirementFileOption = (arguments_) => arguments_.some((argument) => {
392
+ const name = argument.split("=", 1)[0].toLowerCase();
393
+ return name === "-r"
394
+ || name === "-c"
395
+ || name === "--requirement"
396
+ || name === "--constraint"
397
+ || (argument.length > 2
398
+ && (argument.startsWith("-r") || argument.startsWith("-c")));
399
+ });
400
+ const protectedPackageEnvironment = (name) => {
401
+ const lower = name.toLowerCase();
402
+ return lower.startsWith("npm_config_")
403
+ || lower.startsWith("pnpm_config_")
404
+ || lower.startsWith("bun_config_")
405
+ || lower.startsWith("yarn_")
406
+ || lower.startsWith("uv_")
407
+ || lower.startsWith("pip_")
408
+ || lower === "http_proxy"
409
+ || lower === "https_proxy"
410
+ || lower === "ftp_proxy"
411
+ || lower === "all_proxy"
412
+ || lower === "no_proxy"
413
+ || lower === "netrc"
414
+ || lower === "requests_ca_bundle"
415
+ || lower === "curl_ca_bundle"
416
+ || lower === "ssl_cert_file"
417
+ || lower === "ssl_cert_dir"
418
+ || lower === "node_tls_reject_unauthorized"
419
+ || lower === "node_extra_ca_certs";
420
+ };
421
+ const packageManagerConfigurationEnvironment = (executable) => {
422
+ const manager = packageManagerName(executable);
423
+ const emptyConfiguration = process.platform === "win32" ? "NUL" : "/dev/null";
424
+ if (manager === "npm" || manager === "pnpm") {
425
+ return [
426
+ { name: "NPM_CONFIG_USERCONFIG", value: emptyConfiguration },
427
+ { name: "NPM_CONFIG_GLOBALCONFIG", value: emptyConfiguration },
428
+ { name: "NPM_CONFIG_LOCATION", value: "global" },
429
+ ];
430
+ }
431
+ if (manager === "bun") {
432
+ return [{ name: "BUN_CONFIG_FILE", value: emptyConfiguration }];
433
+ }
434
+ if (manager === "uv") {
435
+ return [
436
+ { name: "UV_CONFIG_FILE", value: emptyConfiguration },
437
+ { name: "PIP_CONFIG_FILE", value: emptyConfiguration },
438
+ ];
439
+ }
440
+ if (manager === "pip") {
441
+ return [{ name: "PIP_CONFIG_FILE", value: emptyConfiguration }];
442
+ }
443
+ if (manager === "yarn") {
444
+ return [{ name: "YARN_RC_FILENAME", value: emptyConfiguration }];
445
+ }
446
+ return [];
447
+ };
448
+ export const sanitizedPackageManagerEnvironment = (executable, environment = [], packageRegistryOrigin, packageRegistryScopes = []) => {
449
+ const manager = packageManagerName(executable);
450
+ if (!isRegistryPackageManager(manager))
451
+ return environment;
452
+ const registry = packageRegistryOrigin === undefined
453
+ ? undefined
454
+ : canonicalRegistryUrl(packageRegistryOrigin)?.url;
455
+ return [
456
+ ...environment.filter((entry) => !protectedPackageEnvironment(entry.name)),
457
+ ...packageManagerConfigurationEnvironment(executable),
458
+ ...(registry === undefined
459
+ ? []
460
+ : manager === "npm" || manager === "pnpm"
461
+ ? [
462
+ { name: "NPM_CONFIG_REGISTRY", value: registry },
463
+ ...(manager === "pnpm"
464
+ ? [{ name: "PNPM_CONFIG_REGISTRY", value: registry }]
465
+ : []),
466
+ ...packageRegistryScopes.map((scope) => ({
467
+ name: `npm_config_${scope}:registry`,
468
+ value: registry,
469
+ })),
470
+ ...(manager === "pnpm"
471
+ ? packageRegistryScopes.map((scope) => ({
472
+ name: `pnpm_config_${scope}:registry`,
473
+ value: registry,
474
+ }))
475
+ : []),
476
+ ]
477
+ : manager === "bun"
478
+ ? [{ name: "BUN_CONFIG_REGISTRY", value: registry }]
479
+ : manager === "uv"
480
+ ? [
481
+ { name: "UV_DEFAULT_INDEX", value: registry },
482
+ { name: "UV_INDEX_URL", value: registry },
483
+ { name: "PIP_INDEX_URL", value: registry },
484
+ ]
485
+ : manager === "pip"
486
+ ? [{ name: "PIP_INDEX_URL", value: registry }]
487
+ : manager === "yarn"
488
+ ? [{ name: "YARN_NPM_REGISTRY_SERVER", value: registry }]
489
+ : []),
490
+ ];
491
+ };
492
+ export const controlledEnvironment = (entries, unset = []) => {
493
+ const environment = { ...process.env };
494
+ const blocked = new Set(unset.map((name) => name.toLowerCase()));
495
+ for (const name of Object.keys(environment)) {
496
+ if (blocked.has(name.toLowerCase()))
497
+ delete environment[name];
498
+ }
499
+ for (const entry of entries) {
500
+ if (entry.name.includes("=") || entry.name.length === 0)
501
+ continue;
502
+ if (process.platform === "win32") {
503
+ const existing = Object.keys(environment).find((name) => name.toLowerCase() === entry.name.toLowerCase());
504
+ if (existing !== undefined)
505
+ delete environment[existing];
506
+ }
507
+ environment[entry.name] = entry.value;
508
+ }
509
+ return environment;
510
+ };
511
+ const cancelled = (input) => new AgentExecutionCancelledError({ executable: input.executable });
512
+ export const executeControlledProcess = (input) => {
513
+ const inputBytes = input.standardInput?.byteLength ?? 0;
514
+ if (inputBytes > input.maximumInputBytes) {
515
+ return Effect.fail(new AgentInputLimitError({
516
+ actualBytes: inputBytes,
517
+ maximumBytes: input.maximumInputBytes,
518
+ }));
519
+ }
520
+ if (input.signal?.aborted === true)
521
+ return Effect.fail(cancelled(input));
522
+ if (isRegistryPackageManager(packageManagerName(input.executable))
523
+ && input.arguments.includes("--")) {
524
+ return Effect.fail(new AgentProcessError({
525
+ executable: input.executable,
526
+ message: "package-manager separator form is not authorized",
527
+ }));
528
+ }
529
+ if (packageManagerName(input.executable) === "uv"
530
+ && hasUvRequirementFileOption(input.arguments)) {
531
+ return Effect.fail(new AgentProcessError({
532
+ executable: input.executable,
533
+ message: "uv requirement, constraint, or override files are not authorized",
534
+ }));
535
+ }
536
+ if (packageManagerName(input.executable) === "uv"
537
+ && input.arguments.some(isUvInsecureHostOption)) {
538
+ return Effect.fail(new AgentProcessError({
539
+ executable: input.executable,
540
+ message: "uv insecure-host overrides are not authorized",
541
+ }));
542
+ }
543
+ if (packageManagerName(input.executable) === "pip"
544
+ && hasPipRequirementFileOption(input.arguments)
545
+ && input.pipRequirementFiles === undefined) {
546
+ return Effect.fail(new AgentProcessError({
547
+ executable: input.executable,
548
+ message: "pip requirement files are not authorized by the resolution boundary",
549
+ }));
550
+ }
551
+ if (packageManagerName(input.executable) === "pip"
552
+ && hasPipRequirementFileOption(input.arguments)
553
+ && input.pipRequirementFiles !== undefined
554
+ && input.pipRequirementFiles.length === 0) {
555
+ return Effect.fail(new AgentProcessError({
556
+ executable: input.executable,
557
+ message: "pip requirement files are not authorized by the resolution boundary",
558
+ }));
559
+ }
560
+ if (packageOperationRequiresRegistry(input.executable, input.arguments)
561
+ && (input.packageRegistryOrigin === undefined
562
+ || !packageRegistryInvocationIsSafe(input.executable, input.arguments, input.packageRegistryOrigin))) {
563
+ return Effect.fail(new AgentProcessError({
564
+ executable: input.executable,
565
+ message: "package-manager registry origin is not explicitly authorized",
566
+ }));
567
+ }
568
+ return Effect.tryPromise({
569
+ try: async (effectSignal) => {
570
+ if (packageManagerName(input.executable) === "pip"
571
+ && input.pipRequirementFiles !== undefined
572
+ && !(await pipRequirementFilesUnchanged(input.pipRequirementFiles))) {
573
+ throw new AgentProcessError({
574
+ executable: input.executable,
575
+ message: "pip requirement or constraint input changed after authorization",
576
+ });
577
+ }
578
+ return new Promise((resolve, reject) => {
579
+ const output = [];
580
+ const errors = [];
581
+ let capturedBytes = 0;
582
+ let settled = false;
583
+ let limitExceeded = false;
584
+ let timedOut = false;
585
+ let wasCancelled = false;
586
+ let termination;
587
+ const child = spawn(input.executable, [...input.arguments], {
588
+ cwd: input.workingDirectory,
589
+ detached: process.platform !== "win32",
590
+ env: (() => {
591
+ const manager = packageManagerName(input.executable);
592
+ const packageManager = isRegistryPackageManager(manager);
593
+ const inherited = input.environment ?? [];
594
+ const protectedInput = inherited
595
+ .filter((entry) => protectedPackageEnvironment(entry.name))
596
+ .map((entry) => entry.name);
597
+ const environment = sanitizedPackageManagerEnvironment(input.executable, inherited, input.packageRegistryOrigin, input.packageRegistryScopes);
598
+ return controlledEnvironment(environment, [
599
+ ...(input.environmentUnset ?? []),
600
+ ...protectedInput,
601
+ ...(input.environmentUnsetPrefixes ?? []).flatMap((prefix) => Object.keys(process.env).filter((name) => name.toLowerCase().startsWith(prefix.toLowerCase()))),
602
+ ...(packageManager
603
+ ? Object.keys(process.env).filter(protectedPackageEnvironment)
604
+ : []),
605
+ ]);
606
+ })(),
607
+ shell: false,
608
+ stdio: ["pipe", "pipe", "pipe"],
609
+ });
610
+ const finishWith = (cause) => {
611
+ if (settled)
612
+ return;
613
+ settled = true;
614
+ reject(cause);
615
+ };
616
+ const terminate = () => {
617
+ termination ??= terminateProcessTree(child);
618
+ };
619
+ const capture = (target) => (chunk) => {
620
+ capturedBytes += chunk.byteLength;
621
+ if (capturedBytes > input.maximumOutputBytes) {
622
+ limitExceeded = true;
623
+ terminate();
624
+ return;
625
+ }
626
+ target.push(chunk);
627
+ };
628
+ child.stdout.on("data", capture(output));
629
+ child.stderr.on("data", capture(errors));
630
+ child.once("error", (cause) => {
631
+ finishWith(new AgentProcessError({
632
+ executable: input.executable,
633
+ message: redactText(String(cause), input.secrets),
634
+ }));
635
+ });
636
+ const timer = setTimeout(() => {
637
+ timedOut = true;
638
+ terminate();
639
+ }, input.timeoutMilliseconds);
640
+ const abort = () => {
641
+ wasCancelled = true;
642
+ terminate();
643
+ };
644
+ effectSignal.addEventListener("abort", abort, { once: true });
645
+ input.signal?.addEventListener("abort", abort, { once: true });
646
+ child.once("close", (exitCode, signal) => {
647
+ const complete = () => {
648
+ clearTimeout(timer);
649
+ effectSignal.removeEventListener("abort", abort);
650
+ input.signal?.removeEventListener("abort", abort);
651
+ if (settled)
652
+ return;
653
+ if (wasCancelled) {
654
+ finishWith(cancelled(input));
655
+ return;
656
+ }
657
+ if (timedOut) {
658
+ finishWith(new AgentExecutionTimeoutError({
659
+ executable: input.executable,
660
+ timeoutMilliseconds: input.timeoutMilliseconds,
661
+ }));
662
+ return;
663
+ }
664
+ if (limitExceeded) {
665
+ finishWith(new AgentOutputLimitError({
666
+ executable: input.executable,
667
+ maximumBytes: input.maximumOutputBytes,
668
+ }));
669
+ return;
670
+ }
671
+ settled = true;
672
+ resolve({
673
+ executable: input.executable,
674
+ arguments: input.arguments,
675
+ exitCode,
676
+ signal,
677
+ outputBytes: capturedBytes,
678
+ stdout: redactText(decoder.decode(Buffer.concat(output)), input.secrets),
679
+ stderr: redactText(decoder.decode(Buffer.concat(errors)), input.secrets),
680
+ });
681
+ };
682
+ if (termination === undefined)
683
+ complete();
684
+ else
685
+ void termination.then(complete);
686
+ });
687
+ if (input.standardInput === undefined)
688
+ child.stdin.end();
689
+ else
690
+ child.stdin.end(input.standardInput);
691
+ });
692
+ },
693
+ catch: (cause) => cause instanceof AgentInputLimitError
694
+ || cause instanceof AgentExecutionCancelledError
695
+ || cause instanceof AgentExecutionTimeoutError
696
+ || cause instanceof AgentOutputLimitError
697
+ || cause instanceof AgentProcessError
698
+ ? cause
699
+ : new AgentProcessError({
700
+ executable: input.executable,
701
+ message: redactText(String(cause), input.secrets),
702
+ }),
703
+ });
704
+ };