@mnemom/mnemom 0.17.11-next.0 → 0.17.11
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-doctor.d.ts +19 -1
- package/dist/commands/agent-doctor.js +110 -25
- package/dist/commands/agent-invite.d.ts +7 -0
- package/dist/commands/agent-invite.js +20 -1
- package/dist/commands/agent-setup.d.ts +50 -1
- package/dist/commands/agent-setup.js +133 -19
- package/dist/commands/agent.d.ts +11 -0
- package/dist/commands/agent.js +32 -17
- package/dist/commands/experimental.d.ts +2 -0
- package/dist/commands/experimental.js +12 -0
- package/dist/index.js +13 -5
- package/dist/lib/api.d.ts +21 -0
- package/dist/lib/api.js +16 -0
- package/dist/lib/experimental.d.ts +5 -0
- package/dist/lib/experimental.js +4 -2
- package/dist/lib/launcher-config.js +1 -1
- package/dist/lib/version-gate.js +1 -2
- package/package.json +1 -1
|
@@ -9,11 +9,14 @@
|
|
|
9
9
|
* Checks: settings file, coding-agent CLI, Anthropic key source, gateway health
|
|
10
10
|
* (200 + the x-mnemom-verdict header that proves the governance pipeline is
|
|
11
11
|
* live), Remote-Control capability (node + openssl + the --remote-control flag),
|
|
12
|
-
* claude.ai login (informational — Remote Control needs it),
|
|
12
|
+
* claude.ai login (informational — Remote Control needs it), the agent's governance
|
|
13
|
+
* modes (off by default for new agents), and CLI auto-update status.
|
|
13
14
|
*/
|
|
14
15
|
type Mark = "ok" | "warn" | "miss" | "info";
|
|
15
16
|
export interface DoctorOptions {
|
|
16
17
|
cli?: string;
|
|
18
|
+
/** The agent slug a launch would use (--agent / launcher.toml); default agent-<handle>. */
|
|
19
|
+
agent?: string;
|
|
17
20
|
}
|
|
18
21
|
/** Run the preflight and print the checklist. Sets process.exitCode on failure. */
|
|
19
22
|
export declare function agentDoctorCommand(options?: DoctorOptions): Promise<void>;
|
|
@@ -21,6 +24,8 @@ export interface CodeAccessProbe {
|
|
|
21
24
|
mark: Mark;
|
|
22
25
|
text: string;
|
|
23
26
|
hint?: string;
|
|
27
|
+
/** May this account use Remote Control? Only a determined `remote_control: true` says yes. */
|
|
28
|
+
remoteControl: boolean;
|
|
24
29
|
}
|
|
25
30
|
/**
|
|
26
31
|
* The invited-cohort gate, as the doctor reports it. Signed out → info (we can't
|
|
@@ -29,4 +34,17 @@ export interface CodeAccessProbe {
|
|
|
29
34
|
* anything inconclusive → info, never a MISS (the launch preamble fails open too).
|
|
30
35
|
*/
|
|
31
36
|
export declare function probeCodeAccess(): Promise<CodeAccessProbe>;
|
|
37
|
+
export interface GovernanceProbe {
|
|
38
|
+
mark: Mark;
|
|
39
|
+
text: string;
|
|
40
|
+
hint?: string;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* The governance line: the composed (every layer, strictest wins) integrity and
|
|
44
|
+
* Safe House modes of the agent a launch would use. Signed out, no such agent,
|
|
45
|
+
* or an unreadable card → info with the off-by-default note, never a MISS
|
|
46
|
+
* (governance never blocks a launch). On → info naming the per-turn cost; the
|
|
47
|
+
* agent's own card says off but a stricter layer keeps it on → warn.
|
|
48
|
+
*/
|
|
49
|
+
export declare function probeGovernance(agent: string | undefined): Promise<GovernanceProbe>;
|
|
32
50
|
export {};
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
* Checks: settings file, coding-agent CLI, Anthropic key source, gateway health
|
|
10
10
|
* (200 + the x-mnemom-verdict header that proves the governance pipeline is
|
|
11
11
|
* live), Remote-Control capability (node + openssl + the --remote-control flag),
|
|
12
|
-
* claude.ai login (informational — Remote Control needs it),
|
|
12
|
+
* claude.ai login (informational — Remote Control needs it), the agent's governance
|
|
13
|
+
* modes (off by default for new agents), and CLI auto-update status.
|
|
13
14
|
*/
|
|
14
15
|
import { spawnSync } from "node:child_process";
|
|
15
16
|
import { existsSync } from "node:fs";
|
|
@@ -19,11 +20,12 @@ import { resolveAgentAuth } from "../lib/subscription-login.js";
|
|
|
19
20
|
import { isProdGateway, resolveAnthropicDoor, resolveGatewayHost, singleCellError, } from "../lib/agent.js";
|
|
20
21
|
import { getApiUrl, getGatewayUrl } from "../lib/config.js";
|
|
21
22
|
import { LAUNCHER_CONFIG_PATH } from "../lib/launcher-config.js";
|
|
22
|
-
import { getCodeEntitlement, MnemomApiError } from "../lib/api.js";
|
|
23
|
+
import { getAgentByName, getCardSources, getCodeEntitlement, MnemomApiError } from "../lib/api.js";
|
|
23
24
|
import { isLoggedIn } from "../lib/auth.js";
|
|
24
25
|
import { getActiveOrg } from "../lib/cli-config.js";
|
|
25
|
-
import { isCodeNotEnabled, sourceLabel } from "./agent-invite.js";
|
|
26
|
+
import { isCodeNotEnabled, REQUEST_ACCESS_URL, sanitizeTerminalText, sourceLabel, withRequestAccess, } from "./agent-invite.js";
|
|
26
27
|
import { probeUpdateStatus } from "../lib/update/doctor.js";
|
|
28
|
+
import { describeModes, GOVERNANCE_OPT_IN_HINT, resolveDefaultSlug } from "./agent-setup.js";
|
|
27
29
|
const MARK = { ok: "[ ok ]", warn: "[warn]", miss: "[MISS]", info: "[info]" };
|
|
28
30
|
const out = (line = "") => console.log(line);
|
|
29
31
|
/** Run the preflight and print the checklist. Sets process.exitCode on failure. */
|
|
@@ -94,27 +96,35 @@ export async function agentDoctorCommand(options = {}) {
|
|
|
94
96
|
else {
|
|
95
97
|
add("warn", `gateway: ${host} [${cell}] unreachable (${health.detail})`, "check the host (MNEMOM_AGENT_GATEWAY / `mnemom agent config set gateway <url>`) and your network");
|
|
96
98
|
}
|
|
97
|
-
// ──
|
|
98
|
-
|
|
99
|
+
// ── Mnemom Agent access (invited cohort gate) ── probed here, before the Remote Control
|
|
100
|
+
// lines, because Remote Control is internal-only: an account the api does not entitle to it
|
|
101
|
+
// (every customer) never sees those lines at all.
|
|
102
|
+
const gate = await probeCodeAccess();
|
|
103
|
+
// ── Remote Control capability (entitled accounts only) ──
|
|
104
|
+
if (cli && gate.remoteControl) {
|
|
99
105
|
const rc = checkRcCapability(cli);
|
|
100
106
|
if (rc.ok)
|
|
101
107
|
add("ok", "Remote Control: available (node + openssl + --remote-control)");
|
|
102
108
|
else
|
|
103
109
|
add("warn", `Remote Control: unavailable — ${rc.missing.join("; ")}`, "launches fall back to terminal-only until these are in place");
|
|
104
|
-
// ── claude.ai login (informational — RC needs it) ──
|
|
105
|
-
if (cli.name === "claude") {
|
|
106
|
-
const login = claudeLoggedIn(cli.bin);
|
|
107
|
-
if (login === true)
|
|
108
|
-
add("ok", "claude.ai login: logged in");
|
|
109
|
-
else if (login === false)
|
|
110
|
-
add("info", "claude.ai login: not logged in", "Remote Control needs a claude.ai login; the terminal-only shape does not");
|
|
111
|
-
else
|
|
112
|
-
add("info", "claude.ai login: could not determine");
|
|
113
|
-
}
|
|
114
110
|
}
|
|
115
|
-
// ──
|
|
116
|
-
|
|
111
|
+
// ── claude.ai login (informational) ── Remote Control needs it, and so does auth =
|
|
112
|
+
// subscription; a terminal launch on an Anthropic key does not, so nobody else sees it.
|
|
113
|
+
if (cli && cli.name === "claude" && (gate.remoteControl || auth === "subscription")) {
|
|
114
|
+
const login = claudeLoggedIn(cli.bin);
|
|
115
|
+
if (login === true)
|
|
116
|
+
add("ok", "claude.ai login: logged in");
|
|
117
|
+
else if (login === false)
|
|
118
|
+
add("info", "claude.ai login: not logged in", auth === "subscription"
|
|
119
|
+
? "auth = subscription launches Claude Code on your claude.ai login — run `claude` and /login"
|
|
120
|
+
: "Remote Control needs a claude.ai login; the terminal-only shape does not");
|
|
121
|
+
else
|
|
122
|
+
add("info", "claude.ai login: could not determine");
|
|
123
|
+
}
|
|
117
124
|
add(gate.mark, gate.text, gate.hint);
|
|
125
|
+
// ── governance (off by default for new agents) ──
|
|
126
|
+
const gov = await probeGovernance(options.agent);
|
|
127
|
+
add(gov.mark, gov.text, gov.hint);
|
|
118
128
|
// ── CLI auto-update ──
|
|
119
129
|
const upd = await probeUpdateStatus();
|
|
120
130
|
add(upd.mark, upd.text, upd.hint);
|
|
@@ -187,7 +197,8 @@ export async function probeCodeAccess() {
|
|
|
187
197
|
return {
|
|
188
198
|
mark: "info",
|
|
189
199
|
text: "Mnemom Agent access: sign in to check",
|
|
190
|
-
hint:
|
|
200
|
+
hint: `run \`mnemom login\`; @mnemom.ai accounts are enabled by default, others need an invitation (mnemom agent --invite <token>) — request one at ${REQUEST_ACCESS_URL}`,
|
|
201
|
+
remoteControl: false,
|
|
191
202
|
};
|
|
192
203
|
}
|
|
193
204
|
const org = getActiveOrg();
|
|
@@ -197,9 +208,7 @@ export async function probeCodeAccess() {
|
|
|
197
208
|
return {
|
|
198
209
|
mark: "ok",
|
|
199
210
|
text: `Mnemom Agent access: enabled for ${orgLabel} (${sourceLabel(ent.source)})`,
|
|
200
|
-
|
|
201
|
-
? undefined
|
|
202
|
-
: "Remote Control (steer from claude.ai / the Claude app) is an internal feature — your sessions run terminal-only",
|
|
211
|
+
remoteControl: ent.remote_control === true,
|
|
203
212
|
};
|
|
204
213
|
}
|
|
205
214
|
catch (err) {
|
|
@@ -208,12 +217,88 @@ export async function probeCodeAccess() {
|
|
|
208
217
|
return {
|
|
209
218
|
mark: "miss",
|
|
210
219
|
text: `Mnemom Agent access: not enabled for ${orgLabel}`,
|
|
211
|
-
hint: typeof hint === "string" && hint
|
|
212
|
-
? hint
|
|
213
|
-
: "redeem an invitation: mnemom agent --invite <token>",
|
|
220
|
+
hint: withRequestAccess(typeof hint === "string" && hint
|
|
221
|
+
? sanitizeTerminalText(hint)
|
|
222
|
+
: "redeem an invitation: mnemom agent --invite <token>"),
|
|
223
|
+
remoteControl: false,
|
|
214
224
|
};
|
|
215
225
|
}
|
|
216
226
|
const detail = err instanceof MnemomApiError || err instanceof Error ? err.message : String(err);
|
|
217
|
-
return {
|
|
227
|
+
return {
|
|
228
|
+
mark: "info",
|
|
229
|
+
text: `Mnemom Agent access: could not determine (${detail})`,
|
|
230
|
+
remoteControl: false,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
const OPT_IN_HINT = GOVERNANCE_OPT_IN_HINT.trim();
|
|
235
|
+
/**
|
|
236
|
+
* The governance line: the composed (every layer, strictest wins) integrity and
|
|
237
|
+
* Safe House modes of the agent a launch would use. Signed out, no such agent,
|
|
238
|
+
* or an unreadable card → info with the off-by-default note, never a MISS
|
|
239
|
+
* (governance never blocks a launch). On → info naming the per-turn cost; the
|
|
240
|
+
* agent's own card says off but a stricter layer keeps it on → warn.
|
|
241
|
+
*/
|
|
242
|
+
export async function probeGovernance(agent) {
|
|
243
|
+
const offByDefault = {
|
|
244
|
+
mark: "info",
|
|
245
|
+
text: "governance: off by default for new agents (AIP integrity analysis, Safe House screening)",
|
|
246
|
+
hint: `${OPT_IN_HINT} Agents set up before this default keep their cards.`,
|
|
247
|
+
};
|
|
248
|
+
if (!(await isLoggedIn()))
|
|
249
|
+
return offByDefault;
|
|
250
|
+
try {
|
|
251
|
+
const slug = agent?.trim() || (await resolveDefaultSlug());
|
|
252
|
+
const found = await getAgentByName(slug);
|
|
253
|
+
if (!found)
|
|
254
|
+
return offByDefault;
|
|
255
|
+
const [align, prot] = await Promise.all([
|
|
256
|
+
getCardSources("alignment", found.id),
|
|
257
|
+
getCardSources("protection", found.id),
|
|
258
|
+
]);
|
|
259
|
+
const read = (s, layer, field) => {
|
|
260
|
+
const v = s?.[layer]?.card_json?.[field];
|
|
261
|
+
return typeof v === "string" && v ? v : undefined;
|
|
262
|
+
};
|
|
263
|
+
// A composed card without the field (or none composed yet) is unknown, not
|
|
264
|
+
// off: the data plane falls back to its own default (observe for integrity),
|
|
265
|
+
// so the doctor never claims governance is off when it can't see it.
|
|
266
|
+
const composed = {
|
|
267
|
+
integrity: read(align, "composed", "integrity_mode") ?? "unknown",
|
|
268
|
+
protection: read(prot, "composed", "mode") ?? "unknown",
|
|
269
|
+
};
|
|
270
|
+
const own = {
|
|
271
|
+
integrity: read(align, "agent", "integrity_mode"),
|
|
272
|
+
protection: read(prot, "agent", "mode"),
|
|
273
|
+
};
|
|
274
|
+
const text = `governance: ${slug} — ${describeModes(composed)}`;
|
|
275
|
+
const stricter = (mine, eff) => mine === "off" && eff !== "off" && eff !== "unknown";
|
|
276
|
+
const overridden = stricter(own.integrity, composed.integrity) || stricter(own.protection, composed.protection);
|
|
277
|
+
if (overridden) {
|
|
278
|
+
return {
|
|
279
|
+
mark: "warn",
|
|
280
|
+
text,
|
|
281
|
+
hint: "the agent's own card says off, but a stricter org, team or platform policy keeps it on — it still runs and is billed",
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
if (composed.integrity === "off" && composed.protection === "off") {
|
|
285
|
+
return { mark: "ok", text, hint: OPT_IN_HINT };
|
|
286
|
+
}
|
|
287
|
+
if (composed.integrity === "unknown" || composed.protection === "unknown") {
|
|
288
|
+
return {
|
|
289
|
+
mark: "info",
|
|
290
|
+
text,
|
|
291
|
+
hint: "no composed card yet for at least one feature — it reads once the agent's cards compose (re-run `mnemom agent doctor` in a minute)",
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
return {
|
|
295
|
+
mark: "info",
|
|
296
|
+
text,
|
|
297
|
+
hint: "each feature that is on is a model call per turn, billed at cost on top of the agent fee — `mnemom agent setup --no-governance` turns it off",
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
catch (err) {
|
|
301
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
302
|
+
return { ...offByDefault, text: `governance: could not read the agent's cards (${detail})` };
|
|
218
303
|
}
|
|
219
304
|
}
|
|
@@ -29,6 +29,13 @@ export declare class CodeNotEnabledError extends Error {
|
|
|
29
29
|
/** `pinv_` + base64url — the product-invite token shape the api mints. */
|
|
30
30
|
export declare const INVITE_TOKEN_RE: RegExp;
|
|
31
31
|
export declare function looksLikeInviteToken(value: unknown): value is string;
|
|
32
|
+
/**
|
|
33
|
+
* Where someone without an invitation (or a Mnemom contact) asks for access. Every
|
|
34
|
+
* locked-out message ends with it, so an uninvited customer is never at a dead end.
|
|
35
|
+
*/
|
|
36
|
+
export declare const REQUEST_ACCESS_URL = "https://www.mnemom.ai/contact";
|
|
37
|
+
/** `hint`, plus the request-access pointer unless the hint already carries the URL. */
|
|
38
|
+
export declare function withRequestAccess(hint: string): string;
|
|
32
39
|
/** Drop control characters (ANSI escapes included) from an api-supplied string before
|
|
33
40
|
* it reaches the terminal — defense in depth; the value is first-party but printed raw. */
|
|
34
41
|
export declare function sanitizeTerminalText(s: string): string;
|
|
@@ -39,6 +39,25 @@ export function looksLikeInviteToken(value) {
|
|
|
39
39
|
return typeof value === "string" && INVITE_TOKEN_RE.test(value.trim());
|
|
40
40
|
}
|
|
41
41
|
const FALLBACK_HINT = "Ask your Mnemom contact for a Mnemom Agent invitation, then run: mnemom agent --invite <token>";
|
|
42
|
+
/**
|
|
43
|
+
* Where someone without an invitation (or a Mnemom contact) asks for access. Every
|
|
44
|
+
* locked-out message ends with it, so an uninvited customer is never at a dead end.
|
|
45
|
+
*/
|
|
46
|
+
export const REQUEST_ACCESS_URL = "https://www.mnemom.ai/contact";
|
|
47
|
+
/** Whether `hint` already names the request-access page, as a whole word (trailing
|
|
48
|
+
* punctuation ignored) — a token match, not a substring one. */
|
|
49
|
+
function mentionsRequestAccess(hint) {
|
|
50
|
+
return hint.split(/\s+/).some((w) => w.replace(/[.,;:!?)]+$/, "") === REQUEST_ACCESS_URL);
|
|
51
|
+
}
|
|
52
|
+
/** `hint`, plus the request-access pointer unless the hint already carries the URL. */
|
|
53
|
+
export function withRequestAccess(hint) {
|
|
54
|
+
if (mentionsRequestAccess(hint))
|
|
55
|
+
return hint;
|
|
56
|
+
let end = hint.length;
|
|
57
|
+
while (end > 0 && (hint[end - 1] === "." || hint[end - 1].trim() === ""))
|
|
58
|
+
end--;
|
|
59
|
+
return `${hint.slice(0, end)}. No invitation yet? Request access at ${REQUEST_ACCESS_URL}`;
|
|
60
|
+
}
|
|
42
61
|
/** Drop control characters (ANSI escapes included) from an api-supplied string before
|
|
43
62
|
* it reaches the terminal — defense in depth; the value is first-party but printed raw. */
|
|
44
63
|
export function sanitizeTerminalText(s) {
|
|
@@ -76,7 +95,7 @@ function printNotEnabled(orgLabel, hint) {
|
|
|
76
95
|
say();
|
|
77
96
|
say(" mnemom agent --invite <token>");
|
|
78
97
|
say();
|
|
79
|
-
say(fmt.dim(` ${hint}`));
|
|
98
|
+
say(fmt.dim(` ${withRequestAccess(hint)}`));
|
|
80
99
|
}
|
|
81
100
|
/**
|
|
82
101
|
* Enforce the Mnemom Agent gate for the org the launch targets. Blocks (prints
|
|
@@ -16,6 +16,9 @@
|
|
|
16
16
|
* screen_surfaces declared, ready for when it is turned on.
|
|
17
17
|
* - goal-alignment: nudge — sealed per-conversation via the goal contract
|
|
18
18
|
* (--goal …), graded and re-anchored by the gateway.
|
|
19
|
+
* `--governance [integrity,safe-house]` opts a setup in (observe-only, billed at
|
|
20
|
+
* cost on top of the agent fee); a refresh of an already-claimed agent keeps the
|
|
21
|
+
* modes its own cards carry unless a --governance / --no-governance flag says otherwise.
|
|
19
22
|
*
|
|
20
23
|
* A launch never runs on an unclaimed agent (operator 2026-10-03): an unclaimed agent lands in
|
|
21
24
|
* the sandbox org and bills nobody. ensureClaimedServedAgent asks the gateway which agent it
|
|
@@ -26,6 +29,43 @@
|
|
|
26
29
|
* reinventing it.
|
|
27
30
|
*/
|
|
28
31
|
import { type CodeEntitlement } from "../lib/api.js";
|
|
32
|
+
/** A card's governance mode as the CLI writes it: off, or observe-only. */
|
|
33
|
+
export type GovernanceMode = "off" | "observe";
|
|
34
|
+
/**
|
|
35
|
+
* The governance features a setup turns on. Each is a model call per turn that
|
|
36
|
+
* the customer pays for at cost, so both are off unless asked for.
|
|
37
|
+
*/
|
|
38
|
+
export interface GovernanceOptIn {
|
|
39
|
+
/** AIP integrity analysis (alignment card integrity_mode). */
|
|
40
|
+
integrity: boolean;
|
|
41
|
+
/** Safe House screening (protection card mode). */
|
|
42
|
+
safeHouse: boolean;
|
|
43
|
+
}
|
|
44
|
+
/** The feature names `--governance <list>` accepts (plus `all` / `none`). */
|
|
45
|
+
export declare const GOVERNANCE_FEATURES: readonly ["integrity", "safe-house"];
|
|
46
|
+
/**
|
|
47
|
+
* Parse the `--governance [features]` / `--no-governance` flag. undefined = not
|
|
48
|
+
* given (new agents get none; a refresh keeps the agent's current modes),
|
|
49
|
+
* true = every feature, false = none, a string = a comma-separated list of
|
|
50
|
+
* `integrity`, `safe-house`, `all` or `none`. Throws on an unknown name.
|
|
51
|
+
*/
|
|
52
|
+
export declare function parseGovernanceFlag(value: boolean | string | undefined): GovernanceOptIn | undefined;
|
|
53
|
+
/**
|
|
54
|
+
* The modes a setup writes into the two cards: off/observe from the flag, or
|
|
55
|
+
* whatever an existing agent's own cards already carry (kept as-is on a refresh).
|
|
56
|
+
*/
|
|
57
|
+
export interface CardModes {
|
|
58
|
+
/** autonomy_mode — verification + policy. No model call, so not billed; kept as-is on a refresh. */
|
|
59
|
+
autonomy: string;
|
|
60
|
+
integrity: string;
|
|
61
|
+
protection: string;
|
|
62
|
+
}
|
|
63
|
+
/** One phrase for the posture line: "integrity off, Safe House off". */
|
|
64
|
+
export declare function describeModes(m: Pick<CardModes, "integrity" | "protection">): string;
|
|
65
|
+
/** The modes for a NEW agent: off unless --governance asks for a feature. */
|
|
66
|
+
export declare function newAgentModes(governance?: GovernanceOptIn): CardModes;
|
|
67
|
+
/** Printed when a setup ran without --governance: how to opt in, and what it costs. */
|
|
68
|
+
export declare const GOVERNANCE_OPT_IN_HINT: string;
|
|
29
69
|
/**
|
|
30
70
|
* The default governed-agent slug for the current customer: `agent-<handle>`,
|
|
31
71
|
* where <handle> is the local-part of their mnemom login email (sanitised), or
|
|
@@ -73,6 +113,12 @@ export interface ClaimedAgentOptions {
|
|
|
73
113
|
orgLabel: string;
|
|
74
114
|
/** true = --setup (re-ask the gateway, refresh the cards); false = --no-setup (no card writes). */
|
|
75
115
|
setup?: boolean;
|
|
116
|
+
/**
|
|
117
|
+
* `--governance` / `--no-governance`: the modes the cards get. undefined = a new agent's are
|
|
118
|
+
* off, and a refresh keeps the agent's current modes. Given on a claimed agent, it also
|
|
119
|
+
* refreshes the cards (unless --no-setup).
|
|
120
|
+
*/
|
|
121
|
+
governance?: GovernanceOptIn;
|
|
76
122
|
/** Where the served-agent cache lives (defaults to ~/.mnemom). */
|
|
77
123
|
cacheDir?: string;
|
|
78
124
|
}
|
|
@@ -124,9 +170,10 @@ export declare function ensureClaimedServedAgent(opts: ClaimedAgentOptions): Pro
|
|
|
124
170
|
* (`details.required_scope`). Never the token, never the body.
|
|
125
171
|
*/
|
|
126
172
|
export declare function describeApiError(err: unknown): string;
|
|
127
|
-
/** `mnemom agent setup [--agent <slug>]` — the explicit provisioning entry point. */
|
|
173
|
+
/** `mnemom agent setup [--agent <slug>] [--governance …]` — the explicit provisioning entry point. */
|
|
128
174
|
export declare function agentSetupCommand(opts?: {
|
|
129
175
|
agent?: string;
|
|
176
|
+
governance?: GovernanceOptIn;
|
|
130
177
|
}): Promise<void>;
|
|
131
178
|
/** Thrown when the agent's org has no spendable MU balance. The user-facing
|
|
132
179
|
* top-up guidance is already printed when this is thrown — callers exit quietly. */
|
|
@@ -187,3 +234,5 @@ export declare function resolvePreflightedAccount(): Promise<{
|
|
|
187
234
|
orgLabel: string;
|
|
188
235
|
entitlement: CodeEntitlement | null;
|
|
189
236
|
}>;
|
|
237
|
+
export declare function buildCodeAlignmentCard(agentId: string, modes?: Pick<CardModes, "autonomy" | "integrity">): Record<string, unknown>;
|
|
238
|
+
export declare function buildCodeProtectionCard(agentId: string, mode?: string): Record<string, unknown>;
|
|
@@ -16,6 +16,9 @@
|
|
|
16
16
|
* screen_surfaces declared, ready for when it is turned on.
|
|
17
17
|
* - goal-alignment: nudge — sealed per-conversation via the goal contract
|
|
18
18
|
* (--goal …), graded and re-anchored by the gateway.
|
|
19
|
+
* `--governance [integrity,safe-house]` opts a setup in (observe-only, billed at
|
|
20
|
+
* cost on top of the agent fee); a refresh of an already-claimed agent keeps the
|
|
21
|
+
* modes its own cards carry unless a --governance / --no-governance flag says otherwise.
|
|
19
22
|
*
|
|
20
23
|
* A launch never runs on an unclaimed agent (operator 2026-10-03): an unclaimed agent lands in
|
|
21
24
|
* the sandbox org and bills nobody. ensureClaimedServedAgent asks the gateway which agent it
|
|
@@ -25,7 +28,7 @@
|
|
|
25
28
|
* (agents.ts), claimAgent + putAlignmentCard/putProtectionCard (lib/api.ts) — rather than
|
|
26
29
|
* reinventing it.
|
|
27
30
|
*/
|
|
28
|
-
import { claimAgent, getAgent, getAgentByName, getMe, getMuBalance, getMyPersonalOrg, MnemomApiError, mintProtectionCapability, putAlignmentCard, putProtectionCard, } from "../lib/api.js";
|
|
31
|
+
import { claimAgent, getAgent, getAgentByName, getCardSources, getMe, getMuBalance, getMyPersonalOrg, MnemomApiError, mintProtectionCapability, putAlignmentCard, putProtectionCard, } from "../lib/api.js";
|
|
29
32
|
import { getAuthInfo, loginWithBrowser, loginWithDeviceFlow, resolveAuth } from "../lib/auth.js";
|
|
30
33
|
import { getActiveOrg } from "../lib/cli-config.js";
|
|
31
34
|
import { getApiUrl, getGatewayUrl, getWebsiteUrl, MNEMOM_DIR } from "../lib/config.js";
|
|
@@ -40,6 +43,102 @@ import { birthThroughGateway } from "./wrap.js";
|
|
|
40
43
|
import { resolveAnthropicKey } from "./agent.js";
|
|
41
44
|
import { assertCodeEnabled, CodeNotEnabledError } from "./agent-invite.js";
|
|
42
45
|
const say = (line = "") => console.error(line);
|
|
46
|
+
/** The feature names `--governance <list>` accepts (plus `all` / `none`). */
|
|
47
|
+
export const GOVERNANCE_FEATURES = ["integrity", "safe-house"];
|
|
48
|
+
/**
|
|
49
|
+
* Parse the `--governance [features]` / `--no-governance` flag. undefined = not
|
|
50
|
+
* given (new agents get none; a refresh keeps the agent's current modes),
|
|
51
|
+
* true = every feature, false = none, a string = a comma-separated list of
|
|
52
|
+
* `integrity`, `safe-house`, `all` or `none`. Throws on an unknown name.
|
|
53
|
+
*/
|
|
54
|
+
export function parseGovernanceFlag(value) {
|
|
55
|
+
if (value === undefined)
|
|
56
|
+
return undefined;
|
|
57
|
+
if (typeof value === "boolean")
|
|
58
|
+
return { integrity: value, safeHouse: value };
|
|
59
|
+
const choice = { integrity: false, safeHouse: false };
|
|
60
|
+
const names = value
|
|
61
|
+
.split(",")
|
|
62
|
+
.map((s) => s.trim().toLowerCase())
|
|
63
|
+
.filter(Boolean);
|
|
64
|
+
if (names.length === 0)
|
|
65
|
+
return { integrity: true, safeHouse: true };
|
|
66
|
+
for (const name of names) {
|
|
67
|
+
if (name === "all") {
|
|
68
|
+
choice.integrity = true;
|
|
69
|
+
choice.safeHouse = true;
|
|
70
|
+
}
|
|
71
|
+
else if (name === "none") {
|
|
72
|
+
// contributes nothing; `none` alone means both off
|
|
73
|
+
}
|
|
74
|
+
else if (name === "integrity") {
|
|
75
|
+
choice.integrity = true;
|
|
76
|
+
}
|
|
77
|
+
else if (name === "safe-house") {
|
|
78
|
+
choice.safeHouse = true;
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
throw new Error(`--governance: unknown feature '${name}' — use ${GOVERNANCE_FEATURES.join(", ")}, all or none (comma-separated).`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return choice;
|
|
85
|
+
}
|
|
86
|
+
const modeOf = (on) => (on ? "observe" : "off");
|
|
87
|
+
/** One phrase for the posture line: "integrity off, Safe House off". */
|
|
88
|
+
export function describeModes(m) {
|
|
89
|
+
return `integrity ${m.integrity}, Safe House ${m.protection}`;
|
|
90
|
+
}
|
|
91
|
+
/** The mode string a raw card carries in `field`, or undefined when absent/not a string. */
|
|
92
|
+
function modeField(card, field) {
|
|
93
|
+
const v = card?.[field];
|
|
94
|
+
return typeof v === "string" && v ? v : undefined;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* An existing agent's own (agent-layer) card, to keep its modes on a refresh.
|
|
98
|
+
* null = it has none. THROWS when the read fails: the caller then leaves the
|
|
99
|
+
* cards alone rather than guess a mode in either direction.
|
|
100
|
+
*/
|
|
101
|
+
async function existingAgentCard(kind, agentId) {
|
|
102
|
+
const sources = await getCardSources(kind, agentId);
|
|
103
|
+
return sources?.agent?.card_json ?? null;
|
|
104
|
+
}
|
|
105
|
+
/** The modes for a NEW agent: off unless --governance asks for a feature. */
|
|
106
|
+
export function newAgentModes(governance) {
|
|
107
|
+
const integrity = modeOf(governance?.integrity ?? false);
|
|
108
|
+
return { autonomy: integrity, integrity, protection: modeOf(governance?.safeHouse ?? false) };
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* The modes for refreshing an EXISTING agent: an explicit --governance /
|
|
112
|
+
* --no-governance wins; else it keeps the modes its own cards carry. No agent-layer
|
|
113
|
+
* card → off (it never had one; new setups are off). A card with no mode field →
|
|
114
|
+
* observe (the prior CLI always wrote observe). A failed read → null: skip the refresh.
|
|
115
|
+
*/
|
|
116
|
+
async function existingAgentModes(agentId, governance) {
|
|
117
|
+
if (governance)
|
|
118
|
+
return newAgentModes(governance);
|
|
119
|
+
let align;
|
|
120
|
+
let prot;
|
|
121
|
+
try {
|
|
122
|
+
[align, prot] = await Promise.all([
|
|
123
|
+
existingAgentCard("alignment", agentId),
|
|
124
|
+
existingAgentCard("protection", agentId),
|
|
125
|
+
]);
|
|
126
|
+
}
|
|
127
|
+
catch (err) {
|
|
128
|
+
say(fmt.warn(`mnemom agent: couldn't read ${agentId}'s current cards (${describeApiError(err)}) — leaving them unchanged.`));
|
|
129
|
+
say(fmt.dim(" Re-run `mnemom agent setup`, or pass --governance / --no-governance to set them."));
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
const integrity = align ? (modeField(align, "integrity_mode") ?? "observe") : "off";
|
|
133
|
+
return {
|
|
134
|
+
autonomy: align ? (modeField(align, "autonomy_mode") ?? "observe") : "off",
|
|
135
|
+
integrity,
|
|
136
|
+
protection: prot ? (modeField(prot, "mode") ?? "observe") : "off",
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
/** Printed when a setup ran without --governance: how to opt in, and what it costs. */
|
|
140
|
+
export const GOVERNANCE_OPT_IN_HINT = " Governance (AIP integrity analysis, Safe House screening) is off by default. Turn it on with " +
|
|
141
|
+
"`mnemom agent setup --governance` (or --governance integrity / safe-house); it is billed at cost on top of the agent fee.";
|
|
43
142
|
/**
|
|
44
143
|
* The default governed-agent slug for the current customer: `agent-<handle>`,
|
|
45
144
|
* where <handle> is the local-part of their mnemom login email (sanitised), or
|
|
@@ -193,11 +292,18 @@ async function confirmClaimed(opts, anchorKey, fp) {
|
|
|
193
292
|
const slug = opts.slug;
|
|
194
293
|
const agentId = await resolveServedAgent(opts, fp);
|
|
195
294
|
if (await isClaimed(slug, agentId)) {
|
|
196
|
-
if (opts.setup === true) {
|
|
295
|
+
if (opts.setup === true || (opts.governance && opts.setup !== false)) {
|
|
197
296
|
// An explicit `mnemom agent setup` / --setup repairs or completes the cards (e.g. a
|
|
198
297
|
// protection card that failed on a prior run before its one-time grant was minted).
|
|
199
298
|
say(fmt.dim(`mnemom agent: '${slug}' is ${agentId}, already claimed — refreshing its cards.`));
|
|
200
|
-
await
|
|
299
|
+
const modes = await existingAgentModes(agentId, opts.governance);
|
|
300
|
+
if (modes) {
|
|
301
|
+
if (!opts.governance) {
|
|
302
|
+
say(fmt.dim(` Keeping its current governance (${describeModes(modes)}). New agents default to off; ` +
|
|
303
|
+
"`--governance` turns it on, `--no-governance` turns it off."));
|
|
304
|
+
}
|
|
305
|
+
await publishDefaultCards(agentId, modes);
|
|
306
|
+
}
|
|
201
307
|
persistAgentSlug(slug);
|
|
202
308
|
}
|
|
203
309
|
else {
|
|
@@ -229,10 +335,14 @@ async function confirmClaimed(opts, anchorKey, fp) {
|
|
|
229
335
|
}
|
|
230
336
|
say(fmt.success(`mnemom agent: claimed '${slug}' (${agentId}) in ${opts.orgLabel}.`));
|
|
231
337
|
await noteSameNamedAgent(slug, agentId);
|
|
232
|
-
// Publish the default (off)
|
|
233
|
-
// the agent is claimed and governed on whatever landed; we warn how to
|
|
234
|
-
|
|
235
|
-
|
|
338
|
+
// Publish the default cards (off unless --governance). A card write that can't complete does
|
|
339
|
+
// NOT undo the claim — the agent is claimed and governed on whatever landed; we warn how to
|
|
340
|
+
// finish it.
|
|
341
|
+
if (opts.setup !== false) {
|
|
342
|
+
await publishDefaultCards(agentId, newAgentModes(opts.governance));
|
|
343
|
+
if (!opts.governance)
|
|
344
|
+
say(fmt.dim(GOVERNANCE_OPT_IN_HINT));
|
|
345
|
+
}
|
|
236
346
|
persistAgentSlug(slug);
|
|
237
347
|
return { slug, agentId, claimedNow: true };
|
|
238
348
|
}
|
|
@@ -354,9 +464,9 @@ async function tryPublishCard(name, write) {
|
|
|
354
464
|
}
|
|
355
465
|
return { name, ok: false, error: "unknown error" };
|
|
356
466
|
}
|
|
357
|
-
/** Publish both default
|
|
358
|
-
async function publishDefaultCards(agentId) {
|
|
359
|
-
const align = await tryPublishCard("alignment", () => putAlignmentCard(agentId, JSON.stringify(buildCodeAlignmentCard(agentId)), "application/json"));
|
|
467
|
+
/** Publish both default cards at `modes`; never throws — warns on a card that can't be written. */
|
|
468
|
+
async function publishDefaultCards(agentId, modes) {
|
|
469
|
+
const align = await tryPublishCard("alignment", () => putAlignmentCard(agentId, JSON.stringify(buildCodeAlignmentCard(agentId, modes)), "application/json"));
|
|
360
470
|
// Protection is the "locked door": it needs a one-time cards:write:protection
|
|
361
471
|
// grant that the claim does NOT include (alignment rides a standing cap). Mint
|
|
362
472
|
// it with our human login JWT — the human running `mnemom agent` (which claims
|
|
@@ -375,13 +485,13 @@ async function publishDefaultCards(agentId) {
|
|
|
375
485
|
say(fmt.dim(" That agent is owned by an org other than your active one — `mnemom org use <org>` to switch, or set up under a different --agent."));
|
|
376
486
|
}
|
|
377
487
|
}
|
|
378
|
-
const prot = await tryPublishCard("protection", () => putProtectionCard(agentId, JSON.stringify(buildCodeProtectionCard(agentId)), "application/json"));
|
|
488
|
+
const prot = await tryPublishCard("protection", () => putProtectionCard(agentId, JSON.stringify(buildCodeProtectionCard(agentId, modes.protection)), "application/json"));
|
|
379
489
|
if (align.ok && prot.ok) {
|
|
380
|
-
say(fmt.success(
|
|
490
|
+
say(fmt.success(`mnemom agent: cards published (${describeModes(modes)}; goal contracts nudge).`));
|
|
381
491
|
return;
|
|
382
492
|
}
|
|
383
493
|
if (align.ok)
|
|
384
|
-
say(fmt.success(
|
|
494
|
+
say(fmt.success(`mnemom agent: alignment card published (integrity ${modes.integrity}).`));
|
|
385
495
|
for (const c of [align, prot]) {
|
|
386
496
|
if (c.ok)
|
|
387
497
|
continue;
|
|
@@ -392,7 +502,7 @@ async function publishDefaultCards(agentId) {
|
|
|
392
502
|
say(fmt.dim(` The agent is claimed and running; finish it later with \`${publishCmd}\`.`));
|
|
393
503
|
}
|
|
394
504
|
}
|
|
395
|
-
/** `mnemom agent setup [--agent <slug>]` — the explicit provisioning entry point. */
|
|
505
|
+
/** `mnemom agent setup [--agent <slug>] [--governance …]` — the explicit provisioning entry point. */
|
|
396
506
|
export async function agentSetupCommand(opts = {}) {
|
|
397
507
|
const gatewayHost = resolveGatewayHost(process.env);
|
|
398
508
|
const cellError = singleCellError({
|
|
@@ -427,6 +537,7 @@ export async function agentSetupCommand(opts = {}) {
|
|
|
427
537
|
orgId: account.orgId,
|
|
428
538
|
orgLabel: account.orgLabel,
|
|
429
539
|
setup: true,
|
|
540
|
+
governance: opts.governance,
|
|
430
541
|
});
|
|
431
542
|
}
|
|
432
543
|
function persistAgentSlug(slug) {
|
|
@@ -603,12 +714,15 @@ export async function resolvePreflightedAccount() {
|
|
|
603
714
|
return { orgId: active?.org_id ?? entitlement?.org_id ?? "", orgLabel, entitlement };
|
|
604
715
|
}
|
|
605
716
|
// ── The default coding posture (cards off; goal contracts nudge) ─────────────
|
|
606
|
-
function buildCodeAlignmentCard(agentId) {
|
|
717
|
+
export function buildCodeAlignmentCard(agentId, modes = { autonomy: "off", integrity: "off" }) {
|
|
607
718
|
return {
|
|
608
719
|
card_version: "unified/2026-04-26",
|
|
609
720
|
agent_id: agentId,
|
|
610
|
-
|
|
611
|
-
|
|
721
|
+
// Integrity off ⇒ the gateway skips the AIP checkpoint and the observer skips
|
|
722
|
+
// its trace analysis: no model call, no charge. A new agent's autonomy
|
|
723
|
+
// (verification + policy) follows it; a refresh keeps the agent's own.
|
|
724
|
+
autonomy_mode: modes.autonomy,
|
|
725
|
+
integrity_mode: modes.integrity,
|
|
612
726
|
principal: { type: "agent", identifier: agentId, relationship: "delegated_authority" },
|
|
613
727
|
values: {
|
|
614
728
|
declared: ["transparency", "safety", "honesty", "user_control", "harm_prevention"],
|
|
@@ -641,11 +755,11 @@ function buildCodeAlignmentCard(agentId) {
|
|
|
641
755
|
audit: { retention_days: 30, queryable: false, trace_format: "otel" },
|
|
642
756
|
};
|
|
643
757
|
}
|
|
644
|
-
function buildCodeProtectionCard(agentId) {
|
|
758
|
+
export function buildCodeProtectionCard(agentId, mode = "off") {
|
|
645
759
|
return {
|
|
646
760
|
card_version: "protection/2026-04-26",
|
|
647
761
|
agent_id: agentId,
|
|
648
|
-
mode
|
|
762
|
+
mode,
|
|
649
763
|
thresholds: { warn: 0.6, quarantine: 0.8, block: 0.95 },
|
|
650
764
|
screen_surfaces: {
|
|
651
765
|
incoming: true,
|
package/dist/commands/agent.d.ts
CHANGED
|
@@ -29,6 +29,7 @@ import { type MnemomKeyDeps } from "../lib/subscription-login.js";
|
|
|
29
29
|
import { type SessionRecord } from "../lib/agent-ledger.js";
|
|
30
30
|
import { type ExperimentalId } from "../lib/experimental.js";
|
|
31
31
|
import { type ConsoleKeyDeps } from "../lib/console-key.js";
|
|
32
|
+
import { type GovernanceOptIn } from "./agent-setup.js";
|
|
32
33
|
import { type ContextProfile, type LaunchShape, type SealedContractEnvelope } from "../lib/agent.js";
|
|
33
34
|
export interface AgentOptions {
|
|
34
35
|
goal?: string;
|
|
@@ -102,6 +103,11 @@ export interface AgentOptions {
|
|
|
102
103
|
* default cards, false (`--no-setup`) skips the card writes.
|
|
103
104
|
*/
|
|
104
105
|
setup?: boolean;
|
|
106
|
+
/**
|
|
107
|
+
* `--governance` / `--no-governance`: the governance modes a provisioning (or
|
|
108
|
+
* `--setup` refresh) writes. undefined = off for a new agent, kept on a refresh.
|
|
109
|
+
*/
|
|
110
|
+
governance?: GovernanceOptIn;
|
|
105
111
|
/**
|
|
106
112
|
* Where to source the Anthropic key (from ~/.mnemom/launcher.toml `key_source`).
|
|
107
113
|
* Restricts the default env → store → prompt search: "env" uses env only,
|
|
@@ -339,6 +345,11 @@ export declare function logLaunch(ctx: {
|
|
|
339
345
|
goalMode: string | null;
|
|
340
346
|
cli: ResolvedCli;
|
|
341
347
|
}): void;
|
|
348
|
+
/**
|
|
349
|
+
* The agent a signed-out `--dry-run` shows when the caller named none: the real name is
|
|
350
|
+
* `agent-<handle>` from the Mnemom login, which a dry run (no sign-in) cannot know yet.
|
|
351
|
+
*/
|
|
352
|
+
export declare const DRY_RUN_SIGNED_OUT_AGENT = "agent-<your Mnemom login handle>";
|
|
342
353
|
export declare function printPlan(ctx: {
|
|
343
354
|
scenario: string;
|
|
344
355
|
shape: LaunchShape;
|
package/dist/commands/agent.js
CHANGED
|
@@ -36,6 +36,7 @@ import { getApiUrl, getGatewayUrl, MNEMOM_DIR } from "../lib/config.js";
|
|
|
36
36
|
import { createApiKey } from "../lib/api.js";
|
|
37
37
|
import { MNEMOM_KEY_STORE_FIELD, resolveAgentAuth, resolveSubscriptionMnemomKey, subscriptionChildEnv, subscriptionShape, } from "../lib/subscription-login.js";
|
|
38
38
|
import { LEDGER_FILE, appendSessionRecord, findResumeCandidate, readSessionRecords, resolveRetentionDays, resumedContextProfile, toLedgerContract, } from "../lib/agent-ledger.js";
|
|
39
|
+
import { isLoggedIn } from "../lib/auth.js";
|
|
39
40
|
import { loadCliConfig } from "../lib/cli-config.js";
|
|
40
41
|
import { configKeySpec, loadLauncherConfig, writeLauncherConfig } from "../lib/launcher-config.js";
|
|
41
42
|
import { experimentalNotice } from "../lib/experimental.js";
|
|
@@ -261,20 +262,26 @@ export async function agentCommand(scenarioArg, options = {}) {
|
|
|
261
262
|
// which we only know AFTER sign-in. The entitlement-aware decision
|
|
262
263
|
// (`resolveRemoteControlShape`) happens below, right after
|
|
263
264
|
// `ensureSessionAndBalance`. --dry-run prints the plan BEFORE sign-in, so it
|
|
264
|
-
// can't know the entitlement
|
|
265
|
-
//
|
|
266
|
-
//
|
|
265
|
+
// can't know the entitlement: it previews the REQUESTED shape (terminal unless
|
|
266
|
+
// --remote-control/--server or `launch` asked otherwise) and never the RC
|
|
267
|
+
// auto-upgrade, which only an entitled (internal) account gets. An explicit RC
|
|
268
|
+
// request gets the note that the real launch still applies the entitlement gate.
|
|
267
269
|
if (options.dryRun) {
|
|
268
270
|
const planCli = cli ?? {
|
|
269
271
|
bin: "(not found — pass --cli <path> or `mnemom agent config set cli <path>`)",
|
|
270
272
|
name: basename((options.cli || env.MNEMOM_AGENT_CLI || "claude").trim()),
|
|
271
273
|
};
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
274
|
+
// A defaulted slug is `agent-<handle>` from the login (resolved for real after
|
|
275
|
+
// sign-in, below). Signed in: show that name. Signed out, or no usable handle:
|
|
276
|
+
// say where it comes from rather than print the "agent-user" fallback as if it were it.
|
|
277
|
+
if (!options.agent?.trim()) {
|
|
278
|
+
agent =
|
|
279
|
+
((await isLoggedIn()) && sanitizeAgentName(await resolveDefaultSlug())) ||
|
|
280
|
+
DRY_RUN_SIGNED_OUT_AGENT;
|
|
281
|
+
}
|
|
275
282
|
printPlan({
|
|
276
283
|
scenario,
|
|
277
|
-
shape
|
|
284
|
+
shape,
|
|
278
285
|
agent,
|
|
279
286
|
// The org is only known after sign-in, so a dry run never offers a resume (IG5 #1).
|
|
280
287
|
conversationId: resolveConversationId(options.conversationId),
|
|
@@ -298,8 +305,8 @@ export async function agentCommand(scenarioArg, options = {}) {
|
|
|
298
305
|
}
|
|
299
306
|
if (expNotice)
|
|
300
307
|
say(expNotice);
|
|
301
|
-
if (
|
|
302
|
-
say("mnemom agent: the
|
|
308
|
+
if (shape !== "terminal") {
|
|
309
|
+
say("mnemom agent: the real launch uses this shape only if your account is entitled to it, else terminal-only.");
|
|
303
310
|
}
|
|
304
311
|
sayContextProfile(contextProfile);
|
|
305
312
|
sayCompaction(contextProfile, compactionIntercept, compactAt, launchWindow(windowModelFor(options, cli)));
|
|
@@ -314,14 +321,6 @@ export async function agentCommand(scenarioArg, options = {}) {
|
|
|
314
321
|
if (!cli) {
|
|
315
322
|
cli = await promptForCliPath(options.cli, env, cliError);
|
|
316
323
|
}
|
|
317
|
-
// ── resolve the Anthropic key (zero 1Password; never logged) ──
|
|
318
|
-
// Not on a subscription launch: Claude Code then sends its own claude.ai login instead.
|
|
319
|
-
// A Console launch uses the key Claude Code's Console login created, then runs as api-key.
|
|
320
|
-
const anthropicKey = auth === "subscription"
|
|
321
|
-
? undefined
|
|
322
|
-
: auth === "console"
|
|
323
|
-
? requireConsoleKey(defaultConsoleKeyDeps(env))
|
|
324
|
-
: await resolveAnthropicKey(env, { allowPrompt: true, keySource: options.keySource });
|
|
325
324
|
// ── account + µ-balance gate (no free tier) ─────────────────────────────────
|
|
326
325
|
// mnemom agent runs governed and on µ, so before we seal a contract or launch we
|
|
327
326
|
// (1) ensure a Mnemom session — offering the try-me-style sign-in / sign-up when
|
|
@@ -342,6 +341,16 @@ export async function agentCommand(scenarioArg, options = {}) {
|
|
|
342
341
|
process.exit(1); // guidance already printed
|
|
343
342
|
throw err;
|
|
344
343
|
}
|
|
344
|
+
// ── resolve the Anthropic key (zero 1Password; never logged) ──
|
|
345
|
+
// AFTER the account gate above: an account Mnemom Agent is not enabled for learns that
|
|
346
|
+
// (and how to get access) before it is asked for, or refused for want of, a key.
|
|
347
|
+
// Not on a subscription launch: Claude Code then sends its own claude.ai login instead.
|
|
348
|
+
// A Console launch uses the key Claude Code's Console login created, then runs as api-key.
|
|
349
|
+
const anthropicKey = auth === "subscription"
|
|
350
|
+
? undefined
|
|
351
|
+
: auth === "console"
|
|
352
|
+
? requireConsoleKey(defaultConsoleKeyDeps(env))
|
|
353
|
+
: await resolveAnthropicKey(env, { allowPrompt: true, keySource: options.keySource });
|
|
345
354
|
// ── Remote-Control entitlement gate ─────────────────────────────────────────
|
|
346
355
|
// Remote Control (a session steerable from claude.ai / the Claude app) is
|
|
347
356
|
// internal-only. Now that we know the org's entitlement, resolve the final
|
|
@@ -402,6 +411,7 @@ export async function agentCommand(scenarioArg, options = {}) {
|
|
|
402
411
|
orgId: account.orgId,
|
|
403
412
|
orgLabel: account.orgLabel,
|
|
404
413
|
setup: options.setup,
|
|
414
|
+
governance: options.governance,
|
|
405
415
|
});
|
|
406
416
|
// ── conversation id ── an implicit relaunch continues its earlier conversation (IG5 #1, D1)
|
|
407
417
|
// so the goal the gateway inferred there is still the one it steers by. Resolved here, after
|
|
@@ -1584,6 +1594,11 @@ export function logLaunch(ctx) {
|
|
|
1584
1594
|
say(`mnemom agent: goal mode: ${ctx.goalMode} (inferred from your messages; watch x-mnemom-goal-revision / x-mnemom-goal-steer on each response)`);
|
|
1585
1595
|
}
|
|
1586
1596
|
}
|
|
1597
|
+
/**
|
|
1598
|
+
* The agent a signed-out `--dry-run` shows when the caller named none: the real name is
|
|
1599
|
+
* `agent-<handle>` from the Mnemom login, which a dry run (no sign-in) cannot know yet.
|
|
1600
|
+
*/
|
|
1601
|
+
export const DRY_RUN_SIGNED_OUT_AGENT = "agent-<your Mnemom login handle>";
|
|
1587
1602
|
export function printPlan(ctx) {
|
|
1588
1603
|
const L = [];
|
|
1589
1604
|
L.push("mnemom agent — launch plan (dry run)");
|
|
@@ -16,3 +16,5 @@ export declare function experimentalEnableCommand(id: string | undefined, deps?:
|
|
|
16
16
|
export declare function experimentalDisableCommand(id: string | undefined, deps?: ExperimentalDeps): void;
|
|
17
17
|
/** The one-time migration (lib/experimental.ts migrateExperimental) against the real files. */
|
|
18
18
|
export declare function runExperimentalMigration(): void;
|
|
19
|
+
/** Does `dir` exist with anything in it? Unreadable counts as yes (an upgrader keeps the notice). */
|
|
20
|
+
export declare function hasPriorState(dir: string): boolean;
|
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
* `mnemom experimental list [--json] | enable <id> | disable <id>` — turn experimental
|
|
3
3
|
* features on and off (registry: lib/experimental.ts; stored in ~/.mnemom/config.json).
|
|
4
4
|
*/
|
|
5
|
+
import { existsSync, readdirSync } from "node:fs";
|
|
5
6
|
import { EXPERIMENTAL_CONTRACT, EXPERIMENTAL_ENV, EXPERIMENTAL_FEATURES, EXPERIMENTAL_IDS, isExperimentalId, migrateExperimental, resolveExperimental, } from "../lib/experimental.js";
|
|
6
7
|
import { loadCliConfig, setExperimentalEnabled } from "../lib/cli-config.js";
|
|
8
|
+
import { MNEMOM_DIR } from "../lib/config.js";
|
|
7
9
|
import { launcherEntries, loadLauncherConfig } from "../lib/launcher-config.js";
|
|
8
10
|
function defaultDeps() {
|
|
9
11
|
return {
|
|
@@ -74,5 +76,15 @@ export function runExperimentalMigration() {
|
|
|
74
76
|
launcherEntries: () => launcherEntries(loadLauncherConfig()),
|
|
75
77
|
persist: (ids) => setExperimentalEnabled(ids),
|
|
76
78
|
writeErr: (line) => void process.stderr.write(line),
|
|
79
|
+
priorInstall: () => hasPriorState(MNEMOM_DIR),
|
|
77
80
|
});
|
|
78
81
|
}
|
|
82
|
+
/** Does `dir` exist with anything in it? Unreadable counts as yes (an upgrader keeps the notice). */
|
|
83
|
+
export function hasPriorState(dir) {
|
|
84
|
+
try {
|
|
85
|
+
return existsSync(dir) && readdirSync(dir).length > 0;
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -26,7 +26,7 @@ import { agentCommand } from "./commands/agent.js";
|
|
|
26
26
|
import { agentConfigCommand } from "./commands/agent-config.js";
|
|
27
27
|
import { agentSlackCommand } from "./commands/agent-slack.js";
|
|
28
28
|
import { agentDoctorCommand } from "./commands/agent-doctor.js";
|
|
29
|
-
import { agentSetupCommand } from "./commands/agent-setup.js";
|
|
29
|
+
import { agentSetupCommand, parseGovernanceFlag } from "./commands/agent-setup.js";
|
|
30
30
|
import { agentInviteCommand, runAgentInvocation } from "./commands/agent-invite.js";
|
|
31
31
|
import { agentFirstRunCommand, offerFirstRunOnLaunch } from "./commands/agent-first-run.js";
|
|
32
32
|
import { applyConfigToEnv, launcherEntries, loadLauncherConfig, withoutDisabledExperimental, } from "./lib/launcher-config.js";
|
|
@@ -155,8 +155,11 @@ const agentCmd = program
|
|
|
155
155
|
.option("--conversation-id <id>", "x-mnemom-conversation-id (default: auto-generated)")
|
|
156
156
|
.option("--cli <name|path>", "Coding-agent CLI to launch: a name or full path (default: claude)")
|
|
157
157
|
.option("--gateway <url>", "Gateway host override — must be the same cell as the rest of the CLI (default: that cell's gateway; or MNEMOM_AGENT_GATEWAY)")
|
|
158
|
-
|
|
159
|
-
|
|
158
|
+
// Remote Control is internal-only (the launch gates it on the org's entitlement and runs a
|
|
159
|
+
// customer terminal-only), so its two shapes stay out of --help for everyone. The help is
|
|
160
|
+
// built at load, before any sign-in, so it cannot ask the entitlement; the flags still parse.
|
|
161
|
+
.addOption(new Option("--remote-control", "Interactive Remote Control session in THIS terminal (behind a local proxy)").hideHelp())
|
|
162
|
+
.addOption(new Option("--server", "Headless `claude remote-control` dispatcher (behind a local proxy)").hideHelp())
|
|
160
163
|
.option("--auth <mode>", "How Claude Code authenticates: api-key (default) | subscription (your Claude Team/Enterprise login) | console (the API key your Claude Code Anthropic Console login created)")
|
|
161
164
|
.option("--model <alias|id>", "Model to launch (CLI alias or concrete id)")
|
|
162
165
|
.addOption(new Option("--effort <level>", "Reasoning effort").choices([
|
|
@@ -170,6 +173,8 @@ const agentCmd = program
|
|
|
170
173
|
.option("--invite <token>", "Redeem a Mnemom Agent invitation (pinv_…): unlocks Mnemom Agent for you + credits your starter µ; launches too when a session name is given")
|
|
171
174
|
.option("--setup", "Re-check the served agent and refresh its default cards before launch")
|
|
172
175
|
.option("--no-setup", "Skip the default-card writes (the agent is still confirmed and claimed)")
|
|
176
|
+
.option("--governance [features]", "Turn governance ON for the agent's cards: AIP integrity analysis + Safe House screening, observe-only (default: off for new agents). Pick features with a comma list: integrity, safe-house. Billed at cost on top of the agent fee")
|
|
177
|
+
.option("--no-governance", "Turn governance OFF for the agent's cards (a card refresh otherwise keeps an existing agent's modes)")
|
|
173
178
|
.option("--no-yolo", "Do NOT pass --dangerously-skip-permissions (yolo is on by default)")
|
|
174
179
|
.option("--no-context-hint", "Do NOT seed Claude Code's context-hint feature flag before launch")
|
|
175
180
|
.addOption(new Option("--slack", "EXPERIMENTAL (`mnemom experimental enable slack`): attach the Slack channel — messages addressed to this session arrive in it, and it can reply (see `mnemom agent slack`)").hideHelp(hiddenUnless("slack")))
|
|
@@ -299,13 +304,15 @@ const agentCmd = program
|
|
|
299
304
|
return;
|
|
300
305
|
}
|
|
301
306
|
if (scenario === "doctor") {
|
|
302
|
-
await agentDoctorCommand({ cli: opts.cli });
|
|
307
|
+
await agentDoctorCommand({ cli: opts.cli, agent: program.opts().agent ?? cfg.agent });
|
|
303
308
|
return;
|
|
304
309
|
}
|
|
310
|
+
// Validated up front so a typo fails before any sign-in or provisioning.
|
|
311
|
+
const governance = parseGovernanceFlag(opts.governance);
|
|
305
312
|
// Sub-verb: `mnemom agent setup` provisions/claims the governed agent and
|
|
306
313
|
// never launches. The agent slug follows the same precedence as a launch.
|
|
307
314
|
if (scenario === "setup") {
|
|
308
|
-
await agentSetupCommand({ agent: program.opts().agent ?? cfg.agent });
|
|
315
|
+
await agentSetupCommand({ agent: program.opts().agent ?? cfg.agent, governance });
|
|
309
316
|
return;
|
|
310
317
|
}
|
|
311
318
|
const numToStr = (n) => n === undefined ? undefined : String(n);
|
|
@@ -367,6 +374,7 @@ const agentCmd = program
|
|
|
367
374
|
: false,
|
|
368
375
|
experimental,
|
|
369
376
|
setup: opts.setup,
|
|
377
|
+
governance,
|
|
370
378
|
keySource: cfg.keySource,
|
|
371
379
|
passthrough,
|
|
372
380
|
dryRun: opts.dryRun,
|
package/dist/lib/api.d.ts
CHANGED
|
@@ -630,6 +630,27 @@ export declare const PROTECTION_CARD_MAX_BYTES: number;
|
|
|
630
630
|
export declare function putProtectionCard(agentId: string, body: string, contentType?: "text/yaml" | "application/json", opts?: {
|
|
631
631
|
idempotencyKey?: string;
|
|
632
632
|
}): Promise<PublishedCardResponse>;
|
|
633
|
+
/**
|
|
634
|
+
* The per-layer view of an agent's card (ADR-053 `?include=sources`): the raw
|
|
635
|
+
* card the agent layer itself carries, next to the composed card every layer
|
|
636
|
+
* produced. We type the two rows we read; the platform/org/teams rows ride along.
|
|
637
|
+
*/
|
|
638
|
+
export interface CardSources {
|
|
639
|
+
agent: {
|
|
640
|
+
card_json: Record<string, unknown> | null;
|
|
641
|
+
available: boolean;
|
|
642
|
+
};
|
|
643
|
+
composed: {
|
|
644
|
+
card_json: Record<string, unknown> | null;
|
|
645
|
+
available: boolean;
|
|
646
|
+
};
|
|
647
|
+
[key: string]: unknown;
|
|
648
|
+
}
|
|
649
|
+
/**
|
|
650
|
+
* Fetch an agent's alignment or protection card sources envelope. Returns null
|
|
651
|
+
* when the agent has no canonical card yet (404).
|
|
652
|
+
*/
|
|
653
|
+
export declare function getCardSources(kind: "alignment" | "protection", agentId: string): Promise<CardSources | null>;
|
|
633
654
|
export interface CardPreviewResult {
|
|
634
655
|
/** true ⇔ the server accepted+composed the card (HTTP 200). */
|
|
635
656
|
valid: boolean;
|
package/dist/lib/api.js
CHANGED
|
@@ -841,6 +841,22 @@ export async function putProtectionCard(agentId, body, contentType = "text/yaml"
|
|
|
841
841
|
}
|
|
842
842
|
return response.json();
|
|
843
843
|
}
|
|
844
|
+
/**
|
|
845
|
+
* Fetch an agent's alignment or protection card sources envelope. Returns null
|
|
846
|
+
* when the agent has no canonical card yet (404).
|
|
847
|
+
*/
|
|
848
|
+
export async function getCardSources(kind, agentId) {
|
|
849
|
+
const url = validateUrl(`${API_BASE}/v1/${kind}/agent/${encodeURIComponent(agentId)}?include=sources`);
|
|
850
|
+
const response = await mnemomFetch(url, {
|
|
851
|
+
headers: { Accept: "application/json", ...(await authHeaders()) },
|
|
852
|
+
});
|
|
853
|
+
if (!response.ok) {
|
|
854
|
+
if (response.status === 404)
|
|
855
|
+
return null;
|
|
856
|
+
throw await readApiError(response, `Failed to fetch ${kind} card sources`);
|
|
857
|
+
}
|
|
858
|
+
return response.json();
|
|
859
|
+
}
|
|
844
860
|
/**
|
|
845
861
|
* Dry-run validate + compose a card body against the server's authoritative
|
|
846
862
|
* pipeline. Returns a structured result — a 422/400 validation failure is an
|
|
@@ -98,6 +98,11 @@ export interface MigrationDeps {
|
|
|
98
98
|
launcherEntries(): Array<[string, string]>;
|
|
99
99
|
persist(ids: ExperimentalId[]): void;
|
|
100
100
|
writeErr(line: string): void;
|
|
101
|
+
/**
|
|
102
|
+
* Did this machine run the CLI before this invocation (~/.mnemom already held something)?
|
|
103
|
+
* A brand-new install never had implicit on, so it gets no "it used to be on" notice.
|
|
104
|
+
*/
|
|
105
|
+
priorInstall(): boolean;
|
|
101
106
|
}
|
|
102
107
|
/** One-time notice for users who had implicit goal alignment by default (no goal_mode set). */
|
|
103
108
|
export declare const IMPLICIT_NOW_OFF_NOTICE: string;
|
package/dist/lib/experimental.js
CHANGED
|
@@ -213,6 +213,7 @@ export function migrateExperimental(deps) {
|
|
|
213
213
|
try {
|
|
214
214
|
if (deps.stored() !== undefined)
|
|
215
215
|
return null;
|
|
216
|
+
const upgrading = deps.priorInstall();
|
|
216
217
|
const entries = deps.launcherEntries();
|
|
217
218
|
const ids = featuresInUse(entries);
|
|
218
219
|
deps.persist(ids);
|
|
@@ -220,9 +221,10 @@ export function migrateExperimental(deps) {
|
|
|
220
221
|
deps.writeErr(`mnemom: ${ids.join(", ")} ${ids.length === 1 ? "is" : "are"} now experimental and kept on because your launcher.toml uses ${ids.length === 1 ? "it" : "them"} — see \`mnemom experimental list\`.\n`);
|
|
221
222
|
}
|
|
222
223
|
// Implicit was the unconfigured DEFAULT, so most users relied on it without a
|
|
223
|
-
// goal_mode entry. Don't let it switch off silently for them: say so once.
|
|
224
|
+
// goal_mode entry. Don't let it switch off silently for them: say so once. A fresh
|
|
225
|
+
// install had no default to lose, so it is told nothing.
|
|
224
226
|
const optedOut = entries.some(([k, v]) => k === "goal_mode" && v === "off");
|
|
225
|
-
if (!ids.includes("implicit") && !optedOut)
|
|
227
|
+
if (upgrading && !ids.includes("implicit") && !optedOut)
|
|
226
228
|
deps.writeErr(IMPLICIT_NOW_OFF_NOTICE);
|
|
227
229
|
return ids;
|
|
228
230
|
}
|
package/dist/lib/version-gate.js
CHANGED
|
@@ -74,8 +74,7 @@ export function enforceMinCliVersion(response, exit = process.exit, write = (chu
|
|
|
74
74
|
write(`\n✓ Updated the Mnemom CLI (was v${CLI_VERSION}; server minimum v${floor}). Re-run your command.\n\n`);
|
|
75
75
|
exit(1);
|
|
76
76
|
}
|
|
77
|
-
write(`\n✖ Your Mnemom CLI (v${CLI_VERSION}) is too old for this server (minimum v${floor}).\n` +
|
|
78
|
-
` Old CLIs can silently drop newer request fields — e.g. \`--org\` on \`mnemom agent claim\`.\n\n` +
|
|
77
|
+
write(`\n✖ Your Mnemom CLI (v${CLI_VERSION}) is too old for this server (minimum v${floor}).\n\n` +
|
|
79
78
|
` Upgrade: npm i -g @mnemom/mnemom@latest\n\n`);
|
|
80
79
|
exit(1);
|
|
81
80
|
}
|