@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.
Files changed (195) hide show
  1. package/dist/base-url-proxy.mjs +18 -4
  2. package/dist/commands/agent-config.js +5 -1
  3. package/dist/commands/agent-doctor.d.ts +9 -0
  4. package/dist/commands/agent-doctor.js +4 -0
  5. package/dist/commands/agent-session.d.ts +1 -2
  6. package/dist/commands/agent-session.js +1 -2
  7. package/dist/commands/agent-setup.d.ts +6 -0
  8. package/dist/commands/agent-setup.js +12 -1
  9. package/dist/commands/agent.d.ts +11 -0
  10. package/dist/commands/agent.js +145 -46
  11. package/dist/commands/durable-doctor.d.ts +50 -0
  12. package/dist/commands/durable-doctor.js +186 -0
  13. package/dist/commands/durable-lock.d.ts +254 -0
  14. package/dist/commands/durable-lock.js +595 -0
  15. package/dist/commands/durable-sessions.d.ts +87 -0
  16. package/dist/commands/durable-sessions.js +303 -0
  17. package/dist/commands/durable-verbs.d.ts +116 -0
  18. package/dist/commands/durable-verbs.js +393 -0
  19. package/dist/durable/flock.d.ts +26 -0
  20. package/dist/durable/flock.js +80 -0
  21. package/dist/durable/launch-spec.d.ts +7 -1
  22. package/dist/durable/launch-spec.js +10 -3
  23. package/dist/durable/seat-proxy-entry.d.ts +11 -0
  24. package/dist/durable/seat-proxy-entry.js +25 -0
  25. package/dist/durable/seat-proxy.d.ts +107 -0
  26. package/dist/durable/seat-proxy.js +599 -0
  27. package/dist/durable/seat-socket.d.ts +46 -0
  28. package/dist/durable/seat-socket.js +179 -0
  29. package/dist/durable/seat-spec-validator.d.ts +28 -0
  30. package/dist/durable/seat-spec-validator.js +59 -0
  31. package/dist/durable/test-mode.mjs +81 -0
  32. package/dist/durable/turn-chain.d.ts +55 -0
  33. package/dist/durable/turn-chain.js +178 -0
  34. package/dist/host/auto-continue.d.ts +67 -0
  35. package/dist/host/auto-continue.js +118 -0
  36. package/dist/host/bootid.d.ts +13 -0
  37. package/dist/host/bootid.js +41 -0
  38. package/dist/host/client.d.ts +123 -0
  39. package/dist/host/client.js +171 -0
  40. package/dist/host/commands.d.ts +2 -0
  41. package/dist/host/commands.js +122 -0
  42. package/dist/host/control.d.ts +33 -0
  43. package/dist/host/control.js +144 -0
  44. package/dist/host/daemon.d.ts +217 -0
  45. package/dist/host/daemon.js +606 -0
  46. package/dist/host/gate.d.ts +46 -0
  47. package/dist/host/gate.js +82 -0
  48. package/dist/host/grant.d.ts +24 -0
  49. package/dist/host/grant.js +24 -0
  50. package/dist/host/instance.d.ts +35 -0
  51. package/dist/host/instance.js +62 -0
  52. package/dist/host/integrity.d.ts +35 -0
  53. package/dist/host/integrity.js +115 -0
  54. package/dist/host/launchd.d.ts +16 -0
  55. package/dist/host/launchd.js +45 -0
  56. package/dist/host/lock.d.ts +20 -0
  57. package/dist/host/lock.js +63 -0
  58. package/dist/host/owner.d.ts +55 -0
  59. package/dist/host/owner.js +121 -0
  60. package/dist/host/plist.d.ts +16 -0
  61. package/dist/host/plist.js +85 -0
  62. package/dist/host/procs.d.ts +27 -0
  63. package/dist/host/procs.js +105 -0
  64. package/dist/host/run.d.ts +66 -0
  65. package/dist/host/run.js +314 -0
  66. package/dist/host/runtime.d.ts +8 -0
  67. package/dist/host/runtime.js +22 -0
  68. package/dist/host/s1-names.d.ts +22 -0
  69. package/dist/host/s1-names.js +21 -0
  70. package/dist/host/seat-credential.d.ts +27 -0
  71. package/dist/host/seat-credential.js +41 -0
  72. package/dist/host/seat-launch-main.d.ts +1 -0
  73. package/dist/host/seat-launch-main.js +59 -0
  74. package/dist/host/seat-launch.d.ts +116 -0
  75. package/dist/host/seat-launch.js +302 -0
  76. package/dist/host/seat.d.ts +48 -0
  77. package/dist/host/seat.js +149 -0
  78. package/dist/host/state.d.ts +33 -0
  79. package/dist/host/state.js +117 -0
  80. package/dist/host/status.d.ts +101 -0
  81. package/dist/host/status.js +307 -0
  82. package/dist/host/tmux.d.ts +56 -0
  83. package/dist/host/tmux.js +182 -0
  84. package/dist/host/transcript.d.ts +30 -0
  85. package/dist/host/transcript.js +130 -0
  86. package/dist/host/verbs.d.ts +50 -0
  87. package/dist/host/verbs.js +271 -0
  88. package/dist/index.d.ts +8 -6
  89. package/dist/index.js +197 -35
  90. package/dist/lib/agent.d.ts +8 -26
  91. package/dist/lib/agent.js +11 -26
  92. package/dist/lib/claude-plugin.d.ts +11 -2
  93. package/dist/lib/claude-plugin.js +10 -1
  94. package/dist/lib/durable/api.d.ts +136 -0
  95. package/dist/lib/durable/api.js +220 -0
  96. package/dist/lib/durable/codes.d.ts +8 -0
  97. package/dist/lib/durable/codes.js +2 -0
  98. package/dist/lib/durable/first-turn.d.ts +50 -0
  99. package/dist/lib/durable/first-turn.js +102 -0
  100. package/dist/lib/durable/gate.d.ts +48 -0
  101. package/dist/lib/durable/gate.js +122 -0
  102. package/dist/lib/durable/handover.d.ts +101 -0
  103. package/dist/lib/durable/handover.js +175 -0
  104. package/dist/lib/durable/launch.d.ts +182 -0
  105. package/dist/lib/durable/launch.js +443 -0
  106. package/dist/lib/durable/launching.d.ts +15 -0
  107. package/dist/lib/durable/launching.js +70 -0
  108. package/dist/lib/durable/live.d.ts +194 -0
  109. package/dist/lib/durable/live.js +635 -0
  110. package/dist/lib/durable/refusal.d.ts +57 -0
  111. package/dist/lib/durable/refusal.js +78 -0
  112. package/dist/lib/durable/view.d.ts +83 -0
  113. package/dist/lib/durable/view.js +244 -0
  114. package/dist/lib/experimental.d.ts +0 -9
  115. package/dist/lib/experimental.js +18 -17
  116. package/dist/lib/launcher-config.d.ts +7 -0
  117. package/dist/lib/launcher-config.js +21 -0
  118. package/dist/lib/scan/report-email.d.ts +1 -1
  119. package/dist/lib/scan/report-email.js +9 -1
  120. package/dist/lib/subscription-login.d.ts +15 -0
  121. package/dist/lib/subscription-login.js +28 -1
  122. package/dist/vendor/durable-contract/index.js +240 -18
  123. package/dist/vendor/shared/durable-contract/admission.d.ts +240 -0
  124. package/dist/vendor/shared/durable-contract/admission.js +533 -0
  125. package/dist/vendor/shared/durable-contract/arm.d.ts +51 -0
  126. package/dist/vendor/shared/durable-contract/arm.js +133 -0
  127. package/dist/vendor/shared/durable-contract/canonical.d.ts +29 -0
  128. package/dist/vendor/shared/durable-contract/canonical.js +98 -0
  129. package/dist/vendor/shared/durable-contract/cell-id.d.ts +22 -0
  130. package/dist/vendor/shared/durable-contract/cell-id.js +38 -0
  131. package/dist/vendor/shared/durable-contract/cli-output.d.ts +342 -0
  132. package/dist/vendor/shared/durable-contract/cli-output.js +201 -0
  133. package/dist/vendor/shared/durable-contract/cli-surface.d.ts +119 -0
  134. package/dist/vendor/shared/durable-contract/cli-surface.js +270 -0
  135. package/dist/vendor/shared/durable-contract/codes.d.ts +537 -0
  136. package/dist/vendor/shared/durable-contract/codes.js +475 -0
  137. package/dist/vendor/shared/durable-contract/constants.d.ts +153 -0
  138. package/dist/vendor/shared/durable-contract/constants.js +179 -0
  139. package/dist/vendor/shared/durable-contract/doors.d.ts +119 -0
  140. package/dist/vendor/shared/durable-contract/doors.js +249 -0
  141. package/dist/vendor/shared/durable-contract/envelope.d.ts +103 -0
  142. package/dist/vendor/shared/durable-contract/envelope.js +317 -0
  143. package/dist/vendor/shared/durable-contract/events.d.ts +147 -0
  144. package/dist/vendor/shared/durable-contract/events.js +630 -0
  145. package/dist/vendor/shared/durable-contract/executor-state.d.ts +41 -0
  146. package/dist/vendor/shared/durable-contract/executor-state.js +32 -0
  147. package/dist/vendor/shared/durable-contract/fixlines.d.ts +318 -0
  148. package/dist/vendor/shared/durable-contract/fixlines.js +483 -0
  149. package/dist/vendor/shared/durable-contract/freeze-record.d.ts +787 -0
  150. package/dist/vendor/shared/durable-contract/freeze-record.js +361 -0
  151. package/dist/vendor/shared/durable-contract/index.d.ts +36 -0
  152. package/dist/vendor/shared/durable-contract/index.js +37 -0
  153. package/dist/vendor/shared/durable-contract/journal-approvals.d.ts +29 -0
  154. package/dist/vendor/shared/durable-contract/journal-approvals.js +60 -0
  155. package/dist/vendor/shared/durable-contract/keys.d.ts +24 -0
  156. package/dist/vendor/shared/durable-contract/keys.js +66 -0
  157. package/dist/vendor/shared/durable-contract/lock.d.ts +128 -0
  158. package/dist/vendor/shared/durable-contract/lock.js +151 -0
  159. package/dist/vendor/shared/durable-contract/names.d.ts +25 -0
  160. package/dist/vendor/shared/durable-contract/names.js +78 -0
  161. package/dist/vendor/shared/durable-contract/owner-verbs.d.ts +175 -0
  162. package/dist/vendor/shared/durable-contract/owner-verbs.js +403 -0
  163. package/dist/vendor/shared/durable-contract/paths.d.ts +23 -0
  164. package/dist/vendor/shared/durable-contract/paths.js +35 -0
  165. package/dist/vendor/shared/durable-contract/pure-fn.d.ts +48 -0
  166. package/dist/vendor/shared/durable-contract/pure-fn.js +89 -0
  167. package/dist/vendor/shared/durable-contract/routes.d.ts +205 -0
  168. package/dist/vendor/shared/durable-contract/routes.js +315 -0
  169. package/dist/vendor/shared/durable-contract/schema.d.ts +126 -0
  170. package/dist/vendor/shared/durable-contract/schema.js +441 -0
  171. package/dist/vendor/shared/durable-contract/seal.d.ts +54 -0
  172. package/dist/vendor/shared/durable-contract/seal.js +99 -0
  173. package/dist/vendor/shared/durable-contract/secret-scope.d.ts +28 -0
  174. package/dist/vendor/shared/durable-contract/secret-scope.js +76 -0
  175. package/dist/vendor/shared/durable-contract/session-id.d.ts +21 -0
  176. package/dist/vendor/shared/durable-contract/session-id.js +58 -0
  177. package/dist/vendor/shared/durable-contract/spec.d.ts +162 -0
  178. package/dist/vendor/shared/durable-contract/spec.js +286 -0
  179. package/dist/vendor/shared/durable-contract/states.d.ts +96 -0
  180. package/dist/vendor/shared/durable-contract/states.js +218 -0
  181. package/dist/vendor/shared/durable-contract/telemetry.d.ts +94 -0
  182. package/dist/vendor/shared/durable-contract/telemetry.js +180 -0
  183. package/dist/vendor/shared/durable-contract/test-mode.d.ts +62 -0
  184. package/dist/vendor/shared/durable-contract/test-mode.js +140 -0
  185. package/dist/vendor/shared/durable-contract/turn.d.ts +57 -0
  186. package/dist/vendor/shared/durable-contract/turn.js +81 -0
  187. package/dist/vendor/shared/durable-contract/vectors.d.ts +191 -0
  188. package/dist/vendor/shared/durable-contract/vectors.js +208 -0
  189. package/package.json +3 -2
  190. package/plugins/integrity-manifest.json +35 -0
  191. package/plugins/mnemom/hooks/hooks.json +15 -1
  192. package/plugins/mnemom/hooks/register.tsx +60 -2
  193. package/plugins/mnemom/hooks/seat-hello.mjs +116 -0
  194. package/dist/commands/agent-implicit-notice.d.ts +0 -31
  195. package/dist/commands/agent-implicit-notice.js +0 -73
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Owner-side executor routes (S1 ROUTES exec_enroll, exec_enroll_poll, exec_list, exec_renew;
3
+ * C04). The owner's JWT is used for start, list and renew; the poll uses only the enrollment's
4
+ * poll secret. Nothing here is written to disk except the executor key, into the state file, by
5
+ * the caller. Bodies follow C04 (kind, runtime, contract_version declared at start); response
6
+ * fields beyond S1 are read defensively.
7
+ */
8
+ import { randomUUID } from "node:crypto";
9
+ import { routePath } from "../vendor/shared/durable-contract/routes.js";
10
+ import { ENROLL_POLL_ERRORS } from "../vendor/shared/durable-contract/codes.js";
11
+ import { errorCode } from "./client.js";
12
+ export const EXECUTOR_CONTRACT = "durable-executor/v1";
13
+ export function ownerApi(opts) {
14
+ const base = opts.apiUrl.replace(/\/+$/, "");
15
+ const send = async (url, method, auth, body) => {
16
+ const headers = { accept: "application/json" };
17
+ if (auth)
18
+ headers.authorization = `Bearer ${auth}`;
19
+ if (body !== undefined)
20
+ headers["content-type"] = "application/json";
21
+ if (method !== "GET")
22
+ headers["idempotency-key"] = randomUUID();
23
+ try {
24
+ const res = await opts.fetch(url, {
25
+ method,
26
+ headers,
27
+ ...(body === undefined ? {} : { body: JSON.stringify(body) }),
28
+ });
29
+ const text = await res.text();
30
+ let parsed = null;
31
+ try {
32
+ parsed = text ? JSON.parse(text) : null;
33
+ }
34
+ catch {
35
+ parsed = text;
36
+ }
37
+ return { status: res.status, body: parsed };
38
+ }
39
+ catch (e) {
40
+ return { status: 0, body: null, networkError: e.message };
41
+ }
42
+ };
43
+ return {
44
+ enrollStart: async (body) => send(`${base}${routePath("exec_enroll")}`, "POST", await opts.jwt(), body),
45
+ enrollPoll: (secret, approvalId) => send(`${base}${routePath("exec_enroll_poll")}`, "POST", secret,
46
+ // The api reads the RFC 8628 device_code from the body; the Bearer copy stays for the auth class.
47
+ approvalId ? { device_code: secret, approval_id: approvalId } : { device_code: secret }),
48
+ list: async () => send(`${base}${routePath("exec_list")}`, "GET", await opts.jwt()),
49
+ renew: async (id) => send(`${base}${routePath("exec_renew", id)}`, "POST", await opts.jwt(), {}),
50
+ };
51
+ }
52
+ export function parseExecutorList(body) {
53
+ const arr = Array.isArray(body)
54
+ ? body
55
+ : body && typeof body === "object"
56
+ ? (body.executors ?? body.data)
57
+ : null;
58
+ if (!Array.isArray(arr))
59
+ return null;
60
+ const out = [];
61
+ for (const r of arr) {
62
+ if (!r || typeof r !== "object")
63
+ continue;
64
+ const o = r;
65
+ if (typeof o.id !== "string")
66
+ continue;
67
+ const g = typeof o.grant_expires_at === "string" ? Date.parse(o.grant_expires_at) : NaN;
68
+ const rl = o.risk_line_version;
69
+ out.push({
70
+ id: o.id.toLowerCase(),
71
+ revoked: o.revoked_at !== null && o.revoked_at !== undefined && o.revoked_at !== false,
72
+ grantExpiresAt: Number.isFinite(g) ? g : null,
73
+ riskLineVersion: typeof rl === "number"
74
+ ? rl
75
+ : typeof rl === "string" && /^\d+$/.test(rl)
76
+ ? Number(rl)
77
+ : null,
78
+ label: typeof o.label === "string" ? o.label : null,
79
+ orgId: typeof o.org_id === "string" ? o.org_id : null,
80
+ });
81
+ }
82
+ return out;
83
+ }
84
+ export function parseEnrollStart(body) {
85
+ if (!body || typeof body !== "object")
86
+ return null;
87
+ const o = body;
88
+ const secret = o.poll_secret ?? o.device_code;
89
+ if (typeof o.user_code !== "string" ||
90
+ typeof o.verification_uri_complete !== "string" ||
91
+ typeof secret !== "string")
92
+ return null;
93
+ return {
94
+ approvalId: typeof o.approval_id === "string" ? o.approval_id : null,
95
+ userCode: o.user_code,
96
+ verificationUri: o.verification_uri_complete,
97
+ expiresIn: typeof o.expires_in === "number" ? o.expires_in : 900,
98
+ interval: typeof o.interval === "number" && o.interval > 0 ? o.interval : 2,
99
+ pollSecret: secret,
100
+ };
101
+ }
102
+ export function classifyPoll(r) {
103
+ if (r.status === 200 || r.status === 201) {
104
+ const o = (r.body ?? {});
105
+ const id = o.executor_id;
106
+ const key = o.executor_key ?? o.key;
107
+ if (typeof id === "string" && typeof key === "string" && key !== "")
108
+ return { kind: "done", executorId: id.toLowerCase(), executorKey: key };
109
+ return { kind: "terminal", error: "malformed poll response" };
110
+ }
111
+ const e = errorCode(r.body);
112
+ if (e === "authorization_pending")
113
+ return { kind: "pending" };
114
+ if (e === "slow_down")
115
+ return { kind: "slow_down" };
116
+ if (e && ENROLL_POLL_ERRORS.includes(e))
117
+ return { kind: "terminal", error: e };
118
+ if (r.status === 0)
119
+ return { kind: "pending" };
120
+ return { kind: "terminal", error: e ?? `http ${r.status}` };
121
+ }
@@ -0,0 +1,16 @@
1
+ export declare const THROTTLE_INTERVAL_S = 10;
2
+ /** Env the installing shell may hand the daemon (values copied when set). */
3
+ export declare const PLIST_ENV_PASS: string[];
4
+ /** Never in a plist, whatever the allowlist says (static check, C12-R40). */
5
+ export declare const PLIST_ENV_FORBIDDEN: RegExp;
6
+ export interface PlistInput {
7
+ readonly label: string;
8
+ readonly programArguments: readonly string[];
9
+ readonly home: string;
10
+ readonly path: string;
11
+ readonly claudeBin: string;
12
+ readonly logFile: string;
13
+ readonly env: Readonly<Record<string, string | undefined>>;
14
+ }
15
+ export declare function plistEnv(input: PlistInput): Record<string, string>;
16
+ export declare function renderPlist(input: PlistInput): string;
@@ -0,0 +1,85 @@
1
+ /**
2
+ * The LaunchAgent plist (C12-R22, R27, R40; BACKBONE C2). User space, the instance label,
3
+ * absolute ProgramArguments ending in the hidden `agent host run`, RunAtLoad, KeepAlive,
4
+ * ThrottleInterval 10, AbandonProcessGroup, explicit PATH and HOME, the pinned claude path.
5
+ * EnvironmentVariables come from a closed allowlist: no secret, no MNEMOM_EXPERIMENTAL and no
6
+ * other experimental env (the daemon reads its gate from config.json).
7
+ */
8
+ import { DAEMON_TEST_ENV_PASS } from "../durable/test-mode.mjs";
9
+ export const THROTTLE_INTERVAL_S = 10;
10
+ /** Env the installing shell may hand the daemon (values copied when set). */
11
+ export const PLIST_ENV_PASS = [
12
+ "MNEMOM_API_URL",
13
+ "MNEMOM_GATEWAY_URL",
14
+ "MNEMOM_AGENT_GATEWAY",
15
+ "MNEMOM_HOST_INSTANCE",
16
+ "MNEMOM_HOST_TMUX_SOCKET",
17
+ "TMUX_TMPDIR",
18
+ "CLAUDE_CONFIG_DIR",
19
+ "TMPDIR",
20
+ "LANG",
21
+ "DISABLE_AUTOUPDATER",
22
+ "MNEMOM_DISABLE_AUTOUPDATE",
23
+ "MNEMOM_UPDATE_MODE",
24
+ ...DAEMON_TEST_ENV_PASS,
25
+ ];
26
+ /** Never in a plist, whatever the allowlist says (static check, C12-R40). */
27
+ export const PLIST_ENV_FORBIDDEN = /^(MNEMOM_EXPERIMENTAL|MNEMOM_API_KEY|MNEMOM_TOKEN|ANTHROPIC_API_KEY|.*_SECRET|.*_TOKEN)$/;
28
+ const xml = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
29
+ export function plistEnv(input) {
30
+ const out = {
31
+ HOME: input.home,
32
+ PATH: input.path,
33
+ MNEMOM_AGENT_CLI: input.claudeBin,
34
+ };
35
+ for (const k of PLIST_ENV_PASS) {
36
+ const v = input.env[k];
37
+ if (v)
38
+ out[k] = v;
39
+ }
40
+ return Object.fromEntries(Object.entries(out).filter(([k]) => !PLIST_ENV_FORBIDDEN.test(k)));
41
+ }
42
+ export function renderPlist(input) {
43
+ for (const a of input.programArguments)
44
+ if (!a)
45
+ throw new Error("empty ProgramArguments entry");
46
+ if (!input.programArguments[0].startsWith("/"))
47
+ throw new Error("ProgramArguments[0] must be absolute");
48
+ const env = plistEnv(input);
49
+ const lines = [
50
+ `<?xml version="1.0" encoding="UTF-8"?>`,
51
+ `<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">`,
52
+ `<plist version="1.0">`,
53
+ `<dict>`,
54
+ `\t<key>Label</key>`,
55
+ `\t<string>${xml(input.label)}</string>`,
56
+ `\t<key>ProgramArguments</key>`,
57
+ `\t<array>`,
58
+ ...input.programArguments.map((a) => `\t\t<string>${xml(a)}</string>`),
59
+ `\t</array>`,
60
+ `\t<key>RunAtLoad</key>`,
61
+ `\t<true/>`,
62
+ `\t<key>KeepAlive</key>`,
63
+ `\t<true/>`,
64
+ `\t<key>ThrottleInterval</key>`,
65
+ `\t<integer>${THROTTLE_INTERVAL_S}</integer>`,
66
+ `\t<key>AbandonProcessGroup</key>`,
67
+ `\t<true/>`,
68
+ `\t<key>WorkingDirectory</key>`,
69
+ `\t<string>${xml(input.home)}</string>`,
70
+ `\t<key>StandardOutPath</key>`,
71
+ `\t<string>${xml(input.logFile)}</string>`,
72
+ `\t<key>StandardErrorPath</key>`,
73
+ `\t<string>${xml(input.logFile)}</string>`,
74
+ `\t<key>EnvironmentVariables</key>`,
75
+ `\t<dict>`,
76
+ ...Object.keys(env)
77
+ .sort()
78
+ .flatMap((k) => [`\t\t<key>${xml(k)}</key>`, `\t\t<string>${xml(env[k])}</string>`]),
79
+ `\t</dict>`,
80
+ `</dict>`,
81
+ `</plist>`,
82
+ ``,
83
+ ];
84
+ return lines.join("\n");
85
+ }
@@ -0,0 +1,27 @@
1
+ export interface PsRow {
2
+ readonly pid: number;
3
+ readonly ppid: number;
4
+ readonly argv: string;
5
+ }
6
+ export declare function parsePs(stdout: string): PsRow[];
7
+ export declare function psTable(): PsRow[];
8
+ /** The roots and every descendant, roots first. */
9
+ export declare function treeOf(rows: readonly PsRow[], roots: readonly number[]): number[];
10
+ export declare const isClaudeArgv: (argv: string) => boolean;
11
+ export declare const isSeatProxyArgv: (argv: string) => boolean;
12
+ export interface ProcDeps {
13
+ readonly ps: () => PsRow[];
14
+ readonly kill: (pid: number, sig: NodeJS.Signals | 0) => void;
15
+ }
16
+ export declare const defaultProcDeps: () => ProcDeps;
17
+ export declare function alive(deps: ProcDeps, pid: number | null | undefined): boolean;
18
+ /**
19
+ * SIGSTOP the whole tree first (so nothing forks a new child between the walk and the kill),
20
+ * then SIGKILL every pid. Returns the pids signalled.
21
+ */
22
+ export declare function killTree(deps: ProcDeps, roots: readonly number[]): number[];
23
+ /** The seat's claude and proxy pids from the ps tree under the pane pid. */
24
+ export declare function seatPids(rows: readonly PsRow[], panePid: number): {
25
+ claude: number | null;
26
+ proxy: number | null;
27
+ };
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Process-tree helpers for seats (C12-R11, R17, R19, R22 M22): liveness by pid, and killing a
3
+ * seat's whole tree (claude and its children, not just the pane). Only pids the daemon resolved
4
+ * from its own tagged panes ever reach killTree (C12-R12).
5
+ */
6
+ import { execFileSync } from "node:child_process";
7
+ export function parsePs(stdout) {
8
+ const out = [];
9
+ for (const line of stdout.split("\n")) {
10
+ const m = /^\s*(\d+)\s+(\d+)\s+(.*)$/.exec(line);
11
+ if (m)
12
+ out.push({ pid: Number(m[1]), ppid: Number(m[2]), argv: m[3] });
13
+ }
14
+ return out;
15
+ }
16
+ export function psTable() {
17
+ try {
18
+ return parsePs(execFileSync("/bin/ps", ["-axo", "pid=,ppid=,command="], {
19
+ encoding: "utf8",
20
+ maxBuffer: 32 * 1024 * 1024,
21
+ }));
22
+ }
23
+ catch {
24
+ return [];
25
+ }
26
+ }
27
+ /** The roots and every descendant, roots first. */
28
+ export function treeOf(rows, roots) {
29
+ const kids = new Map();
30
+ for (const r of rows) {
31
+ const k = kids.get(r.ppid);
32
+ if (k)
33
+ k.push(r.pid);
34
+ else
35
+ kids.set(r.ppid, [r.pid]);
36
+ }
37
+ const out = [];
38
+ const seen = new Set();
39
+ const stack = [...roots];
40
+ while (stack.length) {
41
+ const p = stack.shift();
42
+ if (seen.has(p) || p <= 1)
43
+ continue;
44
+ seen.add(p);
45
+ out.push(p);
46
+ for (const k of kids.get(p) ?? [])
47
+ stack.push(k);
48
+ }
49
+ return out;
50
+ }
51
+ export const isClaudeArgv = (argv) => /(^|\/)claude(\s|$)|\/claude\/versions\/|(^|\/)claude-shim/.test(argv.split(/\s+--/)[0] ?? "");
52
+ export const isSeatProxyArgv = (argv) => /agent host seat-proxy/.test(argv);
53
+ export const defaultProcDeps = () => ({
54
+ ps: psTable,
55
+ kill: (pid, sig) => process.kill(pid, sig),
56
+ });
57
+ export function alive(deps, pid) {
58
+ if (!pid || pid <= 1)
59
+ return false;
60
+ try {
61
+ deps.kill(pid, 0);
62
+ return true;
63
+ }
64
+ catch (e) {
65
+ return e.code === "EPERM";
66
+ }
67
+ }
68
+ /**
69
+ * SIGSTOP the whole tree first (so nothing forks a new child between the walk and the kill),
70
+ * then SIGKILL every pid. Returns the pids signalled.
71
+ */
72
+ export function killTree(deps, roots) {
73
+ const pids = treeOf(deps.ps(), roots.filter((p) => p > 1));
74
+ for (const r of roots)
75
+ if (r > 1 && !pids.includes(r))
76
+ pids.push(r);
77
+ const send = (pid, sig) => {
78
+ try {
79
+ deps.kill(pid, sig);
80
+ }
81
+ catch {
82
+ /* gone already */
83
+ }
84
+ };
85
+ for (const p of pids)
86
+ send(p, "SIGSTOP");
87
+ for (const p of pids)
88
+ send(p, "SIGKILL");
89
+ return pids;
90
+ }
91
+ /** The seat's claude and proxy pids from the ps tree under the pane pid. */
92
+ export function seatPids(rows, panePid) {
93
+ const tree = treeOf(rows, [panePid]);
94
+ const byPid = new Map(rows.map((r) => [r.pid, r]));
95
+ let claude = null;
96
+ let proxy = null;
97
+ for (const p of tree) {
98
+ const a = byPid.get(p)?.argv ?? "";
99
+ if (claude === null && isClaudeArgv(a))
100
+ claude = p;
101
+ if (proxy === null && isSeatProxyArgv(a))
102
+ proxy = p;
103
+ }
104
+ return { claude, proxy };
105
+ }
@@ -0,0 +1,66 @@
1
+ import { type HostPaths, type HostInstance } from "./instance.js";
2
+ import { type LoadedState } from "./state.js";
3
+ import { type AutoContinueDeps } from "./auto-continue.js";
4
+ export interface RunIo {
5
+ readonly env: Readonly<Record<string, string | undefined>>;
6
+ readonly home: string;
7
+ readonly err: (line: string) => void;
8
+ }
9
+ /** A tag that names this HOME on a shared tmux server (not secret: a hash of the path). */
10
+ export declare const homeTag: (home: string) => string;
11
+ /** The pinned claude: MNEMOM_AGENT_CLI when absolute, else `claude` on PATH, else null. */
12
+ export declare function resolveClaudeBin(env: Readonly<Record<string, string | undefined>>): string | null;
13
+ /** The argv that runs this CLI again: node + the real entry script. */
14
+ export declare function cliExec(): string[];
15
+ type Startup = {
16
+ kind: "exit";
17
+ code: number;
18
+ message: string;
19
+ } | {
20
+ kind: "go";
21
+ paths: HostPaths;
22
+ instance: HostInstance;
23
+ };
24
+ /** The read-only part of startup: what `run` does before it takes the lock. */
25
+ export declare function startupCheck(io: RunIo): Startup;
26
+ /**
27
+ * The boot-bound nonce (C12-R06), bound on first use: the first call compares the recorded boot id
28
+ * with the OS one, writes a rotated nonce when they differ (or the record is unusable), and caches
29
+ * the result; later calls return it with no I/O.
30
+ */
31
+ export declare function lazyNonce(stateFile: string, loaded: LoadedState, bootId: string, log: (line: string) => void): () => string;
32
+ export declare function hostRun(io: RunIo): Promise<number>;
33
+ /**
34
+ * The runtime file is written only while this daemon still locks the current host.lock. A replaced
35
+ * lock file means another daemon may own the state dir: stop instead of writing over its runtime.
36
+ */
37
+ export declare function publishIfLockHeld(held: () => boolean, write: () => void, lost: () => void): void;
38
+ /** For tests and status: the OS boot id (macOS sysctl). */
39
+ export declare function sysctlBootId(): string;
40
+ export declare const defaultHome: () => string;
41
+ /** The interrupted turn of the transcript at `p` (auto-continue.ts), or null when it cannot be read. */
42
+ export declare function interruptedAtFile(p: string, read: (p: string) => string): string | null;
43
+ /** The fs calls the auto-continue markers make. */
44
+ export interface MarkerFs {
45
+ existsSync(p: string): boolean;
46
+ mkdirSync(p: string, o: {
47
+ recursive: true;
48
+ mode: number;
49
+ }): unknown;
50
+ writeFileSync(p: string, data: string, o: {
51
+ flag: string;
52
+ }): void;
53
+ }
54
+ /**
55
+ * The auto-continue markers: one file per session and turn in `dir` (0700). The write is exclusive
56
+ * ("wx"), so marking a turn already marked throws and that turn is never continued twice.
57
+ */
58
+ export declare function fileContinuedMarkers(dir: string, f: MarkerFs): Pick<AutoContinueDeps, "continued" | "markContinued">;
59
+ /** Write the runtime file; a failed write is logged and the daemon goes on. */
60
+ export declare function writeRuntimeOrLog(write: () => void, log: (line: string) => void): void;
61
+ /**
62
+ * The daemon's last step. Another daemon owns the control socket path now ("left"): leave with
63
+ * exit, since a normal exit would unlink that daemon's socket.
64
+ */
65
+ export declare function exitIfSocketLeft(released: "closed" | "left", exit: (code: number) => void): void;
66
+ export {};