impel-cli 0.15.3 → 0.16.1
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/README.md +13 -9
- package/package.json +1 -1
- package/src/apps.js +56 -5
- package/src/commands/setup.js +74 -25
- package/src/commands/update.js +27 -4
- package/src/installRecovery/checkpoint.js +5 -3
- package/src/installRecovery/engine.js +437 -0
- package/src/installRecovery/inference.js +167 -0
- package/src/installRecovery/redact.js +14 -3
- package/src/installRecovery/tools.js +648 -0
- package/src/installRecovery/actions.js +0 -440
- package/src/installRecovery/client.js +0 -84
- package/src/installRecovery/loop.js +0 -258
|
@@ -1,440 +0,0 @@
|
|
|
1
|
-
import { spawnSync } from "node:child_process";
|
|
2
|
-
import fs from "node:fs";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
|
|
5
|
-
import {
|
|
6
|
-
environmentValue,
|
|
7
|
-
findNativeBinary,
|
|
8
|
-
nativeCommandInvocation,
|
|
9
|
-
nativeSpawnInvocation,
|
|
10
|
-
} from "../nativeProcess.js";
|
|
11
|
-
import { redactInstallRecoveryText } from "./redact.js";
|
|
12
|
-
|
|
13
|
-
export const INSTALL_RECOVERY_ACTION_IDS = Object.freeze([
|
|
14
|
-
"inspect.runtime",
|
|
15
|
-
"inspect.process_lock",
|
|
16
|
-
"verify.cli",
|
|
17
|
-
"verify.gateway",
|
|
18
|
-
"install.vendor_cli",
|
|
19
|
-
"install.vendor_app",
|
|
20
|
-
"repair.user_path",
|
|
21
|
-
"repair.impel_profile",
|
|
22
|
-
"cleanup.impel_staging",
|
|
23
|
-
"retry.step",
|
|
24
|
-
]);
|
|
25
|
-
|
|
26
|
-
const ACTIONS = Object.freeze({
|
|
27
|
-
"inspect.runtime": { mutates: false },
|
|
28
|
-
"inspect.process_lock": { mutates: false },
|
|
29
|
-
"verify.cli": { mutates: false },
|
|
30
|
-
"verify.gateway": { mutates: false },
|
|
31
|
-
"install.vendor_cli": { mutates: true },
|
|
32
|
-
"install.vendor_app": { mutates: true },
|
|
33
|
-
"repair.user_path": { mutates: true },
|
|
34
|
-
"repair.impel_profile": { mutates: true },
|
|
35
|
-
"cleanup.impel_staging": { mutates: true },
|
|
36
|
-
"retry.step": { mutates: true },
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
const ACTION_KEYS = new Set([
|
|
40
|
-
"id",
|
|
41
|
-
"reason",
|
|
42
|
-
"parameters",
|
|
43
|
-
"requiresConfirmation",
|
|
44
|
-
]);
|
|
45
|
-
const PARAMETER_KEYS = new Set(["tool", "step", "path", "target"]);
|
|
46
|
-
|
|
47
|
-
function exactKeys(value, allowed) {
|
|
48
|
-
return Object.keys(value).every((key) => allowed.has(key));
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export function validateInstallRecoveryAction(value) {
|
|
52
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
53
|
-
if (!exactKeys(value, ACTION_KEYS)) return null;
|
|
54
|
-
if (!INSTALL_RECOVERY_ACTION_IDS.includes(value.id)) return null;
|
|
55
|
-
if (typeof value.reason !== "string" || value.reason.length > 2_000) return null;
|
|
56
|
-
if (typeof value.requiresConfirmation !== "boolean") return null;
|
|
57
|
-
const parameters = value.parameters;
|
|
58
|
-
if (!parameters || typeof parameters !== "object" || Array.isArray(parameters)) {
|
|
59
|
-
return null;
|
|
60
|
-
}
|
|
61
|
-
if (!exactKeys(parameters, PARAMETER_KEYS)) return null;
|
|
62
|
-
for (const key of PARAMETER_KEYS) {
|
|
63
|
-
if (!(key in parameters)) return null;
|
|
64
|
-
if (parameters[key] !== null && typeof parameters[key] !== "string") return null;
|
|
65
|
-
if (typeof parameters[key] === "string" && parameters[key].length > 512) return null;
|
|
66
|
-
}
|
|
67
|
-
if (parameters.tool !== null && !["claude", "codex"].includes(parameters.tool)) {
|
|
68
|
-
return null;
|
|
69
|
-
}
|
|
70
|
-
if (ACTIONS[value.id].mutates && value.requiresConfirmation !== true) return null;
|
|
71
|
-
return {
|
|
72
|
-
id: value.id,
|
|
73
|
-
reason: value.reason,
|
|
74
|
-
parameters: {
|
|
75
|
-
tool: parameters.tool,
|
|
76
|
-
step: parameters.step,
|
|
77
|
-
path: parameters.path,
|
|
78
|
-
target: parameters.target,
|
|
79
|
-
},
|
|
80
|
-
requiresConfirmation: value.requiresConfirmation,
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
export function installRecoveryActionMutates(actionId) {
|
|
85
|
-
return ACTIONS[actionId]?.mutates === true;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
function actionResult(outcome, summary, details = {}) {
|
|
89
|
-
return {
|
|
90
|
-
outcome,
|
|
91
|
-
summary: redactInstallRecoveryText(summary).slice(0, 2_000),
|
|
92
|
-
exitCode: Number.isInteger(details.exitCode) ? details.exitCode : null,
|
|
93
|
-
...(details.stdout
|
|
94
|
-
? { stdout: redactInstallRecoveryText(details.stdout).slice(0, 8_000) }
|
|
95
|
-
: {}),
|
|
96
|
-
...(details.stderr
|
|
97
|
-
? { stderr: redactInstallRecoveryText(details.stderr).slice(0, 8_000) }
|
|
98
|
-
: {}),
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
function inspectRuntime(context) {
|
|
103
|
-
return actionResult("succeeded", "Collected bounded runtime metadata.", {
|
|
104
|
-
stdout: JSON.stringify({
|
|
105
|
-
platform: context.platform,
|
|
106
|
-
architecture: process.arch,
|
|
107
|
-
nodeVersion: process.version,
|
|
108
|
-
pathEntries: String(environmentValue(context.environment, "PATH") || "")
|
|
109
|
-
.split(context.platform === "win32" ? ";" : path.delimiter)
|
|
110
|
-
.filter(Boolean).length,
|
|
111
|
-
tty: Boolean(context.isTTY),
|
|
112
|
-
}),
|
|
113
|
-
});
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
function inspectProcessLock(context, dependencies) {
|
|
117
|
-
const invocation = context.platform === "win32"
|
|
118
|
-
? { command: "tasklist.exe", args: ["/fo", "csv", "/nh"] }
|
|
119
|
-
: { command: "ps", args: ["-Ao", "comm="] };
|
|
120
|
-
const result = dependencies.spawnSync(invocation.command, invocation.args, {
|
|
121
|
-
encoding: "utf8",
|
|
122
|
-
env: context.environment,
|
|
123
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
124
|
-
timeout: 10_000,
|
|
125
|
-
windowsHide: true,
|
|
126
|
-
});
|
|
127
|
-
if (result?.error || result?.status !== 0) {
|
|
128
|
-
return actionResult("failed", "Could not inspect running installer processes.", {
|
|
129
|
-
exitCode: result?.status,
|
|
130
|
-
stderr: result?.error?.message || result?.stderr,
|
|
131
|
-
});
|
|
132
|
-
}
|
|
133
|
-
const relevant = String(result.stdout || "")
|
|
134
|
-
.split(/\r?\n/gu)
|
|
135
|
-
.filter((line) => /claude|chatgpt|codex|impel|node|npm|powershell/iu.test(line))
|
|
136
|
-
.slice(0, 40)
|
|
137
|
-
.join("\n");
|
|
138
|
-
return actionResult(
|
|
139
|
-
"succeeded",
|
|
140
|
-
relevant
|
|
141
|
-
? "Found potentially relevant running processes."
|
|
142
|
-
: "No relevant running process was found.",
|
|
143
|
-
{ stdout: relevant || "none" }
|
|
144
|
-
);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
function verifyCli(action, context, dependencies) {
|
|
148
|
-
const tool = action.parameters.tool;
|
|
149
|
-
if (!tool) return actionResult("failed", "verify.cli requires a tool parameter.");
|
|
150
|
-
const binary = dependencies.findNativeBinary(
|
|
151
|
-
tool,
|
|
152
|
-
context.environment,
|
|
153
|
-
context.platform
|
|
154
|
-
);
|
|
155
|
-
if (!binary) return actionResult("failed", `${tool} is not installed or discoverable.`);
|
|
156
|
-
const invocation = nativeSpawnInvocation(
|
|
157
|
-
binary,
|
|
158
|
-
["--version"],
|
|
159
|
-
context.environment,
|
|
160
|
-
context.platform
|
|
161
|
-
);
|
|
162
|
-
const result = dependencies.spawnSync(invocation.command, invocation.args, {
|
|
163
|
-
encoding: "utf8",
|
|
164
|
-
env: context.environment,
|
|
165
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
166
|
-
timeout: 15_000,
|
|
167
|
-
windowsHide: true,
|
|
168
|
-
windowsVerbatimArguments: invocation.windowsVerbatimArguments,
|
|
169
|
-
});
|
|
170
|
-
return result?.status === 0 && !result?.error
|
|
171
|
-
? actionResult("succeeded", `${tool} passed its bounded version check.`, {
|
|
172
|
-
stdout: String(result.stdout || "").trim(),
|
|
173
|
-
})
|
|
174
|
-
: actionResult("failed", `${tool} failed its bounded version check.`, {
|
|
175
|
-
exitCode: result?.status,
|
|
176
|
-
stderr: result?.error?.message || result?.stderr,
|
|
177
|
-
});
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
async function verifyGateway(context) {
|
|
181
|
-
if (typeof context.probeGateway !== "function") {
|
|
182
|
-
return actionResult("failed", "Gateway verification is unavailable in this recovery context.");
|
|
183
|
-
}
|
|
184
|
-
const result = await context.probeGateway();
|
|
185
|
-
return result?.reachable && !result?.rejected
|
|
186
|
-
? actionResult("succeeded", `Gateway responded with HTTP ${result.status}.`)
|
|
187
|
-
: actionResult(
|
|
188
|
-
"failed",
|
|
189
|
-
result?.rejected
|
|
190
|
-
? `Gateway rejected the credential with HTTP ${result.status}.`
|
|
191
|
-
: `Gateway could not be reached: ${result?.error || "unknown error"}.`
|
|
192
|
-
);
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
async function installVendorCli(action, context) {
|
|
196
|
-
if (context.platform !== "win32" || typeof context.installVendorClis !== "function") {
|
|
197
|
-
return actionResult("failed", "Automated vendor CLI installation is unavailable on this platform.");
|
|
198
|
-
}
|
|
199
|
-
const tool = action.parameters.tool;
|
|
200
|
-
if (!tool) return actionResult("failed", "install.vendor_cli requires a tool parameter.");
|
|
201
|
-
const result = await context.installVendorClis(tool);
|
|
202
|
-
return result?.binaries?.[tool]
|
|
203
|
-
? actionResult("succeeded", `${tool} installed and passed verification.`)
|
|
204
|
-
: actionResult("failed", `${tool} installation did not reach a verified state.`);
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
async function installVendorApp(action, context) {
|
|
208
|
-
if (typeof context.installVendorApp !== "function") {
|
|
209
|
-
return actionResult("failed", "Vendor app installation is unavailable in this recovery context.");
|
|
210
|
-
}
|
|
211
|
-
const target = action.parameters.target;
|
|
212
|
-
if (!target || !["claude", "codex", "all"].includes(target)) {
|
|
213
|
-
return actionResult("failed", "install.vendor_app requires claude, codex, or all.");
|
|
214
|
-
}
|
|
215
|
-
const installed = await context.installVendorApp(target);
|
|
216
|
-
return installed === false
|
|
217
|
-
? actionResult("failed", `The ${target} vendor app did not install cleanly.`)
|
|
218
|
-
: actionResult("succeeded", `The ${target} vendor app installation completed.`);
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
function addWindowsUserPath(action, context, dependencies) {
|
|
222
|
-
if (context.platform !== "win32") {
|
|
223
|
-
return actionResult("failed", "User PATH repair is currently available only on Windows.");
|
|
224
|
-
}
|
|
225
|
-
const tool = action.parameters.tool;
|
|
226
|
-
if (!tool) return actionResult("failed", "repair.user_path requires a tool parameter.");
|
|
227
|
-
const binary = dependencies.findNativeBinary(
|
|
228
|
-
tool,
|
|
229
|
-
context.environment,
|
|
230
|
-
context.platform
|
|
231
|
-
);
|
|
232
|
-
if (!binary || !path.win32.isAbsolute(binary)) {
|
|
233
|
-
return actionResult("failed", `${tool} has no trusted absolute install path to add.`);
|
|
234
|
-
}
|
|
235
|
-
const verification = nativeSpawnInvocation(
|
|
236
|
-
binary,
|
|
237
|
-
["--version"],
|
|
238
|
-
context.environment,
|
|
239
|
-
"win32"
|
|
240
|
-
);
|
|
241
|
-
const verified = dependencies.spawnSync(
|
|
242
|
-
verification.command,
|
|
243
|
-
verification.args,
|
|
244
|
-
{
|
|
245
|
-
encoding: "utf8",
|
|
246
|
-
env: context.environment,
|
|
247
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
248
|
-
timeout: 15_000,
|
|
249
|
-
windowsHide: true,
|
|
250
|
-
windowsVerbatimArguments: verification.windowsVerbatimArguments,
|
|
251
|
-
}
|
|
252
|
-
);
|
|
253
|
-
if (verified?.status !== 0 || verified?.error) {
|
|
254
|
-
return actionResult(
|
|
255
|
-
"failed",
|
|
256
|
-
`${tool}'s discovered install path did not pass its bounded version check.`,
|
|
257
|
-
{
|
|
258
|
-
exitCode: verified?.status,
|
|
259
|
-
stderr: verified?.error?.message || verified?.stderr,
|
|
260
|
-
}
|
|
261
|
-
);
|
|
262
|
-
}
|
|
263
|
-
const directory = path.win32.dirname(binary);
|
|
264
|
-
const currentPath = String(environmentValue(context.environment, "PATH") || "");
|
|
265
|
-
const entries = currentPath.split(";").filter(Boolean);
|
|
266
|
-
if (!entries.some((entry) => entry.toLowerCase() === directory.toLowerCase())) {
|
|
267
|
-
context.environment.PATH = [directory, currentPath].filter(Boolean).join(";");
|
|
268
|
-
}
|
|
269
|
-
const powershell = nativeCommandInvocation(
|
|
270
|
-
"powershell",
|
|
271
|
-
[
|
|
272
|
-
"-NoLogo",
|
|
273
|
-
"-NoProfile",
|
|
274
|
-
"-NonInteractive",
|
|
275
|
-
"-ExecutionPolicy",
|
|
276
|
-
"Bypass",
|
|
277
|
-
"-Command",
|
|
278
|
-
"$d=$env:IMPEL_RECOVERY_PATH; $p=[Environment]::GetEnvironmentVariable('Path','User'); $a=@($p -split ';' | Where-Object { $_ }); if (-not ($a | Where-Object { $_ -ieq $d })) { [Environment]::SetEnvironmentVariable('Path',(($a + $d) -join ';'),'User') }",
|
|
279
|
-
],
|
|
280
|
-
context.environment,
|
|
281
|
-
"win32"
|
|
282
|
-
);
|
|
283
|
-
const result = dependencies.spawnSync(powershell.command, powershell.args, {
|
|
284
|
-
encoding: "utf8",
|
|
285
|
-
env: { ...context.environment, IMPEL_RECOVERY_PATH: directory },
|
|
286
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
287
|
-
timeout: 15_000,
|
|
288
|
-
windowsHide: true,
|
|
289
|
-
windowsVerbatimArguments: powershell.windowsVerbatimArguments,
|
|
290
|
-
});
|
|
291
|
-
return result?.status === 0 && !result?.error
|
|
292
|
-
? actionResult("succeeded", `${tool}'s trusted install directory was added to the user PATH.`)
|
|
293
|
-
: actionResult("failed", `Could not add ${tool}'s install directory to the user PATH.`, {
|
|
294
|
-
exitCode: result?.status,
|
|
295
|
-
stderr: result?.error?.message || result?.stderr,
|
|
296
|
-
});
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
async function repairProfile(context) {
|
|
300
|
-
if (typeof context.repairProfiles !== "function") {
|
|
301
|
-
return actionResult("failed", "Impel profile repair is unavailable in this recovery context.");
|
|
302
|
-
}
|
|
303
|
-
const result = await context.repairProfiles();
|
|
304
|
-
return result === false
|
|
305
|
-
? actionResult("failed", "The Impel profile could not be repaired.")
|
|
306
|
-
: actionResult("succeeded", "The Impel-managed profile was regenerated.");
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
function managedStagingPath(candidate, roots) {
|
|
310
|
-
const resolved = path.resolve(candidate);
|
|
311
|
-
return roots.some((root) => {
|
|
312
|
-
const managedRoot = `${path.resolve(root)}${path.sep}`;
|
|
313
|
-
return (
|
|
314
|
-
resolved.startsWith(managedRoot) &&
|
|
315
|
-
/\.tmp-\d+$/u.test(path.basename(resolved))
|
|
316
|
-
);
|
|
317
|
-
});
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
function cleanupStaging(context, dependencies) {
|
|
321
|
-
const roots = context.managedRoots || [];
|
|
322
|
-
const candidates = context.stagingPaths || [];
|
|
323
|
-
const removable = candidates.filter((candidate) =>
|
|
324
|
-
managedStagingPath(candidate, roots)
|
|
325
|
-
);
|
|
326
|
-
for (const candidate of removable) {
|
|
327
|
-
dependencies.rmSync(candidate, { recursive: true, force: true });
|
|
328
|
-
}
|
|
329
|
-
return actionResult(
|
|
330
|
-
"succeeded",
|
|
331
|
-
removable.length
|
|
332
|
-
? `Removed ${removable.length} stale Impel-owned staging path(s).`
|
|
333
|
-
: "No validated stale Impel staging path was present."
|
|
334
|
-
);
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
async function retryStep(action, context) {
|
|
338
|
-
if (typeof context.retryStep !== "function") {
|
|
339
|
-
return actionResult("failed", "The failed setup step cannot be retried in this process.");
|
|
340
|
-
}
|
|
341
|
-
const result = await context.retryStep(action.parameters.step);
|
|
342
|
-
return result === false
|
|
343
|
-
? actionResult("failed", "The setup step failed again.")
|
|
344
|
-
: actionResult("succeeded", "The setup step completed on retry.");
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
export async function executeInstallRecoveryAction(
|
|
348
|
-
rawAction,
|
|
349
|
-
context = {},
|
|
350
|
-
dependencies = {}
|
|
351
|
-
) {
|
|
352
|
-
const action = validateInstallRecoveryAction(rawAction);
|
|
353
|
-
if (!action) {
|
|
354
|
-
return actionResult("failed", "Refused an unknown or malformed recovery action.");
|
|
355
|
-
}
|
|
356
|
-
if (installRecoveryActionMutates(action.id) && context.confirmed !== true) {
|
|
357
|
-
return actionResult("declined", "The local mutation was not confirmed.");
|
|
358
|
-
}
|
|
359
|
-
const resolvedContext = {
|
|
360
|
-
platform: process.platform,
|
|
361
|
-
environment: process.env,
|
|
362
|
-
isTTY: process.stdin.isTTY,
|
|
363
|
-
...context,
|
|
364
|
-
};
|
|
365
|
-
const io = {
|
|
366
|
-
spawnSync,
|
|
367
|
-
findNativeBinary,
|
|
368
|
-
rmSync: fs.rmSync,
|
|
369
|
-
...dependencies,
|
|
370
|
-
};
|
|
371
|
-
switch (action.id) {
|
|
372
|
-
case "inspect.runtime":
|
|
373
|
-
return inspectRuntime(resolvedContext);
|
|
374
|
-
case "inspect.process_lock":
|
|
375
|
-
return inspectProcessLock(resolvedContext, io);
|
|
376
|
-
case "verify.cli":
|
|
377
|
-
return verifyCli(action, resolvedContext, io);
|
|
378
|
-
case "verify.gateway":
|
|
379
|
-
return verifyGateway(resolvedContext);
|
|
380
|
-
case "install.vendor_cli":
|
|
381
|
-
return installVendorCli(action, resolvedContext);
|
|
382
|
-
case "install.vendor_app":
|
|
383
|
-
return installVendorApp(action, resolvedContext);
|
|
384
|
-
case "repair.user_path":
|
|
385
|
-
return addWindowsUserPath(action, resolvedContext, io);
|
|
386
|
-
case "repair.impel_profile":
|
|
387
|
-
return repairProfile(resolvedContext);
|
|
388
|
-
case "cleanup.impel_staging":
|
|
389
|
-
return cleanupStaging(resolvedContext, io);
|
|
390
|
-
case "retry.step":
|
|
391
|
-
return retryStep(action, resolvedContext);
|
|
392
|
-
default:
|
|
393
|
-
return actionResult("failed", "Refused an unregistered recovery action.");
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
export function deterministicInstallRecoveryAction(failure) {
|
|
398
|
-
const text = `${failure.step} ${failure.errorCode || ""} ${failure.message}`.toLowerCase();
|
|
399
|
-
if (/profile|config\.toml|settings\.json/u.test(text)) {
|
|
400
|
-
return {
|
|
401
|
-
id: "repair.impel_profile",
|
|
402
|
-
reason: "Regenerate only Impel-managed profile files.",
|
|
403
|
-
parameters: { tool: null, step: failure.step, path: null, target: null },
|
|
404
|
-
requiresConfirmation: true,
|
|
405
|
-
};
|
|
406
|
-
}
|
|
407
|
-
if (/staging|\.tmp-|incomplete app/u.test(text)) {
|
|
408
|
-
return {
|
|
409
|
-
id: "cleanup.impel_staging",
|
|
410
|
-
reason: "Remove only validated stale Impel staging paths.",
|
|
411
|
-
parameters: { tool: null, step: failure.step, path: null, target: null },
|
|
412
|
-
requiresConfirmation: true,
|
|
413
|
-
};
|
|
414
|
-
}
|
|
415
|
-
if (/installer completed.*not.*verif|not discoverable|stale path/u.test(text)) {
|
|
416
|
-
const tool = /claude/u.test(text) ? "claude" : /codex/u.test(text) ? "codex" : null;
|
|
417
|
-
if (tool) {
|
|
418
|
-
return {
|
|
419
|
-
id: "repair.user_path",
|
|
420
|
-
reason: "Add the discovered trusted vendor install directory to the user PATH.",
|
|
421
|
-
parameters: { tool, step: failure.step, path: null, target: null },
|
|
422
|
-
requiresConfirmation: true,
|
|
423
|
-
};
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
if (/busy|eperm|ebusy|process|running/u.test(text)) {
|
|
427
|
-
return {
|
|
428
|
-
id: "inspect.process_lock",
|
|
429
|
-
reason: "Inspect bounded process names for an installer lock.",
|
|
430
|
-
parameters: { tool: null, step: failure.step, path: null, target: null },
|
|
431
|
-
requiresConfirmation: false,
|
|
432
|
-
};
|
|
433
|
-
}
|
|
434
|
-
return {
|
|
435
|
-
id: "inspect.runtime",
|
|
436
|
-
reason: "Collect bounded local runtime metadata before asking for help.",
|
|
437
|
-
parameters: { tool: null, step: failure.step, path: null, target: null },
|
|
438
|
-
requiresConfirmation: false,
|
|
439
|
-
};
|
|
440
|
-
}
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
import { normalizeGatewayUrl, redactSecretText } from "../config.js";
|
|
2
|
-
|
|
3
|
-
const REQUEST_TIMEOUT_MS = 30_000;
|
|
4
|
-
|
|
5
|
-
async function requestJson(url, options, fetchImpl = fetch) {
|
|
6
|
-
const controller = new AbortController();
|
|
7
|
-
const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
|
|
8
|
-
try {
|
|
9
|
-
const response = await fetchImpl(url, {
|
|
10
|
-
...options,
|
|
11
|
-
headers: {
|
|
12
|
-
accept: "application/json",
|
|
13
|
-
"content-type": "application/json",
|
|
14
|
-
...(options.headers || {}),
|
|
15
|
-
},
|
|
16
|
-
signal: controller.signal,
|
|
17
|
-
});
|
|
18
|
-
const body = await response.json().catch(() => ({}));
|
|
19
|
-
if (!response.ok) {
|
|
20
|
-
const message = body?.error || `HTTP ${response.status}`;
|
|
21
|
-
const error = new Error(redactSecretText(message));
|
|
22
|
-
error.status = response.status;
|
|
23
|
-
error.code = body?.code;
|
|
24
|
-
throw error;
|
|
25
|
-
}
|
|
26
|
-
return body;
|
|
27
|
-
} catch (error) {
|
|
28
|
-
if (error?.name === "AbortError") {
|
|
29
|
-
throw new Error("install recovery request timed out after 30s");
|
|
30
|
-
}
|
|
31
|
-
throw error;
|
|
32
|
-
} finally {
|
|
33
|
-
clearTimeout(timeout);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export function createInstallRecoveryClient({ appUrl, fetchImpl = fetch }) {
|
|
38
|
-
const baseUrl = `${normalizeGatewayUrl(appUrl)}/api/cli/install-recovery/sessions`;
|
|
39
|
-
return {
|
|
40
|
-
create(pat, input) {
|
|
41
|
-
return requestJson(
|
|
42
|
-
baseUrl,
|
|
43
|
-
{
|
|
44
|
-
method: "POST",
|
|
45
|
-
headers: { authorization: `Bearer ${pat}` },
|
|
46
|
-
body: JSON.stringify(input),
|
|
47
|
-
},
|
|
48
|
-
fetchImpl
|
|
49
|
-
);
|
|
50
|
-
},
|
|
51
|
-
read(sessionId, recoveryToken) {
|
|
52
|
-
return requestJson(
|
|
53
|
-
`${baseUrl}/${encodeURIComponent(sessionId)}`,
|
|
54
|
-
{
|
|
55
|
-
method: "GET",
|
|
56
|
-
headers: { authorization: `Bearer ${recoveryToken}` },
|
|
57
|
-
},
|
|
58
|
-
fetchImpl
|
|
59
|
-
);
|
|
60
|
-
},
|
|
61
|
-
submitResult(sessionId, recoveryToken, result) {
|
|
62
|
-
return requestJson(
|
|
63
|
-
`${baseUrl}/${encodeURIComponent(sessionId)}/results`,
|
|
64
|
-
{
|
|
65
|
-
method: "POST",
|
|
66
|
-
headers: { authorization: `Bearer ${recoveryToken}` },
|
|
67
|
-
body: JSON.stringify(result),
|
|
68
|
-
},
|
|
69
|
-
fetchImpl
|
|
70
|
-
);
|
|
71
|
-
},
|
|
72
|
-
complete(sessionId, recoveryToken, result) {
|
|
73
|
-
return requestJson(
|
|
74
|
-
`${baseUrl}/${encodeURIComponent(sessionId)}/complete`,
|
|
75
|
-
{
|
|
76
|
-
method: "POST",
|
|
77
|
-
headers: { authorization: `Bearer ${recoveryToken}` },
|
|
78
|
-
body: JSON.stringify(result),
|
|
79
|
-
},
|
|
80
|
-
fetchImpl
|
|
81
|
-
);
|
|
82
|
-
},
|
|
83
|
-
};
|
|
84
|
-
}
|