@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,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The daemon's control socket: the one channel between the daemon and the seat proxies it
|
|
3
|
+
* spawned (C3, `mnemom agent host seat-proxy`). Newline-delimited JSON, one request per
|
|
4
|
+
* connection, a 0600 unix socket in a 0700 directory. Ops:
|
|
5
|
+
* {op:"seat_key", session_id, spawn_id} -> {ok:true, lease_key} once per spawn, then {ok:false}
|
|
6
|
+
* {op:"hello", session_id, spawn_id} -> {ok, status, body}: the daemon POSTs the executor hello
|
|
7
|
+
* (the executor key never leaves the daemon, C12-R27)
|
|
8
|
+
* Anything else is refused locally.
|
|
9
|
+
*/
|
|
10
|
+
import * as fs from "node:fs";
|
|
11
|
+
import * as net from "node:net";
|
|
12
|
+
import * as path from "node:path";
|
|
13
|
+
import * as os from "node:os";
|
|
14
|
+
import { createHash } from "node:crypto";
|
|
15
|
+
/** macOS sun_path is 104 bytes; keep a margin. */
|
|
16
|
+
export const MAX_SOCKET_PATH = 100;
|
|
17
|
+
/** A socket dir for this HOME: under the durable run dir when short enough, else a hashed /tmp dir. */
|
|
18
|
+
export function socketDir(runDir, home, uid = os.userInfo().uid) {
|
|
19
|
+
if (path.join(runDir, "00000000.sock").length <= MAX_SOCKET_PATH)
|
|
20
|
+
return runDir;
|
|
21
|
+
const h = createHash("sha256").update(home).digest("hex").slice(0, 12);
|
|
22
|
+
return path.join("/tmp", `mnm-host-${uid}`, h);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* One socket directory must be a real directory (not a symlink), owned by this uid, and 0700.
|
|
26
|
+
* The same check as seat-socket.ts's ensureSocketDir: throws when it is not.
|
|
27
|
+
*/
|
|
28
|
+
function checkOwnSocketDir(dir, uid) {
|
|
29
|
+
const st = fs.lstatSync(dir);
|
|
30
|
+
if (!st.isDirectory())
|
|
31
|
+
throw new Error("socket dir is not a directory");
|
|
32
|
+
if (st.uid !== uid)
|
|
33
|
+
throw new Error("socket dir is owned by another user");
|
|
34
|
+
if ((st.mode & 0o777) !== 0o700)
|
|
35
|
+
fs.chmodSync(dir, 0o700);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Create (or check) the control socket's directory: 0700 and owned by this uid. Under the /tmp
|
|
39
|
+
* fallback the predictable /tmp/mnm-host-<uid> parent gets the same check, so a directory another
|
|
40
|
+
* user created there first is refused rather than used. Throws when either is not ours.
|
|
41
|
+
*/
|
|
42
|
+
export function ensureSocketDir(dir, uid = os.userInfo().uid) {
|
|
43
|
+
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
44
|
+
const parent = path.dirname(dir);
|
|
45
|
+
if (parent.startsWith("/tmp/mnm-host-"))
|
|
46
|
+
checkOwnSocketDir(parent, uid);
|
|
47
|
+
checkOwnSocketDir(dir, uid);
|
|
48
|
+
}
|
|
49
|
+
export const controlSocketPath = (dir) => path.join(dir, "daemon.sock");
|
|
50
|
+
const ID = /^[0-9a-f-]{36}$/i;
|
|
51
|
+
export async function handleControl(h, line) {
|
|
52
|
+
let req;
|
|
53
|
+
try {
|
|
54
|
+
req = JSON.parse(line);
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return { ok: false, error: "bad_request" };
|
|
58
|
+
}
|
|
59
|
+
const sid = typeof req.session_id === "string" && ID.test(req.session_id)
|
|
60
|
+
? req.session_id.toLowerCase()
|
|
61
|
+
: null;
|
|
62
|
+
const spawn = typeof req.spawn_id === "string" && ID.test(req.spawn_id) ? req.spawn_id.toLowerCase() : null;
|
|
63
|
+
if (!sid || !spawn)
|
|
64
|
+
return { ok: false, error: "bad_request" };
|
|
65
|
+
if (req.op === "seat_key") {
|
|
66
|
+
const k = h.takeLeaseKey(sid, spawn);
|
|
67
|
+
return k ? { ok: true, lease_key: k } : { ok: false, error: "no_key" };
|
|
68
|
+
}
|
|
69
|
+
if (req.op === "hello") {
|
|
70
|
+
const r = await h.relayHello(sid, spawn);
|
|
71
|
+
return { ok: r.status === 200, status: r.status, body: r.body };
|
|
72
|
+
}
|
|
73
|
+
return { ok: false, error: "unknown_op" };
|
|
74
|
+
}
|
|
75
|
+
export function startControlServer(socket, h, log) {
|
|
76
|
+
fs.rmSync(socket, { force: true });
|
|
77
|
+
const srv = net.createServer((conn) => {
|
|
78
|
+
let buf = "";
|
|
79
|
+
conn.setTimeout(30000, () => conn.destroy());
|
|
80
|
+
conn.on("data", (d) => {
|
|
81
|
+
buf += d.toString("utf8");
|
|
82
|
+
if (buf.length > 64 * 1024)
|
|
83
|
+
return conn.destroy();
|
|
84
|
+
const i = buf.indexOf("\n");
|
|
85
|
+
if (i < 0)
|
|
86
|
+
return;
|
|
87
|
+
const line = buf.slice(0, i);
|
|
88
|
+
buf = "";
|
|
89
|
+
handleControl(h, line)
|
|
90
|
+
.then((res) => conn.end(`${JSON.stringify(res)}\n`))
|
|
91
|
+
.catch((e) => {
|
|
92
|
+
log(`control: ${e.message}`);
|
|
93
|
+
conn.end(`${JSON.stringify({ ok: false, error: "internal" })}\n`);
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
conn.on("error", () => { });
|
|
97
|
+
});
|
|
98
|
+
const old = process.umask(0o177);
|
|
99
|
+
try {
|
|
100
|
+
srv.listen(socket);
|
|
101
|
+
}
|
|
102
|
+
finally {
|
|
103
|
+
process.umask(old);
|
|
104
|
+
}
|
|
105
|
+
srv.on("listening", () => {
|
|
106
|
+
try {
|
|
107
|
+
fs.chmodSync(socket, 0o600);
|
|
108
|
+
boundSockets.set(srv, socketIdentity(socket));
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
/* raced with shutdown */
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
return srv;
|
|
115
|
+
}
|
|
116
|
+
/** dev and inode of the socket file at `socket`, or null when there is none. */
|
|
117
|
+
export function socketIdentity(socket) {
|
|
118
|
+
try {
|
|
119
|
+
const st = fs.statSync(socket);
|
|
120
|
+
return st.isSocket() ? { dev: st.dev, ino: st.ino } : null;
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
/** The socket file each control server created, recorded when it started listening. */
|
|
127
|
+
const boundSockets = new WeakMap();
|
|
128
|
+
/**
|
|
129
|
+
* Stop serving `socket`. Node unlinks a unix socket's path when its server closes, whoever owns
|
|
130
|
+
* the path by then, so the server is closed (and the path removed) only while the path is still
|
|
131
|
+
* the socket this server created. Otherwise another daemon has re-bound it: the server is only
|
|
132
|
+
* unref'd and the caller must leave with process.exit, which never unlinks.
|
|
133
|
+
*/
|
|
134
|
+
export function releaseControlSocket(srv, socket) {
|
|
135
|
+
const bound = boundSockets.get(srv) ?? null;
|
|
136
|
+
const now = socketIdentity(socket);
|
|
137
|
+
if (bound !== null && now !== null && bound.dev === now.dev && bound.ino === now.ino) {
|
|
138
|
+
srv.close();
|
|
139
|
+
fs.rmSync(socket, { force: true });
|
|
140
|
+
return "closed";
|
|
141
|
+
}
|
|
142
|
+
srv.unref();
|
|
143
|
+
return "left";
|
|
144
|
+
}
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import type { ClaimUnavailableCause } from "../vendor/shared/durable-contract/codes.js";
|
|
2
|
+
import { type ExecutorApi, type SessionItem } from "./client.js";
|
|
3
|
+
import { type SeatPane } from "./tmux.js";
|
|
4
|
+
export declare const TICK_MS = 1000;
|
|
5
|
+
export declare const ADOPT_RETRY_MS = 2000;
|
|
6
|
+
export { SPAWNED_KIND } from "./s1-names.js";
|
|
7
|
+
export declare const spawnIdempotencyKey: (sessionId: string, epoch: number, spawnId: string) => string;
|
|
8
|
+
export type PausePoint = "after_claim_before_spawn" | "after_spawn_before_register" | "after_register_before_hello" | "after_first_batch";
|
|
9
|
+
/** One seat this daemon holds (spawned or adopted). */
|
|
10
|
+
export interface Seat {
|
|
11
|
+
readonly sessionId: string;
|
|
12
|
+
readonly name: string | null;
|
|
13
|
+
readonly epoch: number;
|
|
14
|
+
readonly spawnId: string;
|
|
15
|
+
readonly tmuxSession: string;
|
|
16
|
+
panePid: number | null;
|
|
17
|
+
/** The claude pid once seen under the pane; liveness is judged by it (R19). */
|
|
18
|
+
claudePid: number | null;
|
|
19
|
+
/** When the claude pid was first seen gone while the pane still ran (proxy lingering). */
|
|
20
|
+
claudeGoneAt: number | null;
|
|
21
|
+
helloAccepted: boolean;
|
|
22
|
+
registered: boolean;
|
|
23
|
+
/** Hello deadline (ms epoch) while not hello'd: the claim's lease expiry (R17). */
|
|
24
|
+
deadline: number;
|
|
25
|
+
leaseSeconds: number;
|
|
26
|
+
lastHeartbeat: number;
|
|
27
|
+
/** One-time lease key handoff to the seat proxy; cleared once taken. */
|
|
28
|
+
leaseKey: string | null;
|
|
29
|
+
specPath: string | null;
|
|
30
|
+
adopted: boolean;
|
|
31
|
+
/** The transcript's interrupted turn when this seat was spawned, or null (auto-continue). */
|
|
32
|
+
interruptedTurn: string | null;
|
|
33
|
+
}
|
|
34
|
+
export interface SpawnRequest {
|
|
35
|
+
readonly item: SessionItem;
|
|
36
|
+
readonly epoch: number;
|
|
37
|
+
readonly spawnId: string;
|
|
38
|
+
readonly tmuxSession: string;
|
|
39
|
+
/** Null for a born-locked session's first spawn (B5a): no transcript exists yet. */
|
|
40
|
+
readonly transcriptPath: string | null;
|
|
41
|
+
/** B5a: start the CC session fresh under its id (--session-id) instead of resuming it. */
|
|
42
|
+
readonly newSession: boolean;
|
|
43
|
+
}
|
|
44
|
+
export interface DaemonDeps {
|
|
45
|
+
readonly api: ExecutorApi;
|
|
46
|
+
readonly nonce: string;
|
|
47
|
+
readonly home: string;
|
|
48
|
+
readonly now: () => number;
|
|
49
|
+
readonly random: () => number;
|
|
50
|
+
readonly log: (line: string) => void;
|
|
51
|
+
/** Durable gate from config.json only (R39). */
|
|
52
|
+
readonly gateOn: () => boolean;
|
|
53
|
+
/** Plugin integrity of the installed package (R36). */
|
|
54
|
+
readonly integrity: () => Promise<{
|
|
55
|
+
ok: true;
|
|
56
|
+
} | {
|
|
57
|
+
ok: false;
|
|
58
|
+
cause: string;
|
|
59
|
+
}>;
|
|
60
|
+
/** This daemon's tagged panes (only panes tagged with this HOME). */
|
|
61
|
+
readonly panes: () => Promise<SeatPane[]>;
|
|
62
|
+
/** The claude pid under a pane (null when claude is not running). */
|
|
63
|
+
readonly claudePid: (panePid: number) => number | null;
|
|
64
|
+
readonly alive: (pid: number) => boolean;
|
|
65
|
+
/** Kill the pane's whole process tree and its tmux session. */
|
|
66
|
+
readonly killSeat: (tmuxSession: string, panePid: number | null) => Promise<void>;
|
|
67
|
+
/** Spawn a seat in tmux; returns the pane pid and the launch-spec path. */
|
|
68
|
+
readonly spawnSeat: (req: SpawnRequest) => Promise<{
|
|
69
|
+
panePid: number;
|
|
70
|
+
specPath: string;
|
|
71
|
+
}>;
|
|
72
|
+
/** Transcript guard: refresh for an item; returns the transcript path now on disk (or null). */
|
|
73
|
+
readonly guard: (item: SessionItem) => string | null;
|
|
74
|
+
/** Before --resume: present, restored from the guard, or lost. */
|
|
75
|
+
readonly ensureTranscript: (item: SessionItem) => {
|
|
76
|
+
kind: "present" | "restored";
|
|
77
|
+
path: string;
|
|
78
|
+
} | {
|
|
79
|
+
kind: "lost";
|
|
80
|
+
};
|
|
81
|
+
readonly removeGuard: (sessionId: string) => void;
|
|
82
|
+
readonly guardedIds: () => string[];
|
|
83
|
+
/** T2 test pause point (null when not in test mode). */
|
|
84
|
+
readonly pauseAt: PausePoint | null;
|
|
85
|
+
/** Blocks forever (T2): the harness kills the daemon here. */
|
|
86
|
+
readonly pause: (point: PausePoint) => Promise<never>;
|
|
87
|
+
/** Persist status facts (no secret) for `host status`. */
|
|
88
|
+
readonly publish: (snapshot: DaemonSnapshot) => void;
|
|
89
|
+
/** interruptedTurnId of the transcript at `path` (null when unreadable). */
|
|
90
|
+
readonly interruptedAt?: (path: string) => string | null;
|
|
91
|
+
/** The one continue of an interrupted turn after a seat's first accepted hello (host/auto-continue.ts). */
|
|
92
|
+
readonly autoContinue?: (seat: {
|
|
93
|
+
sessionId: string;
|
|
94
|
+
tmuxSession: string;
|
|
95
|
+
epoch: number;
|
|
96
|
+
spawnId: string;
|
|
97
|
+
}, turnId: string | null) => Promise<unknown>;
|
|
98
|
+
}
|
|
99
|
+
export interface UnavailableInfo {
|
|
100
|
+
cause: ClaimUnavailableCause | null;
|
|
101
|
+
count: number;
|
|
102
|
+
nextAt: number;
|
|
103
|
+
}
|
|
104
|
+
export interface DaemonSnapshot {
|
|
105
|
+
readonly pid: number;
|
|
106
|
+
readonly state: "running" | "idle";
|
|
107
|
+
readonly integrity: {
|
|
108
|
+
ok: true;
|
|
109
|
+
} | {
|
|
110
|
+
ok: false;
|
|
111
|
+
cause: string;
|
|
112
|
+
};
|
|
113
|
+
readonly seats: readonly {
|
|
114
|
+
session_id: string;
|
|
115
|
+
name: string | null;
|
|
116
|
+
epoch: number;
|
|
117
|
+
spawn_id: string;
|
|
118
|
+
tmux_session: string;
|
|
119
|
+
pane_pid: number | null;
|
|
120
|
+
hello: boolean;
|
|
121
|
+
spec_path: string | null;
|
|
122
|
+
}[];
|
|
123
|
+
readonly sessions: readonly {
|
|
124
|
+
id: string;
|
|
125
|
+
name: string | null;
|
|
126
|
+
state: string | null;
|
|
127
|
+
state_reason: string | null;
|
|
128
|
+
}[];
|
|
129
|
+
readonly unavailable: Readonly<Record<string, {
|
|
130
|
+
cause: ClaimUnavailableCause | null;
|
|
131
|
+
count: number;
|
|
132
|
+
}>>;
|
|
133
|
+
readonly updated_at: string;
|
|
134
|
+
}
|
|
135
|
+
/** S1 claim backoff: 2 s doubling to 60 s, scaled by 1 + jitter in [0, CLAIM_BACKOFF_JITTER) (R31). */
|
|
136
|
+
export declare function backoffMs(count: number, random: () => number): number;
|
|
137
|
+
/** Heartbeat period from the server's lease (R18): lease_seconds / HEARTBEAT_DIVISOR. */
|
|
138
|
+
export declare const heartbeatPeriodMs: (leaseSeconds: number) => number;
|
|
139
|
+
/**
|
|
140
|
+
* The lease length an adopted seat heartbeats on until a heartbeat answer re-derives it (R18):
|
|
141
|
+
* the time left on the current lease (never longer than the S1 default, so a short lease is never
|
|
142
|
+
* out-waited), else the S1 default. The poll carries no lease_seconds (ruling 33.28.2).
|
|
143
|
+
*/
|
|
144
|
+
export declare function adoptedLeaseSeconds(item: SessionItem, now: number): number;
|
|
145
|
+
export type AdoptVerdict = {
|
|
146
|
+
adopt: true;
|
|
147
|
+
hello: boolean;
|
|
148
|
+
} | {
|
|
149
|
+
adopt: false;
|
|
150
|
+
why: string;
|
|
151
|
+
};
|
|
152
|
+
/**
|
|
153
|
+
* C12-R11: adopt a tagged pane, or say why it is killed. `seen` holds sessions already adopted.
|
|
154
|
+
* The poll carries no spawn id or hello (ruling 33.28.2): the pane's tags name its epoch and spawn,
|
|
155
|
+
* held_here says the server agrees the lease is ours, and the state says whether the epoch's hello
|
|
156
|
+
* landed (hello moves resuming to running). The server stays the arbiter: the heartbeat revives a
|
|
157
|
+
* parked or lease-expired session only for the hello'd spawn, and fences anything else.
|
|
158
|
+
*/
|
|
159
|
+
export declare function adoptVerdict(pane: SeatPane, item: SessionItem | undefined, now: number, panAlive: boolean, seenSessions: ReadonlySet<string>): AdoptVerdict;
|
|
160
|
+
/** How long a pane may outlive its claude before the exit is reported without the pane's status. */
|
|
161
|
+
export declare const CLAUDE_GONE_GRACE_MS = 3000;
|
|
162
|
+
export type Liveness = {
|
|
163
|
+
kind: "alive";
|
|
164
|
+
} | {
|
|
165
|
+
kind: "dead";
|
|
166
|
+
status: number;
|
|
167
|
+
};
|
|
168
|
+
/**
|
|
169
|
+
* R19: a seat is alive while its claude pid is (or, before claude has started, while its pane
|
|
170
|
+
* process is). A dead pane reports its own exit status; a claude that is gone while the pane
|
|
171
|
+
* lingers is reported after CLAUDE_GONE_GRACE_MS with 137.
|
|
172
|
+
*/
|
|
173
|
+
export declare function seatLiveness(seat: Seat, pane: SeatPane | undefined, now: number, claudePid: (panePid: number) => number | null, alive: (pid: number) => boolean): Liveness;
|
|
174
|
+
/** Exit status as the seat-exit route takes it (0..255); a missing status is a failure. */
|
|
175
|
+
export declare const exitStatusOf: (pane: SeatPane | undefined) => number;
|
|
176
|
+
export declare class HostDaemon {
|
|
177
|
+
private readonly d;
|
|
178
|
+
readonly seats: Map<string, Seat>;
|
|
179
|
+
readonly unavailable: Map<string, UnavailableInfo>;
|
|
180
|
+
private lastPoll;
|
|
181
|
+
private adoptDone;
|
|
182
|
+
private idleLogged;
|
|
183
|
+
private items;
|
|
184
|
+
private integrityState;
|
|
185
|
+
/** The cause claims were last held for, so the log names each change once (mnemom-test #981). */
|
|
186
|
+
private heldFor;
|
|
187
|
+
private firstBatchDone;
|
|
188
|
+
private stopped;
|
|
189
|
+
/** The last poll said the grant lapsed (ruling 33.28(4)); logged once per transition. */
|
|
190
|
+
private grantExpired;
|
|
191
|
+
constructor(d: DaemonDeps);
|
|
192
|
+
stop(): void;
|
|
193
|
+
/** One pass of the loop. Returns what it did (for tests and logs). */
|
|
194
|
+
tick(): Promise<string[]>;
|
|
195
|
+
/** Run until stop(): one tick every TICK_MS. */
|
|
196
|
+
run(sleep: (ms: number) => Promise<void>): Promise<void>;
|
|
197
|
+
private syncGuards;
|
|
198
|
+
private adoptOrKill;
|
|
199
|
+
private checkSeats;
|
|
200
|
+
private reportExit;
|
|
201
|
+
private heartbeat;
|
|
202
|
+
private inFlight;
|
|
203
|
+
private claimWave;
|
|
204
|
+
private claimable;
|
|
205
|
+
private claimAndSpawn;
|
|
206
|
+
/** The one-time lease-key handoff to the seat proxy of `spawnId` (C3). */
|
|
207
|
+
takeLeaseKey(sessionId: string, spawnId: string): string | null;
|
|
208
|
+
/**
|
|
209
|
+
* Relay a seat's hello (R17, R32, R34). Only for a registered seat this daemon holds, with its own
|
|
210
|
+
* spawn id. 409 durable_hello_late kills that seat's tree.
|
|
211
|
+
*/
|
|
212
|
+
relayHello(sessionId: string, spawnId: string): Promise<{
|
|
213
|
+
status: number;
|
|
214
|
+
body: unknown;
|
|
215
|
+
}>;
|
|
216
|
+
private publish;
|
|
217
|
+
}
|