@mnemom/mnemom 0.17.24 → 0.17.26
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-config.js +9 -0
- package/dist/commands/agent-doctor.d.ts +3 -2
- package/dist/commands/agent-doctor.js +22 -7
- package/dist/commands/agent-goal.js +7 -2
- package/dist/commands/agent-sessions.js +2 -1
- package/dist/commands/agent-setup.d.ts +104 -4
- package/dist/commands/agent-setup.js +414 -18
- package/dist/commands/agent-slack.d.ts +14 -1
- package/dist/commands/agent-slack.js +108 -23
- package/dist/commands/agent.d.ts +29 -1
- package/dist/commands/agent.js +89 -12
- package/dist/commands/wrap.d.ts +2 -0
- package/dist/commands/wrap.js +13 -1
- package/dist/index.js +19 -5
- package/dist/lib/agent-identity-route.d.ts +21 -3
- package/dist/lib/agent-identity-route.js +42 -15
- package/dist/lib/agent-ledger.d.ts +5 -0
- package/dist/lib/agent.d.ts +62 -1
- package/dist/lib/agent.js +55 -0
- package/dist/lib/api.d.ts +33 -1
- package/dist/lib/api.js +10 -0
- package/dist/lib/experimental.js +14 -2
- package/dist/lib/goal-api.d.ts +3 -3
- package/dist/lib/goal-api.js +5 -2
- package/dist/lib/launcher-config.d.ts +18 -0
- package/dist/lib/launcher-config.js +47 -1
- package/dist/lib/oauth.d.ts +7 -0
- package/dist/lib/oauth.js +92 -17
- package/dist/lib/served-agent-cache.d.ts +8 -1
- package/dist/lib/served-agent-cache.js +11 -2
- package/dist/lib/slack-channel.d.ts +1 -0
- package/dist/lib/slack-channel.js +30 -12
- package/dist/lib/slack-route.d.ts +72 -9
- package/dist/lib/slack-route.js +113 -24
- package/dist/lib/slack-router.d.ts +37 -2
- package/dist/lib/slack-router.js +98 -13
- package/dist/lib/slack-state.d.ts +105 -4
- package/dist/lib/slack-state.js +230 -11
- package/dist/lib/subscription-login.d.ts +12 -3
- package/dist/lib/subscription-login.js +19 -7
- package/dist/mcp/handoff-server.mjs +31 -3
- package/dist/rc-proxy.mjs +14 -0
- package/package.json +1 -1
|
@@ -6,25 +6,28 @@
|
|
|
6
6
|
* mnemom agent slack login # store the bot + app-level tokens (0600)
|
|
7
7
|
* mnemom agent slack logout # remove them
|
|
8
8
|
* mnemom agent slack status # tokens, bot identity, router, live sessions
|
|
9
|
-
* mnemom agent slack post [--channel C] [--thread TS] [--icon-emoji E] <text…|->
|
|
9
|
+
* mnemom agent slack post [--channel C] [--thread TS] [--icon-emoji E] [--] <text…|->
|
|
10
|
+
* # --help prints usage; unknown flags are refused
|
|
10
11
|
* mnemom agent slack delete [--channel C] <ts> # delete a message THIS session posted
|
|
11
12
|
* mnemom agent slack inbox [--peek] # unread messages addressed to THIS session
|
|
13
|
+
* mnemom agent slack alias # list handle → session aliases
|
|
14
|
+
* mnemom agent slack alias <handle> <session> # route @handle to <session> (idempotent)
|
|
15
|
+
* mnemom agent slack alias --remove <handle> # stop routing @handle
|
|
12
16
|
* mnemom agent slack listen # this session's channel server (spawned by --slack)
|
|
13
17
|
* mnemom agent slack router # the per-operator Socket Mode router (foreground)
|
|
14
18
|
*
|
|
15
19
|
* The poster name is MNEMOM_AGENT_SESSION_NAME, which the launcher sets in the
|
|
16
20
|
* session's environment — an agent cannot pick another session's name here.
|
|
17
21
|
*/
|
|
18
|
-
import {
|
|
19
|
-
import { join } from "node:path";
|
|
22
|
+
import { readFileSync } from "node:fs";
|
|
20
23
|
import { configKeySpec, loadLauncherConfig, writeLauncherConfig } from "../lib/launcher-config.js";
|
|
21
24
|
import { runChannelServer } from "../lib/slack-channel.js";
|
|
22
25
|
import { mergeStore, readStore } from "../lib/launcher-store.js";
|
|
23
26
|
import { askInput, isInteractive } from "../lib/prompt.js";
|
|
24
27
|
import { explainSlackError, looksLikeAppToken, looksLikeBotToken, deleteMessage, postMessage, resolveSlackToken, sessionPosterName, slackAppManifest, slackCall, SLACK_APP_TOKEN_STORE_KEY, SLACK_TOKEN_STORE_KEY, } from "../lib/slack.js";
|
|
25
28
|
import { routerStatus, runRouter } from "../lib/slack-router.js";
|
|
26
|
-
import {
|
|
27
|
-
import { findOwnPost, liveSessions, readInboxFrom, recordPost,
|
|
29
|
+
import { normHandle } from "../lib/slack-route.js";
|
|
30
|
+
import { findOwnPost, freshInbox, listAliases, liveSessions, readInboxFrom, readOffset, recordPost, removeAlias, setAlias, writeOffset, } from "../lib/slack-state.js";
|
|
28
31
|
const out = (line = "") => console.log(line);
|
|
29
32
|
/** Entry point for `mnemom agent slack <verb> [args...]`. Throws on misuse. */
|
|
30
33
|
export async function agentSlackCommand(argv) {
|
|
@@ -50,12 +53,14 @@ export async function agentSlackCommand(argv) {
|
|
|
50
53
|
return del(rest);
|
|
51
54
|
case "inbox":
|
|
52
55
|
return inbox(rest);
|
|
56
|
+
case "alias":
|
|
57
|
+
return alias(rest);
|
|
53
58
|
case "router":
|
|
54
59
|
return router();
|
|
55
60
|
case "listen":
|
|
56
61
|
return listen();
|
|
57
62
|
default:
|
|
58
|
-
throw new Error(`mnemom agent slack: unknown verb '${verb}'. Use manifest | login | logout | status | post | delete | inbox | listen | router.`);
|
|
63
|
+
throw new Error(`mnemom agent slack: unknown verb '${verb}'. Use manifest | login | logout | status | post | delete | inbox | alias | listen | router.`);
|
|
59
64
|
}
|
|
60
65
|
}
|
|
61
66
|
function defaultAppName() {
|
|
@@ -118,18 +123,46 @@ async function status() {
|
|
|
118
123
|
out(`router: ${r.running ? `running (pid ${r.pid})` : "not running"}`);
|
|
119
124
|
const live = liveSessions();
|
|
120
125
|
out(`listening: ${live.length ? live.join(", ") : "none"}`);
|
|
121
|
-
const
|
|
126
|
+
const cfg = loadLauncherConfig();
|
|
127
|
+
const aliases = listAliases();
|
|
128
|
+
out(`aliases: ${aliases.length ? aliases.map((a) => `@${a.handle} → ${a.session}`).join(", ") : "none"}${cfg.slackAliases ? "" : " (routing off — `mnemom agent config set slack_aliases true`)"}`);
|
|
129
|
+
out(`sticky threads: ${cfg.slackStickyThreads ? "on" : "off"}`);
|
|
130
|
+
const allow = cfg.slackAllow ?? [];
|
|
122
131
|
out(`allowed senders: ${allow.length ? allow.join(", ") : "none — no message will reach a session"}`);
|
|
123
132
|
}
|
|
133
|
+
export const POST_USAGE = `Usage: mnemom agent slack post [options] [--] <text…|->
|
|
134
|
+
|
|
135
|
+
Post a message to Slack as this session.
|
|
136
|
+
|
|
137
|
+
Options:
|
|
138
|
+
-c, --channel <C> channel name or id (default: slack_channel from \`mnemom agent config\`)
|
|
139
|
+
-t, --thread <TS> reply in the thread whose parent message has this ts
|
|
140
|
+
--icon-emoji <E> emoji to show as the poster's icon
|
|
141
|
+
-h, --help show this help and exit; nothing is posted
|
|
142
|
+
|
|
143
|
+
Text:
|
|
144
|
+
The remaining words, joined by spaces. \`-\` (or no words with a piped stdin) reads stdin.
|
|
145
|
+
Any other word starting with "-" before \`--\` is refused as an unknown flag. A message
|
|
146
|
+
that starts with "-" must come after \`--\`, e.g. mnemom agent slack post -c C1 -- "-1 on this"`;
|
|
147
|
+
const POST_SHORT_FLAGS = new Set(["-c", "-t", "-h"]);
|
|
148
|
+
/**
|
|
149
|
+
* Parse `post` arguments. Before a `--` separator, `--help`/`-h` asks for usage and any
|
|
150
|
+
* other dash-word that is not a known flag is refused, so a typo or a guessed flag can
|
|
151
|
+
* never be posted as message text. Everything after `--` is text, verbatim.
|
|
152
|
+
*/
|
|
124
153
|
export function parsePostArgs(argv) {
|
|
125
154
|
const res = { text: "" };
|
|
126
155
|
const words = [];
|
|
156
|
+
let unknown;
|
|
127
157
|
for (let i = 0; i < argv.length; i++) {
|
|
128
158
|
const a = argv[i];
|
|
129
159
|
const val = () => {
|
|
130
160
|
const v = argv[++i];
|
|
131
|
-
|
|
161
|
+
// A flag is never a value: `--channel --help` must not eat the --help. (A short
|
|
162
|
+
// dash-word that is not a flag stays a value: `--icon-emoji -1` is Slack's thumbsdown.)
|
|
163
|
+
if (v === undefined || v.startsWith("--") || POST_SHORT_FLAGS.has(v)) {
|
|
132
164
|
throw new Error(`mnemom agent slack post: ${a} needs a value.`);
|
|
165
|
+
}
|
|
133
166
|
return v;
|
|
134
167
|
};
|
|
135
168
|
if (a === "--channel" || a === "-c")
|
|
@@ -138,18 +171,32 @@ export function parsePostArgs(argv) {
|
|
|
138
171
|
res.thread = val();
|
|
139
172
|
else if (a === "--icon-emoji")
|
|
140
173
|
res.iconEmoji = val();
|
|
174
|
+
else if (a === "--help" || a === "-h")
|
|
175
|
+
res.help = true;
|
|
141
176
|
else if (a === "--") {
|
|
142
177
|
words.push(...argv.slice(i + 1));
|
|
143
178
|
break;
|
|
144
179
|
}
|
|
180
|
+
else if (a.startsWith("-") && a !== "-")
|
|
181
|
+
unknown ??= a;
|
|
145
182
|
else
|
|
146
183
|
words.push(a);
|
|
147
184
|
}
|
|
185
|
+
// Help wins over an unknown flag: someone asking for usage should get it.
|
|
186
|
+
if (res.help)
|
|
187
|
+
return { help: true, text: "" };
|
|
188
|
+
if (unknown !== undefined) {
|
|
189
|
+
throw new Error(`mnemom agent slack post: unknown flag ${unknown}; put message text after --. See \`mnemom agent slack post --help\`.`);
|
|
190
|
+
}
|
|
148
191
|
res.text = words.join(" ");
|
|
149
192
|
return res;
|
|
150
193
|
}
|
|
151
194
|
async function post(argv) {
|
|
152
195
|
const args = parsePostArgs(argv);
|
|
196
|
+
if (args.help) {
|
|
197
|
+
process.stdout.write(POST_USAGE + "\n");
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
153
200
|
let text = args.text;
|
|
154
201
|
if (text === "-" || (!text && !process.stdin.isTTY))
|
|
155
202
|
text = readFileSync(0, "utf8");
|
|
@@ -196,37 +243,75 @@ async function del(argv) {
|
|
|
196
243
|
out(JSON.stringify({ ok: true, deleted: own.ts, channel: own.channel }));
|
|
197
244
|
}
|
|
198
245
|
// ── inbox (pull) ─────────────────────────────────────────────────────────────
|
|
199
|
-
// Normalised like the inbox file name, so a session name can never escape offsets/.
|
|
200
|
-
const offsetFile = (session) => join(slackDir(), "offsets", `${normSession(session)}.off`);
|
|
201
|
-
function readOffset(session) {
|
|
202
|
-
try {
|
|
203
|
-
return Number(readFileSync(offsetFile(session), "utf8")) || 0;
|
|
204
|
-
}
|
|
205
|
-
catch {
|
|
206
|
-
return 0;
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
246
|
/** Print (and, unless --peek, consume) the messages routed to this session. */
|
|
210
247
|
async function inbox(argv) {
|
|
211
248
|
const session = sessionPosterName(process.env);
|
|
212
249
|
if (!session)
|
|
213
250
|
throw new Error("mnemom agent slack inbox: not inside a `mnemom agent` session.");
|
|
214
251
|
const { messages, offset } = readInboxFrom(session, readOffset(session));
|
|
215
|
-
if (!argv.includes("--peek"))
|
|
216
|
-
|
|
217
|
-
|
|
252
|
+
if (!argv.includes("--peek"))
|
|
253
|
+
writeOffset(session, offset);
|
|
254
|
+
// A held message past its expiry was already reported undelivered to its sender.
|
|
255
|
+
out(JSON.stringify(freshInbox(messages)));
|
|
256
|
+
}
|
|
257
|
+
export const ALIAS_USAGE = `Usage: mnemom agent slack alias # list
|
|
258
|
+
mnemom agent slack alias <handle> <session> # route @handle to <session>
|
|
259
|
+
mnemom agent slack alias --remove <handle> # stop routing @handle
|
|
260
|
+
|
|
261
|
+
A handle is letters, digits and dashes (a leading @ is fine). The session is the name
|
|
262
|
+
\`mnemom agent slack status\` lists under "listening" (e.g. agent-hdc-henry-dorsett-case);
|
|
263
|
+
it need not be live yet. Setting the same pair again is a no-op.
|
|
264
|
+
|
|
265
|
+
Aliases only route when \`mnemom agent config set slack_aliases true\` is set (off by
|
|
266
|
+
default). Then a message for a session that is not listening is held: the sender is
|
|
267
|
+
told it is waiting, and if the session has not read it within an hour, that it was
|
|
268
|
+
not delivered.`;
|
|
269
|
+
/**
|
|
270
|
+
* `alias` — list / set / remove operator-registered handle → session routes. Pure
|
|
271
|
+
* local state (lib/slack-state.ts); no Slack call, so a session can register its own
|
|
272
|
+
* handle at launch before the router has seen it.
|
|
273
|
+
*/
|
|
274
|
+
async function alias(argv) {
|
|
275
|
+
if (argv.includes("--help") || argv.includes("-h")) {
|
|
276
|
+
process.stdout.write(ALIAS_USAGE + "\n");
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
const rm = argv.indexOf("--remove");
|
|
280
|
+
if (rm !== -1) {
|
|
281
|
+
const handle = argv[rm + 1];
|
|
282
|
+
if (!handle || argv.length !== 2 || handle.startsWith("-"))
|
|
283
|
+
throw new Error(`mnemom agent slack alias: --remove takes exactly one handle.\n${ALIAS_USAGE}`);
|
|
284
|
+
if (!normHandle(handle))
|
|
285
|
+
throw new Error(`mnemom agent slack alias: '${handle}' is not a usable handle (letters, digits, dashes).`);
|
|
286
|
+
const removed = removeAlias(handle);
|
|
287
|
+
out(JSON.stringify({ ok: true, removed, handle: normHandle(handle) }));
|
|
288
|
+
return;
|
|
218
289
|
}
|
|
219
|
-
|
|
290
|
+
const unknown = argv.find((a) => a.startsWith("-"));
|
|
291
|
+
if (unknown)
|
|
292
|
+
throw new Error(`mnemom agent slack alias: unknown flag ${unknown}.\n${ALIAS_USAGE}`);
|
|
293
|
+
if (argv.length === 0) {
|
|
294
|
+
out(JSON.stringify(listAliases()));
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
if (argv.length !== 2)
|
|
298
|
+
throw new Error(`mnemom agent slack alias: expected <handle> <session>.\n${ALIAS_USAGE}`);
|
|
299
|
+
const rec = setAlias(argv[0], argv[1]);
|
|
300
|
+
out(JSON.stringify({ ok: true, ...rec }));
|
|
220
301
|
}
|
|
221
302
|
async function router() {
|
|
222
303
|
const store = readStore();
|
|
223
304
|
const app = resolveSlackToken(process.env, store, "app").token;
|
|
224
305
|
if (!app)
|
|
225
306
|
throw new Error("mnemom agent slack router: no app-level token — run `mnemom agent slack login`.");
|
|
307
|
+
const cfg = loadLauncherConfig();
|
|
226
308
|
await runRouter({
|
|
227
309
|
botToken: botToken(),
|
|
228
310
|
appToken: app,
|
|
229
|
-
allow:
|
|
311
|
+
allow: cfg.slackAllow ?? [],
|
|
312
|
+
// Both off unless the operator turned them on: off, routing is unchanged.
|
|
313
|
+
aliases: cfg.slackAliases === true,
|
|
314
|
+
stickyThreads: cfg.slackStickyThreads === true,
|
|
230
315
|
});
|
|
231
316
|
}
|
|
232
317
|
async function listen() {
|
package/dist/commands/agent.d.ts
CHANGED
|
@@ -49,8 +49,13 @@ export interface AgentOptions {
|
|
|
49
49
|
implicit?: boolean;
|
|
50
50
|
/** `--lead-directed`: see `ContractFlags.leadDirected` (lib/agent.ts, MNE-8152). */
|
|
51
51
|
leadDirected?: boolean;
|
|
52
|
-
/**
|
|
52
|
+
/**
|
|
53
|
+
* `--agent` (or launcher.toml `agent`): a NAME (x-mnemom-agent; defaults to `agent-<handle>`), or
|
|
54
|
+
* a full `mnm-<uuid>` id, which PINS the launch to that agent (x-mnemom-agent-id).
|
|
55
|
+
*/
|
|
53
56
|
agent?: string;
|
|
57
|
+
/** launcher.toml `agent_id`: the pin when no `--agent` is given (selectLaunchAgent). */
|
|
58
|
+
agentId?: string;
|
|
54
59
|
conversationId?: string;
|
|
55
60
|
cli?: string;
|
|
56
61
|
remoteControl?: boolean;
|
|
@@ -222,6 +227,8 @@ export declare function recordSessionLaunch(ctx: {
|
|
|
222
227
|
agent: string;
|
|
223
228
|
/** The claimed mnm- id the gateway serves for this launch (ensureClaimedServedAgent). */
|
|
224
229
|
agentId: string;
|
|
230
|
+
/** The launch pinned `agentId` (`--agent mnm-<uuid>` / launcher.toml `agent_id`). */
|
|
231
|
+
pinned?: boolean;
|
|
225
232
|
account: {
|
|
226
233
|
orgId: string;
|
|
227
234
|
orgLabel: string;
|
|
@@ -321,6 +328,22 @@ export interface BaseUrlProxyOptions {
|
|
|
321
328
|
* pass through it), exits with this process, and is killed on exit.
|
|
322
329
|
*/
|
|
323
330
|
export declare function startBaseUrlProxy(door: string, done: (baseUrl: string | null) => void, opts?: BaseUrlProxyOptions): void;
|
|
331
|
+
/**
|
|
332
|
+
* The three secret lines the Remote Control proxy reads on stdin (anthropic, mnemom, openai). The
|
|
333
|
+
* Mnemom key rides line 2 on BOTH auth modes when the launch has one — the proxy forwards it as
|
|
334
|
+
* x-mnemom-api-key either way; a subscription launch always has one, an api-key launch has one
|
|
335
|
+
* when this machine stores one or the launch pins. Never logged.
|
|
336
|
+
*/
|
|
337
|
+
export declare function rcProxyStdin(ctx: {
|
|
338
|
+
anthropicKey?: string;
|
|
339
|
+
mnemomKey?: string;
|
|
340
|
+
}): string;
|
|
341
|
+
/**
|
|
342
|
+
* The agent id a launch pins, or undefined for a name launch: `--agent` when it is a full
|
|
343
|
+
* `mnm-<uuid>` (any case), else launcher.toml `agent_id` (only when no `--agent` was given —
|
|
344
|
+
* selectLaunchAgent). A truncated/garbled `mnm-…` throws instead of becoming a name.
|
|
345
|
+
*/
|
|
346
|
+
export declare function resolveLaunchPin(options: Pick<AgentOptions, "agent" | "agentId">): string | undefined;
|
|
324
347
|
/**
|
|
325
348
|
* Locates the `handoff` MCP server (src/mcp/handoff-server.mjs in dev, dist/mcp/handoff-server.mjs
|
|
326
349
|
* once built — a raw, uncompiled `.mjs` file that `copy-assets.mjs` copies verbatim, exactly like
|
|
@@ -383,6 +406,11 @@ export declare function logLaunch(ctx: {
|
|
|
383
406
|
cli: ResolvedCli;
|
|
384
407
|
/** The plugin line (`pluginPlanLine`): `off`, `off (<why>)` or `<name> (<dir>)`. */
|
|
385
408
|
plugin: string;
|
|
409
|
+
/**
|
|
410
|
+
* The composed Safe House mode (`readComposedProtection`), printed after the goal lines so
|
|
411
|
+
* the goal-contract mode and the protection mode are never mistaken for each other.
|
|
412
|
+
*/
|
|
413
|
+
protection?: string;
|
|
386
414
|
}): void;
|
|
387
415
|
/** The launch line for the yolo default: permission prompts are skipped, and how to keep them. */
|
|
388
416
|
export declare function permissionPromptsLine(cli: ResolvedCli): string;
|
package/dist/commands/agent.js
CHANGED
|
@@ -34,7 +34,7 @@ import { fileURLToPath } from "node:url";
|
|
|
34
34
|
import { MEASURED_ENV, statuslineLaunch } from "../lib/statusline.js";
|
|
35
35
|
import { getApiUrl, getGatewayUrl, MNEMOM_DIR } from "../lib/config.js";
|
|
36
36
|
import { createApiKey } from "../lib/api.js";
|
|
37
|
-
import { MNEMOM_KEY_STORE_FIELD, resolveAgentAuth, sayRetiredConsoleNotice, resolveSubscriptionMnemomKey, subscriptionChildEnv, subscriptionShape, } from "../lib/subscription-login.js";
|
|
37
|
+
import { MNEMOM_KEY_STORE_FIELD, resolveAgentAuth, sayRetiredConsoleNotice, readMnemomKey, 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
39
|
import { isLoggedIn } from "../lib/auth.js";
|
|
40
40
|
import { loadCliConfig } from "../lib/cli-config.js";
|
|
@@ -44,8 +44,8 @@ import { fmt } from "../lib/format.js";
|
|
|
44
44
|
import { askInput, askSelect, askYesNo, isInteractive } from "../lib/prompt.js";
|
|
45
45
|
import { authConfigured, chooseAuth, detectClaudeLogin, readClaudeLoginState, } from "../lib/login-detect.js";
|
|
46
46
|
import { goalAlignmentLine, resolveSessionSetup } from "./agent-session.js";
|
|
47
|
-
import { defaultAgentSlug, ensureClaimedServedAgent, ensureSessionAndBalance, isQuietPreambleExit, resolveDefaultSlug, resolvePreflightedAccount, } from "./agent-setup.js";
|
|
48
|
-
import { assembleContract, buildGoalModeHeader, buildCustomHeaders, buildHandoffMcpConfigJson, buildSessionConfig, buildSessionConfigB64, assertCompactAtCompatible, assertOffProfileCompatible, compactAtWindowWarning, describeCompactAt, launchWindow, resolveCompactAt, applyGatewayHintHeaders, resolveCompactionIntercept, resolveContextHandoff, resolveContextProfile, resolveHandoffGatewayHost, resolveUsageSavings, HANDOFF_RC_KEY_ENV_NAME, isProdGateway, launchPreferenceExplicit, launchPreflighted, remoteControlAllowed, applyToolSearchDefault, DISABLE_1M_ENV, readCcSettings, resolveWindowModel, resolveAnthropicDoor, resolveLaunchModel, resolveConversationId, resolveGatewayHost, singleCellError, resolveLaunchShape, resolveRemoteControlShape, passthroughName, sanitizeAgentName, resumeCommand, sessionDisplayName, SESSION_NAME_ENV, SESSION_CONFIG_HEADER, } from "../lib/agent.js";
|
|
47
|
+
import { defaultAgentSlug, ensureClaimedServedAgent, ensureSessionAndBalance, isQuietPreambleExit, readComposedProtection, resolveDefaultSlug, resolvePinnedAgent, resolvePreflightedAccount, } from "./agent-setup.js";
|
|
48
|
+
import { assembleContract, buildGoalModeHeader, buildCustomHeaders, buildHandoffMcpConfigJson, buildSessionConfig, buildSessionConfigB64, assertCompactAtCompatible, assertOffProfileCompatible, compactAtWindowWarning, describeCompactAt, launchWindow, resolveCompactAt, applyGatewayHintHeaders, resolveCompactionIntercept, resolveContextHandoff, resolveContextProfile, resolveHandoffGatewayHost, resolveUsageSavings, HANDOFF_RC_KEY_ENV_NAME, HANDOFF_RC_MNEMOM_KEY_ENV_NAME, malformedAgentIdMessage, parseAgentRef, isProdGateway, launchPreferenceExplicit, launchPreflighted, remoteControlAllowed, applyToolSearchDefault, DISABLE_1M_ENV, readCcSettings, resolveWindowModel, resolveAnthropicDoor, resolveLaunchModel, resolveConversationId, resolveGatewayHost, singleCellError, resolveLaunchShape, resolveRemoteControlShape, passthroughName, sanitizeAgentName, resumeCommand, sessionDisplayName, SESSION_NAME_ENV, SESSION_CONFIG_HEADER, } from "../lib/agent.js";
|
|
49
49
|
import { LAUNCHER_STORE_PATH, mergeStore } from "../lib/launcher-store.js";
|
|
50
50
|
import { slackChannelArgs } from "../lib/slack-launch.js";
|
|
51
51
|
import { appendPluginDirsEnv, applyPluginEnv, pluginEnabled, pluginPlanLine, resolvePluginDir, } from "../lib/claude-plugin.js";
|
|
@@ -172,7 +172,13 @@ export async function agentCommand(scenarioArg, options = {}) {
|
|
|
172
172
|
// "code-agent" — see defaultAgentSlug; provisioning claims exactly this slug.
|
|
173
173
|
// `let` because a brand-new user who signs in during the pre-launch gate below
|
|
174
174
|
// gets their real handle re-derived once the session exists.)
|
|
175
|
-
|
|
175
|
+
//
|
|
176
|
+
// A full `mnm-<uuid>` (--agent, else launcher.toml agent_id) PINS the launch to that agent: its
|
|
177
|
+
// name is read after sign-in (resolvePinnedAgent) and the gateway must serve exactly it. A
|
|
178
|
+
// truncated `mnm-…` is refused rather than sent as a name — the gateway would birth a new agent
|
|
179
|
+
// named after the id (how a resume hint's id became a stray agent, 2026-10-08).
|
|
180
|
+
const pin = resolveLaunchPin(options);
|
|
181
|
+
let agent = pin ?? sanitizeAgentName(options.agent?.trim() || defaultAgentSlug());
|
|
176
182
|
if (!agent) {
|
|
177
183
|
throw new Error("mnemom agent: --agent sanitised to an empty string — pick a value with at least one letter or digit.");
|
|
178
184
|
}
|
|
@@ -300,7 +306,7 @@ export async function agentCommand(scenarioArg, options = {}) {
|
|
|
300
306
|
// A defaulted slug is `agent-<handle>` from the login (resolved for real after
|
|
301
307
|
// sign-in, below). Signed in: show that name. Signed out, or no usable handle:
|
|
302
308
|
// say where it comes from rather than print the "agent-user" fallback as if it were it.
|
|
303
|
-
if (!options.agent?.trim()) {
|
|
309
|
+
if (!pin && !options.agent?.trim()) {
|
|
304
310
|
agent =
|
|
305
311
|
((await isLoggedIn()) && sanitizeAgentName(await resolveDefaultSlug())) ||
|
|
306
312
|
DRY_RUN_SIGNED_OUT_AGENT;
|
|
@@ -327,7 +333,9 @@ export async function agentCommand(scenarioArg, options = {}) {
|
|
|
327
333
|
if (auth === "subscription") {
|
|
328
334
|
say("mnemom agent: auth = subscription — Claude Code would use your claude.ai login (Team/Enterprise plans only) with a Mnemom API key; no Anthropic key.");
|
|
329
335
|
}
|
|
330
|
-
say(
|
|
336
|
+
say(pin
|
|
337
|
+
? `mnemom agent: pinned to ${pin} — the real launch reads its name, then runs only if the gateway serves exactly that agent (yours, claimed); it never claims or creates one.`
|
|
338
|
+
: `mnemom agent: the real launch first confirms the agent the gateway serves for '${agent}' is claimed — claiming it into the org the launch bills if it is not — and never runs on an unclaimed agent.`);
|
|
331
339
|
if (expNotice)
|
|
332
340
|
say(expNotice);
|
|
333
341
|
if (shape !== "terminal") {
|
|
@@ -404,18 +412,33 @@ export async function agentCommand(scenarioArg, options = {}) {
|
|
|
404
412
|
// Subscription login: Remote Control only for RC-entitled (internal) accounts, and the governed identity anchors on a
|
|
405
413
|
// Mnemom API key (minted once and stored if there is none). Resolved after sign-in so
|
|
406
414
|
// minting can use the session.
|
|
415
|
+
//
|
|
416
|
+
// An api-key launch sends this machine's existing Mnemom key too (env, else the store; never
|
|
417
|
+
// minted), so the gateway can resolve the key's owner — the safe core of #2291, which minted a
|
|
418
|
+
// new key and swapped it in on subscription launches (reverted, #2323). A pinned launch needs a
|
|
419
|
+
// key on every auth mode (the gateway honours a pin only with one), so it mints one if there
|
|
420
|
+
// is none. A subscription launch's key is resolved exactly as before.
|
|
407
421
|
let mnemomKey;
|
|
408
422
|
if (auth === "subscription") {
|
|
409
423
|
shape = subscriptionShape(shape, shapeExplicit, rcAllowed, say);
|
|
410
424
|
mnemomKey = await resolveSubscriptionMnemomKey(defaultMnemomKeyDeps());
|
|
411
425
|
}
|
|
426
|
+
else {
|
|
427
|
+
mnemomKey = pin
|
|
428
|
+
? await resolveSubscriptionMnemomKey(defaultMnemomKeyDeps(), "pinning an agent")
|
|
429
|
+
: readMnemomKey(defaultMnemomKeyDeps());
|
|
430
|
+
}
|
|
412
431
|
// Now that a session exists, a defaulted slug uses the real login handle from
|
|
413
432
|
// /v1/auth/me (the stored token has no email, so the pre-sign-in value was the
|
|
414
433
|
// "agent-user" fallback). Only when the caller didn't name an agent. Under an
|
|
415
434
|
// explicit --setup the lookup is strict and a missing handle aborts the launch:
|
|
416
435
|
// provisioning "agent-user" would govern a different gateway identity than the
|
|
417
436
|
// one a prior setup under the real handle created (MNE-7650).
|
|
418
|
-
if (
|
|
437
|
+
if (pin) {
|
|
438
|
+
// The pinned agent must be visible and claimed; its own name rides as x-mnemom-agent.
|
|
439
|
+
agent = (await resolvePinnedAgent(pin)).slug;
|
|
440
|
+
}
|
|
441
|
+
else if (!options.agent?.trim()) {
|
|
419
442
|
const refreshed = sanitizeAgentName(await resolveDefaultSlug({ strict: options.setup === true }));
|
|
420
443
|
if (refreshed)
|
|
421
444
|
agent = refreshed;
|
|
@@ -425,16 +448,26 @@ export async function agentCommand(scenarioArg, options = {}) {
|
|
|
425
448
|
// launch bills (account.orgId — the active/default org is never changed); anything else
|
|
426
449
|
// refuses the launch. No prompt and no unclaimed fallback, interactive or not, preflighted or
|
|
427
450
|
// not. `--no-setup` only skips the default-card writes.
|
|
428
|
-
|
|
451
|
+
// A pinned launch takes ensureClaimedServedAgent's pin path: served must equal the pin.
|
|
452
|
+
const served = await ensureClaimedServedAgent({
|
|
429
453
|
slug: agent,
|
|
430
454
|
anthropicKey,
|
|
431
455
|
mnemomKey,
|
|
456
|
+
pin,
|
|
432
457
|
gatewayHost,
|
|
433
458
|
orgId: account.orgId,
|
|
434
459
|
orgLabel: account.orgLabel,
|
|
435
460
|
setup: options.setup,
|
|
436
461
|
governance: options.governance,
|
|
437
462
|
});
|
|
463
|
+
const servedAgentId = served.agentId;
|
|
464
|
+
// The composed Safe House mode for the launch summary (MNE-8612). Started now so the read
|
|
465
|
+
// overlaps the continuation and ledger work below, awaited at logLaunch. Never rejects.
|
|
466
|
+
const protection = readComposedProtection(servedAgentId);
|
|
467
|
+
// The gateway refused the stored Mnemom key an api-key launch offered: its requests must not
|
|
468
|
+
// carry it either (the gateway would 401 every one).
|
|
469
|
+
if (served.mnemomKeyRejected)
|
|
470
|
+
mnemomKey = undefined;
|
|
438
471
|
// ── conversation id ── an implicit relaunch continues its earlier conversation (IG5 #1, D1)
|
|
439
472
|
// so the goal the gateway inferred there is still the one it steers by. Resolved here, after
|
|
440
473
|
// sign-in and the agent refresh, because a resume needs the org, agent and gateway host to
|
|
@@ -524,6 +557,7 @@ export async function agentCommand(scenarioArg, options = {}) {
|
|
|
524
557
|
conversationId,
|
|
525
558
|
agent,
|
|
526
559
|
agentId: servedAgentId,
|
|
560
|
+
pinned: pin !== undefined,
|
|
527
561
|
account,
|
|
528
562
|
gatewayHost,
|
|
529
563
|
shape,
|
|
@@ -547,6 +581,7 @@ export async function agentCommand(scenarioArg, options = {}) {
|
|
|
547
581
|
goalMode,
|
|
548
582
|
cli,
|
|
549
583
|
plugin: launchPlugin.line,
|
|
584
|
+
protection: await protection,
|
|
550
585
|
});
|
|
551
586
|
if (pluginWanted && cli.name === "claude" && !bundledPluginDir) {
|
|
552
587
|
say(fmt.warn("mnemom agent: the Mnemom plugin folder is not in this install — launching without the Mnemom band and /mnemom."));
|
|
@@ -610,6 +645,7 @@ export async function agentCommand(scenarioArg, options = {}) {
|
|
|
610
645
|
gatewayHost: resolveHandoffGatewayHost(door),
|
|
611
646
|
anthropicKeySource,
|
|
612
647
|
agent,
|
|
648
|
+
agentId: pin,
|
|
613
649
|
conversationId,
|
|
614
650
|
sessionConfigB64,
|
|
615
651
|
serverScript: resolveHandoffServerScript(),
|
|
@@ -628,6 +664,7 @@ export async function agentCommand(scenarioArg, options = {}) {
|
|
|
628
664
|
anthropicKey,
|
|
629
665
|
mnemomKey,
|
|
630
666
|
agent,
|
|
667
|
+
agentId: pin,
|
|
631
668
|
scenario,
|
|
632
669
|
conversationId,
|
|
633
670
|
envelope,
|
|
@@ -641,7 +678,10 @@ export async function agentCommand(scenarioArg, options = {}) {
|
|
|
641
678
|
handoffMcpConfigPath,
|
|
642
679
|
resumeHint: (id) => resumeHintLine({
|
|
643
680
|
scenario,
|
|
644
|
-
agent
|
|
681
|
+
// A pinned launch resumes pinned to the agent it was SERVED (servedAgentId === pin):
|
|
682
|
+
// the id is the one value that resumes into the same agent. A named launch repeats its
|
|
683
|
+
// name; a defaulted one repeats nothing.
|
|
684
|
+
agent: pin ? servedAgentId : options.agent?.trim() ? agent : undefined,
|
|
645
685
|
ccSessionId: id,
|
|
646
686
|
}),
|
|
647
687
|
knownCcSessionId: cc.id ?? continuedCcSessionId,
|
|
@@ -656,6 +696,7 @@ export async function agentCommand(scenarioArg, options = {}) {
|
|
|
656
696
|
anthropicKey,
|
|
657
697
|
mnemomKey,
|
|
658
698
|
agent,
|
|
699
|
+
agentId: pin,
|
|
659
700
|
conversationId,
|
|
660
701
|
scenario,
|
|
661
702
|
envelope,
|
|
@@ -838,6 +879,7 @@ export function recordSessionLaunch(ctx) {
|
|
|
838
879
|
conversation_id: ctx.conversationId,
|
|
839
880
|
agent_slug: ctx.agent,
|
|
840
881
|
agent_id: ctx.agentId,
|
|
882
|
+
...(ctx.pinned ? { agent_pinned: true } : {}),
|
|
841
883
|
org_id: ctx.account.orgId,
|
|
842
884
|
org_label: ctx.account.orgLabel,
|
|
843
885
|
gateway_host: ctx.gatewayHost,
|
|
@@ -1132,6 +1174,7 @@ function launchTerminal(ctx) {
|
|
|
1132
1174
|
anthropicKey: ctx.anthropicKey,
|
|
1133
1175
|
mnemomKey: ctx.mnemomKey,
|
|
1134
1176
|
agent: ctx.agent,
|
|
1177
|
+
agentId: ctx.agentId,
|
|
1135
1178
|
conversationId: ctx.conversationId,
|
|
1136
1179
|
contractB64: ctx.envelope?.base64,
|
|
1137
1180
|
goalMode: ctx.goalMode ?? undefined,
|
|
@@ -1305,6 +1348,31 @@ export function startBaseUrlProxy(door, done, opts = {}) {
|
|
|
1305
1348
|
// ───────────────────────────────────────────────────────────────────────────
|
|
1306
1349
|
// Remote Control launch (terminal RC + headless server) — behind the bundled proxy
|
|
1307
1350
|
// ───────────────────────────────────────────────────────────────────────────
|
|
1351
|
+
/**
|
|
1352
|
+
* The three secret lines the Remote Control proxy reads on stdin (anthropic, mnemom, openai). The
|
|
1353
|
+
* Mnemom key rides line 2 on BOTH auth modes when the launch has one — the proxy forwards it as
|
|
1354
|
+
* x-mnemom-api-key either way; a subscription launch always has one, an api-key launch has one
|
|
1355
|
+
* when this machine stores one or the launch pins. Never logged.
|
|
1356
|
+
*/
|
|
1357
|
+
export function rcProxyStdin(ctx) {
|
|
1358
|
+
return `${ctx.anthropicKey ?? ""}\n${ctx.mnemomKey ?? ""}\n\n`;
|
|
1359
|
+
}
|
|
1360
|
+
/**
|
|
1361
|
+
* The agent id a launch pins, or undefined for a name launch: `--agent` when it is a full
|
|
1362
|
+
* `mnm-<uuid>` (any case), else launcher.toml `agent_id` (only when no `--agent` was given —
|
|
1363
|
+
* selectLaunchAgent). A truncated/garbled `mnm-…` throws instead of becoming a name.
|
|
1364
|
+
*/
|
|
1365
|
+
export function resolveLaunchPin(options) {
|
|
1366
|
+
const fromFlag = options.agent?.trim();
|
|
1367
|
+
const raw = fromFlag || options.agentId?.trim();
|
|
1368
|
+
if (!raw)
|
|
1369
|
+
return undefined;
|
|
1370
|
+
const ref = parseAgentRef(raw);
|
|
1371
|
+
if (ref.kind === "malformed-id" || (!fromFlag && ref.kind === "name")) {
|
|
1372
|
+
throw new Error(malformedAgentIdMessage(raw, fromFlag ? "--agent" : "agent_id"));
|
|
1373
|
+
}
|
|
1374
|
+
return ref.kind === "id" ? ref.id : undefined;
|
|
1375
|
+
}
|
|
1308
1376
|
function launchRemoteControl(ctx) {
|
|
1309
1377
|
warnAdapter(ctx.cli);
|
|
1310
1378
|
for (const tool of ["node", "openssl"]) {
|
|
@@ -1321,6 +1389,7 @@ function launchRemoteControl(ctx) {
|
|
|
1321
1389
|
...process.env,
|
|
1322
1390
|
MNEMOM_AGENT_RC_DOOR: ctx.door,
|
|
1323
1391
|
MNEMOM_AGENT_RC_AGENT: ctx.agent,
|
|
1392
|
+
MNEMOM_AGENT_RC_AGENT_ID: ctx.agentId ?? "",
|
|
1324
1393
|
MNEMOM_AGENT_RC_CONVERSATION_ID: ctx.conversationId,
|
|
1325
1394
|
MNEMOM_AGENT_RC_CONTRACT_B64: ctx.envelope?.base64 ?? "",
|
|
1326
1395
|
MNEMOM_AGENT_RC_GOAL_MODE: ctx.goalMode ?? "",
|
|
@@ -1353,8 +1422,9 @@ function launchRemoteControl(ctx) {
|
|
|
1353
1422
|
};
|
|
1354
1423
|
process.on("exit", onExit);
|
|
1355
1424
|
// Secrets on stdin — three lines (anthropic, mnemom, openai). An api-key launch sends the
|
|
1356
|
-
// Anthropic key
|
|
1357
|
-
//
|
|
1425
|
+
// Anthropic key, plus this machine's Mnemom key when it has one (the key principal; always
|
|
1426
|
+
// with a pin); a subscription launch sends the Mnemom key only (the identity anchor — the
|
|
1427
|
+
// user's OAuth bearer is what pays). openai is always empty on this Anthropic-only door.
|
|
1358
1428
|
// A proxy that exits before reading them turns this write into an EPIPE; that is
|
|
1359
1429
|
// not a failure of its own — the exited-before-READY check below refuses the
|
|
1360
1430
|
// launch. Any other stdin error fails closed like a spawn error.
|
|
@@ -1365,7 +1435,7 @@ function launchRemoteControl(ctx) {
|
|
|
1365
1435
|
onExit();
|
|
1366
1436
|
process.exit(1);
|
|
1367
1437
|
});
|
|
1368
|
-
proxy.stdin?.end(
|
|
1438
|
+
proxy.stdin?.end(rcProxyStdin(ctx));
|
|
1369
1439
|
let stdoutBuf = "";
|
|
1370
1440
|
const start = Date.now();
|
|
1371
1441
|
const tryStart = () => {
|
|
@@ -1462,6 +1532,10 @@ function spawnRcCli(ctx) {
|
|
|
1462
1532
|
// (Never on a subscription launch — handoff is turned off there, so there is no key to set.)
|
|
1463
1533
|
if (ctx.handoffMcpConfigPath && ctx.anthropicKey) {
|
|
1464
1534
|
childEnv[HANDOFF_RC_KEY_ENV_NAME] = ctx.anthropicKey;
|
|
1535
|
+
// The Mnemom key the session's own requests carry (the proxy adds it), so a pinned handoff
|
|
1536
|
+
// call proves the same principal.
|
|
1537
|
+
if (ctx.mnemomKey)
|
|
1538
|
+
childEnv[HANDOFF_RC_MNEMOM_KEY_ENV_NAME] = ctx.mnemomKey;
|
|
1465
1539
|
}
|
|
1466
1540
|
// The plugin's non-secret launch facts (MNEMOM_CC_*), also after the strip loop: not an
|
|
1467
1541
|
// ANTHROPIC_* var, so the RC gate ignores them, and nothing here is a key or a header value.
|
|
@@ -1748,10 +1822,13 @@ export function logLaunch(ctx) {
|
|
|
1748
1822
|
if (ctx.envelope) {
|
|
1749
1823
|
say(`mnemom agent: contract statement: ${ctx.envelope.contract.statement}`);
|
|
1750
1824
|
say(`mnemom agent: contract sha256[0:16]: ${ctx.envelope.sha}`);
|
|
1825
|
+
say(`mnemom agent: contract mode: ${ctx.envelope.contract.mode ?? "nudge"}`);
|
|
1751
1826
|
}
|
|
1752
1827
|
else if (ctx.goalMode) {
|
|
1753
1828
|
say(`mnemom agent: goal mode: ${ctx.goalMode} (inferred from your messages; watch x-mnemom-goal-revision / x-mnemom-goal-steer on each response)`);
|
|
1754
1829
|
}
|
|
1830
|
+
if (ctx.protection !== undefined)
|
|
1831
|
+
say(`mnemom agent: protection: ${ctx.protection}`);
|
|
1755
1832
|
}
|
|
1756
1833
|
/** The launch line for the yolo default: permission prompts are skipped, and how to keep them. */
|
|
1757
1834
|
export function permissionPromptsLine(cli) {
|
package/dist/commands/wrap.d.ts
CHANGED
|
@@ -26,5 +26,7 @@ export interface WrapOptions {
|
|
|
26
26
|
export declare function wrapCommand(options?: WrapOptions): Promise<void>;
|
|
27
27
|
export declare function birthThroughGateway(gatewayUrl: string, provider: Provider, agentName: string, providerKey: string, opts?: {
|
|
28
28
|
timeoutMs?: number;
|
|
29
|
+
/** More request headers (e.g. the launch's `x-mnemom-api-key`, so the probe is asked as the launch will be). */
|
|
30
|
+
extraHeaders?: Record<string, string>;
|
|
29
31
|
}): Promise<string>;
|
|
30
32
|
export {};
|
package/dist/commands/wrap.js
CHANGED
|
@@ -211,6 +211,8 @@ export async function birthThroughGateway(gatewayUrl, provider, agentName, provi
|
|
|
211
211
|
if (provider === "anthropic") {
|
|
212
212
|
headers["anthropic-version"] = "2023-06-01";
|
|
213
213
|
}
|
|
214
|
+
if (opts.extraHeaders)
|
|
215
|
+
Object.assign(headers, opts.extraHeaders);
|
|
214
216
|
let url;
|
|
215
217
|
let body;
|
|
216
218
|
switch (provider) {
|
|
@@ -244,7 +246,17 @@ export async function birthThroughGateway(gatewayUrl, provider, agentName, provi
|
|
|
244
246
|
const agentId = response.headers.get(AGENT_HEADER);
|
|
245
247
|
if (!response.ok && !agentId) {
|
|
246
248
|
const text = await response.text().catch(() => "");
|
|
247
|
-
|
|
249
|
+
// The gateway's `{error:{code}}` (e.g. agent_name_ambiguous) rides on the error so a caller
|
|
250
|
+
// can say what to fix.
|
|
251
|
+
let code;
|
|
252
|
+
try {
|
|
253
|
+
const c = JSON.parse(text)?.error?.code;
|
|
254
|
+
code = typeof c === "string" ? c : undefined;
|
|
255
|
+
}
|
|
256
|
+
catch {
|
|
257
|
+
code = undefined;
|
|
258
|
+
}
|
|
259
|
+
throw new MnemomApiError(response.status, `Gateway returned ${response.status}: ${text.slice(0, 300)}`, { code });
|
|
248
260
|
}
|
|
249
261
|
if (!agentId || !/^mnm-/.test(agentId)) {
|
|
250
262
|
throw new Error(`Gateway returned no mnm- agent id on the ${AGENT_HEADER} response header (status ${response.status}).`);
|
package/dist/index.js
CHANGED
|
@@ -37,7 +37,7 @@ import { applyConfigToEnv, launcherEntries, loadLauncherConfig, withoutDisabledE
|
|
|
37
37
|
import { loadExperimentalState } from "./lib/cli-config.js";
|
|
38
38
|
import { assertEnabled, EXPERIMENTAL_CONTRACT, gateCheck, ignoredSettingLines, reportCliError, } from "./lib/experimental.js";
|
|
39
39
|
import { experimentalDisableCommand, experimentalEnableCommand, experimentalListCommand, runExperimentalMigration, } from "./commands/experimental.js";
|
|
40
|
-
import { parseCompactAt, parseContextProfile, resolveImplicit, } from "./lib/agent.js";
|
|
40
|
+
import { parseCompactAt, parseContextProfile, resolveImplicit, selectLaunchAgent, } from "./lib/agent.js";
|
|
41
41
|
import { wrapCommand } from "./commands/wrap.js";
|
|
42
42
|
import { onboardCommand } from "./commands/onboard.js";
|
|
43
43
|
import { skillsListCommand, skillsDescribeCommand } from "./commands/skills.js";
|
|
@@ -154,9 +154,11 @@ const agentCmd = program
|
|
|
154
154
|
.option("--budget <usd>", "Guardrail: spend ceiling in USD (positive number)")
|
|
155
155
|
.option("--max-turns <n>", "Guardrail: turn ceiling (positive integer)")
|
|
156
156
|
.option("--stall <n>", "Guardrail: consecutive no-write turns (positive integer)")
|
|
157
|
-
.option("--mode <nudge|enforce>", "
|
|
157
|
+
.option("--mode <nudge|enforce>", "Goal-contract mode (MNE-8199, default nudge). Under enforce, a crossed turn/budget/time " +
|
|
158
158
|
"ceiling REFUSES the next request on this conversation until the owner raises it via a " +
|
|
159
|
-
"contract update
|
|
159
|
+
"contract update. Under nudge the ceiling only nudges, unchanged. Requires --goal. " +
|
|
160
|
+
"This is not the Safe House protection mode, which composes from the agent, team, org and " +
|
|
161
|
+
"platform cards (strictest wins). See `mnemom protection` for that mode.")
|
|
160
162
|
.option("--lead-directed", "Seal the standard lead-directed requirement: own-branch push and draft/ready PR creation, " +
|
|
161
163
|
"from a git worktree, on the lead's direction — not merging. Requires --goal; idempotent.")
|
|
162
164
|
.addOption(new Option("--implicit", "EXPERIMENTAL (`mnemom experimental enable implicit`): implicit goal alignment — the gateway infers the goal from your messages and revises it as you steer (the default while the feature is on; not combinable with --goal/--requirement/--allow/--forbid/--goal-id/--mode/--lead-directed; the guardrail ceilings still apply)").hideHelp(hiddenUnless("implicit")))
|
|
@@ -313,7 +315,14 @@ const agentCmd = program
|
|
|
313
315
|
// Sub-verb: `mnemom agent setup` provisions/claims the governed agent and
|
|
314
316
|
// never launches. The agent slug follows the same precedence as a launch.
|
|
315
317
|
if (scenario === "setup") {
|
|
316
|
-
await agentSetupCommand({
|
|
318
|
+
await agentSetupCommand({
|
|
319
|
+
...selectLaunchAgent({
|
|
320
|
+
flag: program.opts().agent,
|
|
321
|
+
configAgent: cfg.agent,
|
|
322
|
+
configAgentId: cfg.agentId,
|
|
323
|
+
}),
|
|
324
|
+
governance,
|
|
325
|
+
});
|
|
317
326
|
return;
|
|
318
327
|
}
|
|
319
328
|
const numToStr = (n) => n === undefined ? undefined : String(n);
|
|
@@ -351,7 +360,12 @@ const agentCmd = program
|
|
|
351
360
|
maxTurns: opts.maxTurns ?? (contractIntended ? numToStr(cfg.guardrails?.maxTurns) : undefined),
|
|
352
361
|
stall: opts.stall ?? (contractIntended ? numToStr(cfg.guardrails?.stallTurns) : undefined),
|
|
353
362
|
mode: opts.mode,
|
|
354
|
-
agent
|
|
363
|
+
// --agent wins; else launcher.toml agent_id (a pin) over agent (a name).
|
|
364
|
+
...selectLaunchAgent({
|
|
365
|
+
flag: parentOpts.agent,
|
|
366
|
+
configAgent: cfg.agent,
|
|
367
|
+
configAgentId: cfg.agentId,
|
|
368
|
+
}),
|
|
355
369
|
conversationId: opts.conversationId,
|
|
356
370
|
cli: opts.cli,
|
|
357
371
|
remoteControl: opts.remoteControl,
|