@mnemom/mnemom 0.17.34-next.2 → 0.17.34-next.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/agent-run.d.ts +16 -6
- package/dist/commands/agent-run.js +52 -27
- 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 +7 -5
- package/dist/commands/agent.js +84 -45
- package/dist/commands/durable-lock.d.ts +8 -1
- package/dist/commands/durable-lock.js +9 -4
- 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/launch-spec.d.ts +2 -0
- package/dist/durable/launch-spec.js +1 -1
- package/dist/durable/test-mode.mjs +25 -1
- package/dist/host/client.d.ts +3 -1
- package/dist/host/daemon.d.ts +7 -3
- package/dist/host/daemon.js +13 -3
- package/dist/host/owner.d.ts +3 -0
- package/dist/host/owner.js +2 -0
- package/dist/host/run.d.ts +2 -3
- package/dist/host/run.js +5 -20
- package/dist/host/s1-names.d.ts +1 -0
- package/dist/host/s1-names.js +1 -0
- package/dist/host/verbs.d.ts +2 -0
- package/dist/host/verbs.js +11 -0
- package/dist/index.js +13 -9
- package/dist/lib/agent.d.ts +5 -3
- package/dist/lib/agent.js +6 -3
- package/dist/lib/claude-bin.d.ts +6 -0
- package/dist/lib/claude-bin.js +26 -0
- 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/launch.d.ts +28 -11
- package/dist/lib/durable/launch.js +33 -15
- package/dist/lib/durable/live.d.ts +15 -19
- package/dist/lib/durable/live.js +41 -43
- package/dist/lib/launcher-config.d.ts +6 -0
- package/dist/lib/launcher-config.js +11 -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 +11 -4
- package/dist/lib/runner/job.js +59 -16
- 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/outcome.js +2 -3
- 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 +25 -5
- package/dist/lib/scan/report-email.js +23 -2
- package/dist/lib/scan/scan.d.ts +3 -0
- package/dist/lib/scan/scan.js +13 -1
- package/dist/lib/session-key.d.ts +4 -3
- package/dist/vendor/durable-contract/index.js +207 -169
- package/dist/vendor/shared/durable-contract/doors.d.ts +26 -1
- package/dist/vendor/shared/durable-contract/doors.js +37 -1
- package/dist/vendor/shared/durable-contract/events.d.ts +1 -1
- package/dist/vendor/shared/durable-contract/events.js +13 -3
- package/dist/vendor/shared/durable-contract/freeze-record.d.ts +2 -2
- package/dist/vendor/shared/durable-contract/schema.d.ts +5 -0
- package/dist/vendor/shared/durable-contract/schema.js +6 -0
- package/dist/vendor/shared/durable-contract/states.d.ts +2 -1
- package/dist/vendor/shared/durable-contract/states.js +2 -1
- package/package.json +1 -1
- package/dist/lib/durable/first-turn.d.ts +0 -50
- package/dist/lib/durable/first-turn.js +0 -102
package/dist/commands/agent.js
CHANGED
|
@@ -48,7 +48,7 @@ import { isLoggedIn } from "../lib/auth.js";
|
|
|
48
48
|
import { loadCliConfig } from "../lib/cli-config.js";
|
|
49
49
|
import { configKeySpec, loadLauncherConfig, writeLauncherConfig } from "../lib/launcher-config.js";
|
|
50
50
|
import { experimentalNotice } from "../lib/experimental.js";
|
|
51
|
-
import { conductReceiptFor } from "../lib/conduct-receipt.js";
|
|
51
|
+
import { conductReceiptFor, RECEIPT_DEADLINE_MS, } from "../lib/conduct-receipt.js";
|
|
52
52
|
import { fmt } from "../lib/format.js";
|
|
53
53
|
import { askInput, askSelect, askYesNo, isInteractive } from "../lib/prompt.js";
|
|
54
54
|
import { authConfigured, chooseAuth, detectClaudeLogin, readClaudeLoginState, } from "../lib/login-detect.js";
|
|
@@ -59,8 +59,8 @@ import { LAUNCHER_STORE_PATH, mergeStore } from "../lib/launcher-store.js";
|
|
|
59
59
|
import { slackChannelArgs } from "../lib/slack-launch.js";
|
|
60
60
|
import { appendPluginDirsEnv, applyPluginEnv, pluginEnabled, pluginPlanLine, resolvePluginDir, } from "../lib/claude-plugin.js";
|
|
61
61
|
import { CLI_VERSION } from "../version.js";
|
|
62
|
-
import { attachToSeat,
|
|
63
|
-
import { liveDurableApi, liveHomeFacts, livePlanDeps, liveRegistryReads, liveSeatDeps,
|
|
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
64
|
import { mayBeHandover } from "../lib/durable/handover.js";
|
|
65
65
|
import { fetchTierIndex, profileMayMisfit, profileTierWarning, resolveTier, tierLaunchModel, } from "../lib/model-tier.js";
|
|
66
66
|
/** The CLI's own provider-key store (zero 1Password): ~/.mnemom/launcher.json, 0600. */
|
|
@@ -586,8 +586,6 @@ export async function agentCommand(scenarioArg, options = {}) {
|
|
|
586
586
|
const conversationId = continuation.conversationId;
|
|
587
587
|
// The registered row of a durable launch: its child's SIGKILL may be a handover (C02-R26 step 7).
|
|
588
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
589
|
if (durablePlan) {
|
|
592
590
|
options = {
|
|
593
591
|
...options,
|
|
@@ -595,25 +593,26 @@ export async function agentCommand(scenarioArg, options = {}) {
|
|
|
595
593
|
? [...durablePlan.passthrough, "--session-id", durablePlan.newCcSessionId]
|
|
596
594
|
: durablePlan.passthrough,
|
|
597
595
|
};
|
|
598
|
-
//
|
|
599
|
-
//
|
|
600
|
-
//
|
|
601
|
-
//
|
|
602
|
-
//
|
|
603
|
-
|
|
604
|
-
|
|
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;
|
|
605
608
|
return;
|
|
606
609
|
}
|
|
607
610
|
const row = await registerDurableSession(durablePlan, scenario, servedAgentId);
|
|
608
611
|
clearLaunching(scenario, liveLaunchingDeps(MNEMOM_DIR));
|
|
609
|
-
durableRow =
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
if (locked !== null) {
|
|
614
|
-
process.exitCode = locked;
|
|
615
|
-
return;
|
|
616
|
-
}
|
|
612
|
+
durableRow = launchRow(row, durablePlan);
|
|
613
|
+
if (durablePlan.lock && !atCreate) {
|
|
614
|
+
process.exitCode = await lockInsteadOfSpawn(row, scenario, durableKey, durablePlan.durableOnly);
|
|
615
|
+
return;
|
|
617
616
|
}
|
|
618
617
|
}
|
|
619
618
|
// A relaunch that continues an earlier conversation resumes that Claude Code session, so the
|
|
@@ -849,8 +848,8 @@ export async function agentCommand(scenarioArg, options = {}) {
|
|
|
849
848
|
// Record keys the lineage under the served agent's org, not the account org (lib/record-org).
|
|
850
849
|
const recordOrgId = recordOrgWanted({
|
|
851
850
|
receipt: experimental.includes("record"),
|
|
852
|
-
commitReport: cli.name === "claude" && commitReportEnabled(process.env[COMMIT_REPORT_ENV]
|
|
853
|
-
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),
|
|
854
853
|
}) && account.orgId
|
|
855
854
|
? await readServedOrg(account.orgId).then((o) => {
|
|
856
855
|
if (o.fallback)
|
|
@@ -893,7 +892,6 @@ export async function agentCommand(scenarioArg, options = {}) {
|
|
|
893
892
|
row: durableRow,
|
|
894
893
|
name: scenario,
|
|
895
894
|
launcherKey: options.durable.launcherKey,
|
|
896
|
-
lockAfterFirstTurn,
|
|
897
895
|
},
|
|
898
896
|
}
|
|
899
897
|
: {}),
|
|
@@ -903,7 +901,7 @@ export async function agentCommand(scenarioArg, options = {}) {
|
|
|
903
901
|
orgId: recordOrgId,
|
|
904
902
|
commitReport: cli.name === "claude" &&
|
|
905
903
|
Boolean(account.orgId) &&
|
|
906
|
-
commitReportEnabled(process.env[COMMIT_REPORT_ENV]
|
|
904
|
+
commitReportEnabled(process.env[COMMIT_REPORT_ENV]),
|
|
907
905
|
});
|
|
908
906
|
return; // launchTerminal exits the process
|
|
909
907
|
}
|
|
@@ -1012,6 +1010,18 @@ export function resumeHintLine(ctx) {
|
|
|
1012
1010
|
return (`Resume with: ${resumeCommand(ctx)}` +
|
|
1013
1011
|
" (keeps it governed; `claude --resume` runs outside Mnemom)");
|
|
1014
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
|
+
}
|
|
1015
1025
|
/** An interactive Claude Code session — not `-p` / `--print`, which has nothing to resume into. */
|
|
1016
1026
|
function wantsResumeHint(cli, args) {
|
|
1017
1027
|
return cli.name === "claude" && !args.includes("-p") && !args.includes("--print");
|
|
@@ -1343,20 +1353,39 @@ function commandExists(cmd) {
|
|
|
1343
1353
|
});
|
|
1344
1354
|
return res.status === 0;
|
|
1345
1355
|
}
|
|
1356
|
+
/** Probe answers per binary, keyed by path, size and mtime: a replaced binary is probed again. */
|
|
1357
|
+
const rcProbeMemo = new Map();
|
|
1346
1358
|
/**
|
|
1347
1359
|
* Does the resolved coding-agent CLI advertise the `--remote-control` startup
|
|
1348
1360
|
* flag (Claude Code >= 2.1.269)? Determined by scanning its `--help`. Best-effort:
|
|
1349
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).
|
|
1350
1364
|
*/
|
|
1351
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;
|
|
1352
1378
|
try {
|
|
1353
1379
|
const res = spawnSync(bin, ["--help"], { encoding: "utf8", timeout: 10_000 });
|
|
1354
1380
|
const text = `${res.stdout ?? ""}${res.stderr ?? ""}`;
|
|
1355
|
-
|
|
1381
|
+
ok = /--remote-control/.test(text);
|
|
1356
1382
|
}
|
|
1357
1383
|
catch {
|
|
1358
|
-
|
|
1384
|
+
ok = false;
|
|
1359
1385
|
}
|
|
1386
|
+
if (key !== null)
|
|
1387
|
+
rcProbeMemo.set(key, ok);
|
|
1388
|
+
return ok;
|
|
1360
1389
|
}
|
|
1361
1390
|
/**
|
|
1362
1391
|
* Can we launch the DEFAULT Remote-Control-in-terminal shape on this machine?
|
|
@@ -1446,8 +1475,6 @@ function launchTerminal(ctx) {
|
|
|
1446
1475
|
statusline: sl !== null,
|
|
1447
1476
|
orgId: ctx.orgId,
|
|
1448
1477
|
version: CLI_VERSION,
|
|
1449
|
-
goalMode: Boolean(ctx.goalMode),
|
|
1450
|
-
env: process.env,
|
|
1451
1478
|
childEnv,
|
|
1452
1479
|
});
|
|
1453
1480
|
// Autocompact guard (lib/autocompact-guard.ts): one --settings carries every layer. The
|
|
@@ -1473,10 +1500,8 @@ function launchTerminal(ctx) {
|
|
|
1473
1500
|
const run = () => {
|
|
1474
1501
|
const savedTty = ctx.durableHandover ? saveTtyMode() : null;
|
|
1475
1502
|
const launchedSec = Math.floor(Date.now() / 1000);
|
|
1503
|
+
warnNoTty(ctx);
|
|
1476
1504
|
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);
|
|
1480
1505
|
// W1.8: report the session's commits while it runs; no record state (no proxy) → no reports.
|
|
1481
1506
|
const commits = ctx.commitReport && ctx.orgId && recordStatePath
|
|
1482
1507
|
? startCommitReporter({
|
|
@@ -1493,28 +1518,39 @@ function launchTerminal(ctx) {
|
|
|
1493
1518
|
child.on("exit", (code, signal) => {
|
|
1494
1519
|
void (async () => {
|
|
1495
1520
|
// MNE-8245: best-effort revoke of the minted session-scoped key — a failure
|
|
1496
|
-
// here is non-fatal (the key is 24h-TTL'd regardless)
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
}
|
|
1521
|
+
// here is non-fatal (the key is 24h-TTL'd regardless). It starts now and is awaited only
|
|
1522
|
+
// before the process ends; with a receipt to print it shares the receipt's deadline, so
|
|
1523
|
+
// with the API down the two bounds overlap instead of adding up (W1.3).
|
|
1500
1524
|
// A lock handover SIGKILLs the frozen claude once the daemon's seat runs the conversation:
|
|
1501
1525
|
// attach to it instead of dying with the child (C02-R26 step 7). Never relaunches claude.
|
|
1502
1526
|
const h = ctx.durableHandover;
|
|
1503
|
-
|
|
1527
|
+
const handover = mayBeHandover(h !== undefined, signal);
|
|
1528
|
+
// Only an exit that goes straight to the receipt shortens the revoke; a signal or a
|
|
1529
|
+
// handover prints none, so it keeps the full bound.
|
|
1530
|
+
const sharesReceiptDeadline = Boolean(ctx.receipt) && !signal && !handover;
|
|
1531
|
+
const revoked = ctx.sessionKeyId
|
|
1532
|
+
? revokeSessionGatewayKey({
|
|
1533
|
+
...defaultSessionKeyDeps(say),
|
|
1534
|
+
...(sharesReceiptDeadline ? { revokeTimeoutMs: RECEIPT_DEADLINE_MS } : {}),
|
|
1535
|
+
}, ctx.sessionKeyId)
|
|
1536
|
+
: Promise.resolve();
|
|
1537
|
+
if (handover && h) {
|
|
1538
|
+
await revoked;
|
|
1504
1539
|
const attached = await liveParentAfterChildEnded(h.row, h.name, h.launcherKey, savedTty);
|
|
1505
1540
|
if (attached !== null)
|
|
1506
1541
|
process.exit(attached);
|
|
1507
1542
|
}
|
|
1508
1543
|
if (signal) {
|
|
1544
|
+
await revoked;
|
|
1509
1545
|
process.kill(process.pid, signal);
|
|
1510
1546
|
return;
|
|
1511
1547
|
}
|
|
1512
|
-
// The conduct receipt and the last commit sweep run side by side, each under
|
|
1513
|
-
// hard deadline inside W1.3's exit budget;
|
|
1548
|
+
// The revoke, the conduct receipt and the last commit sweep run side by side, each under
|
|
1549
|
+
// its own hard deadline inside W1.3's exit budget; none changes the exit code.
|
|
1514
1550
|
const swept = commits?.finish().catch(() => undefined);
|
|
1515
1551
|
if (ctx.receipt)
|
|
1516
1552
|
await sayConductReceipt({ ...ctx.receipt, statePath: recordStatePath });
|
|
1517
|
-
await swept;
|
|
1553
|
+
await Promise.all([swept, revoked]);
|
|
1518
1554
|
// Claude Code's own last line is "Resume this session with: claude --resume <id>",
|
|
1519
1555
|
// which reopens it OUTSIDE Mnemom (ungoverned, unbilled). Ours goes after it, last.
|
|
1520
1556
|
if (code === 0 && ctx.resumeHint && wantsResumeHint(ctx.cli, args)) {
|
|
@@ -1536,6 +1572,7 @@ function launchTerminal(ctx) {
|
|
|
1536
1572
|
turnHeader,
|
|
1537
1573
|
receipt: Boolean(ctx.receipt),
|
|
1538
1574
|
commitReport: Boolean(ctx.commitReport),
|
|
1575
|
+
fedStatusline: feed !== null,
|
|
1539
1576
|
})) {
|
|
1540
1577
|
run();
|
|
1541
1578
|
return;
|
|
@@ -1568,7 +1605,7 @@ export function launchSettingsExtra(o) {
|
|
|
1568
1605
|
* commit reporting (W1.8) all need it; commit reporting reads the lineage key it records.
|
|
1569
1606
|
*/
|
|
1570
1607
|
export function proxyWanted(o) {
|
|
1571
|
-
return o.turnHeader || o.receipt || o.commitReport;
|
|
1608
|
+
return o.turnHeader || o.receipt || o.commitReport || Boolean(o.fedStatusline);
|
|
1572
1609
|
}
|
|
1573
1610
|
/**
|
|
1574
1611
|
* Whether the proxy captures the gateway's record state: the conduct receipt reads it at exit,
|
|
@@ -1595,7 +1632,7 @@ export function onBaseUrlProxyReady(baseUrl, recordStateFile, childEnv, feed, st
|
|
|
1595
1632
|
* segment off, no billing org, or {@link fedStatuslineWanted} false.
|
|
1596
1633
|
*/
|
|
1597
1634
|
export function prepareStatuslineFeed(o) {
|
|
1598
|
-
if (!o.statusline || !o.orgId || !fedStatuslineWanted(o.version
|
|
1635
|
+
if (!o.statusline || !o.orgId || !fedStatuslineWanted(o.version))
|
|
1599
1636
|
return null;
|
|
1600
1637
|
const feed = (o.files ?? fedStatuslineFiles)();
|
|
1601
1638
|
o.childEnv[FEED_ENV] = feed.feedFile;
|
|
@@ -1610,11 +1647,12 @@ export function recordOrgWanted(o) {
|
|
|
1610
1647
|
}
|
|
1611
1648
|
/**
|
|
1612
1649
|
* Whether this launch feeds the status line measured model cost and μ (W1.4): prerelease
|
|
1613
|
-
* builds only (stable stays "mnemom", decision 14)
|
|
1614
|
-
*
|
|
1650
|
+
* builds only (stable stays "mnemom", decision 14). Not tied to goal mode: the base-URL proxy
|
|
1651
|
+
* runs for a fed launch (proxyWanted) and records the lineage key whatever the goal mode, and
|
|
1652
|
+
* since A-W-016 a -next.N launch has no goal by default.
|
|
1615
1653
|
*/
|
|
1616
|
-
export function fedStatuslineWanted(version
|
|
1617
|
-
return isPrerelease(version)
|
|
1654
|
+
export function fedStatuslineWanted(version) {
|
|
1655
|
+
return isPrerelease(version);
|
|
1618
1656
|
}
|
|
1619
1657
|
/** A private per-session dir holding the feed (launcher → bin). */
|
|
1620
1658
|
function fedStatuslineFiles() {
|
|
@@ -2077,6 +2115,7 @@ function spawnRcCli(ctx) {
|
|
|
2077
2115
|
if (ctx.options.slack)
|
|
2078
2116
|
args.push(...slackChannelArgs(rcName));
|
|
2079
2117
|
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.`);
|
|
2118
|
+
warnNoTty(ctx);
|
|
2080
2119
|
}
|
|
2081
2120
|
const child = spawn(ctx.cli.bin, args, { stdio: "inherit", env: childEnv });
|
|
2082
2121
|
child.on("error", (err) => {
|
|
@@ -127,7 +127,8 @@ export interface LockFacts {
|
|
|
127
127
|
agent: string;
|
|
128
128
|
/** --own-agent: the lock moved the session onto a new agent, named by `agent` (C02-R10). */
|
|
129
129
|
ownAgent: boolean;
|
|
130
|
-
|
|
130
|
+
/** The locked session's agent id (the new agent's under --own-agent): R17's durable-only fragment. */
|
|
131
|
+
agentId: string;
|
|
131
132
|
sharedWith: number;
|
|
132
133
|
droppedFlags: readonly string[];
|
|
133
134
|
}
|
|
@@ -165,6 +166,12 @@ export interface LockDeps extends DurableVerbDeps {
|
|
|
165
166
|
handover?: HandoverDeps;
|
|
166
167
|
/** Claude Code's folder trust for the root (cwd_untrusted); absent: not checked. */
|
|
167
168
|
trust?: TrustProbe;
|
|
169
|
+
/**
|
|
170
|
+
* Does a live row hold the name (the R27 fix lines' `mnemom agent done <name>` clause)? Absent:
|
|
171
|
+
* the row being locked is a registered one, so it holds the name unless it has ended. The launch's
|
|
172
|
+
* create-and-lock passes false: its row is not registered until the create lands.
|
|
173
|
+
*/
|
|
174
|
+
nameHeld?: boolean;
|
|
168
175
|
/** The live original's exact argv (KERN_PROCARGS2, C14-R13), or null when unreadable. */
|
|
169
176
|
readOriginal?: (pid: number) => OriginalLaunch | null;
|
|
170
177
|
/**
|
|
@@ -223,8 +223,9 @@ export function lockLine(f) {
|
|
|
223
223
|
];
|
|
224
224
|
if (f.ownAgent)
|
|
225
225
|
parts.push(`moved to its own agent ${f.agent}`);
|
|
226
|
-
|
|
227
|
-
|
|
226
|
+
// R17 lists this fragment unconditionally: a lock that lands leaves its agent with a live locked
|
|
227
|
+
// session, which is what makes it durable-only (agent_durable_only), so its plain keys stop.
|
|
228
|
+
parts.push(`agent ${f.agentId} durable-only: its plain keys stop`);
|
|
228
229
|
if (f.sharedWith >= 1)
|
|
229
230
|
parts.push(`shares it with ${f.sharedWith} sessions (pausing pauses all)`);
|
|
230
231
|
parts.push(`mnemom agent show ${f.name}`);
|
|
@@ -264,7 +265,11 @@ export function lockFactsOf(body, row, name, built = []) {
|
|
|
264
265
|
row.agent_name ??
|
|
265
266
|
row.agent_id,
|
|
266
267
|
ownAgent: typeof moved === "string",
|
|
267
|
-
|
|
268
|
+
agentId: (typeof moved === "string" && typeof rec(b.agent).id === "string"
|
|
269
|
+
? rec(b.agent).id
|
|
270
|
+
: null) ??
|
|
271
|
+
(typeof b.agent_id === "string" ? b.agent_id : null) ??
|
|
272
|
+
row.agent_id,
|
|
268
273
|
sharedWith: Array.isArray(shared) ? shared.length : (num(shared) ?? 0),
|
|
269
274
|
// The builder's names first (C14-R12), then any the answer adds, each once.
|
|
270
275
|
droppedFlags: [
|
|
@@ -407,7 +412,7 @@ export async function lockResolvedRow(row, name, opts, deps) {
|
|
|
407
412
|
};
|
|
408
413
|
}
|
|
409
414
|
const launcherKey = deps.launcherKey();
|
|
410
|
-
const held = !isEnded(row);
|
|
415
|
+
const held = deps.nameHeld ?? !isEnded(row);
|
|
411
416
|
const ruled = cwdRule(row.cwd ?? deps.cwd(), deps.probe);
|
|
412
417
|
const cwd = ruled.ok ? transcriptSlugRule(ruled.root, cc, deps.transcriptDirs) : ruled;
|
|
413
418
|
if (!cwd.ok && cwd.kind === "transcript_unverifiable") {
|
package/dist/commands/scan.d.ts
CHANGED
|
@@ -5,9 +5,11 @@
|
|
|
5
5
|
* arm is unit-testable without Commander (same pattern as commands/usage.ts).
|
|
6
6
|
*/
|
|
7
7
|
import { type GitHubCredential } from "../lib/scan/credential.js";
|
|
8
|
-
import { type ScanDeps } from "../lib/scan/scan.js";
|
|
8
|
+
import { type ScanDeps, type ScanResult } from "../lib/scan/scan.js";
|
|
9
|
+
import { selectSpend } from "../lib/scan/spend/spend-tiers.js";
|
|
9
10
|
import { type AnthropicUsageDeps } from "../lib/scan/spend/anthropic-usage.js";
|
|
10
11
|
import { type RecordDeps, type RecordOutcome } from "../lib/scan/record.js";
|
|
12
|
+
import { type CostSpend } from "../lib/scan/cost-per-pr.js";
|
|
11
13
|
import { type ReportEmailDeps } from "../lib/scan/report-email.js";
|
|
12
14
|
import { type ReportRecipients } from "../lib/scan/share.js";
|
|
13
15
|
export interface ScanCommandOptions {
|
|
@@ -64,6 +66,15 @@ export declare function resolveRecipients(o: {
|
|
|
64
66
|
}): Promise<ReportRecipients>;
|
|
65
67
|
/** The CTO's figure in USD, or null for none. Throws fixed text only; the input is never echoed. */
|
|
66
68
|
export declare function resolveSelfReportedSpend(o: ScanCommandOptions): Promise<number | null>;
|
|
69
|
+
/**
|
|
70
|
+
* The one spend the cost-per-PR figures split, matching the tier the scan chose. Null leaves them out.
|
|
71
|
+
* Tier 1 sends the measured Anthropic total; tier 2 NEVER does (a figure split from the stated monthly
|
|
72
|
+
* spend would let that number be recovered, and the tier 2 line promises it is "not sent"); tier 3 (or
|
|
73
|
+
* tiers off) the modelled range. Sources are never mixed.
|
|
74
|
+
*/
|
|
75
|
+
export declare function resolveCostSpend(o: ScanCommandOptions, result: ScanResult, spend: ReturnType<typeof selectSpend> | null, measured: {
|
|
76
|
+
usd: number;
|
|
77
|
+
} | null): CostSpend | null;
|
|
67
78
|
export declare const NO_ADMIN_KEY_TERMINAL_MSG = "Set ANTHROPIC_ADMIN_KEY (there is no terminal to ask on).";
|
|
68
79
|
export declare const TIER1_FALLBACK_NOTE = "Tier 1 skipped: falling back to the next tier.";
|
|
69
80
|
/**
|
package/dist/commands/scan.js
CHANGED
|
@@ -13,6 +13,7 @@ import { ADMIN_KEY_ENV, AnthropicUsageError, fetchMeasuredUsage, isAdminKeyShape
|
|
|
13
13
|
import { getAuthInfo } from "../lib/auth.js";
|
|
14
14
|
import { listMyOrgs } from "../lib/api.js";
|
|
15
15
|
import { fetchRecordTotals, isOrgId, } from "../lib/scan/record.js";
|
|
16
|
+
import { modelledSpend } from "../lib/scan/cost-per-pr.js";
|
|
16
17
|
import { RECORD_NO_ORG_NOTE, renderRecordSection } from "../lib/scan/report.js";
|
|
17
18
|
import { buildEmailBody, buildEmailReport, DRY_RUN_MSG, UNAVAILABLE_MSG, failureMessage, ReportEmailError, sendReportEmail, } from "../lib/scan/report-email.js";
|
|
18
19
|
import { buildSharePayload, parseShareLevel, promptShareLevel, } from "../lib/scan/share.js";
|
|
@@ -67,6 +68,23 @@ export async function resolveSelfReportedSpend(o) {
|
|
|
67
68
|
throw new Error(SPEND_INVALID_MSG);
|
|
68
69
|
return n;
|
|
69
70
|
}
|
|
71
|
+
/**
|
|
72
|
+
* The one spend the cost-per-PR figures split, matching the tier the scan chose. Null leaves them out.
|
|
73
|
+
* Tier 1 sends the measured Anthropic total; tier 2 NEVER does (a figure split from the stated monthly
|
|
74
|
+
* spend would let that number be recovered, and the tier 2 line promises it is "not sent"); tier 3 (or
|
|
75
|
+
* tiers off) the modelled range. Sources are never mixed.
|
|
76
|
+
*/
|
|
77
|
+
export function resolveCostSpend(o, result, spend, measured) {
|
|
78
|
+
if (spend === null)
|
|
79
|
+
return modelledSpend(result.spend);
|
|
80
|
+
if (spend.tier === 1) {
|
|
81
|
+
const usd = measured?.usd ?? o.anthropicUsageUsd;
|
|
82
|
+
return typeof usd === "number" ? { source: "anthropic_usage", usd } : null;
|
|
83
|
+
}
|
|
84
|
+
if (spend.tier === 2)
|
|
85
|
+
return null;
|
|
86
|
+
return spend.tier === 3 ? modelledSpend(result.spend) : null;
|
|
87
|
+
}
|
|
70
88
|
export const NO_ADMIN_KEY_TERMINAL_MSG = "Set ANTHROPIC_ADMIN_KEY (there is no terminal to ask on).";
|
|
71
89
|
export const TIER1_FALLBACK_NOTE = "Tier 1 skipped: falling back to the next tier.";
|
|
72
90
|
/**
|
|
@@ -194,7 +212,7 @@ export async function scanCommand(o) {
|
|
|
194
212
|
const level = parseShareLevel(o.share) ?? (await promptShareLevel());
|
|
195
213
|
// Every successful scan emails the report to the person who ran it, at every share level, no opt-out.
|
|
196
214
|
// Mnemom keeps nothing from a share=none send (the server sends the email and does nothing else).
|
|
197
|
-
const emailFailure = await emailReport(o, emailOrg, result, level, period, spend);
|
|
215
|
+
const emailFailure = await emailReport(o, emailOrg, result, level, spend, period, resolveCostSpend(o, result, spend, measured));
|
|
198
216
|
if (level === "none") {
|
|
199
217
|
console.log("Share level: nothing. No share payload is built; Mnemom keeps nothing (the report email is only sent to you).");
|
|
200
218
|
if (emailFailure)
|
|
@@ -211,16 +229,17 @@ export async function scanCommand(o) {
|
|
|
211
229
|
* Emails the report. Loud on every failure: prints the reason name, never the server's words, and
|
|
212
230
|
* returns it so the caller exits non-zero after the local report is shown.
|
|
213
231
|
*/
|
|
214
|
-
async function emailReport(o, org, result, level, period,
|
|
232
|
+
async function emailReport(o, org, result, level, spend, period, costSpend) {
|
|
215
233
|
const fail = (f) => {
|
|
216
234
|
console.log(failureMessage(f));
|
|
217
235
|
return f;
|
|
218
236
|
};
|
|
219
237
|
const report = buildEmailReport(result, {
|
|
220
238
|
orgId: org,
|
|
221
|
-
period,
|
|
222
239
|
now: o.now ?? Date.now,
|
|
223
240
|
sendSpendRange: spend ? spend.shareable && spend.tier === 3 : true,
|
|
241
|
+
period,
|
|
242
|
+
costSpend,
|
|
224
243
|
});
|
|
225
244
|
const body = buildEmailBody(report, level, result.coverage.complete, o.ctoEmail?.trim());
|
|
226
245
|
const outcome = await sendReportEmail(org, body, o.emailDeps);
|
package/dist/commands/wrap.js
CHANGED
|
@@ -11,7 +11,7 @@ import { executorStateOk, executorStatePath, LAUNCHER_KEYS, } from "../vendor/du
|
|
|
11
11
|
import { getApiUrl, MNEMOM_DIR } from "../lib/config.js";
|
|
12
12
|
import { resolveAuth } from "../lib/auth.js";
|
|
13
13
|
import { readSessionRecords } from "../lib/agent-ledger.js";
|
|
14
|
-
import { LAUNCHER_CONFIG_PATH } from "../lib/launcher-config.js";
|
|
14
|
+
import { durableLauncherKey, LAUNCHER_CONFIG_PATH } from "../lib/launcher-config.js";
|
|
15
15
|
import { mnemomFetch } from "../lib/version-gate.js";
|
|
16
16
|
import { httpAdoptApi } from "./adopt-api.js";
|
|
17
17
|
import { liveCwdDeps } from "./cwd-rule.js";
|
|
@@ -37,21 +37,18 @@ export function ledgerFactsFrom(records, cid) {
|
|
|
37
37
|
}
|
|
38
38
|
/** The launcher key `durable` (MNEMOM_AGENT_DURABLE, then launcher.toml), default off. */
|
|
39
39
|
export function launcherKeyFrom(env, tomlText) {
|
|
40
|
-
|
|
41
|
-
const e = env.MNEMOM_AGENT_DURABLE?.trim().toLowerCase();
|
|
42
|
-
if (ok(e))
|
|
43
|
-
return e;
|
|
40
|
+
let configured;
|
|
44
41
|
if (tomlText !== null) {
|
|
45
42
|
try {
|
|
46
43
|
const v = parseToml(tomlText).durable;
|
|
47
|
-
if (
|
|
48
|
-
|
|
44
|
+
if (typeof v === "string" && LAUNCHER_KEYS.includes(v))
|
|
45
|
+
configured = v;
|
|
49
46
|
}
|
|
50
47
|
catch {
|
|
51
48
|
// an unparseable launcher.toml is the launcher's error to report, not adopt's
|
|
52
49
|
}
|
|
53
50
|
}
|
|
54
|
-
return
|
|
51
|
+
return durableLauncherKey(env, configured);
|
|
55
52
|
}
|
|
56
53
|
/** The executor state file, or null when absent or not the frozen S1 shape. */
|
|
57
54
|
export function executorStateFrom(text) {
|
package/dist/durable/adopt.d.ts
CHANGED
|
@@ -172,12 +172,20 @@ export interface AdoptPlan {
|
|
|
172
172
|
export declare function ruleCwd(live: LiveClaude, realpath: (p: string) => string | null): string | null;
|
|
173
173
|
/** The session row of a GET answer: the body itself, or its `session`; agent_id also from `agent.id`. */
|
|
174
174
|
export declare function rowOf(a: ApiAnswer): SessionRow | null;
|
|
175
|
+
/**
|
|
176
|
+
* A session's fix lines name it by its registry row's own name (the row discovery matched by
|
|
177
|
+
* conversation id); only then does the name hold a row, so only then may a line carry the done step.
|
|
178
|
+
* The environment's name is the launcher's display name (agent-scenario), which no verb resolves.
|
|
179
|
+
*/
|
|
180
|
+
export declare function nameHeldOf(s: Pick<PlannedSession, "name" | "_row">): boolean;
|
|
175
181
|
/** What a refusal's fix line may be filled from. */
|
|
176
182
|
export interface RefusalContext {
|
|
177
183
|
name: string;
|
|
178
184
|
launcherKey: LauncherKey;
|
|
179
185
|
/** The group's agent (for agent_contained when the refusal names none). */
|
|
180
186
|
agent?: string;
|
|
187
|
+
/** Whether the name holds a registry row (nameHeldOf): only then does the line carry the done step. */
|
|
188
|
+
nameHeld: boolean;
|
|
181
189
|
}
|
|
182
190
|
/**
|
|
183
191
|
* The S1 fix line for a lock refusal answered by the check or the lock route, or null when the
|
package/dist/durable/adopt.js
CHANGED
|
@@ -76,6 +76,14 @@ export function rowOf(a) {
|
|
|
76
76
|
return { ...inner, agent_id: inner.agent.id };
|
|
77
77
|
return inner;
|
|
78
78
|
}
|
|
79
|
+
/**
|
|
80
|
+
* A session's fix lines name it by its registry row's own name (the row discovery matched by
|
|
81
|
+
* conversation id); only then does the name hold a row, so only then may a line carry the done step.
|
|
82
|
+
* The environment's name is the launcher's display name (agent-scenario), which no verb resolves.
|
|
83
|
+
*/
|
|
84
|
+
export function nameHeldOf(s) {
|
|
85
|
+
return typeof s._row?.name === "string" && s._row.name !== "" && s._row.name === s.name;
|
|
86
|
+
}
|
|
79
87
|
async function planSession(live, deps, launcherKey) {
|
|
80
88
|
const facts = deps.ledger(live.conversationId);
|
|
81
89
|
const base = {
|
|
@@ -117,6 +125,8 @@ async function planSession(live, deps, launcherKey) {
|
|
|
117
125
|
if (!row)
|
|
118
126
|
return unadoptable("row_unreadable");
|
|
119
127
|
Object.assign(base, { _row: row });
|
|
128
|
+
if (typeof row.name === "string" && row.name !== "")
|
|
129
|
+
base.name = row.name;
|
|
120
130
|
const agentId = row.agent_id ?? facts.agentId;
|
|
121
131
|
if (row.durable_mode === "locked") {
|
|
122
132
|
await compareLocked(base, live, row, deps, launcherKey);
|
|
@@ -137,7 +147,7 @@ async function planSession(live, deps, launcherKey) {
|
|
|
137
147
|
base.cwd_kind = verdict.cwd_kind;
|
|
138
148
|
base.reason = `cwd_${verdict.cwd_kind}`;
|
|
139
149
|
// A tracked row holds the name, so the fix carries the done clause (S1 fix-line rule).
|
|
140
|
-
base.fix = cwdFixLine(verdict, { name:
|
|
150
|
+
base.fix = cwdFixLine(verdict, { name: base.name, launcherKey, nameHeld: nameHeldOf(base) });
|
|
141
151
|
base.note = cwdNote(verdict.cwd_kind, base.fix);
|
|
142
152
|
return { s: base, agentId, agentLabel: facts.agentLabel };
|
|
143
153
|
}
|
|
@@ -199,7 +209,8 @@ async function compareLocked(base, live, row, deps, launcherKey) {
|
|
|
199
209
|
// read (gaps/c4-relock-sole-unlock.md): it is never offered from here.
|
|
200
210
|
// launchSpecChangeFixLines' tuple type guarantees the launch_spec_change line is first.
|
|
201
211
|
base.fix = launchSpecChangeFixLines({
|
|
202
|
-
name:
|
|
212
|
+
name: base.name,
|
|
213
|
+
nameHeld: nameHeldOf(base),
|
|
203
214
|
flags: specUserArgv(built.spec).join(" "),
|
|
204
215
|
launcherKey,
|
|
205
216
|
soleLockedSession: false,
|
|
@@ -234,7 +245,7 @@ export function refusalFix(code, details, ctx) {
|
|
|
234
245
|
params.repo = repos.join(", ");
|
|
235
246
|
if (placeholdersOf(key).some((p) => params[p] === undefined))
|
|
236
247
|
return null;
|
|
237
|
-
return renderFixLine(key, { launcherKey: ctx.launcherKey, nameHeld:
|
|
248
|
+
return renderFixLine(key, { launcherKey: ctx.launcherKey, nameHeld: ctx.nameHeld, params });
|
|
238
249
|
}
|
|
239
250
|
const LOCK_REFUSAL_FIX_CODES = Object.freeze({
|
|
240
251
|
durable_lock_not_entitled: true,
|
|
@@ -299,7 +310,7 @@ export async function planAdopt(deps, opts = {}) {
|
|
|
299
310
|
const checkout = root ?? ownCheckout ?? null;
|
|
300
311
|
if (checkout === null)
|
|
301
312
|
continue;
|
|
302
|
-
s.fix = cwdFixLine({ ok: false, cwd_kind: "not_git", params: { checkout } }, { name: s.name, launcherKey, nameHeld:
|
|
313
|
+
s.fix = cwdFixLine({ ok: false, cwd_kind: "not_git", params: { checkout } }, { name: s.name, launcherKey, nameHeld: nameHeldOf(s) });
|
|
303
314
|
s.note = cwdNote("not_git", s.fix);
|
|
304
315
|
}
|
|
305
316
|
const adoptable = g.sessions.filter((s) => s.verdict === "adopt");
|
|
@@ -343,7 +354,12 @@ export async function planAdopt(deps, opts = {}) {
|
|
|
343
354
|
else if (a.kind === "error") {
|
|
344
355
|
g.code = a.code ?? `http_${a.status}`;
|
|
345
356
|
g.reason = a.message;
|
|
346
|
-
g.fix = refusalFix(a.code, a.details, {
|
|
357
|
+
g.fix = refusalFix(a.code, a.details, {
|
|
358
|
+
name: s.name,
|
|
359
|
+
launcherKey,
|
|
360
|
+
agent: g.agent,
|
|
361
|
+
nameHeld: nameHeldOf(s),
|
|
362
|
+
});
|
|
347
363
|
}
|
|
348
364
|
else {
|
|
349
365
|
g.code = "durable_precondition_unavailable";
|
|
@@ -513,7 +529,12 @@ export async function handover(s, g, exec, deps, facts, launcherKey) {
|
|
|
513
529
|
name: s.name,
|
|
514
530
|
committed: false,
|
|
515
531
|
code: a.code ?? `http_${a.status}`,
|
|
516
|
-
fix: refusalFix(a.code, a.details, {
|
|
532
|
+
fix: refusalFix(a.code, a.details, {
|
|
533
|
+
name: s.name,
|
|
534
|
+
launcherKey,
|
|
535
|
+
agent: g.agent,
|
|
536
|
+
nameHeld: nameHeldOf(s),
|
|
537
|
+
}),
|
|
517
538
|
};
|
|
518
539
|
}
|
|
519
540
|
// No attempt was decisive and a request may still be in flight: only a re-read that shows the
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
*
|
|
18
18
|
* The decision is pure over injected facts; liveCwdDeps is the thin driver.
|
|
19
19
|
*/
|
|
20
|
-
import { type CwdKind, type LauncherKey } from "../vendor/durable-contract/index.js";
|
|
20
|
+
import { type CwdCheckCause, type CwdKind, type LauncherKey } from "../vendor/durable-contract/index.js";
|
|
21
21
|
export interface CwdDeps {
|
|
22
22
|
/** realpath(p), or null when it does not resolve. */
|
|
23
23
|
realpath(p: string): string | null;
|
|
@@ -48,6 +48,14 @@ export declare function projectSlug(path: string): string;
|
|
|
48
48
|
* not --resume it).
|
|
49
49
|
*/
|
|
50
50
|
export declare function classifyCwd(cwd: string, deps: CwdDeps, ccSessionId?: string | null): CwdVerdict;
|
|
51
|
+
/**
|
|
52
|
+
* The daemon's spawn-time cwd check (attestation check `cwd`, C14-R04; mnemom-test #1002): the
|
|
53
|
+
* committed cwd's realpath must equal `git rev-parse --show-toplevel` there. Null when it holds;
|
|
54
|
+
* otherwise the S1 CWD_CHECK_CAUSES value: `missing` when there is no committed cwd or it does not
|
|
55
|
+
* resolve, `not_toplevel` when it resolves but is not a work tree's top level (a subdirectory, or
|
|
56
|
+
* not in a git work tree at all).
|
|
57
|
+
*/
|
|
58
|
+
export declare function spawnCwdCause(cwd: string | null, deps: Pick<CwdDeps, "realpath" | "gitToplevel">): CwdCheckCause | null;
|
|
51
59
|
/**
|
|
52
60
|
* The S1 fix line for a cwd refusal (fix-line rule: the done clause when a non-terminal row holds
|
|
53
61
|
* the name, the config suffix when the launcher key is not `always`), or null when a path the
|
package/dist/durable/cwd-rule.js
CHANGED
|
@@ -51,6 +51,24 @@ export function classifyCwd(cwd, deps, ccSessionId = null) {
|
|
|
51
51
|
return { ok: false, cwd_kind: "symlink_transcript", params: { realpath: real } };
|
|
52
52
|
return { ok: false, cwd_kind: null, unadoptable: "no_transcript" };
|
|
53
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* The daemon's spawn-time cwd check (attestation check `cwd`, C14-R04; mnemom-test #1002): the
|
|
56
|
+
* committed cwd's realpath must equal `git rev-parse --show-toplevel` there. Null when it holds;
|
|
57
|
+
* otherwise the S1 CWD_CHECK_CAUSES value: `missing` when there is no committed cwd or it does not
|
|
58
|
+
* resolve, `not_toplevel` when it resolves but is not a work tree's top level (a subdirectory, or
|
|
59
|
+
* not in a git work tree at all).
|
|
60
|
+
*/
|
|
61
|
+
export function spawnCwdCause(cwd, deps) {
|
|
62
|
+
if (cwd === null)
|
|
63
|
+
return "missing";
|
|
64
|
+
const real = deps.realpath(cwd);
|
|
65
|
+
if (real === null)
|
|
66
|
+
return "missing";
|
|
67
|
+
const top = deps.gitToplevel(real);
|
|
68
|
+
if (top === null)
|
|
69
|
+
return "not_toplevel";
|
|
70
|
+
return (deps.realpath(top) ?? top) === real ? null : "not_toplevel";
|
|
71
|
+
}
|
|
54
72
|
const CWD_FIX = {
|
|
55
73
|
subdirectory: "cwd_subdirectory",
|
|
56
74
|
not_git: "cwd_not_git",
|
|
@@ -138,6 +138,8 @@ export declare function launchSpecChangeFixLines(ctx: {
|
|
|
138
138
|
flags: string;
|
|
139
139
|
launcherKey: LauncherKey;
|
|
140
140
|
soleLockedSession: boolean;
|
|
141
|
+
/** Whether the name holds a registry row; absent: it does (every launch path). */
|
|
142
|
+
nameHeld?: boolean;
|
|
141
143
|
}): [string, ...string[]];
|
|
142
144
|
/** The adopt Note for an adopted row (C14-R12, R21): S1 grammar, unchanged builder names. */
|
|
143
145
|
export declare function adoptNote(dropped: readonly string[]): string;
|