helloloop 0.9.1 → 0.10.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 (50) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +230 -506
  3. package/hosts/claude/marketplace/plugins/helloloop/.claude-plugin/plugin.json +1 -1
  4. package/hosts/gemini/extension/gemini-extension.json +1 -1
  5. package/native/windows-hidden-shell-proxy/HelloLoopHiddenShellProxy.csproj +11 -0
  6. package/native/windows-hidden-shell-proxy/Program.cs +498 -0
  7. package/package.json +4 -2
  8. package/src/activity_projection.mjs +294 -0
  9. package/src/analyze_confirmation.mjs +3 -1
  10. package/src/analyzer.mjs +2 -1
  11. package/src/auto_execution_options.mjs +13 -0
  12. package/src/background_launch.mjs +73 -0
  13. package/src/cli.mjs +49 -1
  14. package/src/cli_analyze_command.mjs +9 -5
  15. package/src/cli_args.mjs +102 -37
  16. package/src/cli_command_handlers.mjs +44 -4
  17. package/src/cli_support.mjs +2 -0
  18. package/src/dashboard_command.mjs +371 -0
  19. package/src/dashboard_tui.mjs +289 -0
  20. package/src/dashboard_web.mjs +351 -0
  21. package/src/dashboard_web_client.mjs +167 -0
  22. package/src/dashboard_web_page.mjs +49 -0
  23. package/src/engine_event_parser_codex.mjs +167 -0
  24. package/src/engine_process_support.mjs +1 -0
  25. package/src/engine_selection.mjs +24 -0
  26. package/src/engine_selection_probe.mjs +10 -6
  27. package/src/engine_selection_settings.mjs +12 -19
  28. package/src/execution_interactivity.mjs +12 -0
  29. package/src/host_continuation.mjs +305 -0
  30. package/src/install_codex.mjs +20 -8
  31. package/src/install_shared.mjs +9 -0
  32. package/src/node_process_launch.mjs +28 -0
  33. package/src/process.mjs +2 -0
  34. package/src/runner_execute_task.mjs +4 -0
  35. package/src/runner_execution_support.mjs +69 -3
  36. package/src/runner_once.mjs +4 -0
  37. package/src/runner_status.mjs +63 -7
  38. package/src/runtime_engine_support.mjs +41 -4
  39. package/src/runtime_engine_task.mjs +7 -0
  40. package/src/runtime_settings.mjs +105 -0
  41. package/src/runtime_settings_loader.mjs +19 -0
  42. package/src/shell_invocation.mjs +227 -9
  43. package/src/supervisor_cli_support.mjs +3 -2
  44. package/src/supervisor_guardian.mjs +307 -0
  45. package/src/supervisor_runtime.mjs +138 -82
  46. package/src/supervisor_state.mjs +64 -0
  47. package/src/supervisor_watch.mjs +92 -48
  48. package/src/terminal_session_limits.mjs +1 -21
  49. package/src/windows_hidden_shell_proxy.mjs +405 -0
  50. package/src/workspace_registry.mjs +155 -0
@@ -1,6 +1,7 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
3
 
4
+ import { createActivityProjector } from "./activity_projection.mjs";
4
5
  import { appendText, nowIso, writeText } from "./common.mjs";
5
6
  import { getEngineDisplayName } from "./engine_metadata.mjs";
6
7
  import {
@@ -53,14 +54,16 @@ export function buildHostLeaseStoppedResult(reason) {
53
54
  };
54
55
  }
55
56
 
56
- export function createRuntimeStatusWriter(runtimeStatusFile, baseState) {
57
+ export function createRuntimeStatusWriter(runtimeStatusFile, baseState, onUpdate) {
57
58
  return function writeRuntimeStatus(status, extra = {}) {
58
- writeJson(runtimeStatusFile, {
59
+ const payload = {
59
60
  ...baseState,
60
61
  ...extra,
61
62
  status,
62
63
  updatedAt: nowIso(),
63
- });
64
+ };
65
+ writeJson(runtimeStatusFile, payload);
66
+ onUpdate?.(payload);
64
67
  };
65
68
  }
66
69
 
@@ -167,6 +170,14 @@ export async function runEngineAttempt({
167
170
  const attemptLastMessageFile = path.join(runDir, `${attemptPrefix}-last-message.txt`);
168
171
  const attemptStdoutFile = path.join(runDir, `${attemptPrefix}-stdout.log`);
169
172
  const attemptStderrFile = path.join(runDir, `${attemptPrefix}-stderr.log`);
173
+ const activityProjector = createActivityProjector({
174
+ engine,
175
+ phase: executionMode,
176
+ repoRoot: context.repoRoot,
177
+ runDir,
178
+ outputPrefix: attemptPrefix,
179
+ attemptPrefix,
180
+ });
170
181
 
171
182
  if (invocation.error) {
172
183
  const result = {
@@ -183,6 +194,17 @@ export async function runEngineAttempt({
183
194
  };
184
195
  writeText(attemptPromptFile, prompt);
185
196
  writeEngineRunArtifacts(runDir, attemptPrefix, result, "");
197
+ activityProjector.onRuntimeStatus({
198
+ status: "failed",
199
+ attemptPrefix,
200
+ activityFile: activityProjector.activityFile,
201
+ activityEventsFile: activityProjector.activityEventsFile,
202
+ });
203
+ activityProjector.finalize({
204
+ status: "failed",
205
+ result,
206
+ finalMessage: "",
207
+ });
186
208
  return {
187
209
  result,
188
210
  finalMessage: "",
@@ -216,7 +238,10 @@ export async function runEngineAttempt({
216
238
  const result = await runChild(invocation.command, finalArgs, {
217
239
  cwd: context.repoRoot,
218
240
  stdin: prompt,
219
- env,
241
+ env: {
242
+ ...(invocation.env || {}),
243
+ ...(env || {}),
244
+ },
220
245
  shell: invocation.shell,
221
246
  heartbeatIntervalMs: recoveryPolicy.heartbeatIntervalSeconds * 1000,
222
247
  stallWarningMs: recoveryPolicy.stallWarningSeconds * 1000,
@@ -228,10 +253,18 @@ export async function runEngineAttempt({
228
253
  recoveryCount,
229
254
  recoveryHistory,
230
255
  heartbeat: payload,
256
+ activityFile: activityProjector.activityFile,
257
+ activityEventsFile: activityProjector.activityEventsFile,
258
+ });
259
+ activityProjector.onRuntimeStatus({
260
+ ...payload,
261
+ attemptPrefix,
262
+ recoveryCount,
231
263
  });
232
264
  },
233
265
  onStdout(text) {
234
266
  appendText(attemptStdoutFile, text);
267
+ activityProjector.onStdoutChunk(text);
235
268
  },
236
269
  onStderr(text) {
237
270
  appendText(attemptStderrFile, text);
@@ -245,6 +278,10 @@ export async function runEngineAttempt({
245
278
 
246
279
  writeText(attemptPromptFile, prompt);
247
280
  writeEngineRunArtifacts(runDir, attemptPrefix, result, finalMessage);
281
+ activityProjector.finalize({
282
+ result,
283
+ finalMessage,
284
+ });
248
285
 
249
286
  return {
250
287
  result,
@@ -3,6 +3,7 @@ import path from "node:path";
3
3
  import { ensureDir, nowIso, tailText, writeJson, writeText } from "./common.mjs";
4
4
  import { getEngineDisplayName, normalizeEngineName } from "./engine_metadata.mjs";
5
5
  import { resolveEngineInvocation } from "./engine_process_support.mjs";
6
+ import { refreshHostContinuationArtifacts } from "./host_continuation.mjs";
6
7
  import { isHostLeaseAlive } from "./host_lease.mjs";
7
8
  import {
8
9
  buildRuntimeRecoveryPrompt,
@@ -52,6 +53,12 @@ export async function runEngineTask({
52
53
  outputPrefix: prefix,
53
54
  hardRetryBudget: recoveryPolicy.hardRetryDelaysSeconds.length,
54
55
  softRetryBudget: recoveryPolicy.softRetryDelaysSeconds.length,
56
+ }, () => {
57
+ try {
58
+ refreshHostContinuationArtifacts(context);
59
+ } catch {
60
+ // ignore continuation snapshot refresh failures during heartbeat writes
61
+ }
55
62
  });
56
63
 
57
64
  const recoveryHistory = [];
@@ -0,0 +1,105 @@
1
+ function normalizeBoolean(value, fallback = false) {
2
+ return typeof value === "boolean" ? value : fallback;
3
+ }
4
+
5
+ function normalizeNonNegativeInteger(value, fallbackValue) {
6
+ if (value === null || value === undefined || value === "") {
7
+ return fallbackValue;
8
+ }
9
+ const parsed = Number(value);
10
+ if (!Number.isFinite(parsed) || parsed < 0) {
11
+ return fallbackValue;
12
+ }
13
+ return Math.floor(parsed);
14
+ }
15
+
16
+ function normalizePositiveInteger(value, fallbackValue, minimum = 1) {
17
+ const parsed = normalizeNonNegativeInteger(value, fallbackValue);
18
+ if (!Number.isFinite(parsed) || parsed < minimum) {
19
+ return fallbackValue;
20
+ }
21
+ return parsed;
22
+ }
23
+
24
+ function normalizeSecondsList(values, fallbackValues) {
25
+ if (!Array.isArray(values) || !values.length) {
26
+ return [...fallbackValues];
27
+ }
28
+ const normalized = values
29
+ .map((item) => normalizePositiveInteger(item, 0))
30
+ .filter((item) => item > 0);
31
+ return normalized.length ? normalized : [...fallbackValues];
32
+ }
33
+
34
+ export function defaultTerminalConcurrencySettings() {
35
+ return {
36
+ enabled: true,
37
+ visibleMax: 8,
38
+ backgroundMax: 8,
39
+ totalMax: 8,
40
+ };
41
+ }
42
+
43
+ export function defaultObserverRetrySettings() {
44
+ return {
45
+ enabled: true,
46
+ missingPollsBeforeRetry: 3,
47
+ retryDelaysSeconds: [2, 5, 10, 15, 30, 60],
48
+ maxRetryCount: 0,
49
+ };
50
+ }
51
+
52
+ export function defaultSupervisorKeepAliveSettings() {
53
+ return {
54
+ enabled: true,
55
+ restartDelaysSeconds: [2, 5, 10, 15, 30, 60],
56
+ maxRestartCount: 0,
57
+ };
58
+ }
59
+
60
+ export function normalizeTerminalConcurrencySettings(settings = {}) {
61
+ const defaults = defaultTerminalConcurrencySettings();
62
+ return {
63
+ enabled: normalizeBoolean(settings?.enabled, defaults.enabled),
64
+ visibleMax: normalizeNonNegativeInteger(settings?.visibleMax, defaults.visibleMax),
65
+ backgroundMax: normalizeNonNegativeInteger(settings?.backgroundMax, defaults.backgroundMax),
66
+ totalMax: normalizeNonNegativeInteger(settings?.totalMax, defaults.totalMax),
67
+ };
68
+ }
69
+
70
+ export function normalizeObserverRetrySettings(settings = {}) {
71
+ const defaults = defaultObserverRetrySettings();
72
+ return {
73
+ enabled: normalizeBoolean(settings?.enabled, defaults.enabled),
74
+ missingPollsBeforeRetry: normalizePositiveInteger(
75
+ settings?.missingPollsBeforeRetry,
76
+ defaults.missingPollsBeforeRetry,
77
+ ),
78
+ retryDelaysSeconds: normalizeSecondsList(settings?.retryDelaysSeconds, defaults.retryDelaysSeconds),
79
+ maxRetryCount: normalizeNonNegativeInteger(settings?.maxRetryCount, defaults.maxRetryCount),
80
+ };
81
+ }
82
+
83
+ export function normalizeSupervisorKeepAliveSettings(settings = {}) {
84
+ const defaults = defaultSupervisorKeepAliveSettings();
85
+ return {
86
+ enabled: normalizeBoolean(settings?.enabled, defaults.enabled),
87
+ restartDelaysSeconds: normalizeSecondsList(settings?.restartDelaysSeconds, defaults.restartDelaysSeconds),
88
+ maxRestartCount: normalizeNonNegativeInteger(settings?.maxRestartCount, defaults.maxRestartCount),
89
+ };
90
+ }
91
+
92
+ export function pickRetryDelaySeconds(delays, attemptNumber) {
93
+ const values = Array.isArray(delays) && delays.length
94
+ ? delays.map((item) => normalizePositiveInteger(item, 0)).filter((item) => item > 0)
95
+ : [];
96
+ if (!values.length) {
97
+ return 0;
98
+ }
99
+ const index = Math.max(0, Math.min(Number(attemptNumber || 1) - 1, values.length - 1));
100
+ return values[index];
101
+ }
102
+
103
+ export function hasRetryBudget(maxRetryCount, nextAttemptNumber) {
104
+ return Number(maxRetryCount || 0) <= 0 || Number(nextAttemptNumber || 0) <= Number(maxRetryCount || 0);
105
+ }
@@ -0,0 +1,19 @@
1
+ import { loadGlobalConfig } from "./global_config.mjs";
2
+ import {
3
+ normalizeObserverRetrySettings,
4
+ normalizeSupervisorKeepAliveSettings,
5
+ normalizeTerminalConcurrencySettings,
6
+ } from "./runtime_settings.mjs";
7
+
8
+ export function loadRuntimeSettings(options = {}) {
9
+ const globalConfig = loadGlobalConfig({
10
+ globalConfigFile: options.globalConfigFile,
11
+ });
12
+
13
+ return {
14
+ terminalConcurrency: normalizeTerminalConcurrencySettings(globalConfig?.runtime?.terminalConcurrency || {}),
15
+ observerRetry: normalizeObserverRetrySettings(globalConfig?.runtime?.observerRetry || {}),
16
+ supervisorKeepAlive: normalizeSupervisorKeepAliveSettings(globalConfig?.runtime?.supervisorKeepAlive || {}),
17
+ _meta: globalConfig?._meta || {},
18
+ };
19
+ }
@@ -1,5 +1,16 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
1
3
  import { spawnSync } from "node:child_process";
2
4
 
5
+ import { resolveWindowsHiddenShellEnvPatch } from "./windows_hidden_shell_proxy.mjs";
6
+
7
+ function buildSyncSpawnOptions(platform = process.platform, extra = {}) {
8
+ return {
9
+ ...extra,
10
+ ...(platform === "win32" ? { windowsHide: true } : {}),
11
+ };
12
+ }
13
+
3
14
  function createUnavailableInvocation(message) {
4
15
  return {
5
16
  command: "",
@@ -19,15 +30,19 @@ function parseWindowsCommandMatches(output) {
19
30
  function hasCommand(command, platform = process.platform) {
20
31
  if (platform === "win32") {
21
32
  const result = spawnSync("where.exe", [command], {
22
- encoding: "utf8",
23
- shell: false,
33
+ ...buildSyncSpawnOptions(platform, {
34
+ encoding: "utf8",
35
+ shell: false,
36
+ }),
24
37
  });
25
38
  return result.status === 0;
26
39
  }
27
40
 
28
41
  const result = spawnSync("sh", ["-lc", `command -v ${command}`], {
29
- encoding: "utf8",
30
- shell: false,
42
+ ...buildSyncSpawnOptions(platform, {
43
+ encoding: "utf8",
44
+ shell: false,
45
+ }),
31
46
  });
32
47
  return result.status === 0;
33
48
  }
@@ -35,8 +50,10 @@ function hasCommand(command, platform = process.platform) {
35
50
  function findWindowsCommandPaths(command, resolver) {
36
51
  const lookup = resolver || ((name) => {
37
52
  const result = spawnSync("where.exe", [name], {
38
- encoding: "utf8",
39
- shell: false,
53
+ ...buildSyncSpawnOptions("win32", {
54
+ encoding: "utf8",
55
+ shell: false,
56
+ }),
40
57
  });
41
58
  return result.status === 0 ? parseWindowsCommandMatches(result.stdout) : [];
42
59
  });
@@ -125,6 +142,14 @@ function isCmdLikeExecutable(executable) {
125
142
  return /\.(cmd|bat)$/i.test(String(executable || ""));
126
143
  }
127
144
 
145
+ function trimOuterQuotes(value) {
146
+ return String(value || "").trim().replace(/^"(.*)"$/u, "$1");
147
+ }
148
+
149
+ function uniqueNonEmpty(values) {
150
+ return [...new Set(values.map((item) => trimOuterQuotes(item)).filter(Boolean))];
151
+ }
152
+
128
153
  function resolveWindowsNamedExecutable(toolName, options = {}) {
129
154
  const explicitExecutable = String(options.explicitExecutable || "").trim();
130
155
  const findCommandPaths = options.findCommandPaths || ((command) => findWindowsCommandPaths(command));
@@ -133,7 +158,7 @@ function resolveWindowsNamedExecutable(toolName, options = {}) {
133
158
  return explicitExecutable;
134
159
  }
135
160
 
136
- const searchOrder = [`${toolName}.ps1`, `${toolName}.exe`, toolName];
161
+ const searchOrder = [`${toolName}.exe`, `${toolName}.ps1`, toolName];
137
162
  for (const query of searchOrder) {
138
163
  const safeMatch = findCommandPaths(query).find((candidate) => !isCmdLikeExecutable(candidate));
139
164
  if (safeMatch) {
@@ -144,6 +169,189 @@ function resolveWindowsNamedExecutable(toolName, options = {}) {
144
169
  return "";
145
170
  }
146
171
 
172
+ function resolveNodeHostForWrapper(wrapperPath) {
173
+ const wrapperDir = path.dirname(wrapperPath);
174
+ const bundledNode = path.join(wrapperDir, "node.exe");
175
+ if (fs.existsSync(bundledNode)) {
176
+ return bundledNode;
177
+ }
178
+ return process.execPath || "node";
179
+ }
180
+
181
+ function getUpdatedWindowsPath(newDirs, basePath = process.env.PATH || "") {
182
+ const existing = String(basePath || "")
183
+ .split(";")
184
+ .map((item) => trimOuterQuotes(item))
185
+ .filter(Boolean);
186
+ return uniqueNonEmpty([...newDirs, ...existing]).join(";");
187
+ }
188
+
189
+ function resolveCodexWindowsTargetTriple() {
190
+ if (process.arch === "arm64") {
191
+ return {
192
+ packageName: "@openai/codex-win32-arm64",
193
+ targetTriple: "aarch64-pc-windows-msvc",
194
+ managedEnvKey: "CODEX_MANAGED_BY_NPM",
195
+ };
196
+ }
197
+
198
+ if (process.arch === "x64") {
199
+ return {
200
+ packageName: "@openai/codex-win32-x64",
201
+ targetTriple: "x86_64-pc-windows-msvc",
202
+ managedEnvKey: "CODEX_MANAGED_BY_NPM",
203
+ };
204
+ }
205
+
206
+ return null;
207
+ }
208
+
209
+ function resolveWindowsNativeCodexInvocation(options = {}) {
210
+ const platform = options.platform || process.platform;
211
+ if (platform !== "win32") {
212
+ return null;
213
+ }
214
+
215
+ const target = resolveCodexWindowsTargetTriple();
216
+ if (!target) {
217
+ return null;
218
+ }
219
+
220
+ const explicitExecutable = trimOuterQuotes(options.explicitExecutable || "");
221
+ if (explicitExecutable && /\.exe$/iu.test(explicitExecutable) && fs.existsSync(explicitExecutable)) {
222
+ return {
223
+ command: explicitExecutable,
224
+ argsPrefix: [],
225
+ shell: false,
226
+ };
227
+ }
228
+
229
+ const findCommandPaths = options.findCommandPaths || ((command) => findWindowsCommandPaths(command));
230
+ const wrapperCandidates = [];
231
+
232
+ if (explicitExecutable) {
233
+ if (fs.existsSync(explicitExecutable)) {
234
+ wrapperCandidates.push(explicitExecutable);
235
+ } else {
236
+ wrapperCandidates.push(...findCommandPaths(explicitExecutable));
237
+ }
238
+ } else {
239
+ wrapperCandidates.push(...findCommandPaths("codex.ps1"));
240
+ wrapperCandidates.push(...findCommandPaths("codex"));
241
+ }
242
+
243
+ for (const wrapperPath of uniqueNonEmpty(wrapperCandidates)) {
244
+ const wrapperDir = path.dirname(wrapperPath);
245
+ const codexPackageRoot = path.join(wrapperDir, "node_modules", "@openai", "codex");
246
+ const vendorRoot = path.join(
247
+ codexPackageRoot,
248
+ "node_modules",
249
+ target.packageName,
250
+ "vendor",
251
+ target.targetTriple,
252
+ );
253
+ const binaryPath = path.join(vendorRoot, "codex", "codex.exe");
254
+ if (!fs.existsSync(binaryPath)) {
255
+ continue;
256
+ }
257
+ const rgPathDir = path.join(vendorRoot, "path");
258
+ const envPatch = {
259
+ [target.managedEnvKey]: "1",
260
+ };
261
+ if (fs.existsSync(rgPathDir)) {
262
+ envPatch.PATH = getUpdatedWindowsPath([rgPathDir], process.env.PATH || "");
263
+ }
264
+ return {
265
+ command: binaryPath,
266
+ argsPrefix: [],
267
+ shell: false,
268
+ env: envPatch,
269
+ };
270
+ }
271
+
272
+ return null;
273
+ }
274
+
275
+ function attachWindowsHiddenShellProxy(invocation, options = {}) {
276
+ if (!invocation || (options.platform || process.platform) !== "win32") {
277
+ return invocation;
278
+ }
279
+
280
+ try {
281
+ const envPatch = resolveWindowsHiddenShellEnvPatch({
282
+ basePath: invocation.env?.PATH || process.env.PATH || "",
283
+ });
284
+ if (!envPatch || !Object.keys(envPatch).length) {
285
+ return invocation;
286
+ }
287
+ return {
288
+ ...invocation,
289
+ env: {
290
+ ...(invocation.env || {}),
291
+ ...envPatch,
292
+ },
293
+ };
294
+ } catch (error) {
295
+ return {
296
+ ...invocation,
297
+ error: [
298
+ String(invocation.error || "").trim(),
299
+ `HelloLoop 无法准备 Windows 隐藏 shell 代理:${String(error?.message || error || "未知错误")}`,
300
+ ].filter(Boolean).join("\n"),
301
+ };
302
+ }
303
+ }
304
+
305
+ function resolveWindowsNodePackageInvocation(toolName, packageScriptSegments, options = {}) {
306
+ const platform = options.platform || process.platform;
307
+ if (platform !== "win32") {
308
+ return null;
309
+ }
310
+
311
+ const explicitExecutable = trimOuterQuotes(options.explicitExecutable || "");
312
+ if (explicitExecutable && /\.m?js$/iu.test(explicitExecutable) && fs.existsSync(explicitExecutable)) {
313
+ return {
314
+ command: process.execPath || "node",
315
+ argsPrefix: [explicitExecutable],
316
+ shell: false,
317
+ };
318
+ }
319
+
320
+ const findCommandPaths = options.findCommandPaths || ((command) => findWindowsCommandPaths(command));
321
+ const wrapperCandidates = [];
322
+
323
+ if (explicitExecutable) {
324
+ if (fs.existsSync(explicitExecutable)) {
325
+ wrapperCandidates.push(explicitExecutable);
326
+ } else {
327
+ wrapperCandidates.push(...findCommandPaths(explicitExecutable));
328
+ }
329
+ }
330
+
331
+ if (!wrapperCandidates.length) {
332
+ wrapperCandidates.push(...findCommandPaths(`${toolName}.ps1`));
333
+ wrapperCandidates.push(...findCommandPaths(`${toolName}.exe`));
334
+ wrapperCandidates.push(...findCommandPaths(toolName));
335
+ }
336
+
337
+ for (const wrapperPath of uniqueNonEmpty(wrapperCandidates)) {
338
+ if (isCmdLikeExecutable(wrapperPath) || !fs.existsSync(wrapperPath)) {
339
+ continue;
340
+ }
341
+ const packageScript = path.join(path.dirname(wrapperPath), "node_modules", ...packageScriptSegments);
342
+ if (!fs.existsSync(packageScript)) {
343
+ continue;
344
+ }
345
+ return {
346
+ command: resolveNodeHostForWrapper(wrapperPath),
347
+ argsPrefix: [packageScript],
348
+ shell: false,
349
+ };
350
+ }
351
+
352
+ return null;
353
+ }
354
+
147
355
  export function resolveVerifyShellInvocation(options = {}) {
148
356
  const platform = options.platform || process.platform;
149
357
 
@@ -217,11 +425,21 @@ export function resolveCliInvocation(options = {}) {
217
425
  }
218
426
 
219
427
  export function resolveCodexInvocation(options = {}) {
220
- return resolveCliInvocation({
428
+ const nativeCodexInvocation = resolveWindowsNativeCodexInvocation(options);
429
+ if (nativeCodexInvocation) {
430
+ return attachWindowsHiddenShellProxy(nativeCodexInvocation, options);
431
+ }
432
+
433
+ const nodePackageInvocation = resolveWindowsNodePackageInvocation("codex", ["@openai", "codex", "bin", "codex.js"], options);
434
+ if (nodePackageInvocation) {
435
+ return attachWindowsHiddenShellProxy(nodePackageInvocation, options);
436
+ }
437
+
438
+ return attachWindowsHiddenShellProxy(resolveCliInvocation({
221
439
  ...options,
222
440
  commandName: "codex",
223
441
  toolDisplayName: "Codex",
224
- });
442
+ }), options);
225
443
  }
226
444
 
227
445
  export function resolveClaudeInvocation(options = {}) {
@@ -1,5 +1,5 @@
1
1
  import { launchSupervisedCommand, renderSupervisorLaunchSummary } from "./supervisor_runtime.mjs";
2
- import { watchSupervisorSession } from "./supervisor_watch.mjs";
2
+ import { watchSupervisorSessionWithRecovery } from "./supervisor_watch.mjs";
3
3
 
4
4
  export function shouldUseSupervisor(options = {}) {
5
5
  return !options.dryRun
@@ -34,9 +34,10 @@ export async function launchAndMaybeWatchSupervisedCommand(context, command, opt
34
34
  }
35
35
 
36
36
  console.log("- 已进入附着观察模式;按 Ctrl+C 仅退出观察,不会停止后台任务。");
37
- const watchResult = await watchSupervisorSession(context, {
37
+ const watchResult = await watchSupervisorSessionWithRecovery(context, {
38
38
  sessionId: session.sessionId,
39
39
  pollMs: options.watchPollMs,
40
+ globalConfigFile: options.globalConfigFile,
40
41
  });
41
42
  return {
42
43
  detached: false,