@mnemom/mnemom 0.17.34-next.1 → 0.17.34-next.2

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.
Files changed (195) hide show
  1. package/dist/base-url-proxy.mjs +18 -4
  2. package/dist/commands/agent-config.js +5 -1
  3. package/dist/commands/agent-doctor.d.ts +9 -0
  4. package/dist/commands/agent-doctor.js +4 -0
  5. package/dist/commands/agent-session.d.ts +1 -2
  6. package/dist/commands/agent-session.js +1 -2
  7. package/dist/commands/agent-setup.d.ts +6 -0
  8. package/dist/commands/agent-setup.js +12 -1
  9. package/dist/commands/agent.d.ts +11 -0
  10. package/dist/commands/agent.js +145 -46
  11. package/dist/commands/durable-doctor.d.ts +50 -0
  12. package/dist/commands/durable-doctor.js +186 -0
  13. package/dist/commands/durable-lock.d.ts +254 -0
  14. package/dist/commands/durable-lock.js +595 -0
  15. package/dist/commands/durable-sessions.d.ts +87 -0
  16. package/dist/commands/durable-sessions.js +303 -0
  17. package/dist/commands/durable-verbs.d.ts +116 -0
  18. package/dist/commands/durable-verbs.js +393 -0
  19. package/dist/durable/flock.d.ts +26 -0
  20. package/dist/durable/flock.js +80 -0
  21. package/dist/durable/launch-spec.d.ts +7 -1
  22. package/dist/durable/launch-spec.js +10 -3
  23. package/dist/durable/seat-proxy-entry.d.ts +11 -0
  24. package/dist/durable/seat-proxy-entry.js +25 -0
  25. package/dist/durable/seat-proxy.d.ts +107 -0
  26. package/dist/durable/seat-proxy.js +599 -0
  27. package/dist/durable/seat-socket.d.ts +46 -0
  28. package/dist/durable/seat-socket.js +179 -0
  29. package/dist/durable/seat-spec-validator.d.ts +28 -0
  30. package/dist/durable/seat-spec-validator.js +59 -0
  31. package/dist/durable/test-mode.mjs +81 -0
  32. package/dist/durable/turn-chain.d.ts +55 -0
  33. package/dist/durable/turn-chain.js +178 -0
  34. package/dist/host/auto-continue.d.ts +67 -0
  35. package/dist/host/auto-continue.js +118 -0
  36. package/dist/host/bootid.d.ts +13 -0
  37. package/dist/host/bootid.js +41 -0
  38. package/dist/host/client.d.ts +123 -0
  39. package/dist/host/client.js +171 -0
  40. package/dist/host/commands.d.ts +2 -0
  41. package/dist/host/commands.js +122 -0
  42. package/dist/host/control.d.ts +33 -0
  43. package/dist/host/control.js +144 -0
  44. package/dist/host/daemon.d.ts +217 -0
  45. package/dist/host/daemon.js +606 -0
  46. package/dist/host/gate.d.ts +46 -0
  47. package/dist/host/gate.js +82 -0
  48. package/dist/host/grant.d.ts +24 -0
  49. package/dist/host/grant.js +24 -0
  50. package/dist/host/instance.d.ts +35 -0
  51. package/dist/host/instance.js +62 -0
  52. package/dist/host/integrity.d.ts +35 -0
  53. package/dist/host/integrity.js +115 -0
  54. package/dist/host/launchd.d.ts +16 -0
  55. package/dist/host/launchd.js +45 -0
  56. package/dist/host/lock.d.ts +20 -0
  57. package/dist/host/lock.js +63 -0
  58. package/dist/host/owner.d.ts +55 -0
  59. package/dist/host/owner.js +121 -0
  60. package/dist/host/plist.d.ts +16 -0
  61. package/dist/host/plist.js +85 -0
  62. package/dist/host/procs.d.ts +27 -0
  63. package/dist/host/procs.js +105 -0
  64. package/dist/host/run.d.ts +66 -0
  65. package/dist/host/run.js +314 -0
  66. package/dist/host/runtime.d.ts +8 -0
  67. package/dist/host/runtime.js +22 -0
  68. package/dist/host/s1-names.d.ts +22 -0
  69. package/dist/host/s1-names.js +21 -0
  70. package/dist/host/seat-credential.d.ts +27 -0
  71. package/dist/host/seat-credential.js +41 -0
  72. package/dist/host/seat-launch-main.d.ts +1 -0
  73. package/dist/host/seat-launch-main.js +59 -0
  74. package/dist/host/seat-launch.d.ts +116 -0
  75. package/dist/host/seat-launch.js +302 -0
  76. package/dist/host/seat.d.ts +48 -0
  77. package/dist/host/seat.js +149 -0
  78. package/dist/host/state.d.ts +33 -0
  79. package/dist/host/state.js +117 -0
  80. package/dist/host/status.d.ts +101 -0
  81. package/dist/host/status.js +307 -0
  82. package/dist/host/tmux.d.ts +56 -0
  83. package/dist/host/tmux.js +182 -0
  84. package/dist/host/transcript.d.ts +30 -0
  85. package/dist/host/transcript.js +130 -0
  86. package/dist/host/verbs.d.ts +50 -0
  87. package/dist/host/verbs.js +271 -0
  88. package/dist/index.d.ts +8 -6
  89. package/dist/index.js +197 -35
  90. package/dist/lib/agent.d.ts +8 -26
  91. package/dist/lib/agent.js +11 -26
  92. package/dist/lib/claude-plugin.d.ts +11 -2
  93. package/dist/lib/claude-plugin.js +10 -1
  94. package/dist/lib/durable/api.d.ts +136 -0
  95. package/dist/lib/durable/api.js +220 -0
  96. package/dist/lib/durable/codes.d.ts +8 -0
  97. package/dist/lib/durable/codes.js +2 -0
  98. package/dist/lib/durable/first-turn.d.ts +50 -0
  99. package/dist/lib/durable/first-turn.js +102 -0
  100. package/dist/lib/durable/gate.d.ts +48 -0
  101. package/dist/lib/durable/gate.js +122 -0
  102. package/dist/lib/durable/handover.d.ts +101 -0
  103. package/dist/lib/durable/handover.js +175 -0
  104. package/dist/lib/durable/launch.d.ts +182 -0
  105. package/dist/lib/durable/launch.js +443 -0
  106. package/dist/lib/durable/launching.d.ts +15 -0
  107. package/dist/lib/durable/launching.js +70 -0
  108. package/dist/lib/durable/live.d.ts +194 -0
  109. package/dist/lib/durable/live.js +635 -0
  110. package/dist/lib/durable/refusal.d.ts +57 -0
  111. package/dist/lib/durable/refusal.js +78 -0
  112. package/dist/lib/durable/view.d.ts +83 -0
  113. package/dist/lib/durable/view.js +244 -0
  114. package/dist/lib/experimental.d.ts +0 -9
  115. package/dist/lib/experimental.js +18 -17
  116. package/dist/lib/launcher-config.d.ts +7 -0
  117. package/dist/lib/launcher-config.js +21 -0
  118. package/dist/lib/scan/report-email.d.ts +1 -1
  119. package/dist/lib/scan/report-email.js +9 -1
  120. package/dist/lib/subscription-login.d.ts +15 -0
  121. package/dist/lib/subscription-login.js +28 -1
  122. package/dist/vendor/durable-contract/index.js +240 -18
  123. package/dist/vendor/shared/durable-contract/admission.d.ts +240 -0
  124. package/dist/vendor/shared/durable-contract/admission.js +533 -0
  125. package/dist/vendor/shared/durable-contract/arm.d.ts +51 -0
  126. package/dist/vendor/shared/durable-contract/arm.js +133 -0
  127. package/dist/vendor/shared/durable-contract/canonical.d.ts +29 -0
  128. package/dist/vendor/shared/durable-contract/canonical.js +98 -0
  129. package/dist/vendor/shared/durable-contract/cell-id.d.ts +22 -0
  130. package/dist/vendor/shared/durable-contract/cell-id.js +38 -0
  131. package/dist/vendor/shared/durable-contract/cli-output.d.ts +342 -0
  132. package/dist/vendor/shared/durable-contract/cli-output.js +201 -0
  133. package/dist/vendor/shared/durable-contract/cli-surface.d.ts +119 -0
  134. package/dist/vendor/shared/durable-contract/cli-surface.js +270 -0
  135. package/dist/vendor/shared/durable-contract/codes.d.ts +537 -0
  136. package/dist/vendor/shared/durable-contract/codes.js +475 -0
  137. package/dist/vendor/shared/durable-contract/constants.d.ts +153 -0
  138. package/dist/vendor/shared/durable-contract/constants.js +179 -0
  139. package/dist/vendor/shared/durable-contract/doors.d.ts +119 -0
  140. package/dist/vendor/shared/durable-contract/doors.js +249 -0
  141. package/dist/vendor/shared/durable-contract/envelope.d.ts +103 -0
  142. package/dist/vendor/shared/durable-contract/envelope.js +317 -0
  143. package/dist/vendor/shared/durable-contract/events.d.ts +147 -0
  144. package/dist/vendor/shared/durable-contract/events.js +630 -0
  145. package/dist/vendor/shared/durable-contract/executor-state.d.ts +41 -0
  146. package/dist/vendor/shared/durable-contract/executor-state.js +32 -0
  147. package/dist/vendor/shared/durable-contract/fixlines.d.ts +318 -0
  148. package/dist/vendor/shared/durable-contract/fixlines.js +483 -0
  149. package/dist/vendor/shared/durable-contract/freeze-record.d.ts +787 -0
  150. package/dist/vendor/shared/durable-contract/freeze-record.js +361 -0
  151. package/dist/vendor/shared/durable-contract/index.d.ts +36 -0
  152. package/dist/vendor/shared/durable-contract/index.js +37 -0
  153. package/dist/vendor/shared/durable-contract/journal-approvals.d.ts +29 -0
  154. package/dist/vendor/shared/durable-contract/journal-approvals.js +60 -0
  155. package/dist/vendor/shared/durable-contract/keys.d.ts +24 -0
  156. package/dist/vendor/shared/durable-contract/keys.js +66 -0
  157. package/dist/vendor/shared/durable-contract/lock.d.ts +128 -0
  158. package/dist/vendor/shared/durable-contract/lock.js +151 -0
  159. package/dist/vendor/shared/durable-contract/names.d.ts +25 -0
  160. package/dist/vendor/shared/durable-contract/names.js +78 -0
  161. package/dist/vendor/shared/durable-contract/owner-verbs.d.ts +175 -0
  162. package/dist/vendor/shared/durable-contract/owner-verbs.js +403 -0
  163. package/dist/vendor/shared/durable-contract/paths.d.ts +23 -0
  164. package/dist/vendor/shared/durable-contract/paths.js +35 -0
  165. package/dist/vendor/shared/durable-contract/pure-fn.d.ts +48 -0
  166. package/dist/vendor/shared/durable-contract/pure-fn.js +89 -0
  167. package/dist/vendor/shared/durable-contract/routes.d.ts +205 -0
  168. package/dist/vendor/shared/durable-contract/routes.js +315 -0
  169. package/dist/vendor/shared/durable-contract/schema.d.ts +126 -0
  170. package/dist/vendor/shared/durable-contract/schema.js +441 -0
  171. package/dist/vendor/shared/durable-contract/seal.d.ts +54 -0
  172. package/dist/vendor/shared/durable-contract/seal.js +99 -0
  173. package/dist/vendor/shared/durable-contract/secret-scope.d.ts +28 -0
  174. package/dist/vendor/shared/durable-contract/secret-scope.js +76 -0
  175. package/dist/vendor/shared/durable-contract/session-id.d.ts +21 -0
  176. package/dist/vendor/shared/durable-contract/session-id.js +58 -0
  177. package/dist/vendor/shared/durable-contract/spec.d.ts +162 -0
  178. package/dist/vendor/shared/durable-contract/spec.js +286 -0
  179. package/dist/vendor/shared/durable-contract/states.d.ts +96 -0
  180. package/dist/vendor/shared/durable-contract/states.js +218 -0
  181. package/dist/vendor/shared/durable-contract/telemetry.d.ts +94 -0
  182. package/dist/vendor/shared/durable-contract/telemetry.js +180 -0
  183. package/dist/vendor/shared/durable-contract/test-mode.d.ts +62 -0
  184. package/dist/vendor/shared/durable-contract/test-mode.js +140 -0
  185. package/dist/vendor/shared/durable-contract/turn.d.ts +57 -0
  186. package/dist/vendor/shared/durable-contract/turn.js +81 -0
  187. package/dist/vendor/shared/durable-contract/vectors.d.ts +191 -0
  188. package/dist/vendor/shared/durable-contract/vectors.js +208 -0
  189. package/package.json +3 -2
  190. package/plugins/integrity-manifest.json +35 -0
  191. package/plugins/mnemom/hooks/hooks.json +15 -1
  192. package/plugins/mnemom/hooks/register.tsx +60 -2
  193. package/plugins/mnemom/hooks/seat-hello.mjs +116 -0
  194. package/dist/commands/agent-implicit-notice.d.ts +0 -31
  195. package/dist/commands/agent-implicit-notice.js +0 -73
package/dist/index.js CHANGED
@@ -37,14 +37,13 @@ import { agentDoctorCommand } from "./commands/agent-doctor.js";
37
37
  import { agentSetupCommand, parseGovernanceFlag } from "./commands/agent-setup.js";
38
38
  import { agentInviteCommand, runAgentInvocation } from "./commands/agent-invite.js";
39
39
  import { agentFirstRunCommand, offerFirstRunOnLaunch } from "./commands/agent-first-run.js";
40
- import { showImplicitDefaultNoticeOnce } from "./commands/agent-implicit-notice.js";
41
- import { isPrerelease } from "./lib/update/semver.js";
42
40
  import { applyConfigToEnv, launcherEntries, loadLauncherConfig, withoutDisabledExperimental, } from "./lib/launcher-config.js";
43
- import { loadExperimentalState } from "./lib/cli-config.js";
44
- import { assertEnabled, availableFeatures, EXPERIMENTAL_CONTRACT, gateCheck, ignoredSettingLines, reportCliError, } from "./lib/experimental.js";
41
+ import { loadCliConfig, loadExperimentalState } from "./lib/cli-config.js";
42
+ import { isSeatProxyArgv, runSeatProxyEntry } from "./durable/seat-proxy-entry.js";
43
+ import { assertEnabled, EXPERIMENTAL_CONTRACT, gateCheck, ignoredSettingLines, reportCliError, } from "./lib/experimental.js";
45
44
  import { durableVerbOffLines } from "./durable/gate.js";
46
45
  import { experimentalDisableCommand, experimentalEnableCommand, experimentalListCommand, runExperimentalMigration, } from "./commands/experimental.js";
47
- import { parseCompactAt, parseContextProfile, passthroughIsPrint, resolveImplicitSource, selectLaunchAgent, splitSessionArg, } from "./lib/agent.js";
46
+ import { parseCompactAt, parseContextProfile, resolveImplicit, selectLaunchAgent, splitSessionArg, } from "./lib/agent.js";
48
47
  import { wrapCommand } from "./commands/wrap.js";
49
48
  import { onboardCommand } from "./commands/onboard.js";
50
49
  import { skillsListCommand, skillsDescribeCommand } from "./commands/skills.js";
@@ -54,6 +53,16 @@ import { updateCommand } from "./commands/update.js";
54
53
  import { runAction } from "./lib/update/action.js";
55
54
  import { configListCommand, configGetCommand, configSetCommand, configUnsetCommand, } from "./commands/config.js";
56
55
  import { defaultStartupDeps, maybeRunUpdateHook } from "./lib/update/startup.js";
56
+ import { durableActionGate, durableGate, durableOn, optionShapesOf, } from "./lib/durable/gate.js";
57
+ import { durableSessionsCommand, durableShowCommand } from "./commands/durable-sessions.js";
58
+ import { durablePreflight } from "./lib/durable/launch.js";
59
+ import { durableRefusalExit } from "./lib/durable/refusal.js";
60
+ import { liveDoctorDurableLines, liveLockVerbDeps, liveSessionsReadDeps, liveVerbDeps, } from "./lib/durable/live.js";
61
+ import { amendCommand, containmentCommand, ownerVerbCommand, unlockCommand, } from "./commands/durable-verbs.js";
62
+ import { lockCommand } from "./commands/durable-lock.js";
63
+ import { registerHostCommands } from "./host/commands.js";
64
+ import { hostArgvGate } from "./host/gate.js";
65
+ import { homedir } from "node:os";
57
66
  program
58
67
  .name("mnemom")
59
68
  .description("Transparent AI agent tracing")
@@ -122,14 +131,11 @@ program
122
131
  // ── experimental features (lib/experimental.ts) ── While a feature is off its flags and
123
132
  // commands are hidden from --help (decided once, at load) and using one fails with a
124
133
  // pointer to `mnemom experimental enable <id>` (decided per invocation, in the action).
125
- // A prerelease (`next`) build also makes PRERELEASE_AVAILABLE usable (door one's T16).
126
- const PRERELEASE_BUILD = isPrerelease(CLI_VERSION);
127
- const loadAvailableFeatures = () => availableFeatures(loadExperimentalState().enabled, PRERELEASE_BUILD);
128
- const experimentalAtLoad = loadAvailableFeatures();
134
+ const experimentalAtLoad = loadExperimentalState().enabled;
129
135
  const hiddenUnless = (id) => !experimentalAtLoad.includes(id);
130
136
  /** preAction guard for a gated command: print the guidance and exit 2 while `id` is off. */
131
137
  function requireExperimental(what, id) {
132
- const message = gateCheck(what, id, loadAvailableFeatures());
138
+ const message = gateCheck(what, id, loadExperimentalState().enabled);
133
139
  if (message) {
134
140
  console.error(message);
135
141
  process.exit(2);
@@ -153,6 +159,8 @@ function printAgentHelp(verb) {
153
159
  : undefined;
154
160
  (sub ?? agentCmd).outputHelp();
155
161
  }
162
+ // `agent --new` is a durable option: held here so DURABLE_GATED_OPTIONS can name it.
163
+ const agentNewOption = new Option("--new", "EXPERIMENTAL (`mnemom experimental enable durable`): start a fresh session under this name; never continue one").hideHelp(hiddenUnless("durable"));
156
164
  const agentCmd = program
157
165
  .command("agent [session] [passthrough...]")
158
166
  .description("Launch your governed agent (Claude Code in v1) through the Mnemom gateway — governed identity + optional sealed goal contract. Plain `mnemom agent` asks for a session name. " +
@@ -172,7 +180,7 @@ const agentCmd = program
172
180
  "platform cards (strictest wins). See `mnemom protection` for that mode.")
173
181
  .option("--lead-directed", "Seal the standard lead-directed requirement: own-branch push and draft/ready PR creation, " +
174
182
  "from a git worktree, on the lead's direction — not merging. Requires --goal; idempotent.")
175
- .addOption(new Option("--implicit", "EXPERIMENTAL (`mnemom experimental enable implicit`): implicit goal alignment — the gateway infers the goal from your messages and revises it as you steer (the default while the feature is on, and on a `next` build for every launch but `-p`; not combinable with --goal/--requirement/--allow/--forbid/--goal-id/--mode/--lead-directed; the guardrail ceilings still apply)").hideHelp(hiddenUnless("implicit")))
183
+ .addOption(new Option("--implicit", "EXPERIMENTAL (`mnemom experimental enable implicit`): implicit goal alignment — the gateway infers the goal from your messages and revises it as you steer (the default while the feature is on; not combinable with --goal/--requirement/--allow/--forbid/--goal-id/--mode/--lead-directed; the guardrail ceilings still apply)").hideHelp(hiddenUnless("implicit")))
176
184
  .option("--no-implicit", "Turn implicit goal alignment OFF for this launch (overrides launcher.toml goal_mode = implicit)")
177
185
  .option("--conversation-id <id>", "x-mnemom-conversation-id (default: auto-generated)")
178
186
  .option("--cli <name|path>", "Coding-agent CLI to launch: a name or full path (default: claude)")
@@ -234,6 +242,7 @@ const agentCmd = program
234
242
  }
235
243
  return v;
236
244
  }))
245
+ .addOption(agentNewOption)
237
246
  .addOption(new Option("--context-handoff", "EXPERIMENTAL (`mnemom experimental enable context-handoff`): register the `handoff(note)` MCP tool and tell the gateway to turn on the context-handoff capability for this session").hideHelp(hiddenUnless("context-handoff")))
238
247
  .option("--no-context-handoff", "Keep context handoff off for this launch even when launcher.toml (context_handoff = true) or MNEMOM_AGENT_CONTEXT_HANDOFF turns it on")
239
248
  .option("--compaction-intercept", "Turn the compaction intercept on for this launch even when launcher.toml (compaction_intercept = false) or MNEMOM_AGENT_COMPACTION_INTERCEPT=0 turns it off. On by default: the gateway reports the real context size (so Claude Code compacts at its own threshold) and answers the compaction at once from its fold")
@@ -264,14 +273,18 @@ const agentCmd = program
264
273
  });
265
274
  }
266
275
  catch (error) {
276
+ const durableExit = durableRefusalExit(error, {
277
+ out: (l) => process.stdout.write(`${l}\n`),
278
+ err: (l) => console.error(l),
279
+ });
280
+ if (durableExit !== null)
281
+ process.exit(durableExit);
267
282
  const report = reportCliError(error);
268
283
  console.error(...report.args);
269
284
  process.exit(report.code);
270
285
  }
271
286
  async function launchAgent(scenario) {
272
287
  const experimental = loadExperimentalState().enabled;
273
- // What a flag, key or command may use: the enabled features, plus implicit on `next`.
274
- const available = availableFeatures(experimental, PRERELEASE_BUILD);
275
288
  // The one place the flag's raw string becomes `number | null` (see the argParser's
276
289
  // comment for why commander can't carry `null` through parseArg directly). Already
277
290
  // validated by the argParser, so this never throws.
@@ -279,7 +292,7 @@ const agentCmd = program
279
292
  // Sub-verb: `mnemom agent config …` manages persistent settings and never
280
293
  // launches. (A scenario literally named "config" is reserved for this.)
281
294
  if (scenario === "config") {
282
- await agentConfigCommand(passthrough, { enabled: available });
295
+ await agentConfigCommand(passthrough, { enabled: experimental });
283
296
  return;
284
297
  }
285
298
  // Sub-verb: `mnemom agent slack …` posts/receives Slack messages as this
@@ -299,7 +312,8 @@ const agentCmd = program
299
312
  used: opts.contextHandoff === true,
300
313
  },
301
314
  { what: "--implicit", id: "implicit", used: opts.implicit === true },
302
- ], available);
315
+ { what: "--new", id: "durable", used: opts.new === true },
316
+ ], experimental);
303
317
  if (opts.gateway)
304
318
  process.env.MNEMOM_AGENT_GATEWAY = opts.gateway;
305
319
  // Persistent settings (~/.mnemom/launcher.toml) sit UNDER env and flags:
@@ -312,11 +326,11 @@ const agentCmd = program
312
326
  for (const line of ignoredSettingLines({
313
327
  entries: launcherEntries(rawCfg),
314
328
  env: process.env,
315
- enabled: available,
329
+ enabled: experimental,
316
330
  })) {
317
331
  console.error(line);
318
332
  }
319
- const cfg = withoutDisabledExperimental(rawCfg, available);
333
+ const cfg = withoutDisabledExperimental(rawCfg, experimental);
320
334
  applyConfigToEnv(cfg, process.env);
321
335
  // Sub-verb: `mnemom agent doctor` runs a read-only preflight and never
322
336
  // launches. Placed after config load so it reports the effective settings.
@@ -326,7 +340,11 @@ const agentCmd = program
326
340
  return;
327
341
  }
328
342
  if (scenario === "doctor") {
329
- await agentDoctorCommand({ cli: opts.cli, agent: program.opts().agent ?? cfg.agent });
343
+ await agentDoctorCommand({
344
+ cli: opts.cli,
345
+ agent: program.opts().agent ?? cfg.agent,
346
+ ...(experimental.includes("durable") ? { durable: liveDoctorDurableLines } : {}),
347
+ });
330
348
  return;
331
349
  }
332
350
  // Validated up front so a typo fails before any sign-in or provisioning.
@@ -349,22 +367,14 @@ const agentCmd = program
349
367
  const numToStr = (n) => n === undefined ? undefined : String(n);
350
368
  // Implicit mode: --implicit / --no-implicit win; a --goal statement seals
351
369
  // (the standing config default never overrides it); else launcher.toml
352
- // `goal_mode`; else implicit while the `implicit` feature is on; else, on a
353
- // `next` build, implicit unless the launch is headless `-p` (T16); else off.
354
- // Pure + unit-tested in lib/agent.ts.
355
- const goalMode = resolveImplicitSource({
370
+ // `goal_mode`; else implicit while the `implicit` feature is on, off while it is
371
+ // off. Pure + unit-tested in lib/agent.ts.
372
+ const implicit = resolveImplicit({
356
373
  goal: opts.goal,
357
374
  implicit: opts.implicit,
358
375
  configGoalMode: cfg.goalMode,
359
376
  experimentalOn: experimental.includes("implicit"),
360
- prerelease: PRERELEASE_BUILD,
361
- print: passthroughIsPrint(passthrough),
362
377
  });
363
- const implicit = goalMode.implicit;
364
- // The T16 default tells the user once, the first time it applies.
365
- if (implicit && goalMode.source === "prerelease") {
366
- showImplicitDefaultNoticeOnce({ dryRun: opts.dryRun });
367
- }
368
378
  // Config guardrails apply only when goal alignment is actually intended (a
369
379
  // --goal is present, or implicit mode — where they ride on the mode header),
370
380
  // so a saved ceiling never forces an unrequested contract (which would
@@ -374,6 +384,20 @@ const agentCmd = program
374
384
  // shadows a subcommand `--agent` under commander@12 — the MNE-238 class),
375
385
  // exactly as status/logs/onboard read it.
376
386
  const parentOpts = program.opts();
387
+ // Durable (gate on only, BACKBONE C1): refusals decided before any registry call or spawn
388
+ // (nested launch, --server, the S1 name grammar); the registry plan runs in agentCommand.
389
+ const durableOn = experimental.includes("durable");
390
+ const durableKey = cfg.durable ?? "off";
391
+ if (durableOn) {
392
+ const refusal = durablePreflight({
393
+ name: scenario,
394
+ env: process.env,
395
+ server: opts.server === true,
396
+ launcherKey: durableKey,
397
+ });
398
+ if (refusal !== null)
399
+ throw refusal;
400
+ }
377
401
  // First interactive launch only: offer (opt-in) the local look at what Mnemom
378
402
  // would have saved. Tracked in ~/.mnemom/first-run.json; never blocks the launch.
379
403
  await offerFirstRunOnLaunch({ dryRun: opts.dryRun });
@@ -423,6 +447,9 @@ const agentCmd = program
423
447
  keySource: cfg.keySource,
424
448
  passthrough,
425
449
  dryRun: opts.dryRun,
450
+ ...(durableOn
451
+ ? { durable: { launcherKey: durableKey, newSession: opts.new === true } }
452
+ : {}),
426
453
  });
427
454
  }
428
455
  });
@@ -848,6 +875,7 @@ policyCmd
848
875
  // ============================================================================
849
876
  // Agent management — sub-verbs of `mnemom agent` (the launcher above): list / claim / move.
850
877
  // ============================================================================
878
+ const agentHostCmd = registerHostCommands(agentCmd, hiddenUnless("durable"));
851
879
  agentCmd
852
880
  .command("list")
853
881
  .description("List the agents in your active org (set with `mnemom org use`), or every org you belong to if none is set (ADR-062 org-scoped)")
@@ -941,13 +969,25 @@ const agentSessionsCmd = agentCmd
941
969
  .option("--json", "Output the rows as JSON instead of a table")
942
970
  .action(async (opts) => {
943
971
  try {
944
- await agentSessionsCommand({ all: opts.all, json: opts.json }, liveSessionDeps);
972
+ const ledger = async () => {
973
+ await agentSessionsCommand({ all: opts.all, json: opts.json }, liveSessionDeps);
974
+ return 0;
975
+ };
976
+ // Durable on: the server registry (C01-R12); off: the ledger, exactly as before.
977
+ if (durableOn(loadExperimentalState().enabled))
978
+ process.exitCode = await durableSessionsCommand(opts, liveSessionsReadDeps(), ledger);
979
+ else
980
+ await ledger();
945
981
  }
946
982
  catch (error) {
947
983
  console.error("Error:", error instanceof Error ? error.message : error);
948
984
  process.exit(1);
949
985
  }
950
986
  });
987
+ // `sessions` has no subcommands before M1: its usage line is captured here, before any durable
988
+ // sub-verb (C4 `adopt`, C1 `unlock`) is added, and restored once they are all registered while
989
+ // durable is off (commander counts hidden subcommands in `[command]`; C17-R09).
990
+ const agentSessionsPreM1Usage = agentSessionsCmd.usage();
951
991
  /** preAction (and --help) guard for a durable verb: while durable is off it does not exist (C14-R22). */
952
992
  function requireDurableVerb(word, what) {
953
993
  const lines = durableVerbOffLines(word, what, loadExperimentalState().enabled);
@@ -987,11 +1027,14 @@ const agentSessionsAdoptCmd = agentSessionsCmd
987
1027
  }
988
1028
  });
989
1029
  /**
990
- * The durable-gated commands (BACKBONE Flags/CLI: off lists no durable verb). The committed
991
- * command-tree.json leaves them out whatever the generating machine's experimental state
992
- * (scripts/gen-command-tree.mjs); a piece that registers another durable verb adds it here.
1030
+ * The durable-gated commands and options (BACKBONE Flags/CLI: off lists no durable verb). The
1031
+ * committed command-tree.json leaves them out whatever the generating machine's experimental
1032
+ * state (scripts/gen-command-tree.mjs); --help shows them only once durable is enabled. A piece
1033
+ * that registers another durable verb or option adds it here.
993
1034
  */
994
- export const DURABLE_GATED_COMMANDS = new Set([agentSessionsAdoptCmd]);
1035
+ const durableGatedCommands = new Set([agentHostCmd, agentSessionsAdoptCmd]);
1036
+ export const DURABLE_GATED_COMMANDS = durableGatedCommands;
1037
+ export const DURABLE_GATED_OPTIONS = new Set([agentNewOption]);
995
1038
  // commander answers --help before preAction: gate the help too, so the verb does not exist while off.
996
1039
  const adoptOutputHelp = agentSessionsAdoptCmd.outputHelp.bind(agentSessionsAdoptCmd);
997
1040
  agentSessionsAdoptCmd.outputHelp = ((...args) => {
@@ -1017,13 +1060,92 @@ agentCmd
1017
1060
  }
1018
1061
  followIntervalMs = seconds * 1000;
1019
1062
  }
1020
- await agentShowCommand(ref, { json: opts.json, follow: opts.follow, followIntervalMs }, liveSessionDeps);
1063
+ const ledger = async () => {
1064
+ await agentShowCommand(ref, { json: opts.json, follow: opts.follow, followIntervalMs }, liveSessionDeps);
1065
+ return 0;
1066
+ };
1067
+ // Durable on: resolve the name through the registry (C01-R12, R24); --follow and a ref
1068
+ // the registry does not know stay on the ledger view.
1069
+ if (durableOn(loadExperimentalState().enabled) && !opts.follow)
1070
+ process.exitCode = await durableShowCommand(ref, { json: opts.json }, liveSessionsReadDeps(), ledger);
1071
+ else
1072
+ await ledger();
1021
1073
  }
1022
1074
  catch (error) {
1023
1075
  console.error("Error:", error instanceof Error ? error.message : error);
1024
1076
  process.exit(1);
1025
1077
  }
1026
1078
  });
1079
+ /**
1080
+ * Read an option value the user typed on this parse from `cmd`, then clear it: commander keeps
1081
+ * option values on the command object across parses, so a value left behind would carry into the
1082
+ * next parse of the same program (another amend, or a launch that reads the guardrail).
1083
+ */
1084
+ function takeParentOption(cmd, key) {
1085
+ if (cmd.getOptionValueSource(key) !== "cli")
1086
+ return undefined;
1087
+ const v = cmd.getOptionValue(key);
1088
+ cmd.setOptionValueWithSource(key, undefined, "default");
1089
+ return v;
1090
+ }
1091
+ // ── durable verbs (BACKBONE C1; EXPERIMENTAL `durable`) ── hidden while durable is off, and
1092
+ // unreachable then: runEntrypoint's durable gate exits 2 on these words before commander parses.
1093
+ // Each handler returns its exit status (refusals rendered by lib/durable/refusal.ts).
1094
+ {
1095
+ const hidden = hiddenUnless("durable");
1096
+ // The action-level gate (C17): while durable is off the action exits 2 with the pointer before it
1097
+ // builds any client, for any argv shape that got past runEntrypoint's scan.
1098
+ const exitWith = async (what, run) => {
1099
+ const gated = durableActionGate(what, loadExperimentalState().enabled);
1100
+ if (gated !== null) {
1101
+ console.error(gated);
1102
+ process.exitCode = 2;
1103
+ return;
1104
+ }
1105
+ process.exitCode = await run();
1106
+ };
1107
+ const gate = (cmd) => {
1108
+ durableGatedCommands.add(cmd);
1109
+ return cmd;
1110
+ };
1111
+ gate(agentCmd.command("lock <name>", { hidden }))
1112
+ .description("EXPERIMENTAL (durable): lock a session so the host daemon keeps it running")
1113
+ .option("--own-agent", "Lock even though other sessions share the agent (they are locked too)")
1114
+ .option("--json", "Output the answer as JSON")
1115
+ .action((name, o) => exitWith("`mnemom agent lock`", () => lockCommand(name, o, liveLockVerbDeps())));
1116
+ for (const verb of ["pause", "resume", "done", "kill"]) {
1117
+ gate(agentCmd.command(`${verb} <name>`, { hidden }))
1118
+ .description(`EXPERIMENTAL (durable): ${verb} a session`)
1119
+ .option("--json", "Output the answer as JSON")
1120
+ .action((name, o) => exitWith(`\`mnemom agent ${verb}\``, () => ownerVerbCommand(verb, name, o, liveVerbDeps())));
1121
+ }
1122
+ gate(agentCmd.command("amend <name>", { hidden }))
1123
+ .description("EXPERIMENTAL (durable): lower a session's limits, or raise its budget (approved by device step)")
1124
+ .option("--budget <usd>", "New budget in USD")
1125
+ .option("--turns <n>", "New turn ceiling (lowering only)")
1126
+ .option("--json", "Output the answer as JSON")
1127
+ .action((name, o) => {
1128
+ // `agent` has its own --budget (the launch guardrail), and commander hands an option the
1129
+ // parent knows to the parent wherever it sits in argv: amend's --budget lands there.
1130
+ const budget = takeParentOption(agentCmd, "budget");
1131
+ return exitWith("`mnemom agent amend`", () => amendCommand(name, { ...o, budget }, liveVerbDeps()));
1132
+ });
1133
+ const containmentCmd = gate(agentCmd.command("containment", { hidden })).description("EXPERIMENTAL (durable): org admin containment of an agent (pause | resume)");
1134
+ for (const verb of ["pause", "resume"]) {
1135
+ containmentCmd
1136
+ .command(`${verb} <agent>`)
1137
+ .description(`${verb} every session of an agent (org admin, signed in)`)
1138
+ .action((agent) => exitWith(`\`mnemom agent containment ${verb}\``, () => containmentCommand(verb, agent, liveVerbDeps())));
1139
+ }
1140
+ // `sessions` has no subcommands before M1: keep its usage line byte-identical (commander
1141
+ // counts hidden subcommands in `[command]`). Captured before C4's `adopt` was added too.
1142
+ const sessionsUsage = agentSessionsPreM1Usage;
1143
+ gate(agentSessionsCmd.command("unlock <name>", { hidden }))
1144
+ .description("EXPERIMENTAL (durable): unlock a locked session back to tracked (device step)")
1145
+ .action((name) => exitWith("`mnemom agent sessions unlock`", () => unlockCommand(name, liveVerbDeps())));
1146
+ if (hidden)
1147
+ agentSessionsCmd.usage(sessionsUsage);
1148
+ }
1027
1149
  // `mnemom agent run --watch|--once` — the thin runner (ruling 34.12; Doors W-L13): claim served-phase
1028
1150
  // jobs from the org's ready pool and run their signed phases. Exit 0 done/no_job, 1 failed,
1029
1151
  // 2 usage, 3 refused.
@@ -2291,6 +2413,13 @@ export { program };
2291
2413
  // Parse argv only when invoked as the CLI entrypoint — not when imported.
2292
2414
  // The symlink-safe guard lives in ./lib/entrypoint so it can be unit-tested
2293
2415
  // without executing this module's top-level parse().
2416
+ /**
2417
+ * The value-taking options of the program, `agent` and `agent sessions`: the durable gate steps over
2418
+ * them (and their values) to find the verb word, as commander does before it dispatches.
2419
+ */
2420
+ export function agentOptionShapes() {
2421
+ return optionShapesOf([program, agentCmd, agentSessionsCmd]);
2422
+ }
2294
2423
  /**
2295
2424
  * The entrypoint dispatch. `mnemom agent slack …` owns its own flags (--thread,
2296
2425
  * --channel, …), which the `agent` launcher options would otherwise reject as
@@ -2298,8 +2427,41 @@ export { program };
2298
2427
  * verbatim. Exported (not auto-run) so the bypass is unit-testable.
2299
2428
  */
2300
2429
  export function runEntrypoint(argv = process.argv.slice(2), migrate = runExperimentalMigration) {
2430
+ // The durable seat proxy (C3) is spawned by the host daemon; its stdout carries only READY, so it
2431
+ // runs before the migration and the update hook (durable/seat-proxy-entry.ts holds the gate).
2432
+ if (isSeatProxyArgv(argv)) {
2433
+ runSeatProxyEntry(argv, {
2434
+ stored: () => loadCliConfig().experimental,
2435
+ stderr: (line) => void process.stderr.write(line),
2436
+ exit: (code) => process.exit(code),
2437
+ run: async (rest) => (await import("./durable/seat-proxy.js")).seatProxyMain(rest),
2438
+ });
2439
+ return;
2440
+ }
2301
2441
  // First run after the upgrade that introduced experimental features: keep on whatever
2302
2442
  // launcher.toml already uses (commands/experimental.ts; never throws).
2443
+ // `mnemom agent host …` with durable off: gated before commander's help/usage paths and before
2444
+ // the one-time migration below can write config.json (C17-R10: no HOME write). C2 owns the
2445
+ // `host` word (config.json-only gate for `host run`, C12-R25/R39), so the C1 verb-word gate
2446
+ // below steps aside for it (integration r1: the C1 gate documents "until C2 registers its handler").
2447
+ const hostGate = hostArgvGate(argv, { env: process.env, home: homedir() });
2448
+ if (hostGate) {
2449
+ console.error(hostGate.message);
2450
+ process.exit(hostGate.code);
2451
+ return;
2452
+ }
2453
+ // Durable verb words while `durable` is off: exit 2 with the pointer before commander can
2454
+ // answer --help, and before the one-time migration below can write config.json (BACKBONE
2455
+ // Flags/CLI: no HTTP, no HOME write, no process).
2456
+ const hostArgv = argv[0] === "agent" && argv[1] === "host";
2457
+ const durableGateMessage = hostArgv
2458
+ ? null
2459
+ : durableGate(argv, loadExperimentalState().enabled, agentOptionShapes());
2460
+ if (durableGateMessage !== null) {
2461
+ console.error(durableGateMessage);
2462
+ process.exit(2);
2463
+ return;
2464
+ }
2303
2465
  migrate();
2304
2466
  if (argv[0] === "agent" && argv[1] === "slack") {
2305
2467
  requireExperimental("`mnemom agent slack`", "slack");
@@ -925,47 +925,29 @@ export declare function buildHandoffMcpConfigJson(p: HandoffMcpParams): string;
925
925
  * per-session statement, so it is always a per-launch `--goal`.
926
926
  */
927
927
  export type GoalModeDefault = "implicit" | "off";
928
- /**
929
- * Where a launch's goal mode came from (`resolveImplicitSource`). `prerelease` is door
930
- * one's T16 default (decisions 12 and 22): implicit by default on a `-next.N` build with
931
- * nothing configured. It is the only source that shows the one-time notice.
932
- */
933
- export type ImplicitSource = "flag" | "goal" | "config" | "experimental" | "prerelease" | "default";
934
- /** Does a Claude Code passthrough run headless (`-p` / `--print`)? */
935
- export declare function passthroughIsPrint(passthrough: readonly string[]): boolean;
936
928
  /**
937
929
  * Is this launch implicit-mode? Precedence, highest wins:
938
930
  * 1. an explicit flag — `--implicit` (true) or `--no-implicit` (false);
939
931
  * 2. a `--goal` statement — a sealed contract is a per-launch choice that the
940
- * standing config default must never silently override, so a launch never
941
- * runs both modes;
932
+ * standing config default must never silently override;
942
933
  * 3. the config default `goal_mode` — `"off"` turns alignment off, `"implicit"`
943
934
  * keeps it implicit;
944
- * 4. with the `implicit` experimental feature ON, IMPLICIT (as before T16);
945
- * 5. on a prerelease (`next`) build, IMPLICIT for an interactive or Remote Control
946
- * launch and OFF for a headless `-p` one (OD-13: T16 never covers `-p`);
947
- * 6. otherwise OFF — stable without the experimental feature behaves as today.
935
+ * 4. with the `implicit` experimental feature ON, IMPLICIT: the gateway infers
936
+ * the goal from the user's own messages (turn it off for a launch with
937
+ * `--no-implicit`, or standing with `goal_mode = "off"`); with it OFF (the
938
+ * default), OFF.
948
939
  * Pure so every arm is unit-tested; the caller passes the resolved value on to
949
940
  * `assembleContract` / `buildGoalModeHeader` (which still refuse `--implicit`
950
941
  * combined with a contract flag — that conflict is EXPLICIT and is reported).
951
942
  */
952
- export declare function resolveImplicitSource(opts: {
943
+ export declare function resolveImplicit(opts: {
953
944
  goal?: string;
954
945
  implicit?: boolean;
955
946
  configGoalMode?: GoalModeDefault;
956
947
  /**
957
948
  * Is the `implicit` experimental feature on? Off ⇒ the default is OFF and
958
949
  * `goal_mode = implicit` is ignored (`--implicit` is refused upstream, before
959
- * this runs), unless this is a prerelease build. Omitted ⇒ on.
950
+ * this runs). Omitted ⇒ on.
960
951
  */
961
952
  experimentalOn?: boolean;
962
- /** Is this a prerelease (`-next.N`) CLI build? `isPrerelease(CLI_VERSION)`. Omitted ⇒ no. */
963
- prerelease?: boolean;
964
- /** Does the launch run Claude Code headless (`-p`)? `passthroughIsPrint`. */
965
- print?: boolean;
966
- }): {
967
- implicit: boolean;
968
- source: ImplicitSource;
969
- };
970
- /** `resolveImplicitSource(opts).implicit`. */
971
- export declare function resolveImplicit(opts: Parameters<typeof resolveImplicitSource>[0]): boolean;
953
+ }): boolean;
package/dist/lib/agent.js CHANGED
@@ -1353,42 +1353,27 @@ export function buildHandoffMcpConfig(p) {
1353
1353
  export function buildHandoffMcpConfigJson(p) {
1354
1354
  return JSON.stringify(buildHandoffMcpConfig(p));
1355
1355
  }
1356
- /** Does a Claude Code passthrough run headless (`-p` / `--print`)? */
1357
- export function passthroughIsPrint(passthrough) {
1358
- return passthrough.some((a) => a === "-p" || a === "--print" || a.startsWith("--print="));
1359
- }
1360
1356
  /**
1361
1357
  * Is this launch implicit-mode? Precedence, highest wins:
1362
1358
  * 1. an explicit flag — `--implicit` (true) or `--no-implicit` (false);
1363
1359
  * 2. a `--goal` statement — a sealed contract is a per-launch choice that the
1364
- * standing config default must never silently override, so a launch never
1365
- * runs both modes;
1360
+ * standing config default must never silently override;
1366
1361
  * 3. the config default `goal_mode` — `"off"` turns alignment off, `"implicit"`
1367
1362
  * keeps it implicit;
1368
- * 4. with the `implicit` experimental feature ON, IMPLICIT (as before T16);
1369
- * 5. on a prerelease (`next`) build, IMPLICIT for an interactive or Remote Control
1370
- * launch and OFF for a headless `-p` one (OD-13: T16 never covers `-p`);
1371
- * 6. otherwise OFF — stable without the experimental feature behaves as today.
1363
+ * 4. with the `implicit` experimental feature ON, IMPLICIT: the gateway infers
1364
+ * the goal from the user's own messages (turn it off for a launch with
1365
+ * `--no-implicit`, or standing with `goal_mode = "off"`); with it OFF (the
1366
+ * default), OFF.
1372
1367
  * Pure so every arm is unit-tested; the caller passes the resolved value on to
1373
1368
  * `assembleContract` / `buildGoalModeHeader` (which still refuse `--implicit`
1374
1369
  * combined with a contract flag — that conflict is EXPLICIT and is reported).
1375
1370
  */
1376
- export function resolveImplicitSource(opts) {
1371
+ export function resolveImplicit(opts) {
1377
1372
  if (opts.implicit !== undefined)
1378
- return { implicit: opts.implicit, source: "flag" };
1373
+ return opts.implicit;
1379
1374
  if (hasText(opts.goal))
1380
- return { implicit: false, source: "goal" };
1381
- const experimentalOn = opts.experimentalOn !== false;
1382
- if (!experimentalOn && !opts.prerelease)
1383
- return { implicit: false, source: "default" };
1384
- if (opts.configGoalMode !== undefined) {
1385
- return { implicit: opts.configGoalMode === "implicit", source: "config" };
1386
- }
1387
- if (experimentalOn)
1388
- return { implicit: true, source: "experimental" };
1389
- return { implicit: !opts.print, source: opts.print ? "default" : "prerelease" };
1390
- }
1391
- /** `resolveImplicitSource(opts).implicit`. */
1392
- export function resolveImplicit(opts) {
1393
- return resolveImplicitSource(opts).implicit;
1375
+ return false;
1376
+ if (opts.experimentalOn === false)
1377
+ return false;
1378
+ return opts.configGoalMode !== "off";
1394
1379
  }
@@ -65,11 +65,16 @@ export declare const PLUGIN_ENV: {
65
65
  readonly launcherVersion: "MNEMOM_CC_LAUNCHER_VERSION";
66
66
  };
67
67
  export type PluginEnvName = (typeof PLUGIN_ENV)[keyof typeof PLUGIN_ENV];
68
+ /**
69
+ * Set to "1" only on a launch with the `durable` experimental gate on (BACKBONE C1): the mod then
70
+ * offers `/mnemom lock`. Absent otherwise, so a launch with durable off has the same env as before.
71
+ */
72
+ export declare const PLUGIN_DURABLE_ENV = "MNEMOM_CC_DURABLE";
68
73
  /**
69
74
  * Whether this launch loads the plugin: `--plugin` / `--no-plugin` (an explicit boolean) wins,
70
75
  * then MNEMOM_AGENT_PLUGIN (1/true/yes/on ⇒ on), else OFF: the plugin lands dark.
71
76
  */
72
- export declare function pluginEnabled(optionPlugin: boolean | undefined, env: NodeJS.ProcessEnv): boolean;
77
+ export declare function pluginEnabled(optionPlugin: boolean | undefined, env: NodeJS.ProcessEnv, durable?: boolean): boolean;
73
78
  /**
74
79
  * The bundled plugin's folder (the one holding `.claude-plugin/plugin.json`), or null when it is
75
80
  * missing. Lives at `<package>/plugins/mnemom`, next to `dist/` and `src/`, so it is
@@ -97,12 +102,16 @@ export interface PluginLaunch {
97
102
  launcherExec?: readonly string[];
98
103
  scenario: string;
99
104
  launcherVersion: string;
105
+ /** The `durable` gate is on for this launch (adds PLUGIN_DURABLE_ENV=1). */
106
+ durable?: boolean;
100
107
  }
101
108
  /**
102
109
  * The `MNEMOM_CC_*` block for the child env. Pure: no key, no header, nothing the gateway
103
110
  * authenticates with — just what the launch log already prints to stderr.
104
111
  */
105
- export declare function buildPluginEnv(launch: PluginLaunch): Record<PluginEnvName, string>;
112
+ export declare function buildPluginEnv(launch: PluginLaunch): Record<PluginEnvName, string> & {
113
+ [PLUGIN_DURABLE_ENV]?: "1";
114
+ };
106
115
  /** Writes the block onto a child env in place (the launch shapes all build their env first). */
107
116
  export declare function applyPluginEnv(childEnv: NodeJS.ProcessEnv, launch: PluginLaunch): void;
108
117
  /**
@@ -67,14 +67,22 @@ export const PLUGIN_ENV = {
67
67
  session: "MNEMOM_CC_SESSION",
68
68
  launcherVersion: "MNEMOM_CC_LAUNCHER_VERSION",
69
69
  };
70
+ /**
71
+ * Set to "1" only on a launch with the `durable` experimental gate on (BACKBONE C1): the mod then
72
+ * offers `/mnemom lock`. Absent otherwise, so a launch with durable off has the same env as before.
73
+ */
74
+ export const PLUGIN_DURABLE_ENV = "MNEMOM_CC_DURABLE";
70
75
  const TRUTHY = new Set(["1", "true", "yes", "on"]);
71
76
  /**
72
77
  * Whether this launch loads the plugin: `--plugin` / `--no-plugin` (an explicit boolean) wins,
73
78
  * then MNEMOM_AGENT_PLUGIN (1/true/yes/on ⇒ on), else OFF: the plugin lands dark.
74
79
  */
75
- export function pluginEnabled(optionPlugin, env) {
80
+ export function pluginEnabled(optionPlugin, env, durable = false) {
76
81
  if (optionPlugin !== undefined)
77
82
  return optionPlugin;
83
+ // BACKBONE C1 "plugin on for durable sessions": with the durable gate on it defaults on.
84
+ if (durable)
85
+ return true;
78
86
  const raw = (env.MNEMOM_AGENT_PLUGIN ?? "").trim().toLowerCase();
79
87
  return TRUTHY.has(raw);
80
88
  }
@@ -121,6 +129,7 @@ export function buildPluginEnv(launch) {
121
129
  [PLUGIN_ENV.launcherExec]: exec.length > 0 ? JSON.stringify(exec) : "",
122
130
  [PLUGIN_ENV.session]: clean(launch.scenario),
123
131
  [PLUGIN_ENV.launcherVersion]: clean(launch.launcherVersion),
132
+ ...(launch.durable ? { [PLUGIN_DURABLE_ENV]: "1" } : {}),
124
133
  };
125
134
  }
126
135
  /** Writes the block onto a child env in place (the launch shapes all build their env first). */