@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,136 @@
1
+ /**
2
+ * The durable registry and owner-verb client (BACKBONE C1, A2; S1 routes.ts ROUTES.own_*).
3
+ *
4
+ * Every call returns `{status, body}` and never throws on an HTTP answer: the commands decide from
5
+ * the status and the S1 code. A transport failure, a timeout or a 5xx (other than a durable 503 the
6
+ * caller must surface, which keeps its body) is a `RegistryUnavailable`, so a launch fails closed
7
+ * with `session_registry_unavailable` (C01-R18) instead of minting a conversation it could not
8
+ * resolve. The timeout is fixed in code: REGISTRY_TIMEOUT_MS covers connect plus total.
9
+ *
10
+ * Credentials (S1 owner-credential rule; BACKBONE owner Q6): the owner's JWT, else the personal
11
+ * org `mnm_` API key (MNEMOM_API_KEY). Org containment and device-step polls are JWT-only; the
12
+ * caller asks for that with `jwtOnly`.
13
+ *
14
+ * Not frozen by S1 (A2 owns them; recorded in notes-c1.md as the cross-piece assumption):
15
+ * GET /v1/agent/sessions?name=&agent_id=&org_id= -> {sessions: Row[]} (or a bare array)
16
+ * POST /v1/agent/sessions/{cid}?agent_id= -> 201/200 Row (create)
17
+ * GET /v1/agent/sessions/{cid}/lock?agent_id=&dry_run=true -> the doctor's lock preflight
18
+ */
19
+ import { type RouteName } from "../../vendor/shared/durable-contract/routes.js";
20
+ /** CLI registry timeout, connect plus total (C01-R18: at most 10 s). */
21
+ export declare const REGISTRY_TIMEOUT_MS = 10000;
22
+ export type DurableCredential = {
23
+ type: "jwt";
24
+ token: string;
25
+ } | {
26
+ type: "api-key";
27
+ key: string;
28
+ } | {
29
+ type: "none";
30
+ };
31
+ export interface DurableHttpDeps {
32
+ fetch: typeof fetch;
33
+ apiBase: () => string;
34
+ credential: () => Promise<DurableCredential>;
35
+ /** The active org id (`mnemom org use`), or undefined. */
36
+ activeOrgId: () => string | undefined;
37
+ /** Agents named `name` in the caller's org fleets (lib/api.ts listOrgAgents); none when absent. */
38
+ agentsByName?: (name: string) => Promise<Array<{
39
+ id: string;
40
+ org_id: string;
41
+ }>>;
42
+ }
43
+ export interface ApiAnswer {
44
+ status: number;
45
+ body: unknown;
46
+ }
47
+ /** The registry could not be read: transport error, timeout or a non-durable 5xx. */
48
+ export declare class RegistryUnavailable extends Error {
49
+ constructor(detail: string);
50
+ }
51
+ /** A registry row as the CLI reads it. Every field but the name and cid is optional. */
52
+ export interface RegistryRow {
53
+ id?: string;
54
+ conversation_id: string;
55
+ name: string | null;
56
+ agent_id: string;
57
+ org_id?: string | null;
58
+ agent_name?: string | null;
59
+ durable_mode?: "tracked" | "locked";
60
+ state: string;
61
+ state_reason?: string | null;
62
+ effective_state?: string;
63
+ cc_session_id?: string | null;
64
+ cwd?: string | null;
65
+ home_executor_id?: string | null;
66
+ home_executor?: {
67
+ id?: string;
68
+ machine_label?: string | null;
69
+ hostname?: string | null;
70
+ last_seen_at?: string | null;
71
+ revoked_at?: string | null;
72
+ grant_expires_at?: string | null;
73
+ } | null;
74
+ machine?: string | null;
75
+ spend_usd?: number | null;
76
+ guard_state?: {
77
+ spend_usd?: number | null;
78
+ budget_usd?: number | null;
79
+ } | null;
80
+ budget_usd?: number | null;
81
+ last_activity_at?: string | null;
82
+ updated_at?: string | null;
83
+ created_at?: string | null;
84
+ created_by?: string | null;
85
+ owner?: string | null;
86
+ agent_containment?: string | null;
87
+ [k: string]: unknown;
88
+ }
89
+ export declare function errorCodeOf(body: unknown): string | null;
90
+ export declare function errorDetailsOf(body: unknown): Record<string, unknown>;
91
+ export declare function errorMessageOf(body: unknown): string | null;
92
+ export declare class DurableApi {
93
+ private readonly deps;
94
+ constructor(deps: DurableHttpDeps);
95
+ private request;
96
+ private q;
97
+ /** The caller's own sessions in the active org, optionally by name and agent. */
98
+ listSessions(filter?: {
99
+ name?: string;
100
+ agentId?: string;
101
+ }): Promise<RegistryRow[]>;
102
+ /**
103
+ * GET /v1/code/entitlement in the active org (C01-R26): its `durable` field says whether this
104
+ * caller has the registry at all. The answer as is; the launcher decides (launch.ts).
105
+ */
106
+ entitlement(): Promise<ApiAnswer>;
107
+ getSession(cid: string, agentId: string, include?: "goal"): Promise<ApiAnswer>;
108
+ createSession(cid: string, agentId: string, body: Record<string, unknown>): Promise<ApiAnswer>;
109
+ /** An owner session verb (S1 ROUTES.own_*). */
110
+ ownerVerb(route: RouteName, cid: string, agentId: string, body?: unknown): Promise<ApiAnswer>;
111
+ /** POST ROUTES.own_lock with the closed S1 lock body (spec, executor_id, own_agent). */
112
+ lock(cid: string, agentId: string, body: unknown): Promise<ApiAnswer>;
113
+ /**
114
+ * The read-only lock preflight for `mnemom agent doctor` (C02-R19): a GET on the lock route with
115
+ * dry_run, so it never mutates (A2 owns the route; see commands/durable-doctor.ts).
116
+ */
117
+ lockPreflight(cid: string, agentId: string): Promise<ApiAnswer>;
118
+ /** The existing org containment route; JWT only (the route refuses API keys). */
119
+ containment(orgId: string, agentId: string, verb: "pause" | "resume"): Promise<ApiAnswer>;
120
+ /** The agent record (for its org id). */
121
+ getAgent(agentId: string): Promise<ApiAnswer>;
122
+ /** Agents by name across the caller's orgs (containment by name). */
123
+ agentsByName(name: string): Promise<Array<{
124
+ id: string;
125
+ org_id: string;
126
+ }>>;
127
+ /** RFC 8628 device token poll for an owner_mutation device step (issues no token). */
128
+ devicePoll(deviceCode: string, clientId: string): Promise<ApiAnswer>;
129
+ credentialType(): Promise<DurableCredential["type"]>;
130
+ }
131
+ /** A non-200 list answer the caller turns into a refusal. */
132
+ export declare class RegistryAnswerError extends Error {
133
+ readonly answer: ApiAnswer;
134
+ constructor(answer: ApiAnswer);
135
+ }
136
+ export declare function rowsOf(body: unknown): RegistryRow[];
@@ -0,0 +1,220 @@
1
+ /**
2
+ * The durable registry and owner-verb client (BACKBONE C1, A2; S1 routes.ts ROUTES.own_*).
3
+ *
4
+ * Every call returns `{status, body}` and never throws on an HTTP answer: the commands decide from
5
+ * the status and the S1 code. A transport failure, a timeout or a 5xx (other than a durable 503 the
6
+ * caller must surface, which keeps its body) is a `RegistryUnavailable`, so a launch fails closed
7
+ * with `session_registry_unavailable` (C01-R18) instead of minting a conversation it could not
8
+ * resolve. The timeout is fixed in code: REGISTRY_TIMEOUT_MS covers connect plus total.
9
+ *
10
+ * Credentials (S1 owner-credential rule; BACKBONE owner Q6): the owner's JWT, else the personal
11
+ * org `mnm_` API key (MNEMOM_API_KEY). Org containment and device-step polls are JWT-only; the
12
+ * caller asks for that with `jwtOnly`.
13
+ *
14
+ * Not frozen by S1 (A2 owns them; recorded in notes-c1.md as the cross-piece assumption):
15
+ * GET /v1/agent/sessions?name=&agent_id=&org_id= -> {sessions: Row[]} (or a bare array)
16
+ * POST /v1/agent/sessions/{cid}?agent_id= -> 201/200 Row (create)
17
+ * GET /v1/agent/sessions/{cid}/lock?agent_id=&dry_run=true -> the doctor's lock preflight
18
+ */
19
+ import { randomUUID } from "node:crypto";
20
+ import { routePath } from "../../vendor/shared/durable-contract/routes.js";
21
+ /** CLI registry timeout, connect plus total (C01-R18: at most 10 s). */
22
+ export const REGISTRY_TIMEOUT_MS = 10_000;
23
+ /** The registry could not be read: transport error, timeout or a non-durable 5xx. */
24
+ export class RegistryUnavailable extends Error {
25
+ constructor(detail) {
26
+ super(detail);
27
+ this.name = "RegistryUnavailable";
28
+ }
29
+ }
30
+ export function errorCodeOf(body) {
31
+ if (body === null || typeof body !== "object")
32
+ return null;
33
+ const b = body;
34
+ const e = b.error;
35
+ if (e !== null &&
36
+ typeof e === "object" &&
37
+ typeof e.code === "string")
38
+ return e.code;
39
+ if (typeof b.code === "string")
40
+ return b.code;
41
+ if (typeof e === "string")
42
+ return e;
43
+ return null;
44
+ }
45
+ export function errorDetailsOf(body) {
46
+ if (body === null || typeof body !== "object")
47
+ return {};
48
+ const b = body;
49
+ const e = b.error;
50
+ if (e !== null && typeof e === "object") {
51
+ const d = e.details;
52
+ if (d !== null && typeof d === "object")
53
+ return d;
54
+ }
55
+ if (b.details !== null && typeof b.details === "object")
56
+ return b.details;
57
+ return {};
58
+ }
59
+ export function errorMessageOf(body) {
60
+ if (body === null || typeof body !== "object")
61
+ return null;
62
+ const b = body;
63
+ const e = b.error;
64
+ if (e !== null &&
65
+ typeof e === "object" &&
66
+ typeof e.message === "string")
67
+ return e.message;
68
+ if (typeof b.message === "string")
69
+ return b.message;
70
+ return null;
71
+ }
72
+ export class DurableApi {
73
+ deps;
74
+ constructor(deps) {
75
+ this.deps = deps;
76
+ }
77
+ async request(method, path, opts = {}) {
78
+ const cred = await this.deps.credential();
79
+ const headers = { Accept: "application/json" };
80
+ if (cred.type === "jwt")
81
+ headers.Authorization = `Bearer ${cred.token.trim()}`;
82
+ else if (cred.type === "api-key" && !opts.jwtOnly)
83
+ headers["X-Mnemom-Api-Key"] = cred.key.trim();
84
+ let body;
85
+ if (opts.form) {
86
+ headers["Content-Type"] = "application/x-www-form-urlencoded";
87
+ body = new URLSearchParams(opts.form).toString();
88
+ }
89
+ else if (opts.body !== undefined) {
90
+ headers["Content-Type"] = "application/json";
91
+ body = JSON.stringify(opts.body);
92
+ }
93
+ if (method === "POST")
94
+ headers["Idempotency-Key"] = randomUUID();
95
+ const url = new URL(path, this.deps.apiBase().replace(/\/+$/, "") + "/").href;
96
+ const ctrl = new AbortController();
97
+ const timer = setTimeout(() => ctrl.abort(), REGISTRY_TIMEOUT_MS);
98
+ let res;
99
+ let text;
100
+ try {
101
+ res = await this.deps.fetch(url, { method, headers, body, signal: ctrl.signal });
102
+ // The body read is inside the same budget: a stalled (aborted) body is a timeout too.
103
+ text = await res.text();
104
+ }
105
+ catch (e) {
106
+ throw new RegistryUnavailable(e instanceof Error ? e.message : String(e));
107
+ }
108
+ finally {
109
+ clearTimeout(timer);
110
+ }
111
+ let parsed = null;
112
+ if (text) {
113
+ try {
114
+ parsed = JSON.parse(text);
115
+ }
116
+ catch {
117
+ parsed = { error: { code: "unparseable_body", message: text.slice(0, 200) } };
118
+ }
119
+ }
120
+ // A 5xx is the registry being down unless it carries a durable code the caller surfaces.
121
+ if (res.status >= 500 && !(errorCodeOf(parsed) ?? "").startsWith("durable_")) {
122
+ throw new RegistryUnavailable(`HTTP ${res.status}`);
123
+ }
124
+ return { status: res.status, body: parsed };
125
+ }
126
+ q(params) {
127
+ const s = new URLSearchParams();
128
+ for (const [k, v] of Object.entries(params))
129
+ if (v !== undefined && v !== "")
130
+ s.set(k, v);
131
+ const t = s.toString();
132
+ return t ? `?${t}` : "";
133
+ }
134
+ /** The caller's own sessions in the active org, optionally by name and agent. */
135
+ async listSessions(filter = {}) {
136
+ const a = await this.request("GET", `/v1/agent/sessions${this.q({ name: filter.name, agent_id: filter.agentId, org_id: this.deps.activeOrgId() })}`);
137
+ // Every non-200, a bare 404 (the route absent: API flag off) included, goes to the caller,
138
+ // which maps 403/404 to the legacy launch (launch.ts registryRows).
139
+ if (a.status !== 200)
140
+ throw new RegistryAnswerError(a);
141
+ return rowsOf(a.body);
142
+ }
143
+ /**
144
+ * GET /v1/code/entitlement in the active org (C01-R26): its `durable` field says whether this
145
+ * caller has the registry at all. The answer as is; the launcher decides (launch.ts).
146
+ */
147
+ async entitlement() {
148
+ return this.request("GET", `/v1/code/entitlement${this.q({ org_id: this.deps.activeOrgId() })}`);
149
+ }
150
+ async getSession(cid, agentId, include) {
151
+ return this.request("GET", `/v1/agent/sessions/${encodeURIComponent(cid)}${this.q({ agent_id: agentId, include })}`);
152
+ }
153
+ async createSession(cid, agentId, body) {
154
+ return this.request("POST", `/v1/agent/sessions/${encodeURIComponent(cid)}${this.q({ agent_id: agentId })}`, { body });
155
+ }
156
+ /** An owner session verb (S1 ROUTES.own_*). */
157
+ async ownerVerb(route, cid, agentId, body = {}) {
158
+ return this.request("POST", `${routePath(route, cid)}${this.q({ agent_id: agentId })}`, {
159
+ body,
160
+ });
161
+ }
162
+ /** POST ROUTES.own_lock with the closed S1 lock body (spec, executor_id, own_agent). */
163
+ async lock(cid, agentId, body) {
164
+ return this.ownerVerb("own_lock", cid, agentId, body);
165
+ }
166
+ /**
167
+ * The read-only lock preflight for `mnemom agent doctor` (C02-R19): a GET on the lock route with
168
+ * dry_run, so it never mutates (A2 owns the route; see commands/durable-doctor.ts).
169
+ */
170
+ async lockPreflight(cid, agentId) {
171
+ return this.request("GET", `${routePath("own_lock", cid)}${this.q({ agent_id: agentId, dry_run: "true" })}`);
172
+ }
173
+ /** The existing org containment route; JWT only (the route refuses API keys). */
174
+ async containment(orgId, agentId, verb) {
175
+ return this.request("POST", `/v1/orgs/${encodeURIComponent(orgId)}/agents/${encodeURIComponent(agentId)}/${verb}`, { body: {}, jwtOnly: true });
176
+ }
177
+ /** The agent record (for its org id). */
178
+ async getAgent(agentId) {
179
+ return this.request("GET", `/v1/agents/${encodeURIComponent(agentId)}`);
180
+ }
181
+ /** Agents by name across the caller's orgs (containment by name). */
182
+ async agentsByName(name) {
183
+ return this.deps.agentsByName ? this.deps.agentsByName(name) : [];
184
+ }
185
+ /** RFC 8628 device token poll for an owner_mutation device step (issues no token). */
186
+ async devicePoll(deviceCode, clientId) {
187
+ return this.request("POST", "/v1/oauth/token", {
188
+ form: {
189
+ grant_type: "urn:ietf:params:oauth:grant-type:device_code",
190
+ device_code: deviceCode,
191
+ client_id: clientId,
192
+ },
193
+ });
194
+ }
195
+ async credentialType() {
196
+ return (await this.deps.credential()).type;
197
+ }
198
+ }
199
+ /** A non-200 list answer the caller turns into a refusal. */
200
+ export class RegistryAnswerError extends Error {
201
+ answer;
202
+ constructor(answer) {
203
+ super(errorMessageOf(answer.body) ?? `HTTP ${answer.status}`);
204
+ this.answer = answer;
205
+ this.name = "RegistryAnswerError";
206
+ }
207
+ }
208
+ export function rowsOf(body) {
209
+ const isRow = (r) => r !== null && typeof r === "object" && typeof r.conversation_id === "string";
210
+ if (Array.isArray(body))
211
+ return body.filter(isRow);
212
+ if (body !== null && typeof body === "object") {
213
+ for (const k of ["sessions", "rows", "items", "data"]) {
214
+ const v = body[k];
215
+ if (Array.isArray(v))
216
+ return v.filter(isRow);
217
+ }
218
+ }
219
+ return [];
220
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Every S1 durable code keyed by itself, typed to its literal: `CODE.durable_session_ended` is that
3
+ * code's string. C1 names durable codes only through this map (S1
4
+ * FREEZE_STRUCTURED.CODES, built from DURABLE_CODES), so a code S1 does not freeze fails to compile
5
+ * and no durable code is restated as a quoted literal outside S1 (C10-STATIC-001).
6
+ */
7
+ import type { DurableCode } from "../../vendor/shared/durable-contract/codes.js";
8
+ export declare const CODE: { readonly [K in DurableCode]: K; };
@@ -0,0 +1,2 @@
1
+ import { FREEZE_STRUCTURED } from "../../vendor/shared/durable-contract/freeze-record.js";
2
+ export const CODE = FREEZE_STRUCTURED.CODES;
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Lock after the first turn (ruling dogfood B5). A launch that locks a NEW Claude Code session
3
+ * (`durable = always`, or ask answered yes, with no transcript yet) cannot lock before claude has
4
+ * run: the daemon's seat resumes the session's transcript, and there is none until the first turn
5
+ * writes it (host/transcript.ts ensureTranscript answers lost, the session fails transcript_lost).
6
+ *
7
+ * So the launcher spawns claude attended and watches the transcript. Once the first turn has
8
+ * completed it locks, under the running-session handover (C02-R26: freeze, commit, the seat's
9
+ * hello, then the original is killed and the parent attaches to the seat). Never before. A lock
10
+ * refusal at that point leaves the attended session running and prints the refusal with its fix
11
+ * line.
12
+ *
13
+ * No I/O here: lib/durable/live.ts wires the transcript reads, the lock and the clock.
14
+ */
15
+ import type { LockOutcome, TranscriptDirs } from "../../commands/durable-lock.js";
16
+ import { DurableRefusal } from "./refusal.js";
17
+ /** How often the launcher re-reads the transcript while it waits for the first turn. */
18
+ export declare const FIRST_TURN_POLL_MS = 1000;
19
+ /**
20
+ * Whether a Claude Code transcript (JSONL) holds a completed turn: an assistant entry whose
21
+ * stop_reason ends the turn (end_turn, max_tokens, stop_sequence, refusal), or the engine's own
22
+ * turn-end record (system turn_duration or stop_hook_summary). Unparseable lines (a line still
23
+ * being written) are skipped.
24
+ */
25
+ export declare function turnCompleted(jsonl: string): boolean;
26
+ /**
27
+ * Reads `<projects>/<dir>/<cc>.jsonl` from the first project dir that holds it; null while none
28
+ * does, when it cannot be read (it is read again on the next poll), or with no CC session id.
29
+ */
30
+ export declare function transcriptReader(projects: string, dirs: TranscriptDirs, cc: string | null | undefined, read: (path: string) => string): () => string | null;
31
+ export interface FirstTurnLockDeps {
32
+ /** The session name (the fix line of a lock that could not be sent). */
33
+ name: string;
34
+ /** The session's transcript text, or null while Claude Code has written none. */
35
+ readTranscript(): string | null;
36
+ /** The attended claude is still running. */
37
+ childAlive(): boolean;
38
+ /** The lock of the row under the running-session handover (lockResolvedRow). */
39
+ lock(): Promise<LockOutcome>;
40
+ /** Print a lock refusal and its fix line (the session stays attended). */
41
+ printRefusal(r: DurableRefusal): void;
42
+ sleep(ms: number): Promise<void>;
43
+ }
44
+ export type FirstTurnLock = "locked" | "refused" | "ended";
45
+ /**
46
+ * Wait for the first completed turn, then lock. "ended": claude exited first, nothing was locked.
47
+ * "locked": the handover took the session (the parent's exit path attaches to the seat).
48
+ * "refused": the refusal was printed and the attended session goes on.
49
+ */
50
+ export declare function lockAfterFirstTurn(d: FirstTurnLockDeps, pollMs?: number): Promise<FirstTurnLock>;
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Lock after the first turn (ruling dogfood B5). A launch that locks a NEW Claude Code session
3
+ * (`durable = always`, or ask answered yes, with no transcript yet) cannot lock before claude has
4
+ * run: the daemon's seat resumes the session's transcript, and there is none until the first turn
5
+ * writes it (host/transcript.ts ensureTranscript answers lost, the session fails transcript_lost).
6
+ *
7
+ * So the launcher spawns claude attended and watches the transcript. Once the first turn has
8
+ * completed it locks, under the running-session handover (C02-R26: freeze, commit, the seat's
9
+ * hello, then the original is killed and the parent attaches to the seat). Never before. A lock
10
+ * refusal at that point leaves the attended session running and prints the refusal with its fix
11
+ * line.
12
+ *
13
+ * No I/O here: lib/durable/live.ts wires the transcript reads, the lock and the clock.
14
+ */
15
+ import { join } from "node:path";
16
+ import { DurableRefusal } from "./refusal.js";
17
+ /** How often the launcher re-reads the transcript while it waits for the first turn. */
18
+ export const FIRST_TURN_POLL_MS = 1000;
19
+ /** An assistant stop that ends the turn: anything but a tool call or a paused turn. */
20
+ const TURN_CONTINUES = new Set(["tool_use", "pause_turn"]);
21
+ /**
22
+ * Whether a Claude Code transcript (JSONL) holds a completed turn: an assistant entry whose
23
+ * stop_reason ends the turn (end_turn, max_tokens, stop_sequence, refusal), or the engine's own
24
+ * turn-end record (system turn_duration or stop_hook_summary). Unparseable lines (a line still
25
+ * being written) are skipped.
26
+ */
27
+ export function turnCompleted(jsonl) {
28
+ for (const line of jsonl.split("\n")) {
29
+ let e;
30
+ try {
31
+ e = JSON.parse(line);
32
+ }
33
+ catch {
34
+ continue;
35
+ }
36
+ if (e === null || typeof e !== "object")
37
+ continue;
38
+ const r = e;
39
+ if (r.type === "system" && (r.subtype === "turn_duration" || r.subtype === "stop_hook_summary"))
40
+ return true;
41
+ if (r.type !== "assistant" || r.message === null || typeof r.message !== "object")
42
+ continue;
43
+ const stop = r.message.stop_reason;
44
+ if (typeof stop === "string" && !TURN_CONTINUES.has(stop))
45
+ return true;
46
+ }
47
+ return false;
48
+ }
49
+ /**
50
+ * Reads `<projects>/<dir>/<cc>.jsonl` from the first project dir that holds it; null while none
51
+ * does, when it cannot be read (it is read again on the next poll), or with no CC session id.
52
+ */
53
+ export function transcriptReader(projects, dirs, cc, read) {
54
+ return () => {
55
+ if (!cc)
56
+ return null;
57
+ const found = dirs(cc)?.[0];
58
+ if (found === undefined)
59
+ return null;
60
+ try {
61
+ return read(join(projects, found, `${cc}.jsonl`));
62
+ }
63
+ catch {
64
+ return null;
65
+ }
66
+ };
67
+ }
68
+ /**
69
+ * Wait for the first completed turn, then lock. "ended": claude exited first, nothing was locked.
70
+ * "locked": the handover took the session (the parent's exit path attaches to the seat).
71
+ * "refused": the refusal was printed and the attended session goes on.
72
+ */
73
+ export async function lockAfterFirstTurn(d, pollMs = FIRST_TURN_POLL_MS) {
74
+ for (;;) {
75
+ if (!d.childAlive())
76
+ return "ended";
77
+ const t = d.readTranscript();
78
+ if (t !== null && turnCompleted(t))
79
+ break;
80
+ await d.sleep(pollMs);
81
+ }
82
+ let o;
83
+ try {
84
+ o = await d.lock();
85
+ }
86
+ catch (e) {
87
+ o = {
88
+ ok: false,
89
+ refusal: e instanceof DurableRefusal
90
+ ? e
91
+ : new DurableRefusal({
92
+ code: "session_registry_unavailable",
93
+ message: `mnemom agent: the lock after the first turn failed (${e instanceof Error ? e.message : String(e)}); the session stays attended`,
94
+ fix: `\`mnemom agent lock ${d.name}\``,
95
+ }),
96
+ };
97
+ }
98
+ if (o.ok)
99
+ return "locked";
100
+ d.printRefusal(o.refusal);
101
+ return "refused";
102
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * The CLI `durable` gate (BACKBONE Flags/CLI; C17-R10; amendments 26, 35).
3
+ *
4
+ * While `durable` is off every durable verb word exits 2 with `gatedMessage(…, 'durable')`, sends
5
+ * no HTTP, writes nothing under HOME and starts no process — `--help` and `--json` included, which
6
+ * is why the check runs on raw argv before commander parses it (commander answers `--help` before
7
+ * any preAction hook). The words are the declared allowlisted difference: a session named `lock`
8
+ * or `containment` is not launched by name while durable is off.
9
+ *
10
+ * `mnemom agent host run` is gated like every other word until C2 registers its handler (C2 then
11
+ * reads the gate from config.json only, C12-R25); ungated, it would fall through to the launcher as
12
+ * a session named "host". The pre-existing `mnemom agent sessions` / `show` are not gated.
13
+ */
14
+ import { type ExperimentalId } from "../experimental.js";
15
+ export declare const DURABLE_ID: "durable";
16
+ /** `mnemom agent <word>` words that exist only with durable on. */
17
+ export declare const DURABLE_VERB_WORDS: readonly ["lock", "pause", "resume", "done", "kill", "amend", "host", "containment"];
18
+ /** `mnemom agent sessions <sub>` sub-verbs that exist only with durable on. */
19
+ export declare const DURABLE_SESSIONS_SUBVERBS: readonly ["unlock", "adopt"];
20
+ export declare function durableOn(enabled: readonly ExperimentalId[]): boolean;
21
+ /**
22
+ * The options that take a value, by flag spelling, as commander parses them: a `required` one always
23
+ * takes the next word; an `optional` one takes it only when it does not start with `-`. Every other
24
+ * option is a bare flag. The scan uses them to step over options placed before the verb word.
25
+ */
26
+ export interface OptionShapes {
27
+ required: ReadonlySet<string>;
28
+ optional: ReadonlySet<string>;
29
+ }
30
+ /** The value-taking options of `commands` (commander `Command#options`), merged. */
31
+ export declare function optionShapesOf(commands: ReadonlyArray<{
32
+ options: readonly unknown[];
33
+ }>): OptionShapes;
34
+ /**
35
+ * The gated use `argv` (process.argv.slice(2)) makes, as the `what` of the gate message, or null
36
+ * when argv names no durable verb. The verb is the first operand after `agent`, with options (and
37
+ * their values, per `shapes`) stepped over exactly as commander would before it dispatches the
38
+ * subcommand, so `agent --new lock x` is gated like `agent lock x`. A word after `--` or as a later
39
+ * positional is not a verb use.
40
+ */
41
+ export declare function durableVerbUse(argv: readonly string[], shapes?: OptionShapes): string | null;
42
+ /**
43
+ * The guard inside every durable verb's action: the pointer (exit 2) while durable is off, else
44
+ * null. The argv gate above runs first; this one holds for any argv shape that reaches commander.
45
+ */
46
+ export declare function durableActionGate(what: string, enabled: readonly ExperimentalId[]): string | null;
47
+ /** The gate decision for argv: null when allowed, else the stderr message (exit 2). */
48
+ export declare function durableGate(argv: readonly string[], enabled: readonly ExperimentalId[], shapes?: OptionShapes): string | null;