@mnemom/mnemom 0.17.34-next.2 → 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/commands/agent-run.d.ts +14 -6
- package/dist/commands/agent-run.js +44 -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 +64 -36
- 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 +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 +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/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
|
@@ -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({
|
|
@@ -1536,6 +1561,7 @@ function launchTerminal(ctx) {
|
|
|
1536
1561
|
turnHeader,
|
|
1537
1562
|
receipt: Boolean(ctx.receipt),
|
|
1538
1563
|
commitReport: Boolean(ctx.commitReport),
|
|
1564
|
+
fedStatusline: feed !== null,
|
|
1539
1565
|
})) {
|
|
1540
1566
|
run();
|
|
1541
1567
|
return;
|
|
@@ -1568,7 +1594,7 @@ export function launchSettingsExtra(o) {
|
|
|
1568
1594
|
* commit reporting (W1.8) all need it; commit reporting reads the lineage key it records.
|
|
1569
1595
|
*/
|
|
1570
1596
|
export function proxyWanted(o) {
|
|
1571
|
-
return o.turnHeader || o.receipt || o.commitReport;
|
|
1597
|
+
return o.turnHeader || o.receipt || o.commitReport || Boolean(o.fedStatusline);
|
|
1572
1598
|
}
|
|
1573
1599
|
/**
|
|
1574
1600
|
* Whether the proxy captures the gateway's record state: the conduct receipt reads it at exit,
|
|
@@ -1595,7 +1621,7 @@ export function onBaseUrlProxyReady(baseUrl, recordStateFile, childEnv, feed, st
|
|
|
1595
1621
|
* segment off, no billing org, or {@link fedStatuslineWanted} false.
|
|
1596
1622
|
*/
|
|
1597
1623
|
export function prepareStatuslineFeed(o) {
|
|
1598
|
-
if (!o.statusline || !o.orgId || !fedStatuslineWanted(o.version
|
|
1624
|
+
if (!o.statusline || !o.orgId || !fedStatuslineWanted(o.version))
|
|
1599
1625
|
return null;
|
|
1600
1626
|
const feed = (o.files ?? fedStatuslineFiles)();
|
|
1601
1627
|
o.childEnv[FEED_ENV] = feed.feedFile;
|
|
@@ -1610,11 +1636,12 @@ export function recordOrgWanted(o) {
|
|
|
1610
1636
|
}
|
|
1611
1637
|
/**
|
|
1612
1638
|
* Whether this launch feeds the status line measured model cost and μ (W1.4): prerelease
|
|
1613
|
-
* builds only (stable stays "mnemom", decision 14)
|
|
1614
|
-
*
|
|
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.
|
|
1615
1642
|
*/
|
|
1616
|
-
export function fedStatuslineWanted(version
|
|
1617
|
-
return isPrerelease(version)
|
|
1643
|
+
export function fedStatuslineWanted(version) {
|
|
1644
|
+
return isPrerelease(version);
|
|
1618
1645
|
}
|
|
1619
1646
|
/** A private per-session dir holding the feed (launcher → bin). */
|
|
1620
1647
|
function fedStatuslineFiles() {
|
|
@@ -2077,6 +2104,7 @@ function spawnRcCli(ctx) {
|
|
|
2077
2104
|
if (ctx.options.slack)
|
|
2078
2105
|
args.push(...slackChannelArgs(rcName));
|
|
2079
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);
|
|
2080
2108
|
}
|
|
2081
2109
|
const child = spawn(ctx.cli.bin, args, { stdio: "inherit", env: childEnv });
|
|
2082
2110
|
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;
|
|
@@ -270,7 +270,7 @@ export async function compareWithCommitted(built, committed) {
|
|
|
270
270
|
export function launchSpecChangeFixLines(ctx) {
|
|
271
271
|
const render = {
|
|
272
272
|
launcherKey: ctx.launcherKey,
|
|
273
|
-
nameHeld: true,
|
|
273
|
+
nameHeld: ctx.nameHeld ?? true,
|
|
274
274
|
params: { name: ctx.name, flags: ctx.flags },
|
|
275
275
|
};
|
|
276
276
|
const lines = [renderFixLine("launch_spec_change", render)];
|
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
// is a loopback literal; an unset URL counts as non-loopback. Otherwise a seam env is ignored.
|
|
7
7
|
//
|
|
8
8
|
// The CLI seams (closed list): T1 DURABLE_BOOT_ID_FILE, T2 DURABLE_HOST_TEST_PAUSE_AT, T3 the
|
|
9
|
-
// credential proxy's http door on a loopback host
|
|
9
|
+
// credential proxy's http door on a loopback host, and `agent run`'s GitHub base, read from the
|
|
10
|
+
// api's own T4 variable DURABLE_TEST_GITHUB_API_BASE (Durable lead, Doors W-L13).
|
|
10
11
|
|
|
11
12
|
const TEST_MODE = "DURABLE_SESSIONS_TEST_MODE";
|
|
12
13
|
/** S1 GUARD_LISTS.cli, in order. */
|
|
@@ -40,6 +41,29 @@ export function bootIdFileOverride(env = process.env) {
|
|
|
40
41
|
return { file, ignored: false };
|
|
41
42
|
}
|
|
42
43
|
|
|
44
|
+
/**
|
|
45
|
+
* `agent run`'s GitHub base on a lane: the same fake GitHub the api uses (T4,
|
|
46
|
+
* DURABLE_TEST_GITHUB_API_BASE), serving git and REST from one origin. Honoured only under the seam
|
|
47
|
+
* guard and only for an http(s) loopback URL with no credentials, query or fragment; anything else
|
|
48
|
+
* is null, and `ignored` says a value was set but not used (the caller warns).
|
|
49
|
+
*/
|
|
50
|
+
export function testGithubBase(env = process.env) {
|
|
51
|
+
const raw = env.DURABLE_TEST_GITHUB_API_BASE;
|
|
52
|
+
if (typeof raw !== "string" || raw === "") return { base: null, ignored: false };
|
|
53
|
+
if (!testSeamsHonoured(env) || !isLoopbackUrl(raw)) return { base: null, ignored: true };
|
|
54
|
+
const u = new URL(raw);
|
|
55
|
+
if (
|
|
56
|
+
(u.protocol !== "http:" && u.protocol !== "https:") ||
|
|
57
|
+
u.username ||
|
|
58
|
+
u.password ||
|
|
59
|
+
u.search ||
|
|
60
|
+
u.hash
|
|
61
|
+
) {
|
|
62
|
+
return { base: null, ignored: true };
|
|
63
|
+
}
|
|
64
|
+
return { base: `${u.origin}${u.pathname.replace(/\/+$/, "")}`, ignored: false };
|
|
65
|
+
}
|
|
66
|
+
|
|
43
67
|
/** S1 T2_PAUSE_POINTS. */
|
|
44
68
|
export const PAUSE_POINTS = Object.freeze([
|
|
45
69
|
"after_claim_before_spawn",
|