@mnemom/mnemom 0.17.7-next.1 → 0.17.7
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/README.md +11 -23
- package/dist/commands/agent-doctor.d.ts +4 -21
- package/dist/commands/agent-doctor.js +5 -51
- package/dist/commands/agent-goal.d.ts +1 -1
- package/dist/commands/agent-goal.js +3 -6
- package/dist/commands/agent-setup.d.ts +5 -16
- package/dist/commands/agent-setup.js +12 -29
- package/dist/commands/agent.d.ts +9 -32
- package/dist/commands/agent.js +43 -131
- package/dist/commands/wrap.d.ts +0 -8
- package/dist/commands/wrap.js +3 -36
- package/dist/index.js +1 -1
- package/dist/lib/agent-ledger.d.ts +3 -22
- package/dist/lib/agent-ledger.js +0 -23
- package/dist/lib/agent.d.ts +27 -64
- package/dist/lib/agent.js +26 -58
- package/dist/lib/api.d.ts +2 -27
- package/dist/lib/api.js +2 -64
- package/dist/lib/launcher-config.d.ts +4 -5
- package/dist/lib/launcher-config.js +1 -1
- package/dist/lib/subscription-login.d.ts +20 -152
- package/dist/lib/subscription-login.js +28 -215
- package/dist/mcp/handoff-server.mjs +13 -35
- package/dist/rc-proxy.mjs +3 -6
- package/package.json +1 -1
package/dist/commands/agent.js
CHANGED
|
@@ -33,10 +33,10 @@ import { basename, join, dirname } from "node:path";
|
|
|
33
33
|
import { fileURLToPath } from "node:url";
|
|
34
34
|
import { statuslineLaunch } from "../lib/statusline.js";
|
|
35
35
|
import { getApiUrl, getGatewayUrl, MNEMOM_DIR } from "../lib/config.js";
|
|
36
|
-
import {
|
|
37
|
-
import {
|
|
38
|
-
import { LEDGER_FILE, appendSessionRecord, findResumeCandidate, readSessionRecords, resolveRetentionDays,
|
|
39
|
-
import {
|
|
36
|
+
import { createApiKey, getAgentByName } from "../lib/api.js";
|
|
37
|
+
import { MNEMOM_KEY_STORE_FIELD, resolveAgentAuth, resolveSubscriptionMnemomKey, subscriptionChildEnv, subscriptionShape, } from "../lib/subscription-login.js";
|
|
38
|
+
import { LEDGER_FILE, appendSessionRecord, findResumeCandidate, readSessionRecords, resolveRetentionDays, toLedgerContract, } from "../lib/agent-ledger.js";
|
|
39
|
+
import { loadCliConfig } from "../lib/cli-config.js";
|
|
40
40
|
import { configKeySpec, loadLauncherConfig, writeLauncherConfig } from "../lib/launcher-config.js";
|
|
41
41
|
import { experimentalNotice } from "../lib/experimental.js";
|
|
42
42
|
import { fmt } from "../lib/format.js";
|
|
@@ -45,8 +45,8 @@ import { authConfigured, chooseAuth, detectClaudeLogin, readClaudeLoginState, }
|
|
|
45
45
|
import { requireConsoleKey, readConsoleKey } from "../lib/console-key.js";
|
|
46
46
|
import { goalAlignmentLine, resolveSessionSetup } from "./agent-session.js";
|
|
47
47
|
import { AgentIdentityMismatchError, defaultAgentSlug, ensureGovernedAgent, ensureSessionAndBalance, isQuietPreambleExit, resolveDefaultSlug, resolvePreflightedAccount, } from "./agent-setup.js";
|
|
48
|
-
import { assembleContract, buildGoalModeHeader, buildCustomHeaders, buildHandoffMcpConfigJson, buildSessionConfig, buildSessionConfigB64, assertCompactAtCompatible, compactAtWindowWarning, describeCompactAt, launchWindow, resolveCompactAt, applyGatewayHintHeaders, resolveCompactionIntercept, resolveContextHandoff, resolveContextProfile, resolveHandoffGatewayHost, HANDOFF_RC_KEY_ENV_NAME,
|
|
49
|
-
import { LAUNCHER_STORE_PATH, mergeStore
|
|
48
|
+
import { assembleContract, buildGoalModeHeader, buildCustomHeaders, buildHandoffMcpConfigJson, buildSessionConfig, buildSessionConfigB64, assertCompactAtCompatible, compactAtWindowWarning, describeCompactAt, launchWindow, resolveCompactAt, applyGatewayHintHeaders, resolveCompactionIntercept, resolveContextHandoff, resolveContextProfile, resolveHandoffGatewayHost, HANDOFF_RC_KEY_ENV_NAME, isProdGateway, launchPreferenceExplicit, launchPreflighted, remoteControlAllowed, applyToolSearchDefault, DISABLE_1M_ENV, readCcSettings, resolveWindowModel, resolveAnthropicDoor, resolveLaunchModel, resolveConversationId, resolveGatewayHost, singleCellError, resolveLaunchShape, resolveRemoteControlShape, passthroughName, sanitizeAgentName, sessionDisplayName, SESSION_NAME_ENV, SESSION_CONFIG_HEADER, } from "../lib/agent.js";
|
|
49
|
+
import { LAUNCHER_STORE_PATH, mergeStore } from "../lib/launcher-store.js";
|
|
50
50
|
import { slackChannelArgs } from "../lib/slack-launch.js";
|
|
51
51
|
/** The CLI's own provider-key store (zero 1Password): ~/.mnemom/launcher.json, 0600. */
|
|
52
52
|
const CODE_STORE = LAUNCHER_STORE_PATH;
|
|
@@ -54,19 +54,13 @@ const say = (line = "") => console.error(line);
|
|
|
54
54
|
/** The one-line launch note when the compaction intercept is ON. */
|
|
55
55
|
const COMPACTION_INTERCEPT_NOTE = "mnemom agent: compaction intercept ON (experimental) — requested from the gateway (GATEWAY_USAGE_REAL_SIZE, GATEWAY_COMPACTION_INTERCEPT); a gateway without the companion support drops both keys and the session runs as before.";
|
|
56
56
|
/**
|
|
57
|
-
* The one-line launch note for
|
|
58
|
-
*
|
|
57
|
+
* The one-line launch note for a non-default context profile (`balanced`, the default, is
|
|
58
|
+
* silent — nothing is sent). `off` keeps the wording `--no-context` has always printed.
|
|
59
59
|
*/
|
|
60
60
|
function sayContextProfile(profile) {
|
|
61
|
-
if (profile ===
|
|
62
|
-
say("mnemom agent: context: cell default");
|
|
63
|
-
}
|
|
64
|
-
else if (profile === "off") {
|
|
61
|
+
if (profile === "off") {
|
|
65
62
|
say("mnemom agent: context folding OFF for this session (--no-context or context = false).");
|
|
66
63
|
}
|
|
67
|
-
else if (profile === "balanced") {
|
|
68
|
-
say("mnemom agent: context profile balanced — the gateway keeps more of the earlier work verbatim and folds later.");
|
|
69
|
-
}
|
|
70
64
|
else if (profile === "aggressive") {
|
|
71
65
|
say("mnemom agent: context profile aggressive — the gateway folds earlier and keeps a tighter working set.");
|
|
72
66
|
}
|
|
@@ -180,12 +174,11 @@ export async function agentCommand(scenarioArg, options = {}) {
|
|
|
180
174
|
const cellNote = isProdGateway(env) ? "us-2/prod" : `custom (${gatewayHost})`;
|
|
181
175
|
const yolo = options.yolo !== false; // default ON
|
|
182
176
|
const contextHint = options.contextHint !== false; // default ON
|
|
183
|
-
// Context profile:
|
|
184
|
-
//
|
|
185
|
-
//
|
|
186
|
-
//
|
|
187
|
-
|
|
188
|
-
let contextProfile = resolveContextProfile({
|
|
177
|
+
// Context profile: `balanced` (the gateway's own defaults) unless --context-profile /
|
|
178
|
+
// MNEMOM_AGENT_CONTEXT_PROFILE (the `context_profile` setting) says otherwise; --no-context
|
|
179
|
+
// (or `context = false` in launcher.toml → MNEMOM_AGENT_CONTEXT) is `off`. Anything but
|
|
180
|
+
// balanced rides in the per-session config header; `undefined` sessionConfigB64 ⇒ nothing sent.
|
|
181
|
+
const contextProfile = resolveContextProfile({
|
|
189
182
|
contextProfile: options.contextProfile,
|
|
190
183
|
context: options.context,
|
|
191
184
|
env,
|
|
@@ -216,7 +209,7 @@ export async function agentCommand(scenarioArg, options = {}) {
|
|
|
216
209
|
compactionIntercept,
|
|
217
210
|
compactAt,
|
|
218
211
|
});
|
|
219
|
-
|
|
212
|
+
const sessionConfigB64 = buildSessionConfigB64({
|
|
220
213
|
profile: contextProfile,
|
|
221
214
|
contextHandoff,
|
|
222
215
|
compactionIntercept,
|
|
@@ -309,9 +302,8 @@ export async function agentCommand(scenarioArg, options = {}) {
|
|
|
309
302
|
// lives in (default: the user's personal org). No µ ⇒ blocked with a
|
|
310
303
|
// portal-style top-up message. Skipped only on --dry-run (handled above).
|
|
311
304
|
// MNEMOM_AGENT_PREFLIGHTED=1 (orchestrators such as the ADW fleet that already
|
|
312
|
-
// ran their own session + balance preflight) skips those round trips; the
|
|
313
|
-
//
|
|
314
|
-
// resolvePreflightedAccount.
|
|
305
|
+
// ran their own session + balance preflight) skips those round trips; the
|
|
306
|
+
// entitlement is still fetched — see resolvePreflightedAccount.
|
|
315
307
|
let account;
|
|
316
308
|
try {
|
|
317
309
|
account = launchPreflighted(env)
|
|
@@ -351,19 +343,14 @@ export async function agentCommand(scenarioArg, options = {}) {
|
|
|
351
343
|
rcCapable: rc.ok,
|
|
352
344
|
say,
|
|
353
345
|
});
|
|
354
|
-
// Subscription login: Remote Control only for RC-entitled (internal) accounts
|
|
346
|
+
// Subscription login: Remote Control only for RC-entitled (internal) accounts, and the governed identity anchors on a
|
|
347
|
+
// Mnemom API key (minted once and stored if there is none). Resolved after sign-in so
|
|
348
|
+
// minting can use the session.
|
|
349
|
+
let mnemomKey;
|
|
355
350
|
if (auth === "subscription") {
|
|
356
351
|
shape = subscriptionShape(shape, shapeExplicit, rcAllowed, say);
|
|
352
|
+
mnemomKey = await resolveSubscriptionMnemomKey(defaultMnemomKeyDeps());
|
|
357
353
|
}
|
|
358
|
-
// Every launch — api-key, console and subscription, every shape — sends a Mnemom API key
|
|
359
|
-
// (x-mnemom-api-key): the gateway refuses `mnemom agent` traffic without a verified one.
|
|
360
|
-
// Resolved after sign-in so minting and the one-time liveness check can use the session: the
|
|
361
|
-
// key stored for the active org, checked once (a revoked/rotated one is re-minted), else a
|
|
362
|
-
// fresh key minted for the launch's org (account.orgId: the active org, else the personal
|
|
363
|
-
// org — the org the agent is claimed into, which the gateway's org check requires of the key)
|
|
364
|
-
// and stored. No key ⇒ this throws and the launch does not start (a session the gateway
|
|
365
|
-
// would refuse is worse than an actionable error).
|
|
366
|
-
const mnemomKey = await resolveLaunchMnemomKey(defaultMnemomKeyDeps(CODE_STORE, account.orgId));
|
|
367
354
|
// Now that a session exists, a defaulted slug uses the real login handle from
|
|
368
355
|
// /v1/auth/me (the stored token has no email, so the pre-sign-in value was the
|
|
369
356
|
// "agent-user" fallback). Only when the caller didn't name an agent. Under an
|
|
@@ -394,7 +381,6 @@ export async function agentCommand(scenarioArg, options = {}) {
|
|
|
394
381
|
const ensured = await ensureGovernedAgent({
|
|
395
382
|
slug: agent,
|
|
396
383
|
anthropicKey,
|
|
397
|
-
mnemomKey,
|
|
398
384
|
setup: options.setup,
|
|
399
385
|
gatewayHost,
|
|
400
386
|
});
|
|
@@ -438,33 +424,6 @@ export async function agentCommand(scenarioArg, options = {}) {
|
|
|
438
424
|
passthrough: [...(options.passthrough ?? []), "--resume", continuation.resumeCcSessionId],
|
|
439
425
|
};
|
|
440
426
|
}
|
|
441
|
-
// A launch that resumes a Claude Code session (the continuation above, or the caller's own
|
|
442
|
-
// `-- --resume <id>`) keeps the profile that session ran under, unless this launch chose one
|
|
443
|
-
// with a flag. Read from the ledger only — nothing here writes a default anywhere.
|
|
444
|
-
const resumeId = shape === "server" ? null : passthroughResumeId(options.passthrough ?? []);
|
|
445
|
-
if (resumeId && options.contextProfile === undefined && options.context !== false) {
|
|
446
|
-
let resumed;
|
|
447
|
-
try {
|
|
448
|
-
resumed = resumedContextProfile(readSessionRecords({ dir: MNEMOM_DIR }), {
|
|
449
|
-
ccSessionId: resumeId,
|
|
450
|
-
orgId: account.orgId,
|
|
451
|
-
gatewayHost,
|
|
452
|
-
});
|
|
453
|
-
}
|
|
454
|
-
catch {
|
|
455
|
-
resumed = null; // best-effort: an unreadable ledger keeps the launch's own resolution
|
|
456
|
-
}
|
|
457
|
-
if (resumed !== null && resumed !== contextProfile) {
|
|
458
|
-
contextProfile = resolveContextProfile({ context: options.context, resumed, env });
|
|
459
|
-
sessionConfigB64 = buildSessionConfigB64({
|
|
460
|
-
profile: contextProfile,
|
|
461
|
-
contextHandoff,
|
|
462
|
-
compactionIntercept,
|
|
463
|
-
compactAt,
|
|
464
|
-
experimental,
|
|
465
|
-
});
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
427
|
// ── local session ledger (~/.mnemom/sessions.jsonl) ── one line per real
|
|
469
428
|
// launch so `mnemom agent sessions` / `show` can find this conversation later.
|
|
470
429
|
// Written after provisioning so a freshly claimed agent's id resolves. Holds
|
|
@@ -521,22 +480,15 @@ export async function agentCommand(scenarioArg, options = {}) {
|
|
|
521
480
|
// back out at request time. Remote Control (both shapes) strips every ANTHROPIC_* auth
|
|
522
481
|
// var from the child's env, so `spawnRcCli` below instead sets a dedicated, non-ANTHROPIC
|
|
523
482
|
// var (HANDOFF_RC_KEY_ENV_NAME) on that same child env — still nothing in this file.
|
|
524
|
-
|
|
525
|
-
// in ANTHROPIC_CUSTOM_HEADERS (terminal), or HANDOFF_RC_MNEMOM_KEY_ENV_NAME (RC).
|
|
526
|
-
const terminal = shape === "terminal";
|
|
527
|
-
const anthropicKeySource = terminal
|
|
483
|
+
const anthropicKeySource = shape === "terminal"
|
|
528
484
|
? { envName: "ANTHROPIC_CUSTOM_HEADERS" }
|
|
529
485
|
: { envName: HANDOFF_RC_KEY_ENV_NAME };
|
|
530
|
-
const mnemomKeySource = terminal
|
|
531
|
-
? { envName: "ANTHROPIC_CUSTOM_HEADERS" }
|
|
532
|
-
: { envName: HANDOFF_RC_MNEMOM_KEY_ENV_NAME };
|
|
533
486
|
handoffMcpConfigPath = writeHandoffMcpConfigFile(buildHandoffMcpConfigJson({
|
|
534
487
|
// The SAME origin the session's own model traffic goes to — not resolveGatewayHost,
|
|
535
488
|
// which would ignore a MNEMOM_AGENT_ANTHROPIC_GATEWAY override (a test-cell launch
|
|
536
489
|
// must never send its handoff calls to prod, or vice versa).
|
|
537
490
|
gatewayHost: resolveHandoffGatewayHost(door),
|
|
538
491
|
anthropicKeySource,
|
|
539
|
-
mnemomKeySource,
|
|
540
492
|
agent,
|
|
541
493
|
conversationId,
|
|
542
494
|
sessionConfigB64,
|
|
@@ -737,9 +689,7 @@ export async function recordSessionLaunch(ctx) {
|
|
|
737
689
|
cli: ctx.cli.name,
|
|
738
690
|
contract: toLedgerContract(ctx.envelope?.contract ?? null),
|
|
739
691
|
goal_mode: goalAlignmentMode(ctx.envelope, ctx.goalMode),
|
|
740
|
-
|
|
741
|
-
? { context_profile: ctx.contextProfile, context_profile_v: 2 }
|
|
742
|
-
: {}),
|
|
692
|
+
context_profile: ctx.contextProfile,
|
|
743
693
|
compact_at: ctx.compactAt,
|
|
744
694
|
cc_session_id: ctx.ccSessionId ?? null,
|
|
745
695
|
...(ctx.options.experimental?.length ? { experimental: [...ctx.options.experimental] } : {}),
|
|
@@ -756,55 +706,25 @@ export async function recordSessionLaunch(ctx) {
|
|
|
756
706
|
// ───────────────────────────────────────────────────────────────────────────
|
|
757
707
|
// Credential resolution (zero 1Password)
|
|
758
708
|
// ───────────────────────────────────────────────────────────────────────────
|
|
759
|
-
/**
|
|
760
|
-
export
|
|
761
|
-
/**
|
|
762
|
-
* Mint the launch's key: an ORG-owned key for `orgId` (the launch's org) with scopes exactly
|
|
763
|
-
* ["gateway"], which any owner/admin/member may mint — so the gateway's agent-traffic org check
|
|
764
|
-
* (agent org = key org) passes for the agent the launcher claims into that org. Never a personal
|
|
765
|
-
* key: it has no org, and the gateway would refuse every governed turn with it. A refusal (403:
|
|
766
|
-
* an API that predates member org keys, or a viewer/auditor role) is thrown as is; the launch
|
|
767
|
-
* stops with guidance (lib/subscription-login.ts mintAndStore).
|
|
768
|
-
*/
|
|
769
|
-
export async function mintLaunchMnemomKey(orgId) {
|
|
770
|
-
const k = await createOrgApiKey(orgId, `mnemom-agent ${hostname()}`, ["gateway"]);
|
|
771
|
-
return { key: k.key, keyId: k.key_id };
|
|
772
|
-
}
|
|
773
|
-
/**
|
|
774
|
-
* The real I/O behind every launch's Mnemom key (lib/subscription-login.ts). `launchOrgId` is
|
|
775
|
-
* the org the launch runs in (ensureSessionAndBalance's `orgId`: the active org, else the
|
|
776
|
-
* caller's personal org); omitted or empty, it is the active org.
|
|
777
|
-
*/
|
|
778
|
-
export function defaultMnemomKeyDeps(storePath = CODE_STORE, launchOrgId) {
|
|
779
|
-
const activeOrgId = getActiveOrg()?.org_id || null;
|
|
709
|
+
/** The real I/O behind a subscription launch's Mnemom key (lib/subscription-login.ts). */
|
|
710
|
+
export function defaultMnemomKeyDeps(storePath = CODE_STORE) {
|
|
780
711
|
return {
|
|
781
712
|
env: process.env,
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
713
|
+
readStored: () => {
|
|
714
|
+
try {
|
|
715
|
+
const data = JSON.parse(readFileSync(storePath, "utf8"));
|
|
716
|
+
const v = data[MNEMOM_KEY_STORE_FIELD];
|
|
717
|
+
return typeof v === "string" ? stripCrlf(v) : undefined;
|
|
718
|
+
}
|
|
719
|
+
catch {
|
|
720
|
+
return undefined;
|
|
721
|
+
}
|
|
722
|
+
},
|
|
723
|
+
mint: async () => (await createApiKey(`mnemom-agent ${hostname()}`, ["gateway"])).key,
|
|
724
|
+
store: (key) => mergeStore({ [MNEMOM_KEY_STORE_FIELD]: key }, storePath),
|
|
794
725
|
say,
|
|
795
726
|
};
|
|
796
727
|
}
|
|
797
|
-
/**
|
|
798
|
-
* The Mnemom key already on this machine (MNEMOM_AGENT_MNEMOM_KEY, then ~/.mnemom/launcher.json),
|
|
799
|
-
* never minted: `mnemom agent doctor` reports its source, the goal commands send it.
|
|
800
|
-
*/
|
|
801
|
-
export function localLaunchMnemomKey(storePath = CODE_STORE) {
|
|
802
|
-
return readLocalMnemomKey(defaultMnemomKeyDeps(storePath));
|
|
803
|
-
}
|
|
804
|
-
/** The launch's one-time check of that key (`mnemom agent doctor` reports it). Never throws. */
|
|
805
|
-
export function verifyLocalMnemomKey(local) {
|
|
806
|
-
return defaultMnemomKeyDeps().check(local.key, local.keyId);
|
|
807
|
-
}
|
|
808
728
|
/** The real machine behind lib/console-key.ts: the macOS keychain via `security`, no shell. */
|
|
809
729
|
export function defaultConsoleKeyDeps(env) {
|
|
810
730
|
return {
|
|
@@ -1080,8 +1000,6 @@ function launchTerminal(ctx) {
|
|
|
1080
1000
|
if (ctx.anthropicKey) {
|
|
1081
1001
|
childEnv = { ...process.env };
|
|
1082
1002
|
delete childEnv.ANTHROPIC_API_KEY;
|
|
1083
|
-
// The Mnemom key already rides in the headers; the child has no other use for it.
|
|
1084
|
-
Reflect.deleteProperty(childEnv, MNEMOM_KEY_ENV);
|
|
1085
1003
|
childEnv.ANTHROPIC_BASE_URL = ctx.door;
|
|
1086
1004
|
childEnv.ANTHROPIC_AUTH_TOKEN = "mnemom-gateway";
|
|
1087
1005
|
childEnv.ANTHROPIC_CUSTOM_HEADERS = headers;
|
|
@@ -1266,10 +1184,9 @@ function launchRemoteControl(ctx) {
|
|
|
1266
1184
|
}
|
|
1267
1185
|
};
|
|
1268
1186
|
process.on("exit", onExit);
|
|
1269
|
-
// Secrets on stdin — three lines (anthropic, mnemom, openai).
|
|
1270
|
-
// key
|
|
1271
|
-
//
|
|
1272
|
-
// OAuth bearer is what pays). openai is always empty on this Anthropic-only door.
|
|
1187
|
+
// Secrets on stdin — three lines (anthropic, mnemom, openai). An api-key launch sends the
|
|
1188
|
+
// Anthropic key only; a subscription launch sends the Mnemom key only (the identity anchor —
|
|
1189
|
+
// the user's OAuth bearer is what pays). openai is always empty on this Anthropic-only door.
|
|
1273
1190
|
// A proxy that exits before reading them turns this write into an EPIPE; that is
|
|
1274
1191
|
// not a failure of its own — the exited-before-READY check below refuses the
|
|
1275
1192
|
// launch. Any other stdin error fails closed like a spawn error.
|
|
@@ -1280,7 +1197,7 @@ function launchRemoteControl(ctx) {
|
|
|
1280
1197
|
onExit();
|
|
1281
1198
|
process.exit(1);
|
|
1282
1199
|
});
|
|
1283
|
-
proxy.stdin?.end(`${ctx.anthropicKey ?? ""}\n${ctx.mnemomKey}\n\n`);
|
|
1200
|
+
proxy.stdin?.end(`${ctx.anthropicKey ?? ""}\n${ctx.anthropicKey ? "" : (ctx.mnemomKey ?? "")}\n\n`);
|
|
1284
1201
|
let stdoutBuf = "";
|
|
1285
1202
|
const start = Date.now();
|
|
1286
1203
|
const tryStart = () => {
|
|
@@ -1348,9 +1265,6 @@ function spawnRcCli(ctx) {
|
|
|
1348
1265
|
"no_proxy",
|
|
1349
1266
|
"HTTP_PROXY",
|
|
1350
1267
|
"http_proxy",
|
|
1351
|
-
// The Mnemom key rides to the gateway via the proxy (stdin) and, with handoff on, in
|
|
1352
|
-
// HANDOFF_RC_MNEMOM_KEY_ENV_NAME below; the CLI itself has no use for the launch's env var.
|
|
1353
|
-
MNEMOM_KEY_ENV,
|
|
1354
1268
|
]);
|
|
1355
1269
|
const childEnv = {};
|
|
1356
1270
|
for (const [k, v] of Object.entries(process.env)) {
|
|
@@ -1378,10 +1292,8 @@ function spawnRcCli(ctx) {
|
|
|
1378
1292
|
// ride through here ungated), so this rides through untouched. Only set when the tool is
|
|
1379
1293
|
// actually registered — never on a plain RC launch.
|
|
1380
1294
|
// (Never on a subscription launch — handoff is turned off there, so there is no key to set.)
|
|
1381
|
-
// The Mnemom key the gateway requires rides beside it in its own non-ANTHROPIC var.
|
|
1382
1295
|
if (ctx.handoffMcpConfigPath && ctx.anthropicKey) {
|
|
1383
1296
|
childEnv[HANDOFF_RC_KEY_ENV_NAME] = ctx.anthropicKey;
|
|
1384
|
-
childEnv[HANDOFF_RC_MNEMOM_KEY_ENV_NAME] = ctx.mnemomKey;
|
|
1385
1297
|
}
|
|
1386
1298
|
let args;
|
|
1387
1299
|
if (ctx.shape === "server") {
|
|
@@ -1654,7 +1566,7 @@ export function printPlan(ctx) {
|
|
|
1654
1566
|
L.push(` agent: ${ctx.agent}`);
|
|
1655
1567
|
L.push(` conversation: ${ctx.conversationId}`);
|
|
1656
1568
|
L.push(` yolo: ${ctx.yolo ? "on (--dangerously-skip-permissions)" : "off"}`);
|
|
1657
|
-
L.push(` context: ${ctx.contextProfile
|
|
1569
|
+
L.push(` context: ${ctx.contextProfile}`);
|
|
1658
1570
|
L.push(` compaction: ${describeCompactAt(ctx.compactAt)}`);
|
|
1659
1571
|
const overlay = Object.entries(ctx.sessionConfig);
|
|
1660
1572
|
if (overlay.length > 0) {
|
package/dist/commands/wrap.d.ts
CHANGED
|
@@ -26,13 +26,5 @@ 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
|
-
/**
|
|
30
|
-
* The caller's Mnemom API key, sent as `x-mnemom-api-key` together with a no-op
|
|
31
|
-
* `x-mnemom-session-config` (NOOP_SESSION_CONFIG_B64). The session config makes the call
|
|
32
|
-
* Mnemom agent traffic, so the gateway verifies the key and births a NEW agent in the key's
|
|
33
|
-
* org; the caller's claim step then records the claim in that same org. `mnemom agent`
|
|
34
|
-
* always sends its launch key; `mnemom wrap` sends neither header (plain traffic, as before).
|
|
35
|
-
*/
|
|
36
|
-
mnemomKey?: string;
|
|
37
29
|
}): Promise<string>;
|
|
38
30
|
export {};
|
package/dist/commands/wrap.js
CHANGED
|
@@ -20,9 +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 { NOOP_SESSION_CONFIG_B64, SESSION_CONFIG_HEADER } from "../lib/agent.js";
|
|
24
23
|
const AGENT_HEADER = "x-mnemom-agent";
|
|
25
|
-
const MNEMOM_KEY_HEADER = "x-mnemom-api-key";
|
|
26
24
|
const PROVIDERS = {
|
|
27
25
|
anthropic: {
|
|
28
26
|
label: "Anthropic",
|
|
@@ -210,10 +208,6 @@ export async function birthThroughGateway(gatewayUrl, provider, agentName, provi
|
|
|
210
208
|
[prov.keyHeader]: authValue,
|
|
211
209
|
[AGENT_HEADER]: agentName,
|
|
212
210
|
};
|
|
213
|
-
if (opts.mnemomKey) {
|
|
214
|
-
headers[MNEMOM_KEY_HEADER] = opts.mnemomKey;
|
|
215
|
-
headers[SESSION_CONFIG_HEADER] = NOOP_SESSION_CONFIG_B64;
|
|
216
|
-
}
|
|
217
211
|
if (provider === "anthropic") {
|
|
218
212
|
headers["anthropic-version"] = "2023-06-01";
|
|
219
213
|
}
|
|
@@ -241,24 +235,13 @@ export async function birthThroughGateway(gatewayUrl, provider, agentName, provi
|
|
|
241
235
|
messages: [{ role: "user", content: "hello" }],
|
|
242
236
|
});
|
|
243
237
|
}
|
|
244
|
-
const
|
|
238
|
+
const response = await fetch(url, {
|
|
245
239
|
method: "POST",
|
|
246
|
-
headers
|
|
240
|
+
headers,
|
|
247
241
|
body,
|
|
248
242
|
signal: opts.timeoutMs ? AbortSignal.timeout(opts.timeoutMs) : undefined,
|
|
249
243
|
});
|
|
250
|
-
|
|
251
|
-
let agentId = response.headers.get(AGENT_HEADER);
|
|
252
|
-
// An agent an earlier plain call already birthed sits unclaimed in the sandbox org, and the
|
|
253
|
-
// gateway refuses agent traffic for it with 402 `agent_unclaimed` (and no agent id) — yet that
|
|
254
|
-
// is exactly the agent `mnemom agent setup` must claim. Ask once more as plain traffic (no
|
|
255
|
-
// session config): that serves it exactly as before and returns the id for the claim step.
|
|
256
|
-
if (!agentId && SESSION_CONFIG_HEADER in headers && (await isAgentUnclaimedRefusal(response))) {
|
|
257
|
-
const plain = { ...headers };
|
|
258
|
-
Reflect.deleteProperty(plain, SESSION_CONFIG_HEADER);
|
|
259
|
-
response = await post(plain);
|
|
260
|
-
agentId = response.headers.get(AGENT_HEADER);
|
|
261
|
-
}
|
|
244
|
+
const agentId = response.headers.get(AGENT_HEADER);
|
|
262
245
|
if (!response.ok && !agentId) {
|
|
263
246
|
const text = await response.text().catch(() => "");
|
|
264
247
|
throw new MnemomApiError(response.status, `Gateway returned ${response.status}: ${text.slice(0, 300)}`);
|
|
@@ -268,22 +251,6 @@ export async function birthThroughGateway(gatewayUrl, provider, agentName, provi
|
|
|
268
251
|
}
|
|
269
252
|
return agentId;
|
|
270
253
|
}
|
|
271
|
-
/**
|
|
272
|
-
* Is `response` the gateway's agent-traffic refusal for an agent still unclaimed in the sandbox
|
|
273
|
-
* org — 402 with `error.details.reason === "agent_unclaimed"`? Reads a clone, so the caller can
|
|
274
|
-
* still read the body. Anything unparseable is `false`.
|
|
275
|
-
*/
|
|
276
|
-
async function isAgentUnclaimedRefusal(response) {
|
|
277
|
-
if (response.status !== 402)
|
|
278
|
-
return false;
|
|
279
|
-
try {
|
|
280
|
-
const body = (await response.clone().json());
|
|
281
|
-
return body?.error?.details?.reason === "agent_unclaimed";
|
|
282
|
-
}
|
|
283
|
-
catch {
|
|
284
|
-
return false;
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
254
|
// ── Starter cards ─────────────────────────────────────────────────────────────
|
|
288
255
|
function buildStarterAlignmentCard(agentId) {
|
|
289
256
|
return {
|
package/dist/index.js
CHANGED
|
@@ -175,7 +175,7 @@ const agentCmd = program
|
|
|
175
175
|
.addOption(new Option("--slack", "EXPERIMENTAL (`mnemom experimental enable slack`): attach the Slack channel — messages addressed to this session arrive in it, and it can reply (see `mnemom agent slack`)").hideHelp(hiddenUnless("slack")))
|
|
176
176
|
.option("--context", "Force context folding ON for this launch even when launcher.toml (context = false) or MNEMOM_AGENT_CONTEXT turns it off")
|
|
177
177
|
.option("--no-context", "Disable context folding for this launch (folding is on by default; tells the gateway to skip it for this session)")
|
|
178
|
-
.addOption(new Option("--context-profile <profile>", 'How the gateway folds this session\'s context.
|
|
178
|
+
.addOption(new Option("--context-profile <profile>", 'How the gateway folds this session\'s context. "balanced" (default) keeps more of the earlier work verbatim and folds later — for long sessions that revisit earlier files and decisions. "aggressive" folds earlier and keeps a tighter working set — for cost-sensitive sessions and short, focused tasks. "adaptive" sizes the fold to the conversation: no fold below 200k tokens, aggressive from 200k to 1M, balanced above. "off" leaves compaction to the coding agent. Also: config key context_profile, env MNEMOM_AGENT_CONTEXT_PROFILE.').argParser((v) => {
|
|
179
179
|
// One validator for all three surfaces (flag, MNEMOM_AGENT_CONTEXT_PROFILE,
|
|
180
180
|
// launcher.toml context_profile): trimmed, case-insensitive, same error wording.
|
|
181
181
|
try {
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* `sessions.retention_days` (default 30) are dropped when the next one is
|
|
15
15
|
* recorded. No count cap — a long retention keeps everything in the window.
|
|
16
16
|
*/
|
|
17
|
-
import {
|
|
17
|
+
import type { ContextProfile, LaunchShape, SealedContract } from "./agent.js";
|
|
18
18
|
/** The ledger's file name under MNEMOM_DIR. */
|
|
19
19
|
export declare const LEDGER_FILE = "sessions.jsonl";
|
|
20
20
|
/** Days a launch stays in the ledger when `sessions.retention_days` is unset. */
|
|
@@ -77,17 +77,10 @@ export interface SessionRecord {
|
|
|
77
77
|
cc_session_id?: string | null;
|
|
78
78
|
/**
|
|
79
79
|
* The context profile the launch ran under (`--context-profile` / `context_profile` /
|
|
80
|
-
* `MNEMOM_AGENT_CONTEXT_PROFILE`, or
|
|
81
|
-
*
|
|
80
|
+
* `MNEMOM_AGENT_CONTEXT_PROFILE`, or `balanced` by default). Optional: records written
|
|
81
|
+
* before profiles existed omit it (still `v: 1`).
|
|
82
82
|
*/
|
|
83
83
|
context_profile?: ContextProfile;
|
|
84
|
-
/**
|
|
85
|
-
* `2` when `context_profile` was written with complete profiles (`balanced` sends size policy
|
|
86
|
-
* off, `aggressive` adds it). Absent on older records, where `balanced` was also written for a
|
|
87
|
-
* launch that chose nothing and sent no fold key — so a resume reads an unmarked `balanced` as
|
|
88
|
-
* the cell default, which is what that session actually ran under.
|
|
89
|
-
*/
|
|
90
|
-
context_profile_v?: 2;
|
|
91
84
|
/**
|
|
92
85
|
* The resolved gateway compaction target (`--compact-at` / `compact_at` /
|
|
93
86
|
* `MNEMOM_COMPACT_AT`): `null` means no target (Claude Code's own 1M window; the default,
|
|
@@ -175,15 +168,3 @@ export declare function findResumeCandidate(records: readonly SessionRecord[], w
|
|
|
175
168
|
gatewayHost: string;
|
|
176
169
|
resumeId?: string | null;
|
|
177
170
|
}): ResumeCandidate | null;
|
|
178
|
-
/**
|
|
179
|
-
* The context profile the Claude Code session `ccSessionId` ran under — the newest row with the
|
|
180
|
-
* same org and gateway host that pinned that session — so a launch that resumes it
|
|
181
|
-
* (`--resume <id>`) keeps that session's own profile. `null` when no row matches, the row chose
|
|
182
|
-
* no profile (the cell default), it is an older row's `balanced` (see `context_profile_v`), or its
|
|
183
|
-
* value is not a known profile (the file is hand-editable).
|
|
184
|
-
*/
|
|
185
|
-
export declare function resumedContextProfile(records: readonly SessionRecord[], want: {
|
|
186
|
-
ccSessionId: string | null;
|
|
187
|
-
orgId: string;
|
|
188
|
-
gatewayHost: string;
|
|
189
|
-
}): ContextProfile | null;
|
package/dist/lib/agent-ledger.js
CHANGED
|
@@ -16,7 +16,6 @@
|
|
|
16
16
|
*/
|
|
17
17
|
import { appendFileSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
18
18
|
import { join } from "node:path";
|
|
19
|
-
import { CONTEXT_PROFILES, } from "./agent.js";
|
|
20
19
|
/** The ledger's file name under MNEMOM_DIR. */
|
|
21
20
|
export const LEDGER_FILE = "sessions.jsonl";
|
|
22
21
|
/** Days a launch stays in the ledger when `sessions.retention_days` is unset. */
|
|
@@ -198,25 +197,3 @@ export function findResumeCandidate(records, want) {
|
|
|
198
197
|
}
|
|
199
198
|
return latest ? { record: latest, source: "session_name", byCcSession: false } : null;
|
|
200
199
|
}
|
|
201
|
-
/**
|
|
202
|
-
* The context profile the Claude Code session `ccSessionId` ran under — the newest row with the
|
|
203
|
-
* same org and gateway host that pinned that session — so a launch that resumes it
|
|
204
|
-
* (`--resume <id>`) keeps that session's own profile. `null` when no row matches, the row chose
|
|
205
|
-
* no profile (the cell default), it is an older row's `balanced` (see `context_profile_v`), or its
|
|
206
|
-
* value is not a known profile (the file is hand-editable).
|
|
207
|
-
*/
|
|
208
|
-
export function resumedContextProfile(records, want) {
|
|
209
|
-
const id = want.ccSessionId?.trim();
|
|
210
|
-
if (!id || !want.orgId)
|
|
211
|
-
return null;
|
|
212
|
-
for (let i = records.length - 1; i >= 0; i--) {
|
|
213
|
-
const r = records[i];
|
|
214
|
-
if (r.cc_session_id !== id || r.org_id !== want.orgId || r.gateway_host !== want.gatewayHost)
|
|
215
|
-
continue;
|
|
216
|
-
const p = r.context_profile;
|
|
217
|
-
if (p === "balanced" && r.context_profile_v !== 2)
|
|
218
|
-
return null; // an older row: sent nothing
|
|
219
|
-
return p !== undefined && CONTEXT_PROFILES.includes(p) ? p : null;
|
|
220
|
-
}
|
|
221
|
-
return null;
|
|
222
|
-
}
|