@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
|
@@ -332,17 +332,30 @@ export async function planDurableLaunch(i, deps) {
|
|
|
332
332
|
}
|
|
333
333
|
}
|
|
334
334
|
}
|
|
335
|
+
// ---- a tracked spawn with no terminal on stdin (durable gate on) -------------------------------
|
|
336
|
+
/**
|
|
337
|
+
* The one stderr line a gate-on launch prints before its tracked Claude Code spawn when stdin is
|
|
338
|
+
* not a TTY and the passthrough has no -p/--print: Claude Code started that way waits forever and
|
|
339
|
+
* draws nothing. The spawn itself stays as today (C02-R23 a, b, f fallback); gate off prints
|
|
340
|
+
* nothing (C17-R11). No command line in it, so no placeholder (C11-R27). Null: print nothing.
|
|
341
|
+
*/
|
|
342
|
+
export function trackedNoTtyLine(i) {
|
|
343
|
+
if (!i.gateOn || i.stdinIsTTY || i.cliName !== "claude")
|
|
344
|
+
return null;
|
|
345
|
+
if (i.passthrough.some((a) => a === "-p" || a === "--print" || a.startsWith("--print=")))
|
|
346
|
+
return null;
|
|
347
|
+
return `${i.name}: Claude Code needs a terminal on stdin and will wait without one; run this from a terminal, or pass Claude's -p flag after --.`;
|
|
348
|
+
}
|
|
335
349
|
// ---- lock refused on the launcher path (C02-R23 c, e; C02-R24) ---------------------------------
|
|
336
350
|
/**
|
|
337
|
-
* What a launcher does after its lock attempt refused:
|
|
338
|
-
*
|
|
339
|
-
*
|
|
351
|
+
* What a launcher does after its lock attempt refused: it exits non-zero with the refusal's own fix
|
|
352
|
+
* line and never falls back to an attended (tracked) spawn, for every agent (Durable lead ruling
|
|
353
|
+
* 2026-10-30 on C02-R23 c, e). A durable-only agent gets the durable_only_agent refusal (C02-R24,
|
|
354
|
+
* final F02); any other agent prints the lock refusal itself.
|
|
340
355
|
*/
|
|
341
356
|
export function afterLockRefusal(refusal, durableOnly, launcherKey, name) {
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
// cwd_untrusted is CLI-local (no S1 precondition clause): a durable-only launch prints it as itself,
|
|
345
|
-
// its fix shaped for the launch (rerun it), not for `mnemom agent lock`.
|
|
357
|
+
// cwd_untrusted is CLI-local (no S1 precondition clause): a launch prints it as itself, its fix
|
|
358
|
+
// shaped for the launch (rerun it), not for `mnemom agent lock`.
|
|
346
359
|
const root = refusal.details.cwd;
|
|
347
360
|
if (refusal.code === "cwd_untrusted" && typeof root === "string") {
|
|
348
361
|
const r = new DurableRefusal({
|
|
@@ -353,21 +366,26 @@ export function afterLockRefusal(refusal, durableOnly, launcherKey, name) {
|
|
|
353
366
|
});
|
|
354
367
|
return { print: r, exit: r.exit };
|
|
355
368
|
}
|
|
369
|
+
if (!durableOnly)
|
|
370
|
+
return { print: refusal, exit: refusal.exit };
|
|
356
371
|
const r = durableOnlyRefusal(launcherKey, name, refusal, refusal.fixParams);
|
|
357
372
|
return { print: r, exit: r.exit };
|
|
358
373
|
}
|
|
359
|
-
// ----
|
|
374
|
+
// ---- a new session locks before claude starts (ruling A) --------------------------------------
|
|
360
375
|
/**
|
|
361
|
-
* A lock on a NEW Claude Code session (no transcript yet)
|
|
362
|
-
*
|
|
363
|
-
*
|
|
364
|
-
*
|
|
376
|
+
* A lock on a NEW Claude Code session (no transcript yet) is a create-and-lock (ruling A, which
|
|
377
|
+
* widens B5a to every agent): the session is born locked and its seat starts the transcript, so no
|
|
378
|
+
* attended claude ever runs first. A continued session already has its transcript and locks its
|
|
379
|
+
* registered row instead of spawning, as before.
|
|
365
380
|
*/
|
|
366
|
-
export function
|
|
381
|
+
export function locksAtCreate(plan) {
|
|
367
382
|
return plan.lock && plan.newCcSessionId !== null;
|
|
368
383
|
}
|
|
369
|
-
/**
|
|
370
|
-
|
|
384
|
+
/**
|
|
385
|
+
* The registered row of a tracked launch, naming the CC session this launch pins: what a later lock
|
|
386
|
+
* from another terminal hands over (C02-R26).
|
|
387
|
+
*/
|
|
388
|
+
export function launchRow(row, plan) {
|
|
371
389
|
return typeof row.cc_session_id === "string"
|
|
372
390
|
? row
|
|
373
391
|
: { ...row, cc_session_id: plan.newCcSessionId };
|
|
@@ -15,7 +15,6 @@ import { type DurableLaunchPlan, type PlanDeps, type RegistryReads, type SeatDep
|
|
|
15
15
|
import { type HandoverDeps } from "./handover.js";
|
|
16
16
|
import { type HostInstance } from "../../host/instance.js";
|
|
17
17
|
import { type OwnerHttp } from "../../host/owner.js";
|
|
18
|
-
import { type FirstTurnLock } from "./first-turn.js";
|
|
19
18
|
import type { HomeFacts } from "./view.js";
|
|
20
19
|
export declare function liveDurableApi(): DurableApi;
|
|
21
20
|
/** launcher.toml `durable`, defaulting to off (only read while the gate is on); unreadable is off. */
|
|
@@ -156,19 +155,24 @@ export interface LockInsteadDeps {
|
|
|
156
155
|
lockDeps: LockDeps;
|
|
157
156
|
seat: SeatDeps;
|
|
158
157
|
}
|
|
159
|
-
export declare function lockInsteadOfSpawn(row: RegistryRow, name: string, launcherKey: LauncherKey, durableOnly:
|
|
160
|
-
|
|
158
|
+
export declare function lockInsteadOfSpawn(row: RegistryRow, name: string, launcherKey: LauncherKey, durableOnly: boolean, d?: LockInsteadDeps): Promise<number>;
|
|
159
|
+
/** The api's create-and-lock refusal before migration 06a (or after its rollback): falls back to tracked. */
|
|
160
|
+
export declare const PRE_06A_CREATE_LOCK_CODE = "create_lock_not_durable_only";
|
|
161
161
|
/** The row a create-and-lock is about to make: what the idle-source lock reads (cwd, cc id). */
|
|
162
162
|
export declare function bornRow(plan: Extract<DurableLaunchPlan, {
|
|
163
163
|
kind: "spawn";
|
|
164
164
|
}>, name: string, agentId: string, cwd: string): RegistryRow;
|
|
165
165
|
/**
|
|
166
|
-
* B5a
|
|
167
|
-
* one request (the create route with the lock verb's body; the api commits both
|
|
168
|
-
* transaction), then attaches to the seat like any lock instead of spawning. No tracked row,
|
|
169
|
-
* attended claude, so no turn
|
|
170
|
-
* (lockResolvedRow: cwd rule, executor id, spec); only its POST goes to the create route. A
|
|
171
|
-
*
|
|
166
|
+
* Ruling A (B5a as amended, widened to every agent): a NEW session under `always` / ask-yes is
|
|
167
|
+
* created locked in one request (the create route with the lock verb's body; the api commits both
|
|
168
|
+
* in one transaction), then attaches to the seat like any lock instead of spawning. No tracked row,
|
|
169
|
+
* no attended claude, so no turn runs before the lock. The lock is the lock verb's own
|
|
170
|
+
* (lockResolvedRow: cwd rule, executor id, spec); only its POST goes to the create route. A refusal
|
|
171
|
+
* leaves no row and exits 1: on a durable-only agent with the durable_only_agent refusal (C02-R24),
|
|
172
|
+
* on any other agent with the lock refusal and its fix; the launcher never spawns tracked instead,
|
|
173
|
+
* with one exception (ruling 2026-10-31, option 1): an api without migration 06a, or with it rolled
|
|
174
|
+
* back, answers create_lock_not_durable_only for an agent that is not durable-only. That answer
|
|
175
|
+
* resolves to null, and the launch registers tracked and spawns exactly as before ruling A.
|
|
172
176
|
*
|
|
173
177
|
* No claude runs before the lock, so the spec cannot be read from a live original as the lock after
|
|
174
178
|
* the first turn does: the launch's own `--model` / `--effort` stand in for it (a passthrough
|
|
@@ -181,14 +185,6 @@ export declare function createLockedInsteadOfSpawn(plan: Extract<DurableLaunchPl
|
|
|
181
185
|
model?: string;
|
|
182
186
|
effort?: string;
|
|
183
187
|
trimMcp?: boolean;
|
|
184
|
-
}, d?: LockInsteadDeps & {
|
|
188
|
+
}, durableOnly: boolean, d?: LockInsteadDeps & {
|
|
185
189
|
api: Pick<DurableApi, "createSession">;
|
|
186
|
-
}): Promise<number>;
|
|
187
|
-
/**
|
|
188
|
-
* Dogfood B5: the live lock after the first turn of a new session (lib/durable/first-turn.ts). The
|
|
189
|
-
* transcript is read under `${CLAUDE_CONFIG_DIR:-~/.claude}/projects`; the lock is the lock verb's
|
|
190
|
-
* (cwd rule, transcript slug check, executor id) under the running-session handover, so a commit
|
|
191
|
-
* freezes the attended claude, waits for the seat's hello and ends it, and the launcher's exit path
|
|
192
|
-
* attaches to the seat (C02-R26 step 7). A refusal is printed with its fix line; claude goes on.
|
|
193
|
-
*/
|
|
194
|
-
export declare function liveLockAfterFirstTurn(row: RegistryRow, name: string, launcherKey: LauncherKey, childAlive: () => boolean): Promise<FirstTurnLock>;
|
|
190
|
+
}): Promise<number | null>;
|
package/dist/lib/durable/live.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* every decision lives in the injected modules and is unit-tested there.
|
|
5
5
|
*/
|
|
6
6
|
import { spawnSync } from "node:child_process";
|
|
7
|
-
import { existsSync
|
|
7
|
+
import { existsSync } from "node:fs";
|
|
8
8
|
import { randomUUID } from "node:crypto";
|
|
9
9
|
import { homedir } from "node:os";
|
|
10
10
|
import { getAccessToken, resolveAuth } from "../auth.js";
|
|
@@ -24,11 +24,9 @@ import { liveCwdProbe, liveTranscriptDirs, liveTrustProbe, lockFactsOf, lockReso
|
|
|
24
24
|
import { DurableApi, RegistryUnavailable, } from "./api.js";
|
|
25
25
|
import { afterLockRefusal, attachToSeat, createBody, createOutcome, flagValue, } from "./launch.js";
|
|
26
26
|
import { helloLanded, ledgerCcSessionIdFrom, lockCommitted, parentAfterChildEnded, processTree, sessionRowOf, } from "./handover.js";
|
|
27
|
-
import { ccProjectsDir } from "../../host/transcript.js";
|
|
28
27
|
import { resolveInstance } from "../../host/instance.js";
|
|
29
28
|
import { ownerApi, parseExecutorList } from "../../host/owner.js";
|
|
30
29
|
import { seatTmuxSession } from "../../host/tmux.js";
|
|
31
|
-
import { lockAfterFirstTurn, transcriptReader } from "./first-turn.js";
|
|
32
30
|
import { DurableRefusal, printDurableRefusal } from "./refusal.js";
|
|
33
31
|
export function liveDurableApi() {
|
|
34
32
|
return new DurableApi({
|
|
@@ -530,28 +528,37 @@ function liveLockInsteadDeps(launcherKey) {
|
|
|
530
528
|
seat: liveSeatDeps(launcherKey),
|
|
531
529
|
};
|
|
532
530
|
}
|
|
531
|
+
// A launch that locks never falls back to a spawn: afterLockRefusal always names its exit.
|
|
533
532
|
export async function lockInsteadOfSpawn(row, name, launcherKey, durableOnly, d = liveLockInsteadDeps(launcherKey)) {
|
|
534
|
-
const
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
533
|
+
const o = await d.lockRow(row, name, d.lockDeps);
|
|
534
|
+
if (!o.ok)
|
|
535
|
+
return printLockRefusal(o.refusal, durableOnly, launcherKey, name, d.lockDeps);
|
|
536
|
+
return attachLocked(o, row, name, d);
|
|
537
|
+
}
|
|
538
|
+
/** A lock refusal: printed with its fix; the exit status afterLockRefusal gives. */
|
|
539
|
+
function printLockRefusal(refusal, durableOnly, launcherKey, name, deps) {
|
|
540
|
+
const next = afterLockRefusal(refusal, durableOnly, launcherKey, name);
|
|
541
|
+
printDurableRefusal(next.print, false, deps);
|
|
542
|
+
return next.exit;
|
|
543
|
+
}
|
|
544
|
+
/** A landed lock: print it (interactive), then attach to the seat. */
|
|
545
|
+
async function attachLocked(o, row, name, d) {
|
|
541
546
|
const seat = d.seat;
|
|
542
547
|
if (seat.interactive)
|
|
543
|
-
printLocked(o, row, name,
|
|
548
|
+
printLocked(o, row, name, d.lockDeps.out);
|
|
544
549
|
const dropped = lockFactsOf(o.body, row, name, o.dropped).droppedFlags;
|
|
545
550
|
try {
|
|
546
551
|
return await attachToSeat(row, name, dropped, seat);
|
|
547
552
|
}
|
|
548
553
|
catch (e) {
|
|
549
554
|
if (e instanceof DurableRefusal)
|
|
550
|
-
return printDurableRefusal(e, false,
|
|
555
|
+
return printDurableRefusal(e, false, d.lockDeps);
|
|
551
556
|
throw e;
|
|
552
557
|
}
|
|
553
558
|
}
|
|
554
559
|
// ---- create-and-lock (ruling B5a as amended) ----------------------------------------------------
|
|
560
|
+
/** The api's create-and-lock refusal before migration 06a (or after its rollback): falls back to tracked. */
|
|
561
|
+
export const PRE_06A_CREATE_LOCK_CODE = "create_lock_not_durable_only";
|
|
555
562
|
/** The row a create-and-lock is about to make: what the idle-source lock reads (cwd, cc id). */
|
|
556
563
|
export function bornRow(plan, name, agentId, cwd) {
|
|
557
564
|
return {
|
|
@@ -565,19 +572,23 @@ export function bornRow(plan, name, agentId, cwd) {
|
|
|
565
572
|
};
|
|
566
573
|
}
|
|
567
574
|
/**
|
|
568
|
-
* B5a
|
|
569
|
-
* one request (the create route with the lock verb's body; the api commits both
|
|
570
|
-
* transaction), then attaches to the seat like any lock instead of spawning. No tracked row,
|
|
571
|
-
* attended claude, so no turn
|
|
572
|
-
* (lockResolvedRow: cwd rule, executor id, spec); only its POST goes to the create route. A
|
|
573
|
-
*
|
|
575
|
+
* Ruling A (B5a as amended, widened to every agent): a NEW session under `always` / ask-yes is
|
|
576
|
+
* created locked in one request (the create route with the lock verb's body; the api commits both
|
|
577
|
+
* in one transaction), then attaches to the seat like any lock instead of spawning. No tracked row,
|
|
578
|
+
* no attended claude, so no turn runs before the lock. The lock is the lock verb's own
|
|
579
|
+
* (lockResolvedRow: cwd rule, executor id, spec); only its POST goes to the create route. A refusal
|
|
580
|
+
* leaves no row and exits 1: on a durable-only agent with the durable_only_agent refusal (C02-R24),
|
|
581
|
+
* on any other agent with the lock refusal and its fix; the launcher never spawns tracked instead,
|
|
582
|
+
* with one exception (ruling 2026-10-31, option 1): an api without migration 06a, or with it rolled
|
|
583
|
+
* back, answers create_lock_not_durable_only for an agent that is not durable-only. That answer
|
|
584
|
+
* resolves to null, and the launch registers tracked and spawns exactly as before ruling A.
|
|
574
585
|
*
|
|
575
586
|
* No claude runs before the lock, so the spec cannot be read from a live original as the lock after
|
|
576
587
|
* the first turn does: the launch's own `--model` / `--effort` stand in for it (a passthrough
|
|
577
588
|
* `-- --model` / `--effort` first, as it comes last on claude's argv; else the flag, else
|
|
578
589
|
* launcher.toml), with its `--trim-mcp`, so the seat runs as the launch chose, never the default.
|
|
579
590
|
*/
|
|
580
|
-
export async function createLockedInsteadOfSpawn(plan, name, agentId, launcherKey, launch, d = {
|
|
591
|
+
export async function createLockedInsteadOfSpawn(plan, name, agentId, launcherKey, launch, durableOnly, d = {
|
|
581
592
|
...liveLockInsteadDeps(launcherKey),
|
|
582
593
|
api: liveDurableApi(),
|
|
583
594
|
}) {
|
|
@@ -588,6 +599,10 @@ export async function createLockedInsteadOfSpawn(plan, name, agentId, launcherKe
|
|
|
588
599
|
const lockRow = async (r, n, deps) => {
|
|
589
600
|
const o = await d.lockRow(r, n, {
|
|
590
601
|
...deps,
|
|
602
|
+
// Nothing holds the name: the row is not registered until the create lands, and a live row
|
|
603
|
+
// under the name refuses the launch first (planDurableLaunch, session_name_in_use). So a
|
|
604
|
+
// refusal's fix line never sends the user to `mnemom agent done`, which would fail (C11-R27).
|
|
605
|
+
nameHeld: false,
|
|
591
606
|
specDefaults: (row) => ({
|
|
592
607
|
...deps.specDefaults(row),
|
|
593
608
|
...(model ? { model } : {}),
|
|
@@ -608,28 +623,11 @@ export async function createLockedInsteadOfSpawn(plan, name, agentId, launcherKe
|
|
|
608
623
|
r.id = sid;
|
|
609
624
|
return o;
|
|
610
625
|
};
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
* attaches to the seat (C02-R26 step 7). A refusal is printed with its fix line; claude goes on.
|
|
619
|
-
*/
|
|
620
|
-
export function liveLockAfterFirstTurn(row, name, launcherKey, childAlive) {
|
|
621
|
-
const dirs = liveTranscriptDirs(process.env);
|
|
622
|
-
const seat = liveSeatDeps(launcherKey);
|
|
623
|
-
return lockAfterFirstTurn({
|
|
624
|
-
name,
|
|
625
|
-
readTranscript: transcriptReader(ccProjectsDir(process.env), dirs, row.cc_session_id, (p) => readFileSync(p, "utf8")),
|
|
626
|
-
childAlive,
|
|
627
|
-
lock: () => lockResolvedRow(row, name, {}, {
|
|
628
|
-
...liveLockDeps(),
|
|
629
|
-
handover: liveHandoverDeps(liveDurableApi()),
|
|
630
|
-
transcriptDirs: dirs,
|
|
631
|
-
}),
|
|
632
|
-
printRefusal: (r) => printDurableRefusal(r, false, seat),
|
|
633
|
-
sleep: (ms) => new Promise((r) => setTimeout(r, ms)),
|
|
634
|
-
});
|
|
626
|
+
const o = await lockRow(row, name, d.lockDeps);
|
|
627
|
+
if (o.ok)
|
|
628
|
+
return attachLocked(o, row, name, d);
|
|
629
|
+
// nothing printed: the caller registers tracked and spawns as before ruling A
|
|
630
|
+
if (o.refusal.code === PRE_06A_CREATE_LOCK_CODE)
|
|
631
|
+
return null;
|
|
632
|
+
return printLockRefusal(o.refusal, durableOnly, launcherKey, name, d.lockDeps);
|
|
635
633
|
}
|
|
@@ -98,6 +98,12 @@ export interface LauncherConfig {
|
|
|
98
98
|
stallTurns?: number;
|
|
99
99
|
};
|
|
100
100
|
}
|
|
101
|
+
/**
|
|
102
|
+
* The launcher key `durable` a launch uses: a valid MNEMOM_AGENT_DURABLE wins over launcher.toml
|
|
103
|
+
* (so `MNEMOM_AGENT_DURABLE=off` turns a configured `always` off for one launch), else the
|
|
104
|
+
* launcher.toml value, else `off`. Adopt reads the key the same way.
|
|
105
|
+
*/
|
|
106
|
+
export declare function durableLauncherKey(env: Readonly<Record<string, string | undefined>>, configured: LauncherKey | undefined): LauncherKey;
|
|
101
107
|
/**
|
|
102
108
|
* A settable key: its dotted config name, the getter/setter into a LauncherConfig,
|
|
103
109
|
* and a one-line description for `config list`/help. Kept as data so get/set/list
|
|
@@ -34,6 +34,17 @@ function coerceDurable(raw) {
|
|
|
34
34
|
return v;
|
|
35
35
|
throw new Error(`durable must be one of ${LAUNCHER_KEYS.join(", ")} (got '${raw}').`);
|
|
36
36
|
}
|
|
37
|
+
/**
|
|
38
|
+
* The launcher key `durable` a launch uses: a valid MNEMOM_AGENT_DURABLE wins over launcher.toml
|
|
39
|
+
* (so `MNEMOM_AGENT_DURABLE=off` turns a configured `always` off for one launch), else the
|
|
40
|
+
* launcher.toml value, else `off`. Adopt reads the key the same way.
|
|
41
|
+
*/
|
|
42
|
+
export function durableLauncherKey(env, configured) {
|
|
43
|
+
const e = env.MNEMOM_AGENT_DURABLE?.trim().toLowerCase();
|
|
44
|
+
if (e !== undefined && LAUNCHER_KEYS.includes(e))
|
|
45
|
+
return e;
|
|
46
|
+
return configured ?? "off";
|
|
47
|
+
}
|
|
37
48
|
function coerceLaunch(raw) {
|
|
38
49
|
const v = raw.trim();
|
|
39
50
|
if (LAUNCH_SHAPES.includes(v))
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The exec_append body: exactly these keys, as frozen S1 has it (`refs`, and the boot-bound
|
|
3
|
+
* `nonce`). Every executor append (the daemon's executor.spawned and executor.turn_continued, and the
|
|
4
|
+
* thin runner's executor.spawned) sends this shape. It lives in lib/runner with no imports so the
|
|
5
|
+
* runner can load it inside its allow-listed closure (runner-no-prompts); host/s1-names re-exports it.
|
|
6
|
+
*/
|
|
7
|
+
export interface ExecAppendBody {
|
|
8
|
+
readonly kind: string;
|
|
9
|
+
readonly refs: Readonly<Record<string, unknown>>;
|
|
10
|
+
readonly idempotency_key: string;
|
|
11
|
+
readonly expected_epoch: number;
|
|
12
|
+
readonly nonce: string;
|
|
13
|
+
}
|
|
14
|
+
/** The executor.spawned idempotency key, one per (session, epoch, spawn). */
|
|
15
|
+
export declare const spawnIdempotencyKey: (sessionId: string, epoch: number, spawnId: string) => string;
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* Nothing here decides an outcome: each call returns its status, body and error code, and the job
|
|
9
9
|
* (job.ts) maps them to 34.12's reasons. Never throws: a network failure is status 0.
|
|
10
10
|
*/
|
|
11
|
+
import type { ExecAppendBody } from "./exec-append.js";
|
|
11
12
|
export interface HttpResult {
|
|
12
13
|
readonly status: number;
|
|
13
14
|
readonly body: unknown;
|
|
@@ -40,7 +41,7 @@ export interface RunnerApi {
|
|
|
40
41
|
nonce: string;
|
|
41
42
|
new_key?: true;
|
|
42
43
|
}): Promise<HttpResult>;
|
|
43
|
-
append(sessionId: string, body:
|
|
44
|
+
append(sessionId: string, body: ExecAppendBody): Promise<HttpResult>;
|
|
44
45
|
hello(sessionId: string, body: {
|
|
45
46
|
epoch: number;
|
|
46
47
|
nonce: string;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The enrolled executor's own id, from its executor-state.json (S1 24.14; Durable lead, Doors
|
|
3
|
+
* W-L13). S1's pool claim answer names no executor_id (POOL_CLAIM_FIELDS), so the runner binds the
|
|
4
|
+
* served bundle to its own: the bundle's claim.executor_id must still equal it.
|
|
5
|
+
*
|
|
6
|
+
* The file counts only when it is S1's closed shape and its executor_key is the key this run uses,
|
|
7
|
+
* so an id is never borrowed from another executor's state.
|
|
8
|
+
*/
|
|
9
|
+
/** executor_id from the file's text, or null when it is absent, malformed or another key's. */
|
|
10
|
+
export declare function executorIdFromState(text: string | null, executorKey: string): string | null;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The enrolled executor's own id, from its executor-state.json (S1 24.14; Durable lead, Doors
|
|
3
|
+
* W-L13). S1's pool claim answer names no executor_id (POOL_CLAIM_FIELDS), so the runner binds the
|
|
4
|
+
* served bundle to its own: the bundle's claim.executor_id must still equal it.
|
|
5
|
+
*
|
|
6
|
+
* The file counts only when it is S1's closed shape and its executor_key is the key this run uses,
|
|
7
|
+
* so an id is never borrowed from another executor's state.
|
|
8
|
+
*/
|
|
9
|
+
import { executorStateOk } from "../../vendor/durable-contract/index.js";
|
|
10
|
+
/** executor_id from the file's text, or null when it is absent, malformed or another key's. */
|
|
11
|
+
export function executorIdFromState(text, executorKey) {
|
|
12
|
+
if (text === null)
|
|
13
|
+
return null;
|
|
14
|
+
let v;
|
|
15
|
+
try {
|
|
16
|
+
v = JSON.parse(text);
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
if (!executorStateOk(v))
|
|
22
|
+
return null;
|
|
23
|
+
return v.executor_key === executorKey ? v.executor_id : null;
|
|
24
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where `mnemom agent run` reaches GitHub.
|
|
3
|
+
*
|
|
4
|
+
* github.com for git and api.github.com for REST and GraphQL, always, except on a local Durable lane
|
|
5
|
+
* in test mode: there the fixture repos live on the lane's fake GitHub, the same one the api uses
|
|
6
|
+
* (DURABLE_TEST_GITHUB_API_BASE), which serves git and REST from one loopback origin. The test-mode
|
|
7
|
+
* module decides whether that base is honoured; outside test mode a set value is ignored with a
|
|
8
|
+
* warning and the runner stays on github.com. The runner's GitHub token only goes to these bases.
|
|
9
|
+
*/
|
|
10
|
+
export interface GithubBases {
|
|
11
|
+
/** git remotes and PR URLs: `${web}/${owner}/${repo}`. */
|
|
12
|
+
web: string;
|
|
13
|
+
/** REST and GraphQL: `${api}/repos/...`, `${api}/graphql`. */
|
|
14
|
+
api: string;
|
|
15
|
+
}
|
|
16
|
+
export declare const GITHUB_BASES: Readonly<GithubBases>;
|
|
17
|
+
export declare const GITHUB_BASE_IGNORED = "mnemom agent run: DURABLE_TEST_GITHUB_API_BASE is ignored outside test mode; using github.com";
|
|
18
|
+
/** The bases to use, and a warning when a lane base was set but not honoured. */
|
|
19
|
+
export declare function resolveGithubBases(env: NodeJS.ProcessEnv): {
|
|
20
|
+
bases: GithubBases;
|
|
21
|
+
warning: string | null;
|
|
22
|
+
};
|
|
23
|
+
/** The PR URL pattern for a web base: `${web}/<owner>/<repo>/pull/<n>`. */
|
|
24
|
+
export declare function prUrlPattern(web: string): RegExp;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where `mnemom agent run` reaches GitHub.
|
|
3
|
+
*
|
|
4
|
+
* github.com for git and api.github.com for REST and GraphQL, always, except on a local Durable lane
|
|
5
|
+
* in test mode: there the fixture repos live on the lane's fake GitHub, the same one the api uses
|
|
6
|
+
* (DURABLE_TEST_GITHUB_API_BASE), which serves git and REST from one loopback origin. The test-mode
|
|
7
|
+
* module decides whether that base is honoured; outside test mode a set value is ignored with a
|
|
8
|
+
* warning and the runner stays on github.com. The runner's GitHub token only goes to these bases.
|
|
9
|
+
*/
|
|
10
|
+
import { testGithubBase } from "../../durable/test-mode.mjs";
|
|
11
|
+
export const GITHUB_BASES = Object.freeze({
|
|
12
|
+
web: "https://github.com",
|
|
13
|
+
api: "https://api.github.com",
|
|
14
|
+
});
|
|
15
|
+
export const GITHUB_BASE_IGNORED = "mnemom agent run: DURABLE_TEST_GITHUB_API_BASE is ignored outside test mode; using github.com";
|
|
16
|
+
/** The bases to use, and a warning when a lane base was set but not honoured. */
|
|
17
|
+
export function resolveGithubBases(env) {
|
|
18
|
+
const t = testGithubBase(env);
|
|
19
|
+
if (t.base === null)
|
|
20
|
+
return { bases: GITHUB_BASES, warning: t.ignored ? GITHUB_BASE_IGNORED : null };
|
|
21
|
+
return { bases: { web: t.base, api: t.base }, warning: null };
|
|
22
|
+
}
|
|
23
|
+
/** The PR URL pattern for a web base: `${web}/<owner>/<repo>/pull/<n>`. */
|
|
24
|
+
export function prUrlPattern(web) {
|
|
25
|
+
const esc = web.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
26
|
+
return new RegExp(`^${esc}/([^/]+/[^/]+)/pull/(\\d+)$`);
|
|
27
|
+
}
|
package/dist/lib/runner/job.d.ts
CHANGED
|
@@ -25,6 +25,11 @@ export interface Workspace {
|
|
|
25
25
|
}
|
|
26
26
|
export interface JobDeps {
|
|
27
27
|
api: RunnerApi;
|
|
28
|
+
/**
|
|
29
|
+
* This runner's executor id: MNEMOM_EXECUTOR_ID, else the OIDC exchange answer's, else the
|
|
30
|
+
* host's executor-state.json when it holds the key in use; null: the claim's.
|
|
31
|
+
*/
|
|
32
|
+
executorId: string | null;
|
|
28
33
|
/** One nonce per runner process, reused on every call (A3 fences on it). */
|
|
29
34
|
nonce: string;
|
|
30
35
|
newSpawnId(): string;
|
|
@@ -38,6 +43,8 @@ export interface JobDeps {
|
|
|
38
43
|
runPhase(inv: PhaseInvocation, ws: Workspace, modelUrl: string, signal: AbortSignal): Promise<number>;
|
|
39
44
|
/** Push the job branch and open its PR; resolves the PR URL. */
|
|
40
45
|
openPr(repo: string, ws: Workspace, title: string): Promise<string>;
|
|
46
|
+
/** Commits on the job branch that the base does not have; 0 means there is nothing to open. */
|
|
47
|
+
commitsAhead(ws: Workspace): Promise<number>;
|
|
41
48
|
enableAutoMerge(prUrl: string): Promise<void>;
|
|
42
49
|
cleanup(ws: Workspace): Promise<void>;
|
|
43
50
|
}
|
|
@@ -63,8 +70,8 @@ export declare function authRefusal(r: HttpResult): RefusedReason | null;
|
|
|
63
70
|
*/
|
|
64
71
|
export declare function runJob(sessionId: string, opts: JobOptions, deps: JobDeps): Promise<JobResult>;
|
|
65
72
|
/**
|
|
66
|
-
* Report a failed
|
|
67
|
-
* Durable lead's to rule (A3 accepts only transcript_lost today); a
|
|
68
|
-
* never changes the outcome, because the lease lapses on its own.
|
|
73
|
+
* Report a failed job after claim (34.12 (6), (10)); a refusal releases the lease instead. The
|
|
74
|
+
* fail reason vocabulary is the Durable lead's to rule (A3 accepts only transcript_lost today); a
|
|
75
|
+
* fail the api refuses is logged and never changes the outcome, because the lease lapses on its own.
|
|
69
76
|
*/
|
|
70
|
-
export declare function reportFail(deps: JobDeps, sessionId: string, epoch: number, kind: "runner_failed"
|
|
77
|
+
export declare function reportFail(deps: JobDeps, sessionId: string, epoch: number, kind: "runner_failed", reason: string): Promise<void>;
|