@mnemom/mnemom 0.17.11-next.1 → 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-doctor.d.ts +2 -0
- package/dist/commands/agent-doctor.js +32 -23
- package/dist/commands/agent-invite.d.ts +7 -0
- package/dist/commands/agent-invite.js +20 -1
- 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 +23 -0
- package/dist/commands/agent.js +106 -26
- package/dist/commands/experimental.d.ts +2 -0
- package/dist/commands/experimental.js +12 -0
- package/dist/index.js +6 -3
- package/dist/lib/agent.d.ts +10 -0
- package/dist/lib/agent.js +14 -0
- package/dist/lib/experimental.d.ts +5 -0
- package/dist/lib/experimental.js +4 -2
- package/dist/lib/launcher-config.js +1 -1
- package/dist/lib/update/install-method.js +5 -0
- package/dist/lib/version-gate.js +1 -2
- package/package.json +1 -1
|
@@ -24,6 +24,8 @@ export interface CodeAccessProbe {
|
|
|
24
24
|
mark: Mark;
|
|
25
25
|
text: string;
|
|
26
26
|
hint?: string;
|
|
27
|
+
/** May this account use Remote Control? Only a determined `remote_control: true` says yes. */
|
|
28
|
+
remoteControl: boolean;
|
|
27
29
|
}
|
|
28
30
|
/**
|
|
29
31
|
* The invited-cohort gate, as the doctor reports it. Signed out → info (we can't
|
|
@@ -23,7 +23,7 @@ import { LAUNCHER_CONFIG_PATH } from "../lib/launcher-config.js";
|
|
|
23
23
|
import { getAgentByName, getCardSources, getCodeEntitlement, MnemomApiError } from "../lib/api.js";
|
|
24
24
|
import { isLoggedIn } from "../lib/auth.js";
|
|
25
25
|
import { getActiveOrg } from "../lib/cli-config.js";
|
|
26
|
-
import { isCodeNotEnabled, sourceLabel } from "./agent-invite.js";
|
|
26
|
+
import { isCodeNotEnabled, REQUEST_ACCESS_URL, sanitizeTerminalText, sourceLabel, withRequestAccess, } from "./agent-invite.js";
|
|
27
27
|
import { probeUpdateStatus } from "../lib/update/doctor.js";
|
|
28
28
|
import { describeModes, GOVERNANCE_OPT_IN_HINT, resolveDefaultSlug } from "./agent-setup.js";
|
|
29
29
|
const MARK = { ok: "[ ok ]", warn: "[warn]", miss: "[MISS]", info: "[info]" };
|
|
@@ -96,26 +96,31 @@ export async function agentDoctorCommand(options = {}) {
|
|
|
96
96
|
else {
|
|
97
97
|
add("warn", `gateway: ${host} [${cell}] unreachable (${health.detail})`, "check the host (MNEMOM_AGENT_GATEWAY / `mnemom agent config set gateway <url>`) and your network");
|
|
98
98
|
}
|
|
99
|
-
// ──
|
|
100
|
-
|
|
99
|
+
// ── Mnemom Agent access (invited cohort gate) ── probed here, before the Remote Control
|
|
100
|
+
// lines, because Remote Control is internal-only: an account the api does not entitle to it
|
|
101
|
+
// (every customer) never sees those lines at all.
|
|
102
|
+
const gate = await probeCodeAccess();
|
|
103
|
+
// ── Remote Control capability (entitled accounts only) ──
|
|
104
|
+
if (cli && gate.remoteControl) {
|
|
101
105
|
const rc = checkRcCapability(cli);
|
|
102
106
|
if (rc.ok)
|
|
103
107
|
add("ok", "Remote Control: available (node + openssl + --remote-control)");
|
|
104
108
|
else
|
|
105
109
|
add("warn", `Remote Control: unavailable — ${rc.missing.join("; ")}`, "launches fall back to terminal-only until these are in place");
|
|
106
|
-
// ── claude.ai login (informational — RC needs it) ──
|
|
107
|
-
if (cli.name === "claude") {
|
|
108
|
-
const login = claudeLoggedIn(cli.bin);
|
|
109
|
-
if (login === true)
|
|
110
|
-
add("ok", "claude.ai login: logged in");
|
|
111
|
-
else if (login === false)
|
|
112
|
-
add("info", "claude.ai login: not logged in", "Remote Control needs a claude.ai login; the terminal-only shape does not");
|
|
113
|
-
else
|
|
114
|
-
add("info", "claude.ai login: could not determine");
|
|
115
|
-
}
|
|
116
110
|
}
|
|
117
|
-
// ──
|
|
118
|
-
|
|
111
|
+
// ── claude.ai login (informational) ── Remote Control needs it, and so does auth =
|
|
112
|
+
// subscription; a terminal launch on an Anthropic key does not, so nobody else sees it.
|
|
113
|
+
if (cli && cli.name === "claude" && (gate.remoteControl || auth === "subscription")) {
|
|
114
|
+
const login = claudeLoggedIn(cli.bin);
|
|
115
|
+
if (login === true)
|
|
116
|
+
add("ok", "claude.ai login: logged in");
|
|
117
|
+
else if (login === false)
|
|
118
|
+
add("info", "claude.ai login: not logged in", auth === "subscription"
|
|
119
|
+
? "auth = subscription launches Claude Code on your claude.ai login — run `claude` and /login"
|
|
120
|
+
: "Remote Control needs a claude.ai login; the terminal-only shape does not");
|
|
121
|
+
else
|
|
122
|
+
add("info", "claude.ai login: could not determine");
|
|
123
|
+
}
|
|
119
124
|
add(gate.mark, gate.text, gate.hint);
|
|
120
125
|
// ── governance (off by default for new agents) ──
|
|
121
126
|
const gov = await probeGovernance(options.agent);
|
|
@@ -192,7 +197,8 @@ export async function probeCodeAccess() {
|
|
|
192
197
|
return {
|
|
193
198
|
mark: "info",
|
|
194
199
|
text: "Mnemom Agent access: sign in to check",
|
|
195
|
-
hint:
|
|
200
|
+
hint: `run \`mnemom login\`; @mnemom.ai accounts are enabled by default, others need an invitation (mnemom agent --invite <token>) — request one at ${REQUEST_ACCESS_URL}`,
|
|
201
|
+
remoteControl: false,
|
|
196
202
|
};
|
|
197
203
|
}
|
|
198
204
|
const org = getActiveOrg();
|
|
@@ -202,9 +208,7 @@ export async function probeCodeAccess() {
|
|
|
202
208
|
return {
|
|
203
209
|
mark: "ok",
|
|
204
210
|
text: `Mnemom Agent access: enabled for ${orgLabel} (${sourceLabel(ent.source)})`,
|
|
205
|
-
|
|
206
|
-
? undefined
|
|
207
|
-
: "Remote Control (steer from claude.ai / the Claude app) is an internal feature — your sessions run terminal-only",
|
|
211
|
+
remoteControl: ent.remote_control === true,
|
|
208
212
|
};
|
|
209
213
|
}
|
|
210
214
|
catch (err) {
|
|
@@ -213,13 +217,18 @@ export async function probeCodeAccess() {
|
|
|
213
217
|
return {
|
|
214
218
|
mark: "miss",
|
|
215
219
|
text: `Mnemom Agent access: not enabled for ${orgLabel}`,
|
|
216
|
-
hint: typeof hint === "string" && hint
|
|
217
|
-
? hint
|
|
218
|
-
: "redeem an invitation: mnemom agent --invite <token>",
|
|
220
|
+
hint: withRequestAccess(typeof hint === "string" && hint
|
|
221
|
+
? sanitizeTerminalText(hint)
|
|
222
|
+
: "redeem an invitation: mnemom agent --invite <token>"),
|
|
223
|
+
remoteControl: false,
|
|
219
224
|
};
|
|
220
225
|
}
|
|
221
226
|
const detail = err instanceof MnemomApiError || err instanceof Error ? err.message : String(err);
|
|
222
|
-
return {
|
|
227
|
+
return {
|
|
228
|
+
mark: "info",
|
|
229
|
+
text: `Mnemom Agent access: could not determine (${detail})`,
|
|
230
|
+
remoteControl: false,
|
|
231
|
+
};
|
|
223
232
|
}
|
|
224
233
|
}
|
|
225
234
|
const OPT_IN_HINT = GOVERNANCE_OPT_IN_HINT.trim();
|
|
@@ -29,6 +29,13 @@ export declare class CodeNotEnabledError extends Error {
|
|
|
29
29
|
/** `pinv_` + base64url — the product-invite token shape the api mints. */
|
|
30
30
|
export declare const INVITE_TOKEN_RE: RegExp;
|
|
31
31
|
export declare function looksLikeInviteToken(value: unknown): value is string;
|
|
32
|
+
/**
|
|
33
|
+
* Where someone without an invitation (or a Mnemom contact) asks for access. Every
|
|
34
|
+
* locked-out message ends with it, so an uninvited customer is never at a dead end.
|
|
35
|
+
*/
|
|
36
|
+
export declare const REQUEST_ACCESS_URL = "https://www.mnemom.ai/contact";
|
|
37
|
+
/** `hint`, plus the request-access pointer unless the hint already carries the URL. */
|
|
38
|
+
export declare function withRequestAccess(hint: string): string;
|
|
32
39
|
/** Drop control characters (ANSI escapes included) from an api-supplied string before
|
|
33
40
|
* it reaches the terminal — defense in depth; the value is first-party but printed raw. */
|
|
34
41
|
export declare function sanitizeTerminalText(s: string): string;
|
|
@@ -39,6 +39,25 @@ export function looksLikeInviteToken(value) {
|
|
|
39
39
|
return typeof value === "string" && INVITE_TOKEN_RE.test(value.trim());
|
|
40
40
|
}
|
|
41
41
|
const FALLBACK_HINT = "Ask your Mnemom contact for a Mnemom Agent invitation, then run: mnemom agent --invite <token>";
|
|
42
|
+
/**
|
|
43
|
+
* Where someone without an invitation (or a Mnemom contact) asks for access. Every
|
|
44
|
+
* locked-out message ends with it, so an uninvited customer is never at a dead end.
|
|
45
|
+
*/
|
|
46
|
+
export const REQUEST_ACCESS_URL = "https://www.mnemom.ai/contact";
|
|
47
|
+
/** Whether `hint` already names the request-access page, as a whole word (trailing
|
|
48
|
+
* punctuation ignored) — a token match, not a substring one. */
|
|
49
|
+
function mentionsRequestAccess(hint) {
|
|
50
|
+
return hint.split(/\s+/).some((w) => w.replace(/[.,;:!?)]+$/, "") === REQUEST_ACCESS_URL);
|
|
51
|
+
}
|
|
52
|
+
/** `hint`, plus the request-access pointer unless the hint already carries the URL. */
|
|
53
|
+
export function withRequestAccess(hint) {
|
|
54
|
+
if (mentionsRequestAccess(hint))
|
|
55
|
+
return hint;
|
|
56
|
+
let end = hint.length;
|
|
57
|
+
while (end > 0 && (hint[end - 1] === "." || hint[end - 1].trim() === ""))
|
|
58
|
+
end--;
|
|
59
|
+
return `${hint.slice(0, end)}. No invitation yet? Request access at ${REQUEST_ACCESS_URL}`;
|
|
60
|
+
}
|
|
42
61
|
/** Drop control characters (ANSI escapes included) from an api-supplied string before
|
|
43
62
|
* it reaches the terminal — defense in depth; the value is first-party but printed raw. */
|
|
44
63
|
export function sanitizeTerminalText(s) {
|
|
@@ -76,7 +95,7 @@ function printNotEnabled(orgLabel, hint) {
|
|
|
76
95
|
say();
|
|
77
96
|
say(" mnemom agent --invite <token>");
|
|
78
97
|
say();
|
|
79
|
-
say(fmt.dim(` ${hint}`));
|
|
98
|
+
say(fmt.dim(` ${withRequestAccess(hint)}`));
|
|
80
99
|
}
|
|
81
100
|
/**
|
|
82
101
|
* Enforce the Mnemom Agent gate for the org the launch targets. Blocks (prints
|
|
@@ -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
|
|
@@ -345,6 +363,11 @@ export declare function logLaunch(ctx: {
|
|
|
345
363
|
goalMode: string | null;
|
|
346
364
|
cli: ResolvedCli;
|
|
347
365
|
}): void;
|
|
366
|
+
/**
|
|
367
|
+
* The agent a signed-out `--dry-run` shows when the caller named none: the real name is
|
|
368
|
+
* `agent-<handle>` from the Mnemom login, which a dry run (no sign-in) cannot know yet.
|
|
369
|
+
*/
|
|
370
|
+
export declare const DRY_RUN_SIGNED_OUT_AGENT = "agent-<your Mnemom login handle>";
|
|
348
371
|
export declare function printPlan(ctx: {
|
|
349
372
|
scenario: string;
|
|
350
373
|
shape: LaunchShape;
|
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";
|
|
@@ -36,6 +36,7 @@ import { getApiUrl, getGatewayUrl, MNEMOM_DIR } from "../lib/config.js";
|
|
|
36
36
|
import { createApiKey } from "../lib/api.js";
|
|
37
37
|
import { MNEMOM_KEY_STORE_FIELD, resolveAgentAuth, resolveSubscriptionMnemomKey, subscriptionChildEnv, subscriptionShape, } from "../lib/subscription-login.js";
|
|
38
38
|
import { LEDGER_FILE, appendSessionRecord, findResumeCandidate, readSessionRecords, resolveRetentionDays, resumedContextProfile, toLedgerContract, } from "../lib/agent-ledger.js";
|
|
39
|
+
import { isLoggedIn } from "../lib/auth.js";
|
|
39
40
|
import { loadCliConfig } from "../lib/cli-config.js";
|
|
40
41
|
import { configKeySpec, loadLauncherConfig, writeLauncherConfig } from "../lib/launcher-config.js";
|
|
41
42
|
import { experimentalNotice } from "../lib/experimental.js";
|
|
@@ -45,7 +46,7 @@ import { authConfigured, chooseAuth, detectClaudeLogin, readClaudeLoginState, }
|
|
|
45
46
|
import { requireConsoleKey, readConsoleKey } from "../lib/console-key.js";
|
|
46
47
|
import { goalAlignmentLine, resolveSessionSetup } from "./agent-session.js";
|
|
47
48
|
import { defaultAgentSlug, ensureClaimedServedAgent, ensureSessionAndBalance, isQuietPreambleExit, resolveDefaultSlug, resolvePreflightedAccount, } from "./agent-setup.js";
|
|
48
|
-
import { assembleContract, buildGoalModeHeader, buildCustomHeaders, buildHandoffMcpConfigJson, buildSessionConfig, buildSessionConfigB64, assertCompactAtCompatible, assertOffProfileCompatible, compactAtWindowWarning, describeCompactAt, launchWindow, resolveCompactAt, applyGatewayHintHeaders, resolveCompactionIntercept, resolveContextHandoff, resolveContextProfile, resolveHandoffGatewayHost, 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";
|
|
49
50
|
import { LAUNCHER_STORE_PATH, mergeStore } from "../lib/launcher-store.js";
|
|
50
51
|
import { slackChannelArgs } from "../lib/slack-launch.js";
|
|
51
52
|
/** The CLI's own provider-key store (zero 1Password): ~/.mnemom/launcher.json, 0600. */
|
|
@@ -261,20 +262,26 @@ export async function agentCommand(scenarioArg, options = {}) {
|
|
|
261
262
|
// which we only know AFTER sign-in. The entitlement-aware decision
|
|
262
263
|
// (`resolveRemoteControlShape`) happens below, right after
|
|
263
264
|
// `ensureSessionAndBalance`. --dry-run prints the plan BEFORE sign-in, so it
|
|
264
|
-
// can't know the entitlement
|
|
265
|
-
//
|
|
266
|
-
//
|
|
265
|
+
// can't know the entitlement: it previews the REQUESTED shape (terminal unless
|
|
266
|
+
// --remote-control/--server or `launch` asked otherwise) and never the RC
|
|
267
|
+
// auto-upgrade, which only an entitled (internal) account gets. An explicit RC
|
|
268
|
+
// request gets the note that the real launch still applies the entitlement gate.
|
|
267
269
|
if (options.dryRun) {
|
|
268
270
|
const planCli = cli ?? {
|
|
269
271
|
bin: "(not found — pass --cli <path> or `mnemom agent config set cli <path>`)",
|
|
270
272
|
name: basename((options.cli || env.MNEMOM_AGENT_CLI || "claude").trim()),
|
|
271
273
|
};
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
274
|
+
// A defaulted slug is `agent-<handle>` from the login (resolved for real after
|
|
275
|
+
// sign-in, below). Signed in: show that name. Signed out, or no usable handle:
|
|
276
|
+
// say where it comes from rather than print the "agent-user" fallback as if it were it.
|
|
277
|
+
if (!options.agent?.trim()) {
|
|
278
|
+
agent =
|
|
279
|
+
((await isLoggedIn()) && sanitizeAgentName(await resolveDefaultSlug())) ||
|
|
280
|
+
DRY_RUN_SIGNED_OUT_AGENT;
|
|
281
|
+
}
|
|
275
282
|
printPlan({
|
|
276
283
|
scenario,
|
|
277
|
-
shape
|
|
284
|
+
shape,
|
|
278
285
|
agent,
|
|
279
286
|
// The org is only known after sign-in, so a dry run never offers a resume (IG5 #1).
|
|
280
287
|
conversationId: resolveConversationId(options.conversationId),
|
|
@@ -298,8 +305,8 @@ export async function agentCommand(scenarioArg, options = {}) {
|
|
|
298
305
|
}
|
|
299
306
|
if (expNotice)
|
|
300
307
|
say(expNotice);
|
|
301
|
-
if (
|
|
302
|
-
say("mnemom agent: the
|
|
308
|
+
if (shape !== "terminal") {
|
|
309
|
+
say("mnemom agent: the real launch uses this shape only if your account is entitled to it, else terminal-only.");
|
|
303
310
|
}
|
|
304
311
|
sayContextProfile(contextProfile);
|
|
305
312
|
sayCompaction(contextProfile, compactionIntercept, compactAt, launchWindow(windowModelFor(options, cli)));
|
|
@@ -314,14 +321,6 @@ export async function agentCommand(scenarioArg, options = {}) {
|
|
|
314
321
|
if (!cli) {
|
|
315
322
|
cli = await promptForCliPath(options.cli, env, cliError);
|
|
316
323
|
}
|
|
317
|
-
// ── resolve the Anthropic key (zero 1Password; never logged) ──
|
|
318
|
-
// Not on a subscription launch: Claude Code then sends its own claude.ai login instead.
|
|
319
|
-
// A Console launch uses the key Claude Code's Console login created, then runs as api-key.
|
|
320
|
-
const anthropicKey = auth === "subscription"
|
|
321
|
-
? undefined
|
|
322
|
-
: auth === "console"
|
|
323
|
-
? requireConsoleKey(defaultConsoleKeyDeps(env))
|
|
324
|
-
: await resolveAnthropicKey(env, { allowPrompt: true, keySource: options.keySource });
|
|
325
324
|
// ── account + µ-balance gate (no free tier) ─────────────────────────────────
|
|
326
325
|
// mnemom agent runs governed and on µ, so before we seal a contract or launch we
|
|
327
326
|
// (1) ensure a Mnemom session — offering the try-me-style sign-in / sign-up when
|
|
@@ -342,6 +341,16 @@ export async function agentCommand(scenarioArg, options = {}) {
|
|
|
342
341
|
process.exit(1); // guidance already printed
|
|
343
342
|
throw err;
|
|
344
343
|
}
|
|
344
|
+
// ── resolve the Anthropic key (zero 1Password; never logged) ──
|
|
345
|
+
// AFTER the account gate above: an account Mnemom Agent is not enabled for learns that
|
|
346
|
+
// (and how to get access) before it is asked for, or refused for want of, a key.
|
|
347
|
+
// Not on a subscription launch: Claude Code then sends its own claude.ai login instead.
|
|
348
|
+
// A Console launch uses the key Claude Code's Console login created, then runs as api-key.
|
|
349
|
+
const anthropicKey = auth === "subscription"
|
|
350
|
+
? undefined
|
|
351
|
+
: auth === "console"
|
|
352
|
+
? requireConsoleKey(defaultConsoleKeyDeps(env))
|
|
353
|
+
: await resolveAnthropicKey(env, { allowPrompt: true, keySource: options.keySource });
|
|
345
354
|
// ── Remote-Control entitlement gate ─────────────────────────────────────────
|
|
346
355
|
// Remote Control (a session steerable from claude.ai / the Claude app) is
|
|
347
356
|
// internal-only. Now that we know the org's entitlement, resolve the final
|
|
@@ -432,10 +441,16 @@ export async function agentCommand(scenarioArg, options = {}) {
|
|
|
432
441
|
passthrough: [...(options.passthrough ?? []), "--resume", continuation.resumeCcSessionId],
|
|
433
442
|
};
|
|
434
443
|
}
|
|
435
|
-
// A launch that resumes a Claude Code session (the continuation above,
|
|
436
|
-
// `-- --resume <id
|
|
437
|
-
//
|
|
438
|
-
|
|
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);
|
|
439
454
|
if (resumeId && options.contextProfile === undefined && options.context !== false) {
|
|
440
455
|
let resumed;
|
|
441
456
|
try {
|
|
@@ -493,7 +508,8 @@ export async function agentCommand(scenarioArg, options = {}) {
|
|
|
493
508
|
cli,
|
|
494
509
|
envelope,
|
|
495
510
|
goalMode,
|
|
496
|
-
|
|
511
|
+
// A `--continue` records the session it continues, so a later resume finds its profile.
|
|
512
|
+
ccSessionId: cc.id ?? continuedCcSessionId,
|
|
497
513
|
contextProfile,
|
|
498
514
|
compactAt: contextProfile === "off" ? null : compactAt,
|
|
499
515
|
});
|
|
@@ -556,6 +572,12 @@ export async function agentCommand(scenarioArg, options = {}) {
|
|
|
556
572
|
contextHint,
|
|
557
573
|
ccSessionId,
|
|
558
574
|
handoffMcpConfigPath,
|
|
575
|
+
resumeHint: (id) => resumeHintLine({
|
|
576
|
+
scenario,
|
|
577
|
+
agent: options.agent?.trim() ? agent : undefined,
|
|
578
|
+
ccSessionId: id,
|
|
579
|
+
}),
|
|
580
|
+
knownCcSessionId: cc.id ?? continuedCcSessionId,
|
|
559
581
|
});
|
|
560
582
|
return; // launchTerminal exits the process
|
|
561
583
|
}
|
|
@@ -618,6 +640,52 @@ function choosesCcSession(passthrough) {
|
|
|
618
640
|
return !pickCcSessionId([...passthrough], () => "").inject;
|
|
619
641
|
}
|
|
620
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
|
+
}
|
|
621
689
|
/**
|
|
622
690
|
* Whether Claude Code still has the transcript of session `id`
|
|
623
691
|
* (`${CLAUDE_CONFIG_DIR:-~/.claude}/projects/<any project>/<id>.jsonl`; `--resume` finds it in any
|
|
@@ -627,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
|
|
|
627
695
|
export function ccSessionTranscriptExists(id, env) {
|
|
628
696
|
if (!CC_SESSION_ID_RE.test(id))
|
|
629
697
|
return false;
|
|
630
|
-
const projects =
|
|
698
|
+
const projects = ccProjectsDir(env);
|
|
631
699
|
try {
|
|
632
700
|
return readdirSync(projects).some((d) => existsSync(join(projects, d, `${id}.jsonl`)));
|
|
633
701
|
}
|
|
@@ -1077,8 +1145,15 @@ function launchTerminal(ctx) {
|
|
|
1077
1145
|
child.on("exit", (code, signal) => {
|
|
1078
1146
|
if (signal)
|
|
1079
1147
|
process.kill(process.pid, signal);
|
|
1080
|
-
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
|
+
}
|
|
1081
1155
|
process.exit(code ?? 0);
|
|
1156
|
+
}
|
|
1082
1157
|
});
|
|
1083
1158
|
};
|
|
1084
1159
|
// Implicit goal mode: put the local base-URL proxy in front of the door so each main request
|
|
@@ -1585,6 +1660,11 @@ export function logLaunch(ctx) {
|
|
|
1585
1660
|
say(`mnemom agent: goal mode: ${ctx.goalMode} (inferred from your messages; watch x-mnemom-goal-revision / x-mnemom-goal-steer on each response)`);
|
|
1586
1661
|
}
|
|
1587
1662
|
}
|
|
1663
|
+
/**
|
|
1664
|
+
* The agent a signed-out `--dry-run` shows when the caller named none: the real name is
|
|
1665
|
+
* `agent-<handle>` from the Mnemom login, which a dry run (no sign-in) cannot know yet.
|
|
1666
|
+
*/
|
|
1667
|
+
export const DRY_RUN_SIGNED_OUT_AGENT = "agent-<your Mnemom login handle>";
|
|
1588
1668
|
export function printPlan(ctx) {
|
|
1589
1669
|
const L = [];
|
|
1590
1670
|
L.push("mnemom agent — launch plan (dry run)");
|
|
@@ -16,3 +16,5 @@ export declare function experimentalEnableCommand(id: string | undefined, deps?:
|
|
|
16
16
|
export declare function experimentalDisableCommand(id: string | undefined, deps?: ExperimentalDeps): void;
|
|
17
17
|
/** The one-time migration (lib/experimental.ts migrateExperimental) against the real files. */
|
|
18
18
|
export declare function runExperimentalMigration(): void;
|
|
19
|
+
/** Does `dir` exist with anything in it? Unreadable counts as yes (an upgrader keeps the notice). */
|
|
20
|
+
export declare function hasPriorState(dir: string): boolean;
|
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
* `mnemom experimental list [--json] | enable <id> | disable <id>` — turn experimental
|
|
3
3
|
* features on and off (registry: lib/experimental.ts; stored in ~/.mnemom/config.json).
|
|
4
4
|
*/
|
|
5
|
+
import { existsSync, readdirSync } from "node:fs";
|
|
5
6
|
import { EXPERIMENTAL_CONTRACT, EXPERIMENTAL_ENV, EXPERIMENTAL_FEATURES, EXPERIMENTAL_IDS, isExperimentalId, migrateExperimental, resolveExperimental, } from "../lib/experimental.js";
|
|
6
7
|
import { loadCliConfig, setExperimentalEnabled } from "../lib/cli-config.js";
|
|
8
|
+
import { MNEMOM_DIR } from "../lib/config.js";
|
|
7
9
|
import { launcherEntries, loadLauncherConfig } from "../lib/launcher-config.js";
|
|
8
10
|
function defaultDeps() {
|
|
9
11
|
return {
|
|
@@ -74,5 +76,15 @@ export function runExperimentalMigration() {
|
|
|
74
76
|
launcherEntries: () => launcherEntries(loadLauncherConfig()),
|
|
75
77
|
persist: (ids) => setExperimentalEnabled(ids),
|
|
76
78
|
writeErr: (line) => void process.stderr.write(line),
|
|
79
|
+
priorInstall: () => hasPriorState(MNEMOM_DIR),
|
|
77
80
|
});
|
|
78
81
|
}
|
|
82
|
+
/** Does `dir` exist with anything in it? Unreadable counts as yes (an upgrader keeps the notice). */
|
|
83
|
+
export function hasPriorState(dir) {
|
|
84
|
+
try {
|
|
85
|
+
return existsSync(dir) && readdirSync(dir).length > 0;
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
}
|
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, [])
|
|
@@ -155,8 +155,11 @@ const agentCmd = program
|
|
|
155
155
|
.option("--conversation-id <id>", "x-mnemom-conversation-id (default: auto-generated)")
|
|
156
156
|
.option("--cli <name|path>", "Coding-agent CLI to launch: a name or full path (default: claude)")
|
|
157
157
|
.option("--gateway <url>", "Gateway host override — must be the same cell as the rest of the CLI (default: that cell's gateway; or MNEMOM_AGENT_GATEWAY)")
|
|
158
|
-
|
|
159
|
-
|
|
158
|
+
// Remote Control is internal-only (the launch gates it on the org's entitlement and runs a
|
|
159
|
+
// customer terminal-only), so its two shapes stay out of --help for everyone. The help is
|
|
160
|
+
// built at load, before any sign-in, so it cannot ask the entitlement; the flags still parse.
|
|
161
|
+
.addOption(new Option("--remote-control", "Interactive Remote Control session in THIS terminal (behind a local proxy)").hideHelp())
|
|
162
|
+
.addOption(new Option("--server", "Headless `claude remote-control` dispatcher (behind a local proxy)").hideHelp())
|
|
160
163
|
.option("--auth <mode>", "How Claude Code authenticates: api-key (default) | subscription (your Claude Team/Enterprise login) | console (the API key your Claude Code Anthropic Console login created)")
|
|
161
164
|
.option("--model <alias|id>", "Model to launch (CLI alias or concrete id)")
|
|
162
165
|
.addOption(new Option("--effort <level>", "Reasoning effort").choices([
|
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;
|
|
@@ -98,6 +98,11 @@ export interface MigrationDeps {
|
|
|
98
98
|
launcherEntries(): Array<[string, string]>;
|
|
99
99
|
persist(ids: ExperimentalId[]): void;
|
|
100
100
|
writeErr(line: string): void;
|
|
101
|
+
/**
|
|
102
|
+
* Did this machine run the CLI before this invocation (~/.mnemom already held something)?
|
|
103
|
+
* A brand-new install never had implicit on, so it gets no "it used to be on" notice.
|
|
104
|
+
*/
|
|
105
|
+
priorInstall(): boolean;
|
|
101
106
|
}
|
|
102
107
|
/** One-time notice for users who had implicit goal alignment by default (no goal_mode set). */
|
|
103
108
|
export declare const IMPLICIT_NOW_OFF_NOTICE: string;
|
package/dist/lib/experimental.js
CHANGED
|
@@ -213,6 +213,7 @@ export function migrateExperimental(deps) {
|
|
|
213
213
|
try {
|
|
214
214
|
if (deps.stored() !== undefined)
|
|
215
215
|
return null;
|
|
216
|
+
const upgrading = deps.priorInstall();
|
|
216
217
|
const entries = deps.launcherEntries();
|
|
217
218
|
const ids = featuresInUse(entries);
|
|
218
219
|
deps.persist(ids);
|
|
@@ -220,9 +221,10 @@ export function migrateExperimental(deps) {
|
|
|
220
221
|
deps.writeErr(`mnemom: ${ids.join(", ")} ${ids.length === 1 ? "is" : "are"} now experimental and kept on because your launcher.toml uses ${ids.length === 1 ? "it" : "them"} — see \`mnemom experimental list\`.\n`);
|
|
221
222
|
}
|
|
222
223
|
// Implicit was the unconfigured DEFAULT, so most users relied on it without a
|
|
223
|
-
// goal_mode entry. Don't let it switch off silently for them: say so once.
|
|
224
|
+
// goal_mode entry. Don't let it switch off silently for them: say so once. A fresh
|
|
225
|
+
// install had no default to lose, so it is told nothing.
|
|
224
226
|
const optedOut = entries.some(([k, v]) => k === "goal_mode" && v === "off");
|
|
225
|
-
if (!ids.includes("implicit") && !optedOut)
|
|
227
|
+
if (upgrading && !ids.includes("implicit") && !optedOut)
|
|
226
228
|
deps.writeErr(IMPLICIT_NOW_OFF_NOTICE);
|
|
227
229
|
return ids;
|
|
228
230
|
}
|
|
@@ -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";
|
package/dist/lib/version-gate.js
CHANGED
|
@@ -74,8 +74,7 @@ export function enforceMinCliVersion(response, exit = process.exit, write = (chu
|
|
|
74
74
|
write(`\n✓ Updated the Mnemom CLI (was v${CLI_VERSION}; server minimum v${floor}). Re-run your command.\n\n`);
|
|
75
75
|
exit(1);
|
|
76
76
|
}
|
|
77
|
-
write(`\n✖ Your Mnemom CLI (v${CLI_VERSION}) is too old for this server (minimum v${floor}).\n` +
|
|
78
|
-
` Old CLIs can silently drop newer request fields — e.g. \`--org\` on \`mnemom agent claim\`.\n\n` +
|
|
77
|
+
write(`\n✖ Your Mnemom CLI (v${CLI_VERSION}) is too old for this server (minimum v${floor}).\n\n` +
|
|
79
78
|
` Upgrade: npm i -g @mnemom/mnemom@latest\n\n`);
|
|
80
79
|
exit(1);
|
|
81
80
|
}
|