@mnemom/mnemom 0.17.25 → 0.17.27
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 +25 -9
- package/dist/commands/agent-goal.js +7 -2
- package/dist/commands/agent-sessions.js +2 -1
- package/dist/commands/agent-setup.d.ts +166 -9
- package/dist/commands/agent-setup.js +719 -54
- package/dist/commands/agent-slack.d.ts +4 -0
- package/dist/commands/agent-slack.js +67 -21
- package/dist/commands/agent.d.ts +29 -1
- package/dist/commands/agent.js +133 -17
- 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 +75 -8
- package/dist/lib/agent.js +83 -14
- 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/model-tier.d.ts +75 -0
- package/dist/lib/model-tier.js +166 -0
- 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
|
@@ -10,6 +10,9 @@
|
|
|
10
10
|
* # --help prints usage; unknown flags are refused
|
|
11
11
|
* mnemom agent slack delete [--channel C] <ts> # delete a message THIS session posted
|
|
12
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
|
|
13
16
|
* mnemom agent slack listen # this session's channel server (spawned by --slack)
|
|
14
17
|
* mnemom agent slack router # the per-operator Socket Mode router (foreground)
|
|
15
18
|
*
|
|
@@ -33,4 +36,5 @@ export declare const POST_USAGE = "Usage: mnemom agent slack post [options] [--]
|
|
|
33
36
|
* never be posted as message text. Everything after `--` is text, verbatim.
|
|
34
37
|
*/
|
|
35
38
|
export declare function parsePostArgs(argv: string[]): PostArgs;
|
|
39
|
+
export declare const ALIAS_USAGE = "Usage: mnemom agent slack alias # list\n mnemom agent slack alias <handle> <session> # route @handle to <session>\n mnemom agent slack alias --remove <handle> # stop routing @handle\n\nA handle is letters, digits and dashes (a leading @ is fine). The session is the name\n`mnemom agent slack status` lists under \"listening\" (e.g. agent-hdc-henry-dorsett-case);\nit need not be live yet. Setting the same pair again is a no-op.\n\nAliases only route when `mnemom agent config set slack_aliases true` is set (off by\ndefault). Then a message for a session that is not listening is held: the sender is\ntold it is waiting, and if the session has not read it within an hour, that it was\nnot delivered.";
|
|
36
40
|
export {};
|
|
@@ -10,22 +10,24 @@
|
|
|
10
10
|
* # --help prints usage; unknown flags are refused
|
|
11
11
|
* mnemom agent slack delete [--channel C] <ts> # delete a message THIS session posted
|
|
12
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
|
|
13
16
|
* mnemom agent slack listen # this session's channel server (spawned by --slack)
|
|
14
17
|
* mnemom agent slack router # the per-operator Socket Mode router (foreground)
|
|
15
18
|
*
|
|
16
19
|
* The poster name is MNEMOM_AGENT_SESSION_NAME, which the launcher sets in the
|
|
17
20
|
* session's environment — an agent cannot pick another session's name here.
|
|
18
21
|
*/
|
|
19
|
-
import {
|
|
20
|
-
import { join } from "node:path";
|
|
22
|
+
import { readFileSync } from "node:fs";
|
|
21
23
|
import { configKeySpec, loadLauncherConfig, writeLauncherConfig } from "../lib/launcher-config.js";
|
|
22
24
|
import { runChannelServer } from "../lib/slack-channel.js";
|
|
23
25
|
import { mergeStore, readStore } from "../lib/launcher-store.js";
|
|
24
26
|
import { askInput, isInteractive } from "../lib/prompt.js";
|
|
25
27
|
import { explainSlackError, looksLikeAppToken, looksLikeBotToken, deleteMessage, postMessage, resolveSlackToken, sessionPosterName, slackAppManifest, slackCall, SLACK_APP_TOKEN_STORE_KEY, SLACK_TOKEN_STORE_KEY, } from "../lib/slack.js";
|
|
26
28
|
import { routerStatus, runRouter } from "../lib/slack-router.js";
|
|
27
|
-
import {
|
|
28
|
-
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";
|
|
29
31
|
const out = (line = "") => console.log(line);
|
|
30
32
|
/** Entry point for `mnemom agent slack <verb> [args...]`. Throws on misuse. */
|
|
31
33
|
export async function agentSlackCommand(argv) {
|
|
@@ -51,12 +53,14 @@ export async function agentSlackCommand(argv) {
|
|
|
51
53
|
return del(rest);
|
|
52
54
|
case "inbox":
|
|
53
55
|
return inbox(rest);
|
|
56
|
+
case "alias":
|
|
57
|
+
return alias(rest);
|
|
54
58
|
case "router":
|
|
55
59
|
return router();
|
|
56
60
|
case "listen":
|
|
57
61
|
return listen();
|
|
58
62
|
default:
|
|
59
|
-
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.`);
|
|
60
64
|
}
|
|
61
65
|
}
|
|
62
66
|
function defaultAppName() {
|
|
@@ -119,7 +123,11 @@ async function status() {
|
|
|
119
123
|
out(`router: ${r.running ? `running (pid ${r.pid})` : "not running"}`);
|
|
120
124
|
const live = liveSessions();
|
|
121
125
|
out(`listening: ${live.length ? live.join(", ") : "none"}`);
|
|
122
|
-
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 ?? [];
|
|
123
131
|
out(`allowed senders: ${allow.length ? allow.join(", ") : "none — no message will reach a session"}`);
|
|
124
132
|
}
|
|
125
133
|
export const POST_USAGE = `Usage: mnemom agent slack post [options] [--] <text…|->
|
|
@@ -235,37 +243,75 @@ async function del(argv) {
|
|
|
235
243
|
out(JSON.stringify({ ok: true, deleted: own.ts, channel: own.channel }));
|
|
236
244
|
}
|
|
237
245
|
// ── inbox (pull) ─────────────────────────────────────────────────────────────
|
|
238
|
-
// Normalised like the inbox file name, so a session name can never escape offsets/.
|
|
239
|
-
const offsetFile = (session) => join(slackDir(), "offsets", `${normSession(session)}.off`);
|
|
240
|
-
function readOffset(session) {
|
|
241
|
-
try {
|
|
242
|
-
return Number(readFileSync(offsetFile(session), "utf8")) || 0;
|
|
243
|
-
}
|
|
244
|
-
catch {
|
|
245
|
-
return 0;
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
246
|
/** Print (and, unless --peek, consume) the messages routed to this session. */
|
|
249
247
|
async function inbox(argv) {
|
|
250
248
|
const session = sessionPosterName(process.env);
|
|
251
249
|
if (!session)
|
|
252
250
|
throw new Error("mnemom agent slack inbox: not inside a `mnemom agent` session.");
|
|
253
251
|
const { messages, offset } = readInboxFrom(session, readOffset(session));
|
|
254
|
-
if (!argv.includes("--peek"))
|
|
255
|
-
|
|
256
|
-
|
|
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;
|
|
257
289
|
}
|
|
258
|
-
|
|
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 }));
|
|
259
301
|
}
|
|
260
302
|
async function router() {
|
|
261
303
|
const store = readStore();
|
|
262
304
|
const app = resolveSlackToken(process.env, store, "app").token;
|
|
263
305
|
if (!app)
|
|
264
306
|
throw new Error("mnemom agent slack router: no app-level token — run `mnemom agent slack login`.");
|
|
307
|
+
const cfg = loadLauncherConfig();
|
|
265
308
|
await runRouter({
|
|
266
309
|
botToken: botToken(),
|
|
267
310
|
appToken: app,
|
|
268
|
-
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,
|
|
269
315
|
});
|
|
270
316
|
}
|
|
271
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,12 +44,13 @@ 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, passthroughModel, 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";
|
|
52
52
|
import { CLI_VERSION } from "../version.js";
|
|
53
|
+
import { fetchTierIndex, profileMayMisfit, profileTierWarning, resolveTier, tierLaunchModel, } from "../lib/model-tier.js";
|
|
53
54
|
/** The CLI's own provider-key store (zero 1Password): ~/.mnemom/launcher.json, 0600. */
|
|
54
55
|
const CODE_STORE = LAUNCHER_STORE_PATH;
|
|
55
56
|
const say = (line = "") => console.error(line);
|
|
@@ -117,16 +118,46 @@ function sayCompactAt(compactAt, window) {
|
|
|
117
118
|
* The launch's window (resolveWindowModel) — Claude Code only: it is the adapter that reads the
|
|
118
119
|
* window off the model id. `null` for any other CLI (or none resolved).
|
|
119
120
|
*/
|
|
121
|
+
/** Claude Code's settings files for this launch's cwd (lib/agent readCcSettings). */
|
|
122
|
+
function ccSettings() {
|
|
123
|
+
return readCcSettings({
|
|
124
|
+
cwd: process.cwd(),
|
|
125
|
+
configDir: (process.env.CLAUDE_CONFIG_DIR ?? "").trim() || join(homedir(), ".claude"),
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* The launch line when the chosen context profile is a poor fit for the launched model's tier
|
|
130
|
+
* (lib/model-tier), else null. Asks the gateway's /models.json for the tier only when the chosen
|
|
131
|
+
* profile is a poor fit for some tier (no profile ⇒ the gateway's tier default applies, nothing to
|
|
132
|
+
* warn about; `aggressive` fits every tier). `gatewayOrigin` is the origin of this launch's own
|
|
133
|
+
* Anthropic door (resolveHandoffGatewayHost), so the tier is read from the cell the session's
|
|
134
|
+
* model traffic goes to, even under a MNEMOM_AGENT_ANTHROPIC_GATEWAY door override. Warn-only:
|
|
135
|
+
* every step fails closed to "no line" on its own (readCcSettings skips unreadable files,
|
|
136
|
+
* fetchTierIndex turns any fetch/parse failure into null, the rest is pure), so it never refuses a
|
|
137
|
+
* launch.
|
|
138
|
+
*/
|
|
139
|
+
async function contextProfileTierWarning(profile, options, cli, gatewayOrigin) {
|
|
140
|
+
if (!profileMayMisfit(profile))
|
|
141
|
+
return null;
|
|
142
|
+
const { model, aliasEnv } = tierLaunchModel({
|
|
143
|
+
explicit: options.model,
|
|
144
|
+
passthroughModel: passthroughModel(options.passthrough ?? []),
|
|
145
|
+
env: process.env,
|
|
146
|
+
// Claude Code's own settings files name its model only for a Claude Code launch.
|
|
147
|
+
settings: cli?.name === "claude" ? ccSettings() : [],
|
|
148
|
+
});
|
|
149
|
+
const index = await fetchTierIndex(gatewayOrigin);
|
|
150
|
+
if (index === null)
|
|
151
|
+
return null;
|
|
152
|
+
return profileTierWarning({ profile, model, tier: resolveTier(model, index, aliasEnv) });
|
|
153
|
+
}
|
|
120
154
|
function windowModelFor(options, cli) {
|
|
121
155
|
return cli?.name === "claude"
|
|
122
156
|
? resolveWindowModel({
|
|
123
157
|
explicit: options.model,
|
|
124
158
|
passthrough: options.passthrough ?? [],
|
|
125
159
|
env: process.env,
|
|
126
|
-
settings:
|
|
127
|
-
cwd: process.cwd(),
|
|
128
|
-
configDir: (process.env.CLAUDE_CONFIG_DIR ?? "").trim() || join(homedir(), ".claude"),
|
|
129
|
-
}),
|
|
160
|
+
settings: ccSettings,
|
|
130
161
|
})
|
|
131
162
|
: null;
|
|
132
163
|
}
|
|
@@ -172,7 +203,13 @@ export async function agentCommand(scenarioArg, options = {}) {
|
|
|
172
203
|
// "code-agent" — see defaultAgentSlug; provisioning claims exactly this slug.
|
|
173
204
|
// `let` because a brand-new user who signs in during the pre-launch gate below
|
|
174
205
|
// gets their real handle re-derived once the session exists.)
|
|
175
|
-
|
|
206
|
+
//
|
|
207
|
+
// A full `mnm-<uuid>` (--agent, else launcher.toml agent_id) PINS the launch to that agent: its
|
|
208
|
+
// name is read after sign-in (resolvePinnedAgent) and the gateway must serve exactly it. A
|
|
209
|
+
// truncated `mnm-…` is refused rather than sent as a name — the gateway would birth a new agent
|
|
210
|
+
// named after the id (how a resume hint's id became a stray agent, 2026-10-08).
|
|
211
|
+
const pin = resolveLaunchPin(options);
|
|
212
|
+
let agent = pin ?? sanitizeAgentName(options.agent?.trim() || defaultAgentSlug());
|
|
176
213
|
if (!agent) {
|
|
177
214
|
throw new Error("mnemom agent: --agent sanitised to an empty string — pick a value with at least one letter or digit.");
|
|
178
215
|
}
|
|
@@ -201,7 +238,12 @@ export async function agentCommand(scenarioArg, options = {}) {
|
|
|
201
238
|
// ── door ──
|
|
202
239
|
const gatewayHost = resolveGatewayHost(env);
|
|
203
240
|
const door = resolveAnthropicDoor(env);
|
|
204
|
-
const cellError = singleCellError({
|
|
241
|
+
const cellError = singleCellError({
|
|
242
|
+
door,
|
|
243
|
+
gateway: gatewayHost,
|
|
244
|
+
apiUrl: getApiUrl(),
|
|
245
|
+
cellGateway: getGatewayUrl(),
|
|
246
|
+
});
|
|
205
247
|
if (cellError)
|
|
206
248
|
throw new Error(cellError);
|
|
207
249
|
const cellNote = isProdGateway(env) ? "us-2/prod" : `custom (${gatewayHost})`;
|
|
@@ -300,7 +342,7 @@ export async function agentCommand(scenarioArg, options = {}) {
|
|
|
300
342
|
// A defaulted slug is `agent-<handle>` from the login (resolved for real after
|
|
301
343
|
// sign-in, below). Signed in: show that name. Signed out, or no usable handle:
|
|
302
344
|
// say where it comes from rather than print the "agent-user" fallback as if it were it.
|
|
303
|
-
if (!options.agent?.trim()) {
|
|
345
|
+
if (!pin && !options.agent?.trim()) {
|
|
304
346
|
agent =
|
|
305
347
|
((await isLoggedIn()) && sanitizeAgentName(await resolveDefaultSlug())) ||
|
|
306
348
|
DRY_RUN_SIGNED_OUT_AGENT;
|
|
@@ -327,7 +369,9 @@ export async function agentCommand(scenarioArg, options = {}) {
|
|
|
327
369
|
if (auth === "subscription") {
|
|
328
370
|
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
371
|
}
|
|
330
|
-
say(
|
|
372
|
+
say(pin
|
|
373
|
+
? `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.`
|
|
374
|
+
: `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
375
|
if (expNotice)
|
|
332
376
|
say(expNotice);
|
|
333
377
|
if (shape !== "terminal") {
|
|
@@ -404,18 +448,33 @@ export async function agentCommand(scenarioArg, options = {}) {
|
|
|
404
448
|
// Subscription login: Remote Control only for RC-entitled (internal) accounts, and the governed identity anchors on a
|
|
405
449
|
// Mnemom API key (minted once and stored if there is none). Resolved after sign-in so
|
|
406
450
|
// minting can use the session.
|
|
451
|
+
//
|
|
452
|
+
// An api-key launch sends this machine's existing Mnemom key too (env, else the store; never
|
|
453
|
+
// minted), so the gateway can resolve the key's owner — the safe core of #2291, which minted a
|
|
454
|
+
// new key and swapped it in on subscription launches (reverted, #2323). A pinned launch needs a
|
|
455
|
+
// key on every auth mode (the gateway honours a pin only with one), so it mints one if there
|
|
456
|
+
// is none. A subscription launch's key is resolved exactly as before.
|
|
407
457
|
let mnemomKey;
|
|
408
458
|
if (auth === "subscription") {
|
|
409
459
|
shape = subscriptionShape(shape, shapeExplicit, rcAllowed, say);
|
|
410
460
|
mnemomKey = await resolveSubscriptionMnemomKey(defaultMnemomKeyDeps());
|
|
411
461
|
}
|
|
462
|
+
else {
|
|
463
|
+
mnemomKey = pin
|
|
464
|
+
? await resolveSubscriptionMnemomKey(defaultMnemomKeyDeps(), "pinning an agent")
|
|
465
|
+
: readMnemomKey(defaultMnemomKeyDeps());
|
|
466
|
+
}
|
|
412
467
|
// Now that a session exists, a defaulted slug uses the real login handle from
|
|
413
468
|
// /v1/auth/me (the stored token has no email, so the pre-sign-in value was the
|
|
414
469
|
// "agent-user" fallback). Only when the caller didn't name an agent. Under an
|
|
415
470
|
// explicit --setup the lookup is strict and a missing handle aborts the launch:
|
|
416
471
|
// provisioning "agent-user" would govern a different gateway identity than the
|
|
417
472
|
// one a prior setup under the real handle created (MNE-7650).
|
|
418
|
-
if (
|
|
473
|
+
if (pin) {
|
|
474
|
+
// The pinned agent must be visible and claimed; its own name rides as x-mnemom-agent.
|
|
475
|
+
agent = (await resolvePinnedAgent(pin)).slug;
|
|
476
|
+
}
|
|
477
|
+
else if (!options.agent?.trim()) {
|
|
419
478
|
const refreshed = sanitizeAgentName(await resolveDefaultSlug({ strict: options.setup === true }));
|
|
420
479
|
if (refreshed)
|
|
421
480
|
agent = refreshed;
|
|
@@ -425,16 +484,26 @@ export async function agentCommand(scenarioArg, options = {}) {
|
|
|
425
484
|
// launch bills (account.orgId — the active/default org is never changed); anything else
|
|
426
485
|
// refuses the launch. No prompt and no unclaimed fallback, interactive or not, preflighted or
|
|
427
486
|
// not. `--no-setup` only skips the default-card writes.
|
|
428
|
-
|
|
487
|
+
// A pinned launch takes ensureClaimedServedAgent's pin path: served must equal the pin.
|
|
488
|
+
const served = await ensureClaimedServedAgent({
|
|
429
489
|
slug: agent,
|
|
430
490
|
anthropicKey,
|
|
431
491
|
mnemomKey,
|
|
492
|
+
pin,
|
|
432
493
|
gatewayHost,
|
|
433
494
|
orgId: account.orgId,
|
|
434
495
|
orgLabel: account.orgLabel,
|
|
435
496
|
setup: options.setup,
|
|
436
497
|
governance: options.governance,
|
|
437
498
|
});
|
|
499
|
+
const servedAgentId = served.agentId;
|
|
500
|
+
// The composed Safe House mode for the launch summary (MNE-8612). Started now so the read
|
|
501
|
+
// overlaps the continuation and ledger work below, awaited at logLaunch. Never rejects.
|
|
502
|
+
const protection = readComposedProtection(servedAgentId);
|
|
503
|
+
// The gateway refused the stored Mnemom key an api-key launch offered: its requests must not
|
|
504
|
+
// carry it either (the gateway would 401 every one).
|
|
505
|
+
if (served.mnemomKeyRejected)
|
|
506
|
+
mnemomKey = undefined;
|
|
438
507
|
// ── conversation id ── an implicit relaunch continues its earlier conversation (IG5 #1, D1)
|
|
439
508
|
// so the goal the gateway inferred there is still the one it steers by. Resolved here, after
|
|
440
509
|
// sign-in and the agent refresh, because a resume needs the org, agent and gateway host to
|
|
@@ -524,6 +593,7 @@ export async function agentCommand(scenarioArg, options = {}) {
|
|
|
524
593
|
conversationId,
|
|
525
594
|
agent,
|
|
526
595
|
agentId: servedAgentId,
|
|
596
|
+
pinned: pin !== undefined,
|
|
527
597
|
account,
|
|
528
598
|
gatewayHost,
|
|
529
599
|
shape,
|
|
@@ -547,6 +617,7 @@ export async function agentCommand(scenarioArg, options = {}) {
|
|
|
547
617
|
goalMode,
|
|
548
618
|
cli,
|
|
549
619
|
plugin: launchPlugin.line,
|
|
620
|
+
protection: await protection,
|
|
550
621
|
});
|
|
551
622
|
if (pluginWanted && cli.name === "claude" && !bundledPluginDir) {
|
|
552
623
|
say(fmt.warn("mnemom agent: the Mnemom plugin folder is not in this install — launching without the Mnemom band and /mnemom."));
|
|
@@ -584,6 +655,9 @@ export async function agentCommand(scenarioArg, options = {}) {
|
|
|
584
655
|
if (yolo && shape !== "server")
|
|
585
656
|
say(permissionPromptsLine(cli));
|
|
586
657
|
sayContextProfile(contextProfile);
|
|
658
|
+
const tierWarning = await contextProfileTierWarning(contextProfile, options, cli, resolveHandoffGatewayHost(door));
|
|
659
|
+
if (tierWarning)
|
|
660
|
+
say(fmt.warn(tierWarning));
|
|
587
661
|
sayCompaction(contextProfile, compactionIntercept, compactAt, launchWindow(win));
|
|
588
662
|
// ── handoff MCP tool (MNE-7564) ── OFF by default: absent/false ⇒ handoffMcpConfigPath stays
|
|
589
663
|
// undefined and buildCliArgs/spawnRcCli are byte-identical to a build without this flag. ON:
|
|
@@ -610,6 +684,7 @@ export async function agentCommand(scenarioArg, options = {}) {
|
|
|
610
684
|
gatewayHost: resolveHandoffGatewayHost(door),
|
|
611
685
|
anthropicKeySource,
|
|
612
686
|
agent,
|
|
687
|
+
agentId: pin,
|
|
613
688
|
conversationId,
|
|
614
689
|
sessionConfigB64,
|
|
615
690
|
serverScript: resolveHandoffServerScript(),
|
|
@@ -628,6 +703,7 @@ export async function agentCommand(scenarioArg, options = {}) {
|
|
|
628
703
|
anthropicKey,
|
|
629
704
|
mnemomKey,
|
|
630
705
|
agent,
|
|
706
|
+
agentId: pin,
|
|
631
707
|
scenario,
|
|
632
708
|
conversationId,
|
|
633
709
|
envelope,
|
|
@@ -641,7 +717,10 @@ export async function agentCommand(scenarioArg, options = {}) {
|
|
|
641
717
|
handoffMcpConfigPath,
|
|
642
718
|
resumeHint: (id) => resumeHintLine({
|
|
643
719
|
scenario,
|
|
644
|
-
agent
|
|
720
|
+
// A pinned launch resumes pinned to the agent it was SERVED (servedAgentId === pin):
|
|
721
|
+
// the id is the one value that resumes into the same agent. A named launch repeats its
|
|
722
|
+
// name; a defaulted one repeats nothing.
|
|
723
|
+
agent: pin ? servedAgentId : options.agent?.trim() ? agent : undefined,
|
|
645
724
|
ccSessionId: id,
|
|
646
725
|
}),
|
|
647
726
|
knownCcSessionId: cc.id ?? continuedCcSessionId,
|
|
@@ -656,6 +735,7 @@ export async function agentCommand(scenarioArg, options = {}) {
|
|
|
656
735
|
anthropicKey,
|
|
657
736
|
mnemomKey,
|
|
658
737
|
agent,
|
|
738
|
+
agentId: pin,
|
|
659
739
|
conversationId,
|
|
660
740
|
scenario,
|
|
661
741
|
envelope,
|
|
@@ -838,6 +918,7 @@ export function recordSessionLaunch(ctx) {
|
|
|
838
918
|
conversation_id: ctx.conversationId,
|
|
839
919
|
agent_slug: ctx.agent,
|
|
840
920
|
agent_id: ctx.agentId,
|
|
921
|
+
...(ctx.pinned ? { agent_pinned: true } : {}),
|
|
841
922
|
org_id: ctx.account.orgId,
|
|
842
923
|
org_label: ctx.account.orgLabel,
|
|
843
924
|
gateway_host: ctx.gatewayHost,
|
|
@@ -1132,6 +1213,7 @@ function launchTerminal(ctx) {
|
|
|
1132
1213
|
anthropicKey: ctx.anthropicKey,
|
|
1133
1214
|
mnemomKey: ctx.mnemomKey,
|
|
1134
1215
|
agent: ctx.agent,
|
|
1216
|
+
agentId: ctx.agentId,
|
|
1135
1217
|
conversationId: ctx.conversationId,
|
|
1136
1218
|
contractB64: ctx.envelope?.base64,
|
|
1137
1219
|
goalMode: ctx.goalMode ?? undefined,
|
|
@@ -1305,6 +1387,31 @@ export function startBaseUrlProxy(door, done, opts = {}) {
|
|
|
1305
1387
|
// ───────────────────────────────────────────────────────────────────────────
|
|
1306
1388
|
// Remote Control launch (terminal RC + headless server) — behind the bundled proxy
|
|
1307
1389
|
// ───────────────────────────────────────────────────────────────────────────
|
|
1390
|
+
/**
|
|
1391
|
+
* The three secret lines the Remote Control proxy reads on stdin (anthropic, mnemom, openai). The
|
|
1392
|
+
* Mnemom key rides line 2 on BOTH auth modes when the launch has one — the proxy forwards it as
|
|
1393
|
+
* x-mnemom-api-key either way; a subscription launch always has one, an api-key launch has one
|
|
1394
|
+
* when this machine stores one or the launch pins. Never logged.
|
|
1395
|
+
*/
|
|
1396
|
+
export function rcProxyStdin(ctx) {
|
|
1397
|
+
return `${ctx.anthropicKey ?? ""}\n${ctx.mnemomKey ?? ""}\n\n`;
|
|
1398
|
+
}
|
|
1399
|
+
/**
|
|
1400
|
+
* The agent id a launch pins, or undefined for a name launch: `--agent` when it is a full
|
|
1401
|
+
* `mnm-<uuid>` (any case), else launcher.toml `agent_id` (only when no `--agent` was given —
|
|
1402
|
+
* selectLaunchAgent). A truncated/garbled `mnm-…` throws instead of becoming a name.
|
|
1403
|
+
*/
|
|
1404
|
+
export function resolveLaunchPin(options) {
|
|
1405
|
+
const fromFlag = options.agent?.trim();
|
|
1406
|
+
const raw = fromFlag || options.agentId?.trim();
|
|
1407
|
+
if (!raw)
|
|
1408
|
+
return undefined;
|
|
1409
|
+
const ref = parseAgentRef(raw);
|
|
1410
|
+
if (ref.kind === "malformed-id" || (!fromFlag && ref.kind === "name")) {
|
|
1411
|
+
throw new Error(malformedAgentIdMessage(raw, fromFlag ? "--agent" : "agent_id"));
|
|
1412
|
+
}
|
|
1413
|
+
return ref.kind === "id" ? ref.id : undefined;
|
|
1414
|
+
}
|
|
1308
1415
|
function launchRemoteControl(ctx) {
|
|
1309
1416
|
warnAdapter(ctx.cli);
|
|
1310
1417
|
for (const tool of ["node", "openssl"]) {
|
|
@@ -1321,6 +1428,7 @@ function launchRemoteControl(ctx) {
|
|
|
1321
1428
|
...process.env,
|
|
1322
1429
|
MNEMOM_AGENT_RC_DOOR: ctx.door,
|
|
1323
1430
|
MNEMOM_AGENT_RC_AGENT: ctx.agent,
|
|
1431
|
+
MNEMOM_AGENT_RC_AGENT_ID: ctx.agentId ?? "",
|
|
1324
1432
|
MNEMOM_AGENT_RC_CONVERSATION_ID: ctx.conversationId,
|
|
1325
1433
|
MNEMOM_AGENT_RC_CONTRACT_B64: ctx.envelope?.base64 ?? "",
|
|
1326
1434
|
MNEMOM_AGENT_RC_GOAL_MODE: ctx.goalMode ?? "",
|
|
@@ -1353,8 +1461,9 @@ function launchRemoteControl(ctx) {
|
|
|
1353
1461
|
};
|
|
1354
1462
|
process.on("exit", onExit);
|
|
1355
1463
|
// Secrets on stdin — three lines (anthropic, mnemom, openai). An api-key launch sends the
|
|
1356
|
-
// Anthropic key
|
|
1357
|
-
//
|
|
1464
|
+
// Anthropic key, plus this machine's Mnemom key when it has one (the key principal; always
|
|
1465
|
+
// with a pin); a subscription launch sends the Mnemom key only (the identity anchor — the
|
|
1466
|
+
// user's OAuth bearer is what pays). openai is always empty on this Anthropic-only door.
|
|
1358
1467
|
// A proxy that exits before reading them turns this write into an EPIPE; that is
|
|
1359
1468
|
// not a failure of its own — the exited-before-READY check below refuses the
|
|
1360
1469
|
// launch. Any other stdin error fails closed like a spawn error.
|
|
@@ -1365,7 +1474,7 @@ function launchRemoteControl(ctx) {
|
|
|
1365
1474
|
onExit();
|
|
1366
1475
|
process.exit(1);
|
|
1367
1476
|
});
|
|
1368
|
-
proxy.stdin?.end(
|
|
1477
|
+
proxy.stdin?.end(rcProxyStdin(ctx));
|
|
1369
1478
|
let stdoutBuf = "";
|
|
1370
1479
|
const start = Date.now();
|
|
1371
1480
|
const tryStart = () => {
|
|
@@ -1462,6 +1571,10 @@ function spawnRcCli(ctx) {
|
|
|
1462
1571
|
// (Never on a subscription launch — handoff is turned off there, so there is no key to set.)
|
|
1463
1572
|
if (ctx.handoffMcpConfigPath && ctx.anthropicKey) {
|
|
1464
1573
|
childEnv[HANDOFF_RC_KEY_ENV_NAME] = ctx.anthropicKey;
|
|
1574
|
+
// The Mnemom key the session's own requests carry (the proxy adds it), so a pinned handoff
|
|
1575
|
+
// call proves the same principal.
|
|
1576
|
+
if (ctx.mnemomKey)
|
|
1577
|
+
childEnv[HANDOFF_RC_MNEMOM_KEY_ENV_NAME] = ctx.mnemomKey;
|
|
1465
1578
|
}
|
|
1466
1579
|
// The plugin's non-secret launch facts (MNEMOM_CC_*), also after the strip loop: not an
|
|
1467
1580
|
// ANTHROPIC_* var, so the RC gate ignores them, and nothing here is a key or a header value.
|
|
@@ -1748,10 +1861,13 @@ export function logLaunch(ctx) {
|
|
|
1748
1861
|
if (ctx.envelope) {
|
|
1749
1862
|
say(`mnemom agent: contract statement: ${ctx.envelope.contract.statement}`);
|
|
1750
1863
|
say(`mnemom agent: contract sha256[0:16]: ${ctx.envelope.sha}`);
|
|
1864
|
+
say(`mnemom agent: contract mode: ${ctx.envelope.contract.mode ?? "nudge"}`);
|
|
1751
1865
|
}
|
|
1752
1866
|
else if (ctx.goalMode) {
|
|
1753
1867
|
say(`mnemom agent: goal mode: ${ctx.goalMode} (inferred from your messages; watch x-mnemom-goal-revision / x-mnemom-goal-steer on each response)`);
|
|
1754
1868
|
}
|
|
1869
|
+
if (ctx.protection !== undefined)
|
|
1870
|
+
say(`mnemom agent: protection: ${ctx.protection}`);
|
|
1755
1871
|
}
|
|
1756
1872
|
/** The launch line for the yolo default: permission prompts are skipped, and how to keep them. */
|
|
1757
1873
|
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}).`);
|