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.
@@ -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
- const detect = (tool) => find(
53
- tool,
54
- environment,
55
- "darwin",
56
- "IMPEL",
57
- (binary) => verify(tool, binary, environment),
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
- if (typeof value.invocationId !== "string" || !SAFE_INVOCATION_RE.test(value.invocationId)) {
449
- throw new Error("native-agent continuation invocationId is invalid");
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") {
@@ -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
- export const IMPEL_CLI_ENTRYPOINT = process.env.IMPEL_CLI_EXTENSION_ENTRYPOINT
9
- || fileURLToPath(new URL("../bin/impel.js", import.meta.url));
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,
@@ -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
- return find(
118
- tool,
119
- environment,
120
- platform,
121
- environmentPrefix,
122
- (binary) => verify(tool, binary, environment, { environmentPrefix }),
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
  /**
@@ -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
- "on Codex, call wait_agent exactly once with timeout_ms=60000. Do not send follow-ups, short-poll the child, or synthesize while it is running. " +
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
  }