@mnemom/mnemom 0.17.0-next.6 → 0.17.0-next.8
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 +26 -0
- package/dist/commands/agent-config.d.ts +29 -0
- package/dist/commands/agent-config.js +193 -0
- package/dist/commands/{code-doctor.d.ts → agent-doctor.d.ts} +2 -2
- package/dist/commands/{code-doctor.js → agent-doctor.js} +27 -22
- package/dist/commands/{code-invite.d.ts → agent-invite.d.ts} +18 -18
- package/dist/commands/{code-invite.js → agent-invite.js} +30 -30
- package/dist/commands/agent-session.d.ts +68 -0
- package/dist/commands/agent-session.js +114 -0
- package/dist/commands/{code-setup.d.ts → agent-setup.d.ts} +13 -13
- package/dist/commands/{code-setup.js → agent-setup.js} +46 -42
- package/dist/commands/{code.d.ts → agent.d.ts} +50 -14
- package/dist/commands/{code.js → agent.js} +144 -86
- package/dist/commands/agents.d.ts +2 -2
- package/dist/commands/agents.js +5 -5
- package/dist/commands/onboard.js +1 -1
- package/dist/commands/wrap.js +1 -1
- package/dist/index.js +59 -51
- package/dist/lib/{code.d.ts → agent.d.ts} +52 -10
- package/dist/lib/{code.js → agent.js} +94 -15
- package/dist/lib/api.d.ts +4 -4
- package/dist/lib/api.js +9 -9
- package/dist/lib/auth.js +21 -1
- package/dist/lib/{code-config.d.ts → launcher-config.d.ts} +25 -23
- package/dist/lib/{code-config.js → launcher-config.js} +50 -31
- package/dist/lib/oauth.d.ts +26 -4
- package/dist/lib/oauth.js +98 -29
- package/dist/lib/version-gate.js +1 -1
- package/dist/rc-proxy.mjs +309 -136
- package/package.json +7 -3
- package/dist/commands/code-config.d.ts +0 -17
- package/dist/commands/code-config.js +0 -147
- package/dist/commands/code-session.d.ts +0 -52
- package/dist/commands/code-session.js +0 -78
package/dist/index.js
CHANGED
|
@@ -20,12 +20,13 @@ import { apiKeyListCommand, apiKeyCreateCommand, apiKeyRotateCommand, apiKeyRevo
|
|
|
20
20
|
import { webhooksListCommand, webhooksGetCommand, webhooksCreateCommand, webhooksUpdateCommand, webhooksDeleteCommand, webhooksRotateSecretCommand, webhooksTriggerCommand, webhooksListDeliveriesCommand, webhooksRedeliverCommand, webhooksReplayCommand, } from "./commands/webhooks.js";
|
|
21
21
|
import { listenCommand } from "./commands/listen.js";
|
|
22
22
|
import { tryMeCommand } from "./commands/try-me.js";
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
26
|
-
import {
|
|
27
|
-
import {
|
|
28
|
-
import { applyConfigToEnv,
|
|
23
|
+
import { agentCommand } from "./commands/agent.js";
|
|
24
|
+
import { agentConfigCommand } from "./commands/agent-config.js";
|
|
25
|
+
import { agentDoctorCommand } from "./commands/agent-doctor.js";
|
|
26
|
+
import { agentSetupCommand } from "./commands/agent-setup.js";
|
|
27
|
+
import { agentInviteCommand, runAgentInvocation } from "./commands/agent-invite.js";
|
|
28
|
+
import { applyConfigToEnv, loadLauncherConfig } from "./lib/launcher-config.js";
|
|
29
|
+
import { resolveImplicit } from "./lib/agent.js";
|
|
29
30
|
import { wrapCommand } from "./commands/wrap.js";
|
|
30
31
|
import { onboardCommand } from "./commands/onboard.js";
|
|
31
32
|
import { skillsListCommand, skillsDescribeCommand } from "./commands/skills.js";
|
|
@@ -82,7 +83,7 @@ program
|
|
|
82
83
|
});
|
|
83
84
|
// ── code — first-class governed coding-agent launcher ────────────────────────
|
|
84
85
|
//
|
|
85
|
-
// `mnemom
|
|
86
|
+
// `mnemom agent [session]` launches your coding-agent CLI (Claude Code in v1) with
|
|
86
87
|
// its dev-time model traffic routed through the Mnemom gateway's /anthropic door
|
|
87
88
|
// for observability + governance, under a governed agent identity (the
|
|
88
89
|
// program-level `--agent`, default "code-agent") and an optional per-conversation
|
|
@@ -90,7 +91,7 @@ program
|
|
|
90
91
|
// (default), --remote-control (interactive RC in this terminal), --server
|
|
91
92
|
// (headless RC dispatcher) — with yolo on by default. `--cli` selects the
|
|
92
93
|
// executable (name or full path; default claude). The us-2/prod door is the
|
|
93
|
-
// DEFAULT; set
|
|
94
|
+
// DEFAULT; set MNEMOM_AGENT_GATEWAY (or --gateway) to point elsewhere. The
|
|
94
95
|
// Anthropic key is resolved with zero 1Password dependency (env → ~/.mnemom store
|
|
95
96
|
// → interactive prompt) inside the launched process and never logged. See
|
|
96
97
|
// commands/code.ts.
|
|
@@ -99,10 +100,10 @@ const collectRepeatable = (value, previous) => {
|
|
|
99
100
|
previous.push(value);
|
|
100
101
|
return previous;
|
|
101
102
|
};
|
|
102
|
-
program
|
|
103
|
-
.command("
|
|
104
|
-
.description("Launch
|
|
105
|
-
"Sub-verbs: `
|
|
103
|
+
const agentCmd = program
|
|
104
|
+
.command("agent [session] [passthrough...]")
|
|
105
|
+
.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 and an optional goal. " +
|
|
106
|
+
"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.")
|
|
106
107
|
.option("--goal <statement>", "Contract goal statement (required if any contract flag is given)")
|
|
107
108
|
.option("--requirement <text>", "Contract requirement (repeatable)", collectRepeatable, [])
|
|
108
109
|
.option("--allow <glob>", "Allowed path/glob (repeatable)", collectRepeatable, [])
|
|
@@ -111,9 +112,11 @@ program
|
|
|
111
112
|
.option("--budget <usd>", "Guardrail: spend ceiling in USD (positive number)")
|
|
112
113
|
.option("--max-turns <n>", "Guardrail: turn ceiling (positive integer)")
|
|
113
114
|
.option("--stall <n>", "Guardrail: consecutive no-write turns (positive integer)")
|
|
115
|
+
.option("--implicit", "Goal alignment without a sealed contract: the gateway infers the goal from your messages and revises it as you steer (not combinable with --goal/--requirement/--allow/--forbid/--goal-id; the guardrail ceilings still apply)")
|
|
116
|
+
.option("--no-implicit", "Goal alignment off for this launch even when launcher.toml sets goal_mode = implicit")
|
|
114
117
|
.option("--conversation-id <id>", "x-mnemom-conversation-id (default: auto-generated)")
|
|
115
118
|
.option("--cli <name|path>", "Coding-agent CLI to launch: a name or full path (default: claude)")
|
|
116
|
-
.option("--gateway <url>", "Gateway host override (default: https://gateway.mnemom.ai; or
|
|
119
|
+
.option("--gateway <url>", "Gateway host override (default: https://gateway.mnemom.ai; or MNEMOM_AGENT_GATEWAY)")
|
|
117
120
|
.option("--remote-control", "Interactive Remote Control session in THIS terminal (behind a local proxy)")
|
|
118
121
|
.option("--server", "Headless `claude remote-control` dispatcher (behind a local proxy)")
|
|
119
122
|
.option("--model <alias|id>", "Model to launch (CLI alias or concrete id)")
|
|
@@ -125,7 +128,7 @@ program
|
|
|
125
128
|
"max",
|
|
126
129
|
]))
|
|
127
130
|
.option("--trim-mcp", "Launch the CLI with no MCP servers (--strict-mcp-config + empty --mcp-config)")
|
|
128
|
-
.option("--invite <token>", "Redeem a Mnemom
|
|
131
|
+
.option("--invite <token>", "Redeem a Mnemom Agent invitation (pinv_…): unlocks Mnemom Agent for you + credits your starter µ; launches too when a session name is given")
|
|
129
132
|
.option("--setup", "Provision + claim the governed agent before launch (non-interactive opt-in)")
|
|
130
133
|
.option("--no-setup", "Skip governed-agent provisioning entirely for this launch")
|
|
131
134
|
.option("--no-yolo", "Do NOT pass --dangerously-skip-permissions (yolo is on by default)")
|
|
@@ -133,58 +136,71 @@ program
|
|
|
133
136
|
.option("--dry-run", "Print the resolved launch plan and exit — no key read, no spawn")
|
|
134
137
|
.action(async (scenario, passthrough, opts) => {
|
|
135
138
|
try {
|
|
136
|
-
// Invitation redemption — `mnemom
|
|
137
|
-
// `mnemom
|
|
138
|
-
//
|
|
139
|
+
// Invitation redemption — `mnemom agent invite <token>` (sub-verb) or
|
|
140
|
+
// `mnemom agent --invite <token> [session]` — composes with the launch in
|
|
141
|
+
// runAgentInvocation (commands/code-invite.ts, unit-tested); everything below
|
|
139
142
|
// is the launch half, running only when a real scenario was given.
|
|
140
|
-
await
|
|
143
|
+
await runAgentInvocation({ scenario, passthrough, inviteFlag: opts.invite }, {
|
|
144
|
+
redeem: (token) => agentInviteCommand(token),
|
|
145
|
+
launch: (scenario) => launchAgent(scenario),
|
|
146
|
+
});
|
|
141
147
|
}
|
|
142
148
|
catch (error) {
|
|
143
149
|
console.error("Error:", error instanceof Error ? error.message : error);
|
|
144
150
|
process.exit(1);
|
|
145
151
|
}
|
|
146
|
-
async function
|
|
147
|
-
// Sub-verb: `mnemom
|
|
152
|
+
async function launchAgent(scenario) {
|
|
153
|
+
// Sub-verb: `mnemom agent config …` manages persistent settings and never
|
|
148
154
|
// launches. (A scenario literally named "config" is reserved for this.)
|
|
149
155
|
if (scenario === "config") {
|
|
150
|
-
await
|
|
156
|
+
await agentConfigCommand(passthrough);
|
|
151
157
|
return;
|
|
152
158
|
}
|
|
153
159
|
if (opts.gateway)
|
|
154
|
-
process.env.
|
|
155
|
-
// Persistent settings (~/.mnemom/
|
|
156
|
-
// precedence is flag > env >
|
|
160
|
+
process.env.MNEMOM_AGENT_GATEWAY = opts.gateway;
|
|
161
|
+
// Persistent settings (~/.mnemom/launcher.toml) sit UNDER env and flags:
|
|
162
|
+
// precedence is flag > env > launcher.toml > built-in. Env-backed settings are
|
|
157
163
|
// folded into env only when unset (so env/flags still win); the resolvers
|
|
158
164
|
// in lib/code.ts keep the built-in default as the final fallback.
|
|
159
|
-
const cfg =
|
|
165
|
+
const cfg = loadLauncherConfig();
|
|
160
166
|
applyConfigToEnv(cfg, process.env);
|
|
161
|
-
// Sub-verb: `mnemom
|
|
167
|
+
// Sub-verb: `mnemom agent doctor` runs a read-only preflight and never
|
|
162
168
|
// launches. Placed after config load so it reports the effective settings.
|
|
163
169
|
if (scenario === "doctor") {
|
|
164
|
-
await
|
|
170
|
+
await agentDoctorCommand({ cli: opts.cli });
|
|
165
171
|
return;
|
|
166
172
|
}
|
|
167
|
-
// Sub-verb: `mnemom
|
|
173
|
+
// Sub-verb: `mnemom agent setup` provisions/claims the governed agent and
|
|
168
174
|
// never launches. The agent slug follows the same precedence as a launch.
|
|
169
175
|
if (scenario === "setup") {
|
|
170
|
-
await
|
|
176
|
+
await agentSetupCommand({ agent: program.opts().agent ?? cfg.agent });
|
|
171
177
|
return;
|
|
172
178
|
}
|
|
173
179
|
const numToStr = (n) => n === undefined ? undefined : String(n);
|
|
174
|
-
//
|
|
175
|
-
//
|
|
176
|
-
//
|
|
177
|
-
const
|
|
180
|
+
// Implicit mode: --implicit / --no-implicit win; a --goal statement seals
|
|
181
|
+
// (the standing config default never overrides it); else launcher.toml
|
|
182
|
+
// `goal_mode`; else off. Pure + unit-tested in lib/agent.ts.
|
|
183
|
+
const implicit = resolveImplicit({
|
|
184
|
+
goal: opts.goal,
|
|
185
|
+
implicit: opts.implicit,
|
|
186
|
+
configGoalMode: cfg.goalMode,
|
|
187
|
+
});
|
|
188
|
+
// Config guardrails apply only when goal alignment is actually intended (a
|
|
189
|
+
// --goal is present, or implicit mode — where they ride on the mode header),
|
|
190
|
+
// so a saved ceiling never forces an unrequested contract (which would
|
|
191
|
+
// then demand --goal and fail the launch).
|
|
192
|
+
const contractIntended = Boolean(opts.goal && opts.goal.trim()) || implicit;
|
|
178
193
|
// The governed identity rides on the program-level `--agent` (which
|
|
179
194
|
// shadows a subcommand `--agent` under commander@12 — the MNE-238 class),
|
|
180
195
|
// exactly as status/logs/onboard read it.
|
|
181
196
|
const parentOpts = program.opts();
|
|
182
|
-
await
|
|
197
|
+
await agentCommand(scenario, {
|
|
183
198
|
goal: opts.goal,
|
|
184
199
|
requirement: opts.requirement,
|
|
185
200
|
allow: opts.allow,
|
|
186
201
|
forbid: opts.forbid,
|
|
187
202
|
goalId: opts.goalId,
|
|
203
|
+
implicit,
|
|
188
204
|
budget: opts.budget ?? (contractIntended ? numToStr(cfg.guardrails?.budgetUsd) : undefined),
|
|
189
205
|
maxTurns: opts.maxTurns ?? (contractIntended ? numToStr(cfg.guardrails?.maxTurns) : undefined),
|
|
190
206
|
stall: opts.stall ?? (contractIntended ? numToStr(cfg.guardrails?.stallTurns) : undefined),
|
|
@@ -625,10 +641,10 @@ policyCmd
|
|
|
625
641
|
await policyEvaluateCommand(file, options);
|
|
626
642
|
});
|
|
627
643
|
// ============================================================================
|
|
628
|
-
// Agent management
|
|
644
|
+
// Agent management — sub-verbs of `mnemom agent` (the launcher above): list / claim / move.
|
|
629
645
|
// ============================================================================
|
|
630
|
-
|
|
631
|
-
.command("
|
|
646
|
+
agentCmd
|
|
647
|
+
.command("list")
|
|
632
648
|
.description("List agents across the orgs you belong to (ADR-062 org-scoped)")
|
|
633
649
|
.option("--org <id>", "Scope to a single org (default: all orgs you belong to)")
|
|
634
650
|
.action(async (opts) => {
|
|
@@ -640,7 +656,7 @@ const agentsCmd = program
|
|
|
640
656
|
process.exit(1);
|
|
641
657
|
}
|
|
642
658
|
});
|
|
643
|
-
|
|
659
|
+
agentCmd
|
|
644
660
|
.command("claim <id-or-name>")
|
|
645
661
|
.description("Claim an agent into an org (ADR-062 claim-to-org)")
|
|
646
662
|
.option("--org <slug>", "Org slug or id to claim into (default: your ACTIVE org from `mnemom org use`, else your personal org)")
|
|
@@ -650,18 +666,10 @@ agentsCmd
|
|
|
650
666
|
.option("--json", "Output JSON instead of human-readable text")
|
|
651
667
|
.action(async (idOrName, opts) => {
|
|
652
668
|
try {
|
|
653
|
-
//
|
|
654
|
-
//
|
|
655
|
-
//
|
|
656
|
-
|
|
657
|
-
// `agents claim <id> --org <slug>` had its --org silently swallowed by
|
|
658
|
-
// `agentsCmd` before this subcommand's own `--org` option ever saw it
|
|
659
|
-
// (MNE-2496: confirmed live, the agent always landed in the caller's
|
|
660
|
-
// personal org regardless of --org). Same shadow class as the global
|
|
661
|
-
// `--agent` fix at MNE-238 (see the `advisories list`/`show` actions
|
|
662
|
-
// below) — resolve the local value first, falling back to the
|
|
663
|
-
// parent's captured value so either parse order still works.
|
|
664
|
-
const org = opts.org ?? agentsCmd.opts().org;
|
|
669
|
+
// The parent `agent` command defines no `--org`, so the MNE-2496 shadow
|
|
670
|
+
// (a parent-owned flag swallowing the subcommand.s) cannot recur here;
|
|
671
|
+
// `list` owns its own `--org` as a sibling sub-verb.
|
|
672
|
+
const org = opts.org;
|
|
665
673
|
await agentsClaimCommand(idOrName, {
|
|
666
674
|
org,
|
|
667
675
|
key: opts.key,
|
|
@@ -675,7 +683,7 @@ agentsCmd
|
|
|
675
683
|
process.exit(1);
|
|
676
684
|
}
|
|
677
685
|
});
|
|
678
|
-
|
|
686
|
+
agentCmd
|
|
679
687
|
.command("move <id-or-name>")
|
|
680
688
|
.description("Move an agent to another org — requires owner/admin in BOTH the current and destination org " +
|
|
681
689
|
"(role-based; no agent key needed, unlike re-claim)")
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* lib/code.ts — the pure, IO-free core of `mnemom
|
|
2
|
+
* lib/code.ts — the pure, IO-free core of `mnemom agent`, the CLI's first-class
|
|
3
3
|
* governed-coding-agent launcher.
|
|
4
4
|
*
|
|
5
|
-
* `mnemom
|
|
5
|
+
* `mnemom agent` launches your coding-agent CLI (Claude Code in v1) with its
|
|
6
6
|
* dev-time model traffic routed through the Mnemom gateway for observability and
|
|
7
7
|
* governance, under a governed agent identity and (optionally) a governed
|
|
8
8
|
* per-conversation CONTRACT (goal / requirements / path allow-forbid lists +
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* key resolution, env assembly, and spawning the coding-agent CLI.
|
|
17
17
|
*
|
|
18
18
|
* The us-2/PROD gateway is the DEFAULT door (https://gateway.mnemom.ai). An
|
|
19
|
-
* explicit
|
|
19
|
+
* explicit MNEMOM_AGENT_GATEWAY still wins (so any other cell is reachable when
|
|
20
20
|
* NAMED), but prod is never selected silently — see `resolveGatewayHost`.
|
|
21
21
|
*/
|
|
22
22
|
/** The us-2/prod gateway host. The default door — never any other cell silently. */
|
|
@@ -48,20 +48,28 @@ export interface ContractFlags {
|
|
|
48
48
|
maxTurns?: string | number;
|
|
49
49
|
budget?: string | number;
|
|
50
50
|
stall?: string | number;
|
|
51
|
+
/**
|
|
52
|
+
* `--implicit`: goal alignment WITHOUT a sealed contract. No `x-mnemom-contract`
|
|
53
|
+
* is sent; the gateway infers the goal from the user's own messages (revision 1
|
|
54
|
+
* after the first message) and revises it as the user steers. The guardrail
|
|
55
|
+
* ceilings still apply — they ride on the `x-mnemom-goal-mode` header instead
|
|
56
|
+
* of inside a contract. Cannot be combined with the sealed-contract flags.
|
|
57
|
+
*/
|
|
58
|
+
implicit?: boolean;
|
|
51
59
|
}
|
|
52
60
|
/** The three launch shapes the command supports (see commands/code.ts). */
|
|
53
61
|
export type LaunchShape = "terminal" | "remote-control" | "server";
|
|
54
62
|
/**
|
|
55
63
|
* Resolve the gateway HOST (scheme + host, no path). An explicit, non-empty
|
|
56
|
-
* `
|
|
64
|
+
* `MNEMOM_AGENT_GATEWAY` wins verbatim; otherwise the us-2/prod host. The default
|
|
57
65
|
* is PROD — an unset var resolves to prod, and any other cell is reached only by
|
|
58
66
|
* NAMING it. Trailing slash trimmed so door concatenation is clean.
|
|
59
67
|
*/
|
|
60
68
|
export declare function resolveGatewayHost(env?: NodeJS.ProcessEnv): string;
|
|
61
69
|
/**
|
|
62
70
|
* The Anthropic door the CLI points Claude Code at:
|
|
63
|
-
* `${
|
|
64
|
-
* `
|
|
71
|
+
* `${MNEMOM_AGENT_GATEWAY}/anthropic`, or the full-door override
|
|
72
|
+
* `MNEMOM_AGENT_ANTHROPIC_GATEWAY` when set. v1 is Anthropic-only (no router door
|
|
65
73
|
* / Mnemom key), so the /anthropic door carries the whole contract: x-api-key +
|
|
66
74
|
* x-mnemom-{agent,conversation-id,contract}.
|
|
67
75
|
*/
|
|
@@ -116,11 +124,20 @@ export interface SealedContractEnvelope {
|
|
|
116
124
|
* here matches whatever the gateway seals for the same flags.
|
|
117
125
|
*/
|
|
118
126
|
export declare function assembleContract(flags: ContractFlags): SealedContractEnvelope | null;
|
|
127
|
+
/** The request header that opts a conversation into implicit goal mode. */
|
|
128
|
+
export declare const GOAL_MODE_HEADER = "x-mnemom-goal-mode";
|
|
119
129
|
/**
|
|
120
|
-
*
|
|
130
|
+
* The `x-mnemom-goal-mode` header value for `--implicit`, or null when the flag
|
|
131
|
+
* is absent: `implicit[; max_turns=N][; budget_usd=X][; stall_turns=N]` — the
|
|
132
|
+
* guardrail ceilings validated exactly as `assembleContract` validates them (a
|
|
133
|
+
* non-positive ceiling is refused, never silently dropped into an unguarded run).
|
|
134
|
+
*/
|
|
135
|
+
export declare function buildGoalModeHeader(flags: ContractFlags): string | null;
|
|
136
|
+
/**
|
|
137
|
+
* Resolve the launch shape from the flags and the `MNEMOM_AGENT_REMOTE_CONTROL`
|
|
121
138
|
* env var (the flags win). Default is `terminal` — a plain governed launch in
|
|
122
139
|
* THIS terminal. `--remote-control` → `remote-control` (interactive RC in this
|
|
123
|
-
* terminal); `--server` (or `
|
|
140
|
+
* terminal); `--server` (or `MNEMOM_AGENT_REMOTE_CONTROL=server`) → `server` (the
|
|
124
141
|
* headless `claude remote-control` dispatcher). Throws on an invalid env value.
|
|
125
142
|
*/
|
|
126
143
|
export declare function resolveLaunchShape(opts: {
|
|
@@ -129,7 +146,7 @@ export declare function resolveLaunchShape(opts: {
|
|
|
129
146
|
}, env?: NodeJS.ProcessEnv): LaunchShape;
|
|
130
147
|
/**
|
|
131
148
|
* Did anyone EXPLICITLY choose a launch shape — a `--remote-control`/`--server`
|
|
132
|
-
* flag, or a non-empty `
|
|
149
|
+
* flag, or a non-empty `MNEMOM_AGENT_REMOTE_CONTROL` (which config.launch also
|
|
133
150
|
* sets)? When false, the command is free to AUTO-select the best shape: prefer
|
|
134
151
|
* Remote-Control-in-terminal when the machine can do it, else terminal-only. An
|
|
135
152
|
* explicit `terminal` choice (config `launch = "terminal"` → env "0") is
|
|
@@ -142,7 +159,8 @@ export declare function launchPreferenceExplicit(opts: {
|
|
|
142
159
|
/**
|
|
143
160
|
* Build the `ANTHROPIC_CUSTOM_HEADERS` value for the terminal-only launch: the
|
|
144
161
|
* real Anthropic key as `x-api-key`, plus the governed-identity headers and the
|
|
145
|
-
* sealed contract when present
|
|
162
|
+
* sealed contract when present (or the implicit goal-mode header when the launch
|
|
163
|
+
* is `--implicit` — never both). Newline-separated, exactly the wire shape Claude
|
|
146
164
|
* Code sends to the /anthropic door. NB: the returned string contains the key —
|
|
147
165
|
* callers must place it only in the child's env, never in a log line.
|
|
148
166
|
*/
|
|
@@ -151,4 +169,28 @@ export declare function buildCustomHeaders(opts: {
|
|
|
151
169
|
agent: string;
|
|
152
170
|
conversationId: string;
|
|
153
171
|
contractB64?: string;
|
|
172
|
+
/** `buildGoalModeHeader` output for an `--implicit` launch. */
|
|
173
|
+
goalMode?: string;
|
|
154
174
|
}): string;
|
|
175
|
+
/**
|
|
176
|
+
* The persistable goal-alignment default (`launcher.toml` `goal_mode`). Only the
|
|
177
|
+
* two statement-free modes can be a standing default: a SEALED contract needs a
|
|
178
|
+
* per-session statement, so it is always a per-launch `--goal`.
|
|
179
|
+
*/
|
|
180
|
+
export type GoalModeDefault = "implicit" | "off";
|
|
181
|
+
/**
|
|
182
|
+
* Is this launch implicit-mode? Precedence, highest wins:
|
|
183
|
+
* 1. an explicit flag — `--implicit` (true) or `--no-implicit` (false);
|
|
184
|
+
* 2. a `--goal` statement — a sealed contract is a per-launch choice that the
|
|
185
|
+
* standing config default must never silently override;
|
|
186
|
+
* 3. the config default `goal_mode = "implicit"`;
|
|
187
|
+
* 4. off.
|
|
188
|
+
* Pure so every arm is unit-tested; the caller passes the resolved value on to
|
|
189
|
+
* `assembleContract` / `buildGoalModeHeader` (which still refuse `--implicit`
|
|
190
|
+
* combined with a contract flag — that conflict is EXPLICIT and is reported).
|
|
191
|
+
*/
|
|
192
|
+
export declare function resolveImplicit(opts: {
|
|
193
|
+
goal?: string;
|
|
194
|
+
implicit?: boolean;
|
|
195
|
+
configGoalMode?: GoalModeDefault;
|
|
196
|
+
}): boolean;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* lib/code.ts — the pure, IO-free core of `mnemom
|
|
2
|
+
* lib/code.ts — the pure, IO-free core of `mnemom agent`, the CLI's first-class
|
|
3
3
|
* governed-coding-agent launcher.
|
|
4
4
|
*
|
|
5
|
-
* `mnemom
|
|
5
|
+
* `mnemom agent` launches your coding-agent CLI (Claude Code in v1) with its
|
|
6
6
|
* dev-time model traffic routed through the Mnemom gateway for observability and
|
|
7
7
|
* governance, under a governed agent identity and (optionally) a governed
|
|
8
8
|
* per-conversation CONTRACT (goal / requirements / path allow-forbid lists +
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* key resolution, env assembly, and spawning the coding-agent CLI.
|
|
17
17
|
*
|
|
18
18
|
* The us-2/PROD gateway is the DEFAULT door (https://gateway.mnemom.ai). An
|
|
19
|
-
* explicit
|
|
19
|
+
* explicit MNEMOM_AGENT_GATEWAY still wins (so any other cell is reachable when
|
|
20
20
|
* NAMED), but prod is never selected silently — see `resolveGatewayHost`.
|
|
21
21
|
*/
|
|
22
22
|
import { createHash, randomUUID } from "node:crypto";
|
|
@@ -24,24 +24,24 @@ import { createHash, randomUUID } from "node:crypto";
|
|
|
24
24
|
export const PROD_GATEWAY_HOST = "https://gateway.mnemom.ai";
|
|
25
25
|
/**
|
|
26
26
|
* Resolve the gateway HOST (scheme + host, no path). An explicit, non-empty
|
|
27
|
-
* `
|
|
27
|
+
* `MNEMOM_AGENT_GATEWAY` wins verbatim; otherwise the us-2/prod host. The default
|
|
28
28
|
* is PROD — an unset var resolves to prod, and any other cell is reached only by
|
|
29
29
|
* NAMING it. Trailing slash trimmed so door concatenation is clean.
|
|
30
30
|
*/
|
|
31
31
|
export function resolveGatewayHost(env = process.env) {
|
|
32
|
-
const raw = (env.
|
|
32
|
+
const raw = (env.MNEMOM_AGENT_GATEWAY ?? "").trim();
|
|
33
33
|
const host = raw.length > 0 ? raw : PROD_GATEWAY_HOST;
|
|
34
34
|
return host.replace(/\/+$/, "");
|
|
35
35
|
}
|
|
36
36
|
/**
|
|
37
37
|
* The Anthropic door the CLI points Claude Code at:
|
|
38
|
-
* `${
|
|
39
|
-
* `
|
|
38
|
+
* `${MNEMOM_AGENT_GATEWAY}/anthropic`, or the full-door override
|
|
39
|
+
* `MNEMOM_AGENT_ANTHROPIC_GATEWAY` when set. v1 is Anthropic-only (no router door
|
|
40
40
|
* / Mnemom key), so the /anthropic door carries the whole contract: x-api-key +
|
|
41
41
|
* x-mnemom-{agent,conversation-id,contract}.
|
|
42
42
|
*/
|
|
43
43
|
export function resolveAnthropicDoor(env = process.env) {
|
|
44
|
-
const override = (env.
|
|
44
|
+
const override = (env.MNEMOM_AGENT_ANTHROPIC_GATEWAY ?? "").trim();
|
|
45
45
|
if (override.length > 0)
|
|
46
46
|
return override.replace(/\/+$/, "");
|
|
47
47
|
return `${resolveGatewayHost(env)}/anthropic`;
|
|
@@ -132,6 +132,12 @@ function hasText(v) {
|
|
|
132
132
|
* here matches whatever the gateway seals for the same flags.
|
|
133
133
|
*/
|
|
134
134
|
export function assembleContract(flags) {
|
|
135
|
+
// Implicit goal mode seals nothing: the ceilings ride on the mode header
|
|
136
|
+
// (`buildGoalModeHeader`), so a `--budget` alone must not demand `--goal` here.
|
|
137
|
+
if (flags.implicit) {
|
|
138
|
+
assertImplicitCompatible(flags);
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
135
141
|
if (!contractFlagsPresent(flags))
|
|
136
142
|
return null;
|
|
137
143
|
const statement = (flags.goal ?? "").trim();
|
|
@@ -178,21 +184,73 @@ export function assembleContract(flags) {
|
|
|
178
184
|
sha: sha256Hex16(canonical),
|
|
179
185
|
};
|
|
180
186
|
}
|
|
187
|
+
/** The request header that opts a conversation into implicit goal mode. */
|
|
188
|
+
export const GOAL_MODE_HEADER = "x-mnemom-goal-mode";
|
|
189
|
+
const SEALED_ONLY_FLAGS = "--goal/--requirement/--allow/--forbid/--goal-id";
|
|
190
|
+
/**
|
|
191
|
+
* `--implicit` and a sealed contract are mutually exclusive: on the gateway a
|
|
192
|
+
* sealed `x-mnemom-contract` always wins and would silently switch implicit mode
|
|
193
|
+
* off, so the launch refuses the combination rather than ship a header that does
|
|
194
|
+
* nothing.
|
|
195
|
+
*/
|
|
196
|
+
function assertImplicitCompatible(flags) {
|
|
197
|
+
const sealed = hasText(flags.goal) ||
|
|
198
|
+
(flags.requirement?.length ?? 0) > 0 ||
|
|
199
|
+
(flags.allow?.length ?? 0) > 0 ||
|
|
200
|
+
(flags.forbid?.length ?? 0) > 0 ||
|
|
201
|
+
hasText(flags.goalId);
|
|
202
|
+
if (sealed) {
|
|
203
|
+
throw new Error(`--implicit cannot be combined with ${SEALED_ONLY_FLAGS}: a sealed contract always wins on ` +
|
|
204
|
+
"the gateway and would silently switch implicit mode off. Drop --implicit to seal a " +
|
|
205
|
+
"contract, or drop the contract flags to let the gateway infer the goal from your messages.");
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* The `x-mnemom-goal-mode` header value for `--implicit`, or null when the flag
|
|
210
|
+
* is absent: `implicit[; max_turns=N][; budget_usd=X][; stall_turns=N]` — the
|
|
211
|
+
* guardrail ceilings validated exactly as `assembleContract` validates them (a
|
|
212
|
+
* non-positive ceiling is refused, never silently dropped into an unguarded run).
|
|
213
|
+
*/
|
|
214
|
+
export function buildGoalModeHeader(flags) {
|
|
215
|
+
if (!flags.implicit)
|
|
216
|
+
return null;
|
|
217
|
+
assertImplicitCompatible(flags);
|
|
218
|
+
const parts = ["implicit"];
|
|
219
|
+
if (flags.maxTurns !== undefined) {
|
|
220
|
+
if (!isPositiveInt(flags.maxTurns)) {
|
|
221
|
+
throw new Error(`--max-turns must be a positive integer (got '${flags.maxTurns}').`);
|
|
222
|
+
}
|
|
223
|
+
parts.push(`max_turns=${Number(flags.maxTurns)}`);
|
|
224
|
+
}
|
|
225
|
+
if (flags.budget !== undefined) {
|
|
226
|
+
if (!isPositiveNumber(flags.budget)) {
|
|
227
|
+
throw new Error(`--budget must be a positive USD amount (got '${flags.budget}').`);
|
|
228
|
+
}
|
|
229
|
+
parts.push(`budget_usd=${Number(flags.budget)}`);
|
|
230
|
+
}
|
|
231
|
+
if (flags.stall !== undefined) {
|
|
232
|
+
if (!isPositiveInt(flags.stall)) {
|
|
233
|
+
throw new Error(`--stall must be a positive integer (got '${flags.stall}').`);
|
|
234
|
+
}
|
|
235
|
+
parts.push(`stall_turns=${Number(flags.stall)}`);
|
|
236
|
+
}
|
|
237
|
+
return parts.join("; ");
|
|
238
|
+
}
|
|
181
239
|
/** Trim, drop empties — matches the gateway's `stringArray` filtering. */
|
|
182
240
|
function cleanList(values) {
|
|
183
241
|
return (values ?? []).map((v) => v.trim()).filter((v) => v.length > 0);
|
|
184
242
|
}
|
|
185
243
|
/**
|
|
186
|
-
* Resolve the launch shape from the flags and the `
|
|
244
|
+
* Resolve the launch shape from the flags and the `MNEMOM_AGENT_REMOTE_CONTROL`
|
|
187
245
|
* env var (the flags win). Default is `terminal` — a plain governed launch in
|
|
188
246
|
* THIS terminal. `--remote-control` → `remote-control` (interactive RC in this
|
|
189
|
-
* terminal); `--server` (or `
|
|
247
|
+
* terminal); `--server` (or `MNEMOM_AGENT_REMOTE_CONTROL=server`) → `server` (the
|
|
190
248
|
* headless `claude remote-control` dispatcher). Throws on an invalid env value.
|
|
191
249
|
*/
|
|
192
250
|
export function resolveLaunchShape(opts, env = process.env) {
|
|
193
251
|
let rc = false;
|
|
194
252
|
let server = false;
|
|
195
|
-
const envRc = (env.
|
|
253
|
+
const envRc = (env.MNEMOM_AGENT_REMOTE_CONTROL ?? "").trim();
|
|
196
254
|
switch (envRc) {
|
|
197
255
|
case "":
|
|
198
256
|
case "0":
|
|
@@ -206,7 +264,7 @@ export function resolveLaunchShape(opts, env = process.env) {
|
|
|
206
264
|
server = true;
|
|
207
265
|
break;
|
|
208
266
|
default:
|
|
209
|
-
throw new Error(`
|
|
267
|
+
throw new Error(`MNEMOM_AGENT_REMOTE_CONTROL must be 0, 1, terminal or server (got '${envRc}').`);
|
|
210
268
|
}
|
|
211
269
|
if (opts.remoteControl)
|
|
212
270
|
rc = true;
|
|
@@ -222,7 +280,7 @@ export function resolveLaunchShape(opts, env = process.env) {
|
|
|
222
280
|
}
|
|
223
281
|
/**
|
|
224
282
|
* Did anyone EXPLICITLY choose a launch shape — a `--remote-control`/`--server`
|
|
225
|
-
* flag, or a non-empty `
|
|
283
|
+
* flag, or a non-empty `MNEMOM_AGENT_REMOTE_CONTROL` (which config.launch also
|
|
226
284
|
* sets)? When false, the command is free to AUTO-select the best shape: prefer
|
|
227
285
|
* Remote-Control-in-terminal when the machine can do it, else terminal-only. An
|
|
228
286
|
* explicit `terminal` choice (config `launch = "terminal"` → env "0") is
|
|
@@ -231,12 +289,13 @@ export function resolveLaunchShape(opts, env = process.env) {
|
|
|
231
289
|
export function launchPreferenceExplicit(opts, env = process.env) {
|
|
232
290
|
if (opts.remoteControl || opts.server)
|
|
233
291
|
return true;
|
|
234
|
-
return (env.
|
|
292
|
+
return (env.MNEMOM_AGENT_REMOTE_CONTROL ?? "").trim() !== "";
|
|
235
293
|
}
|
|
236
294
|
/**
|
|
237
295
|
* Build the `ANTHROPIC_CUSTOM_HEADERS` value for the terminal-only launch: the
|
|
238
296
|
* real Anthropic key as `x-api-key`, plus the governed-identity headers and the
|
|
239
|
-
* sealed contract when present
|
|
297
|
+
* sealed contract when present (or the implicit goal-mode header when the launch
|
|
298
|
+
* is `--implicit` — never both). Newline-separated, exactly the wire shape Claude
|
|
240
299
|
* Code sends to the /anthropic door. NB: the returned string contains the key —
|
|
241
300
|
* callers must place it only in the child's env, never in a log line.
|
|
242
301
|
*/
|
|
@@ -248,5 +307,25 @@ export function buildCustomHeaders(opts) {
|
|
|
248
307
|
];
|
|
249
308
|
if (opts.contractB64)
|
|
250
309
|
lines.push(`x-mnemom-contract: ${opts.contractB64}`);
|
|
310
|
+
if (opts.goalMode)
|
|
311
|
+
lines.push(`${GOAL_MODE_HEADER}: ${opts.goalMode}`);
|
|
251
312
|
return lines.join("\n");
|
|
252
313
|
}
|
|
314
|
+
/**
|
|
315
|
+
* Is this launch implicit-mode? Precedence, highest wins:
|
|
316
|
+
* 1. an explicit flag — `--implicit` (true) or `--no-implicit` (false);
|
|
317
|
+
* 2. a `--goal` statement — a sealed contract is a per-launch choice that the
|
|
318
|
+
* standing config default must never silently override;
|
|
319
|
+
* 3. the config default `goal_mode = "implicit"`;
|
|
320
|
+
* 4. off.
|
|
321
|
+
* Pure so every arm is unit-tested; the caller passes the resolved value on to
|
|
322
|
+
* `assembleContract` / `buildGoalModeHeader` (which still refuse `--implicit`
|
|
323
|
+
* combined with a contract flag — that conflict is EXPLICIT and is reported).
|
|
324
|
+
*/
|
|
325
|
+
export function resolveImplicit(opts) {
|
|
326
|
+
if (opts.implicit !== undefined)
|
|
327
|
+
return opts.implicit;
|
|
328
|
+
if (hasText(opts.goal))
|
|
329
|
+
return false;
|
|
330
|
+
return opts.configGoalMode === "implicit";
|
|
331
|
+
}
|
package/dist/lib/api.d.ts
CHANGED
|
@@ -378,7 +378,7 @@ export declare function moveAgent(agentId: string, destOrgId: string, opts?: {
|
|
|
378
378
|
*/
|
|
379
379
|
export declare function getMyPersonalOrg(): Promise<PersonalOrgRef>;
|
|
380
380
|
/** The authenticated user's identity — `GET /v1/auth/me`. Carries the email the
|
|
381
|
-
* stored OAuth token does not, so the CLI can derive a real `
|
|
381
|
+
* stored OAuth token does not, so the CLI can derive a real `agent-<handle>`. */
|
|
382
382
|
export interface MeResponse {
|
|
383
383
|
user_id: string;
|
|
384
384
|
email?: string | null;
|
|
@@ -1204,7 +1204,7 @@ export interface OrgUsageQuery {
|
|
|
1204
1204
|
* the user "org not found" on what is most often just the flag being off.
|
|
1205
1205
|
*/
|
|
1206
1206
|
export declare function getOrgUsage(orgId: string, opts?: OrgUsageQuery): Promise<OrgUsageResponse>;
|
|
1207
|
-
/** `GET /v1/code/entitlement` 200 body — why Mnemom
|
|
1207
|
+
/** `GET /v1/code/entitlement` 200 body — why Mnemom Agent is enabled for the org. */
|
|
1208
1208
|
export interface CodeEntitlement {
|
|
1209
1209
|
org_id: string;
|
|
1210
1210
|
enabled: true;
|
|
@@ -1213,7 +1213,7 @@ export interface CodeEntitlement {
|
|
|
1213
1213
|
/** The machine-readable code on the gate's 403 when the org is not enabled. */
|
|
1214
1214
|
export declare const CODE_NOT_ENABLED = "code_not_enabled";
|
|
1215
1215
|
/**
|
|
1216
|
-
* Is Mnemom
|
|
1216
|
+
* Is Mnemom Agent enabled for `orgId` (default: the caller's primary org)?
|
|
1217
1217
|
* Resolves on 200; throws MnemomApiError with `.code === "code_not_enabled"`
|
|
1218
1218
|
* (and `.details.hint`) when the org is not enabled, or a plain MnemomApiError
|
|
1219
1219
|
* for membership/transport failures.
|
|
@@ -1229,6 +1229,6 @@ export interface AcceptProductInviteResult {
|
|
|
1229
1229
|
products: string[];
|
|
1230
1230
|
grant?: "fired" | "deferred";
|
|
1231
1231
|
}
|
|
1232
|
-
/** Redeem a `pinv_…` product invite (a Mnemom
|
|
1232
|
+
/** Redeem a `pinv_…` product invite (a Mnemom Agent invitation) for the signed-in user. */
|
|
1233
1233
|
export declare function acceptProductInvite(token: string): Promise<AcceptProductInviteResult>;
|
|
1234
1234
|
export {};
|
package/dist/lib/api.js
CHANGED
|
@@ -898,7 +898,7 @@ export async function resolveAgentId(agentName) {
|
|
|
898
898
|
const name = agentName ?? process.env.MNEMOM_AGENT;
|
|
899
899
|
if (!name) {
|
|
900
900
|
console.error("\nAgent required. Use --agent <name> or set MNEMOM_AGENT.\n");
|
|
901
|
-
console.error("List your agents with: mnemom
|
|
901
|
+
console.error("List your agents with: mnemom agent list\n");
|
|
902
902
|
process.exit(1);
|
|
903
903
|
}
|
|
904
904
|
// If it looks like an agent ID, use directly (no server call needed)
|
|
@@ -919,9 +919,9 @@ export async function resolveAgentId(agentName) {
|
|
|
919
919
|
}
|
|
920
920
|
}
|
|
921
921
|
console.error(`\nAgent not found: ${name}`);
|
|
922
|
-
console.error("List your agents with: mnemom
|
|
922
|
+
console.error("List your agents with: mnemom agent list\n");
|
|
923
923
|
console.error(`\nThis can happen when the agent was auto-created by the gateway and has not been claimed to your account yet.`);
|
|
924
|
-
console.error(`To claim it, run: mnemom
|
|
924
|
+
console.error(`To claim it, run: mnemom agent claim <agent-id> --name ${name} --key <your-provider-key>`);
|
|
925
925
|
console.error(`The <agent-id> (mnm-...) is the X-Mnemom-Agent value returned in the gateway response headers.`);
|
|
926
926
|
console.error(`See: https://docs.mnemom.ai/quickstart/gateway\n`);
|
|
927
927
|
process.exit(1);
|
|
@@ -1495,12 +1495,12 @@ export async function getOrgUsage(orgId, opts = {}) {
|
|
|
1495
1495
|
const qs = params.toString() ? `?${params.toString()}` : "";
|
|
1496
1496
|
return gFetch(`/v1/orgs/${encodeURIComponent(orgId)}/usage/by-person${qs}`, {}, { fallback: "Failed to fetch usage" });
|
|
1497
1497
|
}
|
|
1498
|
-
// ─── Mnemom
|
|
1498
|
+
// ─── Mnemom Agent — feature gate + invitation redemption ──────────────────────
|
|
1499
1499
|
//
|
|
1500
|
-
// `mnemom
|
|
1500
|
+
// `mnemom agent` is rolling out to an invited cohort. Two api calls back that:
|
|
1501
1501
|
// the per-org gate (`GET /v1/code/entitlement`) the launch preamble checks, and
|
|
1502
1502
|
// the product-invite accept (`POST /v1/invites/accept`) behind
|
|
1503
|
-
// `mnemom
|
|
1503
|
+
// `mnemom agent --invite <token>`. Both go through `codeFetch` rather than
|
|
1504
1504
|
// `gFetch`: gFetch flattens EVERY 403 into "Permission denied (need org admin /
|
|
1505
1505
|
// membership)", but the gate's 403 carries a machine-readable
|
|
1506
1506
|
// `code_not_enabled` + a hint the command layer must print verbatim, and the
|
|
@@ -1529,16 +1529,16 @@ async function codeFetch(path, init = {}, fallback) {
|
|
|
1529
1529
|
/** The machine-readable code on the gate's 403 when the org is not enabled. */
|
|
1530
1530
|
export const CODE_NOT_ENABLED = "code_not_enabled";
|
|
1531
1531
|
/**
|
|
1532
|
-
* Is Mnemom
|
|
1532
|
+
* Is Mnemom Agent enabled for `orgId` (default: the caller's primary org)?
|
|
1533
1533
|
* Resolves on 200; throws MnemomApiError with `.code === "code_not_enabled"`
|
|
1534
1534
|
* (and `.details.hint`) when the org is not enabled, or a plain MnemomApiError
|
|
1535
1535
|
* for membership/transport failures.
|
|
1536
1536
|
*/
|
|
1537
1537
|
export async function getCodeEntitlement(orgId) {
|
|
1538
1538
|
const q = orgId ? `?org_id=${encodeURIComponent(orgId)}` : "";
|
|
1539
|
-
return codeFetch(`/v1/code/entitlement${q}`, {}, "Could not check Mnemom
|
|
1539
|
+
return codeFetch(`/v1/code/entitlement${q}`, {}, "Could not check Mnemom Agent access");
|
|
1540
1540
|
}
|
|
1541
|
-
/** Redeem a `pinv_…` product invite (a Mnemom
|
|
1541
|
+
/** Redeem a `pinv_…` product invite (a Mnemom Agent invitation) for the signed-in user. */
|
|
1542
1542
|
export async function acceptProductInvite(token) {
|
|
1543
1543
|
return codeFetch("/v1/invites/accept", { method: "POST", body: JSON.stringify({ token }) }, "Could not redeem the invitation");
|
|
1544
1544
|
}
|