@mnemom/mnemom 0.17.28 → 0.17.29
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-session.js +2 -1
- package/dist/commands/agent-setup.d.ts +2 -1
- package/dist/commands/agent-setup.js +131 -6
- package/dist/commands/agent.js +12 -15
- package/dist/commands/wrap.js +3 -11
- package/dist/index.js +7 -2
- package/dist/lib/agent.d.ts +30 -1
- package/dist/lib/agent.js +73 -6
- package/dist/lib/birth-probe.d.ts +20 -0
- package/dist/lib/birth-probe.js +21 -0
- package/dist/lib/config.d.ts +11 -5
- package/dist/lib/config.js +13 -9
- package/dist/lib/slack-route.d.ts +6 -1
- package/dist/lib/slack-route.js +7 -5
- package/package.json +1 -1
|
@@ -22,7 +22,8 @@
|
|
|
22
22
|
*/
|
|
23
23
|
import { basename } from "node:path";
|
|
24
24
|
import { sanitizeAgentName } from "../lib/agent.js";
|
|
25
|
-
export const NO_SESSION_NAME_MESSAGE = "mnemom agent: a session name is required here (e.g. `mnemom agent onboarding`)
|
|
25
|
+
export const NO_SESSION_NAME_MESSAGE = "mnemom agent: a session name is required here: pass it before `--` (e.g. `mnemom agent onboarding`) " +
|
|
26
|
+
"or as `--name <x>` (also `-n <x>` / `--name=<x>`) after `--` (e.g. `mnemom agent -- --name onboarding -p …`). " +
|
|
26
27
|
"In a terminal, plain `mnemom agent` asks for one.";
|
|
27
28
|
/** Current folder name, sanitised like an agent slug; "session" when that is empty. */
|
|
28
29
|
export function defaultSessionName(cwd = process.cwd()) {
|
|
@@ -391,7 +391,7 @@ export declare function resolvePreflightedAccount(): Promise<{
|
|
|
391
391
|
* absent or older, and leaves one at this version or newer as it is. Bump it whenever
|
|
392
392
|
* the content below changes. (`card_version` is the schema date, not this.)
|
|
393
393
|
*/
|
|
394
|
-
export declare const CODE_CARD_CONTENT_VERSION = "mnemom-agent-default/2.1.
|
|
394
|
+
export declare const CODE_CARD_CONTENT_VERSION = "mnemom-agent-default/2.1.2";
|
|
395
395
|
/**
|
|
396
396
|
* Whether a refresh should replace this card's content with the current default: true when
|
|
397
397
|
* it carries no content_version, or an older (or unparseable) `mnemom-agent-default/` one.
|
|
@@ -412,6 +412,7 @@ export interface CardPrincipal {
|
|
|
412
412
|
* takes a role or org, not personal data. The agent is never its own principal.
|
|
413
413
|
*/
|
|
414
414
|
export declare function orgPrincipal(orgId?: string | null): CardPrincipal;
|
|
415
|
+
export declare const IN_PLACE_FLOOR: number[];
|
|
415
416
|
export declare function buildCodeAlignmentCard(agentId: string, modes?: Pick<CardModes, "autonomy" | "integrity" | "allowUnmappedTools">, principal?: CardPrincipal): Record<string, unknown>;
|
|
416
417
|
export declare function buildCodeProtectionCard(agentId: string, mode?: string): Record<string, unknown>;
|
|
417
418
|
export {};
|
|
@@ -903,13 +903,15 @@ export function keptPrincipal(own, opts) {
|
|
|
903
903
|
*
|
|
904
904
|
* `existing` (a refresh that read the agent's own cards) decides content per card: a card
|
|
905
905
|
* that is missing, carries no content_version, or an older default gets the current default
|
|
906
|
-
* content
|
|
907
|
-
*
|
|
906
|
+
* content, except a 2.1.x default, which is brought forward in place (upgradeInPlace) so its
|
|
907
|
+
* edits survive; a card already at this default or a newer one keeps its own content and only
|
|
908
|
+
* has its modes set. Without `existing` (a new agent, or a refresh a --governance flag decided)
|
|
908
909
|
* both cards get the current default content.
|
|
909
910
|
*/
|
|
910
911
|
async function publishDefaultCards(agentId, modes, opts = {}) {
|
|
911
|
-
|
|
912
|
-
const
|
|
912
|
+
// A 2.1.x card is brought forward in place first (upgradeInPlace), so it is then kept.
|
|
913
|
+
const ownAlign = carryForward(opts.existing?.align, "alignment");
|
|
914
|
+
const ownProt = carryForward(opts.existing?.prot, "protection");
|
|
913
915
|
const keepAlign = !!ownAlign && !needsContentUpgrade(ownAlign);
|
|
914
916
|
const keepProt = !!ownProt && !needsContentUpgrade(ownProt);
|
|
915
917
|
// A kept card's principal follows the agent's current org (keptPrincipal).
|
|
@@ -1215,12 +1217,20 @@ export async function resolvePreflightedAccount() {
|
|
|
1215
1217
|
* absent or older, and leaves one at this version or newer as it is. Bump it whenever
|
|
1216
1218
|
* the content below changes. (`card_version` is the schema date, not this.)
|
|
1217
1219
|
*/
|
|
1218
|
-
export const CODE_CARD_CONTENT_VERSION = "mnemom-agent-default/2.1.
|
|
1220
|
+
export const CODE_CARD_CONTENT_VERSION = "mnemom-agent-default/2.1.2";
|
|
1219
1221
|
const CONTENT_LINEAGE = "mnemom-agent-default/";
|
|
1220
1222
|
function parseContentSemver(v) {
|
|
1221
1223
|
const m = /^(\d+)\.(\d+)\.(\d+)$/.exec(v);
|
|
1222
1224
|
return m ? [Number(m[1]), Number(m[2]), Number(m[3])] : null;
|
|
1223
1225
|
}
|
|
1226
|
+
/** Negative, zero or positive as semver `a` is older than, equal to or newer than `b`. */
|
|
1227
|
+
function compareSemver(a, b) {
|
|
1228
|
+
for (let i = 0; i < 3; i++) {
|
|
1229
|
+
if (a[i] !== b[i])
|
|
1230
|
+
return a[i] - b[i];
|
|
1231
|
+
}
|
|
1232
|
+
return 0;
|
|
1233
|
+
}
|
|
1224
1234
|
/**
|
|
1225
1235
|
* Whether a refresh should replace this card's content with the current default: true when
|
|
1226
1236
|
* it carries no content_version, or an older (or unparseable) `mnemom-agent-default/` one.
|
|
@@ -1291,6 +1301,114 @@ const CODE_BOUNDED_ACTIONS = [
|
|
|
1291
1301
|
"WebFetch",
|
|
1292
1302
|
"WebSearch",
|
|
1293
1303
|
];
|
|
1304
|
+
// Cross-session messaging between Claude Code sessions (2.1.1). Mapped to a capability, the
|
|
1305
|
+
// lane-lead card's shape: the card names `converse` with its tools, and each tool is bounded
|
|
1306
|
+
// as "<tool>: converse" (AAP checks the part before the colon, so the observer agrees).
|
|
1307
|
+
const CODE_CONVERSE_TOOLS = ["SendMessage", "ListAgents"];
|
|
1308
|
+
// A session's own schedule (2.1.2), e.g. a seat's hourly self-check. Same shape, its own
|
|
1309
|
+
// capability: none of these talks to another session, and `scheduling` is the policy engine's
|
|
1310
|
+
// catalog name for exactly these four tools.
|
|
1311
|
+
const CODE_SCHEDULING_TOOLS = ["CronCreate", "CronDelete", "CronList", "ScheduleWakeup"];
|
|
1312
|
+
/**
|
|
1313
|
+
* The capabilities the default maps, each with the version that added it. Every version
|
|
1314
|
+
* since IN_PLACE_FLOOR only ADDED one of these (the capability and its "<tool>: <capability>"
|
|
1315
|
+
* bounded actions), which is what lets upgradeInPlace bring a card forward without replacing
|
|
1316
|
+
* it. A version that changes or removes default content (or changes the protection card)
|
|
1317
|
+
* must raise IN_PLACE_FLOOR to itself, so older cards are replaced whole again.
|
|
1318
|
+
*/
|
|
1319
|
+
const CODE_CAPABILITIES = [
|
|
1320
|
+
{
|
|
1321
|
+
name: "converse",
|
|
1322
|
+
since: [2, 1, 1],
|
|
1323
|
+
description: "Message and list the other Claude Code sessions",
|
|
1324
|
+
tools: CODE_CONVERSE_TOOLS,
|
|
1325
|
+
},
|
|
1326
|
+
{
|
|
1327
|
+
name: "scheduling",
|
|
1328
|
+
since: [2, 1, 2],
|
|
1329
|
+
description: "Schedule, list and cancel this session's own checks",
|
|
1330
|
+
tools: CODE_SCHEDULING_TOOLS,
|
|
1331
|
+
},
|
|
1332
|
+
];
|
|
1333
|
+
export const IN_PLACE_FLOOR = [2, 1, 0];
|
|
1334
|
+
/**
|
|
1335
|
+
* Whether an AAP action list names `tool`: a case-insensitive exact match, or the part before
|
|
1336
|
+
* ':' of a "name: description" entry. The policy engine's own rule (shared/policy-engine
|
|
1337
|
+
* src/action-list.ts actionListIncludes), mirrored because the CLI's engine dependency is the
|
|
1338
|
+
* published 0.3.0, which predates it.
|
|
1339
|
+
*/
|
|
1340
|
+
function actionListNames(list, tool) {
|
|
1341
|
+
const target = tool.toLowerCase();
|
|
1342
|
+
return list.some((entry) => {
|
|
1343
|
+
if (typeof entry !== "string")
|
|
1344
|
+
return false;
|
|
1345
|
+
if (entry.toLowerCase() === target)
|
|
1346
|
+
return true;
|
|
1347
|
+
const colon = entry.indexOf(":");
|
|
1348
|
+
return colon > 0 && entry.substring(0, colon).trim().toLowerCase() === target;
|
|
1349
|
+
});
|
|
1350
|
+
}
|
|
1351
|
+
/**
|
|
1352
|
+
* A stamped default from IN_PLACE_FLOOR up to (not including) this version, brought forward
|
|
1353
|
+
* in place: its own content kept (`mnemom card edit` round-trips the stamp, so it may carry
|
|
1354
|
+
* the customer's edits), plus each capability added since its version, stamped current.
|
|
1355
|
+
* A capability the card already declares is the customer's and is left as it is, with no
|
|
1356
|
+
* bounded actions added for it; a tool the card already names in bounded_actions or
|
|
1357
|
+
* forbidden_actions is not bound again. Null when the card is not such a default (an older
|
|
1358
|
+
* one), or has no autonomy.bounded_actions list to bind into (the api requires one): either
|
|
1359
|
+
* is replaced whole, as before. A forbidden_actions that is not a list of strings may hold a
|
|
1360
|
+
* forbid this cannot read, so it fails closed: no capability is added and nothing is bound;
|
|
1361
|
+
* the card keeps its own content and is restamped. So is a NARROWED card, one whose
|
|
1362
|
+
* bounded_actions no longer names every tool its stamped version's default bounded.
|
|
1363
|
+
*
|
|
1364
|
+
* Called only by carryForward, after needsContentUpgrade: the stamp is then absent,
|
|
1365
|
+
* unparseable, or an older default of this lineage, so only the floor needs checking.
|
|
1366
|
+
*/
|
|
1367
|
+
function upgradeInPlace(card, kind) {
|
|
1368
|
+
const v = card.content_version;
|
|
1369
|
+
const have = typeof v === "string" ? parseContentSemver(v.slice(CONTENT_LINEAGE.length)) : null;
|
|
1370
|
+
if (!have || compareSemver(have, IN_PLACE_FLOOR) < 0)
|
|
1371
|
+
return null;
|
|
1372
|
+
const upgraded = { ...card, content_version: CODE_CARD_CONTENT_VERSION };
|
|
1373
|
+
if (kind === "protection")
|
|
1374
|
+
return upgraded;
|
|
1375
|
+
const autonomy = card.autonomy;
|
|
1376
|
+
if (!isRecord(autonomy) || !Array.isArray(autonomy.bounded_actions))
|
|
1377
|
+
return null;
|
|
1378
|
+
const forbidden = autonomy.forbidden_actions ?? [];
|
|
1379
|
+
if (!Array.isArray(forbidden) || forbidden.some((entry) => typeof entry !== "string")) {
|
|
1380
|
+
return upgraded;
|
|
1381
|
+
}
|
|
1382
|
+
const bounded = [...autonomy.bounded_actions];
|
|
1383
|
+
// A card cut below its version's default bounded set was narrowed on purpose, so it gains
|
|
1384
|
+
// no grant (0.17.28 would not have touched it either): restamped with its own content.
|
|
1385
|
+
const stampedDefault = [
|
|
1386
|
+
...CODE_BOUNDED_ACTIONS,
|
|
1387
|
+
...CODE_CAPABILITIES.filter((cap) => compareSemver(cap.since, have) <= 0).flatMap((cap) => cap.tools),
|
|
1388
|
+
];
|
|
1389
|
+
if (!stampedDefault.every((tool) => actionListNames(bounded, tool)))
|
|
1390
|
+
return upgraded;
|
|
1391
|
+
const capabilities = isRecord(card.capabilities) ? { ...card.capabilities } : {};
|
|
1392
|
+
for (const cap of CODE_CAPABILITIES) {
|
|
1393
|
+
if (compareSemver(cap.since, have) <= 0 || cap.name in capabilities)
|
|
1394
|
+
continue;
|
|
1395
|
+
capabilities[cap.name] = { description: cap.description, tools: [...cap.tools] };
|
|
1396
|
+
for (const tool of cap.tools) {
|
|
1397
|
+
if (!actionListNames(bounded, tool) && !actionListNames(forbidden, tool)) {
|
|
1398
|
+
bounded.push(`${tool}: ${cap.name}`);
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
upgraded.capabilities = capabilities;
|
|
1403
|
+
upgraded.autonomy = { ...autonomy, bounded_actions: bounded };
|
|
1404
|
+
return upgraded;
|
|
1405
|
+
}
|
|
1406
|
+
/** An existing card as a refresh decides on it: a 2.1.x default brought forward in place. */
|
|
1407
|
+
function carryForward(card, kind) {
|
|
1408
|
+
if (!card || !needsContentUpgrade(card))
|
|
1409
|
+
return card;
|
|
1410
|
+
return upgradeInPlace(card, kind) ?? card;
|
|
1411
|
+
}
|
|
1294
1412
|
export function buildCodeAlignmentCard(agentId, modes = {
|
|
1295
1413
|
autonomy: "off",
|
|
1296
1414
|
integrity: "off",
|
|
@@ -1339,8 +1457,15 @@ export function buildCodeAlignmentCard(agentId, modes = {
|
|
|
1339
1457
|
},
|
|
1340
1458
|
},
|
|
1341
1459
|
},
|
|
1460
|
+
capabilities: Object.fromEntries(CODE_CAPABILITIES.map((cap) => [
|
|
1461
|
+
cap.name,
|
|
1462
|
+
{ description: cap.description, tools: [...cap.tools] },
|
|
1463
|
+
])),
|
|
1342
1464
|
autonomy: {
|
|
1343
|
-
bounded_actions: [
|
|
1465
|
+
bounded_actions: [
|
|
1466
|
+
...CODE_BOUNDED_ACTIONS,
|
|
1467
|
+
...CODE_CAPABILITIES.flatMap((cap) => cap.tools.map((tool) => `${tool}: ${cap.name}`)),
|
|
1468
|
+
],
|
|
1344
1469
|
forbidden_actions: [
|
|
1345
1470
|
"force_push",
|
|
1346
1471
|
"push_protected_branch",
|
package/dist/commands/agent.js
CHANGED
|
@@ -45,7 +45,7 @@ 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
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";
|
|
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, liftPassthroughName, 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";
|
|
@@ -163,6 +163,9 @@ function windowModelFor(options, cli) {
|
|
|
163
163
|
}
|
|
164
164
|
/** Entry point for `mnemom agent [scenario]`. Throws on fatal misconfig. */
|
|
165
165
|
export async function agentCommand(scenarioArg, options = {}) {
|
|
166
|
+
// An unusable name after `--` refuses here, as a rejected launch: the RC path lifts it again
|
|
167
|
+
// in the proxy-ready timer, where a throw would be an uncaught crash, not a refusal.
|
|
168
|
+
liftPassthroughName(options.passthrough ?? []);
|
|
166
169
|
// ── session name + goal ── (a bare `mnemom agent` in a terminal asks for the name;
|
|
167
170
|
// goal alignment is resolved upstream: --goal seals a contract, implicit alignment
|
|
168
171
|
// is on by default only while the `implicit` experimental feature is on.
|
|
@@ -1515,18 +1518,10 @@ function launchRemoteControl(ctx) {
|
|
|
1515
1518
|
}
|
|
1516
1519
|
function spawnRcCli(ctx) {
|
|
1517
1520
|
// Session name for the claude.ai session list: "<agent>-<scenario>", unless
|
|
1518
|
-
// the caller passed their own `--name
|
|
1519
|
-
const
|
|
1520
|
-
|
|
1521
|
-
const passthrough =
|
|
1522
|
-
for (let i = 0; i < rawPassthrough.length; i++) {
|
|
1523
|
-
if (rawPassthrough[i] === "--name" && i + 1 < rawPassthrough.length) {
|
|
1524
|
-
rcName = rawPassthrough[i + 1];
|
|
1525
|
-
i++; // consume the value
|
|
1526
|
-
continue;
|
|
1527
|
-
}
|
|
1528
|
-
passthrough.push(rawPassthrough[i]);
|
|
1529
|
-
}
|
|
1521
|
+
// the caller passed their own name after `--` (`--name x`, `--name=x`, `-n x`; lifted out here).
|
|
1522
|
+
const lifted = liftPassthroughName(ctx.options.passthrough ?? []);
|
|
1523
|
+
const rcName = lifted.name ?? sessionDisplayName(ctx.agent, ctx.scenario);
|
|
1524
|
+
const passthrough = lifted.rest;
|
|
1530
1525
|
const scopedOptions = { ...ctx.options, passthrough };
|
|
1531
1526
|
// Every auth-shaped ANTHROPIC_* var is UNSET so the RC gate sees a first-party
|
|
1532
1527
|
// OAuth setup; NO_PROXY/HTTP_PROXY unset so api.anthropic.com is not excluded.
|
|
@@ -1605,7 +1600,9 @@ function spawnRcCli(ctx) {
|
|
|
1605
1600
|
else {
|
|
1606
1601
|
// Interactive RC in THIS terminal: the CLI's own `--remote-control <name>`
|
|
1607
1602
|
// startup flag; --model/--effort/--trim-mcp/yolo apply as in a normal launch.
|
|
1608
|
-
// buildCliArgs already includes the (name-stripped) passthrough.
|
|
1603
|
+
// buildCliArgs already includes the (name-stripped) passthrough. `--name` carries the
|
|
1604
|
+
// same name to the session itself, so its cross-session name (what ListAgents and
|
|
1605
|
+
// SendMessage address) is the seat name, not one Claude Code picks.
|
|
1609
1606
|
const flagArgs = buildCliArgs(scopedOptions, {
|
|
1610
1607
|
yolo: ctx.yolo,
|
|
1611
1608
|
allowCliFlags: true,
|
|
@@ -1613,7 +1610,7 @@ function spawnRcCli(ctx) {
|
|
|
1613
1610
|
handoffMcpConfigPath: ctx.handoffMcpConfigPath,
|
|
1614
1611
|
pluginDir: ctx.plugin?.dir,
|
|
1615
1612
|
});
|
|
1616
|
-
args = ["--remote-control", rcName, ...flagArgs];
|
|
1613
|
+
args = ["--remote-control", rcName, "--name", rcName, ...flagArgs];
|
|
1617
1614
|
if (ctx.options.slack)
|
|
1618
1615
|
args.push(...slackChannelArgs(rcName));
|
|
1619
1616
|
say(`mnemom agent: remote-control: interactive session in THIS terminal with Remote Control on (session name '${rcName}'); it also appears under Remote Control at claude.ai/code and in the Claude app. Exit the CLI to stop everything.`);
|
package/dist/commands/wrap.js
CHANGED
|
@@ -20,6 +20,7 @@ import { mergeAgentConfig } from "../lib/agent-config.js";
|
|
|
20
20
|
import { askSelect, askInput, isInteractive } from "../lib/prompt.js";
|
|
21
21
|
import { getGatewayUrl } from "../lib/config.js";
|
|
22
22
|
import { fmt } from "../lib/format.js";
|
|
23
|
+
import { OPENAI_WRAP_DEFAULT_MODEL, openaiBirthProbeBody } from "../lib/birth-probe.js";
|
|
23
24
|
const AGENT_HEADER = "x-mnemom-agent";
|
|
24
25
|
const PROVIDERS = {
|
|
25
26
|
anthropic: {
|
|
@@ -33,9 +34,7 @@ const PROVIDERS = {
|
|
|
33
34
|
label: "OpenAI",
|
|
34
35
|
keyEnv: "OPENAI_API_KEY",
|
|
35
36
|
keyHeader: "Authorization",
|
|
36
|
-
|
|
37
|
-
// 2027-01-14 (MNE-8755).
|
|
38
|
-
defaultModel: "gpt-6-luna",
|
|
37
|
+
defaultModel: OPENAI_WRAP_DEFAULT_MODEL,
|
|
39
38
|
basePath: "/openai",
|
|
40
39
|
},
|
|
41
40
|
gemini: {
|
|
@@ -220,14 +219,7 @@ export async function birthThroughGateway(gatewayUrl, provider, agentName, provi
|
|
|
220
219
|
switch (provider) {
|
|
221
220
|
case "openai":
|
|
222
221
|
url = `${gatewayUrl}${prov.basePath}/v1/chat/completions`;
|
|
223
|
-
|
|
224
|
-
// OpenAI rejects max_tokens for those on Chat Completions (models-registry.ts
|
|
225
|
-
// REASONING_SUPPORTED_PARAMS). Older chat models accept max_completion_tokens too.
|
|
226
|
-
body = JSON.stringify({
|
|
227
|
-
model: prov.defaultModel,
|
|
228
|
-
max_completion_tokens: 16,
|
|
229
|
-
messages: [{ role: "user", content: "hello" }],
|
|
230
|
-
});
|
|
222
|
+
body = JSON.stringify(openaiBirthProbeBody(prov.defaultModel));
|
|
231
223
|
break;
|
|
232
224
|
case "gemini":
|
|
233
225
|
url = `${gatewayUrl}${prov.basePath}/v1beta/models/${prov.defaultModel}:generateContent`;
|
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, selectLaunchAgent, } from "./lib/agent.js";
|
|
40
|
+
import { parseCompactAt, parseContextProfile, resolveImplicit, selectLaunchAgent, splitSessionArg, } 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";
|
|
@@ -228,8 +228,13 @@ const agentCmd = program
|
|
|
228
228
|
.option("--no-compaction-intercept", "Turn the compaction intercept off for this launch (it is on by default) — Claude Code's compaction goes to the model as usual. To turn it off for every launch: config key compaction_intercept = false, or env MNEMOM_AGENT_COMPACTION_INTERCEPT=0")
|
|
229
229
|
.option("--dry-run", "Print the resolved launch plan and exit — no key read, no spawn")
|
|
230
230
|
.option("--first-run", "Show the first-run look again: what Mnemom would have saved on your last 30 days of Claude Code (runs on this machine, read-only, nothing uploaded) — then exit")
|
|
231
|
-
.action(async (
|
|
231
|
+
.action(async (sessionArg, passthroughArg, opts) => {
|
|
232
|
+
let scenario;
|
|
233
|
+
let passthrough;
|
|
232
234
|
try {
|
|
235
|
+
// A Claude Code flag after `--` is never the session name, and an unusable or reserved
|
|
236
|
+
// name given there refuses before any sub-verb runs (splitSessionArg).
|
|
237
|
+
({ scenario, passthrough } = splitSessionArg(sessionArg, passthroughArg));
|
|
233
238
|
// `help` is reserved: `mnemom agent help [verb]` prints help and never launches
|
|
234
239
|
// (nor redeems an --invite). Without this, `help` was taken as a session name and
|
|
235
240
|
// the verb passed through to Claude Code as the opening prompt.
|
package/dist/lib/agent.d.ts
CHANGED
|
@@ -322,8 +322,37 @@ export declare function resumeCommand(ctx: {
|
|
|
322
322
|
}): string;
|
|
323
323
|
/** The session display name: "<agent>-<scenario>" (sanitised), else the agent slug. */
|
|
324
324
|
export declare function sessionDisplayName(agent: string, scenario: string): string;
|
|
325
|
-
/**
|
|
325
|
+
/**
|
|
326
|
+
* A caller's own session name in the Claude Code args after `--`, in each spelling Claude Code
|
|
327
|
+
* takes (`--name <x>`, `--name=<x>`, `-n <x>`; the last one wins), and the args without them.
|
|
328
|
+
* Throws when a name is given but unusable: no value, a flag as its value (`--name -p x` would
|
|
329
|
+
* take `-p` and drop headless mode), or a name with no letters or digits (blank, `!!!`), which
|
|
330
|
+
* would share its Slack state files with every other such name (normSession is empty).
|
|
331
|
+
*/
|
|
332
|
+
export declare function liftPassthroughName(passthrough: readonly string[]): {
|
|
333
|
+
name: string | undefined;
|
|
334
|
+
rest: string[];
|
|
335
|
+
};
|
|
336
|
+
/** A caller's own session name in the passthrough args, if any (liftPassthroughName). */
|
|
326
337
|
export declare function passthroughName(passthrough: readonly string[]): string | undefined;
|
|
338
|
+
/**
|
|
339
|
+
* The words `mnemom agent [session]` routes to a sub-verb (or a commander subcommand), so a
|
|
340
|
+
* session can never take one through `--name`: `-- --name setup -p x` would run provisioning.
|
|
341
|
+
*/
|
|
342
|
+
export declare const AGENT_SUB_VERBS: ReadonlySet<string>;
|
|
343
|
+
/**
|
|
344
|
+
* The session name and Claude Code args of `mnemom agent [session] [passthrough...]`, with a
|
|
345
|
+
* flag commander took as the session put back. Commander fills `[session]` with the first
|
|
346
|
+
* operand even after `--`, so `mnemom agent -- --name x -p` arrived as session "--name" and
|
|
347
|
+
* passthrough `x -p` (probe P8). A leading-dash session can only have come from after `--`
|
|
348
|
+
* (before it, commander parses a flag as an option), so it returns to the front of the
|
|
349
|
+
* passthrough; with no session left, a passthrough `--name <x>` names it. Throws on an
|
|
350
|
+
* unusable name (liftPassthroughName) or a sub-verb word (AGENT_SUB_VERBS).
|
|
351
|
+
*/
|
|
352
|
+
export declare function splitSessionArg(session: string | undefined, passthrough: readonly string[]): {
|
|
353
|
+
scenario: string | undefined;
|
|
354
|
+
passthrough: string[];
|
|
355
|
+
};
|
|
327
356
|
/** A single-line, header-safe conversation id: honour a caller value, else a uuid. */
|
|
328
357
|
export declare function resolveConversationId(raw: string | undefined): string;
|
|
329
358
|
/** A strictly-positive integer (rejects 0, negatives, decimals, NaN, non-numeric strings). */
|
package/dist/lib/agent.js
CHANGED
|
@@ -23,6 +23,7 @@ import { createHash, randomUUID } from "node:crypto";
|
|
|
23
23
|
import { readFileSync } from "node:fs";
|
|
24
24
|
import { join } from "node:path";
|
|
25
25
|
import { getGatewayUrl } from "./config.js";
|
|
26
|
+
import { normSession } from "./slack-route.js";
|
|
26
27
|
/** The us-2/prod gateway host. The default door — never any other cell silently. */
|
|
27
28
|
export const PROD_GATEWAY_HOST = "https://gateway.mnemom.ai";
|
|
28
29
|
/**
|
|
@@ -384,14 +385,80 @@ export function resumeCommand(ctx) {
|
|
|
384
385
|
export function sessionDisplayName(agent, scenario) {
|
|
385
386
|
return sanitizeAgentName(`${agent}-${scenario}`) || agent;
|
|
386
387
|
}
|
|
387
|
-
/**
|
|
388
|
-
|
|
388
|
+
/**
|
|
389
|
+
* A caller's own session name in the Claude Code args after `--`, in each spelling Claude Code
|
|
390
|
+
* takes (`--name <x>`, `--name=<x>`, `-n <x>`; the last one wins), and the args without them.
|
|
391
|
+
* Throws when a name is given but unusable: no value, a flag as its value (`--name -p x` would
|
|
392
|
+
* take `-p` and drop headless mode), or a name with no letters or digits (blank, `!!!`), which
|
|
393
|
+
* would share its Slack state files with every other such name (normSession is empty).
|
|
394
|
+
*/
|
|
395
|
+
export function liftPassthroughName(passthrough) {
|
|
389
396
|
let name;
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
397
|
+
const rest = [];
|
|
398
|
+
for (let i = 0; i < passthrough.length; i++) {
|
|
399
|
+
const arg = passthrough[i];
|
|
400
|
+
if (arg.startsWith("--name=")) {
|
|
401
|
+
name = arg.slice("--name=".length);
|
|
402
|
+
}
|
|
403
|
+
else if (arg === "--name" || arg === "-n") {
|
|
404
|
+
const value = passthrough[i + 1];
|
|
405
|
+
if (value === undefined || value.startsWith("-")) {
|
|
406
|
+
throw new Error(`mnemom agent: ${arg} needs a session name after it (e.g. \`${arg} onboarding\`), not ${value === undefined ? "nothing" : `the flag \`${value}\``}.`);
|
|
407
|
+
}
|
|
408
|
+
name = value;
|
|
409
|
+
i++;
|
|
410
|
+
}
|
|
411
|
+
else {
|
|
412
|
+
rest.push(arg);
|
|
413
|
+
continue;
|
|
414
|
+
}
|
|
415
|
+
if (!normSession(name)) {
|
|
416
|
+
throw new Error(`mnemom agent: '${name}' is not a usable session name: give it a letter or a digit.`);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
return { name, rest };
|
|
420
|
+
}
|
|
421
|
+
/** A caller's own session name in the passthrough args, if any (liftPassthroughName). */
|
|
422
|
+
export function passthroughName(passthrough) {
|
|
423
|
+
return liftPassthroughName(passthrough).name;
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* The words `mnemom agent [session]` routes to a sub-verb (or a commander subcommand), so a
|
|
427
|
+
* session can never take one through `--name`: `-- --name setup -p x` would run provisioning.
|
|
428
|
+
*/
|
|
429
|
+
export const AGENT_SUB_VERBS = new Set([
|
|
430
|
+
"help",
|
|
431
|
+
"invite",
|
|
432
|
+
"config",
|
|
433
|
+
"slack",
|
|
434
|
+
"doctor",
|
|
435
|
+
"setup",
|
|
436
|
+
"list",
|
|
437
|
+
"claim",
|
|
438
|
+
"move",
|
|
439
|
+
"export",
|
|
440
|
+
"sessions",
|
|
441
|
+
"show",
|
|
442
|
+
"goal",
|
|
443
|
+
]);
|
|
444
|
+
/**
|
|
445
|
+
* The session name and Claude Code args of `mnemom agent [session] [passthrough...]`, with a
|
|
446
|
+
* flag commander took as the session put back. Commander fills `[session]` with the first
|
|
447
|
+
* operand even after `--`, so `mnemom agent -- --name x -p` arrived as session "--name" and
|
|
448
|
+
* passthrough `x -p` (probe P8). A leading-dash session can only have come from after `--`
|
|
449
|
+
* (before it, commander parses a flag as an option), so it returns to the front of the
|
|
450
|
+
* passthrough; with no session left, a passthrough `--name <x>` names it. Throws on an
|
|
451
|
+
* unusable name (liftPassthroughName) or a sub-verb word (AGENT_SUB_VERBS).
|
|
452
|
+
*/
|
|
453
|
+
export function splitSessionArg(session, passthrough) {
|
|
454
|
+
if (!session?.startsWith("-"))
|
|
455
|
+
return { scenario: session, passthrough: [...passthrough] };
|
|
456
|
+
const args = [session, ...passthrough];
|
|
457
|
+
const scenario = passthroughName(args);
|
|
458
|
+
if (scenario !== undefined && AGENT_SUB_VERBS.has(scenario.trim().toLowerCase())) {
|
|
459
|
+
throw new Error(`mnemom agent: '${scenario}' is a \`mnemom agent\` sub-verb, not a session name; pass another --name.`);
|
|
393
460
|
}
|
|
394
|
-
return
|
|
461
|
+
return { scenario, passthrough: args };
|
|
395
462
|
}
|
|
396
463
|
/** A single-line, header-safe conversation id: honour a caller value, else a uuid. */
|
|
397
464
|
export function resolveConversationId(raw) {
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The OpenAI half of `mnemom wrap`'s birth probe, kept free of imports so
|
|
3
|
+
* `gateway/test/providers-live/openai-live.test.ts` can send these exact bytes
|
|
4
|
+
* to OpenAI without installing the CLI's dependencies (MNE-8755).
|
|
5
|
+
*/
|
|
6
|
+
export declare const OPENAI_WRAP_DEFAULT_MODEL = "gpt-6-luna";
|
|
7
|
+
/**
|
|
8
|
+
* Chat Completions body for the one-time birth call. max_completion_tokens, not
|
|
9
|
+
* max_tokens: gpt-6-luna is a reasoning-profile model and OpenAI rejects max_tokens
|
|
10
|
+
* for those on Chat Completions (models-registry.ts REASONING_SUPPORTED_PARAMS).
|
|
11
|
+
* Older chat models accept max_completion_tokens too.
|
|
12
|
+
*/
|
|
13
|
+
export declare function openaiBirthProbeBody(model: string): {
|
|
14
|
+
model: string;
|
|
15
|
+
max_completion_tokens: number;
|
|
16
|
+
messages: {
|
|
17
|
+
role: "user";
|
|
18
|
+
content: string;
|
|
19
|
+
}[];
|
|
20
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The OpenAI half of `mnemom wrap`'s birth probe, kept free of imports so
|
|
3
|
+
* `gateway/test/providers-live/openai-live.test.ts` can send these exact bytes
|
|
4
|
+
* to OpenAI without installing the CLI's dependencies (MNE-8755).
|
|
5
|
+
*/
|
|
6
|
+
// gpt-6-luna replaces gpt-4o-mini, which OpenAI shuts down 2027-01-14. MNE-8754 ruled the
|
|
7
|
+
// successor; MNE-8755 made the switch.
|
|
8
|
+
export const OPENAI_WRAP_DEFAULT_MODEL = "gpt-6-luna";
|
|
9
|
+
/**
|
|
10
|
+
* Chat Completions body for the one-time birth call. max_completion_tokens, not
|
|
11
|
+
* max_tokens: gpt-6-luna is a reasoning-profile model and OpenAI rejects max_tokens
|
|
12
|
+
* for those on Chat Completions (models-registry.ts REASONING_SUPPORTED_PARAMS).
|
|
13
|
+
* Older chat models accept max_completion_tokens too.
|
|
14
|
+
*/
|
|
15
|
+
export function openaiBirthProbeBody(model) {
|
|
16
|
+
return {
|
|
17
|
+
model,
|
|
18
|
+
max_completion_tokens: 16,
|
|
19
|
+
messages: [{ role: "user", content: "hello" }],
|
|
20
|
+
};
|
|
21
|
+
}
|
package/dist/lib/config.d.ts
CHANGED
|
@@ -10,11 +10,17 @@ export declare const MNEMOM_DIR: string;
|
|
|
10
10
|
export type Environment = "production" | "local";
|
|
11
11
|
/**
|
|
12
12
|
* Thrown for `MNEMOM_ENV=staging`: the value used to be accepted, and the ring
|
|
13
|
-
* it named is gone (MNE-7968
|
|
14
|
-
* alternatives are both wrong: resolving to
|
|
15
|
-
* every call fail with a DNS/connection error
|
|
16
|
-
* falling through to production would send a caller
|
|
17
|
-
* ring at real customer data.
|
|
13
|
+
* it named is gone (MNE-7968, retired 2026-10-08 by platform#2446). Failing
|
|
14
|
+
* loud is deliberate. The two silent alternatives are both wrong: resolving to
|
|
15
|
+
* the dead staging hostnames makes every call fail with a DNS/connection error
|
|
16
|
+
* that does not say why, and falling through to production would send a caller
|
|
17
|
+
* who asked for a non-prod ring at real customer data.
|
|
18
|
+
*
|
|
19
|
+
* The message is customer-facing (published CLI), so it names only the public
|
|
20
|
+
* values. No internal card ids, no internal rings (MNE-8291: the Azure test
|
|
21
|
+
* cell is not a customer surface), and no link: the staging retirement has no
|
|
22
|
+
* public changelog entry by ruling (2026-10-16), so the message carries the
|
|
23
|
+
* facts itself.
|
|
18
24
|
*/
|
|
19
25
|
export declare class RetiredEnvironmentError extends Error {
|
|
20
26
|
constructor();
|
package/dist/lib/config.js
CHANGED
|
@@ -23,18 +23,22 @@ const WEBSITE_URLS = {
|
|
|
23
23
|
};
|
|
24
24
|
/**
|
|
25
25
|
* Thrown for `MNEMOM_ENV=staging`: the value used to be accepted, and the ring
|
|
26
|
-
* it named is gone (MNE-7968
|
|
27
|
-
* alternatives are both wrong: resolving to
|
|
28
|
-
* every call fail with a DNS/connection error
|
|
29
|
-
* falling through to production would send a caller
|
|
30
|
-
* ring at real customer data.
|
|
26
|
+
* it named is gone (MNE-7968, retired 2026-10-08 by platform#2446). Failing
|
|
27
|
+
* loud is deliberate. The two silent alternatives are both wrong: resolving to
|
|
28
|
+
* the dead staging hostnames makes every call fail with a DNS/connection error
|
|
29
|
+
* that does not say why, and falling through to production would send a caller
|
|
30
|
+
* who asked for a non-prod ring at real customer data.
|
|
31
|
+
*
|
|
32
|
+
* The message is customer-facing (published CLI), so it names only the public
|
|
33
|
+
* values. No internal card ids, no internal rings (MNE-8291: the Azure test
|
|
34
|
+
* cell is not a customer surface), and no link: the staging retirement has no
|
|
35
|
+
* public changelog entry by ruling (2026-10-16), so the message carries the
|
|
36
|
+
* facts itself.
|
|
31
37
|
*/
|
|
32
38
|
export class RetiredEnvironmentError extends Error {
|
|
33
39
|
constructor() {
|
|
34
|
-
super("MNEMOM_ENV=staging is no longer supported: the staging
|
|
35
|
-
"
|
|
36
|
-
"Use MNEMOM_RING=us-1 for the preview cell, MNEMOM_ENV=local for a local stack, " +
|
|
37
|
-
"or set MNEMOM_API_URL / MNEMOM_GATEWAY_URL / MNEMOM_WEBSITE_URL to target another cell.");
|
|
40
|
+
super("MNEMOM_ENV=staging is no longer supported: the staging environment was retired on 2026-10-08. " +
|
|
41
|
+
"Supported MNEMOM_ENV values are production (the default) and local.");
|
|
38
42
|
this.name = "RetiredEnvironmentError";
|
|
39
43
|
}
|
|
40
44
|
}
|
|
@@ -67,7 +67,12 @@ export interface PostRecord {
|
|
|
67
67
|
*/
|
|
68
68
|
export type ThreadIndex = Map<string, Map<string, Map<string, string | undefined>>>;
|
|
69
69
|
export declare function buildThreadIndex(posts: Iterable<PostRecord>): ThreadIndex;
|
|
70
|
-
/**
|
|
70
|
+
/**
|
|
71
|
+
* Normalise a session name for comparison (same shape the launcher emits): the
|
|
72
|
+
* lowercase alphanumeric runs joined by single dashes. Matching the runs is one
|
|
73
|
+
* linear pass, with no trim regex (`/-+$/` backtracks quadratically on a run of
|
|
74
|
+
* dashes).
|
|
75
|
+
*/
|
|
71
76
|
export declare function normSession(raw: string): string;
|
|
72
77
|
/**
|
|
73
78
|
* The live session `name` addresses: an exact (normalised) match, else the ONE live
|
package/dist/lib/slack-route.js
CHANGED
|
@@ -54,12 +54,14 @@ export function buildThreadIndex(posts) {
|
|
|
54
54
|
}
|
|
55
55
|
return idx;
|
|
56
56
|
}
|
|
57
|
-
/**
|
|
57
|
+
/**
|
|
58
|
+
* Normalise a session name for comparison (same shape the launcher emits): the
|
|
59
|
+
* lowercase alphanumeric runs joined by single dashes. Matching the runs is one
|
|
60
|
+
* linear pass, with no trim regex (`/-+$/` backtracks quadratically on a run of
|
|
61
|
+
* dashes).
|
|
62
|
+
*/
|
|
58
63
|
export function normSession(raw) {
|
|
59
|
-
return raw
|
|
60
|
-
.toLowerCase()
|
|
61
|
-
.replace(/[^a-z0-9]+/g, "-")
|
|
62
|
-
.replace(/^-+|-+$/g, "");
|
|
64
|
+
return (raw.toLowerCase().match(/[a-z0-9]+/g) ?? []).join("-");
|
|
63
65
|
}
|
|
64
66
|
const HANDLE_RE = /(?:^|[\s(])@([a-z0-9][a-z0-9-]*[a-z0-9])(?=$|[\s,.:;!?)])/gi;
|
|
65
67
|
/**
|