@mnemom/mnemom 0.17.34 → 0.17.36
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 +3 -0
- package/dist/commands/agent-goal.d.ts +8 -3
- package/dist/commands/agent-goal.js +22 -8
- package/dist/commands/agent-run.d.ts +14 -2
- package/dist/commands/agent-run.js +28 -7
- package/dist/commands/agent-share-render.js +1 -1
- package/dist/commands/agent-slack.js +32 -0
- package/dist/commands/durable-inbox.d.ts +51 -0
- package/dist/commands/durable-inbox.js +134 -0
- package/dist/commands/durable-questions.d.ts +64 -0
- package/dist/commands/durable-questions.js +161 -0
- package/dist/commands/scan.d.ts +13 -3
- package/dist/commands/scan.js +48 -10
- package/dist/durable/seat-proxy.d.ts +12 -1
- package/dist/durable/seat-proxy.js +55 -9
- package/dist/durable/seat-socket.d.ts +20 -2
- package/dist/durable/seat-socket.js +58 -0
- package/dist/durable/turn-chain.d.ts +8 -0
- package/dist/durable/turn-chain.js +25 -4
- package/dist/host/client.d.ts +41 -0
- package/dist/host/client.js +7 -0
- package/dist/host/control.d.ts +7 -0
- package/dist/host/control.js +15 -0
- package/dist/host/daemon.d.ts +20 -0
- package/dist/host/daemon.js +74 -1
- package/dist/host/inbox-delivery.d.ts +174 -0
- package/dist/host/inbox-delivery.js +453 -0
- package/dist/host/run.d.ts +18 -0
- package/dist/host/run.js +61 -0
- package/dist/host/s1-names.d.ts +2 -0
- package/dist/host/s1-names.js +2 -0
- package/dist/host/tmux.d.ts +7 -0
- package/dist/host/tmux.js +19 -0
- package/dist/index.js +62 -3
- package/dist/lib/api.d.ts +5 -0
- package/dist/lib/api.js +16 -0
- package/dist/lib/config.d.ts +7 -0
- package/dist/lib/config.js +21 -0
- package/dist/lib/durable/api.d.ts +11 -0
- package/dist/lib/durable/api.js +28 -3
- package/dist/lib/durable/gate.d.ts +1 -1
- package/dist/lib/durable/gate.js +8 -1
- package/dist/lib/durable/live.d.ts +5 -0
- package/dist/lib/durable/live.js +19 -0
- package/dist/lib/experimental.js +3 -0
- package/dist/lib/goal-api.d.ts +11 -4
- package/dist/lib/goal-api.js +4 -3
- package/dist/lib/launcher-config.d.ts +5 -0
- package/dist/lib/launcher-config.js +17 -2
- package/dist/lib/prompt.d.ts +4 -2
- package/dist/lib/prompt.js +15 -5
- package/dist/lib/runner/job.d.ts +2 -2
- package/dist/lib/runner/job.js +16 -3
- package/dist/lib/runner/pr-body.d.ts +17 -0
- package/dist/lib/runner/pr-body.js +42 -0
- package/dist/lib/runner/proxy.d.ts +18 -3
- package/dist/lib/runner/proxy.js +24 -5
- package/dist/lib/scan/pr-source.js +2 -0
- package/dist/lib/scan/record.d.ts +60 -9
- package/dist/lib/scan/record.js +157 -39
- package/dist/lib/scan/repo-picker.d.ts +32 -0
- package/dist/lib/scan/repo-picker.js +80 -0
- package/dist/lib/scan/report.d.ts +6 -8
- package/dist/lib/scan/report.js +21 -7
- package/dist/lib/scan/share.d.ts +5 -1
- package/dist/lib/scan/share.js +15 -5
- package/dist/lib/slack-durable.d.ts +123 -0
- package/dist/lib/slack-durable.js +237 -0
- package/dist/lib/slack-router.d.ts +7 -0
- package/dist/lib/slack-router.js +50 -4
- package/dist/vendor/durable-contract/index.js +992 -34
- package/dist/vendor/shared/durable-contract/admission.d.ts +81 -2
- package/dist/vendor/shared/durable-contract/admission.js +319 -2
- package/dist/vendor/shared/durable-contract/cli-surface.js +21 -0
- package/dist/vendor/shared/durable-contract/codes.d.ts +60 -0
- package/dist/vendor/shared/durable-contract/codes.js +22 -0
- package/dist/vendor/shared/durable-contract/events.d.ts +4 -2
- package/dist/vendor/shared/durable-contract/events.js +55 -1
- package/dist/vendor/shared/durable-contract/freeze-record.d.ts +14 -0
- package/dist/vendor/shared/durable-contract/inbox.d.ts +299 -0
- package/dist/vendor/shared/durable-contract/inbox.js +462 -0
- package/dist/vendor/shared/durable-contract/index.d.ts +2 -0
- package/dist/vendor/shared/durable-contract/index.js +2 -0
- package/dist/vendor/shared/durable-contract/question.d.ts +218 -0
- package/dist/vendor/shared/durable-contract/question.js +373 -0
- package/dist/vendor/shared/durable-contract/routes.d.ts +44 -2
- package/dist/vendor/shared/durable-contract/routes.js +43 -1
- package/dist/vendor/shared/durable-contract/turn.d.ts +15 -1
- package/dist/vendor/shared/durable-contract/turn.js +33 -1
- package/dist/vendor/shared/durable-contract/vectors.d.ts +45 -0
- package/dist/vendor/shared/durable-contract/vectors.js +49 -0
- package/node_modules/@mnemom/aip-verifier/LICENSE +190 -0
- package/node_modules/@mnemom/aip-verifier/dist/claim-lint-CVITIRcU.d.cts +478 -0
- package/node_modules/@mnemom/aip-verifier/dist/claim-lint-CVITIRcU.d.ts +478 -0
- package/node_modules/@mnemom/aip-verifier/dist/index.cjs +971 -0
- package/node_modules/@mnemom/aip-verifier/dist/index.cjs.map +1 -0
- package/node_modules/@mnemom/aip-verifier/dist/index.d.cts +330 -0
- package/node_modules/@mnemom/aip-verifier/dist/index.d.ts +330 -0
- package/node_modules/@mnemom/aip-verifier/dist/index.js +933 -0
- package/node_modules/@mnemom/aip-verifier/dist/index.js.map +1 -0
- package/node_modules/@mnemom/aip-verifier/dist/render-lines.cjs +879 -0
- package/node_modules/@mnemom/aip-verifier/dist/render-lines.cjs.map +1 -0
- package/node_modules/@mnemom/aip-verifier/dist/render-lines.d.cts +96 -0
- package/node_modules/@mnemom/aip-verifier/dist/render-lines.d.ts +96 -0
- package/node_modules/@mnemom/aip-verifier/dist/render-lines.js +875 -0
- package/node_modules/@mnemom/aip-verifier/dist/render-lines.js.map +1 -0
- package/node_modules/@mnemom/aip-verifier/package.json +86 -0
- package/node_modules/@noble/ed25519/LICENSE +21 -0
- package/node_modules/@noble/ed25519/README.md +371 -0
- package/node_modules/@noble/ed25519/index.d.ts +391 -0
- package/node_modules/@noble/ed25519/index.js +933 -0
- package/node_modules/@noble/ed25519/index.ts +1134 -0
- package/node_modules/@noble/ed25519/package.json +54 -0
- package/node_modules/@noble/hashes/LICENSE +21 -0
- package/node_modules/@noble/hashes/README.md +478 -0
- package/node_modules/@noble/hashes/_blake.d.ts +20 -0
- package/node_modules/@noble/hashes/_blake.js +59 -0
- package/node_modules/@noble/hashes/_md.d.ts +95 -0
- package/node_modules/@noble/hashes/_md.js +209 -0
- package/node_modules/@noble/hashes/_u64.d.ts +36 -0
- package/node_modules/@noble/hashes/_u64.js +77 -0
- package/node_modules/@noble/hashes/argon2.d.ts +148 -0
- package/node_modules/@noble/hashes/argon2.js +577 -0
- package/node_modules/@noble/hashes/blake1.d.ts +162 -0
- package/node_modules/@noble/hashes/blake1.js +630 -0
- package/node_modules/@noble/hashes/blake2.d.ts +203 -0
- package/node_modules/@noble/hashes/blake2.js +573 -0
- package/node_modules/@noble/hashes/blake3.d.ts +68 -0
- package/node_modules/@noble/hashes/blake3.js +310 -0
- package/node_modules/@noble/hashes/eskdf.d.ts +91 -0
- package/node_modules/@noble/hashes/eskdf.js +225 -0
- package/node_modules/@noble/hashes/hkdf.d.ts +70 -0
- package/node_modules/@noble/hashes/hkdf.js +152 -0
- package/node_modules/@noble/hashes/hmac.d.ts +50 -0
- package/node_modules/@noble/hashes/hmac.js +93 -0
- package/node_modules/@noble/hashes/index.d.ts +1 -0
- package/node_modules/@noble/hashes/index.js +34 -0
- package/node_modules/@noble/hashes/legacy.d.ts +107 -0
- package/node_modules/@noble/hashes/legacy.js +348 -0
- package/node_modules/@noble/hashes/package.json +91 -0
- package/node_modules/@noble/hashes/pbkdf2.d.ts +66 -0
- package/node_modules/@noble/hashes/pbkdf2.js +176 -0
- package/node_modules/@noble/hashes/scrypt.d.ts +98 -0
- package/node_modules/@noble/hashes/scrypt.js +304 -0
- package/node_modules/@noble/hashes/sha2.d.ts +176 -0
- package/node_modules/@noble/hashes/sha2.js +446 -0
- package/node_modules/@noble/hashes/sha3-addons.d.ts +533 -0
- package/node_modules/@noble/hashes/sha3-addons.js +973 -0
- package/node_modules/@noble/hashes/sha3.d.ts +208 -0
- package/node_modules/@noble/hashes/sha3.js +475 -0
- package/node_modules/@noble/hashes/src/_blake.ts +67 -0
- package/node_modules/@noble/hashes/src/_md.ts +227 -0
- package/node_modules/@noble/hashes/src/_u64.ts +111 -0
- package/node_modules/@noble/hashes/src/argon2.ts +693 -0
- package/node_modules/@noble/hashes/src/blake1.ts +677 -0
- package/node_modules/@noble/hashes/src/blake2.ts +648 -0
- package/node_modules/@noble/hashes/src/blake3.ts +344 -0
- package/node_modules/@noble/hashes/src/eskdf.ts +265 -0
- package/node_modules/@noble/hashes/src/hkdf.ts +161 -0
- package/node_modules/@noble/hashes/src/hmac.ts +130 -0
- package/node_modules/@noble/hashes/src/index.ts +33 -0
- package/node_modules/@noble/hashes/src/legacy.ts +361 -0
- package/node_modules/@noble/hashes/src/pbkdf2.ts +219 -0
- package/node_modules/@noble/hashes/src/scrypt.ts +375 -0
- package/node_modules/@noble/hashes/src/sha2.ts +486 -0
- package/node_modules/@noble/hashes/src/sha3-addons.ts +1157 -0
- package/node_modules/@noble/hashes/src/sha3.ts +525 -0
- package/node_modules/@noble/hashes/src/utils.ts +978 -0
- package/node_modules/@noble/hashes/src/webcrypto.ts +283 -0
- package/node_modules/@noble/hashes/utils.d.ts +560 -0
- package/node_modules/@noble/hashes/utils.js +687 -0
- package/node_modules/@noble/hashes/webcrypto.d.ts +121 -0
- package/node_modules/@noble/hashes/webcrypto.js +228 -0
- package/package.json +5 -2
- package/plugins/integrity-manifest.json +6 -2
- package/plugins/mnemom/hooks/hooks.json +12 -0
- package/plugins/mnemom/hooks/seat-ask.mjs +221 -0
package/dist/index.js
CHANGED
|
@@ -1147,6 +1147,64 @@ function takeParentOption(cmd, key) {
|
|
|
1147
1147
|
gate(agentSessionsCmd.command("unlock <name>", { hidden }))
|
|
1148
1148
|
.description("EXPERIMENTAL (durable): unlock a locked session back to tracked (device step)")
|
|
1149
1149
|
.action((name) => exitWith("`mnemom agent sessions unlock`", () => unlockCommand(name, liveVerbDeps())));
|
|
1150
|
+
// S1 amendment 60 (signed off by Wassim 2026-11-02): the inbox door and its sender allowlist.
|
|
1151
|
+
gate(agentSessionsCmd.command("send <name> [text]", { hidden }))
|
|
1152
|
+
.description("EXPERIMENTAL (durable): send a message to a locked session's inbox; the host daemon gives it to the session as its next prompt (text, or - / nothing for stdin)")
|
|
1153
|
+
.option("--idempotency-key <key>", "Retry-safe key (default: a new one per send)")
|
|
1154
|
+
.option("--sender-key-env <VAR>", "Send as an allowlisted agent: the environment variable holding that agent's key")
|
|
1155
|
+
.option("--conversation <cid>", "The session's conversation id (with --sender-key-env)")
|
|
1156
|
+
.option("--agent-id <id>", "The session's agent id (with --sender-key-env)")
|
|
1157
|
+
.option("--json", "Output the answer as JSON")
|
|
1158
|
+
.action(async (name, text, o, cmd) => {
|
|
1159
|
+
const json = cmd.optsWithGlobals().json === true;
|
|
1160
|
+
const { sendCommand } = await import("./commands/durable-inbox.js");
|
|
1161
|
+
const { liveInboxDeps } = await import("./lib/durable/live.js");
|
|
1162
|
+
return exitWith("`mnemom agent sessions send`", () => sendCommand(name, text, { ...o, json }, liveInboxDeps()));
|
|
1163
|
+
});
|
|
1164
|
+
const sendersCmd = gate(agentSessionsCmd.command("senders", { hidden })).description("EXPERIMENTAL (durable): the agents allowed to send to a session's agent's inbox (list | add | remove)");
|
|
1165
|
+
for (const verb of ["list", "add", "remove"]) {
|
|
1166
|
+
const c = sendersCmd
|
|
1167
|
+
.command(verb === "list" ? "list <name>" : `${verb} <name> <agent>`)
|
|
1168
|
+
.description(verb === "list"
|
|
1169
|
+
? "List the agents that may send to this session's agent"
|
|
1170
|
+
: verb === "add"
|
|
1171
|
+
? "Allow one of your agents (name or id) to send to this session's agent"
|
|
1172
|
+
: "Stop an agent sending to this session's agent");
|
|
1173
|
+
if (verb === "add")
|
|
1174
|
+
c.option("--label <label>", "A note shown in the list");
|
|
1175
|
+
c.option("--json", "Output the answer as JSON");
|
|
1176
|
+
c.action(async (name, ...rest) => {
|
|
1177
|
+
const agent = verb === "list" ? undefined : rest[0];
|
|
1178
|
+
const cmd = rest[rest.length - 1];
|
|
1179
|
+
const all = cmd.optsWithGlobals();
|
|
1180
|
+
const { sendersCommand } = await import("./commands/durable-inbox.js");
|
|
1181
|
+
const { liveInboxDeps } = await import("./lib/durable/live.js");
|
|
1182
|
+
return exitWith(`\`mnemom agent sessions senders ${verb}\``, () => sendersCommand(verb, name, agent, { label: all.label, json: all.json }, liveInboxDeps()));
|
|
1183
|
+
});
|
|
1184
|
+
}
|
|
1185
|
+
// S1 amendment 61 (signed off by Wassim 2026-11-02): a locked session's questions to its owner.
|
|
1186
|
+
gate(agentSessionsCmd.command("questions <name>", { hidden }))
|
|
1187
|
+
.description("EXPERIMENTAL (durable): a locked session's questions to you (open ones; --all for every one)")
|
|
1188
|
+
.option("--all", "Include answered, dismissed and expired questions")
|
|
1189
|
+
.option("--json", "Output the answer as JSON")
|
|
1190
|
+
.action(async (name, _o, cmd) => {
|
|
1191
|
+
// `agent sessions` takes --all and --json too, so commander may hand either to it.
|
|
1192
|
+
const g = cmd.optsWithGlobals();
|
|
1193
|
+
const json = g.json === true;
|
|
1194
|
+
const { questionsCommand } = await import("./commands/durable-questions.js");
|
|
1195
|
+
const { liveQuestionDeps } = await import("./lib/durable/live.js");
|
|
1196
|
+
return exitWith("`mnemom agent sessions questions`", () => questionsCommand(name, { all: g.all, json }, liveQuestionDeps()));
|
|
1197
|
+
});
|
|
1198
|
+
gate(agentSessionsCmd.command("answer <name> <qid> [choices...]", { hidden }))
|
|
1199
|
+
.description("EXPERIMENTAL (durable): answer a session's question (<qid> or latest); one choice per question: an option number, a label, or free text")
|
|
1200
|
+
.option("--dismiss", "Dismiss the question without answering it")
|
|
1201
|
+
.option("--json", "Output the answer as JSON")
|
|
1202
|
+
.action(async (name, qid, choices, o, cmd) => {
|
|
1203
|
+
const json = cmd.optsWithGlobals().json === true;
|
|
1204
|
+
const { answerCommand } = await import("./commands/durable-questions.js");
|
|
1205
|
+
const { liveQuestionDeps } = await import("./lib/durable/live.js");
|
|
1206
|
+
return exitWith("`mnemom agent sessions answer`", () => answerCommand(name, qid, choices ?? [], { dismiss: o.dismiss, json }, liveQuestionDeps()));
|
|
1207
|
+
});
|
|
1150
1208
|
if (hidden)
|
|
1151
1209
|
agentSessionsCmd.usage(sessionsUsage);
|
|
1152
1210
|
}
|
|
@@ -2326,10 +2384,10 @@ program
|
|
|
2326
2384
|
program
|
|
2327
2385
|
.command("scan")
|
|
2328
2386
|
.description("Read-only local scan of GitHub PR metadata; the report is emailed to you every time (counts only, no repo names) and Mnemom keeps nothing at share level none")
|
|
2329
|
-
.option("--repo <owner/name...>", "Repo to scan (repeatable or comma-separated)")
|
|
2387
|
+
.option("--repo <owner/name...>", "Repo to scan (repeatable or comma-separated; omit on a terminal to pick from your repos)")
|
|
2330
2388
|
.option("--start <YYYY-MM-DD>", "Period start (default: today)")
|
|
2331
2389
|
.option("--end <YYYY-MM-DD>", "Period end (default: today)")
|
|
2332
|
-
.option("--share <level>", "summary | full | none (default:
|
|
2390
|
+
.option("--share <level>", "summary | full | none (default: full when asked; none when not interactive)")
|
|
2333
2391
|
.option("--cto-email <email>", "CTO who receives the report (required unless you are the CTO; asked when omitted)")
|
|
2334
2392
|
.option("--monthly-spend <usd>", "Your own approximate monthly AI spend (self-reported; never sent or saved; needs MNEMOM_ORG_SCAN_MODE=true)")
|
|
2335
2393
|
.option("--anthropic-admin-key", "Ask (masked) for your Anthropic admin key and read measured usage with it, locally; or set ANTHROPIC_ADMIN_KEY. Never stored, logged or sent anywhere but api.anthropic.com (needs MNEMOM_ORG_SCAN_MODE=true)")
|
|
@@ -2337,7 +2395,8 @@ program
|
|
|
2337
2395
|
.option("--org <org_id>", "Your org id; the report is emailed to you for this org (default: your only organization, else the scan stops and asks). Also selects the Record totals preview (needs MNEMOM_ORG_SCAN_MODE=true)")
|
|
2338
2396
|
.action(async (options) => {
|
|
2339
2397
|
try {
|
|
2340
|
-
|
|
2398
|
+
// Omitted --repo is allowed here: scanCommand picks from your repos on a terminal, else stops with a hint.
|
|
2399
|
+
const repos = options.repo === undefined ? [] : parseRepoFlags(options.repo);
|
|
2341
2400
|
if (!repos)
|
|
2342
2401
|
throw new Error("--repo owner/name is required (repeatable)");
|
|
2343
2402
|
const today = new Date().toISOString().slice(0, 10);
|
package/dist/lib/api.d.ts
CHANGED
|
@@ -215,11 +215,14 @@ export declare class MnemomApiError extends Error {
|
|
|
215
215
|
readonly conflictAgentId?: string;
|
|
216
216
|
/** Top-level enforce-hook sibling (present only on a spec-rewritten body). */
|
|
217
217
|
readonly specDeviation?: SpecDeviation;
|
|
218
|
+
/** The server's `Retry-After` in ms (delta-seconds or HTTP-date form), when it sent a valid one. */
|
|
219
|
+
readonly retryAfterMs?: number;
|
|
218
220
|
constructor(status: number, message: string, opts?: {
|
|
219
221
|
code?: string;
|
|
220
222
|
details?: unknown;
|
|
221
223
|
conflictAgentId?: string;
|
|
222
224
|
specDeviation?: SpecDeviation;
|
|
225
|
+
retryAfterMs?: number;
|
|
223
226
|
});
|
|
224
227
|
}
|
|
225
228
|
/**
|
|
@@ -229,6 +232,8 @@ export declare class MnemomApiError extends Error {
|
|
|
229
232
|
* reflects the true intended status behind any synthetic-500 rewrite.
|
|
230
233
|
*/
|
|
231
234
|
export declare function readApiError(response: Response, fallback: string): Promise<MnemomApiError>;
|
|
235
|
+
/** `Retry-After` header to ms: delta-seconds or an HTTP-date. Anything else is undefined. */
|
|
236
|
+
export declare function parseRetryAfterMs(value: string | null | undefined, nowMs: number): number | undefined;
|
|
232
237
|
export declare function postApi<T>(endpoint: string, body: unknown, opts?: {
|
|
233
238
|
idempotencyKey?: string;
|
|
234
239
|
}): Promise<T>;
|
package/dist/lib/api.js
CHANGED
|
@@ -85,9 +85,12 @@ export class MnemomApiError extends Error {
|
|
|
85
85
|
conflictAgentId;
|
|
86
86
|
/** Top-level enforce-hook sibling (present only on a spec-rewritten body). */
|
|
87
87
|
specDeviation;
|
|
88
|
+
/** The server's `Retry-After` in ms (delta-seconds or HTTP-date form), when it sent a valid one. */
|
|
89
|
+
retryAfterMs;
|
|
88
90
|
constructor(status, message, opts = {}) {
|
|
89
91
|
super(message);
|
|
90
92
|
this.name = "MnemomApiError";
|
|
93
|
+
this.retryAfterMs = opts.retryAfterMs;
|
|
91
94
|
this.status = status;
|
|
92
95
|
this.code = opts.code;
|
|
93
96
|
this.details = opts.details;
|
|
@@ -110,8 +113,21 @@ export async function readApiError(response, fallback) {
|
|
|
110
113
|
details: parsed.details,
|
|
111
114
|
conflictAgentId: parsed.conflict_agent_id,
|
|
112
115
|
specDeviation: parsed.spec_deviation,
|
|
116
|
+
retryAfterMs: parseRetryAfterMs(response.headers?.get("retry-after"), Date.now()),
|
|
113
117
|
});
|
|
114
118
|
}
|
|
119
|
+
/** `Retry-After` header to ms: delta-seconds or an HTTP-date. Anything else is undefined. */
|
|
120
|
+
export function parseRetryAfterMs(value, nowMs) {
|
|
121
|
+
if (!value)
|
|
122
|
+
return undefined;
|
|
123
|
+
const v = value.trim();
|
|
124
|
+
if (/^\d{1,9}$/.test(v))
|
|
125
|
+
return Number(v) * 1000;
|
|
126
|
+
if (!/[A-Za-z]/.test(v))
|
|
127
|
+
return undefined; // an HTTP-date names its day and month
|
|
128
|
+
const at = Date.parse(v);
|
|
129
|
+
return Number.isNaN(at) ? undefined : Math.max(0, at - nowMs);
|
|
130
|
+
}
|
|
115
131
|
export async function postApi(endpoint, body, opts = {}) {
|
|
116
132
|
const url = validateUrl(`${API_BASE}${endpoint}`);
|
|
117
133
|
const headers = {
|
package/dist/lib/config.d.ts
CHANGED
|
@@ -42,6 +42,13 @@ export declare function setRing(ring: CellRing | undefined): void;
|
|
|
42
42
|
export declare function getRing(): CellRing | undefined;
|
|
43
43
|
/** Override the active API base for this process (clears on undefined/empty). */
|
|
44
44
|
export declare function setApiUrlOverride(url: string | undefined): void;
|
|
45
|
+
/**
|
|
46
|
+
* True when the API is not production but the gateway is the production one. A lease key minted
|
|
47
|
+
* by a non-production cell's API means nothing to the production gateway, which answers every
|
|
48
|
+
* model call 401 and still receives the key (door2-path run 41783208169), so `agent run` refuses
|
|
49
|
+
* this pairing. A base that does not parse decides nothing. Pure.
|
|
50
|
+
*/
|
|
51
|
+
export declare function gatewayRingMismatch(apiUrl: string, gatewayUrl: string): boolean;
|
|
45
52
|
export declare function getApiUrl(): string;
|
|
46
53
|
export declare function getGatewayUrl(): string;
|
|
47
54
|
export declare function getWebsiteUrl(): string;
|
package/dist/lib/config.js
CHANGED
|
@@ -113,6 +113,27 @@ function ringUrls() {
|
|
|
113
113
|
const ring = getRing();
|
|
114
114
|
return ring ? RING_URLS[ring] : undefined;
|
|
115
115
|
}
|
|
116
|
+
function originOf(url) {
|
|
117
|
+
try {
|
|
118
|
+
return new URL(url).origin.toLowerCase();
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* True when the API is not production but the gateway is the production one. A lease key minted
|
|
126
|
+
* by a non-production cell's API means nothing to the production gateway, which answers every
|
|
127
|
+
* model call 401 and still receives the key (door2-path run 41783208169), so `agent run` refuses
|
|
128
|
+
* this pairing. A base that does not parse decides nothing. Pure.
|
|
129
|
+
*/
|
|
130
|
+
export function gatewayRingMismatch(apiUrl, gatewayUrl) {
|
|
131
|
+
const api = originOf(apiUrl);
|
|
132
|
+
const gateway = originOf(gatewayUrl);
|
|
133
|
+
if (api === null || gateway === null)
|
|
134
|
+
return false;
|
|
135
|
+
return api !== originOf(API_URLS.production) && gateway === originOf(GATEWAY_URLS.production);
|
|
136
|
+
}
|
|
116
137
|
export function getApiUrl() {
|
|
117
138
|
return (apiUrlOverride ?? envBase("MNEMOM_API_URL") ?? ringUrls()?.api ?? API_URLS[getEnvironment()]);
|
|
118
139
|
}
|
|
@@ -108,6 +108,17 @@ export declare class DurableApi {
|
|
|
108
108
|
createSession(cid: string, agentId: string, body: Record<string, unknown>): Promise<ApiAnswer>;
|
|
109
109
|
/** An owner session verb (S1 ROUTES.own_*). */
|
|
110
110
|
ownerVerb(route: RouteName, cid: string, agentId: string, body?: unknown): Promise<ApiAnswer>;
|
|
111
|
+
/**
|
|
112
|
+
* An S1 amendment 60 inbox route on a session (own_inbox_send, own_inbox_list, own_inbox_senders,
|
|
113
|
+
* own_inbox_sender_add, own_inbox_sender_remove), with the route's own method. `credential`
|
|
114
|
+
* sends an allowlisted sender agent's key instead of the caller's (own_inbox_send only).
|
|
115
|
+
*/
|
|
116
|
+
inbox(route: "own_inbox_send" | "own_inbox_list" | "own_inbox_senders" | "own_inbox_sender_add" | "own_inbox_sender_remove", cid: string, agentId: string, body?: unknown, credential?: DurableCredential): Promise<ApiAnswer>;
|
|
117
|
+
/**
|
|
118
|
+
* S1 amendment 61: GET ROUTES.own_questions / POST ROUTES.own_answer for one session (owner
|
|
119
|
+
* credential only; an allowlisted sender may not answer).
|
|
120
|
+
*/
|
|
121
|
+
questions(route: "own_questions" | "own_answer", cid: string, agentId: string, body?: unknown): Promise<ApiAnswer>;
|
|
111
122
|
/** POST ROUTES.own_lock with the closed S1 lock body (spec, executor_id, own_agent). */
|
|
112
123
|
lock(cid: string, agentId: string, body: unknown): Promise<ApiAnswer>;
|
|
113
124
|
/**
|
package/dist/lib/durable/api.js
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* GET /v1/agent/sessions/{cid}/lock?agent_id=&dry_run=true -> the doctor's lock preflight
|
|
18
18
|
*/
|
|
19
19
|
import { randomUUID } from "node:crypto";
|
|
20
|
-
import { routePath } from "../../vendor/shared/durable-contract/routes.js";
|
|
20
|
+
import { ROUTES, routePath } from "../../vendor/shared/durable-contract/routes.js";
|
|
21
21
|
/** CLI registry timeout, connect plus total (C01-R18: at most 10 s). */
|
|
22
22
|
export const REGISTRY_TIMEOUT_MS = 10_000;
|
|
23
23
|
/** The registry could not be read: transport error, timeout or a non-durable 5xx. */
|
|
@@ -75,7 +75,7 @@ export class DurableApi {
|
|
|
75
75
|
this.deps = deps;
|
|
76
76
|
}
|
|
77
77
|
async request(method, path, opts = {}) {
|
|
78
|
-
const cred = await this.deps.credential();
|
|
78
|
+
const cred = opts.credential ?? (await this.deps.credential());
|
|
79
79
|
const headers = { Accept: "application/json" };
|
|
80
80
|
if (cred.type === "jwt")
|
|
81
81
|
headers.Authorization = `Bearer ${cred.token.trim()}`;
|
|
@@ -90,7 +90,8 @@ export class DurableApi {
|
|
|
90
90
|
headers["Content-Type"] = "application/json";
|
|
91
91
|
body = JSON.stringify(opts.body);
|
|
92
92
|
}
|
|
93
|
-
|
|
93
|
+
// Every write carries one: POST, and DELETE (own_inbox_sender_remove) since amendment 60.
|
|
94
|
+
if (method !== "GET")
|
|
94
95
|
headers["Idempotency-Key"] = randomUUID();
|
|
95
96
|
const url = new URL(path, this.deps.apiBase().replace(/\/+$/, "") + "/").href;
|
|
96
97
|
const ctrl = new AbortController();
|
|
@@ -159,6 +160,30 @@ export class DurableApi {
|
|
|
159
160
|
body,
|
|
160
161
|
});
|
|
161
162
|
}
|
|
163
|
+
/**
|
|
164
|
+
* An S1 amendment 60 inbox route on a session (own_inbox_send, own_inbox_list, own_inbox_senders,
|
|
165
|
+
* own_inbox_sender_add, own_inbox_sender_remove), with the route's own method. `credential`
|
|
166
|
+
* sends an allowlisted sender agent's key instead of the caller's (own_inbox_send only).
|
|
167
|
+
*/
|
|
168
|
+
async inbox(route, cid, agentId, body, credential) {
|
|
169
|
+
if (credential !== undefined && route !== "own_inbox_send")
|
|
170
|
+
throw new Error(`a sender credential goes only with own_inbox_send (got ${route})`);
|
|
171
|
+
const method = ROUTES[route].method;
|
|
172
|
+
return this.request(method, `${routePath(route, cid)}${this.q({ agent_id: agentId })}`, {
|
|
173
|
+
...(body === undefined ? {} : { body }),
|
|
174
|
+
...(credential ? { credential } : {}),
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* S1 amendment 61: GET ROUTES.own_questions / POST ROUTES.own_answer for one session (owner
|
|
179
|
+
* credential only; an allowlisted sender may not answer).
|
|
180
|
+
*/
|
|
181
|
+
async questions(route, cid, agentId, body) {
|
|
182
|
+
const method = ROUTES[route].method;
|
|
183
|
+
return this.request(method, `${routePath(route, cid)}${this.q({ agent_id: agentId })}`, {
|
|
184
|
+
...(body === undefined ? {} : { body }),
|
|
185
|
+
});
|
|
186
|
+
}
|
|
162
187
|
/** POST ROUTES.own_lock with the closed S1 lock body (spec, executor_id, own_agent). */
|
|
163
188
|
async lock(cid, agentId, body) {
|
|
164
189
|
return this.ownerVerb("own_lock", cid, agentId, body);
|
|
@@ -16,7 +16,7 @@ export declare const DURABLE_ID: "durable";
|
|
|
16
16
|
/** `mnemom agent <word>` words that exist only with durable on. */
|
|
17
17
|
export declare const DURABLE_VERB_WORDS: readonly ["lock", "pause", "resume", "done", "kill", "amend", "host", "containment"];
|
|
18
18
|
/** `mnemom agent sessions <sub>` sub-verbs that exist only with durable on. */
|
|
19
|
-
export declare const DURABLE_SESSIONS_SUBVERBS: readonly ["unlock", "adopt"];
|
|
19
|
+
export declare const DURABLE_SESSIONS_SUBVERBS: readonly ["unlock", "adopt", "send", "senders", "questions", "answer"];
|
|
20
20
|
export declare function durableOn(enabled: readonly ExperimentalId[]): boolean;
|
|
21
21
|
/**
|
|
22
22
|
* The options that take a value, by flag spelling, as commander parses them: a `required` one always
|
package/dist/lib/durable/gate.js
CHANGED
|
@@ -25,7 +25,14 @@ export const DURABLE_VERB_WORDS = [
|
|
|
25
25
|
"containment",
|
|
26
26
|
];
|
|
27
27
|
/** `mnemom agent sessions <sub>` sub-verbs that exist only with durable on. */
|
|
28
|
-
export const DURABLE_SESSIONS_SUBVERBS = [
|
|
28
|
+
export const DURABLE_SESSIONS_SUBVERBS = [
|
|
29
|
+
"unlock",
|
|
30
|
+
"adopt",
|
|
31
|
+
"send",
|
|
32
|
+
"senders",
|
|
33
|
+
"questions",
|
|
34
|
+
"answer",
|
|
35
|
+
];
|
|
29
36
|
export function durableOn(enabled) {
|
|
30
37
|
return enabled.includes(DURABLE_ID);
|
|
31
38
|
}
|
|
@@ -7,6 +7,8 @@ import { type LauncherConfig } from "../launcher-config.js";
|
|
|
7
7
|
import { type ProcDeps } from "../../durable/proc.js";
|
|
8
8
|
import type { LauncherKey } from "../../vendor/shared/durable-contract/fixlines.js";
|
|
9
9
|
import type { DurableVerbDeps } from "../../commands/durable-verbs.js";
|
|
10
|
+
import type { InboxVerbDeps } from "../../commands/durable-inbox.js";
|
|
11
|
+
import type { QuestionVerbDeps } from "../../commands/durable-questions.js";
|
|
10
12
|
import type { SessionsReadDeps } from "../../commands/durable-sessions.js";
|
|
11
13
|
import { type DoctorLine } from "../../commands/durable-doctor.js";
|
|
12
14
|
import { type OriginalLaunch, type LockDeps, type LockOutcome } from "../../commands/durable-lock.js";
|
|
@@ -24,6 +26,9 @@ export declare function liveLauncherKey(load?: () => {
|
|
|
24
26
|
/** PIDs of local claude processes whose argv names `ccSessionId` (pure over a `ps` listing). */
|
|
25
27
|
export declare function claudePidsFor(psOutput: string, ccSessionId: string, selfPid: number): number[];
|
|
26
28
|
export declare function liveVerbDeps(): DurableVerbDeps;
|
|
29
|
+
/** The inbox verbs' deps: the verb deps, the environment (sender key) and stdin. */
|
|
30
|
+
export declare function liveInboxDeps(): InboxVerbDeps;
|
|
31
|
+
export declare function liveQuestionDeps(): QuestionVerbDeps;
|
|
27
32
|
export declare function liveSessionsReadDeps(): SessionsReadDeps;
|
|
28
33
|
export declare function liveLockDeps(): LockDeps;
|
|
29
34
|
/** The live original's exact argv, its parent launcher's passthrough and its shape (C14-R13). */
|
package/dist/lib/durable/live.js
CHANGED
|
@@ -120,6 +120,25 @@ export function liveVerbDeps() {
|
|
|
120
120
|
stopTrackedClaude,
|
|
121
121
|
};
|
|
122
122
|
}
|
|
123
|
+
/** The inbox verbs' deps: the verb deps, the environment (sender key) and stdin. */
|
|
124
|
+
export function liveInboxDeps() {
|
|
125
|
+
const base = liveVerbDeps();
|
|
126
|
+
return {
|
|
127
|
+
...base,
|
|
128
|
+
api: base.api,
|
|
129
|
+
env: process.env,
|
|
130
|
+
readStdin: async () => {
|
|
131
|
+
const chunks = [];
|
|
132
|
+
for await (const c of process.stdin)
|
|
133
|
+
chunks.push(c);
|
|
134
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
export function liveQuestionDeps() {
|
|
139
|
+
const base = liveVerbDeps();
|
|
140
|
+
return { ...base, api: base.api };
|
|
141
|
+
}
|
|
123
142
|
export function liveSessionsReadDeps() {
|
|
124
143
|
return {
|
|
125
144
|
api: liveDurableApi(),
|
package/dist/lib/experimental.js
CHANGED
|
@@ -54,6 +54,7 @@ export const EXPERIMENTAL_FEATURES = [
|
|
|
54
54
|
"slack_channel",
|
|
55
55
|
"slack_listen",
|
|
56
56
|
"slack_allow",
|
|
57
|
+
"slack_owner",
|
|
57
58
|
"slack_aliases",
|
|
58
59
|
"slack_sticky_threads",
|
|
59
60
|
],
|
|
@@ -217,6 +218,7 @@ export function experimentalIdForConfigKey(key, value) {
|
|
|
217
218
|
switch (key) {
|
|
218
219
|
case "slack_channel":
|
|
219
220
|
case "slack_allow":
|
|
221
|
+
case "slack_owner":
|
|
220
222
|
return "slack";
|
|
221
223
|
case "slack_listen":
|
|
222
224
|
case "slack_aliases":
|
|
@@ -299,6 +301,7 @@ export function configKeyFeature(key) {
|
|
|
299
301
|
if (key === "slack_channel" ||
|
|
300
302
|
key === "slack_listen" ||
|
|
301
303
|
key === "slack_allow" ||
|
|
304
|
+
key === "slack_owner" ||
|
|
302
305
|
key === "slack_aliases" ||
|
|
303
306
|
key === "slack_sticky_threads")
|
|
304
307
|
return "slack";
|
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
|
|
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
|
|
10
|
+
* `resolveAnthropicKey`, or a subscription launch's Mnemom key), `x-mnemom-agent: <record.agent_slug>`,
|
|
11
|
+
* `x-mnemom-api-key` when the 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
|
*
|
|
@@ -25,12 +25,19 @@ 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
27
|
export type GoalControlRecord = Pick<SessionRecord, "gateway_host" | "agent_slug" | "conversation_id"> & Partial<Pick<SessionRecord, "agent_id" | "agent_pinned">>;
|
|
28
|
-
|
|
28
|
+
/**
|
|
29
|
+
* An api-key launch: its Anthropic key (sent as `x-api-key`) and the Mnemom key it sent beside it,
|
|
30
|
+
* if any. A subscription launch had no Anthropic key: its Mnemom key is the credential.
|
|
31
|
+
*/
|
|
32
|
+
export type GoalControlCreds = {
|
|
29
33
|
/** The Anthropic key the launch sent as `x-api-key`. */
|
|
30
34
|
anthropicKey: string;
|
|
31
35
|
/** The Mnemom key, only when the launch sent `x-mnemom-api-key`. */
|
|
32
36
|
mnemomKey?: string | null;
|
|
33
|
-
}
|
|
37
|
+
} | {
|
|
38
|
+
anthropicKey?: undefined;
|
|
39
|
+
mnemomKey: string;
|
|
40
|
+
};
|
|
34
41
|
export type FetchLike = (url: string, init: RequestInit) => Promise<Response>;
|
|
35
42
|
export interface GoalControlDeps {
|
|
36
43
|
fetch: FetchLike;
|
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
|
|
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
|
|
10
|
+
* `resolveAnthropicKey`, or a subscription launch's Mnemom key), `x-mnemom-agent: <record.agent_slug>`,
|
|
11
|
+
* `x-mnemom-api-key` when the 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
|
*
|
|
@@ -52,7 +52,8 @@ export function goalControlUrl(record, op, thread = "main") {
|
|
|
52
52
|
/** The launch's identity headers (never logged). */
|
|
53
53
|
export function goalControlHeaders(record, creds) {
|
|
54
54
|
const headers = {
|
|
55
|
-
|
|
55
|
+
// A subscription session's Mnemom key: the gateway resolves the claimed agent from it.
|
|
56
|
+
"x-api-key": creds.anthropicKey !== undefined ? creds.anthropicKey : creds.mnemomKey,
|
|
56
57
|
"x-mnemom-agent": record.agent_slug,
|
|
57
58
|
accept: "application/json",
|
|
58
59
|
};
|
|
@@ -49,6 +49,11 @@ export interface LauncherConfig {
|
|
|
49
49
|
slackListen?: boolean;
|
|
50
50
|
/** Slack member ids whose messages the router may deliver into a session. */
|
|
51
51
|
slackAllow?: string[];
|
|
52
|
+
/**
|
|
53
|
+
* The owner's own Slack member ids. With durable on, a message from one of them to a locked
|
|
54
|
+
* durable session goes to its durable inbox as from the owner (S1 amendment 60).
|
|
55
|
+
*/
|
|
56
|
+
slackOwner?: string[];
|
|
52
57
|
/**
|
|
53
58
|
* Router: honour `mnemom agent slack alias` handles (off when unset). Off means the
|
|
54
59
|
* router routes exactly as it did before aliases existed; registered aliases are kept
|
|
@@ -100,14 +100,14 @@ function coerceCompactAt(raw) {
|
|
|
100
100
|
parseCompactAt(v, "compact_at");
|
|
101
101
|
return v;
|
|
102
102
|
}
|
|
103
|
-
function coerceMemberIds(raw) {
|
|
103
|
+
function coerceMemberIds(raw, key = "slack_allow") {
|
|
104
104
|
const ids = raw
|
|
105
105
|
.split(/[\s,]+/)
|
|
106
106
|
.map((x) => x.trim())
|
|
107
107
|
.filter(Boolean);
|
|
108
108
|
const bad = ids.filter((x) => !/^[UW][A-Z0-9]{6,}$/.test(x));
|
|
109
109
|
if (!ids.length || bad.length) {
|
|
110
|
-
throw new Error(
|
|
110
|
+
throw new Error(`${key} must be Slack member ids like U012ABCDEF (Profile → ⋯ → Copy member ID); got '${raw}'.`);
|
|
111
111
|
}
|
|
112
112
|
return ids;
|
|
113
113
|
}
|
|
@@ -238,6 +238,14 @@ export const CONFIG_KEYS = [
|
|
|
238
238
|
c.slackAllow = coerceMemberIds(v);
|
|
239
239
|
},
|
|
240
240
|
},
|
|
241
|
+
{
|
|
242
|
+
key: "slack_owner",
|
|
243
|
+
describe: "Your own Slack member ids (comma-separated): with durable on, your messages reach a locked durable session's inbox as from you",
|
|
244
|
+
get: (c) => c.slackOwner?.join(","),
|
|
245
|
+
set: (c, v) => {
|
|
246
|
+
c.slackOwner = coerceMemberIds(v, "slack_owner");
|
|
247
|
+
},
|
|
248
|
+
},
|
|
241
249
|
{
|
|
242
250
|
key: "slack_aliases",
|
|
243
251
|
describe: "Slack router: route `mnemom agent slack alias` handles, queueing for a stopped session (off by default)",
|
|
@@ -366,6 +374,10 @@ export function parseLauncherConfig(text) {
|
|
|
366
374
|
const v = raw.slack_allow;
|
|
367
375
|
cfg.slackAllow = coerceMemberIds(Array.isArray(v) ? v.map(String).join(",") : String(v));
|
|
368
376
|
}
|
|
377
|
+
if (raw.slack_owner !== undefined) {
|
|
378
|
+
const v = raw.slack_owner;
|
|
379
|
+
cfg.slackOwner = coerceMemberIds(Array.isArray(v) ? v.map(String).join(",") : String(v), "slack_owner");
|
|
380
|
+
}
|
|
369
381
|
if (raw.slack_channel !== undefined)
|
|
370
382
|
cfg.slackChannel = coerceNonEmpty("slack_channel", str(raw.slack_channel) ?? "");
|
|
371
383
|
if (raw.slack_aliases !== undefined)
|
|
@@ -452,6 +464,8 @@ export function serializeLauncherConfig(cfg) {
|
|
|
452
464
|
doc.slack_listen = cfg.slackListen;
|
|
453
465
|
if (cfg.slackAllow !== undefined)
|
|
454
466
|
doc.slack_allow = cfg.slackAllow;
|
|
467
|
+
if (cfg.slackOwner !== undefined)
|
|
468
|
+
doc.slack_owner = cfg.slackOwner;
|
|
455
469
|
if (cfg.slackAliases !== undefined)
|
|
456
470
|
doc.slack_aliases = cfg.slackAliases;
|
|
457
471
|
if (cfg.slackStickyThreads !== undefined)
|
|
@@ -550,6 +564,7 @@ export function withoutDisabledExperimental(cfg, enabled) {
|
|
|
550
564
|
delete out.slackChannel;
|
|
551
565
|
delete out.slackListen;
|
|
552
566
|
delete out.slackAllow;
|
|
567
|
+
delete out.slackOwner;
|
|
553
568
|
delete out.slackAliases;
|
|
554
569
|
delete out.slackStickyThreads;
|
|
555
570
|
}
|
package/dist/lib/prompt.d.ts
CHANGED
|
@@ -19,9 +19,11 @@ export declare function askInput(question: string, mask?: boolean): Promise<stri
|
|
|
19
19
|
export declare function askMultiSelect(question: string, options: string[]): Promise<string[]>;
|
|
20
20
|
/**
|
|
21
21
|
* Single-select prompt. Displays numbered options, user enters a number.
|
|
22
|
-
* Returns selected label or null if invalid.
|
|
22
|
+
* Returns selected label or null if invalid. With `defaultIndex` (0-based), pressing Enter on an
|
|
23
|
+
* interactive terminal picks that option; piped (non-TTY) input never uses it, so an empty or missing
|
|
24
|
+
* line still returns null and the caller keeps its fail-closed fallback.
|
|
23
25
|
*/
|
|
24
|
-
export declare function askSelect(question: string, options: string[]): Promise<string | null>;
|
|
26
|
+
export declare function askSelect(question: string, options: string[], defaultIndex?: number): Promise<string | null>;
|
|
25
27
|
/**
|
|
26
28
|
* Check if running in non-interactive mode (no TTY)
|
|
27
29
|
*/
|
package/dist/lib/prompt.js
CHANGED
|
@@ -80,8 +80,12 @@ export async function askInput(question, mask = false) {
|
|
|
80
80
|
process.stdout.write(`${question} `);
|
|
81
81
|
const lines = await readPipedStdinLines();
|
|
82
82
|
const next = lines.shift();
|
|
83
|
-
|
|
84
|
-
|
|
83
|
+
const answer = (next ?? "").trim();
|
|
84
|
+
// A terminal shows what the user typed; a pipe does not, which left a bare "Question: " line in
|
|
85
|
+
// logs and scan output. Echo an unmasked answer so the line reads "Question: value". A masked
|
|
86
|
+
// answer (API key / password) is never echoed.
|
|
87
|
+
process.stdout.write(mask ? "\n" : `${answer}\n`);
|
|
88
|
+
return answer;
|
|
85
89
|
}
|
|
86
90
|
const rl = readline.createInterface({
|
|
87
91
|
input: process.stdin,
|
|
@@ -169,9 +173,11 @@ function resolveSelection(answer, options) {
|
|
|
169
173
|
}
|
|
170
174
|
/**
|
|
171
175
|
* Single-select prompt. Displays numbered options, user enters a number.
|
|
172
|
-
* Returns selected label or null if invalid.
|
|
176
|
+
* Returns selected label or null if invalid. With `defaultIndex` (0-based), pressing Enter on an
|
|
177
|
+
* interactive terminal picks that option; piped (non-TTY) input never uses it, so an empty or missing
|
|
178
|
+
* line still returns null and the caller keeps its fail-closed fallback.
|
|
173
179
|
*/
|
|
174
|
-
export async function askSelect(question, options) {
|
|
180
|
+
export async function askSelect(question, options, defaultIndex) {
|
|
175
181
|
console.log(`\n${question}`);
|
|
176
182
|
for (let i = 0; i < options.length; i++) {
|
|
177
183
|
console.log(` ${i + 1}) ${options[i]}`);
|
|
@@ -194,8 +200,12 @@ export async function askSelect(question, options) {
|
|
|
194
200
|
output: process.stdout,
|
|
195
201
|
});
|
|
196
202
|
return new Promise((resolve) => {
|
|
197
|
-
|
|
203
|
+
const hasDefault = defaultIndex !== undefined && defaultIndex >= 0 && defaultIndex < options.length;
|
|
204
|
+
const prompt = hasDefault ? `Select [${defaultIndex + 1}]: ` : "Select: ";
|
|
205
|
+
rl.question(prompt, (answer) => {
|
|
198
206
|
rl.close();
|
|
207
|
+
if (hasDefault && answer.trim() === "")
|
|
208
|
+
return resolve(options[defaultIndex]);
|
|
199
209
|
resolve(resolveSelection(answer, options));
|
|
200
210
|
});
|
|
201
211
|
});
|
package/dist/lib/runner/job.d.ts
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
import { type HttpResult, type RunnerApi } from "./executor-client.js";
|
|
15
15
|
import { type OutcomeFields, type RefusedReason } from "./outcome.js";
|
|
16
16
|
import { type PhaseInvocation } from "./phases.js";
|
|
17
|
-
import type
|
|
17
|
+
import { type ModelProxy, type ProxyState } from "./proxy.js";
|
|
18
18
|
import { type RunnerFailMode } from "./settings.js";
|
|
19
19
|
/** A claimed job's working copy. */
|
|
20
20
|
export interface Workspace {
|
|
@@ -42,7 +42,7 @@ export interface JobDeps {
|
|
|
42
42
|
/** Run one phase's process; resolves its exit code. Rejects only when it could not start. */
|
|
43
43
|
runPhase(inv: PhaseInvocation, ws: Workspace, modelUrl: string, signal: AbortSignal): Promise<number>;
|
|
44
44
|
/** Push the job branch and open its PR; resolves the PR URL. */
|
|
45
|
-
openPr(repo: string, ws: Workspace, title: string): Promise<string>;
|
|
45
|
+
openPr(repo: string, ws: Workspace, title: string, body: string): Promise<string>;
|
|
46
46
|
/** Commits on the job branch that the base does not have; 0 means there is nothing to open. */
|
|
47
47
|
commitsAhead(ws: Workspace): Promise<number>;
|
|
48
48
|
enableAutoMerge(prUrl: string): Promise<void>;
|
package/dist/lib/runner/job.js
CHANGED
|
@@ -19,6 +19,8 @@ import { sealedContractHeader } from "./contract-header.js";
|
|
|
19
19
|
import { errorCode, errorDetails } from "./executor-client.js";
|
|
20
20
|
import { doneOutcome, failedOutcome, phaseReason, refusedOutcome, } from "./outcome.js";
|
|
21
21
|
import { planPhase } from "./phases.js";
|
|
22
|
+
import { canonNodeKey, prBody } from "./pr-body.js";
|
|
23
|
+
import { requestSummary } from "./proxy.js";
|
|
22
24
|
import { resolveMergeStrategy } from "./settings.js";
|
|
23
25
|
const TOLERATED = RUNNER_OPEN_MODE_TOLERATED;
|
|
24
26
|
const OPEN_MODE_RETRIES = 3;
|
|
@@ -154,7 +156,14 @@ function parseJob(body) {
|
|
|
154
156
|
const agentId = str(b.agent_id);
|
|
155
157
|
if (agentId === null || !AGENT_ID_RE.test(agentId))
|
|
156
158
|
return null;
|
|
157
|
-
return {
|
|
159
|
+
return {
|
|
160
|
+
repo,
|
|
161
|
+
agent_id: agentId,
|
|
162
|
+
contract_sha256: sha,
|
|
163
|
+
version,
|
|
164
|
+
contract: b.contract,
|
|
165
|
+
canon_node_key: canonNodeKey(b.canon_node_key),
|
|
166
|
+
};
|
|
158
167
|
}
|
|
159
168
|
/**
|
|
160
169
|
* Run one candidate session to an outcome. Never throws for a Mnemom error: every one is an
|
|
@@ -354,7 +363,7 @@ export async function runJob(sessionId, opts, deps) {
|
|
|
354
363
|
await reportFail(deps, sessionId, claim.epoch, "runner_failed", "pr_no_changes");
|
|
355
364
|
return { kind: "outcome", outcome: failedOutcome(facts, "pr_no_changes") };
|
|
356
365
|
}
|
|
357
|
-
facts.pr_url = await deps.openPr(job.repo, ws, `mnemom: ${bundle.workflow.id} job ${sessionId.slice(0, 8)}
|
|
366
|
+
facts.pr_url = await deps.openPr(job.repo, ws, `mnemom: ${bundle.workflow.id} job ${sessionId.slice(0, 8)}`, prBody(job));
|
|
358
367
|
}
|
|
359
368
|
catch (e) {
|
|
360
369
|
deps.log(`opening the PR failed: ${e.message}`);
|
|
@@ -377,7 +386,11 @@ export async function runJob(sessionId, opts, deps) {
|
|
|
377
386
|
if (heartbeat)
|
|
378
387
|
clearInterval(heartbeat);
|
|
379
388
|
opts.stop.removeEventListener("abort", onStop);
|
|
380
|
-
|
|
389
|
+
if (proxy) {
|
|
390
|
+
await proxy.close();
|
|
391
|
+
// One line per job that reached its phases: what every model request carried (W3.11).
|
|
392
|
+
deps.log(`model requests: ${JSON.stringify(requestSummary(proxy.forwarded))}`);
|
|
393
|
+
}
|
|
381
394
|
// A workspace left behind never changes the outcome; it is logged for the operator.
|
|
382
395
|
if (ws)
|
|
383
396
|
await deps.cleanup(ws).catch((e) => deps.log(`cleanup failed: ${e.message}`));
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The body of the PR `mnemom agent run` opens: the job's goal line from the sealed contract and the
|
|
3
|
+
* Canon node its seal points to, both from the job read the runner already holds (no extra call).
|
|
4
|
+
* Never the contract body or any key: the goal is the contract's `statement`, on one line and
|
|
5
|
+
* capped; the node is cited only when the job read names a well-formed one, and a job without one
|
|
6
|
+
* still opens its PR, with no citation.
|
|
7
|
+
*/
|
|
8
|
+
/** The goal line's cap, in characters: a PR body line, not the contract. */
|
|
9
|
+
export declare const PR_GOAL_MAX = 280;
|
|
10
|
+
/** The node key when well formed, else null. */
|
|
11
|
+
export declare function canonNodeKey(v: unknown): string | null;
|
|
12
|
+
/** The sealed contract's statement on one line, capped; null when the bytes carry none. */
|
|
13
|
+
export declare function goalLine(contract: string): string | null;
|
|
14
|
+
export declare function prBody(job: {
|
|
15
|
+
contract: string;
|
|
16
|
+
canon_node_key: string | null;
|
|
17
|
+
}): string;
|