@mnemom/mnemom 0.17.33 → 0.17.34-next.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/base-url-proxy.mjs +18 -4
- package/dist/commands/agent-config.js +5 -1
- package/dist/commands/agent-doctor.d.ts +9 -0
- package/dist/commands/agent-doctor.js +4 -0
- package/dist/commands/agent-session.d.ts +1 -2
- package/dist/commands/agent-session.js +1 -2
- package/dist/commands/agent-setup.d.ts +6 -0
- package/dist/commands/agent-setup.js +12 -1
- package/dist/commands/agent-share-render.d.ts +1 -1
- package/dist/commands/agent-share-render.js +1 -1
- package/dist/commands/agent.d.ts +26 -0
- package/dist/commands/agent.js +228 -53
- package/dist/commands/durable-doctor.d.ts +50 -0
- package/dist/commands/durable-doctor.js +186 -0
- package/dist/commands/durable-lock.d.ts +254 -0
- package/dist/commands/durable-lock.js +595 -0
- package/dist/commands/durable-sessions.d.ts +87 -0
- package/dist/commands/durable-sessions.js +303 -0
- package/dist/commands/durable-verbs.d.ts +116 -0
- package/dist/commands/durable-verbs.js +393 -0
- package/dist/commands/protection.js +1 -1
- package/dist/commands/scan.d.ts +97 -0
- package/dist/commands/scan.js +238 -0
- package/dist/durable/flock.d.ts +26 -0
- package/dist/durable/flock.js +80 -0
- package/dist/durable/launch-spec.d.ts +7 -1
- package/dist/durable/launch-spec.js +10 -3
- package/dist/durable/seat-proxy-entry.d.ts +11 -0
- package/dist/durable/seat-proxy-entry.js +25 -0
- package/dist/durable/seat-proxy.d.ts +107 -0
- package/dist/durable/seat-proxy.js +599 -0
- package/dist/durable/seat-socket.d.ts +46 -0
- package/dist/durable/seat-socket.js +179 -0
- package/dist/durable/seat-spec-validator.d.ts +28 -0
- package/dist/durable/seat-spec-validator.js +59 -0
- package/dist/durable/test-mode.mjs +81 -0
- package/dist/durable/turn-chain.d.ts +55 -0
- package/dist/durable/turn-chain.js +178 -0
- package/dist/host/auto-continue.d.ts +67 -0
- package/dist/host/auto-continue.js +118 -0
- package/dist/host/bootid.d.ts +13 -0
- package/dist/host/bootid.js +41 -0
- package/dist/host/client.d.ts +123 -0
- package/dist/host/client.js +171 -0
- package/dist/host/commands.d.ts +2 -0
- package/dist/host/commands.js +122 -0
- package/dist/host/control.d.ts +33 -0
- package/dist/host/control.js +144 -0
- package/dist/host/daemon.d.ts +217 -0
- package/dist/host/daemon.js +606 -0
- package/dist/host/gate.d.ts +46 -0
- package/dist/host/gate.js +82 -0
- package/dist/host/grant.d.ts +24 -0
- package/dist/host/grant.js +24 -0
- package/dist/host/instance.d.ts +35 -0
- package/dist/host/instance.js +62 -0
- package/dist/host/integrity.d.ts +35 -0
- package/dist/host/integrity.js +115 -0
- package/dist/host/launchd.d.ts +16 -0
- package/dist/host/launchd.js +45 -0
- package/dist/host/lock.d.ts +20 -0
- package/dist/host/lock.js +63 -0
- package/dist/host/owner.d.ts +55 -0
- package/dist/host/owner.js +121 -0
- package/dist/host/plist.d.ts +16 -0
- package/dist/host/plist.js +85 -0
- package/dist/host/procs.d.ts +27 -0
- package/dist/host/procs.js +105 -0
- package/dist/host/run.d.ts +66 -0
- package/dist/host/run.js +314 -0
- package/dist/host/runtime.d.ts +8 -0
- package/dist/host/runtime.js +22 -0
- package/dist/host/s1-names.d.ts +22 -0
- package/dist/host/s1-names.js +21 -0
- package/dist/host/seat-credential.d.ts +27 -0
- package/dist/host/seat-credential.js +41 -0
- package/dist/host/seat-launch-main.d.ts +1 -0
- package/dist/host/seat-launch-main.js +59 -0
- package/dist/host/seat-launch.d.ts +116 -0
- package/dist/host/seat-launch.js +302 -0
- package/dist/host/seat.d.ts +48 -0
- package/dist/host/seat.js +149 -0
- package/dist/host/state.d.ts +33 -0
- package/dist/host/state.js +117 -0
- package/dist/host/status.d.ts +101 -0
- package/dist/host/status.js +307 -0
- package/dist/host/tmux.d.ts +56 -0
- package/dist/host/tmux.js +182 -0
- package/dist/host/transcript.d.ts +30 -0
- package/dist/host/transcript.js +130 -0
- package/dist/host/verbs.d.ts +50 -0
- package/dist/host/verbs.js +271 -0
- package/dist/index.d.ts +8 -6
- package/dist/index.js +232 -35
- package/dist/lib/agent.d.ts +8 -26
- package/dist/lib/agent.js +11 -26
- package/dist/lib/api.d.ts +13 -0
- package/dist/lib/api.js +8 -0
- package/dist/lib/claude-plugin.d.ts +11 -2
- package/dist/lib/claude-plugin.js +10 -1
- package/dist/lib/durable/api.d.ts +136 -0
- package/dist/lib/durable/api.js +220 -0
- package/dist/lib/durable/codes.d.ts +8 -0
- package/dist/lib/durable/codes.js +2 -0
- package/dist/lib/durable/first-turn.d.ts +50 -0
- package/dist/lib/durable/first-turn.js +102 -0
- package/dist/lib/durable/gate.d.ts +48 -0
- package/dist/lib/durable/gate.js +122 -0
- package/dist/lib/durable/handover.d.ts +101 -0
- package/dist/lib/durable/handover.js +175 -0
- package/dist/lib/durable/launch.d.ts +182 -0
- package/dist/lib/durable/launch.js +443 -0
- package/dist/lib/durable/launching.d.ts +15 -0
- package/dist/lib/durable/launching.js +70 -0
- package/dist/lib/durable/live.d.ts +194 -0
- package/dist/lib/durable/live.js +635 -0
- package/dist/lib/durable/refusal.d.ts +57 -0
- package/dist/lib/durable/refusal.js +78 -0
- package/dist/lib/durable/view.d.ts +83 -0
- package/dist/lib/durable/view.js +244 -0
- package/dist/lib/experimental.d.ts +0 -9
- package/dist/lib/experimental.js +18 -17
- package/dist/lib/launcher-config.d.ts +7 -0
- package/dist/lib/launcher-config.js +21 -0
- package/dist/lib/scan/caps.d.ts +35 -0
- package/dist/lib/scan/caps.js +67 -0
- package/dist/lib/scan/credential.d.ts +37 -0
- package/dist/lib/scan/credential.js +104 -0
- package/dist/lib/scan/failure.d.ts +10 -0
- package/dist/lib/scan/failure.js +43 -0
- package/dist/lib/scan/pr-source.d.ts +57 -0
- package/dist/lib/scan/pr-source.js +130 -0
- package/dist/lib/scan/record.d.ts +49 -0
- package/dist/lib/scan/record.js +85 -0
- package/dist/lib/scan/report-email.d.ts +92 -0
- package/dist/lib/scan/report-email.js +144 -0
- package/dist/lib/scan/report.d.ts +13 -0
- package/dist/lib/scan/report.js +30 -0
- package/dist/lib/scan/scan.d.ts +64 -0
- package/dist/lib/scan/scan.js +182 -0
- package/dist/lib/scan/share.d.ts +38 -0
- package/dist/lib/scan/share.js +47 -0
- package/dist/lib/scan/spend/ai-tool-detect.d.ts +6 -0
- package/dist/lib/scan/spend/ai-tool-detect.js +18 -0
- package/dist/lib/scan/spend/anthropic-usage.d.ts +57 -0
- package/dist/lib/scan/spend/anthropic-usage.js +244 -0
- package/dist/lib/scan/spend/cost-corpus.v1.d.ts +56 -0
- package/dist/lib/scan/spend/cost-corpus.v1.js +64 -0
- package/dist/lib/scan/spend/prices.d.ts +33 -0
- package/dist/lib/scan/spend/prices.js +57 -0
- package/dist/lib/scan/spend/spend-estimate.d.ts +30 -0
- package/dist/lib/scan/spend/spend-estimate.js +70 -0
- package/dist/lib/scan/spend/spend-tiers.d.ts +25 -0
- package/dist/lib/scan/spend/spend-tiers.js +42 -0
- package/dist/lib/scan/spend/token-rate.d.ts +30 -0
- package/dist/lib/scan/spend/token-rate.js +54 -0
- package/dist/lib/session-key.d.ts +41 -0
- package/dist/lib/session-key.js +124 -0
- package/dist/lib/subscription-login.d.ts +15 -0
- package/dist/lib/subscription-login.js +28 -1
- package/dist/rc-key-401.mjs +75 -0
- package/dist/rc-proxy.mjs +221 -25
- package/dist/vendor/durable-contract/index.js +240 -18
- package/dist/vendor/shared/durable-contract/admission.d.ts +240 -0
- package/dist/vendor/shared/durable-contract/admission.js +533 -0
- package/dist/vendor/shared/durable-contract/arm.d.ts +51 -0
- package/dist/vendor/shared/durable-contract/arm.js +133 -0
- package/dist/vendor/shared/durable-contract/canonical.d.ts +29 -0
- package/dist/vendor/shared/durable-contract/canonical.js +98 -0
- package/dist/vendor/shared/durable-contract/cell-id.d.ts +22 -0
- package/dist/vendor/shared/durable-contract/cell-id.js +38 -0
- package/dist/vendor/shared/durable-contract/cli-output.d.ts +342 -0
- package/dist/vendor/shared/durable-contract/cli-output.js +201 -0
- package/dist/vendor/shared/durable-contract/cli-surface.d.ts +119 -0
- package/dist/vendor/shared/durable-contract/cli-surface.js +270 -0
- package/dist/vendor/shared/durable-contract/codes.d.ts +537 -0
- package/dist/vendor/shared/durable-contract/codes.js +475 -0
- package/dist/vendor/shared/durable-contract/constants.d.ts +153 -0
- package/dist/vendor/shared/durable-contract/constants.js +179 -0
- package/dist/vendor/shared/durable-contract/doors.d.ts +119 -0
- package/dist/vendor/shared/durable-contract/doors.js +249 -0
- package/dist/vendor/shared/durable-contract/envelope.d.ts +103 -0
- package/dist/vendor/shared/durable-contract/envelope.js +317 -0
- package/dist/vendor/shared/durable-contract/events.d.ts +147 -0
- package/dist/vendor/shared/durable-contract/events.js +630 -0
- package/dist/vendor/shared/durable-contract/executor-state.d.ts +41 -0
- package/dist/vendor/shared/durable-contract/executor-state.js +32 -0
- package/dist/vendor/shared/durable-contract/fixlines.d.ts +318 -0
- package/dist/vendor/shared/durable-contract/fixlines.js +483 -0
- package/dist/vendor/shared/durable-contract/freeze-record.d.ts +787 -0
- package/dist/vendor/shared/durable-contract/freeze-record.js +361 -0
- package/dist/vendor/shared/durable-contract/index.d.ts +36 -0
- package/dist/vendor/shared/durable-contract/index.js +37 -0
- package/dist/vendor/shared/durable-contract/journal-approvals.d.ts +29 -0
- package/dist/vendor/shared/durable-contract/journal-approvals.js +60 -0
- package/dist/vendor/shared/durable-contract/keys.d.ts +24 -0
- package/dist/vendor/shared/durable-contract/keys.js +66 -0
- package/dist/vendor/shared/durable-contract/lock.d.ts +128 -0
- package/dist/vendor/shared/durable-contract/lock.js +151 -0
- package/dist/vendor/shared/durable-contract/names.d.ts +25 -0
- package/dist/vendor/shared/durable-contract/names.js +78 -0
- package/dist/vendor/shared/durable-contract/owner-verbs.d.ts +175 -0
- package/dist/vendor/shared/durable-contract/owner-verbs.js +403 -0
- package/dist/vendor/shared/durable-contract/paths.d.ts +23 -0
- package/dist/vendor/shared/durable-contract/paths.js +35 -0
- package/dist/vendor/shared/durable-contract/pure-fn.d.ts +48 -0
- package/dist/vendor/shared/durable-contract/pure-fn.js +89 -0
- package/dist/vendor/shared/durable-contract/routes.d.ts +205 -0
- package/dist/vendor/shared/durable-contract/routes.js +315 -0
- package/dist/vendor/shared/durable-contract/schema.d.ts +126 -0
- package/dist/vendor/shared/durable-contract/schema.js +441 -0
- package/dist/vendor/shared/durable-contract/seal.d.ts +54 -0
- package/dist/vendor/shared/durable-contract/seal.js +99 -0
- package/dist/vendor/shared/durable-contract/secret-scope.d.ts +28 -0
- package/dist/vendor/shared/durable-contract/secret-scope.js +76 -0
- package/dist/vendor/shared/durable-contract/session-id.d.ts +21 -0
- package/dist/vendor/shared/durable-contract/session-id.js +58 -0
- package/dist/vendor/shared/durable-contract/spec.d.ts +162 -0
- package/dist/vendor/shared/durable-contract/spec.js +286 -0
- package/dist/vendor/shared/durable-contract/states.d.ts +96 -0
- package/dist/vendor/shared/durable-contract/states.js +218 -0
- package/dist/vendor/shared/durable-contract/telemetry.d.ts +94 -0
- package/dist/vendor/shared/durable-contract/telemetry.js +180 -0
- package/dist/vendor/shared/durable-contract/test-mode.d.ts +62 -0
- package/dist/vendor/shared/durable-contract/test-mode.js +140 -0
- package/dist/vendor/shared/durable-contract/turn.d.ts +57 -0
- package/dist/vendor/shared/durable-contract/turn.js +81 -0
- package/dist/vendor/shared/durable-contract/vectors.d.ts +191 -0
- package/dist/vendor/shared/durable-contract/vectors.js +208 -0
- package/package.json +4 -3
- package/plugins/integrity-manifest.json +35 -0
- package/plugins/mnemom/hooks/hooks.json +15 -1
- package/plugins/mnemom/hooks/register.tsx +60 -2
- package/plugins/mnemom/hooks/seat-hello.mjs +116 -0
- package/dist/commands/agent-implicit-notice.d.ts +0 -31
- package/dist/commands/agent-implicit-notice.js +0 -73
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The transcript guard (C12-R16, R38; BACKBONE C2 "Transcript guard"). For every locked,
|
|
3
|
+
* non-terminal session homed here the daemon keeps a hard link to the Claude Code transcript at
|
|
4
|
+
* the S1 guard path, so Claude Code's own cleanup cannot lose it. On EXDEV it keeps a copy.
|
|
5
|
+
* Before every `--resume` a missing original is restored from the guard. It never writes any
|
|
6
|
+
* Claude settings file.
|
|
7
|
+
*/
|
|
8
|
+
import * as fs from "node:fs";
|
|
9
|
+
import * as path from "node:path";
|
|
10
|
+
import * as os from "node:os";
|
|
11
|
+
import { transcriptGuardPath } from "../vendor/shared/durable-contract/paths.js";
|
|
12
|
+
export function ccProjectsDir(env, home = os.homedir()) {
|
|
13
|
+
return path.join((env.CLAUDE_CONFIG_DIR ?? "").trim() || path.join(home, ".claude"), "projects");
|
|
14
|
+
}
|
|
15
|
+
/** Claude Code's project folder name for a cwd: every non-alphanumeric character becomes '-'. */
|
|
16
|
+
export const projectSlug = (cwd) => cwd.replace(/[^a-zA-Z0-9]/g, "-");
|
|
17
|
+
export function expectedTranscriptPath(projectsDir, cwd, ccSessionId) {
|
|
18
|
+
return path.join(projectsDir, projectSlug(cwd), `${ccSessionId}.jsonl`);
|
|
19
|
+
}
|
|
20
|
+
/** The transcript as it is on disk now: the cwd's project folder first, then any project folder. */
|
|
21
|
+
export function findTranscript(projectsDir, cwd, ccSessionId) {
|
|
22
|
+
if (cwd) {
|
|
23
|
+
const p = expectedTranscriptPath(projectsDir, cwd, ccSessionId);
|
|
24
|
+
if (fs.existsSync(p))
|
|
25
|
+
return p;
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
for (const d of fs.readdirSync(projectsDir)) {
|
|
29
|
+
const p = path.join(projectsDir, d, `${ccSessionId}.jsonl`);
|
|
30
|
+
if (fs.existsSync(p))
|
|
31
|
+
return p;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
/* no projects dir */
|
|
36
|
+
}
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
/** Create or refresh the guard for one session. Returns the mode now held (null when there is nothing to guard). */
|
|
40
|
+
export function refreshGuard(home, sessionId, original) {
|
|
41
|
+
const guard = transcriptGuardPath(home, sessionId);
|
|
42
|
+
if (!original || !fs.existsSync(original)) {
|
|
43
|
+
return { mode: fs.existsSync(guard) ? currentMode(guard) : null, guardPath: guard };
|
|
44
|
+
}
|
|
45
|
+
fs.mkdirSync(path.dirname(guard), { recursive: true, mode: 0o700 });
|
|
46
|
+
const o = fs.statSync(original);
|
|
47
|
+
let g;
|
|
48
|
+
try {
|
|
49
|
+
g = fs.statSync(guard);
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
g = null;
|
|
53
|
+
}
|
|
54
|
+
if (g && g.ino === o.ino && g.dev === o.dev)
|
|
55
|
+
return { mode: "link", guardPath: guard };
|
|
56
|
+
// A copy is current while size and mtime match (EXDEV mode).
|
|
57
|
+
if (g &&
|
|
58
|
+
g.nlink === 1 &&
|
|
59
|
+
o.dev !== g.dev &&
|
|
60
|
+
g.size === o.size &&
|
|
61
|
+
Math.trunc(g.mtimeMs) === Math.trunc(o.mtimeMs)) {
|
|
62
|
+
return { mode: "copy", guardPath: guard };
|
|
63
|
+
}
|
|
64
|
+
const tmp = `${guard}.${process.pid}.tmp`;
|
|
65
|
+
try {
|
|
66
|
+
fs.rmSync(tmp, { force: true });
|
|
67
|
+
fs.linkSync(original, tmp);
|
|
68
|
+
fs.renameSync(tmp, guard);
|
|
69
|
+
return { mode: "link", guardPath: guard };
|
|
70
|
+
}
|
|
71
|
+
catch (e) {
|
|
72
|
+
fs.rmSync(tmp, { force: true });
|
|
73
|
+
if (e.code !== "EXDEV")
|
|
74
|
+
throw e;
|
|
75
|
+
}
|
|
76
|
+
fs.copyFileSync(original, tmp);
|
|
77
|
+
fs.chmodSync(tmp, 0o600);
|
|
78
|
+
fs.utimesSync(tmp, o.atime, o.mtime);
|
|
79
|
+
fs.renameSync(tmp, guard);
|
|
80
|
+
return { mode: "copy", guardPath: guard };
|
|
81
|
+
}
|
|
82
|
+
function currentMode(guard) {
|
|
83
|
+
try {
|
|
84
|
+
return fs.statSync(guard).nlink > 1 ? "link" : "copy";
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
return "link";
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Before a `--resume`: the original exists, or it is restored from the guard (a new hard link,
|
|
92
|
+
* or a copy across volumes), or both are missing (transcript_lost).
|
|
93
|
+
*/
|
|
94
|
+
export function ensureTranscript(home, sessionId, projectsDir, cwd, ccSessionId, lastKnown) {
|
|
95
|
+
const found = findTranscript(projectsDir, cwd, ccSessionId);
|
|
96
|
+
if (found)
|
|
97
|
+
return { kind: "present", path: found };
|
|
98
|
+
const guard = transcriptGuardPath(home, sessionId);
|
|
99
|
+
if (!fs.existsSync(guard))
|
|
100
|
+
return { kind: "lost" };
|
|
101
|
+
const target = lastKnown ?? (cwd ? expectedTranscriptPath(projectsDir, cwd, ccSessionId) : null);
|
|
102
|
+
if (!target)
|
|
103
|
+
return { kind: "lost" };
|
|
104
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
105
|
+
try {
|
|
106
|
+
fs.linkSync(guard, target);
|
|
107
|
+
}
|
|
108
|
+
catch (e) {
|
|
109
|
+
if (e.code !== "EXDEV")
|
|
110
|
+
throw e;
|
|
111
|
+
fs.copyFileSync(guard, target);
|
|
112
|
+
}
|
|
113
|
+
return { kind: "restored", path: target };
|
|
114
|
+
}
|
|
115
|
+
export function removeGuard(home, sessionId) {
|
|
116
|
+
fs.rmSync(transcriptGuardPath(home, sessionId), { force: true });
|
|
117
|
+
}
|
|
118
|
+
/** Session ids that have a guard file. */
|
|
119
|
+
export function guardedSessionIds(home) {
|
|
120
|
+
const dir = path.dirname(transcriptGuardPath(home, "00000000-0000-0000-0000-000000000000"));
|
|
121
|
+
try {
|
|
122
|
+
return fs
|
|
123
|
+
.readdirSync(dir)
|
|
124
|
+
.filter((f) => f.endsWith(".jsonl"))
|
|
125
|
+
.map((f) => f.slice(0, -6));
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
return [];
|
|
129
|
+
}
|
|
130
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { type LockResult } from "./lock.js";
|
|
2
|
+
import { type Launchctl } from "./launchd.js";
|
|
3
|
+
import { type OwnerApi, type ExecutorRow } from "./owner.js";
|
|
4
|
+
export declare const INSTALL_FIX = "`mnemom agent host install`";
|
|
5
|
+
export declare const UNINSTALL_SESSION_LINE: (name: string) => string;
|
|
6
|
+
export interface VerbIo {
|
|
7
|
+
readonly env: Readonly<Record<string, string | undefined>>;
|
|
8
|
+
readonly home: string;
|
|
9
|
+
readonly uid: number;
|
|
10
|
+
readonly json: boolean;
|
|
11
|
+
readonly out: (line: string) => void;
|
|
12
|
+
readonly err: (line: string) => void;
|
|
13
|
+
readonly owner: OwnerApi;
|
|
14
|
+
readonly launchctl: Launchctl;
|
|
15
|
+
readonly now: () => number;
|
|
16
|
+
readonly bootId: () => string;
|
|
17
|
+
/** The daemon's ProgramArguments (absolute node + CLI entry + agent host run). */
|
|
18
|
+
readonly programArguments: () => string[];
|
|
19
|
+
readonly claudeBin: () => string | null;
|
|
20
|
+
readonly pollSleep?: (ms: number) => Promise<void>;
|
|
21
|
+
/** Takes the enrollment lock; acquireHostLock (macOS flock) when absent. */
|
|
22
|
+
readonly acquireLock?: (lockFile: string) => LockResult;
|
|
23
|
+
}
|
|
24
|
+
/** Fresh-invocation gate for every host verb (C12-R25): exit 2 with the gated text, nothing else. */
|
|
25
|
+
export declare function freshGate(io: VerbIo): number | null;
|
|
26
|
+
export type Liveness = {
|
|
27
|
+
kind: "live";
|
|
28
|
+
row: ExecutorRow;
|
|
29
|
+
} | {
|
|
30
|
+
kind: "lapsed";
|
|
31
|
+
row: ExecutorRow;
|
|
32
|
+
} | {
|
|
33
|
+
kind: "gone";
|
|
34
|
+
} | {
|
|
35
|
+
kind: "unknown";
|
|
36
|
+
why: string;
|
|
37
|
+
};
|
|
38
|
+
export declare function executorLiveness(io: VerbIo, executorId: string): Promise<Liveness>;
|
|
39
|
+
/** The S1 risk line (BACKBONE amendment 27): human text, or a JSON event in --json mode. Recorded nowhere. */
|
|
40
|
+
export declare function printRiskLine(io: VerbIo): void;
|
|
41
|
+
/** The device step: start, show the code, poll, write the state file. Returns an exit code. */
|
|
42
|
+
export declare function enrollDevice(io: VerbIo, opts?: {
|
|
43
|
+
label?: string;
|
|
44
|
+
}): Promise<number>;
|
|
45
|
+
/** Write the plist and (re)load the job. */
|
|
46
|
+
export declare function writeAndLoad(io: VerbIo): Promise<number>;
|
|
47
|
+
export declare function hostInstall(io: VerbIo): Promise<number>;
|
|
48
|
+
export declare function hostEnroll(io: VerbIo): Promise<number>;
|
|
49
|
+
export declare function hostRenew(io: VerbIo): Promise<number>;
|
|
50
|
+
export declare function hostUninstall(io: VerbIo): Promise<number>;
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `mnemom agent host install | enroll | renew | uninstall` (C04, C12-R22, R24, R40; BACKBONE C2).
|
|
3
|
+
*
|
|
4
|
+
* install: gate (config.json only, R40) -> a live enrollment in the state file reloads the job
|
|
5
|
+
* (no device step, C04-R19); a lapsed grant prints the renew fix and stops; a missing state file
|
|
6
|
+
* or a revoked executor enrolls through the device step. Then the plist is written and the job
|
|
7
|
+
* (re)loaded. The S1 risk line is printed and recorded nowhere locally.
|
|
8
|
+
* enroll: the device step alone (writes the state file), under its own lock so a second
|
|
9
|
+
* concurrent enroll starts nothing (C04-CH-FLOCK).
|
|
10
|
+
*/
|
|
11
|
+
import * as fs from "node:fs";
|
|
12
|
+
import * as path from "node:path";
|
|
13
|
+
import { setTimeout as sleep } from "node:timers/promises";
|
|
14
|
+
import { RISK_LINE_TEXT } from "../vendor/shared/durable-contract/fixlines.js";
|
|
15
|
+
import { EXECUTOR_STATE_VERSION } from "../vendor/shared/durable-contract/executor-state.js";
|
|
16
|
+
import { TERMINAL_STATES } from "../vendor/shared/durable-contract/states.js";
|
|
17
|
+
import { RISK_LINE_VERSION } from "../vendor/shared/durable-contract/constants.js";
|
|
18
|
+
import { resolveInstance, hostPaths } from "./instance.js";
|
|
19
|
+
import { installGate, durableOnFresh, GATED_TEXT } from "./gate.js";
|
|
20
|
+
import { readExecutorState, writeExecutorState, newNonce } from "./state.js";
|
|
21
|
+
import { acquireHostLock } from "./lock.js";
|
|
22
|
+
import { renderPlist } from "./plist.js";
|
|
23
|
+
import { loadJob, unloadJob } from "./launchd.js";
|
|
24
|
+
import { classifyPoll, parseEnrollStart, parseExecutorList, EXECUTOR_CONTRACT, } from "./owner.js";
|
|
25
|
+
import { readRuntime } from "./runtime.js";
|
|
26
|
+
import { RENEW_FIX, GRANT_EXPIRED_LINE } from "./grant.js";
|
|
27
|
+
export const INSTALL_FIX = "`mnemom agent host install`";
|
|
28
|
+
export const UNINSTALL_SESSION_LINE = (name) => `${name}: they stop restarting until ${INSTALL_FIX}`;
|
|
29
|
+
const ctx = (io) => {
|
|
30
|
+
const instance = resolveInstance(io.env);
|
|
31
|
+
return { instance, paths: hostPaths(io.home, instance) };
|
|
32
|
+
};
|
|
33
|
+
/** Fresh-invocation gate for every host verb (C12-R25): exit 2 with the gated text, nothing else. */
|
|
34
|
+
export function freshGate(io) {
|
|
35
|
+
const { paths } = ctx(io);
|
|
36
|
+
if (durableOnFresh(paths.configFile, io.env))
|
|
37
|
+
return null;
|
|
38
|
+
io.err(GATED_TEXT);
|
|
39
|
+
return 2;
|
|
40
|
+
}
|
|
41
|
+
export async function executorLiveness(io, executorId) {
|
|
42
|
+
const r = await io.owner.list();
|
|
43
|
+
if (r.status !== 200)
|
|
44
|
+
return { kind: "unknown", why: r.networkError ?? `http ${r.status}` };
|
|
45
|
+
const rows = parseExecutorList(r.body);
|
|
46
|
+
if (!rows)
|
|
47
|
+
return { kind: "unknown", why: "unreadable executor list" };
|
|
48
|
+
const row = rows.find((x) => x.id === executorId.toLowerCase());
|
|
49
|
+
if (!row || row.revoked)
|
|
50
|
+
return { kind: "gone" };
|
|
51
|
+
if (row.grantExpiresAt !== null && row.grantExpiresAt <= io.now())
|
|
52
|
+
return { kind: "lapsed", row };
|
|
53
|
+
return { kind: "live", row };
|
|
54
|
+
}
|
|
55
|
+
/** The S1 risk line (BACKBONE amendment 27): human text, or a JSON event in --json mode. Recorded nowhere. */
|
|
56
|
+
export function printRiskLine(io) {
|
|
57
|
+
if (io.json)
|
|
58
|
+
io.out(JSON.stringify({ event: "risk_line", text: RISK_LINE_TEXT, version: RISK_LINE_VERSION }));
|
|
59
|
+
else
|
|
60
|
+
io.out(RISK_LINE_TEXT);
|
|
61
|
+
}
|
|
62
|
+
/** The device step: start, show the code, poll, write the state file. Returns an exit code. */
|
|
63
|
+
export async function enrollDevice(io, opts = {}) {
|
|
64
|
+
const { paths } = ctx(io);
|
|
65
|
+
const lock = (io.acquireLock ?? acquireHostLock)(path.join(paths.durableDir, "enroll.lock"));
|
|
66
|
+
if (!lock.ok) {
|
|
67
|
+
io.err(`mnemom agent host: an enrollment is already in progress here (${lock.reason}): wait for it, then ${INSTALL_FIX}`);
|
|
68
|
+
return 1;
|
|
69
|
+
}
|
|
70
|
+
try {
|
|
71
|
+
const st = await io.owner.enrollStart({
|
|
72
|
+
kind: "host",
|
|
73
|
+
runtime: "node",
|
|
74
|
+
contract_version: EXECUTOR_CONTRACT,
|
|
75
|
+
...(opts.label ? { machine_label: opts.label } : {}),
|
|
76
|
+
});
|
|
77
|
+
const start = st.status === 202 || st.status === 200 || st.status === 201
|
|
78
|
+
? parseEnrollStart(st.body)
|
|
79
|
+
: null;
|
|
80
|
+
if (!start) {
|
|
81
|
+
const code = st.body && typeof st.body === "object"
|
|
82
|
+
? JSON.stringify(st.body).slice(0, 300)
|
|
83
|
+
: String(st.status);
|
|
84
|
+
io.err(`could not start the enrollment (${st.networkError ?? code}). Sign in with \`mnemom login\`, then ${INSTALL_FIX}`);
|
|
85
|
+
return 1;
|
|
86
|
+
}
|
|
87
|
+
printRiskLine(io);
|
|
88
|
+
if (io.json)
|
|
89
|
+
io.out(JSON.stringify({
|
|
90
|
+
event: "enroll_started",
|
|
91
|
+
user_code: start.userCode,
|
|
92
|
+
verification_uri_complete: start.verificationUri,
|
|
93
|
+
expires_in: start.expiresIn,
|
|
94
|
+
}));
|
|
95
|
+
else {
|
|
96
|
+
io.out(`Approve this machine in your browser: ${start.verificationUri}`);
|
|
97
|
+
io.out(`Code: ${start.userCode}`);
|
|
98
|
+
}
|
|
99
|
+
const nap = io.pollSleep ?? ((ms) => sleep(ms));
|
|
100
|
+
let interval = start.interval * 1000;
|
|
101
|
+
const until = io.now() + start.expiresIn * 1000 + 5000;
|
|
102
|
+
while (io.now() < until) {
|
|
103
|
+
await nap(interval);
|
|
104
|
+
const p = classifyPoll(await io.owner.enrollPoll(start.pollSecret, start.approvalId));
|
|
105
|
+
if (p.kind === "pending")
|
|
106
|
+
continue;
|
|
107
|
+
if (p.kind === "slow_down") {
|
|
108
|
+
interval += 5000;
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
if (p.kind === "terminal") {
|
|
112
|
+
io.err(`enrollment ${p.error.replace(/_/g, " ")}: run ${INSTALL_FIX} again`);
|
|
113
|
+
return 1;
|
|
114
|
+
}
|
|
115
|
+
writeExecutorState(paths.stateFile, {
|
|
116
|
+
version: EXECUTOR_STATE_VERSION,
|
|
117
|
+
executor_id: p.executorId,
|
|
118
|
+
executor_key: p.executorKey,
|
|
119
|
+
nonce: newNonce(),
|
|
120
|
+
boot_id: io.bootId(),
|
|
121
|
+
});
|
|
122
|
+
if (io.json)
|
|
123
|
+
io.out(JSON.stringify({ event: "enrolled", executor_id: p.executorId }));
|
|
124
|
+
else
|
|
125
|
+
io.out(`Enrolled this machine (executor ${p.executorId.slice(0, 8)}).`);
|
|
126
|
+
return 0;
|
|
127
|
+
}
|
|
128
|
+
io.err(`enrollment expired_token: run ${INSTALL_FIX} again`);
|
|
129
|
+
return 1;
|
|
130
|
+
}
|
|
131
|
+
finally {
|
|
132
|
+
fs.closeSync(lock.fd);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
/** Write the plist and (re)load the job. */
|
|
136
|
+
export async function writeAndLoad(io) {
|
|
137
|
+
const { instance, paths } = ctx(io);
|
|
138
|
+
const claude = io.claudeBin();
|
|
139
|
+
if (!claude) {
|
|
140
|
+
io.err("claude was not found on PATH: install Claude Code, or set MNEMOM_AGENT_CLI to its absolute path");
|
|
141
|
+
return 1;
|
|
142
|
+
}
|
|
143
|
+
fs.mkdirSync(paths.logDir, { recursive: true, mode: 0o700 });
|
|
144
|
+
fs.mkdirSync(path.dirname(paths.plistFile), { recursive: true });
|
|
145
|
+
const text = renderPlist({
|
|
146
|
+
label: instance.label,
|
|
147
|
+
programArguments: io.programArguments(),
|
|
148
|
+
home: io.home,
|
|
149
|
+
path: io.env.PATH ?? "/usr/bin:/bin:/usr/sbin:/sbin",
|
|
150
|
+
claudeBin: claude,
|
|
151
|
+
logFile: path.join(paths.logDir, "daemon.log"),
|
|
152
|
+
env: io.env,
|
|
153
|
+
});
|
|
154
|
+
const tmp = `${paths.plistFile}.${process.pid}.tmp`;
|
|
155
|
+
fs.writeFileSync(tmp, text, { mode: 0o644 });
|
|
156
|
+
fs.renameSync(tmp, paths.plistFile);
|
|
157
|
+
const r = await loadJob(io.launchctl, io.uid, instance.label, paths.plistFile);
|
|
158
|
+
if (r.code !== 0) {
|
|
159
|
+
io.err(`launchctl bootstrap failed (${r.code}): ${r.stderr.trim()}`);
|
|
160
|
+
return 1;
|
|
161
|
+
}
|
|
162
|
+
return 0;
|
|
163
|
+
}
|
|
164
|
+
export async function hostInstall(io) {
|
|
165
|
+
const { paths } = ctx(io);
|
|
166
|
+
const g = installGate(paths.configFile, io.env);
|
|
167
|
+
if (!g.ok) {
|
|
168
|
+
io.err(g.message);
|
|
169
|
+
return g.exit;
|
|
170
|
+
}
|
|
171
|
+
const st = readExecutorState(paths.stateFile);
|
|
172
|
+
let reload = false;
|
|
173
|
+
if (st.kind === "ok") {
|
|
174
|
+
const l = await executorLiveness(io, st.state.executor_id);
|
|
175
|
+
if (l.kind === "lapsed") {
|
|
176
|
+
io.err(GRANT_EXPIRED_LINE);
|
|
177
|
+
return 1;
|
|
178
|
+
}
|
|
179
|
+
if (l.kind === "live")
|
|
180
|
+
reload = true;
|
|
181
|
+
else if (l.kind === "unknown") {
|
|
182
|
+
// Cannot ask the server: keep the enrollment; the daemon's claims answer for it.
|
|
183
|
+
io.err(`could not check this machine's enrollment (${l.why}); reloading on the existing enrollment`);
|
|
184
|
+
reload = true;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
if (!reload) {
|
|
188
|
+
const e = await enrollDevice(io);
|
|
189
|
+
if (e !== 0)
|
|
190
|
+
return e;
|
|
191
|
+
}
|
|
192
|
+
else
|
|
193
|
+
printRiskLine(io);
|
|
194
|
+
const loaded = await writeAndLoad(io);
|
|
195
|
+
if (loaded !== 0)
|
|
196
|
+
return loaded;
|
|
197
|
+
if (io.json)
|
|
198
|
+
io.out(JSON.stringify({ event: "installed", reloaded: reload, plist: paths.plistFile }));
|
|
199
|
+
else
|
|
200
|
+
io.out(reload ? "Host daemon reloaded on this machine's enrollment." : "Host daemon installed.");
|
|
201
|
+
return 0;
|
|
202
|
+
}
|
|
203
|
+
export async function hostEnroll(io) {
|
|
204
|
+
return enrollDevice(io);
|
|
205
|
+
}
|
|
206
|
+
export async function hostRenew(io) {
|
|
207
|
+
const { paths } = ctx(io);
|
|
208
|
+
const st = readExecutorState(paths.stateFile);
|
|
209
|
+
if (st.kind !== "ok") {
|
|
210
|
+
io.err(`this machine is not enrolled: ${INSTALL_FIX}`);
|
|
211
|
+
return 1;
|
|
212
|
+
}
|
|
213
|
+
const r = await io.owner.renew(st.state.executor_id);
|
|
214
|
+
const start = r.status === 202 || r.status === 200 ? parseEnrollStart(r.body) : null;
|
|
215
|
+
if (!start) {
|
|
216
|
+
io.err(`renewal refused (${r.networkError ?? `http ${r.status}`})`);
|
|
217
|
+
return 1;
|
|
218
|
+
}
|
|
219
|
+
if (io.json)
|
|
220
|
+
io.out(JSON.stringify({
|
|
221
|
+
event: "renew_started",
|
|
222
|
+
user_code: start.userCode,
|
|
223
|
+
verification_uri_complete: start.verificationUri,
|
|
224
|
+
expires_in: start.expiresIn,
|
|
225
|
+
}));
|
|
226
|
+
else {
|
|
227
|
+
io.out(`Approve the renewal in your browser: ${start.verificationUri}`);
|
|
228
|
+
io.out(`Code: ${start.userCode}`);
|
|
229
|
+
}
|
|
230
|
+
const nap = io.pollSleep ?? ((ms) => sleep(ms));
|
|
231
|
+
const until = io.now() + start.expiresIn * 1000 + 5000;
|
|
232
|
+
while (io.now() < until) {
|
|
233
|
+
await nap(start.interval * 1000);
|
|
234
|
+
const l = await executorLiveness(io, st.state.executor_id);
|
|
235
|
+
if (l.kind === "live" && l.row.grantExpiresAt !== null && l.row.grantExpiresAt > io.now()) {
|
|
236
|
+
if (io.json)
|
|
237
|
+
io.out(JSON.stringify({
|
|
238
|
+
event: "renewed",
|
|
239
|
+
grant_expires_at: new Date(l.row.grantExpiresAt).toISOString(),
|
|
240
|
+
}));
|
|
241
|
+
else
|
|
242
|
+
io.out(`Grant renewed until ${new Date(l.row.grantExpiresAt).toISOString()}.`);
|
|
243
|
+
return 0;
|
|
244
|
+
}
|
|
245
|
+
if (l.kind === "gone") {
|
|
246
|
+
io.err(`this machine's executor was revoked: ${INSTALL_FIX}`);
|
|
247
|
+
return 1;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
io.err(`renewal not approved in time: ${RENEW_FIX}`);
|
|
251
|
+
return 1;
|
|
252
|
+
}
|
|
253
|
+
export async function hostUninstall(io) {
|
|
254
|
+
const { instance, paths } = ctx(io);
|
|
255
|
+
const had = fs.existsSync(paths.plistFile);
|
|
256
|
+
const wasLoaded = await unloadJob(io.launchctl, io.uid, instance.label);
|
|
257
|
+
fs.rmSync(paths.plistFile, { force: true });
|
|
258
|
+
if (!had && !wasLoaded) {
|
|
259
|
+
io.out("The host daemon is not installed here.");
|
|
260
|
+
return 0;
|
|
261
|
+
}
|
|
262
|
+
io.out("Host daemon uninstalled. Seats keep running; this machine stays enrolled.");
|
|
263
|
+
const rt = readRuntime(paths.runtimeFile);
|
|
264
|
+
const terminal = new Set(TERMINAL_STATES);
|
|
265
|
+
for (const s of rt?.sessions ?? []) {
|
|
266
|
+
if (s.state && terminal.has(s.state))
|
|
267
|
+
continue;
|
|
268
|
+
io.out(UNINSTALL_SESSION_LINE(s.name ?? s.id));
|
|
269
|
+
}
|
|
270
|
+
return 0;
|
|
271
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import "./lib/retired-env-guard.js";
|
|
3
|
-
import { program, type Command } from "commander";
|
|
4
|
-
|
|
5
|
-
* The durable-gated commands (BACKBONE Flags/CLI: off lists no durable verb). The committed
|
|
6
|
-
* command-tree.json leaves them out whatever the generating machine's experimental state
|
|
7
|
-
* (scripts/gen-command-tree.mjs); a piece that registers another durable verb adds it here.
|
|
8
|
-
*/
|
|
3
|
+
import { program, Option, type Command } from "commander";
|
|
4
|
+
import { type OptionShapes } from "./lib/durable/gate.js";
|
|
9
5
|
export declare const DURABLE_GATED_COMMANDS: ReadonlySet<Command>;
|
|
6
|
+
export declare const DURABLE_GATED_OPTIONS: ReadonlySet<Option>;
|
|
10
7
|
export { program };
|
|
8
|
+
/**
|
|
9
|
+
* The value-taking options of the program, `agent` and `agent sessions`: the durable gate steps over
|
|
10
|
+
* them (and their values) to find the verb word, as commander does before it dispatches.
|
|
11
|
+
*/
|
|
12
|
+
export declare function agentOptionShapes(): OptionShapes;
|
|
11
13
|
/**
|
|
12
14
|
* The entrypoint dispatch. `mnemom agent slack …` owns its own flags (--thread,
|
|
13
15
|
* --channel, …), which the `agent` launcher options would otherwise reject as
|