@mnemom/mnemom 0.17.33 → 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-share-render.d.ts +1 -1
- package/dist/commands/agent-share-render.js +1 -1
- package/dist/commands/agent.d.ts +26 -0
- package/dist/commands/agent.js +228 -53
- 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/commands/protection.js +1 -1
- package/dist/commands/scan.d.ts +97 -0
- package/dist/commands/scan.js +238 -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 +232 -35
- package/dist/lib/agent.d.ts +8 -26
- package/dist/lib/agent.js +11 -26
- package/dist/lib/api.d.ts +13 -0
- package/dist/lib/api.js +8 -0
- 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/caps.d.ts +35 -0
- package/dist/lib/scan/caps.js +67 -0
- package/dist/lib/scan/credential.d.ts +37 -0
- package/dist/lib/scan/credential.js +104 -0
- package/dist/lib/scan/failure.d.ts +10 -0
- package/dist/lib/scan/failure.js +43 -0
- package/dist/lib/scan/pr-source.d.ts +57 -0
- package/dist/lib/scan/pr-source.js +130 -0
- package/dist/lib/scan/record.d.ts +49 -0
- package/dist/lib/scan/record.js +85 -0
- package/dist/lib/scan/report-email.d.ts +92 -0
- package/dist/lib/scan/report-email.js +144 -0
- package/dist/lib/scan/report.d.ts +13 -0
- package/dist/lib/scan/report.js +30 -0
- package/dist/lib/scan/scan.d.ts +64 -0
- package/dist/lib/scan/scan.js +182 -0
- package/dist/lib/scan/share.d.ts +38 -0
- package/dist/lib/scan/share.js +47 -0
- package/dist/lib/scan/spend/ai-tool-detect.d.ts +6 -0
- package/dist/lib/scan/spend/ai-tool-detect.js +18 -0
- package/dist/lib/scan/spend/anthropic-usage.d.ts +57 -0
- package/dist/lib/scan/spend/anthropic-usage.js +244 -0
- package/dist/lib/scan/spend/cost-corpus.v1.d.ts +56 -0
- package/dist/lib/scan/spend/cost-corpus.v1.js +64 -0
- package/dist/lib/scan/spend/prices.d.ts +33 -0
- package/dist/lib/scan/spend/prices.js +57 -0
- package/dist/lib/scan/spend/spend-estimate.d.ts +30 -0
- package/dist/lib/scan/spend/spend-estimate.js +70 -0
- package/dist/lib/scan/spend/spend-tiers.d.ts +25 -0
- package/dist/lib/scan/spend/spend-tiers.js +42 -0
- package/dist/lib/scan/spend/token-rate.d.ts +30 -0
- package/dist/lib/scan/spend/token-rate.js +54 -0
- package/dist/lib/session-key.d.ts +41 -0
- package/dist/lib/session-key.js +124 -0
- package/dist/lib/subscription-login.d.ts +15 -0
- package/dist/lib/subscription-login.js +28 -1
- package/dist/rc-key-401.mjs +75 -0
- package/dist/rc-proxy.mjs +221 -25
- 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 +4 -3
- 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/commands/agent.js
CHANGED
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
* an argv value.
|
|
27
27
|
*/
|
|
28
28
|
import { spawn, spawnSync } from "node:child_process";
|
|
29
|
-
import { chmodSync, existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync, renameSync, } from "node:fs";
|
|
29
|
+
import { appendFileSync, chmodSync, existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync, renameSync, } from "node:fs";
|
|
30
30
|
import { randomBytes, randomUUID } from "node:crypto";
|
|
31
31
|
import { tmpdir, homedir, hostname } from "node:os";
|
|
32
32
|
import { basename, join, dirname } from "node:path";
|
|
@@ -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 {
|
|
43
|
-
import {
|
|
42
|
+
import { httpDoorAllowed } from "../durable/test-mode.mjs";
|
|
43
|
+
import { defaultSessionKeyDeps, mintSessionGatewayKey, revokeSessionGatewayKey, startSessionKeyRenewal, } from "../lib/session-key.js";
|
|
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
|
|
@@ -612,6 +695,8 @@ export async function agentCommand(scenarioArg, options = {}) {
|
|
|
612
695
|
// org is the key owner's personal org, where the agent does not exist, so the API answers 404
|
|
613
696
|
// and the launch would fail closed (door1-path run 40400479127). Same read as Record's.
|
|
614
697
|
let sessionKeyId;
|
|
698
|
+
// What a Remote Control launch needs to re-mint before the 24h expiry (startSessionKeyRenewal).
|
|
699
|
+
let sessionKeyRenewal;
|
|
615
700
|
// One read of the served agent's org serves the mint and Record; each says why in its own
|
|
616
701
|
// words when the read fails and the account org is used instead.
|
|
617
702
|
let servedOrg;
|
|
@@ -632,13 +717,15 @@ export async function agentCommand(scenarioArg, options = {}) {
|
|
|
632
717
|
say(fmt.warn(`mnemom agent: could not read ${servedAgentId}'s org (${o.fallback}); minting the session key under ${account.orgId}.`));
|
|
633
718
|
mintOrgId = o.orgId;
|
|
634
719
|
}
|
|
635
|
-
const
|
|
720
|
+
const mintArgs = {
|
|
636
721
|
agentId: servedAgentId,
|
|
637
722
|
orgId: mintOrgId,
|
|
638
723
|
sessionId: cc.id ?? undefined,
|
|
639
|
-
}
|
|
724
|
+
};
|
|
725
|
+
const minted = await mintSessionGatewayKey(defaultSessionKeyDeps(say), mintArgs);
|
|
640
726
|
mnemomKey = minted.key;
|
|
641
727
|
sessionKeyId = minted.keyId;
|
|
728
|
+
sessionKeyRenewal = { expiresAt: minted.expiresAt, mintArgs };
|
|
642
729
|
}
|
|
643
730
|
// ── 1M context window by default ── the model this launch runs on gets `[1m]` when it has no
|
|
644
731
|
// window suffix and supports 1M (resolveWindowModel); an explicit choice — the
|
|
@@ -700,6 +787,7 @@ export async function agentCommand(scenarioArg, options = {}) {
|
|
|
700
787
|
goalMode,
|
|
701
788
|
scenario,
|
|
702
789
|
launcherVersion: CLI_VERSION,
|
|
790
|
+
...(options.durable ? { durable: true } : {}),
|
|
703
791
|
// The mod re-runs this CLI (`agent show --json`, `agent goal show --json`) for the live
|
|
704
792
|
// goal state; execArgv keeps a `tsx`/`--import` dev launch runnable the same way.
|
|
705
793
|
launcherExec: process.argv[1]
|
|
@@ -799,6 +887,16 @@ export async function agentCommand(scenarioArg, options = {}) {
|
|
|
799
887
|
}),
|
|
800
888
|
knownCcSessionId: cc.id ?? continuedCcSessionId,
|
|
801
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
|
+
: {}),
|
|
802
900
|
receipt: experimental.includes("record")
|
|
803
901
|
? { orgId: recordOrgId, agentId: servedAgentId }
|
|
804
902
|
: undefined,
|
|
@@ -828,6 +926,7 @@ export async function agentCommand(scenarioArg, options = {}) {
|
|
|
828
926
|
yolo,
|
|
829
927
|
ccSessionId,
|
|
830
928
|
sessionKeyId,
|
|
929
|
+
sessionKeyRenewal,
|
|
831
930
|
});
|
|
832
931
|
}
|
|
833
932
|
// ───────────────────────────────────────────────────────────────────────────
|
|
@@ -1308,30 +1407,14 @@ function launchTerminal(ctx) {
|
|
|
1308
1407
|
handoffMcpConfigPath: ctx.handoffMcpConfigPath,
|
|
1309
1408
|
pluginDir: ctx.plugin?.dir,
|
|
1310
1409
|
});
|
|
1311
|
-
//
|
|
1312
|
-
//
|
|
1313
|
-
//
|
|
1314
|
-
//
|
|
1315
|
-
//
|
|
1316
|
-
//
|
|
1317
|
-
|
|
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);
|
|
1318
1417
|
let feed = null;
|
|
1319
|
-
if (ctx.anthropicKey) {
|
|
1320
|
-
childEnv = { ...process.env };
|
|
1321
|
-
delete childEnv.ANTHROPIC_API_KEY;
|
|
1322
|
-
childEnv.ANTHROPIC_BASE_URL = ctx.door;
|
|
1323
|
-
childEnv.ANTHROPIC_AUTH_TOKEN = "mnemom-gateway";
|
|
1324
|
-
childEnv.ANTHROPIC_CUSTOM_HEADERS = headers;
|
|
1325
|
-
}
|
|
1326
|
-
else {
|
|
1327
|
-
childEnv = subscriptionChildEnv(process.env, ctx.door, headers);
|
|
1328
|
-
}
|
|
1329
|
-
// MNE-8245 defense-in-depth: the owner's own Mnemom credential must never ride
|
|
1330
|
-
// through to the session just because it happened to sit in the CLI's OWN
|
|
1331
|
-
// process env (a shell MNEMOM_API_KEY / MNEMOM_TOKEN export, or a leftover
|
|
1332
|
-
// MNEMOM_AGENT_MNEMOM_KEY) — the session's only Mnemom credential is the
|
|
1333
|
-
// minted key already spliced into `headers` above (ctx.mnemomKey).
|
|
1334
|
-
stripOwnerCredentialEnv(childEnv);
|
|
1335
1418
|
// Both auth arms point ANTHROPIC_BASE_URL at the gateway door, which makes Claude Code turn
|
|
1336
1419
|
// tool search off and inline every tool schema on every request — unless told otherwise.
|
|
1337
1420
|
applyToolSearchDefault(childEnv);
|
|
@@ -1341,10 +1424,9 @@ function launchTerminal(ctx) {
|
|
|
1341
1424
|
const sessionName = passthroughName(ctx.options.passthrough ?? []) ?? sessionDisplayName(ctx.agent, ctx.scenario);
|
|
1342
1425
|
childEnv[SESSION_NAME_ENV] = sessionName;
|
|
1343
1426
|
// Exact compaction signal for the gateway (x-claude-code-request-class: compaction), on every
|
|
1344
|
-
// terminal launch — interactive and `-p` passthrough alike; a user's own value wins
|
|
1345
|
-
// user's CLAUDE_AUTOCOMPACT_PCT_OVERRIDE /
|
|
1346
|
-
// untouched in the copied env (USER_AUTOCOMPACT_ENV).
|
|
1347
|
-
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).
|
|
1348
1430
|
if (ctx.options.slack)
|
|
1349
1431
|
args.push(...slackChannelArgs(sessionName));
|
|
1350
1432
|
// The status-bar segment (lib/statusline.ts). Claude Code only; the user's own
|
|
@@ -1389,8 +1471,12 @@ function launchTerminal(ctx) {
|
|
|
1389
1471
|
}
|
|
1390
1472
|
let recordStatePath;
|
|
1391
1473
|
const run = () => {
|
|
1474
|
+
const savedTty = ctx.durableHandover ? saveTtyMode() : null;
|
|
1392
1475
|
const launchedSec = Math.floor(Date.now() / 1000);
|
|
1393
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);
|
|
1394
1480
|
// W1.8: report the session's commits while it runs; no record state (no proxy) → no reports.
|
|
1395
1481
|
const commits = ctx.commitReport && ctx.orgId && recordStatePath
|
|
1396
1482
|
? startCommitReporter({
|
|
@@ -1411,6 +1497,14 @@ function launchTerminal(ctx) {
|
|
|
1411
1497
|
if (ctx.sessionKeyId) {
|
|
1412
1498
|
await revokeSessionGatewayKey(defaultSessionKeyDeps(say), ctx.sessionKeyId);
|
|
1413
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
|
+
}
|
|
1414
1508
|
if (signal) {
|
|
1415
1509
|
process.kill(process.pid, signal);
|
|
1416
1510
|
return;
|
|
@@ -1557,6 +1651,8 @@ export function turnProxyWanted(env) {
|
|
|
1557
1651
|
const raw = (env.MNEMOM_AGENT_TURN_HEADER ?? "").trim().toLowerCase();
|
|
1558
1652
|
return !["0", "off", "false", "no"].includes(raw);
|
|
1559
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";
|
|
1560
1656
|
/**
|
|
1561
1657
|
* Start base-url-proxy.mjs in front of `door`; calls `done` exactly once with its
|
|
1562
1658
|
* `http://127.0.0.1:<port>` base URL, or with null (after a one-line warning) when it does not
|
|
@@ -1582,6 +1678,13 @@ export function startBaseUrlProxy(door, done, opts = {}) {
|
|
|
1582
1678
|
};
|
|
1583
1679
|
// MNE-8245 defense-in-depth: the proxy reads none of the owner's own Mnemom credential vars.
|
|
1584
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;
|
|
1585
1688
|
const proxy = spawn(opts.node ?? process.execPath, [script], {
|
|
1586
1689
|
env: proxyEnv,
|
|
1587
1690
|
stdio: ["ignore", "pipe", "ignore"],
|
|
@@ -1688,13 +1791,28 @@ function launchRemoteControl(ctx) {
|
|
|
1688
1791
|
MNEMOM_AGENT_RC_PARENT_PID: String(process.pid),
|
|
1689
1792
|
MNEMOM_AGENT_RC_AUTH: ctx.anthropicKey ? "api-key" : "subscription",
|
|
1690
1793
|
};
|
|
1794
|
+
// The minted key expires after 24h and an RC seat outlives that: renew it over a control pipe
|
|
1795
|
+
// (fd 3) — the new key never touches argv, env or disk. Only its id and expiry ride in the env.
|
|
1796
|
+
const renewing = Boolean(ctx.sessionKeyId && ctx.sessionKeyRenewal && ctx.mnemomKey);
|
|
1797
|
+
if (renewing) {
|
|
1798
|
+
proxyEnv.MNEMOM_AGENT_RC_CONTROL_FD = "3";
|
|
1799
|
+
proxyEnv.MNEMOM_AGENT_RC_MNEMOM_KEY_ID = ctx.sessionKeyId;
|
|
1800
|
+
proxyEnv.MNEMOM_AGENT_RC_MNEMOM_KEY_EXPIRES_AT = ctx.sessionKeyRenewal?.expiresAt;
|
|
1801
|
+
}
|
|
1691
1802
|
// MNE-8245 defense-in-depth: the proxy gets the session's (minted) Mnemom key on stdin only
|
|
1692
1803
|
// and reads none of the owner's own Mnemom credential vars, so they never ride into its env.
|
|
1693
1804
|
stripOwnerCredentialEnv(proxyEnv);
|
|
1694
1805
|
const proxy = spawn("node", [proxyScript], {
|
|
1695
1806
|
env: proxyEnv,
|
|
1696
|
-
stdio: ["pipe", "pipe", "inherit"],
|
|
1807
|
+
stdio: ["pipe", "pipe", "inherit", renewing ? "pipe" : "ignore"],
|
|
1697
1808
|
});
|
|
1809
|
+
const renewal = renewing
|
|
1810
|
+
? startRcSessionKeyRenewal(proxy, proxyLog, {
|
|
1811
|
+
keyId: ctx.sessionKeyId,
|
|
1812
|
+
expiresAt: ctx.sessionKeyRenewal?.expiresAt,
|
|
1813
|
+
mintArgs: ctx.sessionKeyRenewal?.mintArgs,
|
|
1814
|
+
})
|
|
1815
|
+
: undefined;
|
|
1698
1816
|
let stopped = false;
|
|
1699
1817
|
const stopProxyAndTempDir = () => {
|
|
1700
1818
|
if (!proxy.killed) {
|
|
@@ -1721,9 +1839,12 @@ function launchRemoteControl(ctx) {
|
|
|
1721
1839
|
if (stopped)
|
|
1722
1840
|
return;
|
|
1723
1841
|
stopped = true;
|
|
1842
|
+
renewal?.stop();
|
|
1724
1843
|
stopProxyAndTempDir();
|
|
1725
|
-
|
|
1726
|
-
|
|
1844
|
+
// After a renewal the proxy holds a newer key than the one minted at launch: revoke that one.
|
|
1845
|
+
const keyId = renewal?.currentKeyId() ?? ctx.sessionKeyId;
|
|
1846
|
+
if (keyId) {
|
|
1847
|
+
await revokeSessionGatewayKey(defaultSessionKeyDeps(say), keyId);
|
|
1727
1848
|
}
|
|
1728
1849
|
};
|
|
1729
1850
|
// Last-resort SYNC fallback only — Node's "exit" handlers cannot await, so this branch never
|
|
@@ -1736,6 +1857,7 @@ function launchRemoteControl(ctx) {
|
|
|
1736
1857
|
if (stopped)
|
|
1737
1858
|
return;
|
|
1738
1859
|
stopped = true;
|
|
1860
|
+
renewal?.stop();
|
|
1739
1861
|
stopProxyAndTempDir();
|
|
1740
1862
|
});
|
|
1741
1863
|
// Secrets on stdin — four lines (anthropic, mnemom, openai, proxy credential). An api-key launch
|
|
@@ -1755,8 +1877,9 @@ function launchRemoteControl(ctx) {
|
|
|
1755
1877
|
let stdoutBuf = "";
|
|
1756
1878
|
const start = Date.now();
|
|
1757
1879
|
const tryStart = () => {
|
|
1758
|
-
//
|
|
1759
|
-
// 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.
|
|
1760
1883
|
if (stopped)
|
|
1761
1884
|
return;
|
|
1762
1885
|
const m = stdoutBuf.match(/^READY (\d+) (.+)$/m);
|
|
@@ -1793,6 +1916,58 @@ function launchRemoteControl(ctx) {
|
|
|
1793
1916
|
});
|
|
1794
1917
|
tryStart();
|
|
1795
1918
|
}
|
|
1919
|
+
/** The longest line the proxy sends the launcher (`remint <key id>`) is far shorter than this. */
|
|
1920
|
+
export const RC_CONTROL_LINE_MAX = 1024;
|
|
1921
|
+
/**
|
|
1922
|
+
* Renew the Remote Control proxy's session key over its control pipe (fd 3; rc-proxy.mjs SESSION
|
|
1923
|
+
* KEY RENEWAL): a `key <id> <expires> <key>` line before each expiry, a `remint <id>` line from the
|
|
1924
|
+
* proxy when the gateway rejects its key, answered with a new key or `key-failed`. Renewal events
|
|
1925
|
+
* go to the proxy log (the terminal belongs to Claude Code by then), as key ids and expiry times.
|
|
1926
|
+
*/
|
|
1927
|
+
export function startRcSessionKeyRenewal(proxy, proxyLog, initial, makeDeps = defaultSessionKeyDeps) {
|
|
1928
|
+
const control = proxy.stdio?.[3];
|
|
1929
|
+
if (!control)
|
|
1930
|
+
return undefined;
|
|
1931
|
+
const logLine = (line) => {
|
|
1932
|
+
try {
|
|
1933
|
+
appendFileSync(proxyLog, `${new Date().toISOString()} launcher: ${line}\n`);
|
|
1934
|
+
}
|
|
1935
|
+
catch {
|
|
1936
|
+
/* best effort: the log dir may be gone at exit */
|
|
1937
|
+
}
|
|
1938
|
+
};
|
|
1939
|
+
control.on("error", () => {
|
|
1940
|
+
/* the proxy exited: the next delivery fails and is logged */
|
|
1941
|
+
});
|
|
1942
|
+
const renewal = startSessionKeyRenewal({
|
|
1943
|
+
deps: makeDeps(logLine),
|
|
1944
|
+
mintArgs: initial.mintArgs,
|
|
1945
|
+
initial: { keyId: initial.keyId, expiresAt: initial.expiresAt },
|
|
1946
|
+
deliver: (minted) => {
|
|
1947
|
+
if (control.destroyed || !control.writable)
|
|
1948
|
+
throw new Error("the proxy's control pipe is closed");
|
|
1949
|
+
control.write(`key ${minted.keyId} ${minted.expiresAt} ${minted.key}\n`);
|
|
1950
|
+
},
|
|
1951
|
+
refuse: (reason) => {
|
|
1952
|
+
if (!control.destroyed && control.writable)
|
|
1953
|
+
control.write(`key-failed ${reason}\n`);
|
|
1954
|
+
},
|
|
1955
|
+
});
|
|
1956
|
+
let buf = "";
|
|
1957
|
+
control.on("data", (chunk) => {
|
|
1958
|
+
buf += chunk.toString("utf8");
|
|
1959
|
+
let i;
|
|
1960
|
+
while ((i = buf.indexOf("\n")) >= 0) {
|
|
1961
|
+
const [cmd, keyId] = buf.slice(0, i).split(" ");
|
|
1962
|
+
buf = buf.slice(i + 1);
|
|
1963
|
+
if (cmd === "remint")
|
|
1964
|
+
void renewal.renew("rejected", keyId);
|
|
1965
|
+
}
|
|
1966
|
+
if (buf.length > RC_CONTROL_LINE_MAX)
|
|
1967
|
+
buf = ""; // a runaway line, never a real request
|
|
1968
|
+
});
|
|
1969
|
+
return renewal;
|
|
1970
|
+
}
|
|
1796
1971
|
function spawnRcCli(ctx) {
|
|
1797
1972
|
// Session name for the claude.ai session list: "<agent>-<scenario>", unless
|
|
1798
1973
|
// the caller passed their own name after `--` (`--name x`, `--name=x`, `-n x`; lifted out here).
|
|
@@ -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[]>;
|