@mnemom/mnemom 0.17.34-next.1 → 0.17.34-next.2
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/base-url-proxy.mjs +18 -4
- package/dist/commands/agent-config.js +5 -1
- package/dist/commands/agent-doctor.d.ts +9 -0
- package/dist/commands/agent-doctor.js +4 -0
- package/dist/commands/agent-session.d.ts +1 -2
- package/dist/commands/agent-session.js +1 -2
- package/dist/commands/agent-setup.d.ts +6 -0
- package/dist/commands/agent-setup.js +12 -1
- package/dist/commands/agent.d.ts +11 -0
- package/dist/commands/agent.js +145 -46
- package/dist/commands/durable-doctor.d.ts +50 -0
- package/dist/commands/durable-doctor.js +186 -0
- package/dist/commands/durable-lock.d.ts +254 -0
- package/dist/commands/durable-lock.js +595 -0
- package/dist/commands/durable-sessions.d.ts +87 -0
- package/dist/commands/durable-sessions.js +303 -0
- package/dist/commands/durable-verbs.d.ts +116 -0
- package/dist/commands/durable-verbs.js +393 -0
- package/dist/durable/flock.d.ts +26 -0
- package/dist/durable/flock.js +80 -0
- package/dist/durable/launch-spec.d.ts +7 -1
- package/dist/durable/launch-spec.js +10 -3
- package/dist/durable/seat-proxy-entry.d.ts +11 -0
- package/dist/durable/seat-proxy-entry.js +25 -0
- package/dist/durable/seat-proxy.d.ts +107 -0
- package/dist/durable/seat-proxy.js +599 -0
- package/dist/durable/seat-socket.d.ts +46 -0
- package/dist/durable/seat-socket.js +179 -0
- package/dist/durable/seat-spec-validator.d.ts +28 -0
- package/dist/durable/seat-spec-validator.js +59 -0
- package/dist/durable/test-mode.mjs +81 -0
- package/dist/durable/turn-chain.d.ts +55 -0
- package/dist/durable/turn-chain.js +178 -0
- package/dist/host/auto-continue.d.ts +67 -0
- package/dist/host/auto-continue.js +118 -0
- package/dist/host/bootid.d.ts +13 -0
- package/dist/host/bootid.js +41 -0
- package/dist/host/client.d.ts +123 -0
- package/dist/host/client.js +171 -0
- package/dist/host/commands.d.ts +2 -0
- package/dist/host/commands.js +122 -0
- package/dist/host/control.d.ts +33 -0
- package/dist/host/control.js +144 -0
- package/dist/host/daemon.d.ts +217 -0
- package/dist/host/daemon.js +606 -0
- package/dist/host/gate.d.ts +46 -0
- package/dist/host/gate.js +82 -0
- package/dist/host/grant.d.ts +24 -0
- package/dist/host/grant.js +24 -0
- package/dist/host/instance.d.ts +35 -0
- package/dist/host/instance.js +62 -0
- package/dist/host/integrity.d.ts +35 -0
- package/dist/host/integrity.js +115 -0
- package/dist/host/launchd.d.ts +16 -0
- package/dist/host/launchd.js +45 -0
- package/dist/host/lock.d.ts +20 -0
- package/dist/host/lock.js +63 -0
- package/dist/host/owner.d.ts +55 -0
- package/dist/host/owner.js +121 -0
- package/dist/host/plist.d.ts +16 -0
- package/dist/host/plist.js +85 -0
- package/dist/host/procs.d.ts +27 -0
- package/dist/host/procs.js +105 -0
- package/dist/host/run.d.ts +66 -0
- package/dist/host/run.js +314 -0
- package/dist/host/runtime.d.ts +8 -0
- package/dist/host/runtime.js +22 -0
- package/dist/host/s1-names.d.ts +22 -0
- package/dist/host/s1-names.js +21 -0
- package/dist/host/seat-credential.d.ts +27 -0
- package/dist/host/seat-credential.js +41 -0
- package/dist/host/seat-launch-main.d.ts +1 -0
- package/dist/host/seat-launch-main.js +59 -0
- package/dist/host/seat-launch.d.ts +116 -0
- package/dist/host/seat-launch.js +302 -0
- package/dist/host/seat.d.ts +48 -0
- package/dist/host/seat.js +149 -0
- package/dist/host/state.d.ts +33 -0
- package/dist/host/state.js +117 -0
- package/dist/host/status.d.ts +101 -0
- package/dist/host/status.js +307 -0
- package/dist/host/tmux.d.ts +56 -0
- package/dist/host/tmux.js +182 -0
- package/dist/host/transcript.d.ts +30 -0
- package/dist/host/transcript.js +130 -0
- package/dist/host/verbs.d.ts +50 -0
- package/dist/host/verbs.js +271 -0
- package/dist/index.d.ts +8 -6
- package/dist/index.js +197 -35
- package/dist/lib/agent.d.ts +8 -26
- package/dist/lib/agent.js +11 -26
- package/dist/lib/claude-plugin.d.ts +11 -2
- package/dist/lib/claude-plugin.js +10 -1
- package/dist/lib/durable/api.d.ts +136 -0
- package/dist/lib/durable/api.js +220 -0
- package/dist/lib/durable/codes.d.ts +8 -0
- package/dist/lib/durable/codes.js +2 -0
- package/dist/lib/durable/first-turn.d.ts +50 -0
- package/dist/lib/durable/first-turn.js +102 -0
- package/dist/lib/durable/gate.d.ts +48 -0
- package/dist/lib/durable/gate.js +122 -0
- package/dist/lib/durable/handover.d.ts +101 -0
- package/dist/lib/durable/handover.js +175 -0
- package/dist/lib/durable/launch.d.ts +182 -0
- package/dist/lib/durable/launch.js +443 -0
- package/dist/lib/durable/launching.d.ts +15 -0
- package/dist/lib/durable/launching.js +70 -0
- package/dist/lib/durable/live.d.ts +194 -0
- package/dist/lib/durable/live.js +635 -0
- package/dist/lib/durable/refusal.d.ts +57 -0
- package/dist/lib/durable/refusal.js +78 -0
- package/dist/lib/durable/view.d.ts +83 -0
- package/dist/lib/durable/view.js +244 -0
- package/dist/lib/experimental.d.ts +0 -9
- package/dist/lib/experimental.js +18 -17
- package/dist/lib/launcher-config.d.ts +7 -0
- package/dist/lib/launcher-config.js +21 -0
- package/dist/lib/scan/report-email.d.ts +1 -1
- package/dist/lib/scan/report-email.js +9 -1
- package/dist/lib/subscription-login.d.ts +15 -0
- package/dist/lib/subscription-login.js +28 -1
- package/dist/vendor/durable-contract/index.js +240 -18
- package/dist/vendor/shared/durable-contract/admission.d.ts +240 -0
- package/dist/vendor/shared/durable-contract/admission.js +533 -0
- package/dist/vendor/shared/durable-contract/arm.d.ts +51 -0
- package/dist/vendor/shared/durable-contract/arm.js +133 -0
- package/dist/vendor/shared/durable-contract/canonical.d.ts +29 -0
- package/dist/vendor/shared/durable-contract/canonical.js +98 -0
- package/dist/vendor/shared/durable-contract/cell-id.d.ts +22 -0
- package/dist/vendor/shared/durable-contract/cell-id.js +38 -0
- package/dist/vendor/shared/durable-contract/cli-output.d.ts +342 -0
- package/dist/vendor/shared/durable-contract/cli-output.js +201 -0
- package/dist/vendor/shared/durable-contract/cli-surface.d.ts +119 -0
- package/dist/vendor/shared/durable-contract/cli-surface.js +270 -0
- package/dist/vendor/shared/durable-contract/codes.d.ts +537 -0
- package/dist/vendor/shared/durable-contract/codes.js +475 -0
- package/dist/vendor/shared/durable-contract/constants.d.ts +153 -0
- package/dist/vendor/shared/durable-contract/constants.js +179 -0
- package/dist/vendor/shared/durable-contract/doors.d.ts +119 -0
- package/dist/vendor/shared/durable-contract/doors.js +249 -0
- package/dist/vendor/shared/durable-contract/envelope.d.ts +103 -0
- package/dist/vendor/shared/durable-contract/envelope.js +317 -0
- package/dist/vendor/shared/durable-contract/events.d.ts +147 -0
- package/dist/vendor/shared/durable-contract/events.js +630 -0
- package/dist/vendor/shared/durable-contract/executor-state.d.ts +41 -0
- package/dist/vendor/shared/durable-contract/executor-state.js +32 -0
- package/dist/vendor/shared/durable-contract/fixlines.d.ts +318 -0
- package/dist/vendor/shared/durable-contract/fixlines.js +483 -0
- package/dist/vendor/shared/durable-contract/freeze-record.d.ts +787 -0
- package/dist/vendor/shared/durable-contract/freeze-record.js +361 -0
- package/dist/vendor/shared/durable-contract/index.d.ts +36 -0
- package/dist/vendor/shared/durable-contract/index.js +37 -0
- package/dist/vendor/shared/durable-contract/journal-approvals.d.ts +29 -0
- package/dist/vendor/shared/durable-contract/journal-approvals.js +60 -0
- package/dist/vendor/shared/durable-contract/keys.d.ts +24 -0
- package/dist/vendor/shared/durable-contract/keys.js +66 -0
- package/dist/vendor/shared/durable-contract/lock.d.ts +128 -0
- package/dist/vendor/shared/durable-contract/lock.js +151 -0
- package/dist/vendor/shared/durable-contract/names.d.ts +25 -0
- package/dist/vendor/shared/durable-contract/names.js +78 -0
- package/dist/vendor/shared/durable-contract/owner-verbs.d.ts +175 -0
- package/dist/vendor/shared/durable-contract/owner-verbs.js +403 -0
- package/dist/vendor/shared/durable-contract/paths.d.ts +23 -0
- package/dist/vendor/shared/durable-contract/paths.js +35 -0
- package/dist/vendor/shared/durable-contract/pure-fn.d.ts +48 -0
- package/dist/vendor/shared/durable-contract/pure-fn.js +89 -0
- package/dist/vendor/shared/durable-contract/routes.d.ts +205 -0
- package/dist/vendor/shared/durable-contract/routes.js +315 -0
- package/dist/vendor/shared/durable-contract/schema.d.ts +126 -0
- package/dist/vendor/shared/durable-contract/schema.js +441 -0
- package/dist/vendor/shared/durable-contract/seal.d.ts +54 -0
- package/dist/vendor/shared/durable-contract/seal.js +99 -0
- package/dist/vendor/shared/durable-contract/secret-scope.d.ts +28 -0
- package/dist/vendor/shared/durable-contract/secret-scope.js +76 -0
- package/dist/vendor/shared/durable-contract/session-id.d.ts +21 -0
- package/dist/vendor/shared/durable-contract/session-id.js +58 -0
- package/dist/vendor/shared/durable-contract/spec.d.ts +162 -0
- package/dist/vendor/shared/durable-contract/spec.js +286 -0
- package/dist/vendor/shared/durable-contract/states.d.ts +96 -0
- package/dist/vendor/shared/durable-contract/states.js +218 -0
- package/dist/vendor/shared/durable-contract/telemetry.d.ts +94 -0
- package/dist/vendor/shared/durable-contract/telemetry.js +180 -0
- package/dist/vendor/shared/durable-contract/test-mode.d.ts +62 -0
- package/dist/vendor/shared/durable-contract/test-mode.js +140 -0
- package/dist/vendor/shared/durable-contract/turn.d.ts +57 -0
- package/dist/vendor/shared/durable-contract/turn.js +81 -0
- package/dist/vendor/shared/durable-contract/vectors.d.ts +191 -0
- package/dist/vendor/shared/durable-contract/vectors.js +208 -0
- package/package.json +3 -2
- package/plugins/integrity-manifest.json +35 -0
- package/plugins/mnemom/hooks/hooks.json +15 -1
- package/plugins/mnemom/hooks/register.tsx +60 -2
- package/plugins/mnemom/hooks/seat-hello.mjs +116 -0
- package/dist/commands/agent-implicit-notice.d.ts +0 -31
- package/dist/commands/agent-implicit-notice.js +0 -73
package/dist/base-url-proxy.mjs
CHANGED
|
@@ -15,7 +15,10 @@
|
|
|
15
15
|
// (> WAIT_MS) → the request is forwarded without it (the gateway then behaves as before).
|
|
16
16
|
//
|
|
17
17
|
// CONFIG (env, none secret)
|
|
18
|
-
// MNEMOM_AGENT_BU_DOOR gateway door URL (required
|
|
18
|
+
// MNEMOM_AGENT_BU_DOOR gateway door URL (required), e.g. https://gateway.mnemom.ai/anthropic;
|
|
19
|
+
// https; http only with MNEMOM_AGENT_BU_HTTP_DOOR=1 and on a
|
|
20
|
+
// loopback literal (127.0.0.1, [::1], localhost: the C17-R16 seam)
|
|
21
|
+
// MNEMOM_AGENT_BU_HTTP_DOOR "1" allows that http door; set by the launcher (see the gate below)
|
|
19
22
|
// MNEMOM_AGENT_BU_CWD the session cwd (Claude Code's transcript lives under its slug)
|
|
20
23
|
// MNEMOM_AGENT_BU_LOG append log lines here instead of stderr (optional)
|
|
21
24
|
// MNEMOM_AGENT_BU_PARENT_PID exit when this pid is gone
|
|
@@ -43,7 +46,18 @@ try {
|
|
|
43
46
|
} catch {
|
|
44
47
|
die("MNEMOM_AGENT_BU_DOOR is required and must be a URL");
|
|
45
48
|
}
|
|
46
|
-
|
|
49
|
+
/** S1 LOOPBACK_HOSTS: the exact hostnames an http door may name (look-alikes such as 127.0.0.1.nip.io fail). */
|
|
50
|
+
const LOOPBACK_HOSTS = new Set(["127.0.0.1", "[::1]", "localhost"]);
|
|
51
|
+
// The C17-R16 http-door seam (T3). The launcher (commands/agent.ts startBaseUrlProxy) sets
|
|
52
|
+
// MNEMOM_AGENT_BU_HTTP_DOOR=1 only when test-mode.mjs testSeamsHonoured / httpDoorAllowed hold, and
|
|
53
|
+
// strips an inherited value otherwise; this proxy reads no test-mode env itself. A proxy spawned
|
|
54
|
+
// directly trusts its parent's flag: the worst case is still http to a loopback literal only.
|
|
55
|
+
const httpDoorAllowed = env.MNEMOM_AGENT_BU_HTTP_DOOR === "1" && LOOPBACK_HOSTS.has(DOOR.hostname);
|
|
56
|
+
if (DOOR.protocol === "http:") {
|
|
57
|
+
if (!httpDoorAllowed)
|
|
58
|
+
die("MNEMOM_AGENT_BU_DOOR must be https (http only on a loopback literal with the door flag)");
|
|
59
|
+
} else if (DOOR.protocol !== "https:") die("MNEMOM_AGENT_BU_DOOR must be https");
|
|
60
|
+
const transport = DOOR.protocol === "http:" ? http : https;
|
|
47
61
|
const LOGFILE = env.MNEMOM_AGENT_BU_LOG || "";
|
|
48
62
|
const PARENT = Number(env.MNEMOM_AGENT_BU_PARENT_PID || 0);
|
|
49
63
|
const PORT = Number(env.MNEMOM_AGENT_BU_PORT || 0);
|
|
@@ -101,7 +115,7 @@ function recordState(h) {
|
|
|
101
115
|
}
|
|
102
116
|
}
|
|
103
117
|
|
|
104
|
-
const agent = new
|
|
118
|
+
const agent = new transport.Agent({ keepAlive: true, maxSockets: 32 });
|
|
105
119
|
const doorPath = DOOR.pathname.replace(/\/$/, "");
|
|
106
120
|
const isMessages = (u) => u === "/v1/messages" || u.startsWith("/v1/messages?");
|
|
107
121
|
|
|
@@ -123,7 +137,7 @@ function handle(req, res) {
|
|
|
123
137
|
|
|
124
138
|
const forward = (note) => {
|
|
125
139
|
const target = new URL(DOOR.origin + doorPath + req.url);
|
|
126
|
-
const up =
|
|
140
|
+
const up = transport.request(target, { method: req.method, headers, agent }, (ur) => {
|
|
127
141
|
recordState(ur.headers);
|
|
128
142
|
res.writeHead(ur.statusCode, ur.headers);
|
|
129
143
|
ur.pipe(res);
|
|
@@ -56,7 +56,8 @@ function listConfig(enabled) {
|
|
|
56
56
|
out("Set one with: mnemom agent config set <key> <value>");
|
|
57
57
|
out("");
|
|
58
58
|
out("Settable keys:");
|
|
59
|
-
|
|
59
|
+
// The durable key is listed only while `durable` is on (hidden like its verbs; C17).
|
|
60
|
+
for (const spec of CONFIG_KEYS.filter((k) => k.key !== "durable" || enabled.includes("durable")))
|
|
60
61
|
out(` ${spec.key.padEnd(22)} ${spec.describe}${configKeyMark(spec.key, undefined, enabled)}`);
|
|
61
62
|
return;
|
|
62
63
|
}
|
|
@@ -199,6 +200,9 @@ function deleteKey(cfg, key) {
|
|
|
199
200
|
case "compaction_intercept":
|
|
200
201
|
delete cfg.compactionIntercept;
|
|
201
202
|
break;
|
|
203
|
+
case "durable":
|
|
204
|
+
delete cfg.durable;
|
|
205
|
+
break;
|
|
202
206
|
case "context_profile":
|
|
203
207
|
delete cfg.contextProfile;
|
|
204
208
|
break;
|
|
@@ -17,6 +17,15 @@ export interface DoctorOptions {
|
|
|
17
17
|
cli?: string;
|
|
18
18
|
/** The agent slug a launch would use (--agent / launcher.toml); default agent-<handle>. */
|
|
19
19
|
agent?: string;
|
|
20
|
+
/**
|
|
21
|
+
* The durable lock lines (commands/durable-doctor.ts), passed only while the experimental
|
|
22
|
+
* `durable` gate is on; absent, the doctor is exactly as before.
|
|
23
|
+
*/
|
|
24
|
+
durable?: () => Promise<Array<{
|
|
25
|
+
mark: "ok" | "warn" | "info";
|
|
26
|
+
text: string;
|
|
27
|
+
hint?: string;
|
|
28
|
+
}>>;
|
|
20
29
|
}
|
|
21
30
|
/** Run the preflight and print the checklist. Sets process.exitCode on failure. */
|
|
22
31
|
export declare function agentDoctorCommand(options?: DoctorOptions): Promise<void>;
|
|
@@ -125,6 +125,10 @@ export async function agentDoctorCommand(options = {}) {
|
|
|
125
125
|
// ── CLI auto-update ──
|
|
126
126
|
const upd = await probeUpdateStatus();
|
|
127
127
|
add(upd.mark, upd.text, upd.hint);
|
|
128
|
+
// ── durable lock preconditions (gate on only): never a MISS ──
|
|
129
|
+
if (options.durable)
|
|
130
|
+
for (const l of await options.durable())
|
|
131
|
+
add(l.mark, l.text, l.hint);
|
|
128
132
|
// ── render ──
|
|
129
133
|
const door = resolveAnthropicDoor(env);
|
|
130
134
|
out("mnemom agent doctor");
|
|
@@ -12,8 +12,7 @@
|
|
|
12
12
|
* (`x-mnemom-contract`). Implicit alignment — the goal inferred from your own
|
|
13
13
|
* messages (`x-mnemom-goal-mode: implicit`, no contract) — is the experimental
|
|
14
14
|
* `implicit` feature: while it is on a bare launch runs implicit, while it is off
|
|
15
|
-
* (the default) a bare launch runs with alignment off.
|
|
16
|
-
* build a bare launch runs implicit unless it is headless `-p` (T16). `--no-implicit` (or
|
|
15
|
+
* (the default) a bare launch runs with alignment off. `--no-implicit` (or
|
|
17
16
|
* launcher.toml `goal_mode = "off"`) turns it off. The mode is resolved upstream
|
|
18
17
|
* (`resolveImplicit`) and announced on the launch line.
|
|
19
18
|
*
|
|
@@ -12,8 +12,7 @@
|
|
|
12
12
|
* (`x-mnemom-contract`). Implicit alignment — the goal inferred from your own
|
|
13
13
|
* messages (`x-mnemom-goal-mode: implicit`, no contract) — is the experimental
|
|
14
14
|
* `implicit` feature: while it is on a bare launch runs implicit, while it is off
|
|
15
|
-
* (the default) a bare launch runs with alignment off.
|
|
16
|
-
* build a bare launch runs implicit unless it is headless `-p` (T16). `--no-implicit` (or
|
|
15
|
+
* (the default) a bare launch runs with alignment off. `--no-implicit` (or
|
|
17
16
|
* launcher.toml `goal_mode = "off"`) turns it off. The mode is resolved upstream
|
|
18
17
|
* (`resolveImplicit`) and announced on the launch line.
|
|
19
18
|
*
|
|
@@ -288,6 +288,12 @@ export declare function resolvePinnedAgent(id: string): Promise<{
|
|
|
288
288
|
id: string;
|
|
289
289
|
slug: string;
|
|
290
290
|
}>;
|
|
291
|
+
/**
|
|
292
|
+
* The x-mnemom-agent value for a pinned agent: its stored name exactly, when that name is a
|
|
293
|
+
* header-safe token (every api-valid name is: AGENT_NAME_REGEX allows only letters, digits and
|
|
294
|
+
* hyphens); "" otherwise, which refuses the launch.
|
|
295
|
+
*/
|
|
296
|
+
export declare function pinnedSlugOf(name: string | null | undefined): string;
|
|
291
297
|
/**
|
|
292
298
|
* One line for an api failure that keeps the parts an operator can act on: the
|
|
293
299
|
* message, the error code, and the scope the endpoint said it needed
|
|
@@ -696,12 +696,23 @@ export async function resolvePinnedAgent(id) {
|
|
|
696
696
|
throw new UnclaimedAgentError(`mnemom agent: agent ${id} is not claimed, and mnemom agent never runs on an unclaimed agent. ` +
|
|
697
697
|
`Claim it first: \`mnemom agent claim ${id} --key <key>\`.`);
|
|
698
698
|
}
|
|
699
|
-
|
|
699
|
+
// The stored name goes out verbatim: the gateway compares the pin's name exactly
|
|
700
|
+
// (agent-identity.ts), and the api allows uppercase and hyphen runs, which sanitizeAgentName
|
|
701
|
+
// would rewrite (mnemom-test #1061). sanitizeAgentName is for new names only.
|
|
702
|
+
const slug = pinnedSlugOf(agent.name);
|
|
700
703
|
if (!slug) {
|
|
701
704
|
throw new UnclaimedAgentError(`mnemom agent: agent ${id} has no usable name to send as x-mnemom-agent; rename it, or launch by name.`);
|
|
702
705
|
}
|
|
703
706
|
return { id, slug };
|
|
704
707
|
}
|
|
708
|
+
/**
|
|
709
|
+
* The x-mnemom-agent value for a pinned agent: its stored name exactly, when that name is a
|
|
710
|
+
* header-safe token (every api-valid name is: AGENT_NAME_REGEX allows only letters, digits and
|
|
711
|
+
* hyphens); "" otherwise, which refuses the launch.
|
|
712
|
+
*/
|
|
713
|
+
export function pinnedSlugOf(name) {
|
|
714
|
+
return typeof name === "string" && /^[A-Za-z0-9-]+$/.test(name) ? name : "";
|
|
715
|
+
}
|
|
705
716
|
/** The refusal for a pinned launch the gateway did not (or could not) honour. */
|
|
706
717
|
function pinRefusal(err, pin, slug) {
|
|
707
718
|
const head = `mnemom agent: the gateway would not run this launch as ${pin}`;
|
package/dist/commands/agent.d.ts
CHANGED
|
@@ -33,6 +33,7 @@ import { type ExperimentalId } from "../lib/experimental.js";
|
|
|
33
33
|
import { conductReceiptFor, type ReceiptLaunch } from "../lib/conduct-receipt.js";
|
|
34
34
|
import { type GovernanceOptIn } from "./agent-setup.js";
|
|
35
35
|
import { type CcSettingsView, type ContextProfile, type LaunchShape, type SealedContractEnvelope } from "../lib/agent.js";
|
|
36
|
+
import type { LauncherKey } from "../vendor/shared/durable-contract/fixlines.js";
|
|
36
37
|
export interface AgentOptions {
|
|
37
38
|
goal?: string;
|
|
38
39
|
requirement?: string[];
|
|
@@ -60,6 +61,14 @@ export interface AgentOptions {
|
|
|
60
61
|
/** launcher.toml `agent_id`: the pin when no `--agent` is given (selectLaunchAgent). */
|
|
61
62
|
agentId?: string;
|
|
62
63
|
conversationId?: string;
|
|
64
|
+
/**
|
|
65
|
+
* Durable launch (EXPERIMENTAL `durable`, BACKBONE C1): set only while the gate is on. Absent,
|
|
66
|
+
* the launch is exactly today's (C17).
|
|
67
|
+
*/
|
|
68
|
+
durable?: {
|
|
69
|
+
launcherKey: LauncherKey;
|
|
70
|
+
newSession: boolean;
|
|
71
|
+
};
|
|
63
72
|
cli?: string;
|
|
64
73
|
remoteControl?: boolean;
|
|
65
74
|
server?: boolean;
|
|
@@ -397,6 +406,8 @@ export declare function exemptLoopbackFromProxy(env: NodeJS.ProcessEnv): void;
|
|
|
397
406
|
export declare function sayConductReceipt(launch: ReceiptLaunch, trigger?: "exit" | "park", receipt?: typeof conductReceiptFor): Promise<void>;
|
|
398
407
|
/** Whether a terminal implicit launch runs the turn-origin proxy (MNEMOM_AGENT_TURN_HEADER=0 opts out). */
|
|
399
408
|
export declare function turnProxyWanted(env: NodeJS.ProcessEnv): boolean;
|
|
409
|
+
/** The flag that lets base-url-proxy.mjs use an http door on a loopback literal (C17-R16 T3). */
|
|
410
|
+
export declare const BU_HTTP_DOOR_ENV = "MNEMOM_AGENT_BU_HTTP_DOOR";
|
|
400
411
|
/** Test seams for startBaseUrlProxy (every real caller omits them). */
|
|
401
412
|
export interface BaseUrlProxyOptions {
|
|
402
413
|
/** The proxy script (default: the bundled base-url-proxy.mjs beside rc-proxy.mjs). */
|
package/dist/commands/agent.js
CHANGED
|
@@ -39,8 +39,10 @@ import { ALLOW_AUTOCOMPACT_OFF_ENV, dropAutocompactOffEnv, hasOwnSettingsFlag, m
|
|
|
39
39
|
import { getApiUrl, getGatewayUrl, MNEMOM_DIR } from "../lib/config.js";
|
|
40
40
|
import { createApiKey } from "../lib/api.js";
|
|
41
41
|
import { recordOrgFor } from "../lib/record-org.js";
|
|
42
|
+
import { httpDoorAllowed } from "../durable/test-mode.mjs";
|
|
42
43
|
import { defaultSessionKeyDeps, mintSessionGatewayKey, revokeSessionGatewayKey, startSessionKeyRenewal, } from "../lib/session-key.js";
|
|
43
|
-
import { MNEMOM_KEY_STORE_FIELD, resolveAgentAuth, sayRetiredConsoleNotice, readMnemomKey, resolveSubscriptionMnemomKey,
|
|
44
|
+
import { MNEMOM_KEY_STORE_FIELD, resolveAgentAuth, sayRetiredConsoleNotice, readMnemomKey, resolveSubscriptionMnemomKey, launchChildEnv, subscriptionShape, } from "../lib/subscription-login.js";
|
|
45
|
+
import { clearLaunching, liveLaunchingDeps, markLaunching } from "../lib/durable/launching.js";
|
|
44
46
|
import { LEDGER_FILE, appendSessionRecord, findResumeCandidate, readSessionRecords, resolveRetentionDays, resumedContextProfile, toLedgerContract, } from "../lib/agent-ledger.js";
|
|
45
47
|
import { isLoggedIn } from "../lib/auth.js";
|
|
46
48
|
import { loadCliConfig } from "../lib/cli-config.js";
|
|
@@ -57,6 +59,9 @@ import { LAUNCHER_STORE_PATH, mergeStore } from "../lib/launcher-store.js";
|
|
|
57
59
|
import { slackChannelArgs } from "../lib/slack-launch.js";
|
|
58
60
|
import { appendPluginDirsEnv, applyPluginEnv, pluginEnabled, pluginPlanLine, resolvePluginDir, } from "../lib/claude-plugin.js";
|
|
59
61
|
import { CLI_VERSION } from "../version.js";
|
|
62
|
+
import { attachToSeat, firstTurnRow, locksAfterFirstTurn, planDurableLaunch, preReadRegistry, } from "../lib/durable/launch.js";
|
|
63
|
+
import { liveDurableApi, liveHomeFacts, livePlanDeps, liveRegistryReads, liveSeatDeps, liveLockAfterFirstTurn, lockInsteadOfSpawn, createLockedInsteadOfSpawn, liveParentAfterChildEnded, registerDurableSession, saveTtyMode, } from "../lib/durable/live.js";
|
|
64
|
+
import { mayBeHandover } from "../lib/durable/handover.js";
|
|
60
65
|
import { fetchTierIndex, profileMayMisfit, profileTierWarning, resolveTier, tierLaunchModel, } from "../lib/model-tier.js";
|
|
61
66
|
/** The CLI's own provider-key store (zero 1Password): ~/.mnemom/launcher.json, 0600. */
|
|
62
67
|
const CODE_STORE = LAUNCHER_STORE_PATH;
|
|
@@ -175,8 +180,7 @@ export async function agentCommand(scenarioArg, options = {}) {
|
|
|
175
180
|
liftPassthroughName(options.passthrough ?? []);
|
|
176
181
|
// ── session name + goal ── (a bare `mnemom agent` in a terminal asks for the name;
|
|
177
182
|
// goal alignment is resolved upstream: --goal seals a contract, implicit alignment
|
|
178
|
-
// is on by default while the `implicit` experimental feature is on
|
|
179
|
-
// build for every launch but `-p` (T16).
|
|
183
|
+
// is on by default only while the `implicit` experimental feature is on.
|
|
180
184
|
const session = await resolveSessionSetup({
|
|
181
185
|
scenario: scenarioArg,
|
|
182
186
|
goal: options.goal,
|
|
@@ -187,8 +191,24 @@ export async function agentCommand(scenarioArg, options = {}) {
|
|
|
187
191
|
askYesNo: (q, d) => askYesNo(q, d),
|
|
188
192
|
});
|
|
189
193
|
const scenario = session.scenario;
|
|
194
|
+
// ── durable (gate on only): read the registry FIRST, before any other API call, so a registry
|
|
195
|
+
// that is down refuses with session_registry_unavailable within REGISTRY_TIMEOUT_MS and nothing
|
|
196
|
+
// is minted, spawned or written (C01-R18). The plan below decides from these rows; a caller not
|
|
197
|
+
// signed in yet (401) is read again by the plan after sign-in.
|
|
198
|
+
// A server-shape launch hosts many Claude Code sessions and pins none: it is never registered
|
|
199
|
+
// (legacy), so neither the read nor the plan runs for it.
|
|
190
200
|
const env = process.env;
|
|
191
201
|
let shape = resolveLaunchShape({ remoteControl: options.remoteControl, server: options.server }, env);
|
|
202
|
+
const durableLaunch = Boolean(options.durable && scenario) && shape !== "server";
|
|
203
|
+
// Until the session registers, a lock by this name answers first_turn_pending, not "no session".
|
|
204
|
+
if (durableLaunch && scenario && !options.dryRun) {
|
|
205
|
+
const ld = liveLaunchingDeps(MNEMOM_DIR);
|
|
206
|
+
markLaunching(scenario, process.pid, ld);
|
|
207
|
+
process.on("exit", () => clearLaunching(scenario, ld));
|
|
208
|
+
}
|
|
209
|
+
const durableRows = durableLaunch && scenario && !options.dryRun
|
|
210
|
+
? await preReadRegistry(liveRegistryReads(liveDurableApi()), scenario)
|
|
211
|
+
: undefined;
|
|
192
212
|
const shapeExplicit = launchPreferenceExplicit({ remoteControl: options.remoteControl, server: options.server }, env);
|
|
193
213
|
// How Claude Code authenticates: an Anthropic key (default) or the user's claude.ai
|
|
194
214
|
// subscription login (Team/Enterprise; lib/subscription-login.ts).
|
|
@@ -260,7 +280,7 @@ export async function agentCommand(scenarioArg, options = {}) {
|
|
|
260
280
|
const cellNote = isProdGateway(env) ? "us-2/prod" : `custom (${gatewayHost})`;
|
|
261
281
|
// ── the bundled Claude Code plugin (lib/claude-plugin) ── resolved up front so the dry-run plan
|
|
262
282
|
// and the launch log print the same line. Only Claude Code proper takes `--plugin-dir`.
|
|
263
|
-
const pluginWanted = pluginEnabled(options.plugin, env);
|
|
283
|
+
const pluginWanted = pluginEnabled(options.plugin, env, options.durable !== undefined);
|
|
264
284
|
const bundledPluginDir = pluginWanted ? resolvePluginDir() : null;
|
|
265
285
|
const pluginFor = (cliName) => cliName !== "claude" && pluginWanted
|
|
266
286
|
? { dir: null, line: `off (${cliName} is not Claude Code)` }
|
|
@@ -519,20 +539,83 @@ export async function agentCommand(scenarioArg, options = {}) {
|
|
|
519
539
|
// so the goal the gateway inferred there is still the one it steers by. Resolved here, after
|
|
520
540
|
// sign-in and the agent refresh, because a resume needs the org, agent and gateway host to
|
|
521
541
|
// match the earlier launch.
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
542
|
+
// ── durable (gate on only) ── the server registry decides continue / new / attach before the
|
|
543
|
+
// local ledger does (C01-R01); a registry-backed plan replaces the ledger continuation.
|
|
544
|
+
let durablePlan = null;
|
|
545
|
+
// The launcher key of a durable plan: durablePlan is only ever set under options.durable.
|
|
546
|
+
let durableKey = "off";
|
|
547
|
+
if (options.durable && scenario && durableLaunch) {
|
|
548
|
+
durableKey = options.durable.launcherKey;
|
|
549
|
+
const plan = await planDurableLaunch({
|
|
550
|
+
name: scenario,
|
|
551
|
+
newFlag: options.durable.newSession,
|
|
552
|
+
launcherKey: options.durable.launcherKey,
|
|
553
|
+
passthrough: options.passthrough ?? [],
|
|
554
|
+
goalGiven: Boolean(options.goal?.trim()),
|
|
555
|
+
explicitAgentId: pin ? servedAgentId : null,
|
|
556
|
+
interactive: isInteractive(),
|
|
557
|
+
home: await liveHomeFacts(),
|
|
558
|
+
rows: durableRows,
|
|
559
|
+
explicitConversationId: options.conversationId ?? null,
|
|
560
|
+
}, livePlanDeps(servedAgentId, (id) => ccSessionTranscriptExists(id, process.env), (q, d) => askYesNo(q, d)));
|
|
561
|
+
if (plan.kind === "attach") {
|
|
562
|
+
process.exitCode = await attachToSeat(plan.row, scenario, [], liveSeatDeps(options.durable.launcherKey));
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
if (plan.kind === "spawn")
|
|
566
|
+
durablePlan = plan;
|
|
567
|
+
}
|
|
568
|
+
const continuation = durablePlan
|
|
569
|
+
? {
|
|
570
|
+
conversationId: durablePlan.conversationId,
|
|
571
|
+
resumeCcSessionId: durablePlan.resumeCcSessionId,
|
|
572
|
+
}
|
|
573
|
+
: await resolveLaunchContinuation({
|
|
574
|
+
session: scenario,
|
|
575
|
+
orgId: account.orgId,
|
|
576
|
+
agent,
|
|
577
|
+
gatewayHost,
|
|
578
|
+
explicit: options.conversationId,
|
|
579
|
+
implicit: goalAlignmentMode(envelope, goalMode) === "implicit",
|
|
580
|
+
passthrough: options.passthrough ?? [],
|
|
581
|
+
interactive: isInteractive(),
|
|
582
|
+
records: () => readSessionRecords({ dir: MNEMOM_DIR }),
|
|
583
|
+
askYesNo: (q, d) => askYesNo(q, d),
|
|
584
|
+
ccSessionExists: (id) => ccSessionTranscriptExists(id, process.env),
|
|
585
|
+
});
|
|
535
586
|
const conversationId = continuation.conversationId;
|
|
587
|
+
// The registered row of a durable launch: its child's SIGKILL may be a handover (C02-R26 step 7).
|
|
588
|
+
let durableRow = null;
|
|
589
|
+
// A lock on a new CC session waits for its first turn (dogfood B5): claude spawns attended first.
|
|
590
|
+
let lockAfterFirstTurn = false;
|
|
591
|
+
if (durablePlan) {
|
|
592
|
+
options = {
|
|
593
|
+
...options,
|
|
594
|
+
passthrough: durablePlan.newCcSessionId
|
|
595
|
+
? [...durablePlan.passthrough, "--session-id", durablePlan.newCcSessionId]
|
|
596
|
+
: durablePlan.passthrough,
|
|
597
|
+
};
|
|
598
|
+
// Register (new) and, under `always` / ask-yes, lock instead of spawning (C02-R23 c, e). A lock
|
|
599
|
+
// refusal prints the refusal and its fix, then falls through to the tracked spawn below. A new
|
|
600
|
+
// CC session has no transcript to resume yet: it spawns, and locks after its first turn (B5).
|
|
601
|
+
// B5a (as amended): a new CC session on a durable-only agent is created locked in one request
|
|
602
|
+
// and attached; it never registers tracked or runs an attended first turn.
|
|
603
|
+
if (durablePlan.durableOnly && locksAfterFirstTurn(durablePlan)) {
|
|
604
|
+
process.exitCode = await createLockedInsteadOfSpawn(durablePlan, scenario, servedAgentId, durableKey, { model: options.model, effort: options.effort, trimMcp: options.trimMcp });
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
607
|
+
const row = await registerDurableSession(durablePlan, scenario, servedAgentId);
|
|
608
|
+
clearLaunching(scenario, liveLaunchingDeps(MNEMOM_DIR));
|
|
609
|
+
durableRow = firstTurnRow(row, durablePlan);
|
|
610
|
+
lockAfterFirstTurn = locksAfterFirstTurn(durablePlan);
|
|
611
|
+
if (durablePlan.lock && !lockAfterFirstTurn) {
|
|
612
|
+
const locked = await lockInsteadOfSpawn(row, scenario, durableKey, durablePlan.durableOnly);
|
|
613
|
+
if (locked !== null) {
|
|
614
|
+
process.exitCode = locked;
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
}
|
|
536
619
|
// A relaunch that continues an earlier conversation resumes that Claude Code session, so the
|
|
537
620
|
// same CC session id keeps owning the conversation's main goal on the gateway (a fresh
|
|
538
621
|
// --session-id would land on a new, empty m.<sha16> thread). The server shape hosts many
|
|
@@ -704,6 +787,7 @@ export async function agentCommand(scenarioArg, options = {}) {
|
|
|
704
787
|
goalMode,
|
|
705
788
|
scenario,
|
|
706
789
|
launcherVersion: CLI_VERSION,
|
|
790
|
+
...(options.durable ? { durable: true } : {}),
|
|
707
791
|
// The mod re-runs this CLI (`agent show --json`, `agent goal show --json`) for the live
|
|
708
792
|
// goal state; execArgv keeps a `tsx`/`--import` dev launch runnable the same way.
|
|
709
793
|
launcherExec: process.argv[1]
|
|
@@ -803,6 +887,16 @@ export async function agentCommand(scenarioArg, options = {}) {
|
|
|
803
887
|
}),
|
|
804
888
|
knownCcSessionId: cc.id ?? continuedCcSessionId,
|
|
805
889
|
plugin,
|
|
890
|
+
...(durableRow && options.durable
|
|
891
|
+
? {
|
|
892
|
+
durableHandover: {
|
|
893
|
+
row: durableRow,
|
|
894
|
+
name: scenario,
|
|
895
|
+
launcherKey: options.durable.launcherKey,
|
|
896
|
+
lockAfterFirstTurn,
|
|
897
|
+
},
|
|
898
|
+
}
|
|
899
|
+
: {}),
|
|
806
900
|
receipt: experimental.includes("record")
|
|
807
901
|
? { orgId: recordOrgId, agentId: servedAgentId }
|
|
808
902
|
: undefined,
|
|
@@ -1313,30 +1407,14 @@ function launchTerminal(ctx) {
|
|
|
1313
1407
|
handoffMcpConfigPath: ctx.handoffMcpConfigPath,
|
|
1314
1408
|
pluginDir: ctx.plugin?.dir,
|
|
1315
1409
|
});
|
|
1316
|
-
//
|
|
1317
|
-
//
|
|
1318
|
-
//
|
|
1319
|
-
//
|
|
1320
|
-
//
|
|
1321
|
-
//
|
|
1322
|
-
|
|
1410
|
+
// The api-key arm (key in ANTHROPIC_CUSTOM_HEADERS, placeholder auth token, ANTHROPIC_API_KEY
|
|
1411
|
+
// unset) or the subscription arm (Claude Code's own claude.ai login), shared with durable seats
|
|
1412
|
+
// (lib/subscription-login.ts launchChildEnv). MNE-8245 defense-in-depth: the owner's own Mnemom
|
|
1413
|
+
// credential must never ride through to the session just because it happened to sit in the
|
|
1414
|
+
// CLI's OWN process env — the session's only Mnemom credential is the minted key already
|
|
1415
|
+
// spliced into `headers` above (ctx.mnemomKey).
|
|
1416
|
+
const childEnv = launchChildEnv(process.env, ctx.door, headers, ctx.anthropicKey);
|
|
1323
1417
|
let feed = null;
|
|
1324
|
-
if (ctx.anthropicKey) {
|
|
1325
|
-
childEnv = { ...process.env };
|
|
1326
|
-
delete childEnv.ANTHROPIC_API_KEY;
|
|
1327
|
-
childEnv.ANTHROPIC_BASE_URL = ctx.door;
|
|
1328
|
-
childEnv.ANTHROPIC_AUTH_TOKEN = "mnemom-gateway";
|
|
1329
|
-
childEnv.ANTHROPIC_CUSTOM_HEADERS = headers;
|
|
1330
|
-
}
|
|
1331
|
-
else {
|
|
1332
|
-
childEnv = subscriptionChildEnv(process.env, ctx.door, headers);
|
|
1333
|
-
}
|
|
1334
|
-
// MNE-8245 defense-in-depth: the owner's own Mnemom credential must never ride
|
|
1335
|
-
// through to the session just because it happened to sit in the CLI's OWN
|
|
1336
|
-
// process env (a shell MNEMOM_API_KEY / MNEMOM_TOKEN export, or a leftover
|
|
1337
|
-
// MNEMOM_AGENT_MNEMOM_KEY) — the session's only Mnemom credential is the
|
|
1338
|
-
// minted key already spliced into `headers` above (ctx.mnemomKey).
|
|
1339
|
-
stripOwnerCredentialEnv(childEnv);
|
|
1340
1418
|
// Both auth arms point ANTHROPIC_BASE_URL at the gateway door, which makes Claude Code turn
|
|
1341
1419
|
// tool search off and inline every tool schema on every request — unless told otherwise.
|
|
1342
1420
|
applyToolSearchDefault(childEnv);
|
|
@@ -1346,10 +1424,9 @@ function launchTerminal(ctx) {
|
|
|
1346
1424
|
const sessionName = passthroughName(ctx.options.passthrough ?? []) ?? sessionDisplayName(ctx.agent, ctx.scenario);
|
|
1347
1425
|
childEnv[SESSION_NAME_ENV] = sessionName;
|
|
1348
1426
|
// Exact compaction signal for the gateway (x-claude-code-request-class: compaction), on every
|
|
1349
|
-
// terminal launch — interactive and `-p` passthrough alike; a user's own value wins
|
|
1350
|
-
// user's CLAUDE_AUTOCOMPACT_PCT_OVERRIDE /
|
|
1351
|
-
// untouched in the copied env (USER_AUTOCOMPACT_ENV).
|
|
1352
|
-
applyGatewayHintHeaders(childEnv);
|
|
1427
|
+
// terminal launch — interactive and `-p` passthrough alike; a user's own value wins: set by
|
|
1428
|
+
// launchChildEnv above (shared with durable seats). The user's CLAUDE_AUTOCOMPACT_PCT_OVERRIDE /
|
|
1429
|
+
// CLAUDE_CODE_AUTO_COMPACT_WINDOW ride through untouched in the copied env (USER_AUTOCOMPACT_ENV).
|
|
1353
1430
|
if (ctx.options.slack)
|
|
1354
1431
|
args.push(...slackChannelArgs(sessionName));
|
|
1355
1432
|
// The status-bar segment (lib/statusline.ts). Claude Code only; the user's own
|
|
@@ -1394,8 +1471,12 @@ function launchTerminal(ctx) {
|
|
|
1394
1471
|
}
|
|
1395
1472
|
let recordStatePath;
|
|
1396
1473
|
const run = () => {
|
|
1474
|
+
const savedTty = ctx.durableHandover ? saveTtyMode() : null;
|
|
1397
1475
|
const launchedSec = Math.floor(Date.now() / 1000);
|
|
1398
1476
|
const child = spawn(ctx.cli.bin, args, { stdio: "inherit", env: childEnv });
|
|
1477
|
+
const firstTurn = ctx.durableHandover?.lockAfterFirstTurn ? ctx.durableHandover : null;
|
|
1478
|
+
if (firstTurn)
|
|
1479
|
+
void liveLockAfterFirstTurn(firstTurn.row, firstTurn.name, firstTurn.launcherKey, () => child.exitCode === null && child.signalCode === null);
|
|
1399
1480
|
// W1.8: report the session's commits while it runs; no record state (no proxy) → no reports.
|
|
1400
1481
|
const commits = ctx.commitReport && ctx.orgId && recordStatePath
|
|
1401
1482
|
? startCommitReporter({
|
|
@@ -1416,6 +1497,14 @@ function launchTerminal(ctx) {
|
|
|
1416
1497
|
if (ctx.sessionKeyId) {
|
|
1417
1498
|
await revokeSessionGatewayKey(defaultSessionKeyDeps(say), ctx.sessionKeyId);
|
|
1418
1499
|
}
|
|
1500
|
+
// A lock handover SIGKILLs the frozen claude once the daemon's seat runs the conversation:
|
|
1501
|
+
// attach to it instead of dying with the child (C02-R26 step 7). Never relaunches claude.
|
|
1502
|
+
const h = ctx.durableHandover;
|
|
1503
|
+
if (mayBeHandover(h !== undefined, signal) && h) {
|
|
1504
|
+
const attached = await liveParentAfterChildEnded(h.row, h.name, h.launcherKey, savedTty);
|
|
1505
|
+
if (attached !== null)
|
|
1506
|
+
process.exit(attached);
|
|
1507
|
+
}
|
|
1419
1508
|
if (signal) {
|
|
1420
1509
|
process.kill(process.pid, signal);
|
|
1421
1510
|
return;
|
|
@@ -1562,6 +1651,8 @@ export function turnProxyWanted(env) {
|
|
|
1562
1651
|
const raw = (env.MNEMOM_AGENT_TURN_HEADER ?? "").trim().toLowerCase();
|
|
1563
1652
|
return !["0", "off", "false", "no"].includes(raw);
|
|
1564
1653
|
}
|
|
1654
|
+
/** The flag that lets base-url-proxy.mjs use an http door on a loopback literal (C17-R16 T3). */
|
|
1655
|
+
export const BU_HTTP_DOOR_ENV = "MNEMOM_AGENT_BU_HTTP_DOOR";
|
|
1565
1656
|
/**
|
|
1566
1657
|
* Start base-url-proxy.mjs in front of `door`; calls `done` exactly once with its
|
|
1567
1658
|
* `http://127.0.0.1:<port>` base URL, or with null (after a one-line warning) when it does not
|
|
@@ -1587,6 +1678,13 @@ export function startBaseUrlProxy(door, done, opts = {}) {
|
|
|
1587
1678
|
};
|
|
1588
1679
|
// MNE-8245 defense-in-depth: the proxy reads none of the owner's own Mnemom credential vars.
|
|
1589
1680
|
stripOwnerCredentialEnv(proxyEnv);
|
|
1681
|
+
// C17-R16 T3: the proxy never reads TEST_MODE itself (one test-mode module per repo), so the seam
|
|
1682
|
+
// decision is made here and handed over as a non-secret flag; an inherited flag never passes.
|
|
1683
|
+
// httpDoorAllowed answers true for any https door, so the flag is set for an http door only.
|
|
1684
|
+
if (/^http:/i.test(door) && httpDoorAllowed(door, process.env))
|
|
1685
|
+
proxyEnv.MNEMOM_AGENT_BU_HTTP_DOOR = "1";
|
|
1686
|
+
else
|
|
1687
|
+
delete proxyEnv.MNEMOM_AGENT_BU_HTTP_DOOR;
|
|
1590
1688
|
const proxy = spawn(opts.node ?? process.execPath, [script], {
|
|
1591
1689
|
env: proxyEnv,
|
|
1592
1690
|
stdio: ["ignore", "pipe", "ignore"],
|
|
@@ -1779,8 +1877,9 @@ function launchRemoteControl(ctx) {
|
|
|
1779
1877
|
let stdoutBuf = "";
|
|
1780
1878
|
const start = Date.now();
|
|
1781
1879
|
const tryStart = () => {
|
|
1782
|
-
//
|
|
1783
|
-
// proxy killed by a signal (exitCode stays null) never
|
|
1880
|
+
// Torn down already (a refusal, the sync exit fallback, or an awaited onExit() from the proxy's
|
|
1881
|
+
// error / stdin paths): stop polling, so a proxy killed by a signal (exitCode stays null) never
|
|
1882
|
+
// reaches the 15s refusal and no later arm can call process.exit(1) a second time.
|
|
1784
1883
|
if (stopped)
|
|
1785
1884
|
return;
|
|
1786
1885
|
const m = stdoutBuf.match(/^READY (\d+) (.+)$/m);
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The durable lines of `mnemom agent doctor` (BACKBONE C1 "doctor (MFA, branch protection)";
|
|
3
|
+
* C02-R19): for each of the caller's open sessions, the MFA and branch-protection verdicts lock would
|
|
4
|
+
* compute at that moment, with the same probes and codes. The API computes them (S1 lock.ts steps
|
|
5
|
+
* `mfa` and `protection`); the doctor only renders. Read-only: one GET per session, never a POST.
|
|
6
|
+
*
|
|
7
|
+
* Not frozen by S1 (A2 owns it; gaps/c1-doctor-lock-preflight.md):
|
|
8
|
+
* GET /v1/agent/sessions/{cid}/lock?agent_id=&dry_run=true
|
|
9
|
+
* 200 {mfa, repos: [{repo, verdict | protected | reason, pushable?}]} (read defensively), or the
|
|
10
|
+
* lock refusal envelope of the first failing step (403 durable_lock_mfa_required, 409
|
|
11
|
+
* durable_lock_branch_unprotected {refusals}, 503 durable_precondition_unavailable, ...).
|
|
12
|
+
* A route that is absent (404/405) prints one info line; nothing here is ever a MISS, so the doctor's
|
|
13
|
+
* exit status keeps meaning "launch-ready".
|
|
14
|
+
*/
|
|
15
|
+
import { type ApiAnswer, type RegistryRow } from "../lib/durable/api.js";
|
|
16
|
+
export interface DoctorApi {
|
|
17
|
+
listSessions(filter: {
|
|
18
|
+
name?: string;
|
|
19
|
+
agentId?: string;
|
|
20
|
+
}): Promise<RegistryRow[]>;
|
|
21
|
+
lockPreflight(cid: string, agentId: string): Promise<ApiAnswer>;
|
|
22
|
+
}
|
|
23
|
+
export interface DoctorLine {
|
|
24
|
+
mark: "ok" | "warn" | "info";
|
|
25
|
+
text: string;
|
|
26
|
+
hint?: string;
|
|
27
|
+
}
|
|
28
|
+
export type RepoState = "protected" | "unprotected" | "unsupported_host" | "unknown";
|
|
29
|
+
export type PreflightVerdict = {
|
|
30
|
+
kind: "verdict";
|
|
31
|
+
mfa: "ok" | "missing" | "unknown";
|
|
32
|
+
repos: Array<{
|
|
33
|
+
repo: string;
|
|
34
|
+
state: RepoState;
|
|
35
|
+
}>;
|
|
36
|
+
} | {
|
|
37
|
+
kind: "absent";
|
|
38
|
+
} | {
|
|
39
|
+
kind: "unavailable";
|
|
40
|
+
detail: string;
|
|
41
|
+
} | {
|
|
42
|
+
kind: "earlier";
|
|
43
|
+
code: string;
|
|
44
|
+
};
|
|
45
|
+
/** At most this many open sessions are checked (one GET each). */
|
|
46
|
+
export declare const DOCTOR_SESSION_CAP = 5;
|
|
47
|
+
/** Read a preflight answer into the verdict the doctor prints. */
|
|
48
|
+
export declare function preflightVerdict(a: ApiAnswer): PreflightVerdict;
|
|
49
|
+
/** The doctor's durable lines: one block per open session (newest first as listed), capped. */
|
|
50
|
+
export declare function durableDoctorLines(api: DoctorApi): Promise<DoctorLine[]>;
|