@mnemom/mnemom 0.17.9-next.0 → 0.17.10
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-setup.d.ts +68 -49
- package/dist/commands/agent-setup.js +242 -160
- package/dist/commands/agent.d.ts +9 -12
- package/dist/commands/agent.js +26 -56
- package/dist/commands/card.js +2 -2
- package/dist/commands/onboard.js +4 -3
- package/dist/commands/protection.js +1 -1
- package/dist/commands/try-me.d.ts +13 -0
- package/dist/commands/try-me.js +44 -13
- package/dist/commands/wrap.js +3 -3
- package/dist/index.js +2 -2
- package/dist/lib/agent-identity-route.d.ts +28 -0
- package/dist/lib/agent-identity-route.js +57 -0
- package/dist/lib/login-detect.d.ts +2 -2
- package/dist/lib/login-detect.js +4 -4
- package/dist/lib/served-agent-cache.d.ts +8 -0
- package/dist/lib/served-agent-cache.js +26 -9
- package/dist/lib/try-me.d.ts +18 -0
- package/dist/lib/try-me.js +70 -3
- package/package.json +1 -1
|
@@ -6,32 +6,35 @@
|
|
|
6
6
|
* governed identity `agent-<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
8
|
* customer owns it), and publishes a sane default coding posture. The CARDS are
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
9
|
+
* OFF by default (operator 2026-10-02: alignment and protection analysis are
|
|
10
|
+
* billed, so nobody pays for them until they turn them on); the goal contract is
|
|
11
|
+
* what nudges (re-anchor on drift). Specifically:
|
|
12
|
+
* - alignment card: autonomy_mode + integrity_mode = off,
|
|
12
13
|
* principal.relationship = delegated_authority, coding bounded/forbidden
|
|
13
14
|
* actions, escalate-on-irreversible;
|
|
14
|
-
* - protection card: mode =
|
|
15
|
-
* screen_surfaces
|
|
15
|
+
* - protection card: mode = off, thresholds 0.60/0.80/0.95 and all four
|
|
16
|
+
* screen_surfaces declared, ready for when it is turned on.
|
|
16
17
|
* - goal-alignment: nudge — sealed per-conversation via the goal contract
|
|
17
18
|
* (--goal …), graded and re-anchored by the gateway.
|
|
18
19
|
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* launch
|
|
22
|
-
*
|
|
23
|
-
* birthThroughGateway (wrap.ts), deriveHashProof
|
|
24
|
-
* putAlignmentCard/putProtectionCard (lib/api.ts) — rather than
|
|
20
|
+
* A launch never runs on an unclaimed agent (operator 2026-10-03): an unclaimed agent lands in
|
|
21
|
+
* the sandbox org and bills nobody. ensureClaimedServedAgent asks the gateway which agent it
|
|
22
|
+
* serves for this launch's credential + name, claims it into the org the launch bills when it
|
|
23
|
+
* is not claimed yet, and refuses otherwise — interactive or not, there is no unclaimed
|
|
24
|
+
* fallback. Reuses the existing machinery — birthThroughGateway (wrap.ts), deriveHashProof
|
|
25
|
+
* (agents.ts), claimAgent + putAlignmentCard/putProtectionCard (lib/api.ts) — rather than
|
|
26
|
+
* reinventing it.
|
|
25
27
|
*/
|
|
26
|
-
import { claimAgent, getAgentByName, getMe, getMuBalance, getMyPersonalOrg, MnemomApiError, mintProtectionCapability, putAlignmentCard, putProtectionCard, } from "../lib/api.js";
|
|
27
|
-
import { getAuthInfo,
|
|
28
|
+
import { claimAgent, getAgent, getAgentByName, getMe, getMuBalance, getMyPersonalOrg, MnemomApiError, mintProtectionCapability, putAlignmentCard, putProtectionCard, } from "../lib/api.js";
|
|
29
|
+
import { getAuthInfo, loginWithBrowser, loginWithDeviceFlow, resolveAuth } from "../lib/auth.js";
|
|
28
30
|
import { getActiveOrg } from "../lib/cli-config.js";
|
|
29
31
|
import { getApiUrl, getGatewayUrl, getWebsiteUrl, MNEMOM_DIR } from "../lib/config.js";
|
|
30
32
|
import { fmt } from "../lib/format.js";
|
|
31
33
|
import { askYesNo, isInteractive } from "../lib/prompt.js";
|
|
32
34
|
import { resolveAnthropicDoor, resolveGatewayHost, sanitizeAgentName, singleCellError, } from "../lib/agent.js";
|
|
33
35
|
import { configKeySpec, loadLauncherConfig, writeLauncherConfig } from "../lib/launcher-config.js";
|
|
34
|
-
import {
|
|
36
|
+
import { IdentityRouteMissingError, resolveServedIdentity } from "../lib/agent-identity-route.js";
|
|
37
|
+
import { readConfirmedAgent, readServedAgent, servedFingerprint, writeConfirmedAgent, writeServedAgent, } from "../lib/served-agent-cache.js";
|
|
35
38
|
import { deriveHashProof } from "./agents.js";
|
|
36
39
|
import { birthThroughGateway } from "./wrap.js";
|
|
37
40
|
import { resolveAnthropicKey } from "./agent.js";
|
|
@@ -82,152 +85,230 @@ export async function resolveDefaultSlug(opts = {}) {
|
|
|
82
85
|
return defaultAgentSlug();
|
|
83
86
|
}
|
|
84
87
|
/**
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
+
* `mnemom agent` refuses to launch: the agent the gateway would serve is not claimed and could
|
|
89
|
+
* not be claimed here (or could not be identified at all). An unclaimed agent lands in the
|
|
90
|
+
* sandbox org and bills nobody, so there is no fallback — the message says what to do.
|
|
88
91
|
*/
|
|
89
|
-
export class
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
constructor(slug,
|
|
94
|
-
/** The agent the org lookup found by name. */
|
|
95
|
-
namedId,
|
|
96
|
-
/** The agent the gateway answered with on x-mnemom-agent. */
|
|
97
|
-
servedId) {
|
|
98
|
-
super(`mnemom agent setup: '${slug}' in your org is ${namedId}, but the gateway serves ${servedId} for this Anthropic key + name. ` +
|
|
99
|
-
"Refusing to govern the wrong agent. The gateway identity is hash(credential, name): a different key, " +
|
|
100
|
-
"or a prior setup under another slug, is a different agent. " +
|
|
101
|
-
`Either claim the served one (mnemom agent claim ${servedId} --key <the same key>) or launch with the --agent and key you set up.`);
|
|
102
|
-
this.slug = slug;
|
|
103
|
-
this.namedId = namedId;
|
|
104
|
-
this.servedId = servedId;
|
|
105
|
-
this.name = "AgentIdentityMismatchError";
|
|
92
|
+
export class UnclaimedAgentError extends Error {
|
|
93
|
+
constructor(message) {
|
|
94
|
+
super(message);
|
|
95
|
+
this.name = "UnclaimedAgentError";
|
|
106
96
|
}
|
|
107
97
|
}
|
|
108
98
|
/** A bare launch never waits on the served-agent probe longer than this. */
|
|
109
99
|
export const SERVED_PROBE_TIMEOUT_MS = 10_000;
|
|
110
100
|
/**
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
*
|
|
117
|
-
*
|
|
101
|
+
* The waits between the tries of a call that failed transiently (network, timeout, 429, 5xx):
|
|
102
|
+
* three tries, about four seconds of backoff. Exported so tests can zero it.
|
|
103
|
+
*/
|
|
104
|
+
export const CONFIRM_RETRY_DELAYS_MS = [1000, 3000];
|
|
105
|
+
/**
|
|
106
|
+
* Mnemom couldn't be reached to answer (a transient failure that outlived its retries). Carries
|
|
107
|
+
* the served id when the gateway did answer before the api failed.
|
|
108
|
+
*/
|
|
109
|
+
class MnemomUnreachableError extends Error {
|
|
110
|
+
reason;
|
|
111
|
+
servedId;
|
|
112
|
+
constructor(reason, servedId) {
|
|
113
|
+
super(describeApiError(reason));
|
|
114
|
+
this.reason = reason;
|
|
115
|
+
this.servedId = servedId;
|
|
116
|
+
this.name = "MnemomUnreachableError";
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* A failure worth retrying, and that an outage fallback may cover: no answer at all (network,
|
|
121
|
+
* timeout, an unparseable reply) or a 408/429/5xx. A 4xx, a missing route, or a refusal is a
|
|
122
|
+
* definite answer.
|
|
118
123
|
*/
|
|
119
|
-
export
|
|
124
|
+
export function isTransient(err) {
|
|
125
|
+
if (err instanceof UnclaimedAgentError || err instanceof IdentityRouteMissingError)
|
|
126
|
+
return false;
|
|
127
|
+
if (err instanceof MnemomApiError) {
|
|
128
|
+
const status = err.effectiveStatus;
|
|
129
|
+
return status === 408 || status === 429 || status >= 500;
|
|
130
|
+
}
|
|
131
|
+
return true;
|
|
132
|
+
}
|
|
133
|
+
/** `call`, retried with backoff while it fails transiently; the last failure is rethrown. */
|
|
134
|
+
async function withRetry(call) {
|
|
135
|
+
for (let attempt = 0;; attempt++) {
|
|
136
|
+
try {
|
|
137
|
+
return await call();
|
|
138
|
+
}
|
|
139
|
+
catch (err) {
|
|
140
|
+
if (!isTransient(err) || attempt >= CONFIRM_RETRY_DELAYS_MS.length)
|
|
141
|
+
throw err;
|
|
142
|
+
await sleep(CONFIRM_RETRY_DELAYS_MS[attempt]);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* The one invariant behind every `mnemom agent` launch: the coding CLI never starts unless the
|
|
148
|
+
* agent the gateway serves for THIS launch's credential + name is claimed.
|
|
149
|
+
*
|
|
150
|
+
* 1. Ask which agent the gateway serves (an Anthropic-key launch: the birth probe, cached per
|
|
151
|
+
* key + name + gateway; a subscription launch: the gateway's identity route).
|
|
152
|
+
* 2. Read its claim state. Claimed ⇒ launch, and never claim it again: a re-claim by its owner
|
|
153
|
+
* MOVES it to the requested org.
|
|
154
|
+
* 3. Unclaimed ⇒ claim it into `orgId` with the hash proof of the key that anchors it. Another
|
|
155
|
+
* account owns it ⇒ refuse; any other claim failure ⇒ refuse.
|
|
156
|
+
*
|
|
157
|
+
* A definite answer (unclaimed and can't be claimed, owned by another account, the identity
|
|
158
|
+
* route missing, a rejected key) always refuses. A transient failure is retried; if Mnemom still
|
|
159
|
+
* can't be reached, the launch uses the agent this machine last confirmed claimed for the same
|
|
160
|
+
* credential + name — and only when the gateway hasn't since named a different one — or
|
|
161
|
+
* refuses. There is deliberately no switch that launches unclaimed (operator ruling 2026-10-03).
|
|
162
|
+
*
|
|
163
|
+
* Interactive and non-interactive runs behave the same: there is no prompt to decline.
|
|
164
|
+
* MNE-7650: the name is client-controlled, so a same-named org agent proves nothing — only the
|
|
165
|
+
* served id counts.
|
|
166
|
+
*/
|
|
167
|
+
export async function ensureClaimedServedAgent(opts) {
|
|
120
168
|
const slug = opts.slug;
|
|
169
|
+
const anchorKey = opts.anthropicKey || opts.mnemomKey;
|
|
170
|
+
if (!anchorKey) {
|
|
171
|
+
throw new UnclaimedAgentError(`mnemom agent: no credential anchors '${slug}' — an Anthropic key or a Mnemom API key is required.`);
|
|
172
|
+
}
|
|
121
173
|
const cacheDir = opts.cacheDir ?? MNEMOM_DIR;
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
// the name recorded and reported an org agent while the gateway governed
|
|
135
|
-
// a different, unclaimed one (a second Anthropic key on a shared name).
|
|
136
|
-
//
|
|
137
|
-
// The answer is stable per (key, name, gateway), so a bare launch reuses a cached one and
|
|
138
|
-
// only probes (time-boxed) the first time; --setup always re-asks.
|
|
139
|
-
const gatewayUrl = opts.gatewayHost.replace(/\/+$/, "");
|
|
140
|
-
const fp = servedFingerprint(opts.anthropicKey, slug, gatewayUrl);
|
|
141
|
-
const cached = opts.setup === true ? null : readServedAgent(cacheDir, fp);
|
|
142
|
-
let served;
|
|
143
|
-
try {
|
|
144
|
-
served =
|
|
145
|
-
cached ??
|
|
146
|
-
(await birthThroughGateway(gatewayUrl, "anthropic", slug, opts.anthropicKey, {
|
|
147
|
-
timeoutMs: opts.setup === true ? undefined : SERVED_PROBE_TIMEOUT_MS,
|
|
148
|
-
}));
|
|
149
|
-
}
|
|
150
|
-
catch (err) {
|
|
151
|
-
// --setup must know which agent it governs; a bare launch carries on
|
|
152
|
-
// with the id unresolved rather than guessing it from the name.
|
|
153
|
-
if (opts.setup === true)
|
|
154
|
-
throw err;
|
|
155
|
-
say(fmt.dim(`mnemom agent: couldn't confirm which agent the gateway serves for '${slug}' (${err instanceof Error ? err.message : String(err)}) — launching anyway.`));
|
|
156
|
-
return { slug, provisioned: false };
|
|
157
|
-
}
|
|
158
|
-
if (!cached)
|
|
159
|
-
writeServedAgent(cacheDir, fp, served);
|
|
160
|
-
if (served !== existing.id) {
|
|
161
|
-
if (opts.setup === true)
|
|
162
|
-
throw new AgentIdentityMismatchError(slug, existing.id, served);
|
|
163
|
-
warnServedMismatch(slug, existing.id, served);
|
|
164
|
-
return { slug, provisioned: false, agentId: served };
|
|
165
|
-
}
|
|
166
|
-
// On an EXPLICIT `mnemom agent setup`, repair/complete the cards on the
|
|
167
|
-
// already-claimed agent (e.g. a protection card that failed on a prior run
|
|
168
|
-
// before we minted its one-time grant). A bare launch leaves it alone.
|
|
169
|
-
if (opts.setup === true) {
|
|
170
|
-
say(fmt.dim(`mnemom agent: '${slug}' is already claimed (${existing.id}, the agent the gateway serves) — refreshing its cards.`));
|
|
171
|
-
await publishDefaultCards(existing.id);
|
|
172
|
-
persistAgentSlug(slug);
|
|
173
|
-
return { slug, provisioned: true, agentId: existing.id };
|
|
174
|
-
}
|
|
175
|
-
return { slug, provisioned: false, agentId: existing.id };
|
|
174
|
+
const fp = servedFingerprint(anchorKey, slug, opts.gatewayHost.replace(/\/+$/, ""));
|
|
175
|
+
let result;
|
|
176
|
+
try {
|
|
177
|
+
result = await confirmClaimed(opts, anchorKey, fp);
|
|
178
|
+
}
|
|
179
|
+
catch (err) {
|
|
180
|
+
if (!(err instanceof MnemomUnreachableError))
|
|
181
|
+
throw err;
|
|
182
|
+
const confirmed = readConfirmedAgent(cacheDir, fp);
|
|
183
|
+
if (confirmed && (err.servedId === undefined || err.servedId === confirmed)) {
|
|
184
|
+
say(fmt.warn(`mnemom agent: couldn't reach Mnemom to confirm '${slug}' (${err.message}) — launching on ${confirmed}, the agent this machine last confirmed claimed.`));
|
|
185
|
+
return { slug, agentId: confirmed, claimedNow: false };
|
|
176
186
|
}
|
|
187
|
+
throw new UnclaimedAgentError(`mnemom agent: couldn't reach Mnemom to confirm your agent '${slug}' (${err.message}); try again.`);
|
|
177
188
|
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
189
|
+
writeConfirmedAgent(cacheDir, fp, result.agentId);
|
|
190
|
+
return result;
|
|
191
|
+
}
|
|
192
|
+
async function confirmClaimed(opts, anchorKey, fp) {
|
|
193
|
+
const slug = opts.slug;
|
|
194
|
+
const agentId = await resolveServedAgent(opts, fp);
|
|
195
|
+
if (await isClaimed(slug, agentId)) {
|
|
196
|
+
if (opts.setup === true) {
|
|
197
|
+
// An explicit `mnemom agent setup` / --setup repairs or completes the cards (e.g. a
|
|
198
|
+
// protection card that failed on a prior run before its one-time grant was minted).
|
|
199
|
+
say(fmt.dim(`mnemom agent: '${slug}' is ${agentId}, already claimed — refreshing its cards.`));
|
|
200
|
+
await publishDefaultCards(agentId);
|
|
201
|
+
persistAgentSlug(slug);
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
say(fmt.dim(`mnemom agent: '${slug}' is ${agentId}, claimed.`));
|
|
205
|
+
}
|
|
206
|
+
return { slug, agentId, claimedNow: false };
|
|
207
|
+
}
|
|
208
|
+
// From here the agent is known to be unclaimed: every failure refuses, none falls back.
|
|
209
|
+
if (!opts.orgId) {
|
|
210
|
+
throw new UnclaimedAgentError(`mnemom agent: '${slug}' (${agentId}) is not claimed, and this launch has no org to claim it into. ` +
|
|
211
|
+
"Choose one with `mnemom org use <org>`, then run `mnemom agent` again.");
|
|
212
|
+
}
|
|
213
|
+
try {
|
|
214
|
+
const hashProof = deriveHashProof(anchorKey, slug);
|
|
215
|
+
await withRetry(() => claimAgent(agentId, { hashProof, orgId: opts.orgId }));
|
|
183
216
|
}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
217
|
+
catch (err) {
|
|
218
|
+
if (err instanceof MnemomApiError && err.code === "agent_cross_tenant") {
|
|
219
|
+
throw new UnclaimedAgentError(`mnemom agent: the agent for this key + name ('${slug}', ${agentId}) belongs to another Mnemom account. ` +
|
|
220
|
+
"Use a name of your own: `mnemom agent <scenario> --agent <a-name-of-your-own>`.");
|
|
188
221
|
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
// 4. Confirm the plan (interactive runs; --setup non-interactive proceeds).
|
|
194
|
-
const org = getActiveOrg();
|
|
195
|
-
const orgLabel = org ? `${org.name} (${org.slug})` : "your personal org";
|
|
196
|
-
if (interactive) {
|
|
197
|
-
say(fmt.section("Set up a governed coding agent"));
|
|
198
|
-
say(fmt.label(" Agent:", slug));
|
|
199
|
-
say(fmt.label(" Org:", orgLabel));
|
|
200
|
-
say(fmt.label(" Posture:", "observe (alignment + protection observe-only, all surfaces screened); goal contracts nudge"));
|
|
201
|
-
say(fmt.dim(" Births the agent through the gateway, claims it to your org, and publishes its cards."));
|
|
202
|
-
if (!(await askYesNo("Provision and claim this agent now?", true))) {
|
|
203
|
-
say(fmt.dim(" Skipped — launching under the unclaimed identity. Run `mnemom agent setup` anytime."));
|
|
204
|
-
return { slug, provisioned: false };
|
|
222
|
+
// A concurrent launch of the same user (another fleet lane) may have claimed it first.
|
|
223
|
+
if (await isClaimed(slug, agentId).catch(() => false)) {
|
|
224
|
+
say(fmt.dim(`mnemom agent: '${slug}' is ${agentId}, claimed by another launch just now.`));
|
|
225
|
+
return { slug, agentId, claimedNow: false };
|
|
205
226
|
}
|
|
227
|
+
throw new UnclaimedAgentError(`mnemom agent: couldn't claim '${slug}' (${agentId}) into ${opts.orgLabel} — ${describeApiError(err)}. ` +
|
|
228
|
+
"mnemom agent never runs on an unclaimed agent; fix that and run it again.");
|
|
206
229
|
}
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
// Publish the observe-only cards. A card write that can't complete (propagation
|
|
214
|
-
// right after the claim, or a missing cards:write scope) does NOT abort the
|
|
215
|
-
// provision — the agent is claimed and governed on whatever landed; we warn how
|
|
216
|
-
// to finish it. So `provisioned` reflects the claim, not a perfect card set.
|
|
217
|
-
await publishDefaultCards(agentId);
|
|
218
|
-
// 6. Remember the slug so future launches skip straight to usable.
|
|
230
|
+
say(fmt.success(`mnemom agent: claimed '${slug}' (${agentId}) in ${opts.orgLabel}.`));
|
|
231
|
+
await noteSameNamedAgent(slug, agentId);
|
|
232
|
+
// Publish the default (off) cards. A card write that can't complete does NOT undo the claim —
|
|
233
|
+
// the agent is claimed and governed on whatever landed; we warn how to finish it.
|
|
234
|
+
if (opts.setup !== false)
|
|
235
|
+
await publishDefaultCards(agentId);
|
|
219
236
|
persistAgentSlug(slug);
|
|
220
|
-
return { slug,
|
|
237
|
+
return { slug, agentId, claimedNow: true };
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Which mnm- agent the gateway serves for this launch's credential + slug. A definite failure
|
|
241
|
+
* throws UnclaimedAgentError; a transient one that outlives its retries throws
|
|
242
|
+
* MnemomUnreachableError. A launch never guesses its agent from the name.
|
|
243
|
+
*/
|
|
244
|
+
async function resolveServedAgent(opts, fp) {
|
|
245
|
+
const { slug } = opts;
|
|
246
|
+
const gatewayUrl = opts.gatewayHost.replace(/\/+$/, "");
|
|
247
|
+
const cacheDir = opts.cacheDir ?? MNEMOM_DIR;
|
|
248
|
+
try {
|
|
249
|
+
if (opts.anthropicKey) {
|
|
250
|
+
const anthropicKey = opts.anthropicKey;
|
|
251
|
+
// The answer is stable per (key, name, gateway) and the probe costs a completion, so a
|
|
252
|
+
// bare launch reuses a cached one and only probes (time-boxed) the first time; --setup
|
|
253
|
+
// re-asks.
|
|
254
|
+
const cached = opts.setup === true ? null : readServedAgent(cacheDir, fp);
|
|
255
|
+
if (cached)
|
|
256
|
+
return cached;
|
|
257
|
+
const served = await withRetry(() => birthThroughGateway(gatewayUrl, "anthropic", slug, anthropicKey, {
|
|
258
|
+
timeoutMs: opts.setup === true ? undefined : SERVED_PROBE_TIMEOUT_MS,
|
|
259
|
+
}));
|
|
260
|
+
writeServedAgent(cacheDir, fp, served);
|
|
261
|
+
return served;
|
|
262
|
+
}
|
|
263
|
+
// Subscription: the identity route makes no upstream call, so it is asked on every launch
|
|
264
|
+
// (a later claim can change which agent the key's owner is served).
|
|
265
|
+
const mnemomKey = opts.mnemomKey ?? "";
|
|
266
|
+
return await withRetry(() => resolveServedIdentity(gatewayUrl, slug, mnemomKey, { timeoutMs: SERVED_PROBE_TIMEOUT_MS }));
|
|
267
|
+
}
|
|
268
|
+
catch (err) {
|
|
269
|
+
if (err instanceof IdentityRouteMissingError) {
|
|
270
|
+
throw new UnclaimedAgentError(`mnemom agent: ${err.message}, so it can't confirm '${slug}' is claimed. ` +
|
|
271
|
+
"Launch with an Anthropic API key instead (`--auth api-key`), or try again once the gateway is updated.");
|
|
272
|
+
}
|
|
273
|
+
if (isTransient(err))
|
|
274
|
+
throw new MnemomUnreachableError(err);
|
|
275
|
+
throw new UnclaimedAgentError(`mnemom agent: the gateway wouldn't say which agent it serves for '${slug}' (${describeApiError(err)}). ` +
|
|
276
|
+
"mnemom agent never runs on an agent it can't confirm is claimed.");
|
|
277
|
+
}
|
|
221
278
|
}
|
|
222
279
|
/**
|
|
223
|
-
*
|
|
224
|
-
*
|
|
225
|
-
*
|
|
280
|
+
* Whether `agentId` is claimed. A 404/403 reads as unclaimed (an unclaimed sandbox agent can be
|
|
281
|
+
* invisible to a normal user), so the caller claims it. A transient failure is retried and then
|
|
282
|
+
* throws MnemomUnreachableError; any other failure refuses.
|
|
226
283
|
*/
|
|
227
|
-
function
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
284
|
+
async function isClaimed(slug, agentId) {
|
|
285
|
+
let agent;
|
|
286
|
+
try {
|
|
287
|
+
agent = await withRetry(() => getAgent(agentId));
|
|
288
|
+
}
|
|
289
|
+
catch (err) {
|
|
290
|
+
const status = err instanceof MnemomApiError ? err.effectiveStatus : undefined;
|
|
291
|
+
if (status === 404 || status === 403)
|
|
292
|
+
return false;
|
|
293
|
+
if (isTransient(err))
|
|
294
|
+
throw new MnemomUnreachableError(err, agentId);
|
|
295
|
+
throw new UnclaimedAgentError(`mnemom agent: couldn't read whether '${slug}' (${agentId}) is claimed — ${describeApiError(err)}. ` +
|
|
296
|
+
"mnemom agent never runs on an agent it can't confirm is claimed.");
|
|
297
|
+
}
|
|
298
|
+
// The org-member view carries the claimed_by column; the public projection a `claimed` flag.
|
|
299
|
+
return agent.claimed_by !== undefined ? !!agent.claimed_by : agent.claimed === true;
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* After a claim: if the caller's org already had an agent of this name that is NOT the served
|
|
303
|
+
* one (another key on the same name, MNE-7650), say so — this launch governs the served one.
|
|
304
|
+
* Best effort.
|
|
305
|
+
*/
|
|
306
|
+
async function noteSameNamedAgent(slug, servedId) {
|
|
307
|
+
const named = await getAgentByName(slug).catch(() => null);
|
|
308
|
+
if (!named || named.id === servedId)
|
|
309
|
+
return;
|
|
310
|
+
say(fmt.dim(` Note: '${slug}' in your org is ${named.id}, a different agent — the gateway identity is hash(key, name), ` +
|
|
311
|
+
`so this key runs as ${servedId}.`));
|
|
231
312
|
}
|
|
232
313
|
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
233
314
|
/**
|
|
@@ -273,14 +354,14 @@ async function tryPublishCard(name, write) {
|
|
|
273
354
|
}
|
|
274
355
|
return { name, ok: false, error: "unknown error" };
|
|
275
356
|
}
|
|
276
|
-
/** Publish both default
|
|
357
|
+
/** Publish both default (off) cards; never throws — warns on a card that can't be written. */
|
|
277
358
|
async function publishDefaultCards(agentId) {
|
|
278
359
|
const align = await tryPublishCard("alignment", () => putAlignmentCard(agentId, JSON.stringify(buildCodeAlignmentCard(agentId)), "application/json"));
|
|
279
360
|
// Protection is the "locked door": it needs a one-time cards:write:protection
|
|
280
361
|
// grant that the claim does NOT include (alignment rides a standing cap). Mint
|
|
281
|
-
// it with our human login JWT — the
|
|
282
|
-
//
|
|
283
|
-
// agent OAuth principals are rejected). The next write consumes the grant. A
|
|
362
|
+
// it with our human login JWT — the human running `mnemom agent` (which claims
|
|
363
|
+
// the agent for them) is the approval, and the mint endpoint accepts a human
|
|
364
|
+
// operator (only agent OAuth principals are rejected). The next write consumes the grant. A
|
|
284
365
|
// mint failure falls through to the write's retry + graceful degrade, but it
|
|
285
366
|
// is NAMED: the mint's error code (e.g. an ownership `agent_org_forbidden`)
|
|
286
367
|
// and `details.required_scope` are the actionable diagnosis — the downstream
|
|
@@ -296,11 +377,11 @@ async function publishDefaultCards(agentId) {
|
|
|
296
377
|
}
|
|
297
378
|
const prot = await tryPublishCard("protection", () => putProtectionCard(agentId, JSON.stringify(buildCodeProtectionCard(agentId)), "application/json"));
|
|
298
379
|
if (align.ok && prot.ok) {
|
|
299
|
-
say(fmt.success("mnemom agent:
|
|
380
|
+
say(fmt.success("mnemom agent: default posture published (alignment + protection off until you turn them on; goal contracts nudge)."));
|
|
300
381
|
return;
|
|
301
382
|
}
|
|
302
383
|
if (align.ok)
|
|
303
|
-
say(fmt.success("mnemom agent: alignment card published (
|
|
384
|
+
say(fmt.success("mnemom agent: alignment card published (off)."));
|
|
304
385
|
for (const c of [align, prot]) {
|
|
305
386
|
if (c.ok)
|
|
306
387
|
continue;
|
|
@@ -322,8 +403,9 @@ export async function agentSetupCommand(opts = {}) {
|
|
|
322
403
|
if (cellError)
|
|
323
404
|
throw new Error(cellError.replace("mnemom agent:", "mnemom agent setup:"));
|
|
324
405
|
// Sign in (offer sign-up) and enforce the MU balance before any account write.
|
|
406
|
+
let account;
|
|
325
407
|
try {
|
|
326
|
-
await ensureSessionAndBalance({});
|
|
408
|
+
account = await ensureSessionAndBalance({});
|
|
327
409
|
}
|
|
328
410
|
catch (err) {
|
|
329
411
|
if (isQuietPreambleExit(err))
|
|
@@ -338,14 +420,14 @@ export async function agentSetupCommand(opts = {}) {
|
|
|
338
420
|
: await resolveDefaultSlug({ strict: true });
|
|
339
421
|
if (!slug)
|
|
340
422
|
throw new Error("mnemom agent setup: agent slug sanitised to empty — pass --agent <slug>.");
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
423
|
+
await ensureClaimedServedAgent({
|
|
424
|
+
slug,
|
|
425
|
+
anthropicKey,
|
|
426
|
+
gatewayHost,
|
|
427
|
+
orgId: account.orgId,
|
|
428
|
+
orgLabel: account.orgLabel,
|
|
429
|
+
setup: true,
|
|
430
|
+
});
|
|
349
431
|
}
|
|
350
432
|
function persistAgentSlug(slug) {
|
|
351
433
|
try {
|
|
@@ -520,13 +602,13 @@ export async function resolvePreflightedAccount() {
|
|
|
520
602
|
const entitlement = await assertCodeEnabled(active?.org_id ?? "", orgLabel);
|
|
521
603
|
return { orgId: active?.org_id ?? entitlement?.org_id ?? "", orgLabel, entitlement };
|
|
522
604
|
}
|
|
523
|
-
// ── The default coding posture (cards
|
|
605
|
+
// ── The default coding posture (cards off; goal contracts nudge) ─────────────
|
|
524
606
|
function buildCodeAlignmentCard(agentId) {
|
|
525
607
|
return {
|
|
526
608
|
card_version: "unified/2026-04-26",
|
|
527
609
|
agent_id: agentId,
|
|
528
|
-
autonomy_mode: "
|
|
529
|
-
integrity_mode: "
|
|
610
|
+
autonomy_mode: "off",
|
|
611
|
+
integrity_mode: "off",
|
|
530
612
|
principal: { type: "agent", identifier: agentId, relationship: "delegated_authority" },
|
|
531
613
|
values: {
|
|
532
614
|
declared: ["transparency", "safety", "honesty", "user_control", "harm_prevention"],
|
|
@@ -563,7 +645,7 @@ function buildCodeProtectionCard(agentId) {
|
|
|
563
645
|
return {
|
|
564
646
|
card_version: "protection/2026-04-26",
|
|
565
647
|
agent_id: agentId,
|
|
566
|
-
mode: "
|
|
648
|
+
mode: "off",
|
|
567
649
|
thresholds: { warn: 0.6, quarantine: 0.8, block: 0.95 },
|
|
568
650
|
screen_surfaces: {
|
|
569
651
|
incoming: true,
|
package/dist/commands/agent.d.ts
CHANGED
|
@@ -97,9 +97,9 @@ export interface AgentOptions {
|
|
|
97
97
|
*/
|
|
98
98
|
compactionIntercept?: boolean;
|
|
99
99
|
/**
|
|
100
|
-
* Governed-agent
|
|
101
|
-
*
|
|
102
|
-
*
|
|
100
|
+
* Governed-agent cards. Every launch confirms (and if needed claims) the served
|
|
101
|
+
* agent regardless; true (`--setup`) re-asks the gateway and refreshes the
|
|
102
|
+
* default cards, false (`--no-setup`) skips the card writes.
|
|
103
103
|
*/
|
|
104
104
|
setup?: boolean;
|
|
105
105
|
/**
|
|
@@ -171,19 +171,16 @@ interface LaunchContinuationInput {
|
|
|
171
171
|
ccSessionExists: (ccSessionId: string) => boolean;
|
|
172
172
|
}
|
|
173
173
|
/**
|
|
174
|
-
* Record this launch in ~/.mnemom/sessions.jsonl
|
|
175
|
-
*
|
|
176
|
-
*
|
|
174
|
+
* Record this launch in ~/.mnemom/sessions.jsonl, with the claimed agent id the
|
|
175
|
+
* gateway serves; a ledger write failure warns to stderr and the launch proceeds
|
|
176
|
+
* regardless.
|
|
177
177
|
*/
|
|
178
178
|
export declare function recordSessionLaunch(ctx: {
|
|
179
179
|
session: string;
|
|
180
180
|
conversationId: string;
|
|
181
181
|
agent: string;
|
|
182
|
-
/**
|
|
183
|
-
|
|
184
|
-
* never asked, so fall back to the org's name lookup.
|
|
185
|
-
*/
|
|
186
|
-
agentId?: string | null;
|
|
182
|
+
/** The claimed mnm- id the gateway serves for this launch (ensureClaimedServedAgent). */
|
|
183
|
+
agentId: string;
|
|
187
184
|
account: {
|
|
188
185
|
orgId: string;
|
|
189
186
|
orgLabel: string;
|
|
@@ -200,7 +197,7 @@ export declare function recordSessionLaunch(ctx: {
|
|
|
200
197
|
contextProfile: ContextProfile | null;
|
|
201
198
|
/** The resolved compaction target this launch runs under (`resolveCompactAt`). */
|
|
202
199
|
compactAt: number | null;
|
|
203
|
-
}):
|
|
200
|
+
}): void;
|
|
204
201
|
/** The real I/O behind a subscription launch's Mnemom key (lib/subscription-login.ts). */
|
|
205
202
|
export declare function defaultMnemomKeyDeps(storePath?: string): MnemomKeyDeps;
|
|
206
203
|
/** The real machine behind lib/console-key.ts: the macOS keychain via `security`, no shell. */
|