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.
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +230 -506
- package/hosts/claude/marketplace/plugins/helloloop/.claude-plugin/plugin.json +1 -1
- package/hosts/gemini/extension/gemini-extension.json +1 -1
- package/native/windows-hidden-shell-proxy/HelloLoopHiddenShellProxy.csproj +11 -0
- package/native/windows-hidden-shell-proxy/Program.cs +498 -0
- package/package.json +4 -2
- package/src/activity_projection.mjs +294 -0
- package/src/analyze_confirmation.mjs +3 -1
- package/src/analyzer.mjs +2 -1
- package/src/auto_execution_options.mjs +13 -0
- package/src/background_launch.mjs +73 -0
- package/src/cli.mjs +49 -1
- package/src/cli_analyze_command.mjs +9 -5
- package/src/cli_args.mjs +102 -37
- package/src/cli_command_handlers.mjs +44 -4
- package/src/cli_support.mjs +2 -0
- package/src/dashboard_command.mjs +371 -0
- package/src/dashboard_tui.mjs +289 -0
- package/src/dashboard_web.mjs +351 -0
- package/src/dashboard_web_client.mjs +167 -0
- package/src/dashboard_web_page.mjs +49 -0
- package/src/engine_event_parser_codex.mjs +167 -0
- package/src/engine_process_support.mjs +1 -0
- package/src/engine_selection.mjs +24 -0
- package/src/engine_selection_probe.mjs +10 -6
- package/src/engine_selection_settings.mjs +12 -19
- package/src/execution_interactivity.mjs +12 -0
- package/src/host_continuation.mjs +305 -0
- package/src/install_codex.mjs +20 -8
- package/src/install_shared.mjs +9 -0
- package/src/node_process_launch.mjs +28 -0
- package/src/process.mjs +2 -0
- package/src/runner_execute_task.mjs +4 -0
- package/src/runner_execution_support.mjs +69 -3
- package/src/runner_once.mjs +4 -0
- package/src/runner_status.mjs +63 -7
- package/src/runtime_engine_support.mjs +41 -4
- package/src/runtime_engine_task.mjs +7 -0
- package/src/runtime_settings.mjs +105 -0
- package/src/runtime_settings_loader.mjs +19 -0
- package/src/shell_invocation.mjs +227 -9
- package/src/supervisor_cli_support.mjs +3 -2
- package/src/supervisor_guardian.mjs +307 -0
- package/src/supervisor_runtime.mjs +138 -82
- package/src/supervisor_state.mjs +64 -0
- package/src/supervisor_watch.mjs +92 -48
- package/src/terminal_session_limits.mjs +1 -21
- package/src/windows_hidden_shell_proxy.mjs +405 -0
- 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
|
-
|
|
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
|
+
}
|
package/src/shell_invocation.mjs
CHANGED
|
@@ -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
|
-
|
|
23
|
-
|
|
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
|
-
|
|
30
|
-
|
|
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
|
-
|
|
39
|
-
|
|
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}.
|
|
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
|
-
|
|
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 {
|
|
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
|
|
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,
|