impel-cli 0.20.37 → 0.20.39
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 +25 -12
- package/RELEASE_NOTES.md +21 -0
- package/bin/impel.js +10 -1
- package/docs/native-agent-host-capability-matrix.md +16 -4
- package/package.json +1 -1
- package/scripts/capture-release-fixtures.mjs +56 -0
- package/scripts/clean-machine/README.md +51 -0
- package/scripts/clean-machine/run-clean-windows.sh +115 -0
- package/scripts/clean-machine/userdata.ps1.template +102 -0
- package/scripts/regen-managed-artifacts.mjs +475 -0
- package/scripts/verify-vendor-pins.mjs +207 -0
- package/src/agents.js +256 -32
- package/src/apps.js +233 -2
- package/src/cli.js +1 -0
- package/src/commands/apps.js +25 -2
- package/src/commands/launch.js +177 -4
- package/src/commands/mcp.js +1 -1
- package/src/commands/sessions.js +44 -11
- package/src/commands/token.js +23 -3
- package/src/macSetup.js +9 -8
- package/src/nativeAgentTransport.js +13 -4
- package/src/selfInvocation.js +12 -2
- package/src/vendorCliBinaries.js +36 -7
- package/src/verbatimRelay.js +9 -1
package/src/commands/token.js
CHANGED
|
@@ -1,14 +1,21 @@
|
|
|
1
1
|
import { loadConfig } from "../config.js";
|
|
2
2
|
import { parseFlags } from "../args.js";
|
|
3
3
|
import { ensureTenantSelection, normalizeTenantId, tenantCredential } from "../tenants.js";
|
|
4
|
+
import { maybeRepairManagedApps } from "./sessions.js";
|
|
4
5
|
import { RUNTIME_BRAND } from "../runtimeBrand.js";
|
|
5
6
|
|
|
6
7
|
// This is the `apiKeyHelper` / auth-command contract: stdout (and only
|
|
7
8
|
// stdout) must be exactly the bearer token, nothing else. Both Claude Code's
|
|
8
9
|
// apiKeyHelper and Codex CLI's `model_providers.<id>.auth.command` call this.
|
|
9
|
-
export async function cmdToken(argv = []) {
|
|
10
|
+
export async function cmdToken(argv = [], overrides = {}) {
|
|
11
|
+
const io = {
|
|
12
|
+
loadConfig,
|
|
13
|
+
ensureTenantSelection,
|
|
14
|
+
repairManagedApps: maybeRepairManagedApps,
|
|
15
|
+
...overrides,
|
|
16
|
+
};
|
|
10
17
|
const { flags } = parseFlags(argv, { tenant: { type: "string" } });
|
|
11
|
-
const config = loadConfig();
|
|
18
|
+
const config = io.loadConfig();
|
|
12
19
|
if (!config?.pat) {
|
|
13
20
|
process.stderr.write(`${RUNTIME_BRAND.cli.command}: not authenticated. Run \`${RUNTIME_BRAND.cli.command} setup\` (or \`${RUNTIME_BRAND.cli.command} auth\`) first.\n`);
|
|
14
21
|
process.exitCode = 1;
|
|
@@ -17,8 +24,21 @@ export async function cmdToken(argv = []) {
|
|
|
17
24
|
try {
|
|
18
25
|
const tenantId = flags.tenant
|
|
19
26
|
? normalizeTenantId(flags.tenant)
|
|
20
|
-
: (await ensureTenantSelection(config)).tenantId;
|
|
27
|
+
: (await io.ensureTenantSelection(config)).tenantId;
|
|
21
28
|
process.stdout.write(`${tenantCredential(config.pat, tenantId)}\n`);
|
|
29
|
+
// Drift-aware self-heal, strictly AFTER the token bytes are on stdout: the
|
|
30
|
+
// vendor apps call this helper on every auth, which makes it an Impel
|
|
31
|
+
// entry point that keeps running even after a vendor settings-writer
|
|
32
|
+
// rewrites a managed profile (a drifted config once stopped invoking the
|
|
33
|
+
// rest of the heal channel entirely). Kick the same detached,
|
|
34
|
+
// heartbeat-locked stale-only refresh the session hooks use — for both
|
|
35
|
+
// Codex and Claude drift. It must never write to stdout, never block the
|
|
36
|
+
// emission above, and never change this command's exit code.
|
|
37
|
+
try {
|
|
38
|
+
io.repairManagedApps(tenantId);
|
|
39
|
+
} catch {
|
|
40
|
+
// Opportunistic only; the token contract is already fulfilled.
|
|
41
|
+
}
|
|
22
42
|
} catch (error) {
|
|
23
43
|
process.stderr.write(`${RUNTIME_BRAND.cli.command}: ${error.message}\n`);
|
|
24
44
|
process.exitCode = 1;
|
package/src/macSetup.js
CHANGED
|
@@ -7,7 +7,7 @@ import { spawnSync } from "node:child_process";
|
|
|
7
7
|
import { ensureImpelClaudeProfile, ensureImpelCodexProfile } from "./cliProfiles.js";
|
|
8
8
|
import { findNativeBinary } from "./nativeProcess.js";
|
|
9
9
|
import { syncSkillsSafe } from "./skills.js";
|
|
10
|
-
import { verifyReviewedMacVendorCli } from "./vendorCliBinaries.js";
|
|
10
|
+
import { findReviewedVendorCliBinary, verifyReviewedMacVendorCli } from "./vendorCliBinaries.js";
|
|
11
11
|
import { PINNED_VENDOR_CLI_VERSIONS } from "./vendorCliVersions.js";
|
|
12
12
|
|
|
13
13
|
const MAX_INSTALLER_BYTES = 512 * 1024;
|
|
@@ -49,13 +49,14 @@ export const MAC_CLI_INSTALLERS = Object.freeze({
|
|
|
49
49
|
});
|
|
50
50
|
|
|
51
51
|
function detectMacClis(find, environment, verify) {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
52
|
+
// Detection must share the launcher's reviewed resolution (including the
|
|
53
|
+
// side-by-side fallback after vendor auto-update drift); a raw PATH probe
|
|
54
|
+
// here would classify a drifted-but-launchable install as missing and
|
|
55
|
+
// reinstall it on every setup/update pass.
|
|
56
|
+
const detect = (tool) => findReviewedVendorCliBinary(tool, environment, "darwin", "IMPEL", {
|
|
57
|
+
find,
|
|
58
|
+
verify,
|
|
59
|
+
});
|
|
59
60
|
return { claude: detect("claude"), codex: detect("codex") };
|
|
60
61
|
}
|
|
61
62
|
|
|
@@ -445,10 +445,18 @@ function resumedHandleForState(state, incomingHandle) {
|
|
|
445
445
|
function normalizeHandle(value) {
|
|
446
446
|
if (value?.schema === NATIVE_AGENT_CONTINUATION_SCHEMA) {
|
|
447
447
|
exactObject(value, ["schema", "invocationId"], "native-agent continuation");
|
|
448
|
-
|
|
449
|
-
|
|
448
|
+
// Minted ids are lowercase UUIDs; tolerate a case-drifting reserialization
|
|
449
|
+
// but nothing that changes the id's shape.
|
|
450
|
+
const invocationId = typeof value.invocationId === "string"
|
|
451
|
+
? value.invocationId.toLowerCase()
|
|
452
|
+
: null;
|
|
453
|
+
if (!invocationId || !SAFE_INVOCATION_RE.test(invocationId)) {
|
|
454
|
+
throw new Error(
|
|
455
|
+
"native-agent continuation invocationId is invalid; pass the exact continuation object "
|
|
456
|
+
+ "returned by the answer call, or call the answer tool again if none was returned",
|
|
457
|
+
);
|
|
450
458
|
}
|
|
451
|
-
return { ...value };
|
|
459
|
+
return { ...value, invocationId };
|
|
452
460
|
}
|
|
453
461
|
exactObject(value, [
|
|
454
462
|
"schema",
|
|
@@ -1854,7 +1862,7 @@ export class NativeAgentCompositeTransport {
|
|
|
1854
1862
|
}
|
|
1855
1863
|
}
|
|
1856
1864
|
|
|
1857
|
-
async answer(args, { signal } = {}) {
|
|
1865
|
+
async answer(args, { signal, onProgress } = {}) {
|
|
1858
1866
|
throwIfAborted(signal);
|
|
1859
1867
|
const attachment = boundedAttachmentSignal(signal, this.attachmentWindowMs);
|
|
1860
1868
|
let state = null;
|
|
@@ -1880,6 +1888,7 @@ export class NativeAgentCompositeTransport {
|
|
|
1880
1888
|
if (!lock) return continuationForState(state);
|
|
1881
1889
|
const result = await this.attach(state, prepared, {
|
|
1882
1890
|
signal: attachment.signal,
|
|
1891
|
+
onProgress,
|
|
1883
1892
|
lock,
|
|
1884
1893
|
});
|
|
1885
1894
|
if (result?.schema === NATIVE_AGENT_RESULT_SCHEMA && result.status === "succeeded") {
|
package/src/selfInvocation.js
CHANGED
|
@@ -5,8 +5,18 @@ import { environmentValue } from "./nativeProcess.js";
|
|
|
5
5
|
import { brandedEnvironmentName, RUNTIME_BRAND } from "./runtimeBrand.js";
|
|
6
6
|
|
|
7
7
|
/** The running package's own bin script, used directly for live child spawns. */
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
const PACKAGE_CLI_ENTRYPOINT = fileURLToPath(new URL("../bin/impel.js", import.meta.url));
|
|
9
|
+
const extensionEntrypoint = process.env.IMPEL_CLI_EXTENSION_ENTRYPOINT;
|
|
10
|
+
// The public Impel package never delegates its trusted MCP command prefix to
|
|
11
|
+
// inherited process state. First-party extension wrappers install a validated
|
|
12
|
+
// non-Impel runtime brand before import, must actually be executing the named
|
|
13
|
+
// entrypoint, and then prove that same path in createImpelCliExtension.
|
|
14
|
+
export const IMPEL_CLI_ENTRYPOINT = RUNTIME_BRAND.cli.packageName === "impel-cli"
|
|
15
|
+
|| typeof extensionEntrypoint !== "string"
|
|
16
|
+
|| !path.isAbsolute(extensionEntrypoint)
|
|
17
|
+
|| path.resolve(process.argv[1] || "") !== path.resolve(extensionEntrypoint)
|
|
18
|
+
? PACKAGE_CLI_ENTRYPOINT
|
|
19
|
+
: extensionEntrypoint;
|
|
10
20
|
|
|
11
21
|
// Matches a real package install (npm/pnpm/volta all place the package under
|
|
12
22
|
// node_modules/impel-cli; an unsupported project-local install matches too,
|
package/src/vendorCliBinaries.js
CHANGED
|
@@ -111,16 +111,45 @@ export function findReviewedVendorCliBinary(
|
|
|
111
111
|
{
|
|
112
112
|
find = findNativeBinary,
|
|
113
113
|
verify = verifyReviewedMacVendorCli,
|
|
114
|
+
realpath = fs.realpathSync,
|
|
115
|
+
stat = fs.statSync,
|
|
114
116
|
} = {},
|
|
115
117
|
) {
|
|
116
118
|
if (platform !== "darwin") return find(tool, environment, platform, environmentPrefix);
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
);
|
|
119
|
+
const accept = (binary) => verify(tool, binary, environment, { environmentPrefix });
|
|
120
|
+
const reviewed = find(tool, environment, platform, environmentPrefix, accept);
|
|
121
|
+
if (reviewed || tool !== "claude") return reviewed;
|
|
122
|
+
// An explicit override remains authoritative even while unusable: callers
|
|
123
|
+
// asked for exactly that binary, so never substitute another install.
|
|
124
|
+
const override = vendorOverrideName(tool, environmentPrefix);
|
|
125
|
+
if (override && environmentValue(environment, override)) return null;
|
|
126
|
+
|
|
127
|
+
// The standalone Claude installer keeps releases side by side under
|
|
128
|
+
// versions/<release> and its auto-updater only moves the launcher symlink.
|
|
129
|
+
// When the symlink drifts past the reviewed release, resolve the exact
|
|
130
|
+
// reviewed release directly instead of failing the launch; every candidate
|
|
131
|
+
// still passes the full version + signature + team verification.
|
|
132
|
+
const candidates = [];
|
|
133
|
+
const drifted = find(tool, environment, platform, environmentPrefix);
|
|
134
|
+
if (drifted) {
|
|
135
|
+
try {
|
|
136
|
+
candidates.push(path.join(path.dirname(realpath(drifted)), PINNED_VENDOR_CLI_VERSIONS.claude));
|
|
137
|
+
} catch {
|
|
138
|
+
// An unresolvable shim only forfeits the sibling candidate.
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
const home = environmentValue(environment, "HOME");
|
|
142
|
+
if (home) {
|
|
143
|
+
candidates.push(path.join(home, ".local", "share", "claude", "versions", PINNED_VENDOR_CLI_VERSIONS.claude));
|
|
144
|
+
}
|
|
145
|
+
for (const candidate of candidates) {
|
|
146
|
+
try {
|
|
147
|
+
if (stat(candidate).isFile() && accept(candidate)) return candidate;
|
|
148
|
+
} catch {
|
|
149
|
+
// A missing side-by-side release keeps resolution fail-closed.
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return null;
|
|
124
153
|
}
|
|
125
154
|
|
|
126
155
|
/**
|
package/src/verbatimRelay.js
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
|
+
import { CODEX_NATIVE_AGENT_ATTACHMENT_WINDOW_MS } from "./selfInvocation.js";
|
|
2
|
+
|
|
1
3
|
export const VERBATIM_RELAY_OPT_IN_MARKER = "verbatimRelay enabled";
|
|
2
4
|
|
|
5
|
+
// The parent's single blocking wait must outlast the Codex child's bounded
|
|
6
|
+
// native-agent attachment window or the parent's visible working span
|
|
7
|
+
// truncates while the child is still running. Deriving it keeps the two
|
|
8
|
+
// contracts linked when the window moves again.
|
|
9
|
+
export const VERBATIM_RELAY_PARENT_WAIT_MS = CODEX_NATIVE_AGENT_ATTACHMENT_WINDOW_MS + 20_000;
|
|
10
|
+
|
|
3
11
|
export const VERBATIM_SPAWN_REQUIREMENT = 'fork_turns="none"';
|
|
4
12
|
|
|
5
13
|
export const VERBATIM_FINAL_TEXT_CONSTRAINTS =
|
|
@@ -14,7 +22,7 @@ export function parentVerbatimRelayAppendix() {
|
|
|
14
22
|
`When an explicit custom agent's catalog-derived description declares ${VERBATIM_RELAY_OPT_IN_MARKER}, ` +
|
|
15
23
|
`spawn it with ${VERBATIM_SPAWN_REQUIREMENT} and relay its finalText verbatim with ${VERBATIM_FINAL_TEXT_CONSTRAINTS}; ` +
|
|
16
24
|
"preserve Sources sections and citations exactly. Start one child, then use one blocking wait sized for the child budget; " +
|
|
17
|
-
|
|
25
|
+
`on Codex, call wait_agent exactly once with timeout_ms=${VERBATIM_RELAY_PARENT_WAIT_MS}. Do not send follow-ups, short-poll the child, or synthesize while it is running. ` +
|
|
18
26
|
"Custom agents without that declaration keep the default delegation behavior."
|
|
19
27
|
);
|
|
20
28
|
}
|