@mnemom/mnemom 0.17.3-next.5 → 0.17.3
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-config.d.ts +4 -1
- package/dist/commands/agent-config.js +8 -5
- package/dist/commands/agent-doctor.js +10 -1
- package/dist/commands/agent-session.d.ts +7 -6
- package/dist/commands/agent-session.js +10 -11
- package/dist/commands/agent-sessions.d.ts +5 -0
- package/dist/commands/agent-sessions.js +10 -1
- package/dist/commands/agent-setup.js +9 -2
- package/dist/commands/agent.d.ts +9 -0
- package/dist/commands/agent.js +24 -6
- package/dist/commands/experimental.d.ts +18 -0
- package/dist/commands/experimental.js +78 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +85 -19
- package/dist/lib/agent-ledger.d.ts +5 -0
- package/dist/lib/agent-ledger.js +5 -0
- package/dist/lib/agent.d.ts +46 -9
- package/dist/lib/agent.js +65 -13
- package/dist/lib/cli-config.d.ts +10 -0
- package/dist/lib/cli-config.js +11 -0
- package/dist/lib/experimental.d.ts +119 -0
- package/dist/lib/experimental.js +226 -0
- package/dist/lib/launcher-config.d.ts +9 -0
- package/dist/lib/launcher-config.js +26 -1
- package/dist/lib/slack-router.js +3 -1
- package/package.json +1 -1
|
@@ -13,8 +13,11 @@
|
|
|
13
13
|
* Settable keys are the single source of truth in lib/code-config.ts CONFIG_KEYS.
|
|
14
14
|
* The secret Anthropic key is NOT a config key — it lives in ~/.mnemom/launcher.json.
|
|
15
15
|
*/
|
|
16
|
+
import { type ExperimentalId } from "../lib/experimental.js";
|
|
16
17
|
/** Entry point for `mnemom agent config <verb> [args...]`. Throws on misuse. */
|
|
17
|
-
export declare function agentConfigCommand(argv: string[]
|
|
18
|
+
export declare function agentConfigCommand(argv: string[], opts?: {
|
|
19
|
+
enabled?: readonly ExperimentalId[];
|
|
20
|
+
}): Promise<void>;
|
|
18
21
|
/**
|
|
19
22
|
* `$VISUAL` / `$EDITOR` is a program plus optional plain flags (`code --wait`, `emacsclient -t`),
|
|
20
23
|
* never shell syntax: the value is split on whitespace and every token must be a bare
|
|
@@ -16,15 +16,17 @@
|
|
|
16
16
|
import { spawnSync } from "node:child_process";
|
|
17
17
|
import { existsSync } from "node:fs";
|
|
18
18
|
import { LAUNCHER_CONFIG_PATH, CONFIG_KEYS, configKeySpec, loadLauncherConfig, writeLauncherConfig, } from "../lib/launcher-config.js";
|
|
19
|
+
import { loadExperimentalState } from "../lib/cli-config.js";
|
|
20
|
+
import { configKeyMark } from "../lib/experimental.js";
|
|
19
21
|
const out = (line = "") => console.log(line);
|
|
20
22
|
const err = (line = "") => console.error(line);
|
|
21
23
|
/** Entry point for `mnemom agent config <verb> [args...]`. Throws on misuse. */
|
|
22
|
-
export async function agentConfigCommand(argv) {
|
|
24
|
+
export async function agentConfigCommand(argv, opts = {}) {
|
|
23
25
|
const [verb, ...rest] = argv;
|
|
24
26
|
switch (verb) {
|
|
25
27
|
case undefined:
|
|
26
28
|
case "list":
|
|
27
|
-
return listConfig();
|
|
29
|
+
return listConfig(opts.enabled ?? loadExperimentalState().enabled);
|
|
28
30
|
case "path":
|
|
29
31
|
out(LAUNCHER_CONFIG_PATH);
|
|
30
32
|
return;
|
|
@@ -40,7 +42,8 @@ export async function agentConfigCommand(argv) {
|
|
|
40
42
|
throw new Error(`mnemom agent config: unknown verb '${verb}'. Use list | get | set | unset | edit | path.`);
|
|
41
43
|
}
|
|
42
44
|
}
|
|
43
|
-
|
|
45
|
+
/** Settings of experimental features that are off are marked `(experimental, off)`. */
|
|
46
|
+
function listConfig(enabled) {
|
|
44
47
|
const cfg = loadLauncherConfig();
|
|
45
48
|
const rows = [];
|
|
46
49
|
for (const spec of CONFIG_KEYS) {
|
|
@@ -54,13 +57,13 @@ function listConfig() {
|
|
|
54
57
|
out("");
|
|
55
58
|
out("Settable keys:");
|
|
56
59
|
for (const spec of CONFIG_KEYS)
|
|
57
|
-
out(` ${spec.key.padEnd(22)} ${spec.describe}`);
|
|
60
|
+
out(` ${spec.key.padEnd(22)} ${spec.describe}${configKeyMark(spec.key, undefined, enabled)}`);
|
|
58
61
|
return;
|
|
59
62
|
}
|
|
60
63
|
const width = Math.max(...rows.map(([k]) => k.length));
|
|
61
64
|
out(`# ${LAUNCHER_CONFIG_PATH}`);
|
|
62
65
|
for (const [k, v] of rows)
|
|
63
|
-
out(`${k.padEnd(width)} ${v}`);
|
|
66
|
+
out(`${k.padEnd(width)} ${v}${configKeyMark(k, v, enabled)}`);
|
|
64
67
|
}
|
|
65
68
|
function getConfig(key) {
|
|
66
69
|
if (!key)
|
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
import { spawnSync } from "node:child_process";
|
|
15
15
|
import { existsSync } from "node:fs";
|
|
16
16
|
import { anthropicKeySource, checkRcCapability, resolveCliBin } from "./agent.js";
|
|
17
|
-
import { isProdGateway, resolveAnthropicDoor, resolveGatewayHost } from "../lib/agent.js";
|
|
17
|
+
import { isProdGateway, resolveAnthropicDoor, resolveGatewayHost, singleCellError, } from "../lib/agent.js";
|
|
18
|
+
import { getApiUrl, getGatewayUrl } from "../lib/config.js";
|
|
18
19
|
import { LAUNCHER_CONFIG_PATH } from "../lib/launcher-config.js";
|
|
19
20
|
import { getCodeEntitlement, MnemomApiError } from "../lib/api.js";
|
|
20
21
|
import { isLoggedIn } from "../lib/auth.js";
|
|
@@ -52,6 +53,14 @@ export async function agentDoctorCommand(options = {}) {
|
|
|
52
53
|
add("ok", "Anthropic key: found in ~/.mnemom/launcher.json");
|
|
53
54
|
else
|
|
54
55
|
add("miss", "Anthropic key: none found", "export MNEMOM_AGENT_ANTHROPIC_KEY=sk-ant-… (or ANTHROPIC_API_KEY), or run `mnemom agent` once and accept the save prompt");
|
|
56
|
+
// ── one cell: the launch door must be the CLI's own cell ──
|
|
57
|
+
const cellError = singleCellError({
|
|
58
|
+
door: resolveAnthropicDoor(env),
|
|
59
|
+
apiUrl: getApiUrl(),
|
|
60
|
+
cellGateway: getGatewayUrl(),
|
|
61
|
+
});
|
|
62
|
+
if (cellError)
|
|
63
|
+
add("miss", "cell: mixed — a launch would be refused", cellError);
|
|
55
64
|
// ── gateway health ──
|
|
56
65
|
const host = resolveGatewayHost(env);
|
|
57
66
|
const cell = isProdGateway(env) ? "us-2/prod" : "custom";
|
|
@@ -8,12 +8,13 @@
|
|
|
8
8
|
* 1. a SESSION NAME — labels the session in traces and the claude.ai session
|
|
9
9
|
* list (defaults to the current folder's name).
|
|
10
10
|
*
|
|
11
|
-
* GOAL ALIGNMENT is not prompted for
|
|
12
|
-
*
|
|
13
|
-
* (`x-mnemom-goal-mode: implicit`, no contract)
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
11
|
+
* GOAL ALIGNMENT is not prompted for. `--goal` seals a one-line contract
|
|
12
|
+
* (`x-mnemom-contract`). Implicit alignment — the goal inferred from your own
|
|
13
|
+
* messages (`x-mnemom-goal-mode: implicit`, no contract) — is the experimental
|
|
14
|
+
* `implicit` feature: while it is on a bare launch runs implicit, while it is off
|
|
15
|
+
* (the default) a bare launch runs with alignment off. `--no-implicit` (or
|
|
16
|
+
* launcher.toml `goal_mode = "off"`) turns it off. The mode is resolved upstream
|
|
17
|
+
* (`resolveImplicit`) and announced on the launch line.
|
|
17
18
|
*
|
|
18
19
|
* Non-interactive runs (pipes, CI) never prompt — they get a clear error asking
|
|
19
20
|
* for a name. Pure apart from the injected `ask` functions, so every arm is
|
|
@@ -8,12 +8,13 @@
|
|
|
8
8
|
* 1. a SESSION NAME — labels the session in traces and the claude.ai session
|
|
9
9
|
* list (defaults to the current folder's name).
|
|
10
10
|
*
|
|
11
|
-
* GOAL ALIGNMENT is not prompted for
|
|
12
|
-
*
|
|
13
|
-
* (`x-mnemom-goal-mode: implicit`, no contract)
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
11
|
+
* GOAL ALIGNMENT is not prompted for. `--goal` seals a one-line contract
|
|
12
|
+
* (`x-mnemom-contract`). Implicit alignment — the goal inferred from your own
|
|
13
|
+
* messages (`x-mnemom-goal-mode: implicit`, no contract) — is the experimental
|
|
14
|
+
* `implicit` feature: while it is on a bare launch runs implicit, while it is off
|
|
15
|
+
* (the default) a bare launch runs with alignment off. `--no-implicit` (or
|
|
16
|
+
* launcher.toml `goal_mode = "off"`) turns it off. The mode is resolved upstream
|
|
17
|
+
* (`resolveImplicit`) and announced on the launch line.
|
|
17
18
|
*
|
|
18
19
|
* Non-interactive runs (pipes, CI) never prompt — they get a clear error asking
|
|
19
20
|
* for a name. Pure apart from the injected `ask` functions, so every arm is
|
|
@@ -65,11 +66,9 @@ export async function resolveSessionSetup(input, ask) {
|
|
|
65
66
|
say(` (Next time you can pass it directly: mnemom agent <name>.)`);
|
|
66
67
|
const typed = await ask.askInput(`Session name [${fallback}]:`);
|
|
67
68
|
const scenario = sanitizeAgentName(typed.trim()) || fallback;
|
|
68
|
-
// ── 2. goal alignment — resolved upstream, no prompt
|
|
69
|
-
//
|
|
70
|
-
//
|
|
71
|
-
// `--implicit` / `goal_mode = "implicit"`) is implicit, and `--no-implicit` /
|
|
72
|
-
// `goal_mode = "off"` turns it off. The launch line announces the mode.
|
|
69
|
+
// ── 2. goal alignment — resolved upstream, no prompt: `--goal` seals a contract,
|
|
70
|
+
// the `implicit` experimental feature (when on) makes implicit the default, and
|
|
71
|
+
// `--no-implicit` / `goal_mode = "off"` turns it off. The launch line announces the mode.
|
|
73
72
|
if (flagGoal) {
|
|
74
73
|
return { scenario, goal: flagGoal, goalAlignment: "on", prompted: true };
|
|
75
74
|
}
|
|
@@ -26,6 +26,11 @@ export interface SessionCommandDeps {
|
|
|
26
26
|
* one `live goal` line an implicit session gets. Optional: absent ⇒ no line.
|
|
27
27
|
*/
|
|
28
28
|
fetchGoal?: (record: SessionRecord) => Promise<GoalShowResponse>;
|
|
29
|
+
/**
|
|
30
|
+
* The gateway of the cell this CLI reads live state from. A session that ran on another
|
|
31
|
+
* cell is not looked up there (it would always read as "no sealed contract"). Absent ⇒ no check.
|
|
32
|
+
*/
|
|
33
|
+
cellGateway?: () => string;
|
|
29
34
|
out: (line: string) => void;
|
|
30
35
|
err: (line: string) => void;
|
|
31
36
|
}
|
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
* only the server's hash is shown, labelled "sealed hash".
|
|
15
15
|
*/
|
|
16
16
|
import { readSessionRecords } from "../lib/agent-ledger.js";
|
|
17
|
-
import { MNEMOM_DIR } from "../lib/config.js";
|
|
17
|
+
import { getGatewayUrl, MNEMOM_DIR } from "../lib/config.js";
|
|
18
|
+
import { sameOrigin } from "../lib/agent.js";
|
|
18
19
|
import { getAgentSession, MnemomApiError } from "../lib/api.js";
|
|
19
20
|
import { fmt, formatRelativeAge } from "../lib/format.js";
|
|
20
21
|
import { goalControlFetch } from "../lib/goal-api.js";
|
|
@@ -31,6 +32,7 @@ export const liveSessionDeps = {
|
|
|
31
32
|
const creds = await resolveLaunchCreds();
|
|
32
33
|
return goalControlFetch(record, "show", { thread: "main" }, { fetch, creds });
|
|
33
34
|
},
|
|
35
|
+
cellGateway: () => getGatewayUrl(),
|
|
34
36
|
out: (line) => console.log(line),
|
|
35
37
|
err: (line) => console.error(line),
|
|
36
38
|
};
|
|
@@ -180,6 +182,9 @@ export function buildShowView(local, remote, reason) {
|
|
|
180
182
|
if (local.context_profile)
|
|
181
183
|
launchBits.push(`context ${local.context_profile}`);
|
|
182
184
|
L.push(fmt.label("launch: ", launchBits.join(" · ")));
|
|
185
|
+
if (local.experimental?.length) {
|
|
186
|
+
L.push(fmt.label("experimental: ", local.experimental.join(", ")));
|
|
187
|
+
}
|
|
183
188
|
L.push(fmt.label("conversation: ", local.conversation_id));
|
|
184
189
|
const goalMode = resolveGoalMode(local, remote);
|
|
185
190
|
L.push(fmt.label("goal alignment:", goalBadge(goalMode)));
|
|
@@ -315,9 +320,13 @@ export async function agentShowCommand(ref, opts, deps) {
|
|
|
315
320
|
const { record, others } = resolved;
|
|
316
321
|
let remote = null;
|
|
317
322
|
let reason;
|
|
323
|
+
const cell = deps.cellGateway?.();
|
|
318
324
|
if (record.agent_id === null) {
|
|
319
325
|
reason = "agent id was not resolved at launch";
|
|
320
326
|
}
|
|
327
|
+
else if (cell !== undefined && !sameOrigin(record.gateway_host, cell)) {
|
|
328
|
+
reason = `this session ran on ${record.gateway_host}, not this CLI's cell (${cell}) — point the CLI at that cell to read it`;
|
|
329
|
+
}
|
|
321
330
|
else {
|
|
322
331
|
try {
|
|
323
332
|
remote = await deps.fetchSession(record.conversation_id, record.agent_id, record.org_id);
|
|
@@ -26,10 +26,10 @@
|
|
|
26
26
|
import { claimAgent, getAgentByName, getMe, getMuBalance, getMyPersonalOrg, MnemomApiError, mintProtectionCapability, putAlignmentCard, putProtectionCard, } from "../lib/api.js";
|
|
27
27
|
import { getAuthInfo, isLoggedIn, loginWithBrowser, loginWithDeviceFlow, resolveAuth, } from "../lib/auth.js";
|
|
28
28
|
import { getActiveOrg } from "../lib/cli-config.js";
|
|
29
|
-
import { getWebsiteUrl } from "../lib/config.js";
|
|
29
|
+
import { getApiUrl, getGatewayUrl, getWebsiteUrl } from "../lib/config.js";
|
|
30
30
|
import { fmt } from "../lib/format.js";
|
|
31
31
|
import { askYesNo, isInteractive } from "../lib/prompt.js";
|
|
32
|
-
import { resolveGatewayHost, sanitizeAgentName } from "../lib/agent.js";
|
|
32
|
+
import { resolveAnthropicDoor, resolveGatewayHost, sanitizeAgentName, singleCellError, } from "../lib/agent.js";
|
|
33
33
|
import { configKeySpec, loadLauncherConfig, writeLauncherConfig } from "../lib/launcher-config.js";
|
|
34
34
|
import { deriveHashProof } from "./agents.js";
|
|
35
35
|
import { birthThroughGateway } from "./wrap.js";
|
|
@@ -273,6 +273,13 @@ async function publishDefaultCards(agentId) {
|
|
|
273
273
|
/** `mnemom agent setup [--agent <slug>]` — the explicit provisioning entry point. */
|
|
274
274
|
export async function agentSetupCommand(opts = {}) {
|
|
275
275
|
const gatewayHost = resolveGatewayHost(process.env);
|
|
276
|
+
const cellError = singleCellError({
|
|
277
|
+
door: resolveAnthropicDoor(process.env),
|
|
278
|
+
apiUrl: getApiUrl(),
|
|
279
|
+
cellGateway: getGatewayUrl(),
|
|
280
|
+
});
|
|
281
|
+
if (cellError)
|
|
282
|
+
throw new Error(cellError.replace("mnemom agent:", "mnemom agent setup:"));
|
|
276
283
|
// Sign in (offer sign-up) and enforce the MU balance before any account write.
|
|
277
284
|
try {
|
|
278
285
|
await ensureSessionAndBalance({});
|
package/dist/commands/agent.d.ts
CHANGED
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
* line, or the proxy's stdin under RC). It is never logged, printed, or passed as
|
|
26
26
|
* an argv value.
|
|
27
27
|
*/
|
|
28
|
+
import { type ExperimentalId } from "../lib/experimental.js";
|
|
28
29
|
import { type ContextProfile, type LaunchShape, type SealedContractEnvelope } from "../lib/agent.js";
|
|
29
30
|
export interface AgentOptions {
|
|
30
31
|
goal?: string;
|
|
@@ -87,6 +88,12 @@ export interface AgentOptions {
|
|
|
87
88
|
* Undefined = the full default order.
|
|
88
89
|
*/
|
|
89
90
|
keySource?: "env" | "store" | "prompt";
|
|
91
|
+
/**
|
|
92
|
+
* The experimental features on for this launch (`mnemom experimental`), already gated
|
|
93
|
+
* upstream in index.ts. Announced on stderr, recorded in the ledger, and sent in the
|
|
94
|
+
* session-config header's `experimental` array. Absent/empty ⇒ none of the three.
|
|
95
|
+
*/
|
|
96
|
+
experimental?: readonly ExperimentalId[];
|
|
90
97
|
passthrough?: string[];
|
|
91
98
|
/** Print the resolved launch plan and exit — no key read, no spawn. */
|
|
92
99
|
dryRun?: boolean;
|
|
@@ -245,4 +252,6 @@ export declare function printPlan(ctx: {
|
|
|
245
252
|
contextProfile: ContextProfile;
|
|
246
253
|
/** The decoded `x-mnemom-session-config` overlay (`buildSessionConfig`); empty ⇒ no header. */
|
|
247
254
|
sessionConfig: Record<string, string>;
|
|
255
|
+
/** The experimental features on for this launch (the header's `experimental` array). */
|
|
256
|
+
experimental?: readonly string[];
|
|
248
257
|
}): void;
|
package/dist/commands/agent.js
CHANGED
|
@@ -31,16 +31,17 @@ import { randomUUID } from "node:crypto";
|
|
|
31
31
|
import { tmpdir, homedir } from "node:os";
|
|
32
32
|
import { basename, join, dirname } from "node:path";
|
|
33
33
|
import { fileURLToPath } from "node:url";
|
|
34
|
-
import { MNEMOM_DIR } from "../lib/config.js";
|
|
34
|
+
import { getApiUrl, getGatewayUrl, MNEMOM_DIR } from "../lib/config.js";
|
|
35
35
|
import { getAgentByName } from "../lib/api.js";
|
|
36
36
|
import { LEDGER_FILE, appendSessionRecord, resolveRetentionDays, toLedgerContract, } from "../lib/agent-ledger.js";
|
|
37
37
|
import { loadCliConfig } from "../lib/cli-config.js";
|
|
38
38
|
import { configKeySpec, loadLauncherConfig, writeLauncherConfig } from "../lib/launcher-config.js";
|
|
39
|
+
import { experimentalNotice } from "../lib/experimental.js";
|
|
39
40
|
import { fmt } from "../lib/format.js";
|
|
40
41
|
import { askInput, askYesNo, isInteractive } from "../lib/prompt.js";
|
|
41
42
|
import { goalAlignmentLine, resolveSessionSetup } from "./agent-session.js";
|
|
42
43
|
import { AgentIdentityMismatchError, defaultAgentSlug, ensureGovernedAgent, ensureSessionAndBalance, isQuietPreambleExit, resolveDefaultSlug, resolvePreflightedAccount, } from "./agent-setup.js";
|
|
43
|
-
import { assembleContract, buildGoalModeHeader, buildCustomHeaders, buildHandoffMcpConfigJson, buildSessionConfig, buildSessionConfigB64, resolveContextHandoff, resolveContextProfile, resolveHandoffGatewayHost, HANDOFF_RC_KEY_ENV_NAME, isProdGateway, launchPreferenceExplicit, launchPreflighted, remoteControlAllowed, resolveAnthropicDoor, resolveConversationId, resolveGatewayHost, resolveLaunchShape, resolveRemoteControlShape, passthroughName, sanitizeAgentName, sessionDisplayName, SESSION_NAME_ENV, SESSION_CONFIG_HEADER, } from "../lib/agent.js";
|
|
44
|
+
import { assembleContract, buildGoalModeHeader, buildCustomHeaders, buildHandoffMcpConfigJson, buildSessionConfig, buildSessionConfigB64, resolveContextHandoff, resolveContextProfile, resolveHandoffGatewayHost, HANDOFF_RC_KEY_ENV_NAME, isProdGateway, launchPreferenceExplicit, launchPreflighted, remoteControlAllowed, resolveAnthropicDoor, resolveConversationId, resolveGatewayHost, singleCellError, resolveLaunchShape, resolveRemoteControlShape, passthroughName, sanitizeAgentName, sessionDisplayName, SESSION_NAME_ENV, SESSION_CONFIG_HEADER, } from "../lib/agent.js";
|
|
44
45
|
import { LAUNCHER_STORE_PATH, mergeStore } from "../lib/launcher-store.js";
|
|
45
46
|
import { slackChannelArgs } from "../lib/slack-launch.js";
|
|
46
47
|
/** The CLI's own provider-key store (zero 1Password): ~/.mnemom/launcher.json, 0600. */
|
|
@@ -60,9 +61,9 @@ function sayContextProfile(profile) {
|
|
|
60
61
|
}
|
|
61
62
|
/** Entry point for `mnemom agent [scenario]`. Throws on fatal misconfig. */
|
|
62
63
|
export async function agentCommand(scenarioArg, options = {}) {
|
|
63
|
-
// ── session name + goal ── (a bare `mnemom agent` in a terminal asks for
|
|
64
|
-
// goal alignment is
|
|
65
|
-
//
|
|
64
|
+
// ── session name + goal ── (a bare `mnemom agent` in a terminal asks for the name;
|
|
65
|
+
// goal alignment is resolved upstream: --goal seals a contract, implicit alignment
|
|
66
|
+
// is on by default only while the `implicit` experimental feature is on.
|
|
66
67
|
const session = await resolveSessionSetup({
|
|
67
68
|
scenario: scenarioArg,
|
|
68
69
|
goal: options.goal,
|
|
@@ -108,6 +109,9 @@ export async function agentCommand(scenarioArg, options = {}) {
|
|
|
108
109
|
// ── door ──
|
|
109
110
|
const gatewayHost = resolveGatewayHost(env);
|
|
110
111
|
const door = resolveAnthropicDoor(env);
|
|
112
|
+
const cellError = singleCellError({ door, apiUrl: getApiUrl(), cellGateway: getGatewayUrl() });
|
|
113
|
+
if (cellError)
|
|
114
|
+
throw new Error(cellError);
|
|
111
115
|
const cellNote = isProdGateway(env) ? "us-2/prod" : `custom (${gatewayHost})`;
|
|
112
116
|
const yolo = options.yolo !== false; // default ON
|
|
113
117
|
const contextHint = options.contextHint !== false; // default ON
|
|
@@ -121,8 +125,14 @@ export async function agentCommand(scenarioArg, options = {}) {
|
|
|
121
125
|
env,
|
|
122
126
|
});
|
|
123
127
|
const contextHandoff = resolveContextHandoff({ contextHandoff: options.contextHandoff, env });
|
|
128
|
+
const experimental = options.experimental ?? [];
|
|
124
129
|
const sessionConfig = buildSessionConfig({ profile: contextProfile, contextHandoff });
|
|
125
|
-
const sessionConfigB64 = buildSessionConfigB64({
|
|
130
|
+
const sessionConfigB64 = buildSessionConfigB64({
|
|
131
|
+
profile: contextProfile,
|
|
132
|
+
contextHandoff,
|
|
133
|
+
experimental,
|
|
134
|
+
});
|
|
135
|
+
const expNotice = experimentalNotice(experimental);
|
|
126
136
|
// ── which CLI executable (name or full path) ── resolve GRACEFULLY: if it's not
|
|
127
137
|
// found we don't crash with a technical error — we ask for the path at launch.
|
|
128
138
|
let cli;
|
|
@@ -163,7 +173,10 @@ export async function agentCommand(scenarioArg, options = {}) {
|
|
|
163
173
|
cli: planCli,
|
|
164
174
|
contextProfile,
|
|
165
175
|
sessionConfig,
|
|
176
|
+
experimental,
|
|
166
177
|
});
|
|
178
|
+
if (expNotice)
|
|
179
|
+
say(expNotice);
|
|
167
180
|
if (previewShape === "remote-control") {
|
|
168
181
|
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.");
|
|
169
182
|
}
|
|
@@ -290,6 +303,8 @@ export async function agentCommand(scenarioArg, options = {}) {
|
|
|
290
303
|
contextProfile,
|
|
291
304
|
});
|
|
292
305
|
logLaunch({ shape, door, agent, conversationId, cellNote, envelope, goalMode, cli });
|
|
306
|
+
if (expNotice)
|
|
307
|
+
say(expNotice);
|
|
293
308
|
say(goalAlignmentLine(goalAlignmentMode(envelope, goalMode)));
|
|
294
309
|
sayContextProfile(contextProfile);
|
|
295
310
|
// ── handoff MCP tool (MNE-7564) ── OFF by default: absent/false ⇒ handoffMcpConfigPath stays
|
|
@@ -396,6 +411,7 @@ export async function recordSessionLaunch(ctx) {
|
|
|
396
411
|
goal_mode: goalAlignmentMode(ctx.envelope, ctx.goalMode),
|
|
397
412
|
context_profile: ctx.contextProfile,
|
|
398
413
|
cc_session_id: ctx.ccSessionId ?? null,
|
|
414
|
+
...(ctx.options.experimental?.length ? { experimental: [...ctx.options.experimental] } : {}),
|
|
399
415
|
}, {
|
|
400
416
|
dir: MNEMOM_DIR,
|
|
401
417
|
now: () => new Date(),
|
|
@@ -1110,6 +1126,8 @@ export function printPlan(ctx) {
|
|
|
1110
1126
|
for (const [k, v] of overlay)
|
|
1111
1127
|
L.push(` ${k}=${v}`);
|
|
1112
1128
|
}
|
|
1129
|
+
if (ctx.experimental?.length)
|
|
1130
|
+
L.push(` experimental: ${ctx.experimental.join(", ")}`);
|
|
1113
1131
|
if (ctx.envelope) {
|
|
1114
1132
|
L.push(` contract: ${ctx.envelope.canonical}`);
|
|
1115
1133
|
L.push(` contract sha: ${ctx.envelope.sha}`);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `mnemom experimental list [--json] | enable <id> | disable <id>` — turn experimental
|
|
3
|
+
* features on and off (registry: lib/experimental.ts; stored in ~/.mnemom/config.json).
|
|
4
|
+
*/
|
|
5
|
+
import { type ExperimentalId, type ExperimentalPrefs } from "../lib/experimental.js";
|
|
6
|
+
export interface ExperimentalDeps {
|
|
7
|
+
stored(): ExperimentalPrefs | undefined;
|
|
8
|
+
persist(ids: ExperimentalId[]): void;
|
|
9
|
+
env: Record<string, string | undefined>;
|
|
10
|
+
log(line: string): void;
|
|
11
|
+
}
|
|
12
|
+
export declare function experimentalListCommand(opts?: {
|
|
13
|
+
json?: boolean;
|
|
14
|
+
}, deps?: ExperimentalDeps): void;
|
|
15
|
+
export declare function experimentalEnableCommand(id: string | undefined, deps?: ExperimentalDeps): void;
|
|
16
|
+
export declare function experimentalDisableCommand(id: string | undefined, deps?: ExperimentalDeps): void;
|
|
17
|
+
/** The one-time migration (lib/experimental.ts migrateExperimental) against the real files. */
|
|
18
|
+
export declare function runExperimentalMigration(): void;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `mnemom experimental list [--json] | enable <id> | disable <id>` — turn experimental
|
|
3
|
+
* features on and off (registry: lib/experimental.ts; stored in ~/.mnemom/config.json).
|
|
4
|
+
*/
|
|
5
|
+
import { EXPERIMENTAL_CONTRACT, EXPERIMENTAL_ENV, EXPERIMENTAL_FEATURES, EXPERIMENTAL_IDS, isExperimentalId, migrateExperimental, resolveExperimental, } from "../lib/experimental.js";
|
|
6
|
+
import { loadCliConfig, setExperimentalEnabled } from "../lib/cli-config.js";
|
|
7
|
+
import { launcherEntries, loadLauncherConfig } from "../lib/launcher-config.js";
|
|
8
|
+
function defaultDeps() {
|
|
9
|
+
return {
|
|
10
|
+
stored: () => loadCliConfig().experimental,
|
|
11
|
+
persist: (ids) => setExperimentalEnabled(ids),
|
|
12
|
+
env: process.env,
|
|
13
|
+
log: (line) => console.log(line),
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
function requireId(id, verb) {
|
|
17
|
+
if (!id)
|
|
18
|
+
throw new Error(`mnemom experimental ${verb}: an <id> is required.`);
|
|
19
|
+
const v = id.trim().toLowerCase();
|
|
20
|
+
if (!isExperimentalId(v)) {
|
|
21
|
+
throw new Error(`Unknown experimental feature '${id}'. Valid ids: ${EXPERIMENTAL_IDS.join(", ")}.`);
|
|
22
|
+
}
|
|
23
|
+
return v;
|
|
24
|
+
}
|
|
25
|
+
export function experimentalListCommand(opts = {}, deps = defaultDeps()) {
|
|
26
|
+
const state = resolveExperimental({ stored: deps.stored(), env: deps.env });
|
|
27
|
+
const rows = EXPERIMENTAL_FEATURES.map((f) => ({
|
|
28
|
+
id: f.id,
|
|
29
|
+
enabled: state.enabled.includes(f.id),
|
|
30
|
+
summary: f.summary,
|
|
31
|
+
since: f.since,
|
|
32
|
+
gates: f.gates,
|
|
33
|
+
}));
|
|
34
|
+
if (opts.json) {
|
|
35
|
+
deps.log(JSON.stringify({ contract: EXPERIMENTAL_CONTRACT, source: state.source, features: rows }, null, 2));
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
deps.log(EXPERIMENTAL_CONTRACT);
|
|
39
|
+
deps.log("");
|
|
40
|
+
const width = Math.max(...rows.map((r) => r.id.length));
|
|
41
|
+
for (const r of rows) {
|
|
42
|
+
deps.log(`${r.enabled ? "on " : "off"} ${r.id.padEnd(width)} ${r.summary}`);
|
|
43
|
+
}
|
|
44
|
+
deps.log("");
|
|
45
|
+
if (state.source === "env") {
|
|
46
|
+
deps.log(`(set by ${EXPERIMENTAL_ENV} in your environment — it overrides config.json)`);
|
|
47
|
+
}
|
|
48
|
+
deps.log("Turn one on with `mnemom experimental enable <id>`.");
|
|
49
|
+
}
|
|
50
|
+
function toggle(verb, rawId, deps) {
|
|
51
|
+
const id = requireId(rawId, verb);
|
|
52
|
+
const current = new Set(resolveExperimental({ stored: deps.stored(), env: {} }).enabled);
|
|
53
|
+
if (verb === "enable")
|
|
54
|
+
current.add(id);
|
|
55
|
+
else
|
|
56
|
+
current.delete(id);
|
|
57
|
+
const next = EXPERIMENTAL_IDS.filter((x) => current.has(x));
|
|
58
|
+
deps.persist(next);
|
|
59
|
+
deps.log(verb === "enable" ? `${id}: on. ${EXPERIMENTAL_CONTRACT}` : `${id}: off.`);
|
|
60
|
+
if ((deps.env[EXPERIMENTAL_ENV] ?? "").trim()) {
|
|
61
|
+
deps.log(`note: ${EXPERIMENTAL_ENV} is set in your environment and overrides this.`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
export function experimentalEnableCommand(id, deps = defaultDeps()) {
|
|
65
|
+
toggle("enable", id, deps);
|
|
66
|
+
}
|
|
67
|
+
export function experimentalDisableCommand(id, deps = defaultDeps()) {
|
|
68
|
+
toggle("disable", id, deps);
|
|
69
|
+
}
|
|
70
|
+
/** The one-time migration (lib/experimental.ts migrateExperimental) against the real files. */
|
|
71
|
+
export function runExperimentalMigration() {
|
|
72
|
+
migrateExperimental({
|
|
73
|
+
stored: () => loadCliConfig().experimental,
|
|
74
|
+
launcherEntries: () => launcherEntries(loadLauncherConfig()),
|
|
75
|
+
persist: (ids) => setExperimentalEnabled(ids),
|
|
76
|
+
writeErr: (line) => void process.stderr.write(line),
|
|
77
|
+
});
|
|
78
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -7,4 +7,4 @@ export { program };
|
|
|
7
7
|
* unknown — so it bypasses commander entirely and reaches the sub-verb parser
|
|
8
8
|
* verbatim. Exported (not auto-run) so the bypass is unit-testable.
|
|
9
9
|
*/
|
|
10
|
-
export declare function runEntrypoint(argv?: string[]): void;
|
|
10
|
+
export declare function runEntrypoint(argv?: string[], migrate?: () => void): void;
|