@mnemom/mnemom 0.17.7-next.2 → 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 +13 -31
- 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 +50 -160
- 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 +44 -100
- package/dist/lib/agent.js +44 -110
- 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,42 +45,22 @@ 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,
|
|
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;
|
|
53
53
|
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
|
-
const OFF_DROPS_COMPACTION_NOTE = "mnemom agent: compaction intercept off for this session — context profile off leaves compaction to the coding agent, so the standing compaction_intercept / compact_at setting is not sent.";
|
|
57
56
|
/**
|
|
58
|
-
* The
|
|
59
|
-
*
|
|
60
|
-
* rather than as ON.
|
|
61
|
-
*/
|
|
62
|
-
function sayCompaction(profile, compactionIntercept, compactAt, window) {
|
|
63
|
-
const off = profile === "off";
|
|
64
|
-
sayCompactAt(off ? null : compactAt, window);
|
|
65
|
-
if (off && (compactionIntercept || compactAt !== null))
|
|
66
|
-
say(OFF_DROPS_COMPACTION_NOTE);
|
|
67
|
-
else if (compactionIntercept)
|
|
68
|
-
say(COMPACTION_INTERCEPT_NOTE);
|
|
69
|
-
}
|
|
70
|
-
/**
|
|
71
|
-
* The one-line launch note for the context profile: `null` (none chosen — nothing is sent) names
|
|
72
|
-
* the cell default. `off` keeps the wording `--no-context` has always printed.
|
|
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.
|
|
73
59
|
*/
|
|
74
60
|
function sayContextProfile(profile) {
|
|
75
|
-
if (profile ===
|
|
76
|
-
say("mnemom agent: context: cell default");
|
|
77
|
-
}
|
|
78
|
-
else if (profile === "off") {
|
|
61
|
+
if (profile === "off") {
|
|
79
62
|
say("mnemom agent: context folding OFF for this session (--no-context or context = false).");
|
|
80
63
|
}
|
|
81
|
-
else if (profile === "balanced") {
|
|
82
|
-
say("mnemom agent: context profile balanced — the gateway keeps more of the earlier work verbatim and folds later.");
|
|
83
|
-
}
|
|
84
64
|
else if (profile === "aggressive") {
|
|
85
65
|
say("mnemom agent: context profile aggressive — the gateway folds earlier and keeps a tighter working set.");
|
|
86
66
|
}
|
|
@@ -194,12 +174,11 @@ export async function agentCommand(scenarioArg, options = {}) {
|
|
|
194
174
|
const cellNote = isProdGateway(env) ? "us-2/prod" : `custom (${gatewayHost})`;
|
|
195
175
|
const yolo = options.yolo !== false; // default ON
|
|
196
176
|
const contextHint = options.contextHint !== false; // default ON
|
|
197
|
-
// Context profile:
|
|
198
|
-
//
|
|
199
|
-
//
|
|
200
|
-
//
|
|
201
|
-
|
|
202
|
-
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({
|
|
203
182
|
contextProfile: options.contextProfile,
|
|
204
183
|
context: options.context,
|
|
205
184
|
env,
|
|
@@ -223,13 +202,6 @@ export async function agentCommand(scenarioArg, options = {}) {
|
|
|
223
202
|
// cannot arm without the intercept it just turned off. Refuse rather than silently pick
|
|
224
203
|
// a side.
|
|
225
204
|
assertCompactAtCompatible({ compactionIntercept: options.compactionIntercept, compactAt });
|
|
226
|
-
// Context profile off sends the intercept off: an explicit --compaction-intercept or
|
|
227
|
-
// --compact-at <N> in the same launch contradicts it. A resume re-checks below.
|
|
228
|
-
assertOffProfileCompatible({
|
|
229
|
-
profile: contextProfile,
|
|
230
|
-
compactionIntercept: options.compactionIntercept,
|
|
231
|
-
compactAt: options.compactAt,
|
|
232
|
-
});
|
|
233
205
|
const experimental = options.experimental ?? [];
|
|
234
206
|
const sessionConfig = buildSessionConfig({
|
|
235
207
|
profile: contextProfile,
|
|
@@ -237,7 +209,7 @@ export async function agentCommand(scenarioArg, options = {}) {
|
|
|
237
209
|
compactionIntercept,
|
|
238
210
|
compactAt,
|
|
239
211
|
});
|
|
240
|
-
|
|
212
|
+
const sessionConfigB64 = buildSessionConfigB64({
|
|
241
213
|
profile: contextProfile,
|
|
242
214
|
contextHandoff,
|
|
243
215
|
compactionIntercept,
|
|
@@ -301,10 +273,12 @@ export async function agentCommand(scenarioArg, options = {}) {
|
|
|
301
273
|
say("mnemom agent: the plan shows Remote Control because this machine can run it; the real launch uses it only if your account is entitled (internal Mnemom accounts), else terminal-only.");
|
|
302
274
|
}
|
|
303
275
|
sayContextProfile(contextProfile);
|
|
304
|
-
|
|
276
|
+
sayCompactAt(compactAt, launchWindow(windowModelFor(options, cli)));
|
|
305
277
|
if (contextHandoff) {
|
|
306
278
|
say("mnemom agent: context handoff ON (experimental) — the `handoff` MCP tool would be registered.");
|
|
307
279
|
}
|
|
280
|
+
if (compactionIntercept)
|
|
281
|
+
say(COMPACTION_INTERCEPT_NOTE);
|
|
308
282
|
return;
|
|
309
283
|
}
|
|
310
284
|
// Real launch: make sure we actually have a CLI to run. If we couldn't find it,
|
|
@@ -328,9 +302,8 @@ export async function agentCommand(scenarioArg, options = {}) {
|
|
|
328
302
|
// lives in (default: the user's personal org). No µ ⇒ blocked with a
|
|
329
303
|
// portal-style top-up message. Skipped only on --dry-run (handled above).
|
|
330
304
|
// MNEMOM_AGENT_PREFLIGHTED=1 (orchestrators such as the ADW fleet that already
|
|
331
|
-
// ran their own session + balance preflight) skips those round trips; the
|
|
332
|
-
//
|
|
333
|
-
// resolvePreflightedAccount.
|
|
305
|
+
// ran their own session + balance preflight) skips those round trips; the
|
|
306
|
+
// entitlement is still fetched — see resolvePreflightedAccount.
|
|
334
307
|
let account;
|
|
335
308
|
try {
|
|
336
309
|
account = launchPreflighted(env)
|
|
@@ -370,19 +343,14 @@ export async function agentCommand(scenarioArg, options = {}) {
|
|
|
370
343
|
rcCapable: rc.ok,
|
|
371
344
|
say,
|
|
372
345
|
});
|
|
373
|
-
// 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;
|
|
374
350
|
if (auth === "subscription") {
|
|
375
351
|
shape = subscriptionShape(shape, shapeExplicit, rcAllowed, say);
|
|
352
|
+
mnemomKey = await resolveSubscriptionMnemomKey(defaultMnemomKeyDeps());
|
|
376
353
|
}
|
|
377
|
-
// Every launch — api-key, console and subscription, every shape — sends a Mnemom API key
|
|
378
|
-
// (x-mnemom-api-key): the gateway refuses `mnemom agent` traffic without a verified one.
|
|
379
|
-
// Resolved after sign-in so minting and the one-time liveness check can use the session: the
|
|
380
|
-
// key stored for the active org, checked once (a revoked/rotated one is re-minted), else a
|
|
381
|
-
// fresh key minted for the launch's org (account.orgId: the active org, else the personal
|
|
382
|
-
// org — the org the agent is claimed into, which the gateway's org check requires of the key)
|
|
383
|
-
// and stored. No key ⇒ this throws and the launch does not start (a session the gateway
|
|
384
|
-
// would refuse is worse than an actionable error).
|
|
385
|
-
const mnemomKey = await resolveLaunchMnemomKey(defaultMnemomKeyDeps(CODE_STORE, account.orgId));
|
|
386
354
|
// Now that a session exists, a defaulted slug uses the real login handle from
|
|
387
355
|
// /v1/auth/me (the stored token has no email, so the pre-sign-in value was the
|
|
388
356
|
// "agent-user" fallback). Only when the caller didn't name an agent. Under an
|
|
@@ -413,7 +381,6 @@ export async function agentCommand(scenarioArg, options = {}) {
|
|
|
413
381
|
const ensured = await ensureGovernedAgent({
|
|
414
382
|
slug: agent,
|
|
415
383
|
anthropicKey,
|
|
416
|
-
mnemomKey,
|
|
417
384
|
setup: options.setup,
|
|
418
385
|
gatewayHost,
|
|
419
386
|
});
|
|
@@ -457,38 +424,6 @@ export async function agentCommand(scenarioArg, options = {}) {
|
|
|
457
424
|
passthrough: [...(options.passthrough ?? []), "--resume", continuation.resumeCcSessionId],
|
|
458
425
|
};
|
|
459
426
|
}
|
|
460
|
-
// A launch that resumes a Claude Code session (the continuation above, or the caller's own
|
|
461
|
-
// `-- --resume <id>`) keeps the profile that session ran under, unless this launch chose one
|
|
462
|
-
// with a flag. Read from the ledger only — nothing here writes a default anywhere.
|
|
463
|
-
const resumeId = shape === "server" ? null : passthroughResumeId(options.passthrough ?? []);
|
|
464
|
-
if (resumeId && options.contextProfile === undefined && options.context !== false) {
|
|
465
|
-
let resumed;
|
|
466
|
-
try {
|
|
467
|
-
resumed = resumedContextProfile(readSessionRecords({ dir: MNEMOM_DIR }), {
|
|
468
|
-
ccSessionId: resumeId,
|
|
469
|
-
orgId: account.orgId,
|
|
470
|
-
gatewayHost,
|
|
471
|
-
});
|
|
472
|
-
}
|
|
473
|
-
catch {
|
|
474
|
-
resumed = null; // best-effort: an unreadable ledger keeps the launch's own resolution
|
|
475
|
-
}
|
|
476
|
-
if (resumed !== null && resumed !== contextProfile) {
|
|
477
|
-
contextProfile = resolveContextProfile({ context: options.context, resumed, env });
|
|
478
|
-
assertOffProfileCompatible({
|
|
479
|
-
profile: contextProfile,
|
|
480
|
-
compactionIntercept: options.compactionIntercept,
|
|
481
|
-
compactAt: options.compactAt,
|
|
482
|
-
});
|
|
483
|
-
sessionConfigB64 = buildSessionConfigB64({
|
|
484
|
-
profile: contextProfile,
|
|
485
|
-
contextHandoff,
|
|
486
|
-
compactionIntercept,
|
|
487
|
-
compactAt,
|
|
488
|
-
experimental,
|
|
489
|
-
});
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
427
|
// ── local session ledger (~/.mnemom/sessions.jsonl) ── one line per real
|
|
493
428
|
// launch so `mnemom agent sessions` / `show` can find this conversation later.
|
|
494
429
|
// Written after provisioning so a freshly claimed agent's id resolves. Holds
|
|
@@ -520,14 +455,16 @@ export async function agentCommand(scenarioArg, options = {}) {
|
|
|
520
455
|
goalMode,
|
|
521
456
|
ccSessionId: cc.id,
|
|
522
457
|
contextProfile,
|
|
523
|
-
compactAt
|
|
458
|
+
compactAt,
|
|
524
459
|
});
|
|
525
460
|
logLaunch({ shape, door, agent, conversationId, cellNote, envelope, goalMode, cli });
|
|
526
461
|
if (expNotice)
|
|
527
462
|
say(expNotice);
|
|
528
463
|
say(goalAlignmentLine(goalAlignmentMode(envelope, goalMode)));
|
|
529
464
|
sayContextProfile(contextProfile);
|
|
530
|
-
|
|
465
|
+
sayCompactAt(compactAt, launchWindow(win));
|
|
466
|
+
if (compactionIntercept)
|
|
467
|
+
say(COMPACTION_INTERCEPT_NOTE);
|
|
531
468
|
// ── handoff MCP tool (MNE-7564) ── OFF by default: absent/false ⇒ handoffMcpConfigPath stays
|
|
532
469
|
// undefined and buildCliArgs/spawnRcCli are byte-identical to a build without this flag. ON:
|
|
533
470
|
// the config is written to a 0600 temp file and referenced by PATH only — never inlined into
|
|
@@ -543,22 +480,15 @@ export async function agentCommand(scenarioArg, options = {}) {
|
|
|
543
480
|
// back out at request time. Remote Control (both shapes) strips every ANTHROPIC_* auth
|
|
544
481
|
// var from the child's env, so `spawnRcCli` below instead sets a dedicated, non-ANTHROPIC
|
|
545
482
|
// var (HANDOFF_RC_KEY_ENV_NAME) on that same child env — still nothing in this file.
|
|
546
|
-
|
|
547
|
-
// in ANTHROPIC_CUSTOM_HEADERS (terminal), or HANDOFF_RC_MNEMOM_KEY_ENV_NAME (RC).
|
|
548
|
-
const terminal = shape === "terminal";
|
|
549
|
-
const anthropicKeySource = terminal
|
|
483
|
+
const anthropicKeySource = shape === "terminal"
|
|
550
484
|
? { envName: "ANTHROPIC_CUSTOM_HEADERS" }
|
|
551
485
|
: { envName: HANDOFF_RC_KEY_ENV_NAME };
|
|
552
|
-
const mnemomKeySource = terminal
|
|
553
|
-
? { envName: "ANTHROPIC_CUSTOM_HEADERS" }
|
|
554
|
-
: { envName: HANDOFF_RC_MNEMOM_KEY_ENV_NAME };
|
|
555
486
|
handoffMcpConfigPath = writeHandoffMcpConfigFile(buildHandoffMcpConfigJson({
|
|
556
487
|
// The SAME origin the session's own model traffic goes to — not resolveGatewayHost,
|
|
557
488
|
// which would ignore a MNEMOM_AGENT_ANTHROPIC_GATEWAY override (a test-cell launch
|
|
558
489
|
// must never send its handoff calls to prod, or vice versa).
|
|
559
490
|
gatewayHost: resolveHandoffGatewayHost(door),
|
|
560
491
|
anthropicKeySource,
|
|
561
|
-
mnemomKeySource,
|
|
562
492
|
agent,
|
|
563
493
|
conversationId,
|
|
564
494
|
sessionConfigB64,
|
|
@@ -759,9 +689,7 @@ export async function recordSessionLaunch(ctx) {
|
|
|
759
689
|
cli: ctx.cli.name,
|
|
760
690
|
contract: toLedgerContract(ctx.envelope?.contract ?? null),
|
|
761
691
|
goal_mode: goalAlignmentMode(ctx.envelope, ctx.goalMode),
|
|
762
|
-
|
|
763
|
-
? { context_profile: ctx.contextProfile, context_profile_v: 2 }
|
|
764
|
-
: {}),
|
|
692
|
+
context_profile: ctx.contextProfile,
|
|
765
693
|
compact_at: ctx.compactAt,
|
|
766
694
|
cc_session_id: ctx.ccSessionId ?? null,
|
|
767
695
|
...(ctx.options.experimental?.length ? { experimental: [...ctx.options.experimental] } : {}),
|
|
@@ -778,55 +706,25 @@ export async function recordSessionLaunch(ctx) {
|
|
|
778
706
|
// ───────────────────────────────────────────────────────────────────────────
|
|
779
707
|
// Credential resolution (zero 1Password)
|
|
780
708
|
// ───────────────────────────────────────────────────────────────────────────
|
|
781
|
-
/**
|
|
782
|
-
export
|
|
783
|
-
/**
|
|
784
|
-
* Mint the launch's key: an ORG-owned key for `orgId` (the launch's org) with scopes exactly
|
|
785
|
-
* ["gateway"], which any owner/admin/member may mint — so the gateway's agent-traffic org check
|
|
786
|
-
* (agent org = key org) passes for the agent the launcher claims into that org. Never a personal
|
|
787
|
-
* key: it has no org, and the gateway would refuse every governed turn with it. A refusal (403:
|
|
788
|
-
* an API that predates member org keys, or a viewer/auditor role) is thrown as is; the launch
|
|
789
|
-
* stops with guidance (lib/subscription-login.ts mintAndStore).
|
|
790
|
-
*/
|
|
791
|
-
export async function mintLaunchMnemomKey(orgId) {
|
|
792
|
-
const k = await createOrgApiKey(orgId, `mnemom-agent ${hostname()}`, ["gateway"]);
|
|
793
|
-
return { key: k.key, keyId: k.key_id };
|
|
794
|
-
}
|
|
795
|
-
/**
|
|
796
|
-
* The real I/O behind every launch's Mnemom key (lib/subscription-login.ts). `launchOrgId` is
|
|
797
|
-
* the org the launch runs in (ensureSessionAndBalance's `orgId`: the active org, else the
|
|
798
|
-
* caller's personal org); omitted or empty, it is the active org.
|
|
799
|
-
*/
|
|
800
|
-
export function defaultMnemomKeyDeps(storePath = CODE_STORE, launchOrgId) {
|
|
801
|
-
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) {
|
|
802
711
|
return {
|
|
803
712
|
env: process.env,
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
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),
|
|
816
725
|
say,
|
|
817
726
|
};
|
|
818
727
|
}
|
|
819
|
-
/**
|
|
820
|
-
* The Mnemom key already on this machine (MNEMOM_AGENT_MNEMOM_KEY, then ~/.mnemom/launcher.json),
|
|
821
|
-
* never minted: `mnemom agent doctor` reports its source, the goal commands send it.
|
|
822
|
-
*/
|
|
823
|
-
export function localLaunchMnemomKey(storePath = CODE_STORE) {
|
|
824
|
-
return readLocalMnemomKey(defaultMnemomKeyDeps(storePath));
|
|
825
|
-
}
|
|
826
|
-
/** The launch's one-time check of that key (`mnemom agent doctor` reports it). Never throws. */
|
|
827
|
-
export function verifyLocalMnemomKey(local) {
|
|
828
|
-
return defaultMnemomKeyDeps().check(local.key, local.keyId);
|
|
829
|
-
}
|
|
830
728
|
/** The real machine behind lib/console-key.ts: the macOS keychain via `security`, no shell. */
|
|
831
729
|
export function defaultConsoleKeyDeps(env) {
|
|
832
730
|
return {
|
|
@@ -1102,8 +1000,6 @@ function launchTerminal(ctx) {
|
|
|
1102
1000
|
if (ctx.anthropicKey) {
|
|
1103
1001
|
childEnv = { ...process.env };
|
|
1104
1002
|
delete childEnv.ANTHROPIC_API_KEY;
|
|
1105
|
-
// The Mnemom key already rides in the headers; the child has no other use for it.
|
|
1106
|
-
Reflect.deleteProperty(childEnv, MNEMOM_KEY_ENV);
|
|
1107
1003
|
childEnv.ANTHROPIC_BASE_URL = ctx.door;
|
|
1108
1004
|
childEnv.ANTHROPIC_AUTH_TOKEN = "mnemom-gateway";
|
|
1109
1005
|
childEnv.ANTHROPIC_CUSTOM_HEADERS = headers;
|
|
@@ -1288,10 +1184,9 @@ function launchRemoteControl(ctx) {
|
|
|
1288
1184
|
}
|
|
1289
1185
|
};
|
|
1290
1186
|
process.on("exit", onExit);
|
|
1291
|
-
// Secrets on stdin — three lines (anthropic, mnemom, openai).
|
|
1292
|
-
// key
|
|
1293
|
-
//
|
|
1294
|
-
// 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.
|
|
1295
1190
|
// A proxy that exits before reading them turns this write into an EPIPE; that is
|
|
1296
1191
|
// not a failure of its own — the exited-before-READY check below refuses the
|
|
1297
1192
|
// launch. Any other stdin error fails closed like a spawn error.
|
|
@@ -1302,7 +1197,7 @@ function launchRemoteControl(ctx) {
|
|
|
1302
1197
|
onExit();
|
|
1303
1198
|
process.exit(1);
|
|
1304
1199
|
});
|
|
1305
|
-
proxy.stdin?.end(`${ctx.anthropicKey ?? ""}\n${ctx.mnemomKey}\n\n`);
|
|
1200
|
+
proxy.stdin?.end(`${ctx.anthropicKey ?? ""}\n${ctx.anthropicKey ? "" : (ctx.mnemomKey ?? "")}\n\n`);
|
|
1306
1201
|
let stdoutBuf = "";
|
|
1307
1202
|
const start = Date.now();
|
|
1308
1203
|
const tryStart = () => {
|
|
@@ -1370,9 +1265,6 @@ function spawnRcCli(ctx) {
|
|
|
1370
1265
|
"no_proxy",
|
|
1371
1266
|
"HTTP_PROXY",
|
|
1372
1267
|
"http_proxy",
|
|
1373
|
-
// The Mnemom key rides to the gateway via the proxy (stdin) and, with handoff on, in
|
|
1374
|
-
// HANDOFF_RC_MNEMOM_KEY_ENV_NAME below; the CLI itself has no use for the launch's env var.
|
|
1375
|
-
MNEMOM_KEY_ENV,
|
|
1376
1268
|
]);
|
|
1377
1269
|
const childEnv = {};
|
|
1378
1270
|
for (const [k, v] of Object.entries(process.env)) {
|
|
@@ -1400,10 +1292,8 @@ function spawnRcCli(ctx) {
|
|
|
1400
1292
|
// ride through here ungated), so this rides through untouched. Only set when the tool is
|
|
1401
1293
|
// actually registered — never on a plain RC launch.
|
|
1402
1294
|
// (Never on a subscription launch — handoff is turned off there, so there is no key to set.)
|
|
1403
|
-
// The Mnemom key the gateway requires rides beside it in its own non-ANTHROPIC var.
|
|
1404
1295
|
if (ctx.handoffMcpConfigPath && ctx.anthropicKey) {
|
|
1405
1296
|
childEnv[HANDOFF_RC_KEY_ENV_NAME] = ctx.anthropicKey;
|
|
1406
|
-
childEnv[HANDOFF_RC_MNEMOM_KEY_ENV_NAME] = ctx.mnemomKey;
|
|
1407
1297
|
}
|
|
1408
1298
|
let args;
|
|
1409
1299
|
if (ctx.shape === "server") {
|
|
@@ -1676,7 +1566,7 @@ export function printPlan(ctx) {
|
|
|
1676
1566
|
L.push(` agent: ${ctx.agent}`);
|
|
1677
1567
|
L.push(` conversation: ${ctx.conversationId}`);
|
|
1678
1568
|
L.push(` yolo: ${ctx.yolo ? "on (--dangerously-skip-permissions)" : "off"}`);
|
|
1679
|
-
L.push(` context: ${ctx.contextProfile
|
|
1569
|
+
L.push(` context: ${ctx.contextProfile}`);
|
|
1680
1570
|
L.push(` compaction: ${describeCompactAt(ctx.compactAt)}`);
|
|
1681
1571
|
const overlay = Object.entries(ctx.sessionConfig);
|
|
1682
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
|
-
}
|