@mnemom/mnemom 0.17.25 → 0.17.26
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.js +9 -0
- package/dist/commands/agent-doctor.d.ts +3 -2
- package/dist/commands/agent-doctor.js +22 -7
- package/dist/commands/agent-goal.js +7 -2
- package/dist/commands/agent-sessions.js +2 -1
- package/dist/commands/agent-setup.d.ts +104 -4
- package/dist/commands/agent-setup.js +414 -18
- package/dist/commands/agent-slack.d.ts +4 -0
- package/dist/commands/agent-slack.js +67 -21
- package/dist/commands/agent.d.ts +29 -1
- package/dist/commands/agent.js +89 -12
- package/dist/commands/wrap.d.ts +2 -0
- package/dist/commands/wrap.js +13 -1
- package/dist/index.js +19 -5
- package/dist/lib/agent-identity-route.d.ts +21 -3
- package/dist/lib/agent-identity-route.js +42 -15
- package/dist/lib/agent-ledger.d.ts +5 -0
- package/dist/lib/agent.d.ts +62 -1
- package/dist/lib/agent.js +55 -0
- package/dist/lib/api.d.ts +33 -1
- package/dist/lib/api.js +10 -0
- package/dist/lib/experimental.js +14 -2
- package/dist/lib/goal-api.d.ts +3 -3
- package/dist/lib/goal-api.js +5 -2
- package/dist/lib/launcher-config.d.ts +18 -0
- package/dist/lib/launcher-config.js +47 -1
- package/dist/lib/oauth.d.ts +7 -0
- package/dist/lib/oauth.js +92 -17
- package/dist/lib/served-agent-cache.d.ts +8 -1
- package/dist/lib/served-agent-cache.js +11 -2
- package/dist/lib/slack-channel.d.ts +1 -0
- package/dist/lib/slack-channel.js +30 -12
- package/dist/lib/slack-route.d.ts +72 -9
- package/dist/lib/slack-route.js +113 -24
- package/dist/lib/slack-router.d.ts +37 -2
- package/dist/lib/slack-router.js +98 -13
- package/dist/lib/slack-state.d.ts +105 -4
- package/dist/lib/slack-state.js +230 -11
- package/dist/lib/subscription-login.d.ts +12 -3
- package/dist/lib/subscription-login.js +19 -7
- package/dist/mcp/handoff-server.mjs +31 -3
- package/dist/rc-proxy.mjs +14 -0
- package/package.json +1 -1
|
@@ -24,34 +24,61 @@ export class IdentityRouteMissingError extends Error {
|
|
|
24
24
|
this.name = "IdentityRouteMissingError";
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
|
+
/** `{error:{code,message}}` (the gateway's envelope) → its code; anything else → undefined. */
|
|
28
|
+
function errorCode(text) {
|
|
29
|
+
try {
|
|
30
|
+
const body = JSON.parse(text);
|
|
31
|
+
const code = body?.error?.code;
|
|
32
|
+
return typeof code === "string" ? code : undefined;
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
27
38
|
/**
|
|
28
|
-
* The mnm- id the gateway serves for (Mnemom key, name). Its response also carries
|
|
29
|
-
*
|
|
30
|
-
*
|
|
39
|
+
* The mnm- id the gateway serves for (Mnemom key, name[, pin]). Its response also carries
|
|
40
|
+
* claimed / org_id / created; the launcher reads the claim state from the api instead. A non-2xx
|
|
41
|
+
* answer throws a MnemomApiError carrying the gateway's error code (agent_pin_forbidden,
|
|
42
|
+
* agent_pin_mismatch, agent_name_ambiguous, mnemom_api_key_required, …) so the caller can say
|
|
43
|
+
* exactly what to fix.
|
|
31
44
|
*/
|
|
32
|
-
export async function
|
|
45
|
+
export async function resolveServedIdentityDetail(gatewayUrl, agentName, mnemomKey, opts = {}) {
|
|
46
|
+
const headers = {
|
|
47
|
+
Accept: "application/json",
|
|
48
|
+
"x-mnemom-api-key": mnemomKey,
|
|
49
|
+
"x-mnemom-agent": agentName,
|
|
50
|
+
};
|
|
51
|
+
// AGENT_ID_HEADER (lib/agent.ts), spelled out so this module imports nothing from the launcher.
|
|
52
|
+
if (opts.pin)
|
|
53
|
+
headers["x-mnemom-agent-id"] = opts.pin;
|
|
33
54
|
const response = await fetch(`${gatewayUrl}${IDENTITY_ROUTE_PATH}`, {
|
|
34
55
|
method: "POST",
|
|
35
|
-
headers
|
|
36
|
-
Accept: "application/json",
|
|
37
|
-
"x-mnemom-api-key": mnemomKey,
|
|
38
|
-
"x-mnemom-agent": agentName,
|
|
39
|
-
},
|
|
56
|
+
headers,
|
|
40
57
|
signal: opts.timeoutMs ? AbortSignal.timeout(opts.timeoutMs) : undefined,
|
|
41
58
|
});
|
|
42
59
|
if (response.status === 404)
|
|
43
60
|
throw new IdentityRouteMissingError(gatewayUrl);
|
|
44
|
-
if (response.status === 401) {
|
|
45
|
-
throw new MnemomApiError(401, "the gateway rejected your Mnemom API key");
|
|
46
|
-
}
|
|
47
61
|
if (!response.ok) {
|
|
48
62
|
const text = await response.text().catch(() => "");
|
|
49
|
-
|
|
63
|
+
const code = errorCode(text);
|
|
64
|
+
if (response.status === 401 && code !== "mnemom_api_key_required") {
|
|
65
|
+
throw new MnemomApiError(401, "the gateway rejected your Mnemom API key", { code });
|
|
66
|
+
}
|
|
67
|
+
throw new MnemomApiError(response.status, `Gateway returned ${response.status}: ${text.slice(0, 300)}`, { code });
|
|
50
68
|
}
|
|
51
69
|
const body = (await response.json().catch(() => null));
|
|
52
70
|
const agentId = body?.agent_id;
|
|
53
|
-
|
|
71
|
+
// Only `mnm-` + [A-Za-z0-9-]: the id is echoed into request headers (x-mnemom-agent-id), the
|
|
72
|
+
// sessions ledger and a printed resume hint, so nothing else from the gateway is trusted there.
|
|
73
|
+
if (typeof agentId !== "string" || !/^mnm-[A-Za-z0-9-]+$/.test(agentId)) {
|
|
54
74
|
throw new Error(`Gateway returned no mnm- agent id from ${IDENTITY_ROUTE_PATH}.`);
|
|
55
75
|
}
|
|
56
|
-
return
|
|
76
|
+
return {
|
|
77
|
+
agentId,
|
|
78
|
+
resolvedBy: typeof body?.resolved_by === "string" ? body.resolved_by : null,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
/** {@link resolveServedIdentityDetail}, the id only (an unpinned launch). */
|
|
82
|
+
export async function resolveServedIdentity(gatewayUrl, agentName, mnemomKey, opts = {}) {
|
|
83
|
+
return (await resolveServedIdentityDetail(gatewayUrl, agentName, mnemomKey, opts)).agentId;
|
|
57
84
|
}
|
|
@@ -49,6 +49,11 @@ export interface SessionRecord {
|
|
|
49
49
|
agent_slug: string;
|
|
50
50
|
/** The agent record id when it resolved at launch, else null. */
|
|
51
51
|
agent_id: string | null;
|
|
52
|
+
/**
|
|
53
|
+
* true when the launch PINNED `agent_id` (`--agent mnm-<uuid>` / launcher.toml `agent_id`), so
|
|
54
|
+
* a resume pins it again. Absent on an unpinned launch (and on every row written before pins).
|
|
55
|
+
*/
|
|
56
|
+
agent_pinned?: true;
|
|
52
57
|
org_id: string;
|
|
53
58
|
org_label: string;
|
|
54
59
|
gateway_host: string;
|
package/dist/lib/agent.d.ts
CHANGED
|
@@ -134,6 +134,52 @@ export declare function isProdGateway(env?: NodeJS.ProcessEnv): boolean;
|
|
|
134
134
|
* session's model traffic went elsewhere (MNE-7564 review).
|
|
135
135
|
*/
|
|
136
136
|
export declare function resolveHandoffGatewayHost(door: string): string;
|
|
137
|
+
/**
|
|
138
|
+
* The explicit agent pin header (`x-mnemom-agent-id: mnm-<uuid>`). The gateway honours it only
|
|
139
|
+
* with a verified Mnemom key, serves exactly that agent (the caller's own, claimed, live one) and
|
|
140
|
+
* never falls back to a name or births an agent; otherwise it refuses (400 agent_pin_mismatch,
|
|
141
|
+
* 403 agent_pin_forbidden, 401 mnemom_api_key_required).
|
|
142
|
+
*/
|
|
143
|
+
export declare const AGENT_ID_HEADER = "x-mnemom-agent-id";
|
|
144
|
+
/** A full Mnemom agent id: `mnm-` + a lowercase UUID. */
|
|
145
|
+
export declare const AGENT_ID_RE: RegExp;
|
|
146
|
+
/** What an `--agent` / launcher.toml `agent` / `agent_id` value names. */
|
|
147
|
+
export type AgentRef = {
|
|
148
|
+
kind: "id";
|
|
149
|
+
id: string;
|
|
150
|
+
} | {
|
|
151
|
+
kind: "name";
|
|
152
|
+
name: string;
|
|
153
|
+
}
|
|
154
|
+
/** Starts with `mnm-` but is not a full id (a truncated/garbled paste). */
|
|
155
|
+
| {
|
|
156
|
+
kind: "malformed-id";
|
|
157
|
+
raw: string;
|
|
158
|
+
};
|
|
159
|
+
/**
|
|
160
|
+
* Classify an agent reference. A full `mnm-<uuid>` (any case — ids are lowercase on the wire, so
|
|
161
|
+
* it is lowercased) is an ID: the launch pins that exact agent. Anything else starting with
|
|
162
|
+
* `mnm-` is a MALFORMED id and refused by the caller: treating it as a name would make the
|
|
163
|
+
* gateway birth a brand-new agent named after the id (the 2026-10-08 restart-resume strays). Any
|
|
164
|
+
* other value is a NAME.
|
|
165
|
+
*/
|
|
166
|
+
export declare function parseAgentRef(raw: string): AgentRef;
|
|
167
|
+
/**
|
|
168
|
+
* Which agent a launch asks for, from the three places it can be named. `--agent` (flag) wins
|
|
169
|
+
* outright — an id there pins, a name there launches by name even when launcher.toml pins;
|
|
170
|
+
* otherwise launcher.toml `agent_id` pins, and wins over launcher.toml `agent`. Returns the
|
|
171
|
+
* `--agent`-shaped value (`agent`) and the config pin (`agentId`) for AgentOptions.
|
|
172
|
+
*/
|
|
173
|
+
export declare function selectLaunchAgent(sources: {
|
|
174
|
+
flag?: string;
|
|
175
|
+
configAgent?: string;
|
|
176
|
+
configAgentId?: string;
|
|
177
|
+
}): {
|
|
178
|
+
agent?: string;
|
|
179
|
+
agentId?: string;
|
|
180
|
+
};
|
|
181
|
+
/** The refusal for a `mnm-…` value that is not a full agent id. */
|
|
182
|
+
export declare function malformedAgentIdMessage(raw: string, source: string): string;
|
|
137
183
|
/**
|
|
138
184
|
* Sanitise a header value into a safe wire token: lowercase, alphanumerics and
|
|
139
185
|
* single hyphens only, no leading/trailing hyphen. Returns "" when nothing
|
|
@@ -597,9 +643,14 @@ export declare function resolveRemoteControlShape(args: {
|
|
|
597
643
|
export declare function buildCustomHeaders(opts: {
|
|
598
644
|
/** Absent on a subscription-login launch: Claude Code then sends its own claude.ai login. */
|
|
599
645
|
anthropicKey?: string;
|
|
600
|
-
/**
|
|
646
|
+
/**
|
|
647
|
+
* The Mnemom key: the governed-identity anchor on a subscription-login launch, and the
|
|
648
|
+
* principal an api-key launch proves when one is stored (lib/subscription-login.ts).
|
|
649
|
+
*/
|
|
601
650
|
mnemomKey?: string;
|
|
602
651
|
agent: string;
|
|
652
|
+
/** The pinned agent id (`--agent mnm-<uuid>` / launcher.toml `agent_id`), sent as AGENT_ID_HEADER. */
|
|
653
|
+
agentId?: string;
|
|
603
654
|
conversationId: string;
|
|
604
655
|
contractB64?: string;
|
|
605
656
|
/** `buildGoalModeHeader` output for an `--implicit` launch. */
|
|
@@ -769,6 +820,14 @@ export declare const HANDOFF_MCP_SERVER_NAME = "mnemom-handoff";
|
|
|
769
820
|
* it the same way it inherits `ANTHROPIC_CUSTOM_HEADERS` under the terminal shape.
|
|
770
821
|
*/
|
|
771
822
|
export declare const HANDOFF_RC_KEY_ENV_NAME = "MNEMOM_HANDOFF_KEY";
|
|
823
|
+
/**
|
|
824
|
+
* The Remote Control counterpart for the Mnemom key: under RC the key rides only the proxy's
|
|
825
|
+
* stdin, so when the handoff tool is registered on a launch that has one, `spawnRcCli` sets this
|
|
826
|
+
* dedicated, non-ANTHROPIC var (same reasoning as HANDOFF_RC_KEY_ENV_NAME). The terminal shape
|
|
827
|
+
* needs none — the server reads the `x-mnemom-api-key:` line of its inherited
|
|
828
|
+
* `ANTHROPIC_CUSTOM_HEADERS`.
|
|
829
|
+
*/
|
|
830
|
+
export declare const HANDOFF_RC_MNEMOM_KEY_ENV_NAME = "MNEMOM_HANDOFF_MNEMOM_KEY";
|
|
772
831
|
/**
|
|
773
832
|
* How the `handoff` MCP server (cli/src/mcp/handoff-server.mjs) recovers the Anthropic key
|
|
774
833
|
* WITHOUT it ever being written into the `--mcp-config` file on disk: `envName` is the NAME of a
|
|
@@ -801,6 +860,8 @@ export interface HandoffMcpParams {
|
|
|
801
860
|
gatewayHost: string;
|
|
802
861
|
anthropicKeySource: HandoffAnthropicKeySource;
|
|
803
862
|
agent: string;
|
|
863
|
+
/** The pinned agent id, when the launch pins one — the handoff call carries the same pin. */
|
|
864
|
+
agentId?: string;
|
|
804
865
|
conversationId: string;
|
|
805
866
|
/** `buildSessionConfigB64` output — forwarded so the route resolves the SAME per-session flag. */
|
|
806
867
|
sessionConfigB64?: string;
|
package/dist/lib/agent.js
CHANGED
|
@@ -108,6 +108,50 @@ export function isProdGateway(env = process.env) {
|
|
|
108
108
|
export function resolveHandoffGatewayHost(door) {
|
|
109
109
|
return new URL(door).origin;
|
|
110
110
|
}
|
|
111
|
+
/**
|
|
112
|
+
* The explicit agent pin header (`x-mnemom-agent-id: mnm-<uuid>`). The gateway honours it only
|
|
113
|
+
* with a verified Mnemom key, serves exactly that agent (the caller's own, claimed, live one) and
|
|
114
|
+
* never falls back to a name or births an agent; otherwise it refuses (400 agent_pin_mismatch,
|
|
115
|
+
* 403 agent_pin_forbidden, 401 mnemom_api_key_required).
|
|
116
|
+
*/
|
|
117
|
+
export const AGENT_ID_HEADER = "x-mnemom-agent-id";
|
|
118
|
+
/** A full Mnemom agent id: `mnm-` + a lowercase UUID. */
|
|
119
|
+
export const AGENT_ID_RE = /^mnm-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
120
|
+
/**
|
|
121
|
+
* Classify an agent reference. A full `mnm-<uuid>` (any case — ids are lowercase on the wire, so
|
|
122
|
+
* it is lowercased) is an ID: the launch pins that exact agent. Anything else starting with
|
|
123
|
+
* `mnm-` is a MALFORMED id and refused by the caller: treating it as a name would make the
|
|
124
|
+
* gateway birth a brand-new agent named after the id (the 2026-10-08 restart-resume strays). Any
|
|
125
|
+
* other value is a NAME.
|
|
126
|
+
*/
|
|
127
|
+
export function parseAgentRef(raw) {
|
|
128
|
+
const v = raw.trim();
|
|
129
|
+
const lower = v.toLowerCase();
|
|
130
|
+
if (AGENT_ID_RE.test(lower))
|
|
131
|
+
return { kind: "id", id: lower };
|
|
132
|
+
if (lower.startsWith("mnm-"))
|
|
133
|
+
return { kind: "malformed-id", raw: v };
|
|
134
|
+
return { kind: "name", name: v };
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Which agent a launch asks for, from the three places it can be named. `--agent` (flag) wins
|
|
138
|
+
* outright — an id there pins, a name there launches by name even when launcher.toml pins;
|
|
139
|
+
* otherwise launcher.toml `agent_id` pins, and wins over launcher.toml `agent`. Returns the
|
|
140
|
+
* `--agent`-shaped value (`agent`) and the config pin (`agentId`) for AgentOptions.
|
|
141
|
+
*/
|
|
142
|
+
export function selectLaunchAgent(sources) {
|
|
143
|
+
if (sources.flag?.trim())
|
|
144
|
+
return { agent: sources.flag };
|
|
145
|
+
if (sources.configAgentId?.trim())
|
|
146
|
+
return { agentId: sources.configAgentId };
|
|
147
|
+
return sources.configAgent?.trim() ? { agent: sources.configAgent } : {};
|
|
148
|
+
}
|
|
149
|
+
/** The refusal for a `mnm-…` value that is not a full agent id. */
|
|
150
|
+
export function malformedAgentIdMessage(raw, source) {
|
|
151
|
+
return (`mnemom agent: ${source} '${raw}' looks like an agent id but isn't a full one (mnm-<uuid>). ` +
|
|
152
|
+
"Pass the complete id (see `mnemom agents`), or a plain agent name. " +
|
|
153
|
+
"It is not used as a name: that would create a new agent named after the id.");
|
|
154
|
+
}
|
|
111
155
|
/**
|
|
112
156
|
* Sanitise a header value into a safe wire token: lowercase, alphanumerics and
|
|
113
157
|
* single hyphens only, no leading/trailing hyphen. Returns "" when nothing
|
|
@@ -966,6 +1010,7 @@ export function buildCustomHeaders(opts) {
|
|
|
966
1010
|
...(opts.anthropicKey ? [`x-api-key: ${opts.anthropicKey}`] : []),
|
|
967
1011
|
...(opts.mnemomKey ? [`x-mnemom-api-key: ${opts.mnemomKey}`] : []),
|
|
968
1012
|
`x-mnemom-agent: ${opts.agent}`,
|
|
1013
|
+
...(opts.agentId ? [`${AGENT_ID_HEADER}: ${opts.agentId}`] : []),
|
|
969
1014
|
`x-mnemom-conversation-id: ${opts.conversationId}`,
|
|
970
1015
|
];
|
|
971
1016
|
if (opts.contractB64)
|
|
@@ -1177,6 +1222,14 @@ export const HANDOFF_MCP_SERVER_NAME = "mnemom-handoff";
|
|
|
1177
1222
|
* it the same way it inherits `ANTHROPIC_CUSTOM_HEADERS` under the terminal shape.
|
|
1178
1223
|
*/
|
|
1179
1224
|
export const HANDOFF_RC_KEY_ENV_NAME = "MNEMOM_HANDOFF_KEY";
|
|
1225
|
+
/**
|
|
1226
|
+
* The Remote Control counterpart for the Mnemom key: under RC the key rides only the proxy's
|
|
1227
|
+
* stdin, so when the handoff tool is registered on a launch that has one, `spawnRcCli` sets this
|
|
1228
|
+
* dedicated, non-ANTHROPIC var (same reasoning as HANDOFF_RC_KEY_ENV_NAME). The terminal shape
|
|
1229
|
+
* needs none — the server reads the `x-mnemom-api-key:` line of its inherited
|
|
1230
|
+
* `ANTHROPIC_CUSTOM_HEADERS`.
|
|
1231
|
+
*/
|
|
1232
|
+
export const HANDOFF_RC_MNEMOM_KEY_ENV_NAME = "MNEMOM_HANDOFF_MNEMOM_KEY";
|
|
1180
1233
|
/** The `--mcp-config` document registering exactly the `handoff` tool. Pure — no I/O. */
|
|
1181
1234
|
export function buildHandoffMcpConfig(p) {
|
|
1182
1235
|
const env = {
|
|
@@ -1187,6 +1240,8 @@ export function buildHandoffMcpConfig(p) {
|
|
|
1187
1240
|
};
|
|
1188
1241
|
if (p.sessionConfigB64)
|
|
1189
1242
|
env.MNEMOM_HANDOFF_SESSION_CONFIG = p.sessionConfigB64;
|
|
1243
|
+
if (p.agentId)
|
|
1244
|
+
env.MNEMOM_HANDOFF_AGENT_ID = p.agentId;
|
|
1190
1245
|
return {
|
|
1191
1246
|
mcpServers: {
|
|
1192
1247
|
[HANDOFF_MCP_SERVER_NAME]: { command: p.nodeBin, args: [p.serverScript], env },
|
package/dist/lib/api.d.ts
CHANGED
|
@@ -31,6 +31,13 @@ export interface Agent {
|
|
|
31
31
|
claimed_at?: string | null;
|
|
32
32
|
name?: string | null;
|
|
33
33
|
claimed?: boolean;
|
|
34
|
+
/** Owner (org-member) view: the org the agent lives in. */
|
|
35
|
+
org_id?: string | null;
|
|
36
|
+
/**
|
|
37
|
+
* `org_member` on the full record, `authenticated` / `anonymous` on the public projection
|
|
38
|
+
* (handleGetAgent). Absent on an older api.
|
|
39
|
+
*/
|
|
40
|
+
caller?: string;
|
|
34
41
|
}
|
|
35
42
|
/**
|
|
36
43
|
* GET /v1/integrity/:id — components/schemas/IntegrityScore:
|
|
@@ -247,6 +254,13 @@ export interface AgentListItem {
|
|
|
247
254
|
* routing here avoids the false "Not authenticated" error (MNE-194 F10).
|
|
248
255
|
*/
|
|
249
256
|
export declare function listAgents(): Promise<AgentListItem[]>;
|
|
257
|
+
/**
|
|
258
|
+
* Every agent the caller OWNS, across all their orgs — `GET /v1/agents?scope=all`: rows whose
|
|
259
|
+
* `created_by` or `claimed_by` is the caller (MNE-191), newest first, up to 500. No org and no
|
|
260
|
+
* claim state on the rows (read those with {@link getAgent}); a tombstoned agent can still be
|
|
261
|
+
* listed and 404s there.
|
|
262
|
+
*/
|
|
263
|
+
export declare function listMyAgents(): Promise<AgentListItem[]>;
|
|
250
264
|
export interface OrgListItem {
|
|
251
265
|
org_id: string;
|
|
252
266
|
name: string;
|
|
@@ -645,7 +659,9 @@ export declare function putProtectionCard(agentId: string, body: string, content
|
|
|
645
659
|
/**
|
|
646
660
|
* The per-layer view of an agent's card (ADR-053 `?include=sources`): the raw
|
|
647
661
|
* card the agent layer itself carries, next to the composed card every layer
|
|
648
|
-
* produced.
|
|
662
|
+
* produced. The platform, org and team rows are optional here because an older
|
|
663
|
+
* api, or a test double, can omit them. Shape per mnemom-api
|
|
664
|
+
* `buildAgentSourcesEnvelope` (src/composition/agent-handlers.ts).
|
|
649
665
|
*/
|
|
650
666
|
export interface CardSources {
|
|
651
667
|
agent: {
|
|
@@ -656,6 +672,22 @@ export interface CardSources {
|
|
|
656
672
|
card_json: Record<string, unknown> | null;
|
|
657
673
|
available: boolean;
|
|
658
674
|
};
|
|
675
|
+
platform?: {
|
|
676
|
+
card_json: Record<string, unknown> | null;
|
|
677
|
+
available: boolean;
|
|
678
|
+
};
|
|
679
|
+
org?: {
|
|
680
|
+
card_json: Record<string, unknown> | null;
|
|
681
|
+
available: boolean;
|
|
682
|
+
org_id?: string | null;
|
|
683
|
+
org_name?: string | null;
|
|
684
|
+
};
|
|
685
|
+
teams?: Array<{
|
|
686
|
+
team_id: string;
|
|
687
|
+
team_name?: string | null;
|
|
688
|
+
card_json: Record<string, unknown> | null;
|
|
689
|
+
available: boolean;
|
|
690
|
+
}>;
|
|
659
691
|
[key: string]: unknown;
|
|
660
692
|
}
|
|
661
693
|
/**
|
package/dist/lib/api.js
CHANGED
|
@@ -211,6 +211,16 @@ export async function listAgents() {
|
|
|
211
211
|
const data = await gFetch("/v1/agents?limit=100", {}, { fallback: "Failed to list agents" });
|
|
212
212
|
return data.agents ?? [];
|
|
213
213
|
}
|
|
214
|
+
/**
|
|
215
|
+
* Every agent the caller OWNS, across all their orgs — `GET /v1/agents?scope=all`: rows whose
|
|
216
|
+
* `created_by` or `claimed_by` is the caller (MNE-191), newest first, up to 500. No org and no
|
|
217
|
+
* claim state on the rows (read those with {@link getAgent}); a tombstoned agent can still be
|
|
218
|
+
* listed and 404s there.
|
|
219
|
+
*/
|
|
220
|
+
export async function listMyAgents() {
|
|
221
|
+
const data = await gFetch("/v1/agents?scope=all&limit=500", {}, { fallback: "Failed to list your agents" });
|
|
222
|
+
return data.agents ?? [];
|
|
223
|
+
}
|
|
214
224
|
/**
|
|
215
225
|
* GET /v1/orgs — list every org the user is a member of, including the
|
|
216
226
|
* personal-org-of-one (per ADR-044 Option A, GitHub model). Personal sorts
|
package/dist/lib/experimental.js
CHANGED
|
@@ -50,7 +50,13 @@ export const EXPERIMENTAL_FEATURES = [
|
|
|
50
50
|
since: "0.17.3",
|
|
51
51
|
gates: {
|
|
52
52
|
flags: ["--slack"],
|
|
53
|
-
configKeys: [
|
|
53
|
+
configKeys: [
|
|
54
|
+
"slack_channel",
|
|
55
|
+
"slack_listen",
|
|
56
|
+
"slack_allow",
|
|
57
|
+
"slack_aliases",
|
|
58
|
+
"slack_sticky_threads",
|
|
59
|
+
],
|
|
54
60
|
commands: ["mnemom agent slack"],
|
|
55
61
|
env: [],
|
|
56
62
|
},
|
|
@@ -156,6 +162,8 @@ export function experimentalIdForConfigKey(key, value) {
|
|
|
156
162
|
case "slack_allow":
|
|
157
163
|
return "slack";
|
|
158
164
|
case "slack_listen":
|
|
165
|
+
case "slack_aliases":
|
|
166
|
+
case "slack_sticky_threads":
|
|
159
167
|
return value === "true" ? "slack" : undefined;
|
|
160
168
|
case "context_handoff":
|
|
161
169
|
return value === "true" ? "context-handoff" : undefined;
|
|
@@ -229,7 +237,11 @@ export function migrateExperimental(deps) {
|
|
|
229
237
|
}
|
|
230
238
|
/** The feature a launcher.toml key belongs to at all (for marking `mnemom agent config list`). */
|
|
231
239
|
export function configKeyFeature(key) {
|
|
232
|
-
if (key === "slack_channel" ||
|
|
240
|
+
if (key === "slack_channel" ||
|
|
241
|
+
key === "slack_listen" ||
|
|
242
|
+
key === "slack_allow" ||
|
|
243
|
+
key === "slack_aliases" ||
|
|
244
|
+
key === "slack_sticky_threads")
|
|
233
245
|
return "slack";
|
|
234
246
|
if (key === "context_handoff")
|
|
235
247
|
return "context-handoff";
|
package/dist/lib/goal-api.d.ts
CHANGED
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
* POST https://<gateway_host>/mnemom/goal/intent/reset {conversation_id, thread, statement?, requirements?}
|
|
8
8
|
*
|
|
9
9
|
* Auth is the SAME credential the launch used: `x-api-key` (the Anthropic key from
|
|
10
|
-
* `resolveAnthropicKey`), `x-mnemom-agent: <record.agent_slug>`,
|
|
11
|
-
* launch had a Mnemom key. The gateway hashes them exactly as a proxied request and only LOOKS
|
|
10
|
+
* `resolveAnthropicKey`), `x-mnemom-agent: <record.agent_slug>`, `x-mnemom-api-key` when the
|
|
11
|
+
* launch had a Mnemom key, and `x-mnemom-agent-id: <record.agent_id>` when the launch pinned it. The gateway hashes them exactly as a proxied request and only LOOKS
|
|
12
12
|
* UP the agent. Key material is never printed: errors carry the status, the gateway's error code
|
|
13
13
|
* and its message, nothing else.
|
|
14
14
|
*
|
|
@@ -24,7 +24,7 @@ export type GoalOp = "show" | "pin" | "reset";
|
|
|
24
24
|
export declare const GOAL_THREAD_RE: RegExp;
|
|
25
25
|
export declare function isValidGoalThread(thread: string): boolean;
|
|
26
26
|
/** The ledger fields the control call needs. */
|
|
27
|
-
export type GoalControlRecord = Pick<SessionRecord, "gateway_host" | "agent_slug" | "conversation_id"
|
|
27
|
+
export type GoalControlRecord = Pick<SessionRecord, "gateway_host" | "agent_slug" | "conversation_id"> & Partial<Pick<SessionRecord, "agent_id" | "agent_pinned">>;
|
|
28
28
|
export interface GoalControlCreds {
|
|
29
29
|
/** The Anthropic key the launch sent as `x-api-key`. */
|
|
30
30
|
anthropicKey: string;
|
package/dist/lib/goal-api.js
CHANGED
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
* POST https://<gateway_host>/mnemom/goal/intent/reset {conversation_id, thread, statement?, requirements?}
|
|
8
8
|
*
|
|
9
9
|
* Auth is the SAME credential the launch used: `x-api-key` (the Anthropic key from
|
|
10
|
-
* `resolveAnthropicKey`), `x-mnemom-agent: <record.agent_slug>`,
|
|
11
|
-
* launch had a Mnemom key. The gateway hashes them exactly as a proxied request and only LOOKS
|
|
10
|
+
* `resolveAnthropicKey`), `x-mnemom-agent: <record.agent_slug>`, `x-mnemom-api-key` when the
|
|
11
|
+
* launch had a Mnemom key, and `x-mnemom-agent-id: <record.agent_id>` when the launch pinned it. The gateway hashes them exactly as a proxied request and only LOOKS
|
|
12
12
|
* UP the agent. Key material is never printed: errors carry the status, the gateway's error code
|
|
13
13
|
* and its message, nothing else.
|
|
14
14
|
*
|
|
@@ -58,6 +58,9 @@ export function goalControlHeaders(record, creds) {
|
|
|
58
58
|
};
|
|
59
59
|
if (creds.mnemomKey)
|
|
60
60
|
headers["x-mnemom-api-key"] = creds.mnemomKey;
|
|
61
|
+
// A pinned launch's control calls carry the same pin, so they reach the agent it ran as.
|
|
62
|
+
if (record.agent_pinned && record.agent_id)
|
|
63
|
+
headers["x-mnemom-agent-id"] = record.agent_id;
|
|
61
64
|
return headers;
|
|
62
65
|
}
|
|
63
66
|
/** `{error:{code,message}}` (the gateway's envelope) → its parts; anything else → nulls. */
|
|
@@ -27,6 +27,11 @@ export interface LauncherConfig {
|
|
|
27
27
|
gateway?: string;
|
|
28
28
|
model?: string;
|
|
29
29
|
agent?: string;
|
|
30
|
+
/**
|
|
31
|
+
* The agent this machine's launches PIN (`x-mnemom-agent-id`): a full `mnm-<uuid>`. `--agent`
|
|
32
|
+
* on the command line wins over it; it wins over `agent`.
|
|
33
|
+
*/
|
|
34
|
+
agentId?: string;
|
|
30
35
|
effort?: string;
|
|
31
36
|
cli?: string;
|
|
32
37
|
launch?: LaunchShape;
|
|
@@ -43,6 +48,17 @@ export interface LauncherConfig {
|
|
|
43
48
|
slackListen?: boolean;
|
|
44
49
|
/** Slack member ids whose messages the router may deliver into a session. */
|
|
45
50
|
slackAllow?: string[];
|
|
51
|
+
/**
|
|
52
|
+
* Router: honour `mnemom agent slack alias` handles (off when unset). Off means the
|
|
53
|
+
* router routes exactly as it did before aliases existed; registered aliases are kept
|
|
54
|
+
* but ignored.
|
|
55
|
+
*/
|
|
56
|
+
slackAliases?: boolean;
|
|
57
|
+
/**
|
|
58
|
+
* Router: a thread a session was addressed in keeps routing replies to it (off when
|
|
59
|
+
* unset). Off means only threads a session posted in route replies, as before.
|
|
60
|
+
*/
|
|
61
|
+
slackStickyThreads?: boolean;
|
|
46
62
|
/**
|
|
47
63
|
* Context-handoff default (EXPERIMENTAL, off when unset): `true` registers the `handoff`
|
|
48
64
|
* MCP tool and sends `CONTEXT_HANDOFF_ENABLED: "true"` in the per-session config header for
|
|
@@ -86,6 +102,8 @@ export interface ConfigKeySpec {
|
|
|
86
102
|
get(cfg: LauncherConfig): string | number | boolean | undefined;
|
|
87
103
|
set(cfg: LauncherConfig, raw: string): void;
|
|
88
104
|
}
|
|
105
|
+
/** launcher.toml `agent_id`: a full `mnm-<uuid>` (any case; stored lowercase). */
|
|
106
|
+
export declare function coerceAgentId(v: string): string;
|
|
89
107
|
/** The full set of settable keys — drives get/set/unset/list and validation. */
|
|
90
108
|
export declare const CONFIG_KEYS: ConfigKeySpec[];
|
|
91
109
|
export declare function configKeySpec(key: string): ConfigKeySpec | undefined;
|
|
@@ -20,7 +20,7 @@ import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "
|
|
|
20
20
|
import { dirname, join } from "node:path";
|
|
21
21
|
import { parse as parseToml, stringify as stringifyToml, TomlError } from "smol-toml";
|
|
22
22
|
import { MNEMOM_DIR } from "./config.js";
|
|
23
|
-
import { CONTEXT_PROFILES, isPositiveInt, isPositiveNumber, parseCompactAt, parseContextProfile, } from "./agent.js";
|
|
23
|
+
import { CONTEXT_PROFILES, isPositiveInt, isPositiveNumber, parseCompactAt, parseAgentRef, parseContextProfile, } from "./agent.js";
|
|
24
24
|
/** The human-editable settings file. Secret key lives elsewhere (launcher.json). */
|
|
25
25
|
export const LAUNCHER_CONFIG_PATH = join(MNEMOM_DIR, "launcher.toml");
|
|
26
26
|
const LAUNCH_SHAPES = ["terminal", "remote-control", "server"];
|
|
@@ -61,6 +61,14 @@ function coerceGoalMode(raw) {
|
|
|
61
61
|
return v;
|
|
62
62
|
throw new Error(`goal_mode must be one of ${GOAL_MODES.join(", ")} (got '${raw}'). A sealed contract needs a statement, so it is always a per-launch --goal.`);
|
|
63
63
|
}
|
|
64
|
+
/** launcher.toml `agent_id`: a full `mnm-<uuid>` (any case; stored lowercase). */
|
|
65
|
+
export function coerceAgentId(v) {
|
|
66
|
+
const ref = parseAgentRef(v);
|
|
67
|
+
if (ref.kind !== "id") {
|
|
68
|
+
throw new Error(`agent_id must be a full agent id, mnm-<uuid> (got '${v.trim()}').`);
|
|
69
|
+
}
|
|
70
|
+
return ref.id;
|
|
71
|
+
}
|
|
64
72
|
function coerceNonEmpty(name, raw) {
|
|
65
73
|
const v = raw.trim();
|
|
66
74
|
if (!v)
|
|
@@ -116,6 +124,14 @@ export const CONFIG_KEYS = [
|
|
|
116
124
|
c.agent = coerceNonEmpty("agent", v);
|
|
117
125
|
},
|
|
118
126
|
},
|
|
127
|
+
{
|
|
128
|
+
key: "agent_id",
|
|
129
|
+
describe: "Pin launches to this agent id (mnm-<uuid>; x-mnemom-agent-id) — `--agent` wins over it, it wins over `agent`",
|
|
130
|
+
get: (c) => c.agentId,
|
|
131
|
+
set: (c, v) => {
|
|
132
|
+
c.agentId = coerceAgentId(v);
|
|
133
|
+
},
|
|
134
|
+
},
|
|
119
135
|
{
|
|
120
136
|
key: "effort",
|
|
121
137
|
describe: `Reasoning effort (${EFFORTS.join("|")})`,
|
|
@@ -196,6 +212,22 @@ export const CONFIG_KEYS = [
|
|
|
196
212
|
c.slackAllow = coerceMemberIds(v);
|
|
197
213
|
},
|
|
198
214
|
},
|
|
215
|
+
{
|
|
216
|
+
key: "slack_aliases",
|
|
217
|
+
describe: "Slack router: route `mnemom agent slack alias` handles, queueing for a stopped session (off by default)",
|
|
218
|
+
get: (c) => c.slackAliases,
|
|
219
|
+
set: (c, v) => {
|
|
220
|
+
c.slackAliases = coerceBool("slack_aliases", v);
|
|
221
|
+
},
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
key: "slack_sticky_threads",
|
|
225
|
+
describe: "Slack router: a thread a session was addressed in keeps routing replies to it (off by default)",
|
|
226
|
+
get: (c) => c.slackStickyThreads,
|
|
227
|
+
set: (c, v) => {
|
|
228
|
+
c.slackStickyThreads = coerceBool("slack_sticky_threads", v);
|
|
229
|
+
},
|
|
230
|
+
},
|
|
199
231
|
{
|
|
200
232
|
key: "context_handoff",
|
|
201
233
|
describe: "EXPERIMENTAL: register the `handoff` MCP tool + tell the gateway CONTEXT_HANDOFF_ENABLED=true (off by default)",
|
|
@@ -284,6 +316,8 @@ export function parseLauncherConfig(text) {
|
|
|
284
316
|
cfg.model = coerceNonEmpty("model", str(raw.model) ?? "");
|
|
285
317
|
if (raw.agent !== undefined)
|
|
286
318
|
cfg.agent = coerceNonEmpty("agent", str(raw.agent) ?? "");
|
|
319
|
+
if (raw.agent_id !== undefined)
|
|
320
|
+
cfg.agentId = coerceAgentId(str(raw.agent_id) ?? "");
|
|
287
321
|
if (raw.effort !== undefined)
|
|
288
322
|
cfg.effort = coerceEffort(str(raw.effort) ?? "");
|
|
289
323
|
if (raw.cli !== undefined)
|
|
@@ -306,6 +340,10 @@ export function parseLauncherConfig(text) {
|
|
|
306
340
|
}
|
|
307
341
|
if (raw.slack_channel !== undefined)
|
|
308
342
|
cfg.slackChannel = coerceNonEmpty("slack_channel", str(raw.slack_channel) ?? "");
|
|
343
|
+
if (raw.slack_aliases !== undefined)
|
|
344
|
+
cfg.slackAliases = coerceBool("slack_aliases", str(raw.slack_aliases) ?? "");
|
|
345
|
+
if (raw.slack_sticky_threads !== undefined)
|
|
346
|
+
cfg.slackStickyThreads = coerceBool("slack_sticky_threads", str(raw.slack_sticky_threads) ?? "");
|
|
309
347
|
if (raw.context_handoff !== undefined)
|
|
310
348
|
cfg.contextHandoff = coerceBool("context_handoff", str(raw.context_handoff) ?? "");
|
|
311
349
|
if (raw.compaction_intercept !== undefined)
|
|
@@ -362,6 +400,8 @@ export function serializeLauncherConfig(cfg) {
|
|
|
362
400
|
doc.model = cfg.model;
|
|
363
401
|
if (cfg.agent !== undefined)
|
|
364
402
|
doc.agent = cfg.agent;
|
|
403
|
+
if (cfg.agentId !== undefined)
|
|
404
|
+
doc.agent_id = cfg.agentId;
|
|
365
405
|
if (cfg.effort !== undefined)
|
|
366
406
|
doc.effort = cfg.effort;
|
|
367
407
|
if (cfg.cli !== undefined)
|
|
@@ -382,6 +422,10 @@ export function serializeLauncherConfig(cfg) {
|
|
|
382
422
|
doc.slack_listen = cfg.slackListen;
|
|
383
423
|
if (cfg.slackAllow !== undefined)
|
|
384
424
|
doc.slack_allow = cfg.slackAllow;
|
|
425
|
+
if (cfg.slackAliases !== undefined)
|
|
426
|
+
doc.slack_aliases = cfg.slackAliases;
|
|
427
|
+
if (cfg.slackStickyThreads !== undefined)
|
|
428
|
+
doc.slack_sticky_threads = cfg.slackStickyThreads;
|
|
385
429
|
if (cfg.contextHandoff !== undefined)
|
|
386
430
|
doc.context_handoff = cfg.contextHandoff;
|
|
387
431
|
if (cfg.compactionIntercept !== undefined)
|
|
@@ -476,6 +520,8 @@ export function withoutDisabledExperimental(cfg, enabled) {
|
|
|
476
520
|
delete out.slackChannel;
|
|
477
521
|
delete out.slackListen;
|
|
478
522
|
delete out.slackAllow;
|
|
523
|
+
delete out.slackAliases;
|
|
524
|
+
delete out.slackStickyThreads;
|
|
479
525
|
}
|
|
480
526
|
if (!enabled.includes("context-handoff"))
|
|
481
527
|
delete out.contextHandoff;
|
package/dist/lib/oauth.d.ts
CHANGED
|
@@ -21,6 +21,13 @@
|
|
|
21
21
|
* cached, so there is no client secret to embed (the AS advertises
|
|
22
22
|
* token_endpoint_auth_method "none" — public clients only).
|
|
23
23
|
*/
|
|
24
|
+
/**
|
|
25
|
+
* The per-request deadline for OAuth calls (ms): the env override when it is a
|
|
26
|
+
* positive integer (capped at 5 min), else the default.
|
|
27
|
+
*/
|
|
28
|
+
export declare function oauthHttpTimeoutMs(env?: NodeJS.ProcessEnv): number;
|
|
29
|
+
/** The name carried by an error thrown when an AS round-trip hit its deadline. */
|
|
30
|
+
export declare const OAUTH_TIMEOUT_ERROR = "OAuthTimeoutError";
|
|
24
31
|
export interface AuthServerMetadata {
|
|
25
32
|
issuer: string;
|
|
26
33
|
authorization_endpoint: string;
|