@mnemom/mnemom 0.17.34-next.1 → 0.17.34-next.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/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-run.d.ts +14 -6
- package/dist/commands/agent-run.js +44 -27
- 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-share-test-issuer.d.ts +67 -0
- package/dist/commands/agent-share-test-issuer.js +139 -0
- package/dist/commands/agent-share.d.ts +7 -1
- package/dist/commands/agent-share.js +14 -4
- package/dist/commands/agent.d.ts +18 -5
- package/dist/commands/agent.js +186 -59
- package/dist/commands/durable-doctor.d.ts +50 -0
- package/dist/commands/durable-doctor.js +186 -0
- package/dist/commands/durable-lock.d.ts +261 -0
- package/dist/commands/durable-lock.js +600 -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/commands/scan.d.ts +12 -1
- package/dist/commands/scan.js +22 -3
- package/dist/commands/wrap.js +1 -1
- package/dist/durable/adopt-live.js +5 -8
- package/dist/durable/adopt.d.ts +8 -0
- package/dist/durable/adopt.js +27 -6
- package/dist/durable/cwd-rule.d.ts +9 -1
- package/dist/durable/cwd-rule.js +18 -0
- package/dist/durable/flock.d.ts +26 -0
- package/dist/durable/flock.js +80 -0
- package/dist/durable/launch-spec.d.ts +9 -1
- package/dist/durable/launch-spec.js +11 -4
- 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 +105 -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 +125 -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 +221 -0
- package/dist/host/daemon.js +616 -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 +58 -0
- package/dist/host/owner.js +123 -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 +65 -0
- package/dist/host/run.js +299 -0
- package/dist/host/runtime.d.ts +8 -0
- package/dist/host/runtime.js +22 -0
- package/dist/host/s1-names.d.ts +23 -0
- package/dist/host/s1-names.js +22 -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 +52 -0
- package/dist/host/verbs.js +282 -0
- package/dist/index.d.ts +8 -6
- package/dist/index.js +209 -43
- package/dist/lib/agent.d.ts +13 -29
- package/dist/lib/agent.js +17 -29
- package/dist/lib/claude-bin.d.ts +6 -0
- package/dist/lib/claude-bin.js +26 -0
- package/dist/lib/claude-plugin.d.ts +11 -2
- package/dist/lib/claude-plugin.js +10 -1
- package/dist/lib/commit-report.d.ts +5 -4
- package/dist/lib/commit-report.js +6 -9
- package/dist/lib/config.d.ts +8 -1
- package/dist/lib/config.js +28 -0
- 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/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 +199 -0
- package/dist/lib/durable/launch.js +461 -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 +190 -0
- package/dist/lib/durable/live.js +633 -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 +13 -0
- package/dist/lib/launcher-config.js +32 -0
- package/dist/lib/runner/exec-append.d.ts +15 -0
- package/dist/lib/runner/exec-append.js +2 -0
- package/dist/lib/runner/executor-client.d.ts +2 -1
- package/dist/lib/runner/executor-state.d.ts +10 -0
- package/dist/lib/runner/executor-state.js +24 -0
- package/dist/lib/runner/github-base.d.ts +24 -0
- package/dist/lib/runner/github-base.js +27 -0
- package/dist/lib/runner/job.d.ts +9 -4
- package/dist/lib/runner/job.js +43 -15
- package/dist/lib/runner/oidc.d.ts +3 -2
- package/dist/lib/runner/oidc.js +7 -4
- package/dist/lib/runner/options.d.ts +6 -0
- package/dist/lib/runner/options.js +19 -1
- package/dist/lib/runner/phases.d.ts +7 -4
- package/dist/lib/runner/phases.js +8 -4
- package/dist/lib/runner/proxy.d.ts +5 -1
- package/dist/lib/runner/proxy.js +7 -1
- package/dist/lib/scan/cost-per-pr.d.ts +93 -0
- package/dist/lib/scan/cost-per-pr.js +101 -0
- package/dist/lib/scan/record.d.ts +27 -1
- package/dist/lib/scan/record.js +114 -16
- package/dist/lib/scan/report-email.d.ts +26 -6
- package/dist/lib/scan/report-email.js +32 -3
- package/dist/lib/scan/scan.d.ts +3 -0
- package/dist/lib/scan/scan.js +13 -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
|
@@ -6,7 +6,11 @@
|
|
|
6
6
|
* (W6.2 — the share offer never auto-posts).
|
|
7
7
|
*
|
|
8
8
|
* Gated by MNEMOM_AGENT_SHARE=1 (not experimental-registry; off by default, so default CLI
|
|
9
|
-
* behaviour is unchanged)
|
|
9
|
+
* behaviour is unchanged), EXCEPT when the CLI targets the test cell (MNEMOM_RING=test, or API
|
|
10
|
+
* and website both resolving to api-az-test.mnemom.ai / test.mnemom.ai): there it is on without
|
|
11
|
+
* the flag. The test target is detected once, by `isTestCellTarget` (agent-share-test-issuer.ts),
|
|
12
|
+
* and reaches the command as `deps.targetsTestCell`. Prod and every other ring stay gated.
|
|
13
|
+
* Everything outside the process is injected:
|
|
10
14
|
* - `source` lists candidate lines from the user's signed statements,
|
|
11
15
|
* - `issuer` returns the SIGNED AAP-ShareCard/v1 for the chosen lines (the CLI never holds
|
|
12
16
|
* a signing key),
|
|
@@ -15,9 +19,10 @@
|
|
|
15
19
|
* identifying fields, and re-checks the returned card for the same.
|
|
16
20
|
*/
|
|
17
21
|
import { createInterface } from "node:readline/promises";
|
|
18
|
-
import {
|
|
22
|
+
import { getShareWebsiteUrl } from "../lib/config.js";
|
|
19
23
|
import { fmt } from "../lib/format.js";
|
|
20
24
|
import { aipLineRenderer, aipLineScreen } from "./agent-share-render.js";
|
|
25
|
+
import { isTestSignedKeyId } from "./agent-share-test-issuer.js";
|
|
21
26
|
export const AGENT_SHARE_ENV = "MNEMOM_AGENT_SHARE";
|
|
22
27
|
export function agentShareEnabled(env = process.env) {
|
|
23
28
|
return env[AGENT_SHARE_ENV] === "1";
|
|
@@ -117,7 +122,7 @@ function checkIssuedCard(card, chosen) {
|
|
|
117
122
|
return null;
|
|
118
123
|
}
|
|
119
124
|
export async function agentShareCommand(flags, deps, env = process.env) {
|
|
120
|
-
if (!agentShareEnabled(env)) {
|
|
125
|
+
if (!agentShareEnabled(env) && deps.targetsTestCell?.() !== true) {
|
|
121
126
|
deps.err(fmt.error("unknown command 'share' for 'mnemom agent'"));
|
|
122
127
|
return 1;
|
|
123
128
|
}
|
|
@@ -218,6 +223,11 @@ export async function agentShareCommand(flags, deps, env = process.env) {
|
|
|
218
223
|
deps.err(fmt.error(`Refusing to share: ${problem}.`));
|
|
219
224
|
return 1;
|
|
220
225
|
}
|
|
226
|
+
// A test-signed card (aip test key id) may only leave a test-cell run; never a prod/www one.
|
|
227
|
+
if (isTestSignedKeyId(card.signing_key_id) && deps.targetsTestCell?.() !== true) {
|
|
228
|
+
deps.err(fmt.error("Refusing to share: the card is test-signed but this is not the test cell."));
|
|
229
|
+
return 1;
|
|
230
|
+
}
|
|
221
231
|
// The renderer is the gate: nothing is linked unless aip renders the card (verified, lint clean).
|
|
222
232
|
let rendered;
|
|
223
233
|
try {
|
|
@@ -266,7 +276,7 @@ export const liveAgentShareDeps = {
|
|
|
266
276
|
}
|
|
267
277
|
},
|
|
268
278
|
isTTY: () => Boolean(process.stdin.isTTY),
|
|
269
|
-
websiteBase:
|
|
279
|
+
websiteBase: getShareWebsiteUrl,
|
|
270
280
|
out: (line) => console.log(line),
|
|
271
281
|
err: (line) => console.error(line),
|
|
272
282
|
};
|
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;
|
|
@@ -298,6 +307,8 @@ export declare function promptForCliPath(cliOpt: string | undefined, env: NodeJS
|
|
|
298
307
|
* Does the resolved coding-agent CLI advertise the `--remote-control` startup
|
|
299
308
|
* flag (Claude Code >= 2.1.269)? Determined by scanning its `--help`. Best-effort:
|
|
300
309
|
* a spawn failure or timeout reads as "no". Only the claude adapter wires RC.
|
|
310
|
+
* One launch asks this more than once (the shape choice, then the lock's RC naming), so the answer
|
|
311
|
+
* for an unchanged binary is remembered and `--help` runs once per process (BACKBONE l.150).
|
|
301
312
|
*/
|
|
302
313
|
export declare function cliSupportsRemoteControl(bin: string): boolean;
|
|
303
314
|
/** The result of checking whether Remote Control can run here. */
|
|
@@ -334,6 +345,7 @@ export declare function proxyWanted(o: {
|
|
|
334
345
|
turnHeader: boolean;
|
|
335
346
|
receipt: boolean;
|
|
336
347
|
commitReport: boolean;
|
|
348
|
+
fedStatusline?: boolean;
|
|
337
349
|
}): boolean;
|
|
338
350
|
/**
|
|
339
351
|
* Whether the proxy captures the gateway's record state: the conduct receipt reads it at exit,
|
|
@@ -360,8 +372,6 @@ export declare function prepareStatuslineFeed(o: {
|
|
|
360
372
|
statusline: boolean;
|
|
361
373
|
orgId?: string;
|
|
362
374
|
version: string;
|
|
363
|
-
goalMode: boolean;
|
|
364
|
-
env: NodeJS.ProcessEnv;
|
|
365
375
|
childEnv: NodeJS.ProcessEnv;
|
|
366
376
|
files?: () => {
|
|
367
377
|
feedFile: string;
|
|
@@ -380,10 +390,11 @@ export declare function recordOrgWanted(o: {
|
|
|
380
390
|
}): boolean;
|
|
381
391
|
/**
|
|
382
392
|
* Whether this launch feeds the status line measured model cost and μ (W1.4): prerelease
|
|
383
|
-
* builds only (stable stays "mnemom", decision 14)
|
|
384
|
-
*
|
|
393
|
+
* builds only (stable stays "mnemom", decision 14). Not tied to goal mode: the base-URL proxy
|
|
394
|
+
* runs for a fed launch (proxyWanted) and records the lineage key whatever the goal mode, and
|
|
395
|
+
* since A-W-016 a -next.N launch has no goal by default.
|
|
385
396
|
*/
|
|
386
|
-
export declare function fedStatuslineWanted(version: string
|
|
397
|
+
export declare function fedStatuslineWanted(version: string): boolean;
|
|
387
398
|
/**
|
|
388
399
|
* Add 127.0.0.1 and localhost to BOTH NO_PROXY and no_proxy (proxy clients disagree on which one
|
|
389
400
|
* wins, and some read only the lowercase one), keeping whatever each already listed.
|
|
@@ -397,6 +408,8 @@ export declare function exemptLoopbackFromProxy(env: NodeJS.ProcessEnv): void;
|
|
|
397
408
|
export declare function sayConductReceipt(launch: ReceiptLaunch, trigger?: "exit" | "park", receipt?: typeof conductReceiptFor): Promise<void>;
|
|
398
409
|
/** Whether a terminal implicit launch runs the turn-origin proxy (MNEMOM_AGENT_TURN_HEADER=0 opts out). */
|
|
399
410
|
export declare function turnProxyWanted(env: NodeJS.ProcessEnv): boolean;
|
|
411
|
+
/** The flag that lets base-url-proxy.mjs use an http door on a loopback literal (C17-R16 T3). */
|
|
412
|
+
export declare const BU_HTTP_DOOR_ENV = "MNEMOM_AGENT_BU_HTTP_DOOR";
|
|
400
413
|
/** Test seams for startBaseUrlProxy (every real caller omits them). */
|
|
401
414
|
export interface BaseUrlProxyOptions {
|
|
402
415
|
/** 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, launchRow, locksAtCreate, planDurableLaunch, preReadRegistry, trackedNoTtyLine, } from "../lib/durable/launch.js";
|
|
63
|
+
import { liveDurableApi, liveHomeFacts, livePlanDeps, liveRegistryReads, liveSeatDeps, 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,82 @@ 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
|
+
if (durablePlan) {
|
|
590
|
+
options = {
|
|
591
|
+
...options,
|
|
592
|
+
passthrough: durablePlan.newCcSessionId
|
|
593
|
+
? [...durablePlan.passthrough, "--session-id", durablePlan.newCcSessionId]
|
|
594
|
+
: durablePlan.passthrough,
|
|
595
|
+
};
|
|
596
|
+
// Under `always` / ask-yes the launch locks instead of spawning (C02-R23 c, e). A new CC session
|
|
597
|
+
// is created locked in one request and attached (ruling A, widening B5a to every agent): no
|
|
598
|
+
// attended claude runs first. A continued one registers and locks its row. A lock refusal prints
|
|
599
|
+
// the refusal and its fix and exits 1 for every agent (C02-R24 for a durable-only one): a launch
|
|
600
|
+
// that locks never falls back to an attended spawn, except on an api without migration 06a
|
|
601
|
+
// (create-and-lock answers null): it registers tracked and spawns as before ruling A.
|
|
602
|
+
const atCreate = locksAtCreate(durablePlan);
|
|
603
|
+
const created = atCreate
|
|
604
|
+
? await createLockedInsteadOfSpawn(durablePlan, scenario, servedAgentId, durableKey, { model: options.model, effort: options.effort, trimMcp: options.trimMcp }, durablePlan.durableOnly)
|
|
605
|
+
: null;
|
|
606
|
+
if (created !== null) {
|
|
607
|
+
process.exitCode = created;
|
|
608
|
+
return;
|
|
609
|
+
}
|
|
610
|
+
const row = await registerDurableSession(durablePlan, scenario, servedAgentId);
|
|
611
|
+
clearLaunching(scenario, liveLaunchingDeps(MNEMOM_DIR));
|
|
612
|
+
durableRow = launchRow(row, durablePlan);
|
|
613
|
+
if (durablePlan.lock && !atCreate) {
|
|
614
|
+
process.exitCode = await lockInsteadOfSpawn(row, scenario, durableKey, durablePlan.durableOnly);
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
617
|
+
}
|
|
536
618
|
// A relaunch that continues an earlier conversation resumes that Claude Code session, so the
|
|
537
619
|
// same CC session id keeps owning the conversation's main goal on the gateway (a fresh
|
|
538
620
|
// --session-id would land on a new, empty m.<sha16> thread). The server shape hosts many
|
|
@@ -704,6 +786,7 @@ export async function agentCommand(scenarioArg, options = {}) {
|
|
|
704
786
|
goalMode,
|
|
705
787
|
scenario,
|
|
706
788
|
launcherVersion: CLI_VERSION,
|
|
789
|
+
...(options.durable ? { durable: true } : {}),
|
|
707
790
|
// The mod re-runs this CLI (`agent show --json`, `agent goal show --json`) for the live
|
|
708
791
|
// goal state; execArgv keeps a `tsx`/`--import` dev launch runnable the same way.
|
|
709
792
|
launcherExec: process.argv[1]
|
|
@@ -765,8 +848,8 @@ export async function agentCommand(scenarioArg, options = {}) {
|
|
|
765
848
|
// Record keys the lineage under the served agent's org, not the account org (lib/record-org).
|
|
766
849
|
const recordOrgId = recordOrgWanted({
|
|
767
850
|
receipt: experimental.includes("record"),
|
|
768
|
-
commitReport: cli.name === "claude" && commitReportEnabled(process.env[COMMIT_REPORT_ENV]
|
|
769
|
-
fedStatusline: cli.name === "claude" && fedStatuslineWanted(CLI_VERSION
|
|
851
|
+
commitReport: cli.name === "claude" && commitReportEnabled(process.env[COMMIT_REPORT_ENV]),
|
|
852
|
+
fedStatusline: cli.name === "claude" && fedStatuslineWanted(CLI_VERSION),
|
|
770
853
|
}) && account.orgId
|
|
771
854
|
? await readServedOrg(account.orgId).then((o) => {
|
|
772
855
|
if (o.fallback)
|
|
@@ -803,13 +886,22 @@ export async function agentCommand(scenarioArg, options = {}) {
|
|
|
803
886
|
}),
|
|
804
887
|
knownCcSessionId: cc.id ?? continuedCcSessionId,
|
|
805
888
|
plugin,
|
|
889
|
+
...(durableRow && options.durable
|
|
890
|
+
? {
|
|
891
|
+
durableHandover: {
|
|
892
|
+
row: durableRow,
|
|
893
|
+
name: scenario,
|
|
894
|
+
launcherKey: options.durable.launcherKey,
|
|
895
|
+
},
|
|
896
|
+
}
|
|
897
|
+
: {}),
|
|
806
898
|
receipt: experimental.includes("record")
|
|
807
899
|
? { orgId: recordOrgId, agentId: servedAgentId }
|
|
808
900
|
: undefined,
|
|
809
901
|
orgId: recordOrgId,
|
|
810
902
|
commitReport: cli.name === "claude" &&
|
|
811
903
|
Boolean(account.orgId) &&
|
|
812
|
-
commitReportEnabled(process.env[COMMIT_REPORT_ENV]
|
|
904
|
+
commitReportEnabled(process.env[COMMIT_REPORT_ENV]),
|
|
813
905
|
});
|
|
814
906
|
return; // launchTerminal exits the process
|
|
815
907
|
}
|
|
@@ -918,6 +1010,18 @@ export function resumeHintLine(ctx) {
|
|
|
918
1010
|
return (`Resume with: ${resumeCommand(ctx)}` +
|
|
919
1011
|
" (keeps it governed; `claude --resume` runs outside Mnemom)");
|
|
920
1012
|
}
|
|
1013
|
+
/** Print trackedNoTtyLine's warning, if any, just before an interactive tracked spawn. */
|
|
1014
|
+
function warnNoTty(ctx) {
|
|
1015
|
+
const line = trackedNoTtyLine({
|
|
1016
|
+
name: ctx.scenario,
|
|
1017
|
+
gateOn: ctx.options.durable !== undefined,
|
|
1018
|
+
stdinIsTTY: Boolean(process.stdin.isTTY),
|
|
1019
|
+
cliName: ctx.cli.name,
|
|
1020
|
+
passthrough: ctx.options.passthrough ?? [],
|
|
1021
|
+
});
|
|
1022
|
+
if (line !== null)
|
|
1023
|
+
console.error(line);
|
|
1024
|
+
}
|
|
921
1025
|
/** An interactive Claude Code session — not `-p` / `--print`, which has nothing to resume into. */
|
|
922
1026
|
function wantsResumeHint(cli, args) {
|
|
923
1027
|
return cli.name === "claude" && !args.includes("-p") && !args.includes("--print");
|
|
@@ -1249,20 +1353,39 @@ function commandExists(cmd) {
|
|
|
1249
1353
|
});
|
|
1250
1354
|
return res.status === 0;
|
|
1251
1355
|
}
|
|
1356
|
+
/** Probe answers per binary, keyed by path, size and mtime: a replaced binary is probed again. */
|
|
1357
|
+
const rcProbeMemo = new Map();
|
|
1252
1358
|
/**
|
|
1253
1359
|
* Does the resolved coding-agent CLI advertise the `--remote-control` startup
|
|
1254
1360
|
* flag (Claude Code >= 2.1.269)? Determined by scanning its `--help`. Best-effort:
|
|
1255
1361
|
* a spawn failure or timeout reads as "no". Only the claude adapter wires RC.
|
|
1362
|
+
* One launch asks this more than once (the shape choice, then the lock's RC naming), so the answer
|
|
1363
|
+
* for an unchanged binary is remembered and `--help` runs once per process (BACKBONE l.150).
|
|
1256
1364
|
*/
|
|
1257
1365
|
export function cliSupportsRemoteControl(bin) {
|
|
1366
|
+
let key = null;
|
|
1367
|
+
try {
|
|
1368
|
+
const st = statSync(bin);
|
|
1369
|
+
key = `${bin}\0${st.size}\0${st.mtimeMs}`;
|
|
1370
|
+
}
|
|
1371
|
+
catch {
|
|
1372
|
+
// unstatable (e.g. a bare command name): probe every time, as before
|
|
1373
|
+
}
|
|
1374
|
+
const known = key === null ? undefined : rcProbeMemo.get(key);
|
|
1375
|
+
if (known !== undefined)
|
|
1376
|
+
return known;
|
|
1377
|
+
let ok;
|
|
1258
1378
|
try {
|
|
1259
1379
|
const res = spawnSync(bin, ["--help"], { encoding: "utf8", timeout: 10_000 });
|
|
1260
1380
|
const text = `${res.stdout ?? ""}${res.stderr ?? ""}`;
|
|
1261
|
-
|
|
1381
|
+
ok = /--remote-control/.test(text);
|
|
1262
1382
|
}
|
|
1263
1383
|
catch {
|
|
1264
|
-
|
|
1384
|
+
ok = false;
|
|
1265
1385
|
}
|
|
1386
|
+
if (key !== null)
|
|
1387
|
+
rcProbeMemo.set(key, ok);
|
|
1388
|
+
return ok;
|
|
1266
1389
|
}
|
|
1267
1390
|
/**
|
|
1268
1391
|
* Can we launch the DEFAULT Remote-Control-in-terminal shape on this machine?
|
|
@@ -1313,30 +1436,14 @@ function launchTerminal(ctx) {
|
|
|
1313
1436
|
handoffMcpConfigPath: ctx.handoffMcpConfigPath,
|
|
1314
1437
|
pluginDir: ctx.plugin?.dir,
|
|
1315
1438
|
});
|
|
1316
|
-
//
|
|
1317
|
-
//
|
|
1318
|
-
//
|
|
1319
|
-
//
|
|
1320
|
-
//
|
|
1321
|
-
//
|
|
1322
|
-
|
|
1439
|
+
// The api-key arm (key in ANTHROPIC_CUSTOM_HEADERS, placeholder auth token, ANTHROPIC_API_KEY
|
|
1440
|
+
// unset) or the subscription arm (Claude Code's own claude.ai login), shared with durable seats
|
|
1441
|
+
// (lib/subscription-login.ts launchChildEnv). MNE-8245 defense-in-depth: the owner's own Mnemom
|
|
1442
|
+
// credential must never ride through to the session just because it happened to sit in the
|
|
1443
|
+
// CLI's OWN process env — the session's only Mnemom credential is the minted key already
|
|
1444
|
+
// spliced into `headers` above (ctx.mnemomKey).
|
|
1445
|
+
const childEnv = launchChildEnv(process.env, ctx.door, headers, ctx.anthropicKey);
|
|
1323
1446
|
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
1447
|
// Both auth arms point ANTHROPIC_BASE_URL at the gateway door, which makes Claude Code turn
|
|
1341
1448
|
// tool search off and inline every tool schema on every request — unless told otherwise.
|
|
1342
1449
|
applyToolSearchDefault(childEnv);
|
|
@@ -1346,10 +1453,9 @@ function launchTerminal(ctx) {
|
|
|
1346
1453
|
const sessionName = passthroughName(ctx.options.passthrough ?? []) ?? sessionDisplayName(ctx.agent, ctx.scenario);
|
|
1347
1454
|
childEnv[SESSION_NAME_ENV] = sessionName;
|
|
1348
1455
|
// 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);
|
|
1456
|
+
// terminal launch — interactive and `-p` passthrough alike; a user's own value wins: set by
|
|
1457
|
+
// launchChildEnv above (shared with durable seats). The user's CLAUDE_AUTOCOMPACT_PCT_OVERRIDE /
|
|
1458
|
+
// CLAUDE_CODE_AUTO_COMPACT_WINDOW ride through untouched in the copied env (USER_AUTOCOMPACT_ENV).
|
|
1353
1459
|
if (ctx.options.slack)
|
|
1354
1460
|
args.push(...slackChannelArgs(sessionName));
|
|
1355
1461
|
// The status-bar segment (lib/statusline.ts). Claude Code only; the user's own
|
|
@@ -1369,8 +1475,6 @@ function launchTerminal(ctx) {
|
|
|
1369
1475
|
statusline: sl !== null,
|
|
1370
1476
|
orgId: ctx.orgId,
|
|
1371
1477
|
version: CLI_VERSION,
|
|
1372
|
-
goalMode: Boolean(ctx.goalMode),
|
|
1373
|
-
env: process.env,
|
|
1374
1478
|
childEnv,
|
|
1375
1479
|
});
|
|
1376
1480
|
// Autocompact guard (lib/autocompact-guard.ts): one --settings carries every layer. The
|
|
@@ -1394,7 +1498,9 @@ function launchTerminal(ctx) {
|
|
|
1394
1498
|
}
|
|
1395
1499
|
let recordStatePath;
|
|
1396
1500
|
const run = () => {
|
|
1501
|
+
const savedTty = ctx.durableHandover ? saveTtyMode() : null;
|
|
1397
1502
|
const launchedSec = Math.floor(Date.now() / 1000);
|
|
1503
|
+
warnNoTty(ctx);
|
|
1398
1504
|
const child = spawn(ctx.cli.bin, args, { stdio: "inherit", env: childEnv });
|
|
1399
1505
|
// W1.8: report the session's commits while it runs; no record state (no proxy) → no reports.
|
|
1400
1506
|
const commits = ctx.commitReport && ctx.orgId && recordStatePath
|
|
@@ -1416,6 +1522,14 @@ function launchTerminal(ctx) {
|
|
|
1416
1522
|
if (ctx.sessionKeyId) {
|
|
1417
1523
|
await revokeSessionGatewayKey(defaultSessionKeyDeps(say), ctx.sessionKeyId);
|
|
1418
1524
|
}
|
|
1525
|
+
// A lock handover SIGKILLs the frozen claude once the daemon's seat runs the conversation:
|
|
1526
|
+
// attach to it instead of dying with the child (C02-R26 step 7). Never relaunches claude.
|
|
1527
|
+
const h = ctx.durableHandover;
|
|
1528
|
+
if (mayBeHandover(h !== undefined, signal) && h) {
|
|
1529
|
+
const attached = await liveParentAfterChildEnded(h.row, h.name, h.launcherKey, savedTty);
|
|
1530
|
+
if (attached !== null)
|
|
1531
|
+
process.exit(attached);
|
|
1532
|
+
}
|
|
1419
1533
|
if (signal) {
|
|
1420
1534
|
process.kill(process.pid, signal);
|
|
1421
1535
|
return;
|
|
@@ -1447,6 +1561,7 @@ function launchTerminal(ctx) {
|
|
|
1447
1561
|
turnHeader,
|
|
1448
1562
|
receipt: Boolean(ctx.receipt),
|
|
1449
1563
|
commitReport: Boolean(ctx.commitReport),
|
|
1564
|
+
fedStatusline: feed !== null,
|
|
1450
1565
|
})) {
|
|
1451
1566
|
run();
|
|
1452
1567
|
return;
|
|
@@ -1479,7 +1594,7 @@ export function launchSettingsExtra(o) {
|
|
|
1479
1594
|
* commit reporting (W1.8) all need it; commit reporting reads the lineage key it records.
|
|
1480
1595
|
*/
|
|
1481
1596
|
export function proxyWanted(o) {
|
|
1482
|
-
return o.turnHeader || o.receipt || o.commitReport;
|
|
1597
|
+
return o.turnHeader || o.receipt || o.commitReport || Boolean(o.fedStatusline);
|
|
1483
1598
|
}
|
|
1484
1599
|
/**
|
|
1485
1600
|
* Whether the proxy captures the gateway's record state: the conduct receipt reads it at exit,
|
|
@@ -1506,7 +1621,7 @@ export function onBaseUrlProxyReady(baseUrl, recordStateFile, childEnv, feed, st
|
|
|
1506
1621
|
* segment off, no billing org, or {@link fedStatuslineWanted} false.
|
|
1507
1622
|
*/
|
|
1508
1623
|
export function prepareStatuslineFeed(o) {
|
|
1509
|
-
if (!o.statusline || !o.orgId || !fedStatuslineWanted(o.version
|
|
1624
|
+
if (!o.statusline || !o.orgId || !fedStatuslineWanted(o.version))
|
|
1510
1625
|
return null;
|
|
1511
1626
|
const feed = (o.files ?? fedStatuslineFiles)();
|
|
1512
1627
|
o.childEnv[FEED_ENV] = feed.feedFile;
|
|
@@ -1521,11 +1636,12 @@ export function recordOrgWanted(o) {
|
|
|
1521
1636
|
}
|
|
1522
1637
|
/**
|
|
1523
1638
|
* Whether this launch feeds the status line measured model cost and μ (W1.4): prerelease
|
|
1524
|
-
* builds only (stable stays "mnemom", decision 14)
|
|
1525
|
-
*
|
|
1639
|
+
* builds only (stable stays "mnemom", decision 14). Not tied to goal mode: the base-URL proxy
|
|
1640
|
+
* runs for a fed launch (proxyWanted) and records the lineage key whatever the goal mode, and
|
|
1641
|
+
* since A-W-016 a -next.N launch has no goal by default.
|
|
1526
1642
|
*/
|
|
1527
|
-
export function fedStatuslineWanted(version
|
|
1528
|
-
return isPrerelease(version)
|
|
1643
|
+
export function fedStatuslineWanted(version) {
|
|
1644
|
+
return isPrerelease(version);
|
|
1529
1645
|
}
|
|
1530
1646
|
/** A private per-session dir holding the feed (launcher → bin). */
|
|
1531
1647
|
function fedStatuslineFiles() {
|
|
@@ -1562,6 +1678,8 @@ export function turnProxyWanted(env) {
|
|
|
1562
1678
|
const raw = (env.MNEMOM_AGENT_TURN_HEADER ?? "").trim().toLowerCase();
|
|
1563
1679
|
return !["0", "off", "false", "no"].includes(raw);
|
|
1564
1680
|
}
|
|
1681
|
+
/** The flag that lets base-url-proxy.mjs use an http door on a loopback literal (C17-R16 T3). */
|
|
1682
|
+
export const BU_HTTP_DOOR_ENV = "MNEMOM_AGENT_BU_HTTP_DOOR";
|
|
1565
1683
|
/**
|
|
1566
1684
|
* Start base-url-proxy.mjs in front of `door`; calls `done` exactly once with its
|
|
1567
1685
|
* `http://127.0.0.1:<port>` base URL, or with null (after a one-line warning) when it does not
|
|
@@ -1587,6 +1705,13 @@ export function startBaseUrlProxy(door, done, opts = {}) {
|
|
|
1587
1705
|
};
|
|
1588
1706
|
// MNE-8245 defense-in-depth: the proxy reads none of the owner's own Mnemom credential vars.
|
|
1589
1707
|
stripOwnerCredentialEnv(proxyEnv);
|
|
1708
|
+
// C17-R16 T3: the proxy never reads TEST_MODE itself (one test-mode module per repo), so the seam
|
|
1709
|
+
// decision is made here and handed over as a non-secret flag; an inherited flag never passes.
|
|
1710
|
+
// httpDoorAllowed answers true for any https door, so the flag is set for an http door only.
|
|
1711
|
+
if (/^http:/i.test(door) && httpDoorAllowed(door, process.env))
|
|
1712
|
+
proxyEnv.MNEMOM_AGENT_BU_HTTP_DOOR = "1";
|
|
1713
|
+
else
|
|
1714
|
+
delete proxyEnv.MNEMOM_AGENT_BU_HTTP_DOOR;
|
|
1590
1715
|
const proxy = spawn(opts.node ?? process.execPath, [script], {
|
|
1591
1716
|
env: proxyEnv,
|
|
1592
1717
|
stdio: ["ignore", "pipe", "ignore"],
|
|
@@ -1779,8 +1904,9 @@ function launchRemoteControl(ctx) {
|
|
|
1779
1904
|
let stdoutBuf = "";
|
|
1780
1905
|
const start = Date.now();
|
|
1781
1906
|
const tryStart = () => {
|
|
1782
|
-
//
|
|
1783
|
-
// proxy killed by a signal (exitCode stays null) never
|
|
1907
|
+
// Torn down already (a refusal, the sync exit fallback, or an awaited onExit() from the proxy's
|
|
1908
|
+
// error / stdin paths): stop polling, so a proxy killed by a signal (exitCode stays null) never
|
|
1909
|
+
// reaches the 15s refusal and no later arm can call process.exit(1) a second time.
|
|
1784
1910
|
if (stopped)
|
|
1785
1911
|
return;
|
|
1786
1912
|
const m = stdoutBuf.match(/^READY (\d+) (.+)$/m);
|
|
@@ -1978,6 +2104,7 @@ function spawnRcCli(ctx) {
|
|
|
1978
2104
|
if (ctx.options.slack)
|
|
1979
2105
|
args.push(...slackChannelArgs(rcName));
|
|
1980
2106
|
say(`mnemom agent: remote-control: interactive session in THIS terminal with Remote Control on (session name '${rcName}'); it also appears under Remote Control at claude.ai/code and in the Claude app. Exit the CLI to stop everything.`);
|
|
2107
|
+
warnNoTty(ctx);
|
|
1981
2108
|
}
|
|
1982
2109
|
const child = spawn(ctx.cli.bin, args, { stdio: "inherit", env: childEnv });
|
|
1983
2110
|
child.on("error", (err) => {
|
|
@@ -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[]>;
|