@mnemom/mnemom 0.17.34-next.1 → 0.17.34-next.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/base-url-proxy.mjs +18 -4
- package/dist/commands/agent-config.js +5 -1
- package/dist/commands/agent-doctor.d.ts +9 -0
- package/dist/commands/agent-doctor.js +4 -0
- package/dist/commands/agent-session.d.ts +1 -2
- package/dist/commands/agent-session.js +1 -2
- package/dist/commands/agent-setup.d.ts +6 -0
- package/dist/commands/agent-setup.js +12 -1
- package/dist/commands/agent.d.ts +11 -0
- package/dist/commands/agent.js +145 -46
- package/dist/commands/durable-doctor.d.ts +50 -0
- package/dist/commands/durable-doctor.js +186 -0
- package/dist/commands/durable-lock.d.ts +254 -0
- package/dist/commands/durable-lock.js +595 -0
- package/dist/commands/durable-sessions.d.ts +87 -0
- package/dist/commands/durable-sessions.js +303 -0
- package/dist/commands/durable-verbs.d.ts +116 -0
- package/dist/commands/durable-verbs.js +393 -0
- package/dist/durable/flock.d.ts +26 -0
- package/dist/durable/flock.js +80 -0
- package/dist/durable/launch-spec.d.ts +7 -1
- package/dist/durable/launch-spec.js +10 -3
- package/dist/durable/seat-proxy-entry.d.ts +11 -0
- package/dist/durable/seat-proxy-entry.js +25 -0
- package/dist/durable/seat-proxy.d.ts +107 -0
- package/dist/durable/seat-proxy.js +599 -0
- package/dist/durable/seat-socket.d.ts +46 -0
- package/dist/durable/seat-socket.js +179 -0
- package/dist/durable/seat-spec-validator.d.ts +28 -0
- package/dist/durable/seat-spec-validator.js +59 -0
- package/dist/durable/test-mode.mjs +81 -0
- package/dist/durable/turn-chain.d.ts +55 -0
- package/dist/durable/turn-chain.js +178 -0
- package/dist/host/auto-continue.d.ts +67 -0
- package/dist/host/auto-continue.js +118 -0
- package/dist/host/bootid.d.ts +13 -0
- package/dist/host/bootid.js +41 -0
- package/dist/host/client.d.ts +123 -0
- package/dist/host/client.js +171 -0
- package/dist/host/commands.d.ts +2 -0
- package/dist/host/commands.js +122 -0
- package/dist/host/control.d.ts +33 -0
- package/dist/host/control.js +144 -0
- package/dist/host/daemon.d.ts +217 -0
- package/dist/host/daemon.js +606 -0
- package/dist/host/gate.d.ts +46 -0
- package/dist/host/gate.js +82 -0
- package/dist/host/grant.d.ts +24 -0
- package/dist/host/grant.js +24 -0
- package/dist/host/instance.d.ts +35 -0
- package/dist/host/instance.js +62 -0
- package/dist/host/integrity.d.ts +35 -0
- package/dist/host/integrity.js +115 -0
- package/dist/host/launchd.d.ts +16 -0
- package/dist/host/launchd.js +45 -0
- package/dist/host/lock.d.ts +20 -0
- package/dist/host/lock.js +63 -0
- package/dist/host/owner.d.ts +55 -0
- package/dist/host/owner.js +121 -0
- package/dist/host/plist.d.ts +16 -0
- package/dist/host/plist.js +85 -0
- package/dist/host/procs.d.ts +27 -0
- package/dist/host/procs.js +105 -0
- package/dist/host/run.d.ts +66 -0
- package/dist/host/run.js +314 -0
- package/dist/host/runtime.d.ts +8 -0
- package/dist/host/runtime.js +22 -0
- package/dist/host/s1-names.d.ts +22 -0
- package/dist/host/s1-names.js +21 -0
- package/dist/host/seat-credential.d.ts +27 -0
- package/dist/host/seat-credential.js +41 -0
- package/dist/host/seat-launch-main.d.ts +1 -0
- package/dist/host/seat-launch-main.js +59 -0
- package/dist/host/seat-launch.d.ts +116 -0
- package/dist/host/seat-launch.js +302 -0
- package/dist/host/seat.d.ts +48 -0
- package/dist/host/seat.js +149 -0
- package/dist/host/state.d.ts +33 -0
- package/dist/host/state.js +117 -0
- package/dist/host/status.d.ts +101 -0
- package/dist/host/status.js +307 -0
- package/dist/host/tmux.d.ts +56 -0
- package/dist/host/tmux.js +182 -0
- package/dist/host/transcript.d.ts +30 -0
- package/dist/host/transcript.js +130 -0
- package/dist/host/verbs.d.ts +50 -0
- package/dist/host/verbs.js +271 -0
- package/dist/index.d.ts +8 -6
- package/dist/index.js +197 -35
- package/dist/lib/agent.d.ts +8 -26
- package/dist/lib/agent.js +11 -26
- package/dist/lib/claude-plugin.d.ts +11 -2
- package/dist/lib/claude-plugin.js +10 -1
- package/dist/lib/durable/api.d.ts +136 -0
- package/dist/lib/durable/api.js +220 -0
- package/dist/lib/durable/codes.d.ts +8 -0
- package/dist/lib/durable/codes.js +2 -0
- package/dist/lib/durable/first-turn.d.ts +50 -0
- package/dist/lib/durable/first-turn.js +102 -0
- package/dist/lib/durable/gate.d.ts +48 -0
- package/dist/lib/durable/gate.js +122 -0
- package/dist/lib/durable/handover.d.ts +101 -0
- package/dist/lib/durable/handover.js +175 -0
- package/dist/lib/durable/launch.d.ts +182 -0
- package/dist/lib/durable/launch.js +443 -0
- package/dist/lib/durable/launching.d.ts +15 -0
- package/dist/lib/durable/launching.js +70 -0
- package/dist/lib/durable/live.d.ts +194 -0
- package/dist/lib/durable/live.js +635 -0
- package/dist/lib/durable/refusal.d.ts +57 -0
- package/dist/lib/durable/refusal.js +78 -0
- package/dist/lib/durable/view.d.ts +83 -0
- package/dist/lib/durable/view.js +244 -0
- package/dist/lib/experimental.d.ts +0 -9
- package/dist/lib/experimental.js +18 -17
- package/dist/lib/launcher-config.d.ts +7 -0
- package/dist/lib/launcher-config.js +21 -0
- package/dist/lib/scan/report-email.d.ts +1 -1
- package/dist/lib/scan/report-email.js +9 -1
- package/dist/lib/subscription-login.d.ts +15 -0
- package/dist/lib/subscription-login.js +28 -1
- package/dist/vendor/durable-contract/index.js +240 -18
- package/dist/vendor/shared/durable-contract/admission.d.ts +240 -0
- package/dist/vendor/shared/durable-contract/admission.js +533 -0
- package/dist/vendor/shared/durable-contract/arm.d.ts +51 -0
- package/dist/vendor/shared/durable-contract/arm.js +133 -0
- package/dist/vendor/shared/durable-contract/canonical.d.ts +29 -0
- package/dist/vendor/shared/durable-contract/canonical.js +98 -0
- package/dist/vendor/shared/durable-contract/cell-id.d.ts +22 -0
- package/dist/vendor/shared/durable-contract/cell-id.js +38 -0
- package/dist/vendor/shared/durable-contract/cli-output.d.ts +342 -0
- package/dist/vendor/shared/durable-contract/cli-output.js +201 -0
- package/dist/vendor/shared/durable-contract/cli-surface.d.ts +119 -0
- package/dist/vendor/shared/durable-contract/cli-surface.js +270 -0
- package/dist/vendor/shared/durable-contract/codes.d.ts +537 -0
- package/dist/vendor/shared/durable-contract/codes.js +475 -0
- package/dist/vendor/shared/durable-contract/constants.d.ts +153 -0
- package/dist/vendor/shared/durable-contract/constants.js +179 -0
- package/dist/vendor/shared/durable-contract/doors.d.ts +119 -0
- package/dist/vendor/shared/durable-contract/doors.js +249 -0
- package/dist/vendor/shared/durable-contract/envelope.d.ts +103 -0
- package/dist/vendor/shared/durable-contract/envelope.js +317 -0
- package/dist/vendor/shared/durable-contract/events.d.ts +147 -0
- package/dist/vendor/shared/durable-contract/events.js +630 -0
- package/dist/vendor/shared/durable-contract/executor-state.d.ts +41 -0
- package/dist/vendor/shared/durable-contract/executor-state.js +32 -0
- package/dist/vendor/shared/durable-contract/fixlines.d.ts +318 -0
- package/dist/vendor/shared/durable-contract/fixlines.js +483 -0
- package/dist/vendor/shared/durable-contract/freeze-record.d.ts +787 -0
- package/dist/vendor/shared/durable-contract/freeze-record.js +361 -0
- package/dist/vendor/shared/durable-contract/index.d.ts +36 -0
- package/dist/vendor/shared/durable-contract/index.js +37 -0
- package/dist/vendor/shared/durable-contract/journal-approvals.d.ts +29 -0
- package/dist/vendor/shared/durable-contract/journal-approvals.js +60 -0
- package/dist/vendor/shared/durable-contract/keys.d.ts +24 -0
- package/dist/vendor/shared/durable-contract/keys.js +66 -0
- package/dist/vendor/shared/durable-contract/lock.d.ts +128 -0
- package/dist/vendor/shared/durable-contract/lock.js +151 -0
- package/dist/vendor/shared/durable-contract/names.d.ts +25 -0
- package/dist/vendor/shared/durable-contract/names.js +78 -0
- package/dist/vendor/shared/durable-contract/owner-verbs.d.ts +175 -0
- package/dist/vendor/shared/durable-contract/owner-verbs.js +403 -0
- package/dist/vendor/shared/durable-contract/paths.d.ts +23 -0
- package/dist/vendor/shared/durable-contract/paths.js +35 -0
- package/dist/vendor/shared/durable-contract/pure-fn.d.ts +48 -0
- package/dist/vendor/shared/durable-contract/pure-fn.js +89 -0
- package/dist/vendor/shared/durable-contract/routes.d.ts +205 -0
- package/dist/vendor/shared/durable-contract/routes.js +315 -0
- package/dist/vendor/shared/durable-contract/schema.d.ts +126 -0
- package/dist/vendor/shared/durable-contract/schema.js +441 -0
- package/dist/vendor/shared/durable-contract/seal.d.ts +54 -0
- package/dist/vendor/shared/durable-contract/seal.js +99 -0
- package/dist/vendor/shared/durable-contract/secret-scope.d.ts +28 -0
- package/dist/vendor/shared/durable-contract/secret-scope.js +76 -0
- package/dist/vendor/shared/durable-contract/session-id.d.ts +21 -0
- package/dist/vendor/shared/durable-contract/session-id.js +58 -0
- package/dist/vendor/shared/durable-contract/spec.d.ts +162 -0
- package/dist/vendor/shared/durable-contract/spec.js +286 -0
- package/dist/vendor/shared/durable-contract/states.d.ts +96 -0
- package/dist/vendor/shared/durable-contract/states.js +218 -0
- package/dist/vendor/shared/durable-contract/telemetry.d.ts +94 -0
- package/dist/vendor/shared/durable-contract/telemetry.js +180 -0
- package/dist/vendor/shared/durable-contract/test-mode.d.ts +62 -0
- package/dist/vendor/shared/durable-contract/test-mode.js +140 -0
- package/dist/vendor/shared/durable-contract/turn.d.ts +57 -0
- package/dist/vendor/shared/durable-contract/turn.js +81 -0
- package/dist/vendor/shared/durable-contract/vectors.d.ts +191 -0
- package/dist/vendor/shared/durable-contract/vectors.js +208 -0
- package/package.json +3 -2
- package/plugins/integrity-manifest.json +35 -0
- package/plugins/mnemom/hooks/hooks.json +15 -1
- package/plugins/mnemom/hooks/register.tsx +60 -2
- package/plugins/mnemom/hooks/seat-hello.mjs +116 -0
- package/dist/commands/agent-implicit-notice.d.ts +0 -31
- package/dist/commands/agent-implicit-notice.js +0 -73
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Durable refusals on the CLI (wave-0 fix ruling 19; S1 cli-output.ts). One shape for every refused
|
|
3
|
+
* durable verb, with or without --json:
|
|
4
|
+
*
|
|
5
|
+
* stderr <refusal line>
|
|
6
|
+
* fix: <S1 catalogue line, rendered under the fix-line rule> (when there is one)
|
|
7
|
+
* code: <machine code>
|
|
8
|
+
* stdout with --json only: exactly one JSON document, the ruling-14 error envelope. An API or
|
|
9
|
+
* gateway refusal body is passed through unchanged; a refusal the CLI decides itself is
|
|
10
|
+
* built with S1 `cliRefusalJson` (details.fix carries the rendered line).
|
|
11
|
+
* exit CLI_REFUSAL_EXIT (1).
|
|
12
|
+
*
|
|
13
|
+
* The refusal line never contains "code:"/"error:" text, so the first machine-readable stderr token
|
|
14
|
+
* is always the code line.
|
|
15
|
+
*/
|
|
16
|
+
import { CLI_REFUSAL_EXIT, cliRefusalJson, } from "../../vendor/shared/durable-contract/cli-output.js";
|
|
17
|
+
import { isDurableCode } from "../../vendor/shared/durable-contract/codes.js";
|
|
18
|
+
import { renderFixLine, } from "../../vendor/shared/durable-contract/fixlines.js";
|
|
19
|
+
/** A refused durable verb. Commands throw it; `printDurableRefusal` renders it. */
|
|
20
|
+
export class DurableRefusal extends Error {
|
|
21
|
+
code;
|
|
22
|
+
fix;
|
|
23
|
+
details;
|
|
24
|
+
apiBody;
|
|
25
|
+
exit;
|
|
26
|
+
fixParams;
|
|
27
|
+
constructor(init) {
|
|
28
|
+
super(init.message);
|
|
29
|
+
this.name = "DurableRefusal";
|
|
30
|
+
this.code = init.code;
|
|
31
|
+
this.fix = init.fix ?? null;
|
|
32
|
+
this.details = init.details ?? {};
|
|
33
|
+
this.apiBody = init.apiBody;
|
|
34
|
+
this.exit = init.exit ?? CLI_REFUSAL_EXIT;
|
|
35
|
+
this.fixParams = init.fixParams ?? {};
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/** Render one catalogue line (thin wrapper so callers never restate a fix text). */
|
|
39
|
+
export function fixLine(key, launcherKey, nameHeld, params) {
|
|
40
|
+
const ctx = { launcherKey, nameHeld, params };
|
|
41
|
+
return renderFixLine(key, ctx);
|
|
42
|
+
}
|
|
43
|
+
/** The --json stdout document for a refusal. */
|
|
44
|
+
export function refusalJson(r) {
|
|
45
|
+
if (r.apiBody !== undefined && r.apiBody !== null)
|
|
46
|
+
return r.apiBody;
|
|
47
|
+
const details = { ...r.details };
|
|
48
|
+
if (r.fix)
|
|
49
|
+
details.fix = r.fix;
|
|
50
|
+
if (isDurableCode(r.code) && r.fix) {
|
|
51
|
+
return cliRefusalJson(r.code, r.message, details);
|
|
52
|
+
}
|
|
53
|
+
// A non-durable code (agent_not_found, nested_launch_refused, …) or no fix: the same envelope.
|
|
54
|
+
return {
|
|
55
|
+
error: {
|
|
56
|
+
code: r.code,
|
|
57
|
+
message: r.message,
|
|
58
|
+
...(Object.keys(details).length ? { details } : {}),
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
/** Print a refusal (stderr always; stdout JSON only with --json) and return its exit status. */
|
|
63
|
+
export function printDurableRefusal(r, json, sink) {
|
|
64
|
+
sink.err(r.message);
|
|
65
|
+
if (r.fix)
|
|
66
|
+
sink.err(`fix: ${r.fix}`);
|
|
67
|
+
sink.err(`code: ${r.code}`);
|
|
68
|
+
if (json)
|
|
69
|
+
sink.out(JSON.stringify(refusalJson(r)));
|
|
70
|
+
return r.exit;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* The agent action's catch (index.ts): a DurableRefusal is printed (refusal line, fix, code) and its
|
|
74
|
+
* exit status returned; anything else is null and goes to the generic CLI error report.
|
|
75
|
+
*/
|
|
76
|
+
export function durableRefusalExit(error, sink) {
|
|
77
|
+
return error instanceof DurableRefusal ? printDurableRefusal(error, false, sink) : null;
|
|
78
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure presentation and decision helpers for durable sessions on the CLI (C01-R12, R16, R24, R25,
|
|
3
|
+
* R28, R30; C11-R27). No I/O: every input is passed in, so each branch is unit-tested.
|
|
4
|
+
*/
|
|
5
|
+
import { type FixKey, type LauncherKey } from "../../vendor/shared/durable-contract/fixlines.js";
|
|
6
|
+
import { type EffectiveState, type StateReason, type UserState } from "../../vendor/shared/durable-contract/states.js";
|
|
7
|
+
import type { RegistryRow } from "./api.js";
|
|
8
|
+
/** The effective state the CLI branches on (GET returns both; I23). */
|
|
9
|
+
export declare function effectiveOf(row: RegistryRow): EffectiveState;
|
|
10
|
+
export declare function isLocked(row: RegistryRow): boolean;
|
|
11
|
+
export declare function isEnded(row: RegistryRow): boolean;
|
|
12
|
+
export declare function reasonOf(row: RegistryRow): StateReason | null;
|
|
13
|
+
/** The user-facing state word (running | waiting | stopped | ended). */
|
|
14
|
+
export declare function userState(row: RegistryRow): UserState;
|
|
15
|
+
/** "ended: agent killed" style label: user state, then the reason in words when there is one. */
|
|
16
|
+
export declare function stateLabel(row: RegistryRow): string;
|
|
17
|
+
/** Spend column (C01-R25): guard_state.spend_usd for locked rows, NA_TEXT for tracked ones. */
|
|
18
|
+
export declare function spendText(row: RegistryRow): string;
|
|
19
|
+
/** --json spend: the cents string for locked rows, null for tracked ones. */
|
|
20
|
+
export declare function spendJson(row: RegistryRow): string | null;
|
|
21
|
+
export declare function committedBudget(row: RegistryRow): number;
|
|
22
|
+
/** The home machine's name, or null when the row carries none. */
|
|
23
|
+
export declare function machineName(row: RegistryRow): string | null;
|
|
24
|
+
/** Where the session lives, for the machine column. */
|
|
25
|
+
export declare function machineText(row: RegistryRow): string;
|
|
26
|
+
export declare function lastActivity(row: RegistryRow): string | null;
|
|
27
|
+
export interface HomeFacts {
|
|
28
|
+
/** This machine's executor id from its state file, or null (no enrollment here). */
|
|
29
|
+
localExecutorId: string | null;
|
|
30
|
+
/** This machine's label (machine_label), for the unreachable-home rule. */
|
|
31
|
+
localMachineLabel: string | null;
|
|
32
|
+
nowMs: number;
|
|
33
|
+
}
|
|
34
|
+
export type HomeKind = "here" | "unreachable" | "elsewhere_live" | "elsewhere_stale";
|
|
35
|
+
export declare function homeKind(row: RegistryRow, f: HomeFacts): HomeKind;
|
|
36
|
+
export interface FixContext {
|
|
37
|
+
launcherKey: LauncherKey;
|
|
38
|
+
/** The name is held by a non-terminal row (it is when this row is non-terminal). */
|
|
39
|
+
nameHeld: boolean;
|
|
40
|
+
}
|
|
41
|
+
/** The catalogue key for a row's stopped or ended state, or null for running/waiting. */
|
|
42
|
+
export declare function stateFixKey(row: RegistryRow, extra?: {
|
|
43
|
+
agentKilled?: boolean;
|
|
44
|
+
cwdCause?: "missing" | "not_toplevel" | null;
|
|
45
|
+
}): FixKey | null;
|
|
46
|
+
export declare function renderKey(key: FixKey, row: RegistryRow, ctx: FixContext, params?: Record<string, string>): string;
|
|
47
|
+
/** The unreachable-home line (C01-R28): done-then-new, same text in continue, sessions and show. */
|
|
48
|
+
export declare function unreachableHomeFix(row: RegistryRow, ctx: FixContext): string;
|
|
49
|
+
/** The grant warning (BACKBONE C1): days left when inside GRANT_WARNING_S, else null. */
|
|
50
|
+
export declare function grantWarning(row: RegistryRow, nowMs: number): string | null;
|
|
51
|
+
/** The single fix line a row shows in `sessions` / `show` (null when it needs none). */
|
|
52
|
+
export declare function rowFixLine(row: RegistryRow, ctx: FixContext, home: HomeFacts | null): string | null;
|
|
53
|
+
export type ContinueDecision = {
|
|
54
|
+
kind: "fresh";
|
|
55
|
+
} | {
|
|
56
|
+
kind: "tracked_continue";
|
|
57
|
+
row: RegistryRow;
|
|
58
|
+
} | {
|
|
59
|
+
kind: "attach";
|
|
60
|
+
row: RegistryRow;
|
|
61
|
+
wait: boolean;
|
|
62
|
+
} | {
|
|
63
|
+
kind: "refuse";
|
|
64
|
+
code: string;
|
|
65
|
+
message: string;
|
|
66
|
+
fix: string | null;
|
|
67
|
+
details?: Record<string, unknown>;
|
|
68
|
+
};
|
|
69
|
+
export interface ContinueInput {
|
|
70
|
+
/** Every row the caller owns under this name in the active org (any state). */
|
|
71
|
+
rows: RegistryRow[];
|
|
72
|
+
launcherKey: LauncherKey;
|
|
73
|
+
home: HomeFacts;
|
|
74
|
+
/** This machine's host daemon job is loaded (C01-R16 (c)). */
|
|
75
|
+
daemonLoaded: boolean;
|
|
76
|
+
/** The agent the name's latest session ran on is killed (C01-R16 (j), R27). */
|
|
77
|
+
agentKilled: boolean;
|
|
78
|
+
/** An explicit --agent that differs from the row's agent (C01-R17). */
|
|
79
|
+
explicitAgentId: string | null;
|
|
80
|
+
}
|
|
81
|
+
/** The non-terminal row holding the name, if any (names are unique among non-terminal rows). */
|
|
82
|
+
export declare function liveRow(rows: RegistryRow[]): RegistryRow | null;
|
|
83
|
+
export declare function continueDecision(i: ContinueInput): ContinueDecision;
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure presentation and decision helpers for durable sessions on the CLI (C01-R12, R16, R24, R25,
|
|
3
|
+
* R28, R30; C11-R27). No I/O: every input is passed in, so each branch is unit-tested.
|
|
4
|
+
*/
|
|
5
|
+
import { EXECUTOR_LIVE_S, GRANT_WARNING_S, PER_SESSION_BUDGET_LIMIT_USD, } from "../../vendor/shared/durable-contract/constants.js";
|
|
6
|
+
import { formatSpendCents, grantWarningDays, NA_TEXT, renderFixLine, stoppedFixKey, } from "../../vendor/shared/durable-contract/fixlines.js";
|
|
7
|
+
import { endedBody, isEffectiveState, isSessionState, isTerminalState, userStateOf, } from "../../vendor/shared/durable-contract/states.js";
|
|
8
|
+
import { CODE } from "./codes.js";
|
|
9
|
+
/** The effective state the CLI branches on (GET returns both; I23). */
|
|
10
|
+
export function effectiveOf(row) {
|
|
11
|
+
const e = row.effective_state;
|
|
12
|
+
if (isEffectiveState(e))
|
|
13
|
+
return e;
|
|
14
|
+
if (isSessionState(row.state))
|
|
15
|
+
return row.state;
|
|
16
|
+
return "running";
|
|
17
|
+
}
|
|
18
|
+
export function isLocked(row) {
|
|
19
|
+
return row.durable_mode === "locked";
|
|
20
|
+
}
|
|
21
|
+
export function isEnded(row) {
|
|
22
|
+
const e = effectiveOf(row);
|
|
23
|
+
return e === "expired" || (isSessionState(e) && isTerminalState(e));
|
|
24
|
+
}
|
|
25
|
+
export function reasonOf(row) {
|
|
26
|
+
return (row.state_reason ?? null);
|
|
27
|
+
}
|
|
28
|
+
/** The user-facing state word (running | waiting | stopped | ended). */
|
|
29
|
+
export function userState(row) {
|
|
30
|
+
return userStateOf(effectiveOf(row));
|
|
31
|
+
}
|
|
32
|
+
/** "ended: agent killed" style label: user state, then the reason in words when there is one. */
|
|
33
|
+
export function stateLabel(row) {
|
|
34
|
+
const u = userState(row);
|
|
35
|
+
const e = effectiveOf(row);
|
|
36
|
+
if (e === "suspended_grant_expired")
|
|
37
|
+
return `${u}: grant expired`;
|
|
38
|
+
const reason = reasonOf(row);
|
|
39
|
+
if (u === "ended" && e === "expired")
|
|
40
|
+
return "ended: expired";
|
|
41
|
+
if (reason)
|
|
42
|
+
return `${u}: ${reason.replace(/_/g, " ")}`;
|
|
43
|
+
return u;
|
|
44
|
+
}
|
|
45
|
+
/** Spend column (C01-R25): guard_state.spend_usd for locked rows, NA_TEXT for tracked ones. */
|
|
46
|
+
export function spendText(row) {
|
|
47
|
+
if (!isLocked(row))
|
|
48
|
+
return NA_TEXT;
|
|
49
|
+
const s = row.guard_state?.spend_usd ?? row.spend_usd ?? null;
|
|
50
|
+
return typeof s === "number" && Number.isFinite(s) && s >= 0 ? formatSpendCents(s) : NA_TEXT;
|
|
51
|
+
}
|
|
52
|
+
/** --json spend: the cents string for locked rows, null for tracked ones. */
|
|
53
|
+
export function spendJson(row) {
|
|
54
|
+
const t = spendText(row);
|
|
55
|
+
return t === NA_TEXT ? null : t;
|
|
56
|
+
}
|
|
57
|
+
export function committedBudget(row) {
|
|
58
|
+
const b = row.guard_state?.budget_usd ?? row.budget_usd ?? 0;
|
|
59
|
+
return typeof b === "number" && Number.isFinite(b) ? b : 0;
|
|
60
|
+
}
|
|
61
|
+
/** The home machine's name, or null when the row carries none. */
|
|
62
|
+
export function machineName(row) {
|
|
63
|
+
const h = row.home_executor;
|
|
64
|
+
return h?.machine_label ?? h?.hostname ?? row.machine ?? null;
|
|
65
|
+
}
|
|
66
|
+
/** Where the session lives, for the machine column. */
|
|
67
|
+
export function machineText(row) {
|
|
68
|
+
return machineName(row) ?? NA_TEXT;
|
|
69
|
+
}
|
|
70
|
+
export function lastActivity(row) {
|
|
71
|
+
return row.last_activity_at ?? row.updated_at ?? row.created_at ?? null;
|
|
72
|
+
}
|
|
73
|
+
export function homeKind(row, f) {
|
|
74
|
+
const h = row.home_executor ?? null;
|
|
75
|
+
const homeId = row.home_executor_id ?? h?.id ?? null;
|
|
76
|
+
if (h?.revoked_at)
|
|
77
|
+
return "unreachable";
|
|
78
|
+
if (homeId !== null && f.localExecutorId !== null && homeId === f.localExecutorId)
|
|
79
|
+
return "here";
|
|
80
|
+
const seen = h?.last_seen_at ? Date.parse(h.last_seen_at) : NaN;
|
|
81
|
+
const stale = !Number.isFinite(seen) || f.nowMs - seen > EXECUTOR_LIVE_S * 1000;
|
|
82
|
+
if (stale && f.localMachineLabel !== null && h?.machine_label === f.localMachineLabel)
|
|
83
|
+
return "unreachable";
|
|
84
|
+
return stale ? "elsewhere_stale" : "elsewhere_live";
|
|
85
|
+
}
|
|
86
|
+
/** The catalogue key for a row's stopped or ended state, or null for running/waiting. */
|
|
87
|
+
export function stateFixKey(row, extra = {}) {
|
|
88
|
+
const e = effectiveOf(row);
|
|
89
|
+
const reason = reasonOf(row);
|
|
90
|
+
const ended = endedBody(e, reason);
|
|
91
|
+
if (ended !== null)
|
|
92
|
+
return ended.state_reason === "agent_killed" || extra.agentKilled ? "agent_killed" : null;
|
|
93
|
+
if (!isLocked(row))
|
|
94
|
+
return null;
|
|
95
|
+
if (e === "paused" || e === "suspended" || e === "suspended_grant_expired") {
|
|
96
|
+
try {
|
|
97
|
+
return stoppedFixKey(e, reason, {
|
|
98
|
+
committedBudgetUsd: committedBudget(row),
|
|
99
|
+
cwdCause: extra.cwdCause ?? null,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
return "stopped_no_m1_remedy";
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
export function renderKey(key, row, ctx, params = {}) {
|
|
109
|
+
const name = row.name ?? row.conversation_id;
|
|
110
|
+
return renderFixLine(key, {
|
|
111
|
+
launcherKey: ctx.launcherKey,
|
|
112
|
+
nameHeld: ctx.nameHeld,
|
|
113
|
+
params: {
|
|
114
|
+
name,
|
|
115
|
+
agent: row.agent_name ?? row.agent_id,
|
|
116
|
+
limit: String(PER_SESSION_BUDGET_LIMIT_USD),
|
|
117
|
+
n: String(PER_SESSION_BUDGET_LIMIT_USD),
|
|
118
|
+
machine: machineText(row),
|
|
119
|
+
days: "",
|
|
120
|
+
cwd: row.cwd ?? "",
|
|
121
|
+
toplevel: row.cwd ?? "",
|
|
122
|
+
checkout: row.cwd ?? "",
|
|
123
|
+
realpath: row.cwd ?? "",
|
|
124
|
+
...params,
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
/** The unreachable-home line (C01-R28): done-then-new, same text in continue, sessions and show. */
|
|
129
|
+
export function unreachableHomeFix(row, ctx) {
|
|
130
|
+
return renderKey("unreachable_home", row, { ...ctx, nameHeld: true });
|
|
131
|
+
}
|
|
132
|
+
/** The grant warning (BACKBONE C1): days left when inside GRANT_WARNING_S, else null. */
|
|
133
|
+
export function grantWarning(row, nowMs) {
|
|
134
|
+
const g = row.home_executor?.grant_expires_at;
|
|
135
|
+
if (!g)
|
|
136
|
+
return null;
|
|
137
|
+
const left = (Date.parse(g) - nowMs) / 1000;
|
|
138
|
+
const days = grantWarningDays(left, GRANT_WARNING_S);
|
|
139
|
+
if (days === null)
|
|
140
|
+
return null;
|
|
141
|
+
return renderFixLine("grant_warning", {
|
|
142
|
+
launcherKey: "always",
|
|
143
|
+
nameHeld: false,
|
|
144
|
+
params: { days: String(days) },
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
/** The single fix line a row shows in `sessions` / `show` (null when it needs none). */
|
|
148
|
+
export function rowFixLine(row, ctx, home) {
|
|
149
|
+
if (isLocked(row) && !isEnded(row) && home) {
|
|
150
|
+
const k = homeKind(row, home);
|
|
151
|
+
if (k === "unreachable")
|
|
152
|
+
return unreachableHomeFix(row, ctx);
|
|
153
|
+
}
|
|
154
|
+
const key = stateFixKey(row);
|
|
155
|
+
if (key === null)
|
|
156
|
+
return null;
|
|
157
|
+
return renderKey(key, row, { ...ctx, nameHeld: ctx.nameHeld && !isEnded(row) });
|
|
158
|
+
}
|
|
159
|
+
/** The non-terminal row holding the name, if any (names are unique among non-terminal rows). */
|
|
160
|
+
export function liveRow(rows) {
|
|
161
|
+
return rows.find((r) => !isEnded(r)) ?? null;
|
|
162
|
+
}
|
|
163
|
+
export function continueDecision(i) {
|
|
164
|
+
const row = liveRow(i.rows);
|
|
165
|
+
const ctx = { launcherKey: i.launcherKey, nameHeld: row !== null };
|
|
166
|
+
if (row === null) {
|
|
167
|
+
// (j) every session under the name ended: fresh, unless the agent was killed.
|
|
168
|
+
const last = i.rows[0];
|
|
169
|
+
if (last && i.agentKilled && i.explicitAgentId === null) {
|
|
170
|
+
return {
|
|
171
|
+
kind: "refuse",
|
|
172
|
+
code: CODE.durable_session_ended,
|
|
173
|
+
message: `${last.name ?? "this session"}: the agent was killed`,
|
|
174
|
+
fix: renderKey("agent_killed", last, { ...ctx, nameHeld: false }),
|
|
175
|
+
details: { state: "killed", state_reason: "agent_killed" },
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
return { kind: "fresh" };
|
|
179
|
+
}
|
|
180
|
+
if (i.explicitAgentId !== null && i.explicitAgentId !== row.agent_id) {
|
|
181
|
+
return {
|
|
182
|
+
kind: "refuse",
|
|
183
|
+
code: "session_agent_mismatch",
|
|
184
|
+
message: `${row.name}: this session runs on agent ${row.agent_name ?? row.agent_id}; a session never changes agents`,
|
|
185
|
+
fix: null,
|
|
186
|
+
details: { agent_id: row.agent_id },
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
if (!isLocked(row))
|
|
190
|
+
return { kind: "tracked_continue", row };
|
|
191
|
+
const e = effectiveOf(row);
|
|
192
|
+
// stopped (d)-(i): their own line, wherever the home is.
|
|
193
|
+
const stoppedKey = stateFixKey(row);
|
|
194
|
+
if (e === "paused" || e === "suspended" || e === "suspended_grant_expired") {
|
|
195
|
+
return {
|
|
196
|
+
kind: "refuse",
|
|
197
|
+
code: CODE.durable_session_stopped,
|
|
198
|
+
message: `${row.name} is ${stateLabel(row)}`,
|
|
199
|
+
fix: stoppedKey ? renderKey(stoppedKey, row, ctx) : null,
|
|
200
|
+
details: { state: row.state, state_reason: row.state_reason ?? null },
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
const home = homeKind(row, i.home);
|
|
204
|
+
if (home === "unreachable") {
|
|
205
|
+
// C01-R28: only a home elsewhere is `session_homed_elsewhere`. S1 names no code for an
|
|
206
|
+
// unreachable home, so it takes a CLI-local one (like cwd_untrusted), never a durable_* name.
|
|
207
|
+
return {
|
|
208
|
+
kind: "refuse",
|
|
209
|
+
code: "home_unreachable",
|
|
210
|
+
message: `${row.name}: its home machine can no longer run it`,
|
|
211
|
+
fix: unreachableHomeFix(row, ctx),
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
if (home === "elsewhere_live" || home === "elsewhere_stale") {
|
|
215
|
+
// Both fix lines name <machine>: with no machine name the line is omitted, never printed with a
|
|
216
|
+
// placeholder (C11-R27).
|
|
217
|
+
const machine = machineName(row);
|
|
218
|
+
if (machine === null) {
|
|
219
|
+
return {
|
|
220
|
+
kind: "refuse",
|
|
221
|
+
code: "session_homed_elsewhere",
|
|
222
|
+
message: `${row.name} runs on another machine`,
|
|
223
|
+
fix: null,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
return {
|
|
227
|
+
kind: "refuse",
|
|
228
|
+
code: "session_homed_elsewhere",
|
|
229
|
+
message: `${row.name} runs on ${machine}`,
|
|
230
|
+
fix: renderKey(home === "elsewhere_live" ? "session_homed_elsewhere" : "session_homed_elsewhere_stale", row, ctx),
|
|
231
|
+
details: { machine },
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
// home here: running/parked/detached/resuming
|
|
235
|
+
if (!i.daemonLoaded) {
|
|
236
|
+
return {
|
|
237
|
+
kind: "refuse",
|
|
238
|
+
code: "session_locked",
|
|
239
|
+
message: `${row.name} is locked and the host daemon is not running here`,
|
|
240
|
+
fix: renderKey("daemon_not_running", row, ctx),
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
return { kind: "attach", row, wait: e !== "running" };
|
|
244
|
+
}
|
|
@@ -63,15 +63,6 @@ export declare function resolveExperimental(opts: {
|
|
|
63
63
|
stored: ExperimentalPrefs | undefined;
|
|
64
64
|
env: Record<string, string | undefined>;
|
|
65
65
|
}): ExperimentalState;
|
|
66
|
-
/**
|
|
67
|
-
* Features a prerelease (`-next.N`) build makes available without `mnemom experimental
|
|
68
|
-
* enable`: door one's T16 turns implicit goals on by default on `next` (decisions 12 and
|
|
69
|
-
* 22), so its flag, its launcher.toml value and `mnemom agent goal` are not refused there.
|
|
70
|
-
* Stable is unchanged.
|
|
71
|
-
*/
|
|
72
|
-
export declare const PRERELEASE_AVAILABLE: readonly ExperimentalId[];
|
|
73
|
-
/** The features whose flags, keys and commands are usable: the enabled ones, plus PRERELEASE_AVAILABLE on a prerelease. */
|
|
74
|
-
export declare function availableFeatures(enabled: readonly ExperimentalId[], prerelease: boolean): ExperimentalId[];
|
|
75
66
|
/** The one-line stderr notice for a launch with experimental features on; null when none are. */
|
|
76
67
|
export declare function experimentalNotice(enabled: readonly ExperimentalId[]): string | null;
|
|
77
68
|
/** The error for using an experimental flag or command while its feature is off. */
|
package/dist/lib/experimental.js
CHANGED
|
@@ -108,15 +108,23 @@ export const EXPERIMENTAL_FEATURES = [
|
|
|
108
108
|
},
|
|
109
109
|
},
|
|
110
110
|
{
|
|
111
|
-
// Durable
|
|
112
|
-
//
|
|
111
|
+
// Durable M1 (BACKBONE Flags/CLI): every durable verb word, `mnemom agent host …` and
|
|
112
|
+
// `mnemom agent containment …` exit 2 with the pointer while off; the host daemon and
|
|
113
|
+
// `host install` read the gate from config.json only (never MNEMOM_EXPERIMENTAL).
|
|
113
114
|
id: "durable",
|
|
114
|
-
summary: "Durable sessions: lock a session so
|
|
115
|
+
summary: "Durable sessions: lock a session so the host daemon (`mnemom agent host`) keeps it running and brings it back after a crash or reboot, and adopt running ones",
|
|
115
116
|
since: "0.17.27",
|
|
116
117
|
gates: {
|
|
117
|
-
flags: [],
|
|
118
|
-
configKeys: ["durable"],
|
|
119
|
-
commands: [
|
|
118
|
+
flags: ["--new"],
|
|
119
|
+
configKeys: ["durable = ask|always"],
|
|
120
|
+
commands: [
|
|
121
|
+
"mnemom agent lock|pause|resume|done|kill|amend",
|
|
122
|
+
"mnemom agent containment",
|
|
123
|
+
"mnemom agent containment pause|resume",
|
|
124
|
+
"mnemom agent host",
|
|
125
|
+
"mnemom agent sessions unlock",
|
|
126
|
+
"mnemom agent sessions adopt",
|
|
127
|
+
],
|
|
120
128
|
env: [],
|
|
121
129
|
},
|
|
122
130
|
},
|
|
@@ -175,17 +183,6 @@ export function resolveExperimental(opts) {
|
|
|
175
183
|
unknown: stored.filter((t) => !isExperimentalId(t)),
|
|
176
184
|
};
|
|
177
185
|
}
|
|
178
|
-
/**
|
|
179
|
-
* Features a prerelease (`-next.N`) build makes available without `mnemom experimental
|
|
180
|
-
* enable`: door one's T16 turns implicit goals on by default on `next` (decisions 12 and
|
|
181
|
-
* 22), so its flag, its launcher.toml value and `mnemom agent goal` are not refused there.
|
|
182
|
-
* Stable is unchanged.
|
|
183
|
-
*/
|
|
184
|
-
export const PRERELEASE_AVAILABLE = ["implicit"];
|
|
185
|
-
/** The features whose flags, keys and commands are usable: the enabled ones, plus PRERELEASE_AVAILABLE on a prerelease. */
|
|
186
|
-
export function availableFeatures(enabled, prerelease) {
|
|
187
|
-
return normalize(prerelease ? [...enabled, ...PRERELEASE_AVAILABLE] : enabled);
|
|
188
|
-
}
|
|
189
186
|
/** The one-line stderr notice for a launch with experimental features on; null when none are. */
|
|
190
187
|
export function experimentalNotice(enabled) {
|
|
191
188
|
if (enabled.length === 0)
|
|
@@ -229,6 +226,8 @@ export function experimentalIdForConfigKey(key, value) {
|
|
|
229
226
|
return value === "true" ? "context-handoff" : undefined;
|
|
230
227
|
case "goal_mode":
|
|
231
228
|
return value === "implicit" ? "implicit" : undefined;
|
|
229
|
+
case "durable":
|
|
230
|
+
return value === "off" ? undefined : "durable";
|
|
232
231
|
default:
|
|
233
232
|
return undefined;
|
|
234
233
|
}
|
|
@@ -307,6 +306,8 @@ export function configKeyFeature(key) {
|
|
|
307
306
|
return "context-handoff";
|
|
308
307
|
if (key === "goal_mode")
|
|
309
308
|
return "implicit";
|
|
309
|
+
if (key === "durable")
|
|
310
|
+
return "durable";
|
|
310
311
|
return undefined;
|
|
311
312
|
}
|
|
312
313
|
/**
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
* the caller folds the plain-value settings under the CLI flags.
|
|
17
17
|
*/
|
|
18
18
|
import { type SavedAgentAuth } from "./subscription-login.js";
|
|
19
|
+
import { type LauncherKey } from "../vendor/shared/durable-contract/fixlines.js";
|
|
19
20
|
import { type ExperimentalId } from "./experimental.js";
|
|
20
21
|
import { type ContextProfile, type GoalModeDefault, type LaunchShape } from "./agent.js";
|
|
21
22
|
/** The human-editable settings file. Secret key lives elsewhere (launcher.json). */
|
|
@@ -85,6 +86,12 @@ export interface LauncherConfig {
|
|
|
85
86
|
* "off", "350000"), parsed with `parseCompactAt` at the point of use.
|
|
86
87
|
*/
|
|
87
88
|
compactAt?: string;
|
|
89
|
+
/**
|
|
90
|
+
* Durable launcher key (EXPERIMENTAL `durable`; S1 LAUNCHER_KEYS): `off` (default) launches
|
|
91
|
+
* tracked, `ask` offers to lock on a TTY, `always` locks every launch. Ignored with the
|
|
92
|
+
* pointer while `durable` is off (`durable = off` is harmless either way).
|
|
93
|
+
*/
|
|
94
|
+
durable?: LauncherKey;
|
|
88
95
|
guardrails?: {
|
|
89
96
|
maxTurns?: number;
|
|
90
97
|
budgetUsd?: number;
|
|
@@ -20,6 +20,7 @@ import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "
|
|
|
20
20
|
import { dirname, join } from "node:path";
|
|
21
21
|
import { parse as parseToml, stringify as stringifyToml, TomlError } from "smol-toml";
|
|
22
22
|
import { MNEMOM_DIR } from "./config.js";
|
|
23
|
+
import { LAUNCHER_KEYS } from "../vendor/shared/durable-contract/fixlines.js";
|
|
23
24
|
import { CONTEXT_PROFILES, isPositiveInt, isPositiveNumber, parseCompactAt, parseAgentRef, parseContextProfile, } from "./agent.js";
|
|
24
25
|
/** The human-editable settings file. Secret key lives elsewhere (launcher.json). */
|
|
25
26
|
export const LAUNCHER_CONFIG_PATH = join(MNEMOM_DIR, "launcher.toml");
|
|
@@ -27,6 +28,12 @@ const LAUNCH_SHAPES = ["terminal", "remote-control", "server"];
|
|
|
27
28
|
const EFFORTS = ["low", "medium", "high", "xhigh", "max"];
|
|
28
29
|
const KEY_SOURCES = ["env", "store", "prompt"];
|
|
29
30
|
const GOAL_MODES = ["implicit", "off"];
|
|
31
|
+
function coerceDurable(raw) {
|
|
32
|
+
const v = raw.trim().toLowerCase();
|
|
33
|
+
if (LAUNCHER_KEYS.includes(v))
|
|
34
|
+
return v;
|
|
35
|
+
throw new Error(`durable must be one of ${LAUNCHER_KEYS.join(", ")} (got '${raw}').`);
|
|
36
|
+
}
|
|
30
37
|
function coerceLaunch(raw) {
|
|
31
38
|
const v = raw.trim();
|
|
32
39
|
if (LAUNCH_SHAPES.includes(v))
|
|
@@ -180,6 +187,14 @@ export const CONFIG_KEYS = [
|
|
|
180
187
|
c.goalMode = coerceGoalMode(v);
|
|
181
188
|
},
|
|
182
189
|
},
|
|
190
|
+
{
|
|
191
|
+
key: "durable",
|
|
192
|
+
describe: `Durable sessions (${LAUNCHER_KEYS.join("|")}): lock launches so the host daemon runs them (experimental)`,
|
|
193
|
+
get: (c) => c.durable,
|
|
194
|
+
set: (c, v) => {
|
|
195
|
+
c.durable = coerceDurable(v);
|
|
196
|
+
},
|
|
197
|
+
},
|
|
183
198
|
{
|
|
184
199
|
key: "context",
|
|
185
200
|
describe: "Context folding (on by default; false disables it for every launch)",
|
|
@@ -330,6 +345,8 @@ export function parseLauncherConfig(text) {
|
|
|
330
345
|
cfg.keySource = coerceKeySource(str(raw.key_source) ?? "");
|
|
331
346
|
if (raw.goal_mode !== undefined)
|
|
332
347
|
cfg.goalMode = coerceGoalMode(str(raw.goal_mode) ?? "");
|
|
348
|
+
if (raw.durable !== undefined)
|
|
349
|
+
cfg.durable = coerceDurable(str(raw.durable) ?? "");
|
|
333
350
|
if (raw.context !== undefined)
|
|
334
351
|
cfg.context = coerceBool("context", str(raw.context) ?? "");
|
|
335
352
|
if (raw.slack_listen !== undefined)
|
|
@@ -414,6 +431,8 @@ export function serializeLauncherConfig(cfg) {
|
|
|
414
431
|
doc.key_source = cfg.keySource;
|
|
415
432
|
if (cfg.goalMode !== undefined)
|
|
416
433
|
doc.goal_mode = cfg.goalMode;
|
|
434
|
+
if (cfg.durable !== undefined)
|
|
435
|
+
doc.durable = cfg.durable;
|
|
417
436
|
if (cfg.context !== undefined)
|
|
418
437
|
doc.context = cfg.context;
|
|
419
438
|
if (cfg.slackChannel !== undefined)
|
|
@@ -527,5 +546,7 @@ export function withoutDisabledExperimental(cfg, enabled) {
|
|
|
527
546
|
delete out.contextHandoff;
|
|
528
547
|
if (!enabled.includes("implicit") && out.goalMode === "implicit")
|
|
529
548
|
delete out.goalMode;
|
|
549
|
+
if (!enabled.includes("durable"))
|
|
550
|
+
delete out.durable;
|
|
530
551
|
return out;
|
|
531
552
|
}
|
|
@@ -66,7 +66,7 @@ export declare function buildEmailReport(result: ScanResult, o: {
|
|
|
66
66
|
}): EmailReport;
|
|
67
67
|
export declare function buildEmailBody(report: EmailReport, level: ShareLevel, complete: boolean, ctoEmail?: string): ReportEmailBody;
|
|
68
68
|
/** Closed set of failure names; each has fixed text. */
|
|
69
|
-
export type EmailFailure = "presend_check_failed" | "email_not_sent" | "not_signed_in" | "not_a_member" | "report_rejected" | "no_recipients" | "network_error" | "unexpected_response";
|
|
69
|
+
export type EmailFailure = "presend_check_failed" | "email_not_sent" | "not_signed_in" | "not_a_member" | "report_rejected" | "no_recipients" | "runner_not_emailed" | "network_error" | "unexpected_response";
|
|
70
70
|
export declare const failureMessage: (f: EmailFailure) => string;
|
|
71
71
|
export declare const DRY_RUN_MSG = "Report email is in dry-run on this environment; nothing was sent.";
|
|
72
72
|
export declare const UNAVAILABLE_MSG = "Report email is not available for this org yet; nothing was sent.";
|
|
@@ -82,6 +82,7 @@ const FAILURE_TEXT = {
|
|
|
82
82
|
not_a_member: "your account is not a member of the org passed to --org.",
|
|
83
83
|
report_rejected: "Mnemom rejected the report as malformed.",
|
|
84
84
|
no_recipients: "the server accepted the report but reported zero recipients.",
|
|
85
|
+
runner_not_emailed: "the report was not delivered to you (the person who ran the scan); it may have gone only to the CTO. Re-run the scan, and check that your account has a valid email address.",
|
|
85
86
|
network_error: "Mnemom could not be reached.",
|
|
86
87
|
unexpected_response: "Mnemom answered in an unexpected way.",
|
|
87
88
|
};
|
|
@@ -120,8 +121,12 @@ export async function sendReportEmail(orgId, body, deps = {}) {
|
|
|
120
121
|
catch (e) {
|
|
121
122
|
if (e instanceof MnemomApiError && e.effectiveStatus === 404)
|
|
122
123
|
return { status: "unavailable" };
|
|
123
|
-
if (e instanceof MnemomApiError)
|
|
124
|
+
if (e instanceof MnemomApiError) {
|
|
125
|
+
// A 4xx that names the runner (the API refuses a send that would skip the runner) gets its own text.
|
|
126
|
+
if (e.effectiveStatus >= 400 && e.effectiveStatus < 500 && /runner/i.test(e.code ?? ""))
|
|
127
|
+
return { status: "failed", reason: "runner_not_emailed" };
|
|
124
128
|
return { status: "failed", reason: classifyStatus(e.effectiveStatus) };
|
|
129
|
+
}
|
|
125
130
|
return { status: "failed", reason: "network_error" };
|
|
126
131
|
}
|
|
127
132
|
const o = typeof res === "object" && res !== null ? res : null;
|
|
@@ -132,5 +137,8 @@ export async function sendReportEmail(orgId, body, deps = {}) {
|
|
|
132
137
|
return { status: "failed", reason: "unexpected_response" };
|
|
133
138
|
if (recipients === 0)
|
|
134
139
|
return { status: "failed", reason: "no_recipients" };
|
|
140
|
+
// `skipped` lists the roles ("runner", "cto") that got no copy. The runner must always get the report.
|
|
141
|
+
if (Array.isArray(o.skipped) && o.skipped.includes("runner"))
|
|
142
|
+
return { status: "failed", reason: "runner_not_emailed" };
|
|
135
143
|
return o.status === "dry_run" ? { status: "dry_run" } : { status: "sent", recipients };
|
|
136
144
|
}
|
|
@@ -79,6 +79,21 @@ export declare function resolveSubscriptionMnemomKey(deps: MnemomKeyDeps, purpos
|
|
|
79
79
|
* a non-entitled account to terminal; this is the fail-closed backstop.)
|
|
80
80
|
*/
|
|
81
81
|
export declare function subscriptionShape(shape: LaunchShape, shapeExplicit: boolean, rcAllowed: boolean, say: (line: string) => void): LaunchShape;
|
|
82
|
+
/**
|
|
83
|
+
* The coding agent's env for either auth arm: what `mnemom agent` launches claude with, and what a
|
|
84
|
+
* durable seat launches its claude with (host/seat-launch.ts, dogfood B6). With an Anthropic key:
|
|
85
|
+
* Claude Code logged in via claude.ai (OAuth) PREEMPTS ANTHROPIC_API_KEY, so it is force-unset;
|
|
86
|
+
* the real key rides in ANTHROPIC_CUSTOM_HEADERS (`headers`) and ANTHROPIC_AUTH_TOKEN is a fixed
|
|
87
|
+
* non-secret placeholder. Without one (a subscription launch) NO auth token is set: Claude Code
|
|
88
|
+
* then sends its own claude.ai login. Either way the owner's own Mnemom credential env is
|
|
89
|
+
* stripped (MNE-8245), as is MNEMOM_AGENT_ANTHROPIC_KEY (the key reaches the child only in the
|
|
90
|
+
* headers: C14, B6), and Claude Code's gateway hint headers are on (a value the owner set wins):
|
|
91
|
+
* the seat proxy chains each turn by x-claude-code-prompt-id, and without it a seat's first typed
|
|
92
|
+
* turn chained to the launch prompt (dogfood B8). Set here so the two callers cannot drift apart.
|
|
93
|
+
*/
|
|
94
|
+
export declare function launchChildEnv(base: NodeJS.ProcessEnv, door: string, headers: string, anthropicKey: string | undefined): NodeJS.ProcessEnv;
|
|
95
|
+
/** The fixed, non-secret ANTHROPIC_AUTH_TOKEN of an api-key launch (the key rides in a header). */
|
|
96
|
+
export declare const GATEWAY_AUTH_PLACEHOLDER = "mnemom-gateway";
|
|
82
97
|
/**
|
|
83
98
|
* The child env for a subscription launch: the gateway door, the Mnemom headers, and
|
|
84
99
|
* NOTHING that would make Claude Code send a key instead of its claude.ai login.
|