@mnemom/mnemom 0.17.29 → 0.17.31-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.
@@ -88,6 +88,12 @@ export interface AgentOptions {
88
88
  * the #2216 intercept/real-size keys for this session only; unset/null sends nothing new.
89
89
  */
90
90
  compactAt?: number | null;
91
+ /**
92
+ * `--allow-autocompact-off` (or MNEMOM_AGENT_ALLOW_AUTOCOMPACT_OFF=1): keep Claude Code's
93
+ * auto-compaction off when the user turned it off. Default: the launch turns it back on for
94
+ * this session and says so (lib/autocompact-guard.ts).
95
+ */
96
+ allowAutocompactOff?: boolean;
91
97
  /** context-hint seed default: commander `--no-context-hint` sets this false. */
92
98
  contextHint?: boolean;
93
99
  /** Attach the Slack channel server (`--slack`; lib/slack-launch.ts). */
@@ -32,6 +32,7 @@ import { tmpdir, homedir, hostname } from "node:os";
32
32
  import { basename, join, dirname } from "node:path";
33
33
  import { fileURLToPath } from "node:url";
34
34
  import { MEASURED_ENV, statuslineLaunch } from "../lib/statusline.js";
35
+ import { ALLOW_AUTOCOMPACT_OFF_ENV, dropAutocompactOffEnv, hasOwnSettingsFlag, mergedSettingsArgs, planAutocompactGuard, } from "../lib/autocompact-guard.js";
35
36
  import { getApiUrl, getGatewayUrl, MNEMOM_DIR } from "../lib/config.js";
36
37
  import { createApiKey } from "../lib/api.js";
37
38
  import { MNEMOM_KEY_STORE_FIELD, resolveAgentAuth, sayRetiredConsoleNotice, readMnemomKey, resolveSubscriptionMnemomKey, subscriptionChildEnv, subscriptionShape, } from "../lib/subscription-login.js";
@@ -1266,18 +1267,20 @@ function launchTerminal(ctx) {
1266
1267
  // decision alone: a value inherited from an outer `mnemom agent` session never rides along.
1267
1268
  Reflect.deleteProperty(childEnv, MEASURED_ENV);
1268
1269
  if (ctx.cli.name === "claude") {
1270
+ const passthrough = ctx.options.passthrough ?? [];
1269
1271
  const sl = statuslineLaunch({
1270
- passthrough: ctx.options.passthrough ?? [],
1272
+ passthrough,
1271
1273
  env: childEnv,
1272
1274
  cwd: process.cwd(),
1273
1275
  node: process.execPath,
1274
1276
  bin: join(dirname(fileURLToPath(import.meta.url)), "..", "lib", "statusline-bin.js"),
1275
1277
  measured: ctx.usageSavings,
1276
1278
  });
1277
- if (sl) {
1278
- args.push(...sl.args);
1279
+ if (sl)
1279
1280
  Object.assign(childEnv, sl.env);
1280
- }
1281
+ // Autocompact guard (lib/autocompact-guard.ts): one --settings carries both layers.
1282
+ const guard = applyAutocompactGuard(childEnv, ctx.options, !hasOwnSettingsFlag(passthrough));
1283
+ args.push(...mergedSettingsArgs(guard, sl?.settings ?? null));
1281
1284
  }
1282
1285
  if (ctx.contextHint) {
1283
1286
  seedContextHintFlag(childEnv);
@@ -1547,6 +1550,10 @@ function spawnRcCli(ctx) {
1547
1550
  childEnv.https_proxy = `http://127.0.0.1:${ctx.port}`;
1548
1551
  childEnv.NODE_EXTRA_CA_CERTS = ctx.ca;
1549
1552
  childEnv[SESSION_NAME_ENV] = rcName;
1553
+ // Autocompact guard (lib/autocompact-guard.ts). The interactive shape gets the --settings layer
1554
+ // below; the headless server only drops the env switches (its sessions are spawned by the server).
1555
+ const rcGuard = ctx.cli.name === "claude" &&
1556
+ applyAutocompactGuard(childEnv, ctx.options, ctx.shape !== "server" && !hasOwnSettingsFlag(passthrough));
1550
1557
  // Same exact compaction signal as the terminal shape. Not an ANTHROPIC_* var, so the RC gate
1551
1558
  // ignores it (like HTTPS_PROXY above); the proxy forwards every non-hop request header, so the
1552
1559
  // x-claude-code-* hints reach the gateway. The user's own autocompact env is not on the strip
@@ -1611,6 +1618,8 @@ function spawnRcCli(ctx) {
1611
1618
  pluginDir: ctx.plugin?.dir,
1612
1619
  });
1613
1620
  args = ["--remote-control", rcName, "--name", rcName, ...flagArgs];
1621
+ if (rcGuard)
1622
+ args.push(...mergedSettingsArgs(true, null));
1614
1623
  if (ctx.options.slack)
1615
1624
  args.push(...slackChannelArgs(rcName));
1616
1625
  say(`mnemom agent: remote-control: interactive session in THIS terminal with Remote Control on (session name '${rcName}'); it also appears under Remote Control at claude.ai/code and in the Claude app. Exit the CLI to stop everything.`);
@@ -1804,6 +1813,25 @@ export function pickCcSessionId(passthrough, newId = randomUUID) {
1804
1813
  // ───────────────────────────────────────────────────────────────────────────
1805
1814
  // Context-hint seed (opt-out) — best-effort, never throws
1806
1815
  // ───────────────────────────────────────────────────────────────────────────
1816
+ /**
1817
+ * Warn when Claude Code's auto-compaction is off for this launch and, unless the user opted out,
1818
+ * drop its env switches from `childEnv`. Returns true when the caller should add the
1819
+ * `--settings` layer that turns it back on (only when `canPassSettings`).
1820
+ */
1821
+ function applyAutocompactGuard(childEnv, options, canPassSettings) {
1822
+ const plan = planAutocompactGuard({
1823
+ env: childEnv,
1824
+ cwd: process.cwd(),
1825
+ allowOff: options.allowAutocompactOff === true || process.env[ALLOW_AUTOCOMPACT_OFF_ENV] === "1",
1826
+ canPassSettings,
1827
+ });
1828
+ if (plan.warning)
1829
+ say(plan.warning);
1830
+ if (!plan.override)
1831
+ return false;
1832
+ dropAutocompactOffEnv(childEnv);
1833
+ return canPassSettings;
1834
+ }
1807
1835
  /**
1808
1836
  * Set cachedGrowthBookFeatures.tengu_hazel_osprey = true in the local Claude
1809
1837
  * Code config (${CLAUDE_CONFIG_DIR:-$HOME}/.claude.json) so the client offers the
package/dist/index.js CHANGED
@@ -180,6 +180,7 @@ const agentCmd = program
180
180
  "xhigh",
181
181
  "max",
182
182
  ]))
183
+ .option("--allow-autocompact-off", 'Keep Claude Code\'s auto-compact off when you turned it off (DISABLE_AUTO_COMPACT, DISABLE_COMPACT or autoCompactEnabled: false). By default the launch turns it back on for this session and says so: with it off, a long session ends in "Prompt is too long" at the context limit. Also: env MNEMOM_AGENT_ALLOW_AUTOCOMPACT_OFF=1')
183
184
  .option("--trim-mcp", "Launch the CLI with no MCP servers (--strict-mcp-config + empty --mcp-config)")
184
185
  .option("--invite <token>", "Redeem a Mnemom Agent invitation (pinv_…): unlocks Mnemom Agent for you + credits your starter µ; launches too when a session name is given")
185
186
  .option("--setup", "Re-check the served agent and refresh its default cards before launch")
@@ -379,6 +380,7 @@ const agentCmd = program
379
380
  model: opts.model ?? cfg.model,
380
381
  effort: opts.effort ?? cfg.effort,
381
382
  trimMcp: opts.trimMcp,
383
+ allowAutocompactOff: opts.allowAutocompactOff,
382
384
  yolo: opts.yolo,
383
385
  contextHint: opts.contextHint,
384
386
  plugin: opts.plugin,
@@ -0,0 +1,67 @@
1
+ /** Opt-out env: "1" keeps the user's autocompact-off setting as it is (warning only). */
2
+ export declare const ALLOW_AUTOCOMPACT_OFF_ENV = "MNEMOM_AGENT_ALLOW_AUTOCOMPACT_OFF";
3
+ /** Claude Code's env switches that turn auto-compaction off (DISABLE_COMPACT also /compact). */
4
+ export declare const AUTOCOMPACT_OFF_ENV: readonly ["DISABLE_AUTO_COMPACT", "DISABLE_COMPACT"];
5
+ /** The `--settings` layer that turns it back on. */
6
+ export declare const AUTOCOMPACT_ON_SETTINGS: {
7
+ readonly autoCompactEnabled: true;
8
+ readonly env: {
9
+ readonly DISABLE_AUTO_COMPACT: "0";
10
+ readonly DISABLE_COMPACT: "0";
11
+ };
12
+ };
13
+ /** One switch that turns autocompact off, named the way the user would find it. */
14
+ export interface AutocompactOffSwitch {
15
+ /** e.g. `DISABLE_AUTO_COMPACT=1` or `autoCompactEnabled: false`. */
16
+ setting: string;
17
+ /** e.g. `your environment` or a file path. */
18
+ where: string;
19
+ }
20
+ /**
21
+ * Claude Code's truthiness for these env values. A settings file's `env` block can hold a JSON
22
+ * boolean: `true` turns it off and `false` clears an off process env (MEASURED, 2.1.301).
23
+ */
24
+ export declare function envOn(v: unknown): boolean;
25
+ /**
26
+ * Every switch that leaves Claude Code's autocompact off for a launch from `cwd`, in the order
27
+ * Claude Code layers them (later wins): the global config, then the user, project and local
28
+ * settings files for `autoCompactEnabled`; the process env, then each settings file's `env` block
29
+ * for the two env switches. Only the switches still in force are returned (a later layer turning
30
+ * it back on clears an earlier one). Managed (policy) settings are not read — the launcher could
31
+ * not override them anyway. Never throws.
32
+ */
33
+ export declare function detectAutocompactOff(o: {
34
+ env: NodeJS.ProcessEnv;
35
+ cwd: string;
36
+ home?: string;
37
+ }): AutocompactOffSwitch[];
38
+ /** True when the user passed their own `--settings` (a second one would replace ours whole). */
39
+ export declare function hasOwnSettingsFlag(passthrough: readonly string[]): boolean;
40
+ export interface AutocompactGuardPlan {
41
+ /** What turned it off; empty ⇒ nothing to do, no warning. */
42
+ switches: AutocompactOffSwitch[];
43
+ /** True ⇒ the launcher turns it back on for this child (env drop + settings layer). */
44
+ override: boolean;
45
+ /** The one warning line to print, or null when nothing is off. */
46
+ warning: string | null;
47
+ }
48
+ /**
49
+ * Decide what the launch does about autocompact. `canPassSettings` is false where the launcher
50
+ * cannot add a `--settings` layer (the user's own `--settings`, the headless RC server); there the
51
+ * env switches are still dropped, but a settings-file switch can only be warned about.
52
+ */
53
+ export declare function planAutocompactGuard(o: {
54
+ env: NodeJS.ProcessEnv;
55
+ cwd: string;
56
+ home?: string;
57
+ allowOff: boolean;
58
+ canPassSettings: boolean;
59
+ }): AutocompactGuardPlan;
60
+ /** Drop the env switches from a child env. Mutates and returns it. */
61
+ export declare function dropAutocompactOffEnv(childEnv: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
62
+ /**
63
+ * The child's `--settings` args: our layer merged with the status-bar layer (`extra`), since
64
+ * Claude Code keeps only the LAST `--settings` it is given (MEASURED, 2.1.301) — two would make
65
+ * one of them vanish. Empty when there is nothing to pass.
66
+ */
67
+ export declare function mergedSettingsArgs(guard: boolean, extra: Record<string, unknown> | null): string[];
@@ -0,0 +1,157 @@
1
+ /**
2
+ * Autocompact guard — Claude Code's own auto-compaction switched off ends a long session in a
3
+ * lockout: once the context reaches the client's hard block (967k on a 1M window), every turn
4
+ * fails locally with "Prompt is too long", nothing reaches the gateway, and only a manual
5
+ * /compact gets the session going again. Reproduced 2026-10-17 through the launcher on the test
6
+ * cell (Claude Code 2.1.301): with autocompact on, every crossing compacts (the gateway intercepts
7
+ * it); with DISABLE_AUTO_COMPACT=1 the session locks at 966,753 and the debug file shows no
8
+ * `autocompact:` line at all.
9
+ *
10
+ * At launch the launcher finds every switch that turns it off and, unless the user opts out
11
+ * (`--allow-autocompact-off` / MNEMOM_AGENT_ALLOW_AUTOCOMPACT_OFF=1), turns it back on for the
12
+ * child only: the env switches are dropped from the child env and a `--settings` layer sets
13
+ * `autoCompactEnabled: true` with both env switches `"0"`. Flag settings outrank every settings
14
+ * file and the global config, and their `env` block outranks the process env (MEASURED against
15
+ * 2.1.301 for each source below). Nothing on disk is changed.
16
+ *
17
+ * Claude Code's gate (2.1.301): `zp(){ if (DISABLE_COMPACT || DISABLE_AUTO_COMPACT) return false;
18
+ * return settings("autoCompactEnabled", true) }`; the env values count as on when they are
19
+ * 1/true/yes/on (trimmed, any case).
20
+ */
21
+ import { existsSync, readFileSync } from "node:fs";
22
+ import { homedir } from "node:os";
23
+ import { join } from "node:path";
24
+ /** Opt-out env: "1" keeps the user's autocompact-off setting as it is (warning only). */
25
+ export const ALLOW_AUTOCOMPACT_OFF_ENV = "MNEMOM_AGENT_ALLOW_AUTOCOMPACT_OFF";
26
+ /** Claude Code's env switches that turn auto-compaction off (DISABLE_COMPACT also /compact). */
27
+ export const AUTOCOMPACT_OFF_ENV = ["DISABLE_AUTO_COMPACT", "DISABLE_COMPACT"];
28
+ /** The `--settings` layer that turns it back on. */
29
+ export const AUTOCOMPACT_ON_SETTINGS = {
30
+ autoCompactEnabled: true,
31
+ env: { DISABLE_AUTO_COMPACT: "0", DISABLE_COMPACT: "0" },
32
+ };
33
+ /**
34
+ * Claude Code's truthiness for these env values. A settings file's `env` block can hold a JSON
35
+ * boolean: `true` turns it off and `false` clears an off process env (MEASURED, 2.1.301).
36
+ */
37
+ export function envOn(v) {
38
+ if (v === undefined || v === null)
39
+ return false;
40
+ return ["1", "true", "yes", "on"].includes(String(v).trim().toLowerCase());
41
+ }
42
+ function readJsonObject(file) {
43
+ try {
44
+ if (!existsSync(file))
45
+ return null;
46
+ const d = JSON.parse(readFileSync(file, "utf8"));
47
+ return d && typeof d === "object" && !Array.isArray(d) ? d : null;
48
+ }
49
+ catch {
50
+ return null;
51
+ }
52
+ }
53
+ /**
54
+ * Every switch that leaves Claude Code's autocompact off for a launch from `cwd`, in the order
55
+ * Claude Code layers them (later wins): the global config, then the user, project and local
56
+ * settings files for `autoCompactEnabled`; the process env, then each settings file's `env` block
57
+ * for the two env switches. Only the switches still in force are returned (a later layer turning
58
+ * it back on clears an earlier one). Managed (policy) settings are not read — the launcher could
59
+ * not override them anyway. Never throws.
60
+ */
61
+ export function detectAutocompactOff(o) {
62
+ const home = o.home ?? homedir();
63
+ const configDir = o.env.CLAUDE_CONFIG_DIR || join(home, ".claude");
64
+ const globalFile = join(o.env.CLAUDE_CONFIG_DIR || home, ".claude.json");
65
+ const settingsFiles = [
66
+ join(configDir, "settings.json"),
67
+ join(o.cwd, ".claude", "settings.json"),
68
+ join(o.cwd, ".claude", "settings.local.json"),
69
+ ];
70
+ // autoCompactEnabled: the global config, then each settings file (a settings value wins).
71
+ let enabled = null;
72
+ const global = readJsonObject(globalFile);
73
+ if (typeof global?.autoCompactEnabled === "boolean") {
74
+ enabled = { value: global.autoCompactEnabled, where: globalFile };
75
+ }
76
+ const settings = settingsFiles.map((f) => ({ file: f, data: readJsonObject(f) }));
77
+ for (const s of settings) {
78
+ if (typeof s.data?.autoCompactEnabled === "boolean") {
79
+ enabled = { value: s.data.autoCompactEnabled, where: s.file };
80
+ }
81
+ }
82
+ const out = [];
83
+ if (enabled && !enabled.value) {
84
+ out.push({ setting: "autoCompactEnabled: false", where: enabled.where });
85
+ }
86
+ // The env switches: the process env, then each settings file's env block (last one wins).
87
+ for (const key of AUTOCOMPACT_OFF_ENV) {
88
+ let last = o.env[key] !== undefined ? { value: o.env[key], where: "your environment" } : null;
89
+ for (const s of settings) {
90
+ const env = s.data?.env;
91
+ if (env && typeof env === "object" && !Array.isArray(env) && key in env) {
92
+ last = { value: env[key], where: `${s.file} (env)` };
93
+ }
94
+ }
95
+ if (last && envOn(last.value)) {
96
+ out.push({ setting: `${key}=${String(last.value).trim()}`, where: last.where });
97
+ }
98
+ }
99
+ return out;
100
+ }
101
+ /** True when the user passed their own `--settings` (a second one would replace ours whole). */
102
+ export function hasOwnSettingsFlag(passthrough) {
103
+ return passthrough.some((a) => a === "--settings" || a.startsWith("--settings="));
104
+ }
105
+ /**
106
+ * Decide what the launch does about autocompact. `canPassSettings` is false where the launcher
107
+ * cannot add a `--settings` layer (the user's own `--settings`, the headless RC server); there the
108
+ * env switches are still dropped, but a settings-file switch can only be warned about.
109
+ */
110
+ export function planAutocompactGuard(o) {
111
+ const switches = detectAutocompactOff(o);
112
+ if (switches.length === 0)
113
+ return { switches, override: false, warning: null };
114
+ const named = switches.map((s) => `${s.setting} in ${s.where}`).join("; ");
115
+ const lead = `mnemom agent: Claude Code auto-compact is off (${named}). On a long session that ends in ` +
116
+ `"Prompt is too long" at the context limit, with every turn refused until you run /compact.`;
117
+ if (o.allowOff) {
118
+ return {
119
+ switches,
120
+ override: false,
121
+ warning: `${lead} Leaving it off (--allow-autocompact-off).`,
122
+ };
123
+ }
124
+ const fromFile = switches.some((s) => !s.where.startsWith("your environment"));
125
+ if (fromFile && !o.canPassSettings) {
126
+ return {
127
+ switches,
128
+ override: true,
129
+ warning: `${lead} mnemom could not turn it back on for this launch (your own --settings is in use, ` +
130
+ `or this is a headless server): set "autoCompactEnabled": true there, or turn Auto-compact on in /config.`,
131
+ };
132
+ }
133
+ return {
134
+ switches,
135
+ override: true,
136
+ warning: `${lead} Turning it back on for this session only (pass --allow-autocompact-off to keep it off).`,
137
+ };
138
+ }
139
+ /** Drop the env switches from a child env. Mutates and returns it. */
140
+ export function dropAutocompactOffEnv(childEnv) {
141
+ for (const key of AUTOCOMPACT_OFF_ENV)
142
+ Reflect.deleteProperty(childEnv, key);
143
+ return childEnv;
144
+ }
145
+ /**
146
+ * The child's `--settings` args: our layer merged with the status-bar layer (`extra`), since
147
+ * Claude Code keeps only the LAST `--settings` it is given (MEASURED, 2.1.301) — two would make
148
+ * one of them vanish. Empty when there is nothing to pass.
149
+ */
150
+ export function mergedSettingsArgs(guard, extra) {
151
+ if (!guard && !extra)
152
+ return [];
153
+ return [
154
+ "--settings",
155
+ JSON.stringify({ ...(extra ?? {}), ...(guard ? AUTOCOMPACT_ON_SETTINGS : {}) }),
156
+ ];
157
+ }
@@ -128,4 +128,5 @@ export declare function statuslineLaunch(o: {
128
128
  }): {
129
129
  args: string[];
130
130
  env: Record<string, string>;
131
+ settings: Record<string, unknown>;
131
132
  } | null;
@@ -339,5 +339,7 @@ export function statuslineLaunch(o) {
339
339
  const env = user ? { [INNER_ENV]: user.command } : {};
340
340
  if (o.measured)
341
341
  env[MEASURED_ENV] = "1";
342
- return { args: ["--settings", JSON.stringify({ statusLine })], env };
342
+ // `settings` is the same layer as `args`, for a caller that merges it with its own (Claude Code
343
+ // keeps only the last --settings it is given).
344
+ return { args: ["--settings", JSON.stringify({ statusLine })], env, settings: { statusLine } };
343
345
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mnemom/mnemom",
3
- "version": "0.17.29",
3
+ "version": "0.17.31-next.0",
4
4
  "description": "Transparent AI agent tracing",
5
5
  "type": "module",
6
6
  "bin": {