@mnemom/mnemom 0.17.38 → 0.17.39-next.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.
@@ -43,7 +43,7 @@ import { getApiUrl, getGatewayUrl, getWebsiteUrl, MNEMOM_DIR } from "../lib/conf
43
43
  import { fmt } from "../lib/format.js";
44
44
  import { askYesNo, isInteractive } from "../lib/prompt.js";
45
45
  import { malformedAgentIdMessage, parseAgentRef, resolveAnthropicDoor, resolveGatewayHost, sanitizeAgentName, singleCellError, } from "../lib/agent.js";
46
- import { readMnemomKey, resolveAgentAuth, resolveSubscriptionMnemomKey, sayRetiredConsoleNotice, } from "../lib/subscription-login.js";
46
+ import { mnemomKeyRejectedFix, readMnemomKey, resolveAgentAuth, resolveSubscriptionMnemomKey, sayRetiredConsoleNotice, } from "../lib/subscription-login.js";
47
47
  import { configKeySpec, loadLauncherConfig, writeLauncherConfig } from "../lib/launcher-config.js";
48
48
  import { IdentityRouteMissingError, resolveServedIdentity, resolveServedIdentityDetail, } from "../lib/agent-identity-route.js";
49
49
  import { readConfirmedAgent, readServedAgent, servedFingerprint, writeConfirmedAgent, writeServedAgent, } from "../lib/served-agent-cache.js";
@@ -431,7 +431,7 @@ export async function ensureClaimedServedAgent(opts) {
431
431
  }
432
432
  catch (err) {
433
433
  if (err instanceof MnemomKeyRejectedError) {
434
- say(fmt.warn("mnemom agent: the gateway rejected your stored Mnemom API key — launching without it (fix: `mnemom agent config` / re-run subscription setup, or unset MNEMOM_AGENT_MNEMOM_KEY)."));
434
+ say(fmt.warn(`mnemom agent: the gateway rejected your Mnemom API key — launching without it. ${mnemomKeyRejectedFix(getApiUrl(), process.env)}`));
435
435
  const retried = await ensureClaimedServedAgent({ ...opts, mnemomKey: undefined });
436
436
  return { ...retried, mnemomKeyRejected: true };
437
437
  }
@@ -560,6 +560,15 @@ async function resolveServedAgent(opts, fp) {
560
560
  if (opts.anthropicKey && opts.mnemomKey && isRejectedMnemomKey(err)) {
561
561
  throw new MnemomKeyRejectedError();
562
562
  }
563
+ // A subscription launch whose Mnemom key the gateway rejected (401, e.g. invalid_mnemom_key):
564
+ // most often a key saved for another cell. Name the cell and the exact fix.
565
+ if (!opts.anthropicKey &&
566
+ opts.mnemomKey &&
567
+ err instanceof MnemomApiError &&
568
+ err.effectiveStatus === 401) {
569
+ throw new UnclaimedAgentError(`mnemom agent: the gateway rejected the Mnemom API key this launch sent (${err.code ?? "401"}), so it can't confirm '${slug}' is claimed. ` +
570
+ mnemomKeyRejectedFix(getApiUrl(), process.env));
571
+ }
563
572
  if (err instanceof IdentityRouteMissingError) {
564
573
  throw new UnclaimedAgentError(`mnemom agent: ${err.message}, so it can't confirm '${slug}' is claimed. ` +
565
574
  "Launch with an Anthropic API key instead (`--auth api-key`), or try again once the gateway is updated.");
@@ -736,7 +745,7 @@ function pinRefusal(err, pin, slug) {
736
745
  }
737
746
  if (code === "mnemom_api_key_required" || status === 401) {
738
747
  return (`${head}: a pin needs a valid Mnemom API key (${code ?? "401"}). ` +
739
- "Set MNEMOM_AGENT_MNEMOM_KEY, or delete mnemom_api_key from ~/.mnemom/launcher.json so the next pinned launch mints a fresh one.");
748
+ mnemomKeyRejectedFix(getApiUrl(), process.env));
740
749
  }
741
750
  return `${head} (${describeApiError(err)}). A pinned launch never falls back to another agent.`;
742
751
  }
@@ -265,7 +265,7 @@ export declare function recordSessionLaunch(ctx: {
265
265
  compactAt: number | null;
266
266
  }): void;
267
267
  /** The real I/O behind a subscription launch's Mnemom key (lib/subscription-login.ts). */
268
- export declare function defaultMnemomKeyDeps(storePath?: string): MnemomKeyDeps;
268
+ export declare function defaultMnemomKeyDeps(storePath?: string, apiUrl?: string): MnemomKeyDeps;
269
269
  /**
270
270
  * Resolve the Anthropic key with NO 1Password dependency (built for external
271
271
  * customer adoption), in order:
@@ -41,7 +41,7 @@ import { createApiKey } from "../lib/api.js";
41
41
  import { recordOrgFor } from "../lib/record-org.js";
42
42
  import { httpDoorAllowed } from "../durable/test-mode.mjs";
43
43
  import { defaultSessionKeyDeps, mintSessionGatewayKey, revokeSessionGatewayKey, startSessionKeyRenewal, } from "../lib/session-key.js";
44
- import { MNEMOM_KEY_STORE_FIELD, resolveAgentAuth, sayRetiredConsoleNotice, readMnemomKey, resolveSubscriptionMnemomKey, launchChildEnv, subscriptionShape, } from "../lib/subscription-login.js";
44
+ import { mnemomKeyStorePatch, storedMnemomKeyForCell, resolveAgentAuth, sayRetiredConsoleNotice, readMnemomKey, resolveSubscriptionMnemomKey, launchChildEnv, subscriptionShape, } from "../lib/subscription-login.js";
45
45
  import { clearLaunching, liveLaunchingDeps, markLaunching } from "../lib/durable/launching.js";
46
46
  import { LEDGER_FILE, appendSessionRecord, findResumeCandidate, readSessionRecords, resolveRetentionDays, resumedContextProfile, toLedgerContract, } from "../lib/agent-ledger.js";
47
47
  import { isLoggedIn } from "../lib/auth.js";
@@ -55,7 +55,7 @@ import { authConfigured, chooseAuth, detectClaudeLogin, readClaudeLoginState, }
55
55
  import { goalAlignmentLine, resolveSessionSetup } from "./agent-session.js";
56
56
  import { defaultAgentSlug, ensureClaimedServedAgent, ensureSessionAndBalance, isQuietPreambleExit, readComposedProtection, resolveDefaultSlug, resolvePinnedAgent, resolvePreflightedAccount, } from "./agent-setup.js";
57
57
  import { assembleContract, buildGoalModeHeader, buildCustomHeaders, buildHandoffMcpConfigJson, buildSessionConfig, buildSessionConfigB64, assertCompactAtCompatible, assertOffProfileCompatible, compactAtWindowWarning, describeCompactAt, launchWindow, resolveCompactAt, applyGatewayHintHeaders, resolveCompactionIntercept, resolveContextHandoff, resolveContextProfile, resolveHandoffGatewayHost, resolveUsageSavings, HANDOFF_RC_KEY_ENV_NAME, HANDOFF_RC_MNEMOM_KEY_ENV_NAME, malformedAgentIdMessage, parseAgentRef, isProdGateway, launchPreferenceExplicit, launchPreflighted, remoteControlAllowed, applyToolSearchDefault, DISABLE_1M_ENV, readCcSettings, passthroughModel, resolveWindowModel, resolveAnthropicDoor, resolveLaunchModel, resolveConversationId, resolveGatewayHost, singleCellError, resolveLaunchShape, resolveRemoteControlShape, liftPassthroughName, passthroughName, sanitizeAgentName, resumeCommand, sessionDisplayName, SESSION_NAME_ENV, SESSION_CONFIG_HEADER, stripOwnerCredentialEnv, } from "../lib/agent.js";
58
- import { LAUNCHER_STORE_PATH, mergeStore } from "../lib/launcher-store.js";
58
+ import { LAUNCHER_STORE_PATH, mergeStore, readStore } from "../lib/launcher-store.js";
59
59
  import { slackChannelArgs } from "../lib/slack-launch.js";
60
60
  import { appendPluginDirsEnv, applyPluginEnv, pluginEnabled, pluginPlanLine, resolvePluginDir, } from "../lib/claude-plugin.js";
61
61
  import { CLI_VERSION } from "../version.js";
@@ -1146,21 +1146,17 @@ export function recordSessionLaunch(ctx) {
1146
1146
  // Credential resolution (zero 1Password)
1147
1147
  // ───────────────────────────────────────────────────────────────────────────
1148
1148
  /** The real I/O behind a subscription launch's Mnemom key (lib/subscription-login.ts). */
1149
- export function defaultMnemomKeyDeps(storePath = CODE_STORE) {
1149
+ export function defaultMnemomKeyDeps(storePath = CODE_STORE, apiUrl = getApiUrl()) {
1150
1150
  return {
1151
1151
  env: process.env,
1152
+ // The key is per cell (keyed by the API origin): a key minted on one cell is rejected by
1153
+ // another cell's gateway (401), so it is never read for, or sent to, a different cell.
1152
1154
  readStored: () => {
1153
- try {
1154
- const data = JSON.parse(readFileSync(storePath, "utf8"));
1155
- const v = data[MNEMOM_KEY_STORE_FIELD];
1156
- return typeof v === "string" ? stripCrlf(v) : undefined;
1157
- }
1158
- catch {
1159
- return undefined;
1160
- }
1155
+ const v = storedMnemomKeyForCell(readStore(storePath), apiUrl);
1156
+ return v === undefined ? undefined : stripCrlf(v);
1161
1157
  },
1162
1158
  mint: async () => (await createApiKey(`mnemom-agent ${hostname()}`, ["gateway"])).key,
1163
- store: (key) => mergeStore({ [MNEMOM_KEY_STORE_FIELD]: key }, storePath),
1159
+ store: (key) => mergeStore(mnemomKeyStorePatch(readStore(storePath), apiUrl, key), storePath),
1164
1160
  say,
1165
1161
  };
1166
1162
  }
@@ -38,8 +38,31 @@ export declare function sayRetiredConsoleNotice(flag: string | undefined, env: R
38
38
  export declare const AGENT_AUTH_ENV = "MNEMOM_AGENT_AUTH";
39
39
  /** Env override for the Mnemom key a subscription launch sends. */
40
40
  export declare const MNEMOM_KEY_ENV = "MNEMOM_AGENT_MNEMOM_KEY";
41
- /** Field in ~/.mnemom/launcher.json (0600) holding the minted key. */
41
+ /**
42
+ * LEGACY field in ~/.mnemom/launcher.json (0600): the single, cell-blind key every launcher
43
+ * before the per-cell store saved. Every such key was minted against production, so it is read
44
+ * ONLY when the CLI points at the production API and is never sent to another cell.
45
+ */
42
46
  export declare const MNEMOM_KEY_STORE_FIELD = "mnemom_api_key";
47
+ /** Field in ~/.mnemom/launcher.json (0600): the minted keys per cell, `{ "<api origin>": key }`. */
48
+ export declare const MNEMOM_KEYS_STORE_FIELD = "mnemom_api_keys";
49
+ /** The production API origin: the only cell the legacy flat field may be used for. */
50
+ export declare const PRODUCTION_API_ORIGIN = "https://api.mnemom.ai";
51
+ /**
52
+ * The cell a Mnemom key belongs to: the API base's origin, lowercased. An unparseable base
53
+ * gives null, and every caller then fails closed (no stored key is read, none is saved).
54
+ */
55
+ export declare function mnemomKeyCell(apiUrl: string): string | null;
56
+ /**
57
+ * The stored Mnemom key for the cell `apiUrl` names, from the launcher.json object `data`:
58
+ * the per-cell entry, else (production only) the legacy flat field, else undefined. Pure.
59
+ */
60
+ export declare function storedMnemomKeyForCell(data: Record<string, unknown>, apiUrl: string): string | undefined;
61
+ /**
62
+ * The launcher.json patch that saves `key` for the cell `apiUrl` names, keeping every other
63
+ * cell's entry. Throws on an unparseable base, so nothing is saved under a cell we can't name.
64
+ */
65
+ export declare function mnemomKeyStorePatch(data: Record<string, unknown>, apiUrl: string, key: string): Record<string, unknown>;
43
66
  export declare function coerceAgentAuth(raw: string): AgentAuth;
44
67
  /**
45
68
  * launcher.toml `auth`: the retired `console` is kept as-is (never a load error); it folds into
@@ -48,6 +71,13 @@ export declare function coerceAgentAuth(raw: string): AgentAuth;
48
71
  export declare function parseSavedAgentAuth(raw: string): SavedAgentAuth;
49
72
  /** `--auth` flag > MNEMOM_AGENT_AUTH (launcher.toml folds in here; a saved retired `console` → api-key) > api-key. */
50
73
  export declare function resolveAgentAuth(flag: string | undefined, env: Record<string, string | undefined>): AgentAuth;
74
+ /**
75
+ * The fix line for a gateway 401 on the Mnemom key this launch sent. The saved key is per cell
76
+ * (keyed by the API origin), so the line names the cell and the exact remedy: a key set in
77
+ * MNEMOM_AGENT_MNEMOM_KEY must come from this cell; a saved one is removed from this cell's
78
+ * entry so the next launch mints one here. Pure.
79
+ */
80
+ export declare function mnemomKeyRejectedFix(apiUrl: string, env: Record<string, string | undefined>): string;
51
81
  export interface MnemomKeyDeps {
52
82
  env: Record<string, string | undefined>;
53
83
  readStored(): string | undefined;
@@ -39,8 +39,64 @@ export function sayRetiredConsoleNotice(flag, env, say) {
39
39
  export const AGENT_AUTH_ENV = "MNEMOM_AGENT_AUTH";
40
40
  /** Env override for the Mnemom key a subscription launch sends. */
41
41
  export const MNEMOM_KEY_ENV = "MNEMOM_AGENT_MNEMOM_KEY";
42
- /** Field in ~/.mnemom/launcher.json (0600) holding the minted key. */
42
+ /**
43
+ * LEGACY field in ~/.mnemom/launcher.json (0600): the single, cell-blind key every launcher
44
+ * before the per-cell store saved. Every such key was minted against production, so it is read
45
+ * ONLY when the CLI points at the production API and is never sent to another cell.
46
+ */
43
47
  export const MNEMOM_KEY_STORE_FIELD = "mnemom_api_key";
48
+ /** Field in ~/.mnemom/launcher.json (0600): the minted keys per cell, `{ "<api origin>": key }`. */
49
+ export const MNEMOM_KEYS_STORE_FIELD = "mnemom_api_keys";
50
+ /** The production API origin: the only cell the legacy flat field may be used for. */
51
+ export const PRODUCTION_API_ORIGIN = "https://api.mnemom.ai";
52
+ /**
53
+ * The cell a Mnemom key belongs to: the API base's origin, lowercased. An unparseable base
54
+ * gives null, and every caller then fails closed (no stored key is read, none is saved).
55
+ */
56
+ export function mnemomKeyCell(apiUrl) {
57
+ try {
58
+ const origin = new URL(apiUrl).origin.toLowerCase();
59
+ return origin && origin !== "null" ? origin : null;
60
+ }
61
+ catch {
62
+ return null;
63
+ }
64
+ }
65
+ /**
66
+ * The stored Mnemom key for the cell `apiUrl` names, from the launcher.json object `data`:
67
+ * the per-cell entry, else (production only) the legacy flat field, else undefined. Pure.
68
+ */
69
+ export function storedMnemomKeyForCell(data, apiUrl) {
70
+ const cell = mnemomKeyCell(apiUrl);
71
+ if (cell === null)
72
+ return undefined;
73
+ const map = data[MNEMOM_KEYS_STORE_FIELD];
74
+ if (map && typeof map === "object" && !Array.isArray(map)) {
75
+ const v = map[cell];
76
+ if (typeof v === "string" && v.trim())
77
+ return v;
78
+ }
79
+ if (cell === PRODUCTION_API_ORIGIN) {
80
+ const legacy = data[MNEMOM_KEY_STORE_FIELD];
81
+ if (typeof legacy === "string")
82
+ return legacy;
83
+ }
84
+ return undefined;
85
+ }
86
+ /**
87
+ * The launcher.json patch that saves `key` for the cell `apiUrl` names, keeping every other
88
+ * cell's entry. Throws on an unparseable base, so nothing is saved under a cell we can't name.
89
+ */
90
+ export function mnemomKeyStorePatch(data, apiUrl, key) {
91
+ const cell = mnemomKeyCell(apiUrl);
92
+ if (cell === null)
93
+ throw new Error(`cannot tell which cell the API base '${apiUrl}' is`);
94
+ const current = data[MNEMOM_KEYS_STORE_FIELD];
95
+ const kept = current && typeof current === "object" && !Array.isArray(current)
96
+ ? current
97
+ : {};
98
+ return { [MNEMOM_KEYS_STORE_FIELD]: { ...kept, [cell]: key } };
99
+ }
44
100
  /**
45
101
  * The key is spliced into the newline-joined ANTHROPIC_CUSTOM_HEADERS block, so an embedded
46
102
  * CR/LF (a multi-line paste, a bad env value) would inject extra header lines.
@@ -72,6 +128,30 @@ export function resolveAgentAuth(flag, env) {
72
128
  const fromEnv = (env[AGENT_AUTH_ENV] ?? "").trim();
73
129
  return fromEnv ? coerceAgentAuth(fromEnv) : "api-key";
74
130
  }
131
+ /**
132
+ * The fix line for a gateway 401 on the Mnemom key this launch sent. The saved key is per cell
133
+ * (keyed by the API origin), so the line names the cell and the exact remedy: a key set in
134
+ * MNEMOM_AGENT_MNEMOM_KEY must come from this cell; a saved one is removed from this cell's
135
+ * entry so the next launch mints one here. Pure.
136
+ */
137
+ export function mnemomKeyRejectedFix(apiUrl, env) {
138
+ const cell = mnemomKeyCell(apiUrl);
139
+ const where = cell ?? `the API base '${apiUrl}'`;
140
+ const create = "`mnemom api-key create --name mnemom-agent --scopes gateway` while the CLI points at it";
141
+ if (headerSafe(env[MNEMOM_KEY_ENV] ?? "")) {
142
+ return (`The Mnemom API key in ${MNEMOM_KEY_ENV} is not valid on this cell (${where}); a key from another cell is rejected here. ` +
143
+ `Set ${MNEMOM_KEY_ENV} to a key from this cell (${create}), or unset it so the launcher uses or mints this cell's own key.`);
144
+ }
145
+ if (cell === null) {
146
+ return (`The Mnemom API key is not valid on ${where}, and the launcher can't tell which cell that is. ` +
147
+ `Set ${MNEMOM_KEY_ENV} to a key from that cell (${create}).`);
148
+ }
149
+ const legacy = cell === PRODUCTION_API_ORIGIN
150
+ ? ` (and the older top-level "${MNEMOM_KEY_STORE_FIELD}" field)`
151
+ : "";
152
+ return (`The saved Mnemom API key is per cell, and this cell (${cell}) rejected the one saved for it. ` +
153
+ `Set ${MNEMOM_KEY_ENV} to a key from this cell (${create}), or remove the "${cell}" entry under "${MNEMOM_KEYS_STORE_FIELD}"${legacy} in ~/.mnemom/launcher.json so the next launch mints one.`);
154
+ }
75
155
  /**
76
156
  * The Mnemom key this machine already has — MNEMOM_AGENT_MNEMOM_KEY, else the stored key — or
77
157
  * undefined. NEVER mints. An unpinned api-key launch sends it (`x-mnemom-api-key`) alongside its
@@ -108,7 +188,7 @@ export async function resolveSubscriptionMnemomKey(deps, purpose = "subscription
108
188
  throw new Error("mnemom agent: the API returned an empty Mnemom key.");
109
189
  try {
110
190
  deps.store(minted);
111
- deps.say(`mnemom agent: minted a gateway-only Mnemom API key for ${purpose} (saved to ~/.mnemom/launcher.json, 0600).`);
191
+ deps.say(`mnemom agent: minted a gateway-only Mnemom API key for ${purpose} (saved for this cell to ~/.mnemom/launcher.json, 0600).`);
112
192
  }
113
193
  catch {
114
194
  deps.say("mnemom agent: minted a gateway-only Mnemom API key for this launch but could not save it — the next launch mints another.");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mnemom/mnemom",
3
- "version": "0.17.38",
3
+ "version": "0.17.39-next.0",
4
4
  "description": "Transparent AI agent tracing",
5
5
  "type": "module",
6
6
  "bin": {