@mnemom/mnemom 0.17.11 → 0.17.12
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 -2
- package/dist/commands/agent-sessions.js +8 -1
- package/dist/commands/agent-setup.js +2 -2
- package/dist/commands/agent.d.ts +18 -0
- package/dist/commands/agent.js +75 -9
- package/dist/index.js +1 -1
- package/dist/lib/agent.d.ts +10 -0
- package/dist/lib/agent.js +14 -0
- package/dist/lib/update/install-method.js +5 -0
- package/package.json +1 -1
|
@@ -37,7 +37,7 @@ export function goalAlignmentLine(mode) {
|
|
|
37
37
|
}
|
|
38
38
|
return m === "on"
|
|
39
39
|
? "mnemom agent: goal alignment: on (the judge nudges the agent back when it drifts from your goal)"
|
|
40
|
-
:
|
|
40
|
+
: 'mnemom agent: goal alignment: off — add --goal "…" to seal a goal for this session';
|
|
41
41
|
}
|
|
42
42
|
const defaultSay = (line = "") => console.error(line);
|
|
43
43
|
/**
|
|
@@ -62,7 +62,7 @@ export async function resolveSessionSetup(input, ask) {
|
|
|
62
62
|
// ── 1. session name ──
|
|
63
63
|
const fallback = defaultSessionName(input.cwd);
|
|
64
64
|
say();
|
|
65
|
-
say("Session name — labels this session in your traces and
|
|
65
|
+
say("Session name — labels this session in your traces and in `mnemom agent sessions`.");
|
|
66
66
|
say(` (Next time you can pass it directly: mnemom agent <name>.)`);
|
|
67
67
|
const typed = await ask.askInput(`Session name [${fallback}]:`);
|
|
68
68
|
const scenario = sanitizeAgentName(typed.trim()) || fallback;
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
*/
|
|
16
16
|
import { readSessionRecords } from "../lib/agent-ledger.js";
|
|
17
17
|
import { getGatewayUrl, MNEMOM_DIR } from "../lib/config.js";
|
|
18
|
-
import { sameOrigin } from "../lib/agent.js";
|
|
18
|
+
import { resumeCommand, sameOrigin } from "../lib/agent.js";
|
|
19
19
|
import { getAgentSession, MnemomApiError } from "../lib/api.js";
|
|
20
20
|
import { fmt, formatRelativeAge } from "../lib/format.js";
|
|
21
21
|
import { goalControlFetch } from "../lib/goal-api.js";
|
|
@@ -186,6 +186,13 @@ export function buildShowView(local, remote, reason) {
|
|
|
186
186
|
L.push(fmt.label("experimental: ", local.experimental.join(", ")));
|
|
187
187
|
}
|
|
188
188
|
L.push(fmt.label("conversation: ", local.conversation_id));
|
|
189
|
+
if (local.cc_session_id && local.launch_shape !== "server") {
|
|
190
|
+
L.push(fmt.label("resume: ", resumeCommand({
|
|
191
|
+
scenario: local.session,
|
|
192
|
+
agent: local.agent_slug,
|
|
193
|
+
ccSessionId: local.cc_session_id,
|
|
194
|
+
})));
|
|
195
|
+
}
|
|
189
196
|
const goalMode = resolveGoalMode(local, remote);
|
|
190
197
|
L.push(fmt.label("goal alignment:", goalBadge(goalMode)));
|
|
191
198
|
if (local.contract) {
|
|
@@ -598,7 +598,7 @@ export async function ensureMnemomSession(opts = {}) {
|
|
|
598
598
|
say(fmt.section("Sign in to Mnemom"));
|
|
599
599
|
say(fmt.dim(" mnemom agent runs your coding session governed and under your Mnemom account,"));
|
|
600
600
|
say(fmt.dim(" so it shows up in your traces and draws on your org's µ balance."));
|
|
601
|
-
say(fmt.dim(` New to Mnemom? You can create
|
|
601
|
+
say(fmt.dim(` New to Mnemom? You can create your account in the same flow (or at ${getWebsiteUrl()}/signup).`));
|
|
602
602
|
if (!(await askYesNo("Sign in or create your account now?", true)))
|
|
603
603
|
return false;
|
|
604
604
|
const device = headlessEnv();
|
|
@@ -688,7 +688,7 @@ export async function ensureSessionAndBalance(opts) {
|
|
|
688
688
|
if (!haveSession) {
|
|
689
689
|
throw new Error("mnemom agent needs a Mnemom account — it runs your coding session governed and on your org's µ balance.\n" +
|
|
690
690
|
" Sign in: mnemom login (or set MNEMOM_API_KEY)\n" +
|
|
691
|
-
` New here? create
|
|
691
|
+
` New here? create your account, then top up µ: ${getWebsiteUrl()}/settings/billing`);
|
|
692
692
|
}
|
|
693
693
|
const active = getActiveOrg();
|
|
694
694
|
const orgId = active?.org_id ?? (await getMyPersonalOrg()).org_id;
|
package/dist/commands/agent.d.ts
CHANGED
|
@@ -143,6 +143,24 @@ export declare function passthroughResumeId(passthrough: readonly string[]): str
|
|
|
143
143
|
* The ledger read is best-effort: a failure means a new id.
|
|
144
144
|
*/
|
|
145
145
|
export declare function resolveLaunchConversationId(input: LaunchContinuationInput): Promise<string>;
|
|
146
|
+
/** Whether the passthrough continues the directory's newest session (`--continue` / `-c`, unforked). */
|
|
147
|
+
export declare function passthroughContinues(passthrough: readonly string[]): boolean;
|
|
148
|
+
/**
|
|
149
|
+
* The Claude Code session `--continue` picks in `cwd`: the newest `<uuid>.jsonl` in that
|
|
150
|
+
* directory's project folder (Claude Code names it after the path, each non-alphanumeric
|
|
151
|
+
* character a `-`). Best-effort: null when there is none or it can't be read.
|
|
152
|
+
*/
|
|
153
|
+
export declare function newestCcSessionIn(cwd: string, env: NodeJS.ProcessEnv): string | null;
|
|
154
|
+
/**
|
|
155
|
+
* The line `mnemom agent` prints after Claude Code exits: how to resume the session THROUGH
|
|
156
|
+
* Mnemom (Claude Code's own `claude --resume <id>` leaves the gateway). `--agent` is repeated
|
|
157
|
+
* only when this launch named one; an unknown session id falls back to `--continue`.
|
|
158
|
+
*/
|
|
159
|
+
export declare function resumeHintLine(ctx: {
|
|
160
|
+
scenario: string;
|
|
161
|
+
agent?: string;
|
|
162
|
+
ccSessionId: string | null;
|
|
163
|
+
}): string;
|
|
146
164
|
/**
|
|
147
165
|
* Whether Claude Code still has the transcript of session `id`
|
|
148
166
|
* (`${CLAUDE_CONFIG_DIR:-~/.claude}/projects/<any project>/<id>.jsonl`; `--resume` finds it in any
|
package/dist/commands/agent.js
CHANGED
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
* an argv value.
|
|
27
27
|
*/
|
|
28
28
|
import { spawn, spawnSync } from "node:child_process";
|
|
29
|
-
import { existsSync, mkdtempSync, readdirSync, readFileSync, rmSync, writeFileSync, renameSync, } from "node:fs";
|
|
29
|
+
import { existsSync, mkdtempSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync, renameSync, } from "node:fs";
|
|
30
30
|
import { randomUUID } from "node:crypto";
|
|
31
31
|
import { tmpdir, homedir, hostname, userInfo } from "node:os";
|
|
32
32
|
import { basename, join, dirname } from "node:path";
|
|
@@ -46,7 +46,7 @@ import { authConfigured, chooseAuth, detectClaudeLogin, readClaudeLoginState, }
|
|
|
46
46
|
import { requireConsoleKey, readConsoleKey } from "../lib/console-key.js";
|
|
47
47
|
import { goalAlignmentLine, resolveSessionSetup } from "./agent-session.js";
|
|
48
48
|
import { defaultAgentSlug, ensureClaimedServedAgent, ensureSessionAndBalance, isQuietPreambleExit, resolveDefaultSlug, resolvePreflightedAccount, } from "./agent-setup.js";
|
|
49
|
-
import { assembleContract, buildGoalModeHeader, buildCustomHeaders, buildHandoffMcpConfigJson, buildSessionConfig, buildSessionConfigB64, assertCompactAtCompatible, assertOffProfileCompatible, 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 { assembleContract, buildGoalModeHeader, buildCustomHeaders, buildHandoffMcpConfigJson, buildSessionConfig, buildSessionConfigB64, assertCompactAtCompatible, assertOffProfileCompatible, 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, resumeCommand, sessionDisplayName, SESSION_NAME_ENV, SESSION_CONFIG_HEADER, } from "../lib/agent.js";
|
|
50
50
|
import { LAUNCHER_STORE_PATH, mergeStore } from "../lib/launcher-store.js";
|
|
51
51
|
import { slackChannelArgs } from "../lib/slack-launch.js";
|
|
52
52
|
/** The CLI's own provider-key store (zero 1Password): ~/.mnemom/launcher.json, 0600. */
|
|
@@ -441,10 +441,16 @@ export async function agentCommand(scenarioArg, options = {}) {
|
|
|
441
441
|
passthrough: [...(options.passthrough ?? []), "--resume", continuation.resumeCcSessionId],
|
|
442
442
|
};
|
|
443
443
|
}
|
|
444
|
-
// A launch that resumes a Claude Code session (the continuation above,
|
|
445
|
-
// `-- --resume <id
|
|
446
|
-
//
|
|
447
|
-
|
|
444
|
+
// A launch that resumes a Claude Code session (the continuation above, the caller's own
|
|
445
|
+
// `-- --resume <id>`, or `-- --continue`, which picks this directory's newest session) keeps
|
|
446
|
+
// the profile that session ran under, unless this launch chose one with a flag. Read from the
|
|
447
|
+
// ledger only — nothing here writes a default anywhere.
|
|
448
|
+
const continuedCcSessionId = shape !== "server" && passthroughContinues(options.passthrough ?? [])
|
|
449
|
+
? newestCcSessionIn(process.cwd(), process.env)
|
|
450
|
+
: null;
|
|
451
|
+
const resumeId = shape === "server"
|
|
452
|
+
? null
|
|
453
|
+
: (passthroughResumeId(options.passthrough ?? []) ?? continuedCcSessionId);
|
|
448
454
|
if (resumeId && options.contextProfile === undefined && options.context !== false) {
|
|
449
455
|
let resumed;
|
|
450
456
|
try {
|
|
@@ -502,7 +508,8 @@ export async function agentCommand(scenarioArg, options = {}) {
|
|
|
502
508
|
cli,
|
|
503
509
|
envelope,
|
|
504
510
|
goalMode,
|
|
505
|
-
|
|
511
|
+
// A `--continue` records the session it continues, so a later resume finds its profile.
|
|
512
|
+
ccSessionId: cc.id ?? continuedCcSessionId,
|
|
506
513
|
contextProfile,
|
|
507
514
|
compactAt: contextProfile === "off" ? null : compactAt,
|
|
508
515
|
});
|
|
@@ -565,6 +572,12 @@ export async function agentCommand(scenarioArg, options = {}) {
|
|
|
565
572
|
contextHint,
|
|
566
573
|
ccSessionId,
|
|
567
574
|
handoffMcpConfigPath,
|
|
575
|
+
resumeHint: (id) => resumeHintLine({
|
|
576
|
+
scenario,
|
|
577
|
+
agent: options.agent?.trim() ? agent : undefined,
|
|
578
|
+
ccSessionId: id,
|
|
579
|
+
}),
|
|
580
|
+
knownCcSessionId: cc.id ?? continuedCcSessionId,
|
|
568
581
|
});
|
|
569
582
|
return; // launchTerminal exits the process
|
|
570
583
|
}
|
|
@@ -627,6 +640,52 @@ function choosesCcSession(passthrough) {
|
|
|
627
640
|
return !pickCcSessionId([...passthrough], () => "").inject;
|
|
628
641
|
}
|
|
629
642
|
const CC_SESSION_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
643
|
+
/** `${CLAUDE_CONFIG_DIR:-~/.claude}/projects` — where Claude Code keeps session transcripts. */
|
|
644
|
+
function ccProjectsDir(env) {
|
|
645
|
+
return join((env.CLAUDE_CONFIG_DIR ?? "").trim() || join(homedir(), ".claude"), "projects");
|
|
646
|
+
}
|
|
647
|
+
/** Whether the passthrough continues the directory's newest session (`--continue` / `-c`, unforked). */
|
|
648
|
+
export function passthroughContinues(passthrough) {
|
|
649
|
+
return ((passthrough.includes("--continue") || passthrough.includes("-c")) &&
|
|
650
|
+
!passthrough.includes("--fork-session") &&
|
|
651
|
+
passthroughResumeId(passthrough) === null);
|
|
652
|
+
}
|
|
653
|
+
/**
|
|
654
|
+
* The Claude Code session `--continue` picks in `cwd`: the newest `<uuid>.jsonl` in that
|
|
655
|
+
* directory's project folder (Claude Code names it after the path, each non-alphanumeric
|
|
656
|
+
* character a `-`). Best-effort: null when there is none or it can't be read.
|
|
657
|
+
*/
|
|
658
|
+
export function newestCcSessionIn(cwd, env) {
|
|
659
|
+
const dir = join(ccProjectsDir(env), cwd.replace(/[^a-zA-Z0-9]/g, "-"));
|
|
660
|
+
try {
|
|
661
|
+
let best = null;
|
|
662
|
+
for (const f of readdirSync(dir)) {
|
|
663
|
+
const id = f.endsWith(".jsonl") ? f.slice(0, -".jsonl".length) : "";
|
|
664
|
+
if (!CC_SESSION_ID_RE.test(id))
|
|
665
|
+
continue;
|
|
666
|
+
const mtime = statSync(join(dir, f)).mtimeMs;
|
|
667
|
+
if (!best || mtime > best.mtime)
|
|
668
|
+
best = { id, mtime };
|
|
669
|
+
}
|
|
670
|
+
return best?.id ?? null;
|
|
671
|
+
}
|
|
672
|
+
catch {
|
|
673
|
+
return null;
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
/**
|
|
677
|
+
* The line `mnemom agent` prints after Claude Code exits: how to resume the session THROUGH
|
|
678
|
+
* Mnemom (Claude Code's own `claude --resume <id>` leaves the gateway). `--agent` is repeated
|
|
679
|
+
* only when this launch named one; an unknown session id falls back to `--continue`.
|
|
680
|
+
*/
|
|
681
|
+
export function resumeHintLine(ctx) {
|
|
682
|
+
return (`Resume with: ${resumeCommand(ctx)}` +
|
|
683
|
+
" (keeps it governed; `claude --resume` runs outside Mnemom)");
|
|
684
|
+
}
|
|
685
|
+
/** An interactive Claude Code session — not `-p` / `--print`, which has nothing to resume into. */
|
|
686
|
+
function wantsResumeHint(cli, args) {
|
|
687
|
+
return cli.name === "claude" && !args.includes("-p") && !args.includes("--print");
|
|
688
|
+
}
|
|
630
689
|
/**
|
|
631
690
|
* Whether Claude Code still has the transcript of session `id`
|
|
632
691
|
* (`${CLAUDE_CONFIG_DIR:-~/.claude}/projects/<any project>/<id>.jsonl`; `--resume` finds it in any
|
|
@@ -636,7 +695,7 @@ const CC_SESSION_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a
|
|
|
636
695
|
export function ccSessionTranscriptExists(id, env) {
|
|
637
696
|
if (!CC_SESSION_ID_RE.test(id))
|
|
638
697
|
return false;
|
|
639
|
-
const projects =
|
|
698
|
+
const projects = ccProjectsDir(env);
|
|
640
699
|
try {
|
|
641
700
|
return readdirSync(projects).some((d) => existsSync(join(projects, d, `${id}.jsonl`)));
|
|
642
701
|
}
|
|
@@ -1086,8 +1145,15 @@ function launchTerminal(ctx) {
|
|
|
1086
1145
|
child.on("exit", (code, signal) => {
|
|
1087
1146
|
if (signal)
|
|
1088
1147
|
process.kill(process.pid, signal);
|
|
1089
|
-
else
|
|
1148
|
+
else {
|
|
1149
|
+
// Claude Code's own last line is "Resume this session with: claude --resume <id>",
|
|
1150
|
+
// which reopens it OUTSIDE Mnemom (ungoverned, unbilled). Ours goes after it, last.
|
|
1151
|
+
if (code === 0 && ctx.resumeHint && wantsResumeHint(ctx.cli, args)) {
|
|
1152
|
+
const id = ctx.knownCcSessionId ?? newestCcSessionIn(process.cwd(), process.env);
|
|
1153
|
+
say(ctx.resumeHint(id));
|
|
1154
|
+
}
|
|
1090
1155
|
process.exit(code ?? 0);
|
|
1156
|
+
}
|
|
1091
1157
|
});
|
|
1092
1158
|
};
|
|
1093
1159
|
// Implicit goal mode: put the local base-URL proxy in front of the door so each main request
|
package/dist/index.js
CHANGED
|
@@ -140,7 +140,7 @@ function printAgentHelp(verb) {
|
|
|
140
140
|
}
|
|
141
141
|
const agentCmd = program
|
|
142
142
|
.command("agent [session] [passthrough...]")
|
|
143
|
-
.description("Launch your governed agent (Claude Code in v1) through the Mnemom gateway — governed identity + optional sealed goal contract. Plain `mnemom agent` asks for a session name
|
|
143
|
+
.description("Launch your governed agent (Claude Code in v1) through the Mnemom gateway — governed identity + optional sealed goal contract. Plain `mnemom agent` asks for a session name. " +
|
|
144
144
|
"Sub-verbs: `setup` provisions your governed agent; `doctor` preflights prerequisites; `config …` manages persistent settings (~/.mnemom/launcher.toml); `invite <token>` redeems a Mnemom Agent invitation; `list` / `claim` / `move` manage the agents in your orgs; `sessions` lists your past launches and `show [ref]` merges one with its live goal state; `help [verb]` prints help and never launches.")
|
|
145
145
|
.option("--goal <statement>", "Contract goal statement (required if any contract flag is given)")
|
|
146
146
|
.option("--requirement <text>", "Contract requirement (repeatable)", collectRepeatable, [])
|
package/dist/lib/agent.d.ts
CHANGED
|
@@ -228,6 +228,16 @@ export declare function resolveWindowModel(input: {
|
|
|
228
228
|
env: Record<string, string | undefined>;
|
|
229
229
|
settings: () => CcSettingsView[];
|
|
230
230
|
}): WindowModel;
|
|
231
|
+
/**
|
|
232
|
+
* The command that resumes a Claude Code session THROUGH Mnemom (governed, billed) — never
|
|
233
|
+
* Claude Code's own `claude --resume <id>`, which leaves the gateway. An unknown session id
|
|
234
|
+
* falls back to `--continue` (Claude Code's newest session in this directory).
|
|
235
|
+
*/
|
|
236
|
+
export declare function resumeCommand(ctx: {
|
|
237
|
+
scenario: string;
|
|
238
|
+
agent?: string;
|
|
239
|
+
ccSessionId: string | null;
|
|
240
|
+
}): string;
|
|
231
241
|
/** The session display name: "<agent>-<scenario>" (sanitised), else the agent slug. */
|
|
232
242
|
export declare function sessionDisplayName(agent: string, scenario: string): string;
|
|
233
243
|
/** A caller's own `--name <x>` in the passthrough args, if any (last one wins). */
|
package/dist/lib/agent.js
CHANGED
|
@@ -301,6 +301,20 @@ export function resolveWindowModel(input) {
|
|
|
301
301
|
return { model: explicit, reason: "unsupported" };
|
|
302
302
|
return { model: `${base}${ONE_M_SUFFIX}`, reason: "one-m", from: base };
|
|
303
303
|
}
|
|
304
|
+
/** Single-quote `s` for a POSIX shell unless it is plainly safe as-is. */
|
|
305
|
+
function shellArg(s) {
|
|
306
|
+
return /^[A-Za-z0-9._@%+=:,/-]+$/.test(s) ? s : `'${s.replace(/'/g, "'\\''")}'`;
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* The command that resumes a Claude Code session THROUGH Mnemom (governed, billed) — never
|
|
310
|
+
* Claude Code's own `claude --resume <id>`, which leaves the gateway. An unknown session id
|
|
311
|
+
* falls back to `--continue` (Claude Code's newest session in this directory).
|
|
312
|
+
*/
|
|
313
|
+
export function resumeCommand(ctx) {
|
|
314
|
+
const agentFlag = ctx.agent ? ` --agent ${shellArg(ctx.agent)}` : "";
|
|
315
|
+
const resume = ctx.ccSessionId ? `--resume ${shellArg(ctx.ccSessionId)}` : "--continue";
|
|
316
|
+
return `mnemom agent ${shellArg(ctx.scenario)}${agentFlag} -- ${resume}`;
|
|
317
|
+
}
|
|
304
318
|
/** The session display name: "<agent>-<scenario>" (sanitised), else the agent slug. */
|
|
305
319
|
export function sessionDisplayName(agent, scenario) {
|
|
306
320
|
return sanitizeAgentName(`${agent}-${scenario}`) || agent;
|
|
@@ -27,6 +27,11 @@ export function classifyInstall(entryRealpath, npmGlobalRoot) {
|
|
|
27
27
|
if (npmGlobalRoot && p.startsWith(`${norm(npmGlobalRoot).replace(/\/$/, "")}/@mnemom/mnemom/`)) {
|
|
28
28
|
return "npm-global";
|
|
29
29
|
}
|
|
30
|
+
// `<prefix>/lib/node_modules/…` is npm's GLOBAL layout under a prefix `npm root -g` doesn't
|
|
31
|
+
// report (a custom --prefix, a switched HOME/.npmrc): not a project dependency, and not ours
|
|
32
|
+
// to self-update either — "unknown" gets the plain `npm install -g` hint.
|
|
33
|
+
if (p.includes("/lib/node_modules/@mnemom/mnemom/"))
|
|
34
|
+
return "unknown";
|
|
30
35
|
if (p.includes("/node_modules/@mnemom/mnemom/"))
|
|
31
36
|
return "project";
|
|
32
37
|
return "unknown";
|