@mnemom/mnemom 0.17.0-next.1 → 0.17.0-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.
@@ -5,12 +5,16 @@
5
5
  * If a customer names no agent, `mnemom code` launches under a per-customer
6
6
  * governed identity `code-<handle>`. This module makes that identity REAL in
7
7
  * their Mnemom org: it births the agent through the gateway, claims it (so the
8
- * customer owns it), and publishes a sane default coding posture nudge/nudge:
9
- * - alignment card: autonomy_mode + integrity_mode = nudge,
8
+ * customer owns it), and publishes a sane default coding posture. The CARDS are
9
+ * observe-only (visibility, no active intervention); the goal contract is what
10
+ * nudges (re-anchor on drift). Specifically:
11
+ * - alignment card: autonomy_mode + integrity_mode = observe,
10
12
  * principal.relationship = delegated_authority, coding bounded/forbidden
11
13
  * actions, escalate-on-irreversible;
12
- * - protection card: mode = nudge, thresholds 0.60/0.80/0.95, all four
13
- * screen_surfaces on.
14
+ * - protection card: mode = observe, thresholds 0.60/0.80/0.95, all four
15
+ * screen_surfaces on (screened for observation).
16
+ * - goal-alignment: nudge — sealed per-conversation via the goal contract
17
+ * (--goal …), graded and re-anchored by the gateway.
14
18
  *
15
19
  * All account writes are gated: interactive runs show the plan and confirm;
16
20
  * non-interactive runs provision only with an explicit `--setup`, and otherwise
@@ -5,12 +5,16 @@
5
5
  * If a customer names no agent, `mnemom code` launches under a per-customer
6
6
  * governed identity `code-<handle>`. This module makes that identity REAL in
7
7
  * their Mnemom org: it births the agent through the gateway, claims it (so the
8
- * customer owns it), and publishes a sane default coding posture nudge/nudge:
9
- * - alignment card: autonomy_mode + integrity_mode = nudge,
8
+ * customer owns it), and publishes a sane default coding posture. The CARDS are
9
+ * observe-only (visibility, no active intervention); the goal contract is what
10
+ * nudges (re-anchor on drift). Specifically:
11
+ * - alignment card: autonomy_mode + integrity_mode = observe,
10
12
  * principal.relationship = delegated_authority, coding bounded/forbidden
11
13
  * actions, escalate-on-irreversible;
12
- * - protection card: mode = nudge, thresholds 0.60/0.80/0.95, all four
13
- * screen_surfaces on.
14
+ * - protection card: mode = observe, thresholds 0.60/0.80/0.95, all four
15
+ * screen_surfaces on (screened for observation).
16
+ * - goal-alignment: nudge — sealed per-conversation via the goal contract
17
+ * (--goal …), graded and re-anchored by the gateway.
14
18
  *
15
19
  * All account writes are gated: interactive runs show the plan and confirm;
16
20
  * non-interactive runs provision only with an explicit `--setup`, and otherwise
@@ -83,21 +87,21 @@ export async function ensureGovernedAgent(opts) {
83
87
  say(fmt.section("Set up a governed coding agent"));
84
88
  say(fmt.label(" Agent:", slug));
85
89
  say(fmt.label(" Org:", orgLabel));
86
- say(fmt.label(" Posture:", "nudge / nudge (alignment: autonomy + integrity nudge; protection: all surfaces screened)"));
90
+ say(fmt.label(" Posture:", "observe (alignment + protection observe-only, all surfaces screened); goal contracts nudge"));
87
91
  say(fmt.dim(" Births the agent through the gateway, claims it to your org, and publishes its cards."));
88
92
  if (!(await askYesNo("Provision and claim this agent now?", true))) {
89
93
  say(fmt.dim(" Skipped — launching under the unclaimed identity. Run `mnemom code setup` anytime."));
90
94
  return { slug, provisioned: false };
91
95
  }
92
96
  }
93
- // 5. Birth → claim → publish nudge/nudge cards. (Key never logged.)
97
+ // 5. Birth → claim → publish observe-only cards. (Key never logged.)
94
98
  const gatewayUrl = opts.gatewayHost.replace(/\/+$/, "");
95
99
  const agentId = await birthThroughGateway(gatewayUrl, "anthropic", slug, opts.anthropicKey);
96
100
  const hashProof = deriveHashProof(opts.anthropicKey, slug);
97
101
  await claimAgent(agentId, { hashProof, orgId: org?.org_id });
98
102
  await putAlignmentCard(agentId, JSON.stringify(buildCodeAlignmentCard(agentId)), "application/json");
99
103
  await putProtectionCard(agentId, JSON.stringify(buildCodeProtectionCard(agentId)), "application/json");
100
- say(fmt.success(`mnemom code: provisioned + claimed '${slug}' (${agentId}) in ${orgLabel} — nudge/nudge posture published.`));
104
+ say(fmt.success(`mnemom code: provisioned + claimed '${slug}' (${agentId}) in ${orgLabel} — observe posture published (goal contracts nudge).`));
101
105
  // 6. Remember the slug so future launches skip straight to usable.
102
106
  persistAgentSlug(slug);
103
107
  return { slug, provisioned: true };
@@ -125,7 +129,7 @@ export async function codeSetupCommand(opts = {}) {
125
129
  }
126
130
  function warnUnclaimed(slug) {
127
131
  say(fmt.warn(`mnemom code: agent '${slug}' is not set up in your org — launching with an UNCLAIMED identity (governance is advisory/fail-open).`));
128
- say(fmt.dim(" Provision it (creates + claims the agent, publishes nudge/nudge cards): mnemom code setup"));
132
+ say(fmt.dim(" Provision it (creates + claims the agent, publishes observe cards): mnemom code setup"));
129
133
  }
130
134
  function persistAgentSlug(slug) {
131
135
  try {
@@ -273,13 +277,13 @@ export async function ensureSessionAndBalance(opts) {
273
277
  await assertMuBalance(orgId, orgLabel);
274
278
  return { orgId, orgLabel };
275
279
  }
276
- // ── The default coding posture (nudge/nudge) ────────────────────────────────
280
+ // ── The default coding posture (cards observe-only; goal contracts nudge) ────
277
281
  function buildCodeAlignmentCard(agentId) {
278
282
  return {
279
283
  card_version: "unified/2026-04-26",
280
284
  agent_id: agentId,
281
- autonomy_mode: "nudge",
282
- integrity_mode: "nudge",
285
+ autonomy_mode: "observe",
286
+ integrity_mode: "observe",
283
287
  principal: { type: "agent", identifier: agentId, relationship: "delegated_authority" },
284
288
  values: {
285
289
  declared: ["transparency", "safety", "honesty", "user_control", "harm_prevention"],
@@ -316,7 +320,7 @@ function buildCodeProtectionCard(agentId) {
316
320
  return {
317
321
  card_version: "protection/2026-04-26",
318
322
  agent_id: agentId,
319
- mode: "nudge",
323
+ mode: "observe",
320
324
  thresholds: { warn: 0.6, quarantine: 0.8, block: 0.95 },
321
325
  screen_surfaces: {
322
326
  incoming: true,
@@ -100,7 +100,7 @@ export interface ResolvedCli {
100
100
  * a non-claude executable is launched through it with a warning.
101
101
  */
102
102
  export declare function resolveCliBin(cliOpt: string | undefined, env: NodeJS.ProcessEnv): ResolvedCli;
103
- export declare function commandExists(cmd: string): boolean;
103
+ export declare function promptForCliPath(cliOpt: string | undefined, env: NodeJS.ProcessEnv, origErr: Error | undefined): Promise<ResolvedCli>;
104
104
  /**
105
105
  * Does the resolved coding-agent CLI advertise the `--remote-control` startup
106
106
  * flag (Claude Code >= 2.1.269)? Determined by scanning its `--help`. Best-effort:
@@ -31,6 +31,7 @@ import { tmpdir, homedir } from "node:os";
31
31
  import { basename, join, dirname } from "node:path";
32
32
  import { fileURLToPath } from "node:url";
33
33
  import { MNEMOM_DIR } from "../lib/config.js";
34
+ import { configKeySpec, loadCodeConfig, writeCodeConfig } from "../lib/code-config.js";
34
35
  import { fmt } from "../lib/format.js";
35
36
  import { askInput, askYesNo, isInteractive } from "../lib/prompt.js";
36
37
  import { defaultAgentSlug, ensureGovernedAgent, ensureSessionAndBalance, MuInsufficientError, } from "./code-setup.js";
@@ -72,14 +73,25 @@ export async function codeCommand(scenario, options = {}) {
72
73
  const cellNote = isProdGateway(env) ? "us-2/prod" : `custom (${gatewayHost})`;
73
74
  const yolo = options.yolo !== false; // default ON
74
75
  const contextHint = options.contextHint !== false; // default ON
75
- // ── which CLI executable (name or full path) ──
76
- const cli = resolveCliBin(options.cli, env);
77
- // ── default launch shape: prefer Remote-Control-in-terminal (the good default
78
- // — one session steerable from this terminal AND claude.ai/the app) when the
79
- // machine can do it; otherwise fall back to terminal-only with a clear notice.
80
- // An explicit choice (flag / env / config launch=) is always respected. ──
81
- if (!shapeExplicit && shape === "terminal") {
82
- const rc = checkRcCapability(cli);
76
+ // ── which CLI executable (name or full path) ── resolve GRACEFULLY: if it's not
77
+ // found we don't crash with a technical error — we ask for the path at launch.
78
+ let cli;
79
+ let cliError;
80
+ try {
81
+ cli = resolveCliBin(options.cli, env);
82
+ }
83
+ catch (err) {
84
+ cliError = err instanceof Error ? err : new Error(String(err));
85
+ }
86
+ // Default launch shape: prefer Remote-Control-in-terminal (the good default —
87
+ // one session steerable from this terminal AND claude.ai/the app) when the
88
+ // machine can do it; otherwise terminal-only with a clear notice. An explicit
89
+ // choice (flag / env / config launch=) is always respected. Needs a resolved
90
+ // CLI to test RC capability, so it's applied once the CLI is known.
91
+ const applyAutoShape = (resolved) => {
92
+ if (shapeExplicit || shape !== "terminal")
93
+ return;
94
+ const rc = checkRcCapability(resolved);
83
95
  if (rc.ok) {
84
96
  shape = "remote-control";
85
97
  }
@@ -87,11 +99,24 @@ export async function codeCommand(scenario, options = {}) {
87
99
  say(`mnemom code: launching terminal-only (Remote Control unavailable: ${rc.missing.join("; ")}).`);
88
100
  say("mnemom code: once those are in place, `mnemom code <scenario> --remote-control` (or `config set launch remote-control`) gives you a session steerable from claude.ai and the Claude app too.");
89
101
  }
90
- }
102
+ };
103
+ if (cli)
104
+ applyAutoShape(cli);
91
105
  if (options.dryRun) {
92
- printPlan({ scenario, shape, agent, conversationId, door, cellNote, envelope, yolo, cli });
106
+ const planCli = cli ?? {
107
+ bin: "(not found — pass --cli <path> or `mnemom code config set cli <path>`)",
108
+ name: basename((options.cli || env.MNEMOM_CODE_CLI || "claude").trim()),
109
+ };
110
+ printPlan({ scenario, shape, agent, conversationId, door, cellNote, envelope, yolo, cli: planCli });
93
111
  return;
94
112
  }
113
+ // Real launch: make sure we actually have a CLI to run. If we couldn't find it,
114
+ // ask for the path (a good experience beats a hard technical error). A
115
+ // non-interactive session still gets the clear, actionable error.
116
+ if (!cli) {
117
+ cli = await promptForCliPath(options.cli, env, cliError);
118
+ applyAutoShape(cli);
119
+ }
95
120
  // ── resolve the Anthropic key (zero 1Password; never logged) ──
96
121
  const anthropicKey = await resolveAnthropicKey(env, {
97
122
  allowPrompt: true,
@@ -295,7 +320,52 @@ export function resolveCliBin(cliOpt, env) {
295
320
  (name === "claude" ? " and in the fnm/nvm/asdf node dirs" : "") +
296
321
  ".\n Pass --cli <name|/full/path> or set MNEMOM_CODE_CLI.");
297
322
  }
298
- export function commandExists(cmd) {
323
+ export /**
324
+ * Recover when the coding-agent CLI can't be found — a good experience beats a
325
+ * hard technical error. In an interactive session, tell the customer we couldn't
326
+ * find it and let them paste the full path (offering to save it as their default
327
+ * so they never retype it). A non-interactive session re-throws the clear,
328
+ * actionable error rather than hanging on a prompt.
329
+ */ async function promptForCliPath(cliOpt, env, origErr) {
330
+ const requested = (cliOpt || env.MNEMOM_CODE_CLI || "claude").trim();
331
+ if (!isInteractive()) {
332
+ throw origErr ?? new Error(`mnemom code: coding-agent CLI '${requested}' not found.`);
333
+ }
334
+ say(`mnemom code: couldn't find the '${requested}' CLI on your PATH.`);
335
+ if (requested === "claude") {
336
+ say(" If Claude Code is installed, paste its full path (find it with: which claude).");
337
+ say(" Not installed yet? Get it at https://claude.com/claude-code, then re-run.");
338
+ }
339
+ else {
340
+ say(" Paste its full path, or install it and re-run.");
341
+ }
342
+ for (let attempt = 0; attempt < 3; attempt++) {
343
+ const entered = (await askInput("Path to your coding-agent CLI (Enter to cancel):")).trim();
344
+ if (!entered) {
345
+ throw new Error("mnemom code: no coding-agent CLI available.\n" +
346
+ " Install Claude Code (https://claude.com/claude-code), or pass --cli <path> " +
347
+ "(or save it: mnemom code config set cli <path>).");
348
+ }
349
+ if (!existsSync(entered)) {
350
+ say(` '${entered}' doesn't exist — try again.`);
351
+ continue;
352
+ }
353
+ if (await askYesNo("Save this as your default CLI so you don't retype it?", true)) {
354
+ try {
355
+ const cfg = loadCodeConfig();
356
+ configKeySpec("cli").set(cfg, entered);
357
+ writeCodeConfig(cfg);
358
+ say(" Saved — future launches will use it.");
359
+ }
360
+ catch {
361
+ // best effort — the launch proceeds with the entered path either way
362
+ }
363
+ }
364
+ return { bin: entered, name: basename(entered) };
365
+ }
366
+ throw new Error("mnemom code: couldn't resolve a coding-agent CLI path — pass --cli <path>.");
367
+ }
368
+ function commandExists(cmd) {
299
369
  const res = spawnSync("sh", ["-c", `command -v "$1" >/dev/null 2>&1`, "sh", cmd], {
300
370
  stdio: "ignore",
301
371
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mnemom/mnemom",
3
- "version": "0.17.0-next.1",
3
+ "version": "0.17.0-next.2",
4
4
  "description": "Transparent AI agent tracing",
5
5
  "type": "module",
6
6
  "bin": {