@mnemom/mnemom 0.17.20 → 0.17.21
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.
- package/dist/commands/agent-sessions.d.ts +15 -5
- package/dist/commands/agent-sessions.js +29 -5
- package/dist/commands/agent.d.ts +1 -1
- package/dist/commands/agent.js +21 -3
- package/dist/commands/validate.d.ts +1 -1
- package/dist/commands/validate.js +7 -5
- package/dist/index.d.ts +1 -0
- package/dist/index.js +5 -2
- package/dist/lib/api.d.ts +41 -0
- package/dist/lib/claude-plugin.d.ts +7 -8
- package/dist/lib/claude-plugin.js +8 -9
- package/dist/lib/config.d.ts +13 -2
- package/dist/lib/config.js +22 -6
- package/dist/lib/first-run/governed.js +7 -4
- package/dist/lib/retired-env-guard.d.ts +7 -0
- package/dist/lib/retired-env-guard.js +46 -0
- package/dist/rc-proxy.mjs +83 -13
- package/dist/rc-state.mjs +257 -0
- package/package.json +5 -1
- package/plugins/.claude-plugin/marketplace.json +20 -0
- package/plugins/mnemom/.claude-plugin/plugin.json +47 -0
- package/plugins/mnemom/.mcp.json +12 -0
- package/plugins/mnemom/hooks/hooks.json +3 -0
- package/plugins/mnemom/hooks/register.tsx +1476 -0
- package/plugins/mnemom/skills/about-mnemom/SKILL.md +88 -0
- package/plugins/mnemom/types/index.d.ts +142 -0
|
@@ -110,11 +110,13 @@ export declare function formatTrailEntry(e: AgentSessionVerdictTrailEntry): stri
|
|
|
110
110
|
*/
|
|
111
111
|
export declare function verdictTrailKey(e: Pick<AgentSessionVerdictTrailEntry, "request_id" | "turn">): string;
|
|
112
112
|
/**
|
|
113
|
-
* Best-effort "has this session ended" check for `--follow`'s exit condition.
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
* neither
|
|
113
|
+
* Best-effort "has this session ended" check for `--follow`'s exit condition. MNE-8234 wired the
|
|
114
|
+
* seal lifecycle through on the wire (`remote.seal.state`), so that is read first and is
|
|
115
|
+
* authoritative: `"expired"` there means the prior seal lapsed and ends the watch. The older
|
|
116
|
+
* top-level `state` and `remote.sealed.state` fallbacks predate that field actually landing on
|
|
117
|
+
* the response — neither has ever been observed non-null on any real server — and are kept
|
|
118
|
+
* defensively in case an even older/non-conformant server ever carries one of them; this never
|
|
119
|
+
* throws, returning null when nothing recognized is present.
|
|
118
120
|
*/
|
|
119
121
|
export declare function sessionEndedState(remote: AgentSessionResponse): "expired" | "ended" | null;
|
|
120
122
|
/** Default and floor for `--follow`'s poll interval. */
|
|
@@ -152,6 +154,14 @@ export interface ShowJson {
|
|
|
152
154
|
guardrails: AgentSessionResponse["guardrails"];
|
|
153
155
|
/** R10 (MNE-8200): oldest first (the gateway's own wire order), unlike the human view. */
|
|
154
156
|
verdict_trail: AgentSessionResponse["verdict_trail"];
|
|
157
|
+
/**
|
|
158
|
+
* MNE-8234: the seal's lifecycle (state incl. `expired`, version, mode, hashes). Absent on
|
|
159
|
+
* an older server; `null` when the API reports no seal.
|
|
160
|
+
*/
|
|
161
|
+
seal: AgentSessionResponse["seal"];
|
|
162
|
+
/** MNE-8234 (MNE-8166 R4): judging counts under the current seal. */
|
|
163
|
+
unjudged_turns: AgentSessionResponse["unjudged_turns"];
|
|
164
|
+
memoised_turns: AgentSessionResponse["memoised_turns"];
|
|
155
165
|
} | {
|
|
156
166
|
source: "gateway";
|
|
157
167
|
available: false;
|
|
@@ -190,14 +190,21 @@ export function verdictTrailKey(e) {
|
|
|
190
190
|
return `${e.request_id ?? ""}#${e.turn}`;
|
|
191
191
|
}
|
|
192
192
|
/**
|
|
193
|
-
* Best-effort "has this session ended" check for `--follow`'s exit condition.
|
|
194
|
-
*
|
|
195
|
-
*
|
|
196
|
-
*
|
|
197
|
-
* neither
|
|
193
|
+
* Best-effort "has this session ended" check for `--follow`'s exit condition. MNE-8234 wired the
|
|
194
|
+
* seal lifecycle through on the wire (`remote.seal.state`), so that is read first and is
|
|
195
|
+
* authoritative: `"expired"` there means the prior seal lapsed and ends the watch. The older
|
|
196
|
+
* top-level `state` and `remote.sealed.state` fallbacks predate that field actually landing on
|
|
197
|
+
* the response — neither has ever been observed non-null on any real server — and are kept
|
|
198
|
+
* defensively in case an even older/non-conformant server ever carries one of them; this never
|
|
199
|
+
* throws, returning null when nothing recognized is present.
|
|
198
200
|
*/
|
|
199
201
|
export function sessionEndedState(remote) {
|
|
200
202
|
const as = (v) => v === "expired" || v === "ended" ? v : null;
|
|
203
|
+
if (remote.seal) {
|
|
204
|
+
const sealState = as(remote.seal.state);
|
|
205
|
+
if (sealState)
|
|
206
|
+
return sealState;
|
|
207
|
+
}
|
|
201
208
|
const top = as(remote.state);
|
|
202
209
|
if (top)
|
|
203
210
|
return top;
|
|
@@ -349,6 +356,12 @@ export function buildShowView(local, remote, reason) {
|
|
|
349
356
|
else {
|
|
350
357
|
L.push(fmt.dim(" no sealed contract on the gateway for this conversation"));
|
|
351
358
|
}
|
|
359
|
+
// MNE-8234: the seal's lifecycle — independent of `sealed` above (which carries the contract
|
|
360
|
+
// body, not whether the seal itself is still live or has lapsed). Absent on an older server.
|
|
361
|
+
if (remote.seal) {
|
|
362
|
+
const modeBit = remote.seal.state === "live" ? ` · mode ${remote.seal.mode}` : "";
|
|
363
|
+
L.push(fmt.label("seal: ", `${remote.seal.state.toUpperCase()} (v${remote.seal.version})${modeBit}`));
|
|
364
|
+
}
|
|
352
365
|
if (remote.verdict) {
|
|
353
366
|
const v = remote.verdict;
|
|
354
367
|
L.push(fmt.label("verdict: ", `${v.verdict.toUpperCase()} at turn ${v.turn} — ${v.reason}`));
|
|
@@ -385,6 +398,14 @@ export function buildShowView(local, remote, reason) {
|
|
|
385
398
|
if (remote.turn !== null) {
|
|
386
399
|
L.push(fmt.label("turn: ", String(remote.turn)));
|
|
387
400
|
}
|
|
401
|
+
// MNE-8234 (MNE-8166 R4): judging counts under the current seal. `null`/absent ⇒ no judge
|
|
402
|
+
// ledger counts (older server, or nothing judged under this seal yet) — no line then.
|
|
403
|
+
if (remote.unjudged_turns !== null && remote.unjudged_turns !== undefined) {
|
|
404
|
+
const memoisedBit = remote.memoised_turns !== null && remote.memoised_turns !== undefined
|
|
405
|
+
? ` · memoised ${remote.memoised_turns}`
|
|
406
|
+
: "";
|
|
407
|
+
L.push(fmt.label("judging: ", `unjudged ${remote.unjudged_turns}${memoisedBit}`));
|
|
408
|
+
}
|
|
388
409
|
return L;
|
|
389
410
|
}
|
|
390
411
|
export function buildShowJson(local, remote, reason) {
|
|
@@ -400,6 +421,9 @@ export function buildShowJson(local, remote, reason) {
|
|
|
400
421
|
turn: remote.turn,
|
|
401
422
|
guardrails: remote.guardrails,
|
|
402
423
|
verdict_trail: remote.verdict_trail,
|
|
424
|
+
seal: remote.seal,
|
|
425
|
+
unjudged_turns: remote.unjudged_turns,
|
|
426
|
+
memoised_turns: remote.memoised_turns,
|
|
403
427
|
}
|
|
404
428
|
: { source: "gateway", available: false, reason: reason ?? "unknown" },
|
|
405
429
|
};
|
package/dist/commands/agent.d.ts
CHANGED
|
@@ -127,7 +127,7 @@ export interface AgentOptions {
|
|
|
127
127
|
/**
|
|
128
128
|
* Load the "Mnemom for Claude Code" plugin (`plugins/mnemom`: the Mnemom band,
|
|
129
129
|
* `/mnemom`, the gateway guard, Mnemom attribution) into the launched Claude Code.
|
|
130
|
-
* Default OFF (
|
|
130
|
+
* Default OFF (the plugin lands dark); `--plugin` ⇒ true; `--no-plugin` ⇒ false; undefined ⇒
|
|
131
131
|
* MNEMOM_AGENT_PLUGIN decides (lib/claude-plugin).
|
|
132
132
|
*/
|
|
133
133
|
plugin?: boolean;
|
package/dist/commands/agent.js
CHANGED
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
* an argv value.
|
|
27
27
|
*/
|
|
28
28
|
import { spawn, spawnSync } from "node:child_process";
|
|
29
|
-
import { existsSync, mkdtempSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync, renameSync, } from "node:fs";
|
|
29
|
+
import { chmodSync, existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync, renameSync, } from "node:fs";
|
|
30
30
|
import { randomUUID } from "node:crypto";
|
|
31
31
|
import { tmpdir, homedir, hostname } from "node:os";
|
|
32
32
|
import { basename, join, dirname } from "node:path";
|
|
@@ -539,7 +539,7 @@ export async function agentCommand(scenarioArg, options = {}) {
|
|
|
539
539
|
plugin: launchPlugin.line,
|
|
540
540
|
});
|
|
541
541
|
if (pluginWanted && cli.name === "claude" && !bundledPluginDir) {
|
|
542
|
-
say(fmt.warn("mnemom agent: the Mnemom plugin folder is not in this install
|
|
542
|
+
say(fmt.warn("mnemom agent: the Mnemom plugin folder is not in this install — launching without the Mnemom band and /mnemom."));
|
|
543
543
|
}
|
|
544
544
|
// Non-secret launch facts for the plugin's band and `/mnemom status`. Built here, applied to the
|
|
545
545
|
// child env by each launch shape AFTER it has built that env (lib/claude-plugin, PLUGIN_ENV).
|
|
@@ -1303,7 +1303,8 @@ function launchRemoteControl(ctx) {
|
|
|
1303
1303
|
}
|
|
1304
1304
|
}
|
|
1305
1305
|
const proxyScript = resolveProxyScript();
|
|
1306
|
-
const rcDir = mkdtempSync(join(
|
|
1306
|
+
const rcDir = mkdtempSync(join(rcStateRoot(), "mnemom-code-rc."));
|
|
1307
|
+
writeFileSync(join(rcDir, "owner.pid"), `${process.pid}\n`, { mode: 0o600 }); // rc-state.mjs sweep
|
|
1307
1308
|
const proxyLog = process.env.MNEMOM_AGENT_RC_PROXY_LOG || join(rcDir, "proxy.log");
|
|
1308
1309
|
const proxy = spawn("node", [proxyScript], {
|
|
1309
1310
|
env: {
|
|
@@ -1519,6 +1520,23 @@ function warnServerDrops(options, yolo) {
|
|
|
1519
1520
|
say("mnemom agent: yolo (--dangerously-skip-permissions) is not applied under --server; set permissions from the driven sessions.");
|
|
1520
1521
|
}
|
|
1521
1522
|
}
|
|
1523
|
+
/**
|
|
1524
|
+
* ~/.mnemom/rc, created 0700 — the same root src/rc-state.mjs gives the proxy's CA dir (tsc emits
|
|
1525
|
+
* no .mjs, so this mirrors its rcStateRoot() rather than importing it). Never the OS temp dir,
|
|
1526
|
+
* whose cleaners delete files behind a long session; that is only the fallback when home is not
|
|
1527
|
+
* writable. Read at call time, not from MNEMOM_DIR: HOME can move after import (tests do).
|
|
1528
|
+
*/
|
|
1529
|
+
function rcStateRoot() {
|
|
1530
|
+
const root = join(homedir(), ".mnemom", "rc");
|
|
1531
|
+
try {
|
|
1532
|
+
mkdirSync(root, { recursive: true, mode: 0o700 });
|
|
1533
|
+
chmodSync(root, 0o700);
|
|
1534
|
+
return root;
|
|
1535
|
+
}
|
|
1536
|
+
catch {
|
|
1537
|
+
return tmpdir();
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1522
1540
|
function resolveProxyScript() {
|
|
1523
1541
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
1524
1542
|
const cand = join(here, "..", "rc-proxy.mjs");
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* integration). Surface for engineers to check whether staging is safe
|
|
4
4
|
* to promote to production based on the most recent harness run state.
|
|
5
5
|
*
|
|
6
|
-
* mnemom validate safe-house [--against=
|
|
6
|
+
* mnemom validate safe-house [--against=test] [--max-age=24h]
|
|
7
7
|
* [--lane=full|fast] [--strict] [--json]
|
|
8
8
|
*
|
|
9
9
|
* Wraps `GET /v1/admin/safe-house/harness-state` (mnemom-staff only).
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* integration). Surface for engineers to check whether staging is safe
|
|
4
4
|
* to promote to production based on the most recent harness run state.
|
|
5
5
|
*
|
|
6
|
-
* mnemom validate safe-house [--against=
|
|
6
|
+
* mnemom validate safe-house [--against=test] [--max-age=24h]
|
|
7
7
|
* [--lane=full|fast] [--strict] [--json]
|
|
8
8
|
*
|
|
9
9
|
* Wraps `GET /v1/admin/safe-house/harness-state` (mnemom-staff only).
|
|
@@ -76,10 +76,12 @@ export async function validateSafeHouseCommand(opts) {
|
|
|
76
76
|
await requireAuth();
|
|
77
77
|
// `--against` is informational only today (validation is always against
|
|
78
78
|
// the api root the user is authenticated to). Keep the flag for forward-
|
|
79
|
-
// compat: production gains a separate harness corpus per T1.
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
79
|
+
// compat: production gains a separate harness corpus per T1. The non-prod
|
|
80
|
+
// value is `test` (the Azure test cell): `staging` named the retired ring
|
|
81
|
+
// (MNE-7968) and is rejected so a stale script fails loud, not quietly.
|
|
82
|
+
const against = opts.against ?? "test";
|
|
83
|
+
if (against !== "test" && against !== "production") {
|
|
84
|
+
console.log(fmt.error(`--against must be 'test' or 'production' (got '${against}')`) + "\n");
|
|
83
85
|
process.exit(1);
|
|
84
86
|
return;
|
|
85
87
|
}
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
// MUST stay the first import: it exits cleanly on MNEMOM_ENV=staging before any
|
|
3
|
+
// module below resolves the (retired) staging URLs at import time (MNE-7968).
|
|
4
|
+
import "./lib/retired-env-guard.js";
|
|
2
5
|
import { program, InvalidArgumentError, Option } from "commander";
|
|
3
6
|
import { isEntrypoint } from "./lib/entrypoint.js";
|
|
4
7
|
import { CLI_VERSION } from "./version.js";
|
|
@@ -141,7 +144,7 @@ function printAgentHelp(verb) {
|
|
|
141
144
|
const agentCmd = program
|
|
142
145
|
.command("agent [session] [passthrough...]")
|
|
143
146
|
.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. " +
|
|
144
|
-
"Sub-verbs: `setup` provisions your governed agent; `doctor` preflights prerequisites; `config …` manages persistent settings (~/.mnemom/launcher.toml);
|
|
147
|
+
"Sub-verbs: `setup` provisions your governed agent; `doctor` preflights prerequisites; `config …` manages persistent settings (~/.mnemom/launcher.toml); `--invite <token>` redeems a Mnemom Agent invitation (the one-liner your invitation email shows; `invite <token>` is its alias); `list` / `claim` / `move` manage the agents in your orgs; `sessions` lists your past launches and `show [ref]` merges one with its live goal state; `help [verb]` prints help and never launches.")
|
|
145
148
|
.option("--goal <statement>", "Contract goal statement (required if any contract flag is given)")
|
|
146
149
|
.option("--requirement <text>", "Contract requirement (repeatable)", collectRepeatable, [])
|
|
147
150
|
.option("--allow <glob>", "Allowed path/glob (repeatable)", collectRepeatable, [])
|
|
@@ -1656,7 +1659,7 @@ const validateCmd = program
|
|
|
1656
1659
|
validateCmd
|
|
1657
1660
|
.command("safe-house")
|
|
1658
1661
|
.description("Render Safe House harness state per lane (full/fast) — promotion-readiness signal")
|
|
1659
|
-
.option("--against <env>", "Target environment ('
|
|
1662
|
+
.option("--against <env>", "Target environment ('test' or 'production'; informational)", "test")
|
|
1660
1663
|
.option("--max-age <duration>", "Reject runs older than this (e.g. 30m, 2h, 24h, 1d)", "24h")
|
|
1661
1664
|
.option("--lane <lane>", "Show only one lane ('full', 'fast', or 'both')", "both")
|
|
1662
1665
|
.option("--strict", "Exit non-zero if any selected lane is stale or red")
|
package/dist/lib/api.d.ts
CHANGED
|
@@ -1305,6 +1305,29 @@ export interface AgentSessionVerdictTrailEntry {
|
|
|
1305
1305
|
fence_breach: boolean | null;
|
|
1306
1306
|
at: string;
|
|
1307
1307
|
}
|
|
1308
|
+
/**
|
|
1309
|
+
* The seal's lifecycle (MNE-8149 R2, MNE-8199 D9/R9, MNE-8234/mnemom-api#3541). `live`: a sealed
|
|
1310
|
+
* contract currently governs — `mode` says whether a crossed guardrail only nudges or enforces
|
|
1311
|
+
* (refuses the next request). `expired`: the prior seal lapsed (24h idle) and the next engaged
|
|
1312
|
+
* request will seal afresh; no `mode` because nothing is being enforced.
|
|
1313
|
+
*/
|
|
1314
|
+
export interface AgentSessionSealLive {
|
|
1315
|
+
state: "live";
|
|
1316
|
+
version: number;
|
|
1317
|
+
hash: string;
|
|
1318
|
+
sealed_at: string;
|
|
1319
|
+
reason: "first" | "after_expiry" | null;
|
|
1320
|
+
expired_hash: string | null;
|
|
1321
|
+
updated_from_hash: string | null;
|
|
1322
|
+
mode: "nudge" | "enforce";
|
|
1323
|
+
}
|
|
1324
|
+
export interface AgentSessionSealExpired {
|
|
1325
|
+
state: "expired";
|
|
1326
|
+
version: number;
|
|
1327
|
+
hash: string;
|
|
1328
|
+
sealed_at: string;
|
|
1329
|
+
}
|
|
1330
|
+
export type AgentSessionSeal = AgentSessionSealLive | AgentSessionSealExpired;
|
|
1308
1331
|
/** `GET /v1/agent/sessions/{conversationId}` 200 body. Nulls mean "nothing stored" — absence is data. */
|
|
1309
1332
|
export interface AgentSessionResponse {
|
|
1310
1333
|
conversation_id: string;
|
|
@@ -1319,6 +1342,24 @@ export interface AgentSessionResponse {
|
|
|
1319
1342
|
guardrails: AgentSessionGuardrails | null;
|
|
1320
1343
|
/** Absent on an older server (back-compat) — never shown then, never an empty-array assumption. */
|
|
1321
1344
|
verdict_trail?: AgentSessionVerdictTrailEntry[];
|
|
1345
|
+
/**
|
|
1346
|
+
* MNE-8234: the seal's lifecycle, incl. `expired`. Absent on an older server (back-compat —
|
|
1347
|
+
* never shown then). `null` means the API itself reports no seal: either none was ever made for
|
|
1348
|
+
* this conversation, or the gateway sent a seal payload out of contract and the API dropped it
|
|
1349
|
+
* rather than relay it half-trusted — the two are indistinguishable from this field alone.
|
|
1350
|
+
*/
|
|
1351
|
+
seal?: AgentSessionSeal | null;
|
|
1352
|
+
/**
|
|
1353
|
+
* MNE-8166 R4 / MNE-8234: assistant turns under the current seal with no verdict (a cut-off/
|
|
1354
|
+
* aborted response, a judge that failed or never ran, a non-streamed final turn). Absent on an
|
|
1355
|
+
* older server (back-compat). `null` when no judge ledger counts under the current seal.
|
|
1356
|
+
*/
|
|
1357
|
+
unjudged_turns?: number | null;
|
|
1358
|
+
/**
|
|
1359
|
+
* MNE-8166 R4 / MNE-8234: turns the judge memo graded (aligned by inheritance, no verdict
|
|
1360
|
+
* stored). Absent on an older server (back-compat). `null` alongside `unjudged_turns`.
|
|
1361
|
+
*/
|
|
1362
|
+
memoised_turns?: number | null;
|
|
1322
1363
|
}
|
|
1323
1364
|
/**
|
|
1324
1365
|
* The live goal state of one `mnemom agent` session, read through mnemom-api
|
|
@@ -14,13 +14,12 @@
|
|
|
14
14
|
* Only Claude Code proper (`cli.name === "claude"`) understands `--plugin-dir`; every other
|
|
15
15
|
* adapter launches without the plugin.
|
|
16
16
|
*
|
|
17
|
-
* OFF BY DEFAULT
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* back into `files`, and `pluginEnabled`'s default to true. It never blocks the launch.
|
|
17
|
+
* OFF BY DEFAULT. The folder ships in the npm tarball (`package.json` `files`, minus its tests
|
|
18
|
+
* and checkout-only files) but no launch loads it unless asked: `--plugin` or
|
|
19
|
+
* MNEMOM_AGENT_PLUGIN=1|true|yes|on opts in (`--no-plugin` or 0|false|off|no wins over the env),
|
|
20
|
+
* and the flags stay out of `--help`. An install missing the folder says so and launches without
|
|
21
|
+
* it. Turning it on for everyone is one edit: `pluginEnabled`'s default to true. It never blocks
|
|
22
|
+
* the launch.
|
|
24
23
|
*/
|
|
25
24
|
/** The plugin's `name` in its plugin.json, and its folder under `cli/plugins/`. */
|
|
26
25
|
export declare const CLAUDE_PLUGIN_NAME = "mnemom";
|
|
@@ -68,7 +67,7 @@ export declare const PLUGIN_ENV: {
|
|
|
68
67
|
export type PluginEnvName = (typeof PLUGIN_ENV)[keyof typeof PLUGIN_ENV];
|
|
69
68
|
/**
|
|
70
69
|
* Whether this launch loads the plugin: `--plugin` / `--no-plugin` (an explicit boolean) wins,
|
|
71
|
-
* then MNEMOM_AGENT_PLUGIN (1/true/yes/on ⇒ on), else OFF: the plugin
|
|
70
|
+
* then MNEMOM_AGENT_PLUGIN (1/true/yes/on ⇒ on), else OFF: the plugin lands dark.
|
|
72
71
|
*/
|
|
73
72
|
export declare function pluginEnabled(optionPlugin: boolean | undefined, env: NodeJS.ProcessEnv): boolean;
|
|
74
73
|
/**
|
|
@@ -14,13 +14,12 @@
|
|
|
14
14
|
* Only Claude Code proper (`cli.name === "claude"`) understands `--plugin-dir`; every other
|
|
15
15
|
* adapter launches without the plugin.
|
|
16
16
|
*
|
|
17
|
-
* OFF BY DEFAULT
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* back into `files`, and `pluginEnabled`'s default to true. It never blocks the launch.
|
|
17
|
+
* OFF BY DEFAULT. The folder ships in the npm tarball (`package.json` `files`, minus its tests
|
|
18
|
+
* and checkout-only files) but no launch loads it unless asked: `--plugin` or
|
|
19
|
+
* MNEMOM_AGENT_PLUGIN=1|true|yes|on opts in (`--no-plugin` or 0|false|off|no wins over the env),
|
|
20
|
+
* and the flags stay out of `--help`. An install missing the folder says so and launches without
|
|
21
|
+
* it. Turning it on for everyone is one edit: `pluginEnabled`'s default to true. It never blocks
|
|
22
|
+
* the launch.
|
|
24
23
|
*/
|
|
25
24
|
import { existsSync } from "node:fs";
|
|
26
25
|
import { dirname, join, delimiter } from "node:path";
|
|
@@ -71,7 +70,7 @@ export const PLUGIN_ENV = {
|
|
|
71
70
|
const TRUTHY = new Set(["1", "true", "yes", "on"]);
|
|
72
71
|
/**
|
|
73
72
|
* Whether this launch loads the plugin: `--plugin` / `--no-plugin` (an explicit boolean) wins,
|
|
74
|
-
* then MNEMOM_AGENT_PLUGIN (1/true/yes/on ⇒ on), else OFF: the plugin
|
|
73
|
+
* then MNEMOM_AGENT_PLUGIN (1/true/yes/on ⇒ on), else OFF: the plugin lands dark.
|
|
75
74
|
*/
|
|
76
75
|
export function pluginEnabled(optionPlugin, env) {
|
|
77
76
|
if (optionPlugin !== undefined)
|
|
@@ -144,6 +143,6 @@ export function pluginPlanLine(pluginDir, enabled) {
|
|
|
144
143
|
if (!enabled)
|
|
145
144
|
return "off";
|
|
146
145
|
if (!pluginDir)
|
|
147
|
-
return "off (the plugin folder is not in this install
|
|
146
|
+
return "off (the plugin folder is not in this install)";
|
|
148
147
|
return `${CLAUDE_PLUGIN_NAME} (${pluginDir})`;
|
|
149
148
|
}
|
package/dist/lib/config.d.ts
CHANGED
|
@@ -7,10 +7,21 @@
|
|
|
7
7
|
*/
|
|
8
8
|
/** Base directory for mnemom CLI state (auth tokens, caches). */
|
|
9
9
|
export declare const MNEMOM_DIR: string;
|
|
10
|
-
export type Environment = "production" | "
|
|
10
|
+
export type Environment = "production" | "local";
|
|
11
|
+
/**
|
|
12
|
+
* Thrown for `MNEMOM_ENV=staging`: the value used to be accepted, and the ring
|
|
13
|
+
* it named is gone (MNE-7968). Failing loud is deliberate — the two silent
|
|
14
|
+
* alternatives are both wrong: resolving to the dead staging hostnames makes
|
|
15
|
+
* every call fail with a DNS/connection error that does not say why, and
|
|
16
|
+
* falling through to production would send a caller who asked for a non-prod
|
|
17
|
+
* ring at real customer data.
|
|
18
|
+
*/
|
|
19
|
+
export declare class RetiredEnvironmentError extends Error {
|
|
20
|
+
constructor();
|
|
21
|
+
}
|
|
11
22
|
/**
|
|
12
23
|
* Resolve the active environment from MNEMOM_ENV.
|
|
13
|
-
* Defaults to production.
|
|
24
|
+
* Defaults to production. `staging` throws (see RetiredEnvironmentError).
|
|
14
25
|
*/
|
|
15
26
|
export declare function getEnvironment(): Environment;
|
|
16
27
|
export type CellRing = "us-1";
|
package/dist/lib/config.js
CHANGED
|
@@ -11,27 +11,43 @@ import * as os from "node:os";
|
|
|
11
11
|
export const MNEMOM_DIR = path.join(os.homedir(), ".mnemom");
|
|
12
12
|
const API_URLS = {
|
|
13
13
|
production: "https://api.mnemom.ai",
|
|
14
|
-
staging: "https://api-staging.mnemom.ai",
|
|
15
14
|
local: "http://localhost:8787",
|
|
16
15
|
};
|
|
17
16
|
const GATEWAY_URLS = {
|
|
18
17
|
production: "https://gateway.mnemom.ai",
|
|
19
|
-
staging: "https://gateway-staging.mnemom.ai",
|
|
20
18
|
local: "http://localhost:8787",
|
|
21
19
|
};
|
|
22
20
|
const WEBSITE_URLS = {
|
|
23
21
|
production: "https://www.mnemom.ai",
|
|
24
|
-
staging: "https://staging.mnemom.ai",
|
|
25
22
|
local: "http://localhost:5173",
|
|
26
23
|
};
|
|
24
|
+
/**
|
|
25
|
+
* Thrown for `MNEMOM_ENV=staging`: the value used to be accepted, and the ring
|
|
26
|
+
* it named is gone (MNE-7968). Failing loud is deliberate — the two silent
|
|
27
|
+
* alternatives are both wrong: resolving to the dead staging hostnames makes
|
|
28
|
+
* every call fail with a DNS/connection error that does not say why, and
|
|
29
|
+
* falling through to production would send a caller who asked for a non-prod
|
|
30
|
+
* ring at real customer data.
|
|
31
|
+
*/
|
|
32
|
+
export class RetiredEnvironmentError extends Error {
|
|
33
|
+
constructor() {
|
|
34
|
+
super("MNEMOM_ENV=staging is no longer supported: the staging ring was retired (MNE-7968) " +
|
|
35
|
+
"and nothing serves api-staging / gateway-staging.mnemom.ai. " +
|
|
36
|
+
"Use MNEMOM_RING=us-1 for the preview cell, MNEMOM_ENV=local for a local stack, " +
|
|
37
|
+
"or set MNEMOM_API_URL / MNEMOM_GATEWAY_URL / MNEMOM_WEBSITE_URL to target another cell.");
|
|
38
|
+
this.name = "RetiredEnvironmentError";
|
|
39
|
+
}
|
|
40
|
+
}
|
|
27
41
|
/**
|
|
28
42
|
* Resolve the active environment from MNEMOM_ENV.
|
|
29
|
-
* Defaults to production.
|
|
43
|
+
* Defaults to production. `staging` throws (see RetiredEnvironmentError).
|
|
30
44
|
*/
|
|
31
45
|
export function getEnvironment() {
|
|
32
46
|
const env = process.env.MNEMOM_ENV;
|
|
33
|
-
if (env === "
|
|
47
|
+
if (env === "local")
|
|
34
48
|
return env;
|
|
49
|
+
if (env === "staging")
|
|
50
|
+
throw new RetiredEnvironmentError();
|
|
35
51
|
return "production";
|
|
36
52
|
}
|
|
37
53
|
const RING_URLS = {
|
|
@@ -61,7 +77,7 @@ export function getRing() {
|
|
|
61
77
|
const env = process.env.MNEMOM_RING?.trim();
|
|
62
78
|
return env && isCellRing(env) ? env : undefined;
|
|
63
79
|
}
|
|
64
|
-
// Process-level base overrides. MNEMOM_ENV only selects prod/
|
|
80
|
+
// Process-level base overrides. MNEMOM_ENV only selects prod/local;
|
|
65
81
|
// to target an arbitrary ring (preview/us-1, test) a command can set an
|
|
66
82
|
// explicit override (e.g. `try-me --api <ring>`) or the operator can export
|
|
67
83
|
// MNEMOM_API_URL / MNEMOM_GATEWAY_URL / MNEMOM_WEBSITE_URL. Crucially these
|
|
@@ -123,11 +123,14 @@ export class Governance {
|
|
|
123
123
|
if (!r.cc_session_id && t !== null)
|
|
124
124
|
this.windows.push(["registry_window", t]);
|
|
125
125
|
}
|
|
126
|
-
// Remote Control proxy logs:
|
|
126
|
+
// Remote Control proxy logs: ~/.mnemom/rc/mnemom-code-rc.*/proxy.log (the launcher's dir),
|
|
127
|
+
// $TMPDIR/mnemom-code-rc.*/proxy.log (where older CLIs put it), ~/.mnemom/*rc-proxy*.log
|
|
127
128
|
const logs = [];
|
|
128
|
-
for (const
|
|
129
|
-
|
|
130
|
-
|
|
129
|
+
for (const base of [join(mnemomDir, "rc"), tmp]) {
|
|
130
|
+
for (const d of safeList(base)) {
|
|
131
|
+
if (d.startsWith("mnemom-code-rc."))
|
|
132
|
+
logs.push(join(base, d, "proxy.log"));
|
|
133
|
+
}
|
|
131
134
|
}
|
|
132
135
|
for (const f of safeList(mnemomDir)) {
|
|
133
136
|
if (f.includes("rc-proxy") && f.endsWith(".log"))
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare const RETIRED_ENV_EXIT_CODE = 2;
|
|
2
|
+
/**
|
|
3
|
+
* The startup error line when any CLI base URL would resolve via the retired
|
|
4
|
+
* `MNEMOM_ENV=staging`, else undefined. Exported for the unit test; the module
|
|
5
|
+
* body below is what the CLI relies on.
|
|
6
|
+
*/
|
|
7
|
+
export declare function retiredEnvMessage(): string | undefined;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Startup guard for `MNEMOM_ENV=staging` (MNE-7968).
|
|
3
|
+
*
|
|
4
|
+
* `getEnvironment()` throws `RetiredEnvironmentError` for that value, and three
|
|
5
|
+
* modules resolve the API base at MODULE-EVAL time (`lib/api.ts`,
|
|
6
|
+
* `lib/webhooks-api.ts`, `commands/listen.ts` — see the note at the top of
|
|
7
|
+
* `__tests__/cli-api-integration.test.ts`). Those modules are statically
|
|
8
|
+
* imported by `index.ts`, so without this guard the throw surfaces during ESM
|
|
9
|
+
* linking — before `runEntrypoint()`, before commander, before any try/catch —
|
|
10
|
+
* and every invocation, `mnemom --help` included, dies with a raw Node stack
|
|
11
|
+
* trace. This module is the FIRST import of `index.ts`, so it evaluates before
|
|
12
|
+
* any of them: it prints the same message on one line and exits 2 (the CLI's
|
|
13
|
+
* usage-error code).
|
|
14
|
+
*
|
|
15
|
+
* The guard decides by running config.ts's own resolvers rather than reading
|
|
16
|
+
* MNEMOM_ENV, so it follows their precedence exactly (raw MNEMOM_*_URL > ring >
|
|
17
|
+
* MNEMOM_ENV): when a valid MNEMOM_RING, or all three MNEMOM_*_URL overrides,
|
|
18
|
+
* answer every base URL, MNEMOM_ENV is never consulted and a stale
|
|
19
|
+
* `MNEMOM_ENV=staging` beside them is harmless. Importing this module has no
|
|
20
|
+
* effect unless some base URL would resolve via the retired environment.
|
|
21
|
+
*/
|
|
22
|
+
import { getApiUrl, getGatewayUrl, getWebsiteUrl, RetiredEnvironmentError } from "./config.js";
|
|
23
|
+
export const RETIRED_ENV_EXIT_CODE = 2;
|
|
24
|
+
/**
|
|
25
|
+
* The startup error line when any CLI base URL would resolve via the retired
|
|
26
|
+
* `MNEMOM_ENV=staging`, else undefined. Exported for the unit test; the module
|
|
27
|
+
* body below is what the CLI relies on.
|
|
28
|
+
*/
|
|
29
|
+
export function retiredEnvMessage() {
|
|
30
|
+
try {
|
|
31
|
+
getApiUrl();
|
|
32
|
+
getGatewayUrl();
|
|
33
|
+
getWebsiteUrl();
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
catch (err) {
|
|
37
|
+
if (err instanceof RetiredEnvironmentError)
|
|
38
|
+
return `error: ${err.message}`;
|
|
39
|
+
throw err;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
const message = retiredEnvMessage();
|
|
43
|
+
if (message !== undefined) {
|
|
44
|
+
console.error(message);
|
|
45
|
+
process.exit(RETIRED_ENV_EXIT_CODE);
|
|
46
|
+
}
|