@mnemom/mnemom 0.17.32-next.2 → 0.17.32-next.3

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/dist/index.js CHANGED
@@ -41,6 +41,7 @@ import { isPrerelease } from "./lib/update/semver.js";
41
41
  import { applyConfigToEnv, launcherEntries, loadLauncherConfig, withoutDisabledExperimental, } from "./lib/launcher-config.js";
42
42
  import { loadExperimentalState } from "./lib/cli-config.js";
43
43
  import { assertEnabled, availableFeatures, EXPERIMENTAL_CONTRACT, gateCheck, ignoredSettingLines, reportCliError, } from "./lib/experimental.js";
44
+ import { durableVerbOffLines } from "./durable/gate.js";
44
45
  import { experimentalDisableCommand, experimentalEnableCommand, experimentalListCommand, runExperimentalMigration, } from "./commands/experimental.js";
45
46
  import { parseCompactAt, parseContextProfile, passthroughIsPrint, resolveImplicitSource, selectLaunchAgent, splitSessionArg, } from "./lib/agent.js";
46
47
  import { wrapCommand } from "./commands/wrap.js";
@@ -928,7 +929,7 @@ agentCmd
928
929
  : liveAgentShareDeps;
929
930
  process.exitCode = await agentShareCommand(opts, deps);
930
931
  });
931
- agentCmd
932
+ const agentSessionsCmd = agentCmd
932
933
  .command("sessions")
933
934
  .description("List your Mnemom Agent sessions from the local ledger (~/.mnemom/sessions.jsonl), newest first — the last 20 unless --all")
934
935
  .option("--all", "Every session, not just the newest 20")
@@ -942,6 +943,56 @@ agentCmd
942
943
  process.exit(1);
943
944
  }
944
945
  });
946
+ /** preAction (and --help) guard for a durable verb: while durable is off it does not exist (C14-R22). */
947
+ function requireDurableVerb(word, what) {
948
+ const lines = durableVerbOffLines(word, what, loadExperimentalState().enabled);
949
+ if (lines) {
950
+ for (const l of lines)
951
+ console.error(l);
952
+ process.exit(2);
953
+ }
954
+ }
955
+ // `mnemom agent sessions adopt` (durable M1, C4): hand the running tracked sessions on this
956
+ // machine to the host daemon, one table and one [Y/n] per agent group. Gated on `durable`.
957
+ const ADOPT_WHAT = "`mnemom agent sessions adopt`";
958
+ const agentSessionsAdoptCmd = agentSessionsCmd
959
+ .command("adopt", { hidden: hiddenUnless("durable") })
960
+ .description("EXPERIMENTAL (`mnemom experimental enable durable`): adopt the running sessions on this machine as durable sessions the host daemon runs, one table per agent")
961
+ .option("--dry-run", "Print the plan (tables, specs, dropped flags) and change nothing")
962
+ .option("--json", "Output the plan as JSON")
963
+ .option("--own-agent", "Move each adopted group onto its own agent")
964
+ .hook("preAction", () => requireDurableVerb("adopt", ADOPT_WHAT))
965
+ .action(async (opts, cmd) => {
966
+ try {
967
+ const { adoptCommand } = await import("./durable/adopt.js");
968
+ const { liveAdoptDeps } = await import("./durable/adopt-live.js");
969
+ // `agent` declares --dry-run and `sessions` declares --json, and commander hands a flag to
970
+ // the first command on the line that declares it: read both with the ancestors' options,
971
+ // or `adopt --dry-run` would adopt for real.
972
+ const all = cmd.optsWithGlobals();
973
+ process.exitCode = await adoptCommand(liveAdoptDeps(), {
974
+ dryRun: all.dryRun === true,
975
+ json: all.json === true,
976
+ ownAgent: opts.ownAgent,
977
+ });
978
+ }
979
+ catch (error) {
980
+ console.error("Error:", error instanceof Error ? error.message : error);
981
+ process.exit(1);
982
+ }
983
+ });
984
+ /**
985
+ * The durable-gated commands (BACKBONE Flags/CLI: off lists no durable verb). The committed
986
+ * command-tree.json leaves them out whatever the generating machine's experimental state
987
+ * (scripts/gen-command-tree.mjs); a piece that registers another durable verb adds it here.
988
+ */
989
+ export const DURABLE_GATED_COMMANDS = new Set([agentSessionsAdoptCmd]);
990
+ // commander answers --help before preAction: gate the help too, so the verb does not exist while off.
991
+ const adoptOutputHelp = agentSessionsAdoptCmd.outputHelp.bind(agentSessionsAdoptCmd);
992
+ agentSessionsAdoptCmd.outputHelp = ((...args) => {
993
+ requireDurableVerb("adopt", ADOPT_WHAT);
994
+ adoptOutputHelp(...args);
995
+ });
945
996
  agentCmd
946
997
  .command("show [ref]")
947
998
  .description("Show one session — the local launch record merged with the gateway's live goal state (sealed contract, latest verdict, guardrails). " +
@@ -690,6 +690,13 @@ export declare function buildCustomHeaders(opts: {
690
690
  /** `buildSessionConfigB64` output — the per-session env overlay (e.g. fold off). */
691
691
  sessionConfigB64?: string;
692
692
  }): string;
693
+ /**
694
+ * Strip the owner's own Mnemom credential env vars from a child env, IN
695
+ * PLACE — defense in depth against them riding through just because they
696
+ * happened to sit in the CLI's own process env (an exported shell var, or a
697
+ * leftover override) when `{ ...process.env }` was spread into it.
698
+ */
699
+ export declare function stripOwnerCredentialEnv(env: NodeJS.ProcessEnv): void;
693
700
  /**
694
701
  * Whether context folding is ON for this launch. Folding is a gateway feature
695
702
  * that summarises older turns to fit the window; it is ON by default. Precedence:
package/dist/lib/agent.js CHANGED
@@ -1096,6 +1096,32 @@ export function buildCustomHeaders(opts) {
1096
1096
  lines.push(`${SESSION_CONFIG_HEADER}: ${opts.sessionConfigB64}`);
1097
1097
  return lines.join("\n");
1098
1098
  }
1099
+ /**
1100
+ * Env vars that carry the OWNER's own Mnemom credential, in the CLI process's
1101
+ * own env — never the launched session's (MNE-8245). `MNEMOM_API_KEY` /
1102
+ * `MNEMOM_TOKEN` are `lib/auth.ts`'s ambient owner-credential vars (the ones
1103
+ * `resolveAuth` reads to authenticate the CLI's OWN calls, e.g. the session-key
1104
+ * mint); `MNEMOM_AGENT_MNEMOM_KEY` is `lib/subscription-login.ts`'s override for
1105
+ * that same owner credential on a subscription launch. None of the three has
1106
+ * any legitimate reason to reach a launched session's own env: the session's
1107
+ * only Mnemom credential is the minted, session-scoped key already spliced
1108
+ * into its headers/stdin by the caller.
1109
+ */
1110
+ const OWNER_CREDENTIAL_ENV_VARS = [
1111
+ "MNEMOM_API_KEY",
1112
+ "MNEMOM_TOKEN",
1113
+ "MNEMOM_AGENT_MNEMOM_KEY",
1114
+ ];
1115
+ /**
1116
+ * Strip the owner's own Mnemom credential env vars from a child env, IN
1117
+ * PLACE — defense in depth against them riding through just because they
1118
+ * happened to sit in the CLI's own process env (an exported shell var, or a
1119
+ * leftover override) when `{ ...process.env }` was spread into it.
1120
+ */
1121
+ export function stripOwnerCredentialEnv(env) {
1122
+ for (const k of OWNER_CREDENTIAL_ENV_VARS)
1123
+ Reflect.deleteProperty(env, k);
1124
+ }
1099
1125
  /**
1100
1126
  * Whether context folding is ON for this launch. Folding is a gateway feature
1101
1127
  * that summarises older turns to fit the window; it is ON by default. Precedence:
package/dist/lib/api.d.ts CHANGED
@@ -1181,6 +1181,38 @@ export declare function createApiKey(name: string, scopes: ApiKeyScope[]): Promi
1181
1181
  * moment this returns.
1182
1182
  */
1183
1183
  export declare function rotateApiKey(keyId: string): Promise<ApiKeyCreated>;
1184
+ /**
1185
+ * Response shape of `POST /v1/agents/{agent_id}/session-keys` (MNE-8244,
1186
+ * mnemom-api `src/code/session-keys.ts`). `key` is the cleartext secret,
1187
+ * returned once.
1188
+ */
1189
+ export interface SessionKeyMinted {
1190
+ key_id: string;
1191
+ key: string;
1192
+ key_prefix: string;
1193
+ scopes: string[];
1194
+ kind: string;
1195
+ agent_id: string;
1196
+ session_id: string | null;
1197
+ expires_at: string;
1198
+ created_at: string;
1199
+ }
1200
+ /**
1201
+ * POST /v1/agents/{agent_id}/session-keys (MNE-8244) — mint a short-lived
1202
+ * (24h), `['gateway']`-only API key bound to `agentId` (and `sessionId`
1203
+ * when known), using the CALLER'S OWN ambient credential (`postApi`'s
1204
+ * `authHeaders()` — never anything read from a launched session). This is
1205
+ * owner-gated server-side: a 403 `session_credential_forbidden` is what
1206
+ * the API returns if a session key is ever presented here instead.
1207
+ *
1208
+ * `mnemom agent` calls this so the LAUNCHED session never holds the
1209
+ * owner's own key (MNE-8245) — see `lib/session-key.ts` for the
1210
+ * fail-closed wiring around this call.
1211
+ */
1212
+ export declare function mintAgentSessionKey(agentId: string, opts?: {
1213
+ orgId?: string;
1214
+ sessionId?: string;
1215
+ }): Promise<SessionKeyMinted>;
1184
1216
  /**
1185
1217
  * DELETE /v1/api-keys/{key_id} — soft-revoke. The key row stays for
1186
1218
  * audit; `is_active` flips to false and `revoked_at` is timestamped.
package/dist/lib/api.js CHANGED
@@ -1477,6 +1477,22 @@ export async function rotateApiKey(keyId) {
1477
1477
  }
1478
1478
  return (await response.json());
1479
1479
  }
1480
+ /**
1481
+ * POST /v1/agents/{agent_id}/session-keys (MNE-8244) — mint a short-lived
1482
+ * (24h), `['gateway']`-only API key bound to `agentId` (and `sessionId`
1483
+ * when known), using the CALLER'S OWN ambient credential (`postApi`'s
1484
+ * `authHeaders()` — never anything read from a launched session). This is
1485
+ * owner-gated server-side: a 403 `session_credential_forbidden` is what
1486
+ * the API returns if a session key is ever presented here instead.
1487
+ *
1488
+ * `mnemom agent` calls this so the LAUNCHED session never holds the
1489
+ * owner's own key (MNE-8245) — see `lib/session-key.ts` for the
1490
+ * fail-closed wiring around this call.
1491
+ */
1492
+ export async function mintAgentSessionKey(agentId, opts = {}) {
1493
+ const endpoint = `/v1/agents/${encodeURIComponent(agentId)}/session-keys${opts.orgId ? `?org_id=${encodeURIComponent(opts.orgId)}` : ""}`;
1494
+ return postApi(endpoint, opts.sessionId ? { session_id: opts.sessionId } : {});
1495
+ }
1480
1496
  /**
1481
1497
  * DELETE /v1/api-keys/{key_id} — soft-revoke. The key row stays for
1482
1498
  * audit; `is_active` flips to false and `revoked_at` is timestamped.
@@ -12,7 +12,7 @@
12
12
  * Everything here is pure or dependency-injected; index.ts and the command
13
13
  * modules are the thin drivers.
14
14
  */
15
- export type ExperimentalId = "context-handoff" | "slack" | "implicit" | "measured-savings" | "record" | "fuel";
15
+ export type ExperimentalId = "context-handoff" | "slack" | "implicit" | "measured-savings" | "record" | "fuel" | "durable";
16
16
  /**
17
17
  * Features that used to be experimental and are now on by default for everyone. Their ids are
18
18
  * no longer in the registry, but `mnemom experimental enable|disable <id>` still accepts them
@@ -107,6 +107,19 @@ export const EXPERIMENTAL_FEATURES = [
107
107
  env: [],
108
108
  },
109
109
  },
110
+ {
111
+ // Durable governed sessions (M1). Off: every durable verb word exits 2 with the gate pointer
112
+ // and launches are unchanged; the host daemon reads config.json only, never MNEMOM_EXPERIMENTAL.
113
+ id: "durable",
114
+ summary: "Durable sessions: lock a session so this machine keeps it running, and adopt running ones",
115
+ since: "0.17.27",
116
+ gates: {
117
+ flags: [],
118
+ configKeys: ["durable"],
119
+ commands: ["mnemom agent sessions adopt"],
120
+ env: [],
121
+ },
122
+ },
110
123
  ];
111
124
  export const EXPERIMENTAL_IDS = EXPERIMENTAL_FEATURES.map((f) => f.id);
112
125
  export const EXPERIMENTAL_ENV = "MNEMOM_EXPERIMENTAL";
@@ -0,0 +1,68 @@
1
+ /**
2
+ * MNE-8245 — the LAUNCHED `mnemom agent` session must never hold the owner's
3
+ * own Mnemom API key: code running inside that session (a shell tool call,
4
+ * a malicious prompt injection, …) could read it straight out of its own
5
+ * environment and call owner-only mnemom-api routes as the owner, including
6
+ * the owner-only `update_contract` endpoint (MNE-8233) — defeating sealed
7
+ * mode, where a sealed contract changes only through the owner's explicit,
8
+ * out-of-session update.
9
+ *
10
+ * Per the 2026-10-06 ruling: the session gets a minted, session-scoped,
11
+ * `['gateway']`-only key instead (MNE-8244's `POST
12
+ * /v1/agents/{agent_id}/session-keys`), and the launch FAILS CLOSED if
13
+ * minting doesn't succeed — never a fallback to the owner's own credential.
14
+ *
15
+ * Pure apart from the injected deps, so every arm (mint success, every
16
+ * failure mode, revoke success/failure) is unit-tested without a real API.
17
+ */
18
+ import { type SessionKeyMinted } from "./api.js";
19
+ export interface SessionKeyDeps {
20
+ mint: (agentId: string, opts: {
21
+ orgId?: string;
22
+ sessionId?: string;
23
+ }) => Promise<SessionKeyMinted>;
24
+ revoke: (keyId: string) => Promise<void>;
25
+ say: (line: string) => void;
26
+ /**
27
+ * Override the mint/revoke timeout, tests only — real callers never set these, so the real
28
+ * 10s/3s bounds below always apply in production. Exists so a test can exercise "never
29
+ * settles" against a short, fast timeout rather than waiting out the real one.
30
+ */
31
+ mintTimeoutMs?: number;
32
+ revokeTimeoutMs?: number;
33
+ }
34
+ /** The real deps: `lib/api.ts`'s `mintAgentSessionKey` / `revokeApiKey`. */
35
+ export declare function defaultSessionKeyDeps(say: (line: string) => void): SessionKeyDeps;
36
+ export interface MintedSessionGatewayKey {
37
+ /** The cleartext secret — place this ONLY in the launched session's env. */
38
+ key: string;
39
+ /** Needed to revoke the key later; never placed in the session's env. */
40
+ keyId: string;
41
+ expiresAt: string;
42
+ }
43
+ /**
44
+ * Mint the ONE gateway-only, session-scoped key a launched session is
45
+ * allowed to carry.
46
+ *
47
+ * FAILS CLOSED: throws on ANY problem — a mint call that rejects (network
48
+ * error, non-2xx HTTP status, a malformed/empty body) — or a response that
49
+ * parses fine but is not the credential this is for: no key, no key_id (it
50
+ * could never be revoked), scopes that are not EXACTLY `['gateway']`, or a
51
+ * `kind` other than `'session'` when the response reports one. Every arm
52
+ * throws a `mnemom agent: …`-prefixed `Error` — the caller MUST NOT catch
53
+ * it and fall back to any other credential; it is meant to propagate all
54
+ * the way to the CLI's top-level error handler, which exits non-zero
55
+ * before anything is spawned.
56
+ */
57
+ export declare function mintSessionGatewayKey(deps: SessionKeyDeps, args: {
58
+ agentId: string;
59
+ orgId?: string;
60
+ sessionId?: string;
61
+ }): Promise<MintedSessionGatewayKey>;
62
+ /**
63
+ * Best-effort revoke on session exit (clean or not) — a failure here is a
64
+ * WARNING only, never thrown: the key is 24h-TTL'd regardless, so an
65
+ * un-revoked key is not a fresh security hole, just a slightly longer-lived
66
+ * one. Never called with the owner's own key id.
67
+ */
68
+ export declare function revokeSessionGatewayKey(deps: SessionKeyDeps, keyId: string): Promise<void>;
@@ -0,0 +1,101 @@
1
+ /**
2
+ * MNE-8245 — the LAUNCHED `mnemom agent` session must never hold the owner's
3
+ * own Mnemom API key: code running inside that session (a shell tool call,
4
+ * a malicious prompt injection, …) could read it straight out of its own
5
+ * environment and call owner-only mnemom-api routes as the owner, including
6
+ * the owner-only `update_contract` endpoint (MNE-8233) — defeating sealed
7
+ * mode, where a sealed contract changes only through the owner's explicit,
8
+ * out-of-session update.
9
+ *
10
+ * Per the 2026-10-06 ruling: the session gets a minted, session-scoped,
11
+ * `['gateway']`-only key instead (MNE-8244's `POST
12
+ * /v1/agents/{agent_id}/session-keys`), and the launch FAILS CLOSED if
13
+ * minting doesn't succeed — never a fallback to the owner's own credential.
14
+ *
15
+ * Pure apart from the injected deps, so every arm (mint success, every
16
+ * failure mode, revoke success/failure) is unit-tested without a real API.
17
+ */
18
+ import { mintAgentSessionKey, revokeApiKey } from "./api.js";
19
+ /** The real deps: `lib/api.ts`'s `mintAgentSessionKey` / `revokeApiKey`. */
20
+ export function defaultSessionKeyDeps(say) {
21
+ return { mint: mintAgentSessionKey, revoke: revokeApiKey, say };
22
+ }
23
+ /** How long a launch waits on the mint call before giving up (still fails closed — see below). */
24
+ const MINT_TIMEOUT_MS = 10_000;
25
+ /** How long session exit waits on the best-effort revoke before moving on regardless. */
26
+ const REVOKE_TIMEOUT_MS = 3_000;
27
+ /**
28
+ * Bounds `promise` to `ms`: resolves/rejects exactly as `promise` would, UNLESS `AbortSignal
29
+ * .timeout(ms)` fires first, in which case this rejects with a fresh timeout Error instead. Does
30
+ * NOT cancel `promise` itself — `deps.mint`/`deps.revoke` take no signal of their own — this only
31
+ * bounds how long the CALLER waits, which is exactly what both call sites below need: the launch
32
+ * (mint) and the exit handler (revoke) must never hang on a stuck network call.
33
+ */
34
+ function raceTimeout(promise, ms, message) {
35
+ return new Promise((resolve, reject) => {
36
+ const signal = AbortSignal.timeout(ms);
37
+ const onAbort = () => reject(new Error(message));
38
+ signal.addEventListener("abort", onAbort, { once: true });
39
+ promise.then((value) => {
40
+ signal.removeEventListener("abort", onAbort);
41
+ resolve(value);
42
+ }, (err) => {
43
+ signal.removeEventListener("abort", onAbort);
44
+ reject(err);
45
+ });
46
+ });
47
+ }
48
+ /**
49
+ * Mint the ONE gateway-only, session-scoped key a launched session is
50
+ * allowed to carry.
51
+ *
52
+ * FAILS CLOSED: throws on ANY problem — a mint call that rejects (network
53
+ * error, non-2xx HTTP status, a malformed/empty body) — or a response that
54
+ * parses fine but is not the credential this is for: no key, no key_id (it
55
+ * could never be revoked), scopes that are not EXACTLY `['gateway']`, or a
56
+ * `kind` other than `'session'` when the response reports one. Every arm
57
+ * throws a `mnemom agent: …`-prefixed `Error` — the caller MUST NOT catch
58
+ * it and fall back to any other credential; it is meant to propagate all
59
+ * the way to the CLI's top-level error handler, which exits non-zero
60
+ * before anything is spawned.
61
+ */
62
+ export async function mintSessionGatewayKey(deps, args) {
63
+ let res;
64
+ try {
65
+ const timeoutMs = deps.mintTimeoutMs ?? MINT_TIMEOUT_MS;
66
+ res = await raceTimeout(deps.mint(args.agentId, { orgId: args.orgId, sessionId: args.sessionId }), timeoutMs, `mint timed out after ${timeoutMs}ms`);
67
+ }
68
+ catch (err) {
69
+ const msg = err instanceof Error ? err.message : String(err);
70
+ throw new Error(`mnemom agent: could not mint a session-scoped gateway key for this launch (${msg}) — refusing to launch with the owner's own credential instead.`, { cause: err });
71
+ }
72
+ if (!res || typeof res.key !== "string" || res.key.length === 0) {
73
+ throw new Error("mnemom agent: the session-key mint API returned no key — refusing to launch.");
74
+ }
75
+ if (!res.key_id) {
76
+ throw new Error("mnemom agent: the session-key mint API returned no key_id (it could never be revoked) — refusing to launch.");
77
+ }
78
+ const scopes = res.scopes ?? [];
79
+ if (!(scopes.length === 1 && scopes[0] === "gateway")) {
80
+ throw new Error(`mnemom agent: the minted key's scopes were ${JSON.stringify(scopes)}, not exactly ['gateway'] — refusing to launch with it.`);
81
+ }
82
+ if (res.kind !== undefined && res.kind !== null && res.kind !== "session") {
83
+ throw new Error(`mnemom agent: the minted key's kind was '${String(res.kind)}', not 'session' — refusing to launch with it.`);
84
+ }
85
+ return { key: res.key, keyId: res.key_id, expiresAt: res.expires_at };
86
+ }
87
+ /**
88
+ * Best-effort revoke on session exit (clean or not) — a failure here is a
89
+ * WARNING only, never thrown: the key is 24h-TTL'd regardless, so an
90
+ * un-revoked key is not a fresh security hole, just a slightly longer-lived
91
+ * one. Never called with the owner's own key id.
92
+ */
93
+ export async function revokeSessionGatewayKey(deps, keyId) {
94
+ try {
95
+ const timeoutMs = deps.revokeTimeoutMs ?? REVOKE_TIMEOUT_MS;
96
+ await raceTimeout(deps.revoke(keyId), timeoutMs, `revoke timed out after ${timeoutMs}ms`);
97
+ }
98
+ catch (err) {
99
+ deps.say(`mnemom agent: could not revoke the session key (non-fatal — it expires on its own within 24h): ${err instanceof Error ? err.message : String(err)}`);
100
+ }
101
+ }
@@ -59,12 +59,25 @@ export declare function render(stdin: string, deps?: RenderDeps): string;
59
59
  export interface UserStatusLine {
60
60
  command: string;
61
61
  padding?: number;
62
+ /** Seconds between timed re-runs (Claude Code's statusLine.refreshInterval). */
63
+ refreshInterval?: number;
62
64
  }
63
65
  /**
64
66
  * The statusLine Claude Code would use without us: the first settings file (highest
65
67
  * precedence first) that sets one. Only a `command` statusLine can be wrapped.
66
68
  */
67
69
  export declare function userStatusLine(cwd: string, configDir: string): UserStatusLine | null;
70
+ /**
71
+ * Seconds between timed redraws on a fed launch, the feed's own poll (FEED_INTERVAL_MS).
72
+ * Claude Code redraws the status line only on conversation events unless refreshInterval is
73
+ * set, so totals that land after the last turn would never reach the screen.
74
+ */
75
+ export declare const FED_REFRESH_S = 10;
76
+ /**
77
+ * The statusLine layer to launch with: none when the segment is off; on a fed launch a timed
78
+ * redraw, the user's own shorter interval kept.
79
+ */
80
+ export declare function launchStatusLine(settings: Record<string, unknown> | null, fed: boolean): Record<string, unknown> | null;
68
81
  /**
69
82
  * Launch args + env that add the segment, or null when it should stay off: the opt-out
70
83
  * env, or the user passing their own --settings (two would fight over precedence).
@@ -122,12 +122,34 @@ export function userStatusLine(cwd, configDir) {
122
122
  continue;
123
123
  if (sl.type !== "command" || typeof sl.command !== "string" || !sl.command)
124
124
  return null;
125
- return typeof sl.padding === "number"
126
- ? { command: sl.command, padding: sl.padding }
127
- : { command: sl.command };
125
+ const user = { command: sl.command };
126
+ if (typeof sl.padding === "number")
127
+ user.padding = sl.padding;
128
+ if (typeof sl.refreshInterval === "number" && sl.refreshInterval >= 1)
129
+ user.refreshInterval = sl.refreshInterval;
130
+ return user;
128
131
  }
129
132
  return null;
130
133
  }
134
+ /**
135
+ * Seconds between timed redraws on a fed launch, the feed's own poll (FEED_INTERVAL_MS).
136
+ * Claude Code redraws the status line only on conversation events unless refreshInterval is
137
+ * set, so totals that land after the last turn would never reach the screen.
138
+ */
139
+ export const FED_REFRESH_S = 10;
140
+ /**
141
+ * The statusLine layer to launch with: none when the segment is off; on a fed launch a timed
142
+ * redraw, the user's own shorter interval kept.
143
+ */
144
+ export function launchStatusLine(settings, fed) {
145
+ if (!settings || !fed)
146
+ return settings;
147
+ const sl = settings.statusLine;
148
+ if (!sl || typeof sl !== "object")
149
+ return settings;
150
+ const own = typeof sl.refreshInterval === "number" ? sl.refreshInterval : Infinity;
151
+ return { ...settings, statusLine: { ...sl, refreshInterval: Math.min(own, FED_REFRESH_S) } };
152
+ }
131
153
  const sq = (s) => `'${s.replace(/'/g, `'\\''`)}'`;
132
154
  /**
133
155
  * Launch args + env that add the segment, or null when it should stay off: the opt-out
@@ -146,6 +168,8 @@ export function statuslineLaunch(o) {
146
168
  };
147
169
  if (user?.padding !== undefined)
148
170
  statusLine.padding = user.padding;
171
+ if (user?.refreshInterval !== undefined)
172
+ statusLine.refreshInterval = user.refreshInterval;
149
173
  const env = user ? { [INNER_ENV]: user.command } : {};
150
174
  // `settings` is the same layer as `args`, for a caller that merges it with its own (Claude Code
151
175
  // keeps only the last --settings it is given).
@@ -16,7 +16,7 @@
16
16
  *
17
17
  * Pure apart from the injected deps, so every arm is unit-tested.
18
18
  */
19
- import type { LaunchShape } from "./agent.js";
19
+ import { type LaunchShape } from "./agent.js";
20
20
  export type AgentAuth = "api-key" | "subscription";
21
21
  export declare const AGENT_AUTHS: readonly AgentAuth[];
22
22
  /**
@@ -16,6 +16,7 @@
16
16
  *
17
17
  * Pure apart from the injected deps, so every arm is unit-tested.
18
18
  */
19
+ import { stripOwnerCredentialEnv } from "./agent.js";
19
20
  export const AGENT_AUTHS = ["api-key", "subscription"];
20
21
  /**
21
22
  * The retired `console` value (it read the key Claude Code's Anthropic Console /login
@@ -140,7 +141,9 @@ export function subscriptionChildEnv(base, door, headers) {
140
141
  delete env.ANTHROPIC_API_KEY;
141
142
  delete env.ANTHROPIC_AUTH_TOKEN;
142
143
  // The key already rides in the headers; the child process has no other use for it.
143
- delete env.MNEMOM_AGENT_MNEMOM_KEY;
144
+ // MNE-8245 defense-in-depth: also strips MNEMOM_API_KEY/MNEMOM_TOKEN, in case the
145
+ // owner's own ambient credential sits in the CLI's process env for an unrelated reason.
146
+ stripOwnerCredentialEnv(env);
144
147
  env.ANTHROPIC_BASE_URL = door;
145
148
  env.ANTHROPIC_CUSTOM_HEADERS = headers;
146
149
  return env;