@mnemom/mnemom 0.17.31 → 0.17.32-next.1

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 (46) hide show
  1. package/dist/base-url-proxy.mjs +30 -0
  2. package/dist/commands/agent-fuel.d.ts +75 -0
  3. package/dist/commands/agent-fuel.js +298 -0
  4. package/dist/commands/agent-implicit-notice.d.ts +31 -0
  5. package/dist/commands/agent-implicit-notice.js +73 -0
  6. package/dist/commands/agent-session.d.ts +2 -1
  7. package/dist/commands/agent-session.js +2 -1
  8. package/dist/commands/agent-share-fixture.d.ts +20 -0
  9. package/dist/commands/agent-share-fixture.js +134 -0
  10. package/dist/commands/agent-share.d.ts +87 -0
  11. package/dist/commands/agent-share.js +251 -0
  12. package/dist/commands/agent.d.ts +75 -2
  13. package/dist/commands/agent.js +165 -28
  14. package/dist/commands/status.js +1 -1
  15. package/dist/commands/update.js +3 -1
  16. package/dist/commands/validate.d.ts +1 -1
  17. package/dist/commands/validate.js +1 -1
  18. package/dist/index.js +68 -12
  19. package/dist/lib/agent.d.ts +34 -19
  20. package/dist/lib/agent.js +41 -32
  21. package/dist/lib/api.d.ts +66 -0
  22. package/dist/lib/api.js +50 -10
  23. package/dist/lib/canonical-json.d.ts +12 -0
  24. package/dist/lib/canonical-json.js +22 -0
  25. package/dist/lib/commit-report.d.ts +109 -0
  26. package/dist/lib/commit-report.js +205 -0
  27. package/dist/lib/conduct-receipt.d.ts +81 -0
  28. package/dist/lib/conduct-receipt.js +177 -0
  29. package/dist/lib/contract-hash.d.ts +61 -0
  30. package/dist/lib/contract-hash.js +126 -0
  31. package/dist/lib/experimental.d.ts +10 -1
  32. package/dist/lib/experimental.js +35 -1
  33. package/dist/lib/oauth.d.ts +2 -1
  34. package/dist/lib/oauth.js +2 -1
  35. package/dist/lib/statusline-bin.d.ts +2 -2
  36. package/dist/lib/statusline-bin.js +4 -7
  37. package/dist/lib/statusline-feed.d.ts +33 -0
  38. package/dist/lib/statusline-feed.js +68 -0
  39. package/dist/lib/statusline.d.ts +39 -88
  40. package/dist/lib/statusline.js +58 -250
  41. package/dist/lib/update/decide.d.ts +22 -3
  42. package/dist/lib/update/decide.js +35 -12
  43. package/dist/lib/update/doctor.js +5 -1
  44. package/dist/lib/update/run.d.ts +2 -0
  45. package/dist/lib/update/run.js +22 -8
  46. package/package.json +1 -1
@@ -31,7 +31,10 @@ import { randomUUID } from "node:crypto";
31
31
  import { tmpdir, homedir, hostname } from "node:os";
32
32
  import { basename, join, dirname } from "node:path";
33
33
  import { fileURLToPath } from "node:url";
34
- import { MEASURED_ENV, statuslineLaunch } from "../lib/statusline.js";
34
+ import { FEED_ENV, statuslineLaunch } from "../lib/statusline.js";
35
+ import { startFeed } from "../lib/statusline-feed.js";
36
+ import { isPrerelease } from "../lib/update/semver.js";
37
+ import { attributionSettings, COMMIT_REPORT_ENV, commitReportEnabled, realGit, startCommitReporter, } from "../lib/commit-report.js";
35
38
  import { ALLOW_AUTOCOMPACT_OFF_ENV, dropAutocompactOffEnv, hasOwnSettingsFlag, mergedSettingsArgs, planAutocompactGuard, } from "../lib/autocompact-guard.js";
36
39
  import { getApiUrl, getGatewayUrl, MNEMOM_DIR } from "../lib/config.js";
37
40
  import { createApiKey } from "../lib/api.js";
@@ -41,6 +44,7 @@ import { isLoggedIn } from "../lib/auth.js";
41
44
  import { loadCliConfig } from "../lib/cli-config.js";
42
45
  import { configKeySpec, loadLauncherConfig, writeLauncherConfig } from "../lib/launcher-config.js";
43
46
  import { experimentalNotice } from "../lib/experimental.js";
47
+ import { conductReceiptFor } from "../lib/conduct-receipt.js";
44
48
  import { fmt } from "../lib/format.js";
45
49
  import { askInput, askSelect, askYesNo, isInteractive } from "../lib/prompt.js";
46
50
  import { authConfigured, chooseAuth, detectClaudeLogin, readClaudeLoginState, } from "../lib/login-detect.js";
@@ -169,7 +173,8 @@ export async function agentCommand(scenarioArg, options = {}) {
169
173
  liftPassthroughName(options.passthrough ?? []);
170
174
  // ── session name + goal ── (a bare `mnemom agent` in a terminal asks for the name;
171
175
  // goal alignment is resolved upstream: --goal seals a contract, implicit alignment
172
- // is on by default only while the `implicit` experimental feature is on.
176
+ // is on by default while the `implicit` experimental feature is on, and on a `next`
177
+ // build for every launch but `-p` (T16).
173
178
  const session = await resolveSessionSetup({
174
179
  scenario: scenarioArg,
175
180
  goal: options.goal,
@@ -729,6 +734,13 @@ export async function agentCommand(scenarioArg, options = {}) {
729
734
  }),
730
735
  knownCcSessionId: cc.id ?? continuedCcSessionId,
731
736
  plugin,
737
+ receipt: experimental.includes("record")
738
+ ? { orgId: account.orgId, agentId: servedAgentId }
739
+ : undefined,
740
+ orgId: account.orgId,
741
+ commitReport: cli.name === "claude" &&
742
+ Boolean(account.orgId) &&
743
+ commitReportEnabled(process.env[COMMIT_REPORT_ENV], CLI_VERSION),
732
744
  });
733
745
  return; // launchTerminal exits the process
734
746
  }
@@ -1237,6 +1249,7 @@ function launchTerminal(ctx) {
1237
1249
  // A subscription launch (no Anthropic key) sets NO auth token: Claude Code then sends its
1238
1250
  // own claude.ai login to the gateway (lib/subscription-login.ts).
1239
1251
  let childEnv;
1252
+ let feed = null;
1240
1253
  if (ctx.anthropicKey) {
1241
1254
  childEnv = { ...process.env };
1242
1255
  delete childEnv.ANTHROPIC_API_KEY;
@@ -1262,10 +1275,8 @@ function launchTerminal(ctx) {
1262
1275
  applyGatewayHintHeaders(childEnv);
1263
1276
  if (ctx.options.slack)
1264
1277
  args.push(...slackChannelArgs(sessionName));
1265
- // The status-bar savings counter (lib/statusline.ts). Claude Code only; the
1266
- // user's own statusLine keeps running in front of it. Measured mode is this launch's
1267
- // decision alone: a value inherited from an outer `mnemom agent` session never rides along.
1268
- Reflect.deleteProperty(childEnv, MEASURED_ENV);
1278
+ // The status-bar segment (lib/statusline.ts). Claude Code only; the user's own
1279
+ // statusLine keeps running in front of it.
1269
1280
  if (ctx.cli.name === "claude") {
1270
1281
  const passthrough = ctx.options.passthrough ?? [];
1271
1282
  const sl = statuslineLaunch({
@@ -1274,21 +1285,48 @@ function launchTerminal(ctx) {
1274
1285
  cwd: process.cwd(),
1275
1286
  node: process.execPath,
1276
1287
  bin: join(dirname(fileURLToPath(import.meta.url)), "..", "lib", "statusline-bin.js"),
1277
- measured: ctx.usageSavings,
1278
1288
  });
1279
1289
  if (sl)
1280
1290
  Object.assign(childEnv, sl.env);
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));
1291
+ feed = prepareStatuslineFeed({
1292
+ statusline: sl !== null,
1293
+ orgId: ctx.orgId,
1294
+ version: CLI_VERSION,
1295
+ goalMode: Boolean(ctx.goalMode),
1296
+ env: process.env,
1297
+ childEnv,
1298
+ });
1299
+ // Autocompact guard (lib/autocompact-guard.ts): one --settings carries every layer. The
1300
+ // commit trailer (W1.8) rides in it unless the user passed their own --settings.
1301
+ const ownSettings = hasOwnSettingsFlag(passthrough);
1302
+ const guard = applyAutocompactGuard(childEnv, ctx.options, !ownSettings);
1303
+ const extra = launchSettingsExtra({
1304
+ statusline: sl?.settings ?? null,
1305
+ commitReport: Boolean(ctx.commitReport),
1306
+ ownSettings,
1307
+ conversationId: ctx.conversationId,
1308
+ settings: ctx.commitReport && !ownSettings ? ccSettings() : [],
1309
+ });
1310
+ args.push(...mergedSettingsArgs(guard, extra));
1284
1311
  }
1285
1312
  if (ctx.contextHint) {
1286
1313
  seedContextHintFlag(childEnv);
1287
1314
  childEnv.CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC = "1";
1288
1315
  childEnv.CLAUDE_CODE_GB_DISK_CACHE_WHEN_TELEMETRY_OFF = "1";
1289
1316
  }
1317
+ let recordStatePath;
1290
1318
  const run = () => {
1319
+ const launchedSec = Math.floor(Date.now() / 1000);
1291
1320
  const child = spawn(ctx.cli.bin, args, { stdio: "inherit", env: childEnv });
1321
+ // W1.8: report the session's commits while it runs; no record state (no proxy) → no reports.
1322
+ const commits = ctx.commitReport && ctx.orgId && recordStatePath
1323
+ ? startCommitReporter({
1324
+ orgId: ctx.orgId,
1325
+ recordStateFile: recordStatePath,
1326
+ sinceSec: launchedSec,
1327
+ git: realGit(process.cwd()),
1328
+ })
1329
+ : null;
1292
1330
  child.on("error", (err) => {
1293
1331
  say(`mnemom agent: failed to launch ${ctx.cli.name}: ${err.message}`);
1294
1332
  process.exit(1);
@@ -1296,34 +1334,114 @@ function launchTerminal(ctx) {
1296
1334
  child.on("exit", (code, signal) => {
1297
1335
  if (signal)
1298
1336
  process.kill(process.pid, signal);
1299
- else {
1300
- // Claude Code's own last line is "Resume this session with: claude --resume <id>",
1301
- // which reopens it OUTSIDE Mnemom (ungoverned, unbilled). Ours goes after it, last.
1302
- if (code === 0 && ctx.resumeHint && wantsResumeHint(ctx.cli, args)) {
1303
- const id = ctx.knownCcSessionId ?? newestCcSessionIn(process.cwd(), process.env);
1304
- say(ctx.resumeHint(id));
1305
- }
1306
- process.exit(code ?? 0);
1307
- }
1337
+ else
1338
+ void (async () => {
1339
+ // The conduct receipt and the last commit sweep run side by side, each under its own
1340
+ // hard deadline inside W1.3's exit budget; neither changes the exit code.
1341
+ const swept = commits?.finish().catch(() => undefined);
1342
+ if (ctx.receipt)
1343
+ await sayConductReceipt({ ...ctx.receipt, statePath: recordStatePath });
1344
+ await swept;
1345
+ // Claude Code's own last line is "Resume this session with: claude --resume <id>",
1346
+ // which reopens it OUTSIDE Mnemom (ungoverned, unbilled). Ours goes after it, last.
1347
+ if (code === 0 && ctx.resumeHint && wantsResumeHint(ctx.cli, args)) {
1348
+ const id = ctx.knownCcSessionId ?? newestCcSessionIn(process.cwd(), process.env);
1349
+ say(ctx.resumeHint(id));
1350
+ }
1351
+ process.exit(code ?? 0);
1352
+ })();
1308
1353
  });
1309
1354
  };
1310
1355
  // Implicit goal mode: put the local base-URL proxy in front of the door so each main request
1311
1356
  // carries x-mnemom-turn (where each recent user turn came from, read from Claude Code's own
1312
1357
  // transcript — base-url-proxy.mjs / turn-origin.mjs). The prompt id it joins on comes from the
1313
- // gateway hint headers set above. Fail-open: no proxy → the door directly, as before.
1314
- if (!ctx.goalMode || !turnProxyWanted(process.env)) {
1358
+ // gateway hint headers set above. Experimental `record` runs it too (turn header off unless
1359
+ // implicit), to capture the lineage key the conduct receipt reads. Fail-open: no proxy → the
1360
+ // door directly, as before, and a receipt that links instead of giving figures.
1361
+ const turnHeader = Boolean(ctx.goalMode) && turnProxyWanted(process.env);
1362
+ if (!proxyWanted({
1363
+ turnHeader,
1364
+ receipt: Boolean(ctx.receipt),
1365
+ commitReport: Boolean(ctx.commitReport),
1366
+ })) {
1315
1367
  run();
1316
1368
  return;
1317
1369
  }
1318
- startBaseUrlProxy(ctx.door, (baseUrl) => {
1319
- if (baseUrl) {
1320
- childEnv.ANTHROPIC_BASE_URL = baseUrl;
1321
- // An inherited HTTPS_PROXY must not carry the loopback hop.
1322
- exemptLoopbackFromProxy(childEnv);
1323
- }
1370
+ startBaseUrlProxy(ctx.door, (baseUrl, statePath) => {
1371
+ if (baseUrl)
1372
+ recordStatePath = statePath;
1373
+ onBaseUrlProxyReady(baseUrl, statePath, childEnv, feed && ctx.orgId ? { ...feed, orgId: ctx.orgId } : null);
1324
1374
  run();
1375
+ }, {
1376
+ turnHeader,
1377
+ recordState: wantsRecordState(Boolean(ctx.receipt || ctx.commitReport), feed !== null),
1325
1378
  });
1326
1379
  }
1380
+ /**
1381
+ * The extra `--settings` the launch merges (beside the autocompact guard): the status line's,
1382
+ * and with commit reporting on (W1.8) the `Mnemom-Conversation` commit trailer, which keeps the
1383
+ * user's own commit attribution in front of it. Never when the user passed their own --settings.
1384
+ */
1385
+ export function launchSettingsExtra(o) {
1386
+ const trailer = o.commitReport && !o.ownSettings
1387
+ ? attributionSettings(o.conversationId, o.settings.find((v) => v.commitAttribution !== undefined)?.commitAttribution)
1388
+ : null;
1389
+ if (!o.statusline && !trailer)
1390
+ return null;
1391
+ return { ...(o.statusline ?? {}), ...(trailer ?? {}) };
1392
+ }
1393
+ /**
1394
+ * Whether the local base-URL proxy runs: implicit goals (turn header), the conduct receipt and
1395
+ * commit reporting (W1.8) all need it; commit reporting reads the lineage key it records.
1396
+ */
1397
+ export function proxyWanted(o) {
1398
+ return o.turnHeader || o.receipt || o.commitReport;
1399
+ }
1400
+ /**
1401
+ * Whether the proxy captures the gateway's record state: the conduct receipt reads it at exit,
1402
+ * and the fed status line reads its lineage key while the session runs.
1403
+ */
1404
+ export function wantsRecordState(receipt, fed) {
1405
+ return receipt || fed;
1406
+ }
1407
+ /**
1408
+ * Point Claude Code at a ready proxy and, on a fed launch, start the status-line feed on the
1409
+ * proxy's record state (no state file, no feed: the line stays "mnemom").
1410
+ */
1411
+ export function onBaseUrlProxyReady(baseUrl, recordStateFile, childEnv, feed, start = startFeed) {
1412
+ if (!baseUrl)
1413
+ return;
1414
+ childEnv.ANTHROPIC_BASE_URL = baseUrl;
1415
+ // An inherited HTTPS_PROXY must not carry the loopback hop.
1416
+ exemptLoopbackFromProxy(childEnv);
1417
+ if (feed && recordStateFile)
1418
+ start({ ...feed, recordStateFile });
1419
+ }
1420
+ /**
1421
+ * The status-line feed file for a fed launch (and FEED_ENV set on the child), or null: the
1422
+ * segment off, no billing org, or {@link fedStatuslineWanted} false.
1423
+ */
1424
+ export function prepareStatuslineFeed(o) {
1425
+ if (!o.statusline || !o.orgId || !fedStatuslineWanted(o.version, o.goalMode, o.env))
1426
+ return null;
1427
+ const feed = (o.files ?? fedStatuslineFiles)();
1428
+ o.childEnv[FEED_ENV] = feed.feedFile;
1429
+ return feed;
1430
+ }
1431
+ /**
1432
+ * Whether this launch feeds the status line measured model cost and μ (W1.4): prerelease
1433
+ * builds only (stable stays "mnemom", decision 14), and only when the turn proxy runs, since
1434
+ * the proxy is what learns the gateway's lineage key for the session.
1435
+ */
1436
+ export function fedStatuslineWanted(version, goalMode, env) {
1437
+ return isPrerelease(version) && goalMode && turnProxyWanted(env);
1438
+ }
1439
+ /** A private per-session dir holding the feed (launcher → bin). */
1440
+ function fedStatuslineFiles() {
1441
+ const dir = mkdtempSync(join(tmpdir(), "mnemom-statusline."));
1442
+ process.on("exit", () => rmSync(dir, { recursive: true, force: true }));
1443
+ return { feedFile: join(dir, "feed.json") };
1444
+ }
1327
1445
  /**
1328
1446
  * Add 127.0.0.1 and localhost to BOTH NO_PROXY and no_proxy (proxy clients disagree on which one
1329
1447
  * wins, and some read only the lowercase one), keeping whatever each already listed.
@@ -1334,6 +1452,20 @@ export function exemptLoopbackFromProxy(env) {
1334
1452
  env.NO_PROXY = [upper, "127.0.0.1,localhost"].filter(Boolean).join(",");
1335
1453
  env.no_proxy = [lower, "127.0.0.1,localhost"].filter(Boolean).join(",");
1336
1454
  }
1455
+ /**
1456
+ * Print the conduct receipt (experimental `record`) — the exit call site here, and the one a
1457
+ * durable park calls. Never throws: a failure is one warning line, and the caller's exit code
1458
+ * is untouched.
1459
+ */
1460
+ export async function sayConductReceipt(launch, trigger = "exit", receipt = conductReceiptFor) {
1461
+ try {
1462
+ for (const line of await receipt(launch, trigger))
1463
+ say(line);
1464
+ }
1465
+ catch (err) {
1466
+ say(fmt.warn(`mnemom agent: no conduct receipt (${err instanceof Error ? err.message : String(err)})`));
1467
+ }
1468
+ }
1337
1469
  /** Whether a terminal implicit launch runs the turn-origin proxy (MNEMOM_AGENT_TURN_HEADER=0 opts out). */
1338
1470
  export function turnProxyWanted(env) {
1339
1471
  const raw = (env.MNEMOM_AGENT_TURN_HEADER ?? "").trim().toLowerCase();
@@ -1350,6 +1482,9 @@ export function startBaseUrlProxy(door, done, opts = {}) {
1350
1482
  const script = opts.script ?? join(dirname(fileURLToPath(import.meta.url)), "..", "base-url-proxy.mjs");
1351
1483
  // The log carries method / path / status / latency / turn note only (never a header value or body).
1352
1484
  const logDir = mkdtempSync(join(tmpdir(), "mnemom-turn-proxy."));
1485
+ // The receipt's record state (lineage key, record URL: non-secret) lives beside the log and is
1486
+ // removed with it on exit, after the receipt has read it.
1487
+ const recordStatePath = opts.recordState ? join(logDir, "record.json") : undefined;
1353
1488
  const proxy = spawn(opts.node ?? process.execPath, [script], {
1354
1489
  env: {
1355
1490
  ...process.env,
@@ -1357,6 +1492,8 @@ export function startBaseUrlProxy(door, done, opts = {}) {
1357
1492
  MNEMOM_AGENT_BU_CWD: process.cwd(),
1358
1493
  MNEMOM_AGENT_BU_LOG: process.env.MNEMOM_AGENT_TURN_PROXY_LOG || join(logDir, "proxy.log"),
1359
1494
  MNEMOM_AGENT_BU_PARENT_PID: String(process.pid),
1495
+ ...(opts.turnHeader === false ? { MNEMOM_AGENT_TURN_HEADER: "0" } : {}),
1496
+ ...(recordStatePath ? { MNEMOM_AGENT_BU_RECORD_STATE: recordStatePath } : {}),
1360
1497
  },
1361
1498
  stdio: ["ignore", "pipe", "ignore"],
1362
1499
  });
@@ -1375,7 +1512,7 @@ export function startBaseUrlProxy(door, done, opts = {}) {
1375
1512
  if (!url) {
1376
1513
  say(fmt.warn(`mnemom agent: turn-origin proxy unavailable (${why}) — launching straight to the gateway; the gateway reads turn origins from the wire only.`));
1377
1514
  }
1378
- done(url);
1515
+ done(url, url ? recordStatePath : undefined);
1379
1516
  };
1380
1517
  const timeoutMs = opts.readyTimeoutMs ?? 5000;
1381
1518
  const timer = setTimeout(() => finish(null, `no READY within ${timeoutMs}ms`), timeoutMs);
@@ -138,7 +138,7 @@ async function checkApiConnectivity(agentId) {
138
138
  catch (error) {
139
139
  // 404 = agent not registered yet. Branch on effectiveStatus (enforce hook may
140
140
  // rewrite an undocumented 404 → synthetic 500 + spec_deviation.original_status;
141
- // effectiveStatus === status when documented). getAgent → fetchApi → MnemomApiError.
141
+ // effectiveStatus === status when documented). getAgent → readApiError → MnemomApiError.
142
142
  if (error instanceof MnemomApiError && error.effectiveStatus === 404) {
143
143
  return {
144
144
  name: "API",
@@ -43,7 +43,9 @@ export function formatUpdateResult(result, current) {
43
43
  lines.push(` Try manually: ${manual(action.version)}`);
44
44
  }
45
45
  else if (action.kind === "notify") {
46
- lines.push(`mnemom ${action.version} is available on ${result.channel} (you have ${current}).`);
46
+ lines.push(result.movedBack
47
+ ? `mnemom ${current} was withdrawn; ${result.channel} is back on ${action.version}.`
48
+ : `mnemom ${action.version} is available on ${result.channel} (you have ${current}).`);
47
49
  lines.push(` Run: ${canSelfUpdate(result.method ?? "unknown") ? "mnemom update" : manual(action.version)}`);
48
50
  }
49
51
  else {
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * `mnemom validate ...` commands — Safe House Hardening T1-4 (CI gate
3
- * integration). Surface for engineers to check whether staging is safe
3
+ * integration). Surface for engineers to check whether the test cell is safe
4
4
  * to promote to production based on the most recent harness run state.
5
5
  *
6
6
  * mnemom validate safe-house [--against=test] [--max-age=24h]
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * `mnemom validate ...` commands — Safe House Hardening T1-4 (CI gate
3
- * integration). Surface for engineers to check whether staging is safe
3
+ * integration). Surface for engineers to check whether the test cell is safe
4
4
  * to promote to production based on the most recent harness run state.
5
5
  *
6
6
  * mnemom validate safe-house [--against=test] [--max-age=24h]
package/dist/index.js CHANGED
@@ -14,8 +14,11 @@ import { policyInitCommand, policyValidateCommand, policyPublishCommand, policyL
14
14
  import { protectionShowCommand, protectionPublishCommand, protectionValidateCommand, protectionEditCommand, protectionDriftCommand, } from "./commands/protection.js";
15
15
  import { agentsListCommand, agentsClaimCommand, agentsMoveCommand } from "./commands/agents.js";
16
16
  import { agentExportCommand, liveExportDeps } from "./commands/agent-export.js";
17
+ import { agentShareCommand, liveAgentShareDeps } from "./commands/agent-share.js";
18
+ import { agentShareFixtureEnabled, fixtureAgentShareParts, } from "./commands/agent-share-fixture.js";
17
19
  import { agentSessionsCommand, agentShowCommand, liveSessionDeps, } from "./commands/agent-sessions.js";
18
20
  import { agentGoalPinCommand, agentGoalResetCommand, agentGoalShowCommand, liveGoalDeps, } from "./commands/agent-goal.js";
21
+ import { agentFuelCommand, liveFuelDeps } from "./commands/agent-fuel.js";
19
22
  import { orgListCommand, orgShowCommand, orgUseCommand } from "./commands/org.js";
20
23
  import { teamListCommand, teamShowCommand, teamTemplateCommand, teamPreviewComposeCommand, teamAdminGrantCommand, teamAdminRevokeCommand, teamAdminListCommand, teamCoverageCommand, } from "./commands/team.js";
21
24
  import { advisoriesListCommand, advisoriesShowCommand } from "./commands/advisories.js";
@@ -33,11 +36,13 @@ import { agentDoctorCommand } from "./commands/agent-doctor.js";
33
36
  import { agentSetupCommand, parseGovernanceFlag } from "./commands/agent-setup.js";
34
37
  import { agentInviteCommand, runAgentInvocation } from "./commands/agent-invite.js";
35
38
  import { agentFirstRunCommand, offerFirstRunOnLaunch } from "./commands/agent-first-run.js";
39
+ import { showImplicitDefaultNoticeOnce } from "./commands/agent-implicit-notice.js";
40
+ import { isPrerelease } from "./lib/update/semver.js";
36
41
  import { applyConfigToEnv, launcherEntries, loadLauncherConfig, withoutDisabledExperimental, } from "./lib/launcher-config.js";
37
42
  import { loadExperimentalState } from "./lib/cli-config.js";
38
- import { assertEnabled, EXPERIMENTAL_CONTRACT, gateCheck, ignoredSettingLines, reportCliError, } from "./lib/experimental.js";
43
+ import { assertEnabled, availableFeatures, EXPERIMENTAL_CONTRACT, gateCheck, ignoredSettingLines, reportCliError, } from "./lib/experimental.js";
39
44
  import { experimentalDisableCommand, experimentalEnableCommand, experimentalListCommand, runExperimentalMigration, } from "./commands/experimental.js";
40
- import { parseCompactAt, parseContextProfile, resolveImplicit, selectLaunchAgent, splitSessionArg, } from "./lib/agent.js";
45
+ import { parseCompactAt, parseContextProfile, passthroughIsPrint, resolveImplicitSource, selectLaunchAgent, splitSessionArg, } from "./lib/agent.js";
41
46
  import { wrapCommand } from "./commands/wrap.js";
42
47
  import { onboardCommand } from "./commands/onboard.js";
43
48
  import { skillsListCommand, skillsDescribeCommand } from "./commands/skills.js";
@@ -114,11 +119,14 @@ program
114
119
  // ── experimental features (lib/experimental.ts) ── While a feature is off its flags and
115
120
  // commands are hidden from --help (decided once, at load) and using one fails with a
116
121
  // pointer to `mnemom experimental enable <id>` (decided per invocation, in the action).
117
- const experimentalAtLoad = loadExperimentalState().enabled;
122
+ // A prerelease (`next`) build also makes PRERELEASE_AVAILABLE usable (door one's T16).
123
+ const PRERELEASE_BUILD = isPrerelease(CLI_VERSION);
124
+ const loadAvailableFeatures = () => availableFeatures(loadExperimentalState().enabled, PRERELEASE_BUILD);
125
+ const experimentalAtLoad = loadAvailableFeatures();
118
126
  const hiddenUnless = (id) => !experimentalAtLoad.includes(id);
119
127
  /** preAction guard for a gated command: print the guidance and exit 2 while `id` is off. */
120
128
  function requireExperimental(what, id) {
121
- const message = gateCheck(what, id, loadExperimentalState().enabled);
129
+ const message = gateCheck(what, id, loadAvailableFeatures());
122
130
  if (message) {
123
131
  console.error(message);
124
132
  process.exit(2);
@@ -161,7 +169,7 @@ const agentCmd = program
161
169
  "platform cards (strictest wins). See `mnemom protection` for that mode.")
162
170
  .option("--lead-directed", "Seal the standard lead-directed requirement: own-branch push and draft/ready PR creation, " +
163
171
  "from a git worktree, on the lead's direction — not merging. Requires --goal; idempotent.")
164
- .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")))
172
+ .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")))
165
173
  .option("--no-implicit", "Turn implicit goal alignment OFF for this launch (overrides launcher.toml goal_mode = implicit)")
166
174
  .option("--conversation-id <id>", "x-mnemom-conversation-id (default: auto-generated)")
167
175
  .option("--cli <name|path>", "Coding-agent CLI to launch: a name or full path (default: claude)")
@@ -259,6 +267,8 @@ const agentCmd = program
259
267
  }
260
268
  async function launchAgent(scenario) {
261
269
  const experimental = loadExperimentalState().enabled;
270
+ // What a flag, key or command may use: the enabled features, plus implicit on `next`.
271
+ const available = availableFeatures(experimental, PRERELEASE_BUILD);
262
272
  // The one place the flag's raw string becomes `number | null` (see the argParser's
263
273
  // comment for why commander can't carry `null` through parseArg directly). Already
264
274
  // validated by the argParser, so this never throws.
@@ -266,7 +276,7 @@ const agentCmd = program
266
276
  // Sub-verb: `mnemom agent config …` manages persistent settings and never
267
277
  // launches. (A scenario literally named "config" is reserved for this.)
268
278
  if (scenario === "config") {
269
- await agentConfigCommand(passthrough, { enabled: experimental });
279
+ await agentConfigCommand(passthrough, { enabled: available });
270
280
  return;
271
281
  }
272
282
  // Sub-verb: `mnemom agent slack …` posts/receives Slack messages as this
@@ -286,7 +296,7 @@ const agentCmd = program
286
296
  used: opts.contextHandoff === true,
287
297
  },
288
298
  { what: "--implicit", id: "implicit", used: opts.implicit === true },
289
- ], experimental);
299
+ ], available);
290
300
  if (opts.gateway)
291
301
  process.env.MNEMOM_AGENT_GATEWAY = opts.gateway;
292
302
  // Persistent settings (~/.mnemom/launcher.toml) sit UNDER env and flags:
@@ -299,11 +309,11 @@ const agentCmd = program
299
309
  for (const line of ignoredSettingLines({
300
310
  entries: launcherEntries(rawCfg),
301
311
  env: process.env,
302
- enabled: experimental,
312
+ enabled: available,
303
313
  })) {
304
314
  console.error(line);
305
315
  }
306
- const cfg = withoutDisabledExperimental(rawCfg, experimental);
316
+ const cfg = withoutDisabledExperimental(rawCfg, available);
307
317
  applyConfigToEnv(cfg, process.env);
308
318
  // Sub-verb: `mnemom agent doctor` runs a read-only preflight and never
309
319
  // launches. Placed after config load so it reports the effective settings.
@@ -334,14 +344,22 @@ const agentCmd = program
334
344
  const numToStr = (n) => n === undefined ? undefined : String(n);
335
345
  // Implicit mode: --implicit / --no-implicit win; a --goal statement seals
336
346
  // (the standing config default never overrides it); else launcher.toml
337
- // `goal_mode`; else implicit while the `implicit` feature is on, off while it is
338
- // off. Pure + unit-tested in lib/agent.ts.
339
- const implicit = resolveImplicit({
347
+ // `goal_mode`; else implicit while the `implicit` feature is on; else, on a
348
+ // `next` build, implicit unless the launch is headless `-p` (T16); else off.
349
+ // Pure + unit-tested in lib/agent.ts.
350
+ const goalMode = resolveImplicitSource({
340
351
  goal: opts.goal,
341
352
  implicit: opts.implicit,
342
353
  configGoalMode: cfg.goalMode,
343
354
  experimentalOn: experimental.includes("implicit"),
355
+ prerelease: PRERELEASE_BUILD,
356
+ print: passthroughIsPrint(passthrough),
344
357
  });
358
+ const implicit = goalMode.implicit;
359
+ // The T16 default tells the user once, the first time it applies.
360
+ if (implicit && goalMode.source === "prerelease") {
361
+ showImplicitDefaultNoticeOnce({ dryRun: opts.dryRun });
362
+ }
345
363
  // Config guardrails apply only when goal alignment is actually intended (a
346
364
  // --goal is present, or implicit mode — where they ride on the mode header),
347
365
  // so a saved ceiling never forces an unrequested contract (which would
@@ -895,6 +913,21 @@ agentCmd
895
913
  .action(async (idOrName, opts) => {
896
914
  process.exitCode = await agentExportCommand(idOrName, opts, liveExportDeps);
897
915
  });
916
+ // Hidden until MNEMOM_AGENT_SHARE=1 (MNE-9037): with the flag off the command behaves as unknown.
917
+ agentCmd
918
+ .command("share", { hidden: true })
919
+ .description("Build a shareable card from lines of your signed statements and print the share-page link (card in the URL fragment; nothing is posted). Needs MNEMOM_AGENT_SHARE=1")
920
+ .option("--lines <ids>", "Comma-separated line ids to include (skips the picker)")
921
+ .option("--list", "List the lines available to share and exit")
922
+ .option("--json", "Print JSON instead of text")
923
+ .action(async (opts) => {
924
+ // MNEMOM_AGENT_SHARE_FIXTURE=1 (with MNEMOM_AGENT_SHARE=1) swaps in the test-only fixture
925
+ // source/issuer; otherwise the not-yet-connected live deps are used, unchanged.
926
+ const deps = agentShareFixtureEnabled()
927
+ ? { ...liveAgentShareDeps, ...fixtureAgentShareParts }
928
+ : liveAgentShareDeps;
929
+ process.exitCode = await agentShareCommand(opts, deps);
930
+ });
898
931
  agentCmd
899
932
  .command("sessions")
900
933
  .description("List your Mnemom Agent sessions from the local ledger (~/.mnemom/sessions.jsonl), newest first — the last 20 unless --all")
@@ -935,6 +968,29 @@ agentCmd
935
968
  process.exit(1);
936
969
  }
937
970
  });
971
+ // `mnemom agent fuel <file>` — the fuel step (Doors W3.2): one confirmation seals a batch of job
972
+ // contracts through the batch-seal endpoint and commits them to the repo.
973
+ agentCmd
974
+ .command("fuel <file>", { hidden: hiddenUnless("fuel") })
975
+ .description("EXPERIMENTAL (`mnemom experimental enable fuel`): seal a batch of job contracts with one confirmation. " +
976
+ '`file` is a JSON array of contracts (or `{ "contracts": [...] }`; `-` reads stdin); each is sealed, ' +
977
+ "then written to .mnemom/contracts/<sha256>.json and committed")
978
+ .option("--repo <owner/name>", "The repo the jobs run against (default: the GitHub origin remote)")
979
+ .option("--org <org_id>", "The org to seal under (default: your only organization)")
980
+ .option("--yes", "Seal without the confirmation prompt")
981
+ .option("--no-commit", "Seal only; do not write or commit the contract files")
982
+ .option("--json", "Output the batch-seal response, the files and the commit as JSON")
983
+ .option("--key <idempotency-key>", "Replay a batch already sealed with this key (printed when committing its files failed)")
984
+ .hook("preAction", () => requireExperimental("`mnemom agent fuel`", "fuel"))
985
+ .action(async (file, opts) => {
986
+ try {
987
+ await agentFuelCommand(file, opts, liveFuelDeps);
988
+ }
989
+ catch (error) {
990
+ console.error(error instanceof Error ? error.message : String(error));
991
+ process.exitCode = 1;
992
+ }
993
+ });
938
994
  // `mnemom agent goal show|pin|reset` — the implicit goal the gateway inferred for a session
939
995
  // (implicit-goal CLI). The handlers set process.exitCode themselves (1 / 2); the catch is for
940
996
  // the unexpected.
@@ -269,6 +269,8 @@ export declare function supportsOneM(model: string): boolean;
269
269
  export interface CcSettingsView {
270
270
  model?: string;
271
271
  env?: Record<string, string | undefined>;
272
+ /** `attribution.commit`, or false when `attribution` is false (all attribution hidden). */
273
+ commitAttribution?: string | false;
272
274
  }
273
275
  /**
274
276
  * Claude Code's settings files, highest precedence first: `<cwd>/.claude/settings.local.json`,
@@ -359,13 +361,7 @@ export declare function resolveConversationId(raw: string | undefined): string;
359
361
  export declare function isPositiveInt(value: unknown): boolean;
360
362
  /** A strictly-positive finite number (rejects 0, negatives, NaN, Infinity, non-numeric). */
361
363
  export declare function isPositiveNumber(value: unknown): boolean;
362
- /**
363
- * Canonical JSON — object keys sorted recursively, arrays in order, no
364
- * whitespace, `undefined` keys dropped. Byte-identical to the gateway's
365
- * `canonicalJson` (gateway/src/goal-contract.ts), so the base64 we send and the
366
- * sha we print match what the gateway seals.
367
- */
368
- export declare function canonicalJson(value: unknown): string;
364
+ export { canonicalJson } from "./canonical-json.js";
369
365
  /** sha256 hex (first 16 chars) of a string — the operator-eyeball contract hash. */
370
366
  export declare function sha256Hex16(text: string): string;
371
367
  /**
@@ -447,7 +443,8 @@ export declare const COMPACTION_INTERCEPT_KEY = "GATEWAY_COMPACTION_INTERCEPT";
447
443
  /**
448
444
  * The gateway env key that has it measure each request's saving for a single session and put
449
445
  * it in the client-facing usage (`mnemom_savings` in the read `usage.iterations` entry), where
450
- * the status bar reads it from Claude Code's transcript. Default OFF on the gateway; sent `"on"`
446
+ * it lands in Claude Code's transcript. The status bar never shows it (decision 14: never
447
+ * "saved"). Default OFF on the gateway; sent `"on"`
451
448
  * only while the `measured-savings` experimental feature is on and the launched CLI is Claude
452
449
  * Code (`resolveUsageSavings`). Independent of folding, so every profile sends it — `off` too,
453
450
  * where the measured saving is honestly $0.00.
@@ -834,9 +831,9 @@ export declare function resolveCompactionIntercept(opts: {
834
831
  env?: NodeJS.ProcessEnv;
835
832
  }): boolean;
836
833
  /**
837
- * Does the gateway measure this launch's saving (`GATEWAY_USAGE_SAVINGS` in the header, and the
838
- * status bar in measured mode)? Only with the `measured-savings` experimental feature on, and
839
- * only for Claude Code — the one CLI whose status bar reads the measurement. No flag or
834
+ * Does the gateway measure this launch's saving (`GATEWAY_USAGE_SAVINGS` in the header)? Only
835
+ * with the `measured-savings` experimental feature on, and only for Claude Code, whose
836
+ * transcript keeps the measurement. No flag or
840
837
  * launcher.toml key: `mnemom experimental enable measured-savings` is the switch.
841
838
  */
842
839
  export declare function resolveUsageSavings(opts: {
@@ -921,29 +918,47 @@ export declare function buildHandoffMcpConfigJson(p: HandoffMcpParams): string;
921
918
  * per-session statement, so it is always a per-launch `--goal`.
922
919
  */
923
920
  export type GoalModeDefault = "implicit" | "off";
921
+ /**
922
+ * Where a launch's goal mode came from (`resolveImplicitSource`). `prerelease` is door
923
+ * one's T16 default (decisions 12 and 22): implicit by default on a `-next.N` build with
924
+ * nothing configured. It is the only source that shows the one-time notice.
925
+ */
926
+ export type ImplicitSource = "flag" | "goal" | "config" | "experimental" | "prerelease" | "default";
927
+ /** Does a Claude Code passthrough run headless (`-p` / `--print`)? */
928
+ export declare function passthroughIsPrint(passthrough: readonly string[]): boolean;
924
929
  /**
925
930
  * Is this launch implicit-mode? Precedence, highest wins:
926
931
  * 1. an explicit flag — `--implicit` (true) or `--no-implicit` (false);
927
932
  * 2. a `--goal` statement — a sealed contract is a per-launch choice that the
928
- * standing config default must never silently override;
933
+ * standing config default must never silently override, so a launch never
934
+ * runs both modes;
929
935
  * 3. the config default `goal_mode` — `"off"` turns alignment off, `"implicit"`
930
936
  * keeps it implicit;
931
- * 4. with the `implicit` experimental feature ON, IMPLICIT: the gateway infers
932
- * the goal from the user's own messages (turn it off for a launch with
933
- * `--no-implicit`, or standing with `goal_mode = "off"`); with it OFF (the
934
- * default), OFF.
937
+ * 4. with the `implicit` experimental feature ON, IMPLICIT (as before T16);
938
+ * 5. on a prerelease (`next`) build, IMPLICIT for an interactive or Remote Control
939
+ * launch and OFF for a headless `-p` one (OD-13: T16 never covers `-p`);
940
+ * 6. otherwise OFF — stable without the experimental feature behaves as today.
935
941
  * Pure so every arm is unit-tested; the caller passes the resolved value on to
936
942
  * `assembleContract` / `buildGoalModeHeader` (which still refuse `--implicit`
937
943
  * combined with a contract flag — that conflict is EXPLICIT and is reported).
938
944
  */
939
- export declare function resolveImplicit(opts: {
945
+ export declare function resolveImplicitSource(opts: {
940
946
  goal?: string;
941
947
  implicit?: boolean;
942
948
  configGoalMode?: GoalModeDefault;
943
949
  /**
944
950
  * Is the `implicit` experimental feature on? Off ⇒ the default is OFF and
945
951
  * `goal_mode = implicit` is ignored (`--implicit` is refused upstream, before
946
- * this runs). Omitted ⇒ on.
952
+ * this runs), unless this is a prerelease build. Omitted ⇒ on.
947
953
  */
948
954
  experimentalOn?: boolean;
949
- }): boolean;
955
+ /** Is this a prerelease (`-next.N`) CLI build? `isPrerelease(CLI_VERSION)`. Omitted ⇒ no. */
956
+ prerelease?: boolean;
957
+ /** Does the launch run Claude Code headless (`-p`)? `passthroughIsPrint`. */
958
+ print?: boolean;
959
+ }): {
960
+ implicit: boolean;
961
+ source: ImplicitSource;
962
+ };
963
+ /** `resolveImplicitSource(opts).implicit`. */
964
+ export declare function resolveImplicit(opts: Parameters<typeof resolveImplicitSource>[0]): boolean;