intentdna 1.9.0 → 1.9.3

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 (131) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/README.md +227 -87
  4. package/dist/cli/commands/run-lifecycle.d.ts +61 -11
  5. package/dist/cli/commands/run-lifecycle.js +184 -50
  6. package/dist/cli/commands/run-observer.d.ts +15 -0
  7. package/dist/cli/commands/run-observer.js +210 -0
  8. package/dist/cli/commands/run.d.ts +16 -20
  9. package/dist/cli/commands/run.js +461 -659
  10. package/dist/cli/commands/sync.js +16 -12
  11. package/dist/cli/index.js +14 -5
  12. package/dist/compiler/compile.d.ts +1 -0
  13. package/dist/compiler/compile.js +1 -1
  14. package/dist/compiler/controller.d.ts +16 -0
  15. package/dist/compiler/controller.js +693 -0
  16. package/dist/compiler/input-resolver.d.ts +2 -0
  17. package/dist/compiler/input-resolver.js +22 -6
  18. package/dist/hooks/cli.d.ts +45 -0
  19. package/dist/hooks/cli.js +263 -31
  20. package/dist/hooks/host-event.d.ts +57 -0
  21. package/dist/hooks/host-event.js +187 -0
  22. package/dist/hooks/index.d.ts +1 -0
  23. package/dist/hooks/index.js +1 -0
  24. package/dist/hooks/protocol.d.ts +7 -0
  25. package/dist/hooks/schema.d.ts +1 -0
  26. package/dist/hooks/schema.js +89 -1
  27. package/dist/hooks/state.d.ts +23 -1
  28. package/dist/hooks/state.js +9 -0
  29. package/dist/index.d.ts +1 -0
  30. package/dist/index.js +1 -0
  31. package/dist/mcp/index.js +2 -0
  32. package/dist/mcp/tools-run.d.ts +4 -0
  33. package/dist/mcp/tools-run.js +419 -0
  34. package/dist/mcp/tools-state.d.ts +1 -1
  35. package/dist/mcp/tools-state.js +13 -241
  36. package/dist/runtime/artifact-store.d.ts +146 -0
  37. package/dist/runtime/artifact-store.js +1436 -0
  38. package/dist/runtime/canonical-attempt-outcome.d.ts +12 -0
  39. package/dist/runtime/canonical-attempt-outcome.js +195 -0
  40. package/dist/runtime/canonical-json.d.ts +10 -0
  41. package/dist/runtime/canonical-json.js +105 -0
  42. package/dist/runtime/canonical-run-application.d.ts +93 -0
  43. package/dist/runtime/canonical-run-application.js +229 -0
  44. package/dist/runtime/canonical-run-service.d.ts +454 -0
  45. package/dist/runtime/canonical-run-service.js +4760 -0
  46. package/dist/runtime/canonical-runtime-composition.d.ts +22 -0
  47. package/dist/runtime/canonical-runtime-composition.js +34 -0
  48. package/dist/runtime/canonical-runtime-projection.d.ts +3 -0
  49. package/dist/runtime/canonical-runtime-projection.js +4 -0
  50. package/dist/runtime/canonical-target-compiler.d.ts +46 -0
  51. package/dist/runtime/canonical-target-compiler.js +514 -0
  52. package/dist/runtime/claude-agent-identity.d.ts +2 -0
  53. package/dist/runtime/claude-agent-identity.js +21 -0
  54. package/dist/runtime/executable-run-plan.d.ts +328 -0
  55. package/dist/runtime/executable-run-plan.js +1485 -0
  56. package/dist/runtime/execution-authority.d.ts +83 -0
  57. package/dist/runtime/execution-authority.js +98 -0
  58. package/dist/runtime/handoff-resolver.js +4 -1
  59. package/dist/runtime/harness-pull-adapter.d.ts +146 -0
  60. package/dist/runtime/harness-pull-adapter.js +315 -0
  61. package/dist/runtime/index.d.ts +39 -14
  62. package/dist/runtime/index.js +29 -7
  63. package/dist/runtime/local-execution-authority.d.ts +194 -0
  64. package/dist/runtime/local-execution-authority.js +2897 -0
  65. package/dist/runtime/local-execution-reconciliation.d.ts +20 -0
  66. package/dist/runtime/local-execution-reconciliation.js +107 -0
  67. package/dist/runtime/local-execution-supervisor-script.d.ts +7 -0
  68. package/dist/runtime/local-execution-supervisor-script.js +793 -0
  69. package/dist/runtime/local-provider-sandbox.d.ts +42 -0
  70. package/dist/runtime/local-provider-sandbox.js +154 -0
  71. package/dist/runtime/local-verifier-execution.d.ts +24 -0
  72. package/dist/runtime/local-verifier-execution.js +290 -0
  73. package/dist/runtime/local-windows-execution-supervisor-script.d.ts +6 -0
  74. package/dist/runtime/local-windows-execution-supervisor-script.js +788 -0
  75. package/dist/runtime/plan-store.d.ts +30 -0
  76. package/dist/runtime/plan-store.js +231 -0
  77. package/dist/runtime/process-tree.d.ts +9 -0
  78. package/dist/runtime/process-tree.js +97 -24
  79. package/dist/runtime/providers/codex.js +2 -2
  80. package/dist/runtime/push-driver.d.ts +184 -0
  81. package/dist/runtime/push-driver.js +1002 -0
  82. package/dist/runtime/result-store.d.ts +34 -3
  83. package/dist/runtime/result-store.js +1073 -2
  84. package/dist/runtime/run-binding.d.ts +68 -0
  85. package/dist/runtime/run-binding.js +278 -0
  86. package/dist/runtime/run-contracts.d.ts +575 -0
  87. package/dist/runtime/run-contracts.js +58 -7
  88. package/dist/runtime/run-controller.d.ts +1 -6
  89. package/dist/runtime/run-controller.js +0 -41
  90. package/dist/runtime/run-store.d.ts +85 -7
  91. package/dist/runtime/run-store.js +2528 -186
  92. package/dist/runtime/skill-adapter.d.ts +18 -7
  93. package/dist/runtime/skill-adapter.js +100 -742
  94. package/dist/runtime/structured-output-validator.d.ts +44 -0
  95. package/dist/runtime/structured-output-validator.js +171 -0
  96. package/dist/runtime/verifier-command-binding.d.ts +22 -0
  97. package/dist/runtime/verifier-command-binding.js +162 -0
  98. package/dist/runtime/verifier.d.ts +46 -6
  99. package/dist/runtime/verifier.js +355 -53
  100. package/dist/runtime/windows-job-keeper.d.ts +47 -0
  101. package/dist/runtime/windows-job-keeper.js +231 -0
  102. package/dist/runtime/worker-executor.d.ts +17 -1
  103. package/dist/runtime/worker-executor.js +26 -5
  104. package/dist/runtime/workflow-plan-adapter.d.ts +19 -0
  105. package/dist/runtime/workflow-plan-adapter.js +502 -6
  106. package/dist/runtime/workflow-runtime-manifest.d.ts +1 -0
  107. package/dist/runtime/workflow-runtime-manifest.js +5 -0
  108. package/dist/runtime/workspace-isolation.d.ts +29 -2
  109. package/dist/runtime/workspace-isolation.js +488 -11
  110. package/dist/runtime/workspace-observation.d.ts +44 -0
  111. package/dist/runtime/workspace-observation.js +216 -0
  112. package/dist/schema/controller-registry.d.ts +0 -7
  113. package/dist/schema/controller-registry.js +0 -8
  114. package/dist/schema/types.d.ts +84 -10
  115. package/dist/schema/types.js +2 -0
  116. package/dist/schema/validate.js +151 -1
  117. package/dist/schema/validators/controllers.d.ts +1 -1
  118. package/dist/schema/validators/controllers.js +126 -41
  119. package/dist/schema/workflow-authoring-contract.js +2 -0
  120. package/dist/schema/yaml-parser.js +1 -1
  121. package/dist/templates/flutter-rewrite-new.dna.yaml +1 -0
  122. package/dist/templates/flutter-rewrite.dna.yaml +87 -60
  123. package/dist/templates/safe-refactoring.dna.yaml +1 -0
  124. package/native/windows-job-keeper/README.md +80 -0
  125. package/native/windows-job-keeper/bin/aarch64/intentdna-windows-job-keeper.exe +0 -0
  126. package/native/windows-job-keeper/bin/x86_64/intentdna-windows-job-keeper.exe +0 -0
  127. package/native/windows-job-keeper/keeper.c +979 -0
  128. package/native/windows-job-keeper/manifest.json +33 -0
  129. package/package.json +6 -2
  130. package/scripts/build-windows-job-keeper.mjs +172 -0
  131. package/scripts/verify-windows-job-keeper.mjs +184 -0
@@ -0,0 +1,30 @@
1
+ import { type ExecutableRunPlanPayload, type Sha256Digest, type StoredExecutableRunPlan } from "./executable-run-plan.js";
2
+ export interface DurablePlanStoreOptions {
3
+ /** The shared `.dna/runtime` directory. */
4
+ readonly root_directory: string;
5
+ }
6
+ export type PlanStoreErrorCode = "invalid_options" | "invalid_plan_id" | "invalid_plan" | "plan_not_found" | "corrupt_plan" | "immutable_conflict";
7
+ export declare class PlanStoreError extends Error {
8
+ readonly code: PlanStoreErrorCode;
9
+ constructor(code: PlanStoreErrorCode, message: string, options?: ErrorOptions);
10
+ }
11
+ /**
12
+ * Durable immutable storage for canonical executable plans.
13
+ *
14
+ * A plan is first written and synced under a private temporary name. Linking
15
+ * that complete inode to its digest path is the publication CAS: link(2) is
16
+ * atomic and never replaces an existing destination.
17
+ */
18
+ export declare class DurablePlanStore {
19
+ private readonly rootDirectory;
20
+ constructor(options: DurablePlanStoreOptions);
21
+ private plansDirectory;
22
+ private planPath;
23
+ private readStoredPlan;
24
+ commitPlan(plan: ExecutableRunPlanPayload): Promise<StoredExecutableRunPlan>;
25
+ loadPlan(planId: string): Promise<StoredExecutableRunPlan | null>;
26
+ requirePlan(planId: string): Promise<StoredExecutableRunPlan>;
27
+ hasPlan(planId: string): Promise<boolean>;
28
+ listPlanIds(): Promise<readonly Sha256Digest[]>;
29
+ loadAllPlans(): Promise<readonly StoredExecutableRunPlan[]>;
30
+ }
@@ -0,0 +1,231 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { chmod, link, lstat, mkdir, open, readFile, readdir, unlink, } from "node:fs/promises";
3
+ import { join, resolve } from "node:path";
4
+ import { canonicalizeJson } from "./canonical-json.js";
5
+ import { createStoredExecutableRunPlan, parseStoredExecutableRunPlan, } from "./executable-run-plan.js";
6
+ export class PlanStoreError extends Error {
7
+ code;
8
+ constructor(code, message, options) {
9
+ super(message, options);
10
+ this.name = "PlanStoreError";
11
+ this.code = code;
12
+ }
13
+ }
14
+ const DIRECTORY_MODE = 0o700;
15
+ const TEMPORARY_FILE_MODE = 0o600;
16
+ const STORED_FILE_MODE = 0o400;
17
+ const PLAN_FILE_PATTERN = /^([a-f0-9]{64})\.json$/u;
18
+ const PLAN_ID_PATTERN = /^sha256:([a-f0-9]{64})$/u;
19
+ function isErrno(error, code) {
20
+ return (error instanceof Error
21
+ && "code" in error
22
+ && error.code === code);
23
+ }
24
+ function assertPlanId(planId) {
25
+ if (!PLAN_ID_PATTERN.test(planId)) {
26
+ throw new PlanStoreError("invalid_plan_id", "plan_id must be a lowercase sha256 digest");
27
+ }
28
+ }
29
+ function planFileName(planId) {
30
+ return `${planId.slice("sha256:".length)}.json`;
31
+ }
32
+ function isUnsupportedDirectorySync(error) {
33
+ if (!(error instanceof Error) || !("code" in error))
34
+ return false;
35
+ const code = error.code;
36
+ return (code === "EINVAL"
37
+ || code === "ENOTSUP"
38
+ || code === "EISDIR"
39
+ || (code === "EPERM" && process.platform === "win32"));
40
+ }
41
+ async function syncDirectory(path) {
42
+ let handle;
43
+ try {
44
+ handle = await open(path, "r");
45
+ await handle.sync();
46
+ }
47
+ catch (error) {
48
+ if (!isUnsupportedDirectorySync(error))
49
+ throw error;
50
+ }
51
+ finally {
52
+ await handle?.close().catch(() => undefined);
53
+ }
54
+ }
55
+ async function ensurePrivateDirectory(path) {
56
+ await mkdir(path, { recursive: true, mode: DIRECTORY_MODE });
57
+ const metadata = await lstat(path);
58
+ if (!metadata.isDirectory() || metadata.isSymbolicLink()) {
59
+ throw new PlanStoreError("immutable_conflict", `plan store path is not a real directory: ${path}`);
60
+ }
61
+ await chmod(path, DIRECTORY_MODE);
62
+ }
63
+ async function requireRealDirectoryIfPresent(path) {
64
+ let metadata;
65
+ try {
66
+ metadata = await lstat(path);
67
+ }
68
+ catch (error) {
69
+ if (isErrno(error, "ENOENT"))
70
+ return false;
71
+ throw error;
72
+ }
73
+ if (!metadata.isDirectory() || metadata.isSymbolicLink()) {
74
+ throw new PlanStoreError("immutable_conflict", `plan store path is not a real directory: ${path}`);
75
+ }
76
+ return true;
77
+ }
78
+ /**
79
+ * Durable immutable storage for canonical executable plans.
80
+ *
81
+ * A plan is first written and synced under a private temporary name. Linking
82
+ * that complete inode to its digest path is the publication CAS: link(2) is
83
+ * atomic and never replaces an existing destination.
84
+ */
85
+ export class DurablePlanStore {
86
+ rootDirectory;
87
+ constructor(options) {
88
+ if (!options.root_directory.trim()) {
89
+ throw new PlanStoreError("invalid_options", "root_directory must not be empty");
90
+ }
91
+ this.rootDirectory = resolve(options.root_directory);
92
+ }
93
+ plansDirectory() {
94
+ return join(this.rootDirectory, "plans");
95
+ }
96
+ planPath(planId) {
97
+ return join(this.plansDirectory(), planFileName(planId));
98
+ }
99
+ async readStoredPlan(planId) {
100
+ if (!await requireRealDirectoryIfPresent(this.rootDirectory))
101
+ return null;
102
+ if (!await requireRealDirectoryIfPresent(this.plansDirectory()))
103
+ return null;
104
+ const path = this.planPath(planId);
105
+ let metadata;
106
+ try {
107
+ metadata = await lstat(path);
108
+ }
109
+ catch (error) {
110
+ if (isErrno(error, "ENOENT"))
111
+ return null;
112
+ throw error;
113
+ }
114
+ if (!metadata.isFile() || metadata.isSymbolicLink()) {
115
+ throw new PlanStoreError("corrupt_plan", `stored plan ${planId} is not a regular file`);
116
+ }
117
+ if ((metadata.mode & 0o222) !== 0) {
118
+ throw new PlanStoreError("corrupt_plan", `stored plan ${planId} must be read-only`);
119
+ }
120
+ let value;
121
+ try {
122
+ value = JSON.parse(await readFile(path, "utf8"));
123
+ }
124
+ catch (error) {
125
+ if (isErrno(error, "ENOENT"))
126
+ return null;
127
+ if (error instanceof SyntaxError) {
128
+ throw new PlanStoreError("corrupt_plan", `stored plan ${planId} contains invalid JSON`, { cause: error });
129
+ }
130
+ throw error;
131
+ }
132
+ let stored;
133
+ try {
134
+ stored = parseStoredExecutableRunPlan(value);
135
+ }
136
+ catch (error) {
137
+ throw new PlanStoreError("corrupt_plan", `stored plan ${planId} failed schema or digest validation`, { cause: error });
138
+ }
139
+ if (stored.plan_id !== planId) {
140
+ throw new PlanStoreError("corrupt_plan", `stored plan ${stored.plan_id} does not match requested identity ${planId}`);
141
+ }
142
+ return stored;
143
+ }
144
+ async commitPlan(plan) {
145
+ let stored;
146
+ try {
147
+ stored = createStoredExecutableRunPlan(plan);
148
+ }
149
+ catch (error) {
150
+ throw new PlanStoreError("invalid_plan", "executable run plan failed schema validation", { cause: error });
151
+ }
152
+ const plansDirectory = this.plansDirectory();
153
+ await ensurePrivateDirectory(this.rootDirectory);
154
+ await ensurePrivateDirectory(plansDirectory);
155
+ const destination = this.planPath(stored.plan_id);
156
+ const temporaryPath = join(plansDirectory, `.${planFileName(stored.plan_id)}.tmp.${process.pid}.${randomUUID()}`);
157
+ let handle;
158
+ try {
159
+ handle = await open(temporaryPath, "wx", TEMPORARY_FILE_MODE);
160
+ await handle.writeFile(`${canonicalizeJson(stored)}\n`, "utf8");
161
+ await handle.chmod(STORED_FILE_MODE);
162
+ await handle.sync();
163
+ await handle.close();
164
+ handle = undefined;
165
+ while (true) {
166
+ try {
167
+ await link(temporaryPath, destination);
168
+ await syncDirectory(plansDirectory);
169
+ return stored;
170
+ }
171
+ catch (error) {
172
+ if (!isErrno(error, "EEXIST"))
173
+ throw error;
174
+ await syncDirectory(plansDirectory);
175
+ const existing = await this.readStoredPlan(stored.plan_id);
176
+ if (existing)
177
+ return existing;
178
+ // A concurrent external deletion raced the EEXIST check; retry the CAS.
179
+ }
180
+ }
181
+ }
182
+ finally {
183
+ await handle?.close().catch(() => undefined);
184
+ await unlink(temporaryPath).catch((error) => {
185
+ if (!isErrno(error, "ENOENT"))
186
+ throw error;
187
+ });
188
+ }
189
+ }
190
+ async loadPlan(planId) {
191
+ assertPlanId(planId);
192
+ return this.readStoredPlan(planId);
193
+ }
194
+ async requirePlan(planId) {
195
+ const stored = await this.loadPlan(planId);
196
+ if (!stored) {
197
+ throw new PlanStoreError("plan_not_found", `stored plan ${planId} was not found`);
198
+ }
199
+ return stored;
200
+ }
201
+ async hasPlan(planId) {
202
+ return (await this.loadPlan(planId)) !== null;
203
+ }
204
+ async listPlanIds() {
205
+ if (!await requireRealDirectoryIfPresent(this.rootDirectory))
206
+ return [];
207
+ if (!await requireRealDirectoryIfPresent(this.plansDirectory()))
208
+ return [];
209
+ let entries;
210
+ try {
211
+ entries = await readdir(this.plansDirectory(), { withFileTypes: true });
212
+ }
213
+ catch (error) {
214
+ if (isErrno(error, "ENOENT"))
215
+ return [];
216
+ throw error;
217
+ }
218
+ const ids = entries
219
+ .map((entry) => entry.name.match(PLAN_FILE_PATTERN)?.[1])
220
+ .filter((digest) => digest !== undefined)
221
+ .sort()
222
+ .map((digest) => `sha256:${digest}`);
223
+ for (const planId of ids) {
224
+ await this.requirePlan(planId);
225
+ }
226
+ return ids;
227
+ }
228
+ async loadAllPlans() {
229
+ return Promise.all((await this.listPlanIds()).map((planId) => this.requirePlan(planId)));
230
+ }
231
+ }
@@ -41,7 +41,16 @@ export interface ProcessTreeRunResult {
41
41
  readonly processLimitExceeded: boolean;
42
42
  readonly outputLimitExceeded: boolean;
43
43
  readonly terminationReason: ProcessTerminationReason | null;
44
+ readonly processId: number | null;
45
+ readonly processTreeDrained: boolean;
44
46
  readonly error?: NodeJS.ErrnoException;
45
47
  }
48
+ export interface ProcessCommandSpec {
49
+ readonly command: string;
50
+ readonly args: readonly string[];
51
+ readonly windowsVerbatimArguments?: boolean;
52
+ readonly windowsBatchTransport?: boolean;
53
+ }
54
+ export declare function resolveProcessCommand(command: string, args: readonly string[], platform: NodeJS.Platform, env: NodeJS.ProcessEnv, cwd?: string): ProcessCommandSpec;
46
55
  export declare function runProcessTree(command: string, args: readonly string[], options?: ProcessTreeRunOptions): Promise<ProcessTreeRunResult>;
47
56
  export declare const runProcessTreeWithTimeout: typeof runProcessTree;
@@ -8,7 +8,7 @@
8
8
  */
9
9
  import { spawn, spawnSync, } from "node:child_process";
10
10
  import { readdirSync, readFileSync, statSync } from "node:fs";
11
- import { basename, delimiter, extname, join, } from "node:path";
11
+ import { win32 as win32Path, } from "node:path";
12
12
  const DEFAULT_TERMINATION_GRACE_MS = 1_000;
13
13
  const DEFAULT_PROCESS_LIMIT_POLL_MS = 100;
14
14
  const WINDOWS_DIRECT_EXTENSIONS = new Set([".com", ".exe"]);
@@ -42,8 +42,8 @@ function fileExists(path) {
42
42
  return false;
43
43
  }
44
44
  }
45
- function resolveWindowsCommand(command, env) {
46
- const hasPath = /^[A-Za-z]:/.test(command) || /[\\/]/.test(command);
45
+ function resolveWindowsCommand(command, env, cwd) {
46
+ const hasPath = win32Path.isAbsolute(command) || /[\\/]/.test(command);
47
47
  const rawExtensions = String(env.PATHEXT ?? "").trim();
48
48
  const extensions = rawExtensions === ""
49
49
  ? WINDOWS_DEFAULT_PATHEXT
@@ -53,10 +53,12 @@ function resolveWindowsCommand(command, env) {
53
53
  ])];
54
54
  const directories = hasPath
55
55
  ? [""]
56
- : String(env.Path ?? env.PATH ?? "").split(delimiter).filter(Boolean);
57
- const extension = extname(command);
56
+ : [cwd, ...String(env.Path ?? env.PATH ?? "").split(win32Path.delimiter).filter(Boolean)];
57
+ const extension = win32Path.extname(command);
58
58
  for (const directory of directories) {
59
- const base = directory === "" ? command : join(directory, command);
59
+ const base = hasPath
60
+ ? win32Path.isAbsolute(command) ? command : win32Path.resolve(cwd, command)
61
+ : win32Path.join(directory, command);
60
62
  const candidates = extension ? [base] : [...extensions.map((item) => `${base}${item}`), base];
61
63
  for (const candidate of candidates) {
62
64
  if (fileExists(candidate))
@@ -65,49 +67,63 @@ function resolveWindowsCommand(command, env) {
65
67
  }
66
68
  return null;
67
69
  }
68
- function commandSpec(command, args, platform, env) {
70
+ export function resolveProcessCommand(command, args, platform, env, cwd = process.cwd()) {
69
71
  if (platform !== "win32")
70
72
  return { command, args: [...args] };
71
- const resolved = resolveWindowsCommand(command, env);
73
+ const resolved = resolveWindowsCommand(command, env, cwd);
72
74
  if (!resolved)
73
75
  return { command, args: [...args] };
74
- const extension = extname(resolved).toLowerCase();
76
+ const extension = win32Path.extname(resolved).toLowerCase();
75
77
  if (WINDOWS_CMD_EXTENSIONS.has(extension)) {
76
- const quote = (value) => `"${value.replace(/"/g, "\"\"")}"`;
78
+ const quote = (value) => {
79
+ const escapedQuotes = value.replace(/"/g, "\"\"");
80
+ const escapedTrailingBackslashes = escapedQuotes.replace(/(\\+)$/, "$1$1");
81
+ return `"${escapedTrailingBackslashes}"`;
82
+ };
77
83
  const commandLine = [resolved, ...args].map(quote).join(" ");
78
84
  return {
79
85
  command: env.ComSpec || "cmd.exe",
80
86
  args: ["/d", "/s", "/c", `"${commandLine}"`],
81
87
  windowsVerbatimArguments: true,
88
+ windowsBatchTransport: true,
82
89
  };
83
90
  }
84
91
  if (extension === ".ps1") {
92
+ const escapedScript = resolved.replace(/'/g, "''");
93
+ const encodedArgs = Buffer.from(JSON.stringify({ args }), "utf8").toString("base64");
94
+ const script = [
95
+ "$PSNativeCommandArgumentPassing = 'Standard'",
96
+ `$intentdnaDoc = ConvertFrom-Json -InputObject ([Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('${encodedArgs}')))`,
97
+ "[string[]] $intentdnaArgs = [string[]]::new($intentdnaDoc.args.Count)",
98
+ "for ($intentdnaIndex = 0; $intentdnaIndex -lt $intentdnaDoc.args.Count; $intentdnaIndex++) { $intentdnaArgs[$intentdnaIndex] = [string] $intentdnaDoc.args[$intentdnaIndex] }",
99
+ `& '${escapedScript}' @intentdnaArgs`,
100
+ "exit $LASTEXITCODE",
101
+ ].join("; ");
85
102
  return {
86
- command: "powershell.exe",
103
+ command: "pwsh.exe",
87
104
  args: [
88
105
  "-NoLogo",
89
106
  "-NoProfile",
90
107
  "-ExecutionPolicy",
91
108
  "Bypass",
92
- "-File",
93
- resolved,
94
- ...args,
109
+ "-EncodedCommand",
110
+ Buffer.from(script, "utf16le").toString("base64"),
95
111
  ],
96
112
  };
97
113
  }
98
- if (WINDOWS_DIRECT_EXTENSIONS.has(extension) || basename(resolved) !== "") {
114
+ if (WINDOWS_DIRECT_EXTENSIONS.has(extension) || win32Path.basename(resolved) !== "") {
99
115
  return { command: resolved, args: [...args] };
100
116
  }
101
117
  return { command, args: [...args] };
102
118
  }
103
119
  function terminateProcessTree(child, platform, signal) {
104
120
  if (child.pid === undefined)
105
- return;
121
+ return true;
106
122
  if (platform === "win32") {
107
123
  const force = signal === "SIGKILL" ? ["/F"] : [];
108
124
  const result = spawnSync("taskkill", ["/PID", String(child.pid), "/T", ...force], { windowsHide: true, stdio: "ignore" });
109
125
  if (!result.error && result.status === 0)
110
- return;
126
+ return true;
111
127
  }
112
128
  try {
113
129
  if (platform === "win32") {
@@ -120,7 +136,7 @@ function terminateProcessTree(child, platform, signal) {
120
136
  catch (error) {
121
137
  const code = error.code;
122
138
  if (code === "ESRCH")
123
- return;
139
+ return platform !== "win32";
124
140
  try {
125
141
  child.kill(signal);
126
142
  }
@@ -128,8 +144,52 @@ function terminateProcessTree(child, platform, signal) {
128
144
  if (fallbackError.code !== "ESRCH") {
129
145
  throw fallbackError;
130
146
  }
147
+ return platform !== "win32";
131
148
  }
132
149
  }
150
+ return false;
151
+ }
152
+ function processTreeExists(processId, platform) {
153
+ if (platform === "win32")
154
+ return null;
155
+ try {
156
+ process.kill(-processId, 0);
157
+ return true;
158
+ }
159
+ catch (error) {
160
+ const code = error.code;
161
+ if (code === "ESRCH")
162
+ return false;
163
+ if (code === "EPERM")
164
+ return true;
165
+ return null;
166
+ }
167
+ }
168
+ async function drainProcessTree(child, platform, signal, graceMs) {
169
+ if (child.pid === undefined)
170
+ return true;
171
+ if (terminateProcessTree(child, platform, signal) && platform === "win32")
172
+ return true;
173
+ const deadline = Date.now() + graceMs;
174
+ for (;;) {
175
+ const exists = processTreeExists(child.pid, platform);
176
+ if (exists === false)
177
+ return true;
178
+ if (exists === null || Date.now() >= deadline)
179
+ break;
180
+ await new Promise((resolve) => setTimeout(resolve, 10));
181
+ }
182
+ if (terminateProcessTree(child, platform, "SIGKILL") && platform === "win32")
183
+ return true;
184
+ const forceDeadline = Date.now() + Math.max(100, graceMs);
185
+ for (;;) {
186
+ const exists = processTreeExists(child.pid, platform);
187
+ if (exists === false)
188
+ return true;
189
+ if (exists === null || Date.now() >= forceDeadline)
190
+ return false;
191
+ await new Promise((resolve) => setTimeout(resolve, 10));
192
+ }
133
193
  }
134
194
  function readLinuxProcessTable() {
135
195
  try {
@@ -195,11 +255,13 @@ export function runProcessTree(command, args, options = {}) {
195
255
  processLimitExceeded: false,
196
256
  outputLimitExceeded: false,
197
257
  terminationReason: "cancellation",
258
+ processId: null,
259
+ processTreeDrained: true,
198
260
  });
199
261
  }
200
262
  const platform = options.platform ?? process.platform;
201
263
  const env = options.env ?? process.env;
202
- const spec = commandSpec(command, args, platform, env);
264
+ const spec = resolveProcessCommand(command, args, platform, env, options.cwd ?? process.cwd());
203
265
  const spawnImpl = options.spawnImpl ?? spawn;
204
266
  const timeoutMs = positiveInteger(options.timeoutMs);
205
267
  const killSignal = options.killSignal ?? "SIGTERM";
@@ -223,6 +285,7 @@ export function runProcessTree(command, args, options = {}) {
223
285
  let settled = false;
224
286
  let timeoutTimer;
225
287
  let forceKillTimer;
288
+ let residualKillTimer;
226
289
  let processLimitTimer;
227
290
  const spawnOptions = {
228
291
  cwd: options.cwd,
@@ -248,6 +311,8 @@ export function runProcessTree(command, args, options = {}) {
248
311
  processLimitExceeded: false,
249
312
  outputLimitExceeded: false,
250
313
  terminationReason: null,
314
+ processId: null,
315
+ processTreeDrained: true,
251
316
  error: error,
252
317
  });
253
318
  return;
@@ -294,7 +359,7 @@ export function runProcessTree(command, args, options = {}) {
294
359
  requestTermination("cancellation", cancellationGraceMs);
295
360
  };
296
361
  cancellationSignal?.addEventListener("abort", cancellationHandler, { once: true });
297
- const finish = (status, signal, error) => {
362
+ const finish = async (status, signal, error) => {
298
363
  if (settled)
299
364
  return;
300
365
  settled = true;
@@ -302,10 +367,16 @@ export function runProcessTree(command, args, options = {}) {
302
367
  clearTimeout(timeoutTimer);
303
368
  if (forceKillTimer)
304
369
  clearTimeout(forceKillTimer);
370
+ if (residualKillTimer)
371
+ clearTimeout(residualKillTimer);
305
372
  if (processLimitTimer)
306
373
  clearInterval(processLimitTimer);
307
374
  cancellationSignal?.removeEventListener("abort", cancellationHandler);
308
375
  removeParentHandlers();
376
+ const residualGraceMs = terminationReason === "cancellation"
377
+ ? cancellationGraceMs
378
+ : timeoutGraceMs;
379
+ const processTreeDrained = await drainProcessTree(child, platform, killSignal, residualGraceMs);
309
380
  resolve({
310
381
  stdout,
311
382
  stderr,
@@ -319,6 +390,8 @@ export function runProcessTree(command, args, options = {}) {
319
390
  processLimitExceeded,
320
391
  outputLimitExceeded,
321
392
  terminationReason,
393
+ processId: child.pid ?? null,
394
+ processTreeDrained,
322
395
  ...(error ? { error } : {}),
323
396
  });
324
397
  };
@@ -356,7 +429,7 @@ export function runProcessTree(command, args, options = {}) {
356
429
  });
357
430
  child.stdin?.end(options.stdin);
358
431
  child.on("error", (error) => {
359
- finish(null, null, error);
432
+ void finish(null, null, error);
360
433
  });
361
434
  child.on("exit", () => {
362
435
  if (platform === "win32")
@@ -365,13 +438,13 @@ export function runProcessTree(command, args, options = {}) {
365
438
  const residualGraceMs = terminationReason === "cancellation"
366
439
  ? cancellationGraceMs
367
440
  : timeoutGraceMs;
368
- const residualTimer = setTimeout(() => {
441
+ residualKillTimer = setTimeout(() => {
369
442
  terminateProcessTree(child, platform, "SIGKILL");
370
443
  }, residualGraceMs);
371
- residualTimer.unref?.();
444
+ residualKillTimer.unref?.();
372
445
  });
373
446
  child.on("close", (status, signal) => {
374
- finish(status, signal);
447
+ void finish(status, signal);
375
448
  });
376
449
  if (cancellationSignal?.aborted)
377
450
  cancellationHandler();
@@ -18,9 +18,9 @@ export function createCodexExecutionProvider(options = {}) {
18
18
  "--sandbox",
19
19
  options.sandbox ?? "workspace-write",
20
20
  ...(options.extraArgs ?? []),
21
- packet.standalone_prompt,
21
+ "-",
22
22
  ],
23
- stdin: null,
23
+ stdin: packet.standalone_prompt,
24
24
  cwd: packet.workspace.working_directory,
25
25
  ...(options.env ? { env: options.env } : {}),
26
26
  };