@mnemom/mnemom 0.17.7-next.2 → 0.17.7

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.
@@ -33,10 +33,10 @@ import { basename, join, dirname } from "node:path";
33
33
  import { fileURLToPath } from "node:url";
34
34
  import { statuslineLaunch } from "../lib/statusline.js";
35
35
  import { getApiUrl, getGatewayUrl, MNEMOM_DIR } from "../lib/config.js";
36
- import { apiAcceptsMnemomKey, createOrgApiKey, getAgentByName, listOwnApiKeysForCheck, revokeApiKey, } from "../lib/api.js";
37
- import { checkMnemomKey, MNEMOM_KEY_ENV, readLocalMnemomKey, readStoredMnemomKey, resolveAgentAuth, resolveLaunchMnemomKey, storePatchForMnemomKey, subscriptionChildEnv, subscriptionShape, } from "../lib/subscription-login.js";
38
- import { LEDGER_FILE, appendSessionRecord, findResumeCandidate, readSessionRecords, resolveRetentionDays, resumedContextProfile, toLedgerContract, } from "../lib/agent-ledger.js";
39
- import { getActiveOrg, loadCliConfig } from "../lib/cli-config.js";
36
+ import { createApiKey, getAgentByName } from "../lib/api.js";
37
+ import { MNEMOM_KEY_STORE_FIELD, resolveAgentAuth, resolveSubscriptionMnemomKey, subscriptionChildEnv, subscriptionShape, } from "../lib/subscription-login.js";
38
+ import { LEDGER_FILE, appendSessionRecord, findResumeCandidate, readSessionRecords, resolveRetentionDays, toLedgerContract, } from "../lib/agent-ledger.js";
39
+ import { loadCliConfig } from "../lib/cli-config.js";
40
40
  import { configKeySpec, loadLauncherConfig, writeLauncherConfig } from "../lib/launcher-config.js";
41
41
  import { experimentalNotice } from "../lib/experimental.js";
42
42
  import { fmt } from "../lib/format.js";
@@ -45,42 +45,22 @@ import { authConfigured, chooseAuth, detectClaudeLogin, readClaudeLoginState, }
45
45
  import { requireConsoleKey, readConsoleKey } from "../lib/console-key.js";
46
46
  import { goalAlignmentLine, resolveSessionSetup } from "./agent-session.js";
47
47
  import { AgentIdentityMismatchError, defaultAgentSlug, ensureGovernedAgent, ensureSessionAndBalance, isQuietPreambleExit, resolveDefaultSlug, resolvePreflightedAccount, } from "./agent-setup.js";
48
- import { assembleContract, buildGoalModeHeader, buildCustomHeaders, buildHandoffMcpConfigJson, buildSessionConfig, buildSessionConfigB64, assertCompactAtCompatible, assertOffProfileCompatible, compactAtWindowWarning, describeCompactAt, launchWindow, resolveCompactAt, applyGatewayHintHeaders, resolveCompactionIntercept, resolveContextHandoff, resolveContextProfile, resolveHandoffGatewayHost, HANDOFF_RC_KEY_ENV_NAME, HANDOFF_RC_MNEMOM_KEY_ENV_NAME, isProdGateway, launchPreferenceExplicit, launchPreflighted, remoteControlAllowed, applyToolSearchDefault, DISABLE_1M_ENV, readCcSettings, resolveWindowModel, resolveAnthropicDoor, resolveLaunchModel, resolveConversationId, resolveGatewayHost, singleCellError, resolveLaunchShape, resolveRemoteControlShape, passthroughName, sanitizeAgentName, sessionDisplayName, SESSION_NAME_ENV, SESSION_CONFIG_HEADER, } from "../lib/agent.js";
49
- import { LAUNCHER_STORE_PATH, mergeStore, readStore } from "../lib/launcher-store.js";
48
+ import { assembleContract, buildGoalModeHeader, buildCustomHeaders, buildHandoffMcpConfigJson, buildSessionConfig, buildSessionConfigB64, assertCompactAtCompatible, compactAtWindowWarning, describeCompactAt, launchWindow, resolveCompactAt, applyGatewayHintHeaders, resolveCompactionIntercept, resolveContextHandoff, resolveContextProfile, resolveHandoffGatewayHost, HANDOFF_RC_KEY_ENV_NAME, isProdGateway, launchPreferenceExplicit, launchPreflighted, remoteControlAllowed, applyToolSearchDefault, DISABLE_1M_ENV, readCcSettings, resolveWindowModel, resolveAnthropicDoor, resolveLaunchModel, resolveConversationId, resolveGatewayHost, singleCellError, resolveLaunchShape, resolveRemoteControlShape, passthroughName, sanitizeAgentName, sessionDisplayName, SESSION_NAME_ENV, SESSION_CONFIG_HEADER, } from "../lib/agent.js";
49
+ import { LAUNCHER_STORE_PATH, mergeStore } from "../lib/launcher-store.js";
50
50
  import { slackChannelArgs } from "../lib/slack-launch.js";
51
51
  /** The CLI's own provider-key store (zero 1Password): ~/.mnemom/launcher.json, 0600. */
52
52
  const CODE_STORE = LAUNCHER_STORE_PATH;
53
53
  const say = (line = "") => console.error(line);
54
54
  /** The one-line launch note when the compaction intercept is ON. */
55
55
  const COMPACTION_INTERCEPT_NOTE = "mnemom agent: compaction intercept ON (experimental) — requested from the gateway (GATEWAY_USAGE_REAL_SIZE, GATEWAY_COMPACTION_INTERCEPT); a gateway without the companion support drops both keys and the session runs as before.";
56
- const OFF_DROPS_COMPACTION_NOTE = "mnemom agent: compaction intercept off for this session — context profile off leaves compaction to the coding agent, so the standing compaction_intercept / compact_at setting is not sent.";
57
56
  /**
58
- * The compaction notes for a resolved launch. Context profile `off` sends the intercept off and
59
- * no target (`buildSessionConfig`), so a standing intercept or target is reported as dropped
60
- * rather than as ON.
61
- */
62
- function sayCompaction(profile, compactionIntercept, compactAt, window) {
63
- const off = profile === "off";
64
- sayCompactAt(off ? null : compactAt, window);
65
- if (off && (compactionIntercept || compactAt !== null))
66
- say(OFF_DROPS_COMPACTION_NOTE);
67
- else if (compactionIntercept)
68
- say(COMPACTION_INTERCEPT_NOTE);
69
- }
70
- /**
71
- * The one-line launch note for the context profile: `null` (none chosen — nothing is sent) names
72
- * the cell default. `off` keeps the wording `--no-context` has always printed.
57
+ * The one-line launch note for a non-default context profile (`balanced`, the default, is
58
+ * silent — nothing is sent). `off` keeps the wording `--no-context` has always printed.
73
59
  */
74
60
  function sayContextProfile(profile) {
75
- if (profile === null) {
76
- say("mnemom agent: context: cell default");
77
- }
78
- else if (profile === "off") {
61
+ if (profile === "off") {
79
62
  say("mnemom agent: context folding OFF for this session (--no-context or context = false).");
80
63
  }
81
- else if (profile === "balanced") {
82
- say("mnemom agent: context profile balanced — the gateway keeps more of the earlier work verbatim and folds later.");
83
- }
84
64
  else if (profile === "aggressive") {
85
65
  say("mnemom agent: context profile aggressive — the gateway folds earlier and keeps a tighter working set.");
86
66
  }
@@ -194,12 +174,11 @@ export async function agentCommand(scenarioArg, options = {}) {
194
174
  const cellNote = isProdGateway(env) ? "us-2/prod" : `custom (${gatewayHost})`;
195
175
  const yolo = options.yolo !== false; // default ON
196
176
  const contextHint = options.contextHint !== false; // default ON
197
- // Context profile: none (null — the cell's own default, no fold key sent) unless
198
- // --context-profile / MNEMOM_AGENT_CONTEXT_PROFILE (the `context_profile` setting) chooses
199
- // one; --no-context (or `context = false` in launcher.toml → MNEMOM_AGENT_CONTEXT) is `off`. A
200
- // chosen profile rides in the per-session config header; `undefined` sessionConfigB64 ⇒
201
- // nothing sent. A resume re-resolves it below, once the resumed session is known.
202
- let contextProfile = resolveContextProfile({
177
+ // Context profile: `balanced` (the gateway's own defaults) unless --context-profile /
178
+ // MNEMOM_AGENT_CONTEXT_PROFILE (the `context_profile` setting) says otherwise; --no-context
179
+ // (or `context = false` in launcher.toml → MNEMOM_AGENT_CONTEXT) is `off`. Anything but
180
+ // balanced rides in the per-session config header; `undefined` sessionConfigB64 ⇒ nothing sent.
181
+ const contextProfile = resolveContextProfile({
203
182
  contextProfile: options.contextProfile,
204
183
  context: options.context,
205
184
  env,
@@ -223,13 +202,6 @@ export async function agentCommand(scenarioArg, options = {}) {
223
202
  // cannot arm without the intercept it just turned off. Refuse rather than silently pick
224
203
  // a side.
225
204
  assertCompactAtCompatible({ compactionIntercept: options.compactionIntercept, compactAt });
226
- // Context profile off sends the intercept off: an explicit --compaction-intercept or
227
- // --compact-at <N> in the same launch contradicts it. A resume re-checks below.
228
- assertOffProfileCompatible({
229
- profile: contextProfile,
230
- compactionIntercept: options.compactionIntercept,
231
- compactAt: options.compactAt,
232
- });
233
205
  const experimental = options.experimental ?? [];
234
206
  const sessionConfig = buildSessionConfig({
235
207
  profile: contextProfile,
@@ -237,7 +209,7 @@ export async function agentCommand(scenarioArg, options = {}) {
237
209
  compactionIntercept,
238
210
  compactAt,
239
211
  });
240
- let sessionConfigB64 = buildSessionConfigB64({
212
+ const sessionConfigB64 = buildSessionConfigB64({
241
213
  profile: contextProfile,
242
214
  contextHandoff,
243
215
  compactionIntercept,
@@ -301,10 +273,12 @@ export async function agentCommand(scenarioArg, options = {}) {
301
273
  say("mnemom agent: the plan shows Remote Control because this machine can run it; the real launch uses it only if your account is entitled (internal Mnemom accounts), else terminal-only.");
302
274
  }
303
275
  sayContextProfile(contextProfile);
304
- sayCompaction(contextProfile, compactionIntercept, compactAt, launchWindow(windowModelFor(options, cli)));
276
+ sayCompactAt(compactAt, launchWindow(windowModelFor(options, cli)));
305
277
  if (contextHandoff) {
306
278
  say("mnemom agent: context handoff ON (experimental) — the `handoff` MCP tool would be registered.");
307
279
  }
280
+ if (compactionIntercept)
281
+ say(COMPACTION_INTERCEPT_NOTE);
308
282
  return;
309
283
  }
310
284
  // Real launch: make sure we actually have a CLI to run. If we couldn't find it,
@@ -328,9 +302,8 @@ export async function agentCommand(scenarioArg, options = {}) {
328
302
  // lives in (default: the user's personal org). No µ ⇒ blocked with a
329
303
  // portal-style top-up message. Skipped only on --dry-run (handled above).
330
304
  // MNEMOM_AGENT_PREFLIGHTED=1 (orchestrators such as the ADW fleet that already
331
- // ran their own session + balance preflight) skips those round trips; the launch
332
- // org resolves the same way and its entitlement is still fetched — see
333
- // resolvePreflightedAccount.
305
+ // ran their own session + balance preflight) skips those round trips; the
306
+ // entitlement is still fetched — see resolvePreflightedAccount.
334
307
  let account;
335
308
  try {
336
309
  account = launchPreflighted(env)
@@ -370,19 +343,14 @@ export async function agentCommand(scenarioArg, options = {}) {
370
343
  rcCapable: rc.ok,
371
344
  say,
372
345
  });
373
- // Subscription login: Remote Control only for RC-entitled (internal) accounts.
346
+ // Subscription login: Remote Control only for RC-entitled (internal) accounts, and the governed identity anchors on a
347
+ // Mnemom API key (minted once and stored if there is none). Resolved after sign-in so
348
+ // minting can use the session.
349
+ let mnemomKey;
374
350
  if (auth === "subscription") {
375
351
  shape = subscriptionShape(shape, shapeExplicit, rcAllowed, say);
352
+ mnemomKey = await resolveSubscriptionMnemomKey(defaultMnemomKeyDeps());
376
353
  }
377
- // Every launch — api-key, console and subscription, every shape — sends a Mnemom API key
378
- // (x-mnemom-api-key): the gateway refuses `mnemom agent` traffic without a verified one.
379
- // Resolved after sign-in so minting and the one-time liveness check can use the session: the
380
- // key stored for the active org, checked once (a revoked/rotated one is re-minted), else a
381
- // fresh key minted for the launch's org (account.orgId: the active org, else the personal
382
- // org — the org the agent is claimed into, which the gateway's org check requires of the key)
383
- // and stored. No key ⇒ this throws and the launch does not start (a session the gateway
384
- // would refuse is worse than an actionable error).
385
- const mnemomKey = await resolveLaunchMnemomKey(defaultMnemomKeyDeps(CODE_STORE, account.orgId));
386
354
  // Now that a session exists, a defaulted slug uses the real login handle from
387
355
  // /v1/auth/me (the stored token has no email, so the pre-sign-in value was the
388
356
  // "agent-user" fallback). Only when the caller didn't name an agent. Under an
@@ -413,7 +381,6 @@ export async function agentCommand(scenarioArg, options = {}) {
413
381
  const ensured = await ensureGovernedAgent({
414
382
  slug: agent,
415
383
  anthropicKey,
416
- mnemomKey,
417
384
  setup: options.setup,
418
385
  gatewayHost,
419
386
  });
@@ -457,38 +424,6 @@ export async function agentCommand(scenarioArg, options = {}) {
457
424
  passthrough: [...(options.passthrough ?? []), "--resume", continuation.resumeCcSessionId],
458
425
  };
459
426
  }
460
- // A launch that resumes a Claude Code session (the continuation above, or the caller's own
461
- // `-- --resume <id>`) keeps the profile that session ran under, unless this launch chose one
462
- // with a flag. Read from the ledger only — nothing here writes a default anywhere.
463
- const resumeId = shape === "server" ? null : passthroughResumeId(options.passthrough ?? []);
464
- if (resumeId && options.contextProfile === undefined && options.context !== false) {
465
- let resumed;
466
- try {
467
- resumed = resumedContextProfile(readSessionRecords({ dir: MNEMOM_DIR }), {
468
- ccSessionId: resumeId,
469
- orgId: account.orgId,
470
- gatewayHost,
471
- });
472
- }
473
- catch {
474
- resumed = null; // best-effort: an unreadable ledger keeps the launch's own resolution
475
- }
476
- if (resumed !== null && resumed !== contextProfile) {
477
- contextProfile = resolveContextProfile({ context: options.context, resumed, env });
478
- assertOffProfileCompatible({
479
- profile: contextProfile,
480
- compactionIntercept: options.compactionIntercept,
481
- compactAt: options.compactAt,
482
- });
483
- sessionConfigB64 = buildSessionConfigB64({
484
- profile: contextProfile,
485
- contextHandoff,
486
- compactionIntercept,
487
- compactAt,
488
- experimental,
489
- });
490
- }
491
- }
492
427
  // ── local session ledger (~/.mnemom/sessions.jsonl) ── one line per real
493
428
  // launch so `mnemom agent sessions` / `show` can find this conversation later.
494
429
  // Written after provisioning so a freshly claimed agent's id resolves. Holds
@@ -520,14 +455,16 @@ export async function agentCommand(scenarioArg, options = {}) {
520
455
  goalMode,
521
456
  ccSessionId: cc.id,
522
457
  contextProfile,
523
- compactAt: contextProfile === "off" ? null : compactAt,
458
+ compactAt,
524
459
  });
525
460
  logLaunch({ shape, door, agent, conversationId, cellNote, envelope, goalMode, cli });
526
461
  if (expNotice)
527
462
  say(expNotice);
528
463
  say(goalAlignmentLine(goalAlignmentMode(envelope, goalMode)));
529
464
  sayContextProfile(contextProfile);
530
- sayCompaction(contextProfile, compactionIntercept, compactAt, launchWindow(win));
465
+ sayCompactAt(compactAt, launchWindow(win));
466
+ if (compactionIntercept)
467
+ say(COMPACTION_INTERCEPT_NOTE);
531
468
  // ── handoff MCP tool (MNE-7564) ── OFF by default: absent/false ⇒ handoffMcpConfigPath stays
532
469
  // undefined and buildCliArgs/spawnRcCli are byte-identical to a build without this flag. ON:
533
470
  // the config is written to a 0600 temp file and referenced by PATH only — never inlined into
@@ -543,22 +480,15 @@ export async function agentCommand(scenarioArg, options = {}) {
543
480
  // back out at request time. Remote Control (both shapes) strips every ANTHROPIC_* auth
544
481
  // var from the child's env, so `spawnRcCli` below instead sets a dedicated, non-ANTHROPIC
545
482
  // var (HANDOFF_RC_KEY_ENV_NAME) on that same child env — still nothing in this file.
546
- // The Mnemom key the gateway requires rides the same way: its `x-mnemom-api-key:` line
547
- // in ANTHROPIC_CUSTOM_HEADERS (terminal), or HANDOFF_RC_MNEMOM_KEY_ENV_NAME (RC).
548
- const terminal = shape === "terminal";
549
- const anthropicKeySource = terminal
483
+ const anthropicKeySource = shape === "terminal"
550
484
  ? { envName: "ANTHROPIC_CUSTOM_HEADERS" }
551
485
  : { envName: HANDOFF_RC_KEY_ENV_NAME };
552
- const mnemomKeySource = terminal
553
- ? { envName: "ANTHROPIC_CUSTOM_HEADERS" }
554
- : { envName: HANDOFF_RC_MNEMOM_KEY_ENV_NAME };
555
486
  handoffMcpConfigPath = writeHandoffMcpConfigFile(buildHandoffMcpConfigJson({
556
487
  // The SAME origin the session's own model traffic goes to — not resolveGatewayHost,
557
488
  // which would ignore a MNEMOM_AGENT_ANTHROPIC_GATEWAY override (a test-cell launch
558
489
  // must never send its handoff calls to prod, or vice versa).
559
490
  gatewayHost: resolveHandoffGatewayHost(door),
560
491
  anthropicKeySource,
561
- mnemomKeySource,
562
492
  agent,
563
493
  conversationId,
564
494
  sessionConfigB64,
@@ -759,9 +689,7 @@ export async function recordSessionLaunch(ctx) {
759
689
  cli: ctx.cli.name,
760
690
  contract: toLedgerContract(ctx.envelope?.contract ?? null),
761
691
  goal_mode: goalAlignmentMode(ctx.envelope, ctx.goalMode),
762
- ...(ctx.contextProfile !== null
763
- ? { context_profile: ctx.contextProfile, context_profile_v: 2 }
764
- : {}),
692
+ context_profile: ctx.contextProfile,
765
693
  compact_at: ctx.compactAt,
766
694
  cc_session_id: ctx.ccSessionId ?? null,
767
695
  ...(ctx.options.experimental?.length ? { experimental: [...ctx.options.experimental] } : {}),
@@ -778,55 +706,25 @@ export async function recordSessionLaunch(ctx) {
778
706
  // ───────────────────────────────────────────────────────────────────────────
779
707
  // Credential resolution (zero 1Password)
780
708
  // ───────────────────────────────────────────────────────────────────────────
781
- /** Per-call timeout of the launch's Mnemom-key check: a slow API must not hold up a launch. */
782
- export const MNEMOM_KEY_CHECK_TIMEOUT_MS = 2500;
783
- /**
784
- * Mint the launch's key: an ORG-owned key for `orgId` (the launch's org) with scopes exactly
785
- * ["gateway"], which any owner/admin/member may mint — so the gateway's agent-traffic org check
786
- * (agent org = key org) passes for the agent the launcher claims into that org. Never a personal
787
- * key: it has no org, and the gateway would refuse every governed turn with it. A refusal (403:
788
- * an API that predates member org keys, or a viewer/auditor role) is thrown as is; the launch
789
- * stops with guidance (lib/subscription-login.ts mintAndStore).
790
- */
791
- export async function mintLaunchMnemomKey(orgId) {
792
- const k = await createOrgApiKey(orgId, `mnemom-agent ${hostname()}`, ["gateway"]);
793
- return { key: k.key, keyId: k.key_id };
794
- }
795
- /**
796
- * The real I/O behind every launch's Mnemom key (lib/subscription-login.ts). `launchOrgId` is
797
- * the org the launch runs in (ensureSessionAndBalance's `orgId`: the active org, else the
798
- * caller's personal org); omitted or empty, it is the active org.
799
- */
800
- export function defaultMnemomKeyDeps(storePath = CODE_STORE, launchOrgId) {
801
- const activeOrgId = getActiveOrg()?.org_id || null;
709
+ /** The real I/O behind a subscription launch's Mnemom key (lib/subscription-login.ts). */
710
+ export function defaultMnemomKeyDeps(storePath = CODE_STORE) {
802
711
  return {
803
712
  env: process.env,
804
- activeOrgId,
805
- launchOrgId: launchOrgId || activeOrgId,
806
- readStored: (orgId) => readStoredMnemomKey(readStore(storePath), orgId),
807
- mint: mintLaunchMnemomKey,
808
- store: (orgId, key) => mergeStore(storePatchForMnemomKey(readStore(storePath), orgId, key), storePath),
809
- check: (key, keyId) => checkMnemomKey(key, keyId, {
810
- apiAccepts: (k) => apiAcceptsMnemomKey(k, MNEMOM_KEY_CHECK_TIMEOUT_MS),
811
- listOwnKeys: () => listOwnApiKeysForCheck(MNEMOM_KEY_CHECK_TIMEOUT_MS),
812
- }),
813
- // DELETE /v1/api-keys/:key_id revokes only keys the signed-in user created (org keys
814
- // included), so a replaced key minted by someone else is never touched.
815
- revoke: (keyId) => revokeApiKey(keyId, { timeoutMs: MNEMOM_KEY_CHECK_TIMEOUT_MS }),
713
+ readStored: () => {
714
+ try {
715
+ const data = JSON.parse(readFileSync(storePath, "utf8"));
716
+ const v = data[MNEMOM_KEY_STORE_FIELD];
717
+ return typeof v === "string" ? stripCrlf(v) : undefined;
718
+ }
719
+ catch {
720
+ return undefined;
721
+ }
722
+ },
723
+ mint: async () => (await createApiKey(`mnemom-agent ${hostname()}`, ["gateway"])).key,
724
+ store: (key) => mergeStore({ [MNEMOM_KEY_STORE_FIELD]: key }, storePath),
816
725
  say,
817
726
  };
818
727
  }
819
- /**
820
- * The Mnemom key already on this machine (MNEMOM_AGENT_MNEMOM_KEY, then ~/.mnemom/launcher.json),
821
- * never minted: `mnemom agent doctor` reports its source, the goal commands send it.
822
- */
823
- export function localLaunchMnemomKey(storePath = CODE_STORE) {
824
- return readLocalMnemomKey(defaultMnemomKeyDeps(storePath));
825
- }
826
- /** The launch's one-time check of that key (`mnemom agent doctor` reports it). Never throws. */
827
- export function verifyLocalMnemomKey(local) {
828
- return defaultMnemomKeyDeps().check(local.key, local.keyId);
829
- }
830
728
  /** The real machine behind lib/console-key.ts: the macOS keychain via `security`, no shell. */
831
729
  export function defaultConsoleKeyDeps(env) {
832
730
  return {
@@ -1102,8 +1000,6 @@ function launchTerminal(ctx) {
1102
1000
  if (ctx.anthropicKey) {
1103
1001
  childEnv = { ...process.env };
1104
1002
  delete childEnv.ANTHROPIC_API_KEY;
1105
- // The Mnemom key already rides in the headers; the child has no other use for it.
1106
- Reflect.deleteProperty(childEnv, MNEMOM_KEY_ENV);
1107
1003
  childEnv.ANTHROPIC_BASE_URL = ctx.door;
1108
1004
  childEnv.ANTHROPIC_AUTH_TOKEN = "mnemom-gateway";
1109
1005
  childEnv.ANTHROPIC_CUSTOM_HEADERS = headers;
@@ -1288,10 +1184,9 @@ function launchRemoteControl(ctx) {
1288
1184
  }
1289
1185
  };
1290
1186
  process.on("exit", onExit);
1291
- // Secrets on stdin — three lines (anthropic, mnemom, openai). Every launch sends the Mnemom
1292
- // key (the proxy adds it as x-mnemom-api-key on every gateway request); an api-key launch
1293
- // also sends the Anthropic key, a subscription launch leaves that line empty (the user's
1294
- // OAuth bearer is what pays). openai is always empty on this Anthropic-only door.
1187
+ // Secrets on stdin — three lines (anthropic, mnemom, openai). An api-key launch sends the
1188
+ // Anthropic key only; a subscription launch sends the Mnemom key only (the identity anchor —
1189
+ // the user's OAuth bearer is what pays). openai is always empty on this Anthropic-only door.
1295
1190
  // A proxy that exits before reading them turns this write into an EPIPE; that is
1296
1191
  // not a failure of its own — the exited-before-READY check below refuses the
1297
1192
  // launch. Any other stdin error fails closed like a spawn error.
@@ -1302,7 +1197,7 @@ function launchRemoteControl(ctx) {
1302
1197
  onExit();
1303
1198
  process.exit(1);
1304
1199
  });
1305
- proxy.stdin?.end(`${ctx.anthropicKey ?? ""}\n${ctx.mnemomKey}\n\n`);
1200
+ proxy.stdin?.end(`${ctx.anthropicKey ?? ""}\n${ctx.anthropicKey ? "" : (ctx.mnemomKey ?? "")}\n\n`);
1306
1201
  let stdoutBuf = "";
1307
1202
  const start = Date.now();
1308
1203
  const tryStart = () => {
@@ -1370,9 +1265,6 @@ function spawnRcCli(ctx) {
1370
1265
  "no_proxy",
1371
1266
  "HTTP_PROXY",
1372
1267
  "http_proxy",
1373
- // The Mnemom key rides to the gateway via the proxy (stdin) and, with handoff on, in
1374
- // HANDOFF_RC_MNEMOM_KEY_ENV_NAME below; the CLI itself has no use for the launch's env var.
1375
- MNEMOM_KEY_ENV,
1376
1268
  ]);
1377
1269
  const childEnv = {};
1378
1270
  for (const [k, v] of Object.entries(process.env)) {
@@ -1400,10 +1292,8 @@ function spawnRcCli(ctx) {
1400
1292
  // ride through here ungated), so this rides through untouched. Only set when the tool is
1401
1293
  // actually registered — never on a plain RC launch.
1402
1294
  // (Never on a subscription launch — handoff is turned off there, so there is no key to set.)
1403
- // The Mnemom key the gateway requires rides beside it in its own non-ANTHROPIC var.
1404
1295
  if (ctx.handoffMcpConfigPath && ctx.anthropicKey) {
1405
1296
  childEnv[HANDOFF_RC_KEY_ENV_NAME] = ctx.anthropicKey;
1406
- childEnv[HANDOFF_RC_MNEMOM_KEY_ENV_NAME] = ctx.mnemomKey;
1407
1297
  }
1408
1298
  let args;
1409
1299
  if (ctx.shape === "server") {
@@ -1676,7 +1566,7 @@ export function printPlan(ctx) {
1676
1566
  L.push(` agent: ${ctx.agent}`);
1677
1567
  L.push(` conversation: ${ctx.conversationId}`);
1678
1568
  L.push(` yolo: ${ctx.yolo ? "on (--dangerously-skip-permissions)" : "off"}`);
1679
- L.push(` context: ${ctx.contextProfile ?? "cell default"}`);
1569
+ L.push(` context: ${ctx.contextProfile}`);
1680
1570
  L.push(` compaction: ${describeCompactAt(ctx.compactAt)}`);
1681
1571
  const overlay = Object.entries(ctx.sessionConfig);
1682
1572
  if (overlay.length > 0) {
@@ -26,13 +26,5 @@ export interface WrapOptions {
26
26
  export declare function wrapCommand(options?: WrapOptions): Promise<void>;
27
27
  export declare function birthThroughGateway(gatewayUrl: string, provider: Provider, agentName: string, providerKey: string, opts?: {
28
28
  timeoutMs?: number;
29
- /**
30
- * The caller's Mnemom API key, sent as `x-mnemom-api-key` together with a no-op
31
- * `x-mnemom-session-config` (NOOP_SESSION_CONFIG_B64). The session config makes the call
32
- * Mnemom agent traffic, so the gateway verifies the key and births a NEW agent in the key's
33
- * org; the caller's claim step then records the claim in that same org. `mnemom agent`
34
- * always sends its launch key; `mnemom wrap` sends neither header (plain traffic, as before).
35
- */
36
- mnemomKey?: string;
37
29
  }): Promise<string>;
38
30
  export {};
@@ -20,9 +20,7 @@ import { mergeAgentConfig } from "../lib/agent-config.js";
20
20
  import { askSelect, askInput, isInteractive } from "../lib/prompt.js";
21
21
  import { getGatewayUrl } from "../lib/config.js";
22
22
  import { fmt } from "../lib/format.js";
23
- import { NOOP_SESSION_CONFIG_B64, SESSION_CONFIG_HEADER } from "../lib/agent.js";
24
23
  const AGENT_HEADER = "x-mnemom-agent";
25
- const MNEMOM_KEY_HEADER = "x-mnemom-api-key";
26
24
  const PROVIDERS = {
27
25
  anthropic: {
28
26
  label: "Anthropic",
@@ -210,10 +208,6 @@ export async function birthThroughGateway(gatewayUrl, provider, agentName, provi
210
208
  [prov.keyHeader]: authValue,
211
209
  [AGENT_HEADER]: agentName,
212
210
  };
213
- if (opts.mnemomKey) {
214
- headers[MNEMOM_KEY_HEADER] = opts.mnemomKey;
215
- headers[SESSION_CONFIG_HEADER] = NOOP_SESSION_CONFIG_B64;
216
- }
217
211
  if (provider === "anthropic") {
218
212
  headers["anthropic-version"] = "2023-06-01";
219
213
  }
@@ -241,24 +235,13 @@ export async function birthThroughGateway(gatewayUrl, provider, agentName, provi
241
235
  messages: [{ role: "user", content: "hello" }],
242
236
  });
243
237
  }
244
- const post = (h) => fetch(url, {
238
+ const response = await fetch(url, {
245
239
  method: "POST",
246
- headers: h,
240
+ headers,
247
241
  body,
248
242
  signal: opts.timeoutMs ? AbortSignal.timeout(opts.timeoutMs) : undefined,
249
243
  });
250
- let response = await post(headers);
251
- let agentId = response.headers.get(AGENT_HEADER);
252
- // An agent an earlier plain call already birthed sits unclaimed in the sandbox org, and the
253
- // gateway refuses agent traffic for it with 402 `agent_unclaimed` (and no agent id) — yet that
254
- // is exactly the agent `mnemom agent setup` must claim. Ask once more as plain traffic (no
255
- // session config): that serves it exactly as before and returns the id for the claim step.
256
- if (!agentId && SESSION_CONFIG_HEADER in headers && (await isAgentUnclaimedRefusal(response))) {
257
- const plain = { ...headers };
258
- Reflect.deleteProperty(plain, SESSION_CONFIG_HEADER);
259
- response = await post(plain);
260
- agentId = response.headers.get(AGENT_HEADER);
261
- }
244
+ const agentId = response.headers.get(AGENT_HEADER);
262
245
  if (!response.ok && !agentId) {
263
246
  const text = await response.text().catch(() => "");
264
247
  throw new MnemomApiError(response.status, `Gateway returned ${response.status}: ${text.slice(0, 300)}`);
@@ -268,22 +251,6 @@ export async function birthThroughGateway(gatewayUrl, provider, agentName, provi
268
251
  }
269
252
  return agentId;
270
253
  }
271
- /**
272
- * Is `response` the gateway's agent-traffic refusal for an agent still unclaimed in the sandbox
273
- * org — 402 with `error.details.reason === "agent_unclaimed"`? Reads a clone, so the caller can
274
- * still read the body. Anything unparseable is `false`.
275
- */
276
- async function isAgentUnclaimedRefusal(response) {
277
- if (response.status !== 402)
278
- return false;
279
- try {
280
- const body = (await response.clone().json());
281
- return body?.error?.details?.reason === "agent_unclaimed";
282
- }
283
- catch {
284
- return false;
285
- }
286
- }
287
254
  // ── Starter cards ─────────────────────────────────────────────────────────────
288
255
  function buildStarterAlignmentCard(agentId) {
289
256
  return {
package/dist/index.js CHANGED
@@ -175,7 +175,7 @@ const agentCmd = program
175
175
  .addOption(new Option("--slack", "EXPERIMENTAL (`mnemom experimental enable slack`): attach the Slack channel — messages addressed to this session arrive in it, and it can reply (see `mnemom agent slack`)").hideHelp(hiddenUnless("slack")))
176
176
  .option("--context", "Force context folding ON for this launch even when launcher.toml (context = false) or MNEMOM_AGENT_CONTEXT turns it off")
177
177
  .option("--no-context", "Disable context folding for this launch (folding is on by default; tells the gateway to skip it for this session)")
178
- .addOption(new Option("--context-profile <profile>", 'How the gateway folds this session\'s context. Unset (the default): nothing is sent and the gateway cell\'s own default decides. "balanced" keeps more of the earlier work verbatim and folds later — for long sessions that revisit earlier files and decisions. "aggressive" folds earlier and keeps a tighter working set — for cost-sensitive sessions and short, focused tasks. "adaptive" sizes the fold to the conversation: no fold below 200k tokens, aggressive from 200k to 1M, balanced above. "off" leaves compaction to the coding agent and never intercepts it (fold, compaction intercept and real-size usage all off; refused with --compaction-intercept or --compact-at <n>). Applies to this launch only (a resumed session keeps its own); to change the default for every launch: config key context_profile, env MNEMOM_AGENT_CONTEXT_PROFILE.').argParser((v) => {
178
+ .addOption(new Option("--context-profile <profile>", 'How the gateway folds this session\'s context. "balanced" (default) keeps more of the earlier work verbatim and folds later — for long sessions that revisit earlier files and decisions. "aggressive" folds earlier and keeps a tighter working set — for cost-sensitive sessions and short, focused tasks. "adaptive" sizes the fold to the conversation: no fold below 200k tokens, aggressive from 200k to 1M, balanced above. "off" leaves compaction to the coding agent. Also: config key context_profile, env MNEMOM_AGENT_CONTEXT_PROFILE.').argParser((v) => {
179
179
  // One validator for all three surfaces (flag, MNEMOM_AGENT_CONTEXT_PROFILE,
180
180
  // launcher.toml context_profile): trimmed, case-insensitive, same error wording.
181
181
  try {
@@ -14,7 +14,7 @@
14
14
  * `sessions.retention_days` (default 30) are dropped when the next one is
15
15
  * recorded. No count cap — a long retention keeps everything in the window.
16
16
  */
17
- import { type ContextProfile, type LaunchShape, type SealedContract } from "./agent.js";
17
+ import type { ContextProfile, LaunchShape, SealedContract } from "./agent.js";
18
18
  /** The ledger's file name under MNEMOM_DIR. */
19
19
  export declare const LEDGER_FILE = "sessions.jsonl";
20
20
  /** Days a launch stays in the ledger when `sessions.retention_days` is unset. */
@@ -77,17 +77,10 @@ export interface SessionRecord {
77
77
  cc_session_id?: string | null;
78
78
  /**
79
79
  * The context profile the launch ran under (`--context-profile` / `context_profile` /
80
- * `MNEMOM_AGENT_CONTEXT_PROFILE`, or the resumed session's own). Absent when none was chosen
81
- * (the cell default) and on records written before profiles existed (still `v: 1`).
80
+ * `MNEMOM_AGENT_CONTEXT_PROFILE`, or `balanced` by default). Optional: records written
81
+ * before profiles existed omit it (still `v: 1`).
82
82
  */
83
83
  context_profile?: ContextProfile;
84
- /**
85
- * `2` when `context_profile` was written with complete profiles (`balanced` sends size policy
86
- * off, `aggressive` adds it). Absent on older records, where `balanced` was also written for a
87
- * launch that chose nothing and sent no fold key — so a resume reads an unmarked `balanced` as
88
- * the cell default, which is what that session actually ran under.
89
- */
90
- context_profile_v?: 2;
91
84
  /**
92
85
  * The resolved gateway compaction target (`--compact-at` / `compact_at` /
93
86
  * `MNEMOM_COMPACT_AT`): `null` means no target (Claude Code's own 1M window; the default,
@@ -175,15 +168,3 @@ export declare function findResumeCandidate(records: readonly SessionRecord[], w
175
168
  gatewayHost: string;
176
169
  resumeId?: string | null;
177
170
  }): ResumeCandidate | null;
178
- /**
179
- * The context profile the Claude Code session `ccSessionId` ran under — the newest row with the
180
- * same org and gateway host that pinned that session — so a launch that resumes it
181
- * (`--resume <id>`) keeps that session's own profile. `null` when no row matches, the row chose
182
- * no profile (the cell default), it is an older row's `balanced` (see `context_profile_v`), or its
183
- * value is not a known profile (the file is hand-editable).
184
- */
185
- export declare function resumedContextProfile(records: readonly SessionRecord[], want: {
186
- ccSessionId: string | null;
187
- orgId: string;
188
- gatewayHost: string;
189
- }): ContextProfile | null;
@@ -16,7 +16,6 @@
16
16
  */
17
17
  import { appendFileSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
18
18
  import { join } from "node:path";
19
- import { CONTEXT_PROFILES, } from "./agent.js";
20
19
  /** The ledger's file name under MNEMOM_DIR. */
21
20
  export const LEDGER_FILE = "sessions.jsonl";
22
21
  /** Days a launch stays in the ledger when `sessions.retention_days` is unset. */
@@ -198,25 +197,3 @@ export function findResumeCandidate(records, want) {
198
197
  }
199
198
  return latest ? { record: latest, source: "session_name", byCcSession: false } : null;
200
199
  }
201
- /**
202
- * The context profile the Claude Code session `ccSessionId` ran under — the newest row with the
203
- * same org and gateway host that pinned that session — so a launch that resumes it
204
- * (`--resume <id>`) keeps that session's own profile. `null` when no row matches, the row chose
205
- * no profile (the cell default), it is an older row's `balanced` (see `context_profile_v`), or its
206
- * value is not a known profile (the file is hand-editable).
207
- */
208
- export function resumedContextProfile(records, want) {
209
- const id = want.ccSessionId?.trim();
210
- if (!id || !want.orgId)
211
- return null;
212
- for (let i = records.length - 1; i >= 0; i--) {
213
- const r = records[i];
214
- if (r.cc_session_id !== id || r.org_id !== want.orgId || r.gateway_host !== want.gatewayHost)
215
- continue;
216
- const p = r.context_profile;
217
- if (p === "balanced" && r.context_profile_v !== 2)
218
- return null; // an older row: sent nothing
219
- return p !== undefined && CONTEXT_PROFILES.includes(p) ? p : null;
220
- }
221
- return null;
222
- }