@threadbase-sh/streamer 1.63.0 → 1.64.0

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 (296) hide show
  1. package/dist/agent/agent-client.d.ts +14 -0
  2. package/dist/agent/agent-client.d.ts.map +1 -0
  3. package/dist/agent/agent-config.d.ts +24 -0
  4. package/dist/agent/agent-config.d.ts.map +1 -0
  5. package/dist/agent/conversation-writer.d.ts +13 -0
  6. package/dist/agent/conversation-writer.d.ts.map +1 -0
  7. package/dist/agent/dedupe.d.ts +6 -0
  8. package/dist/agent/dedupe.d.ts.map +1 -0
  9. package/dist/agent/errors.d.ts +23 -0
  10. package/dist/agent/errors.d.ts.map +1 -0
  11. package/dist/agent/handle-send-agent-input.d.ts +21 -0
  12. package/dist/agent/handle-send-agent-input.d.ts.map +1 -0
  13. package/dist/agent/handle-start-agent-session.d.ts +21 -0
  14. package/dist/agent/handle-start-agent-session.d.ts.map +1 -0
  15. package/dist/agent/history-mapper.d.ts +4 -0
  16. package/dist/agent/history-mapper.d.ts.map +1 -0
  17. package/dist/agent/payload-guard.d.ts +29 -0
  18. package/dist/agent/payload-guard.d.ts.map +1 -0
  19. package/dist/api/app.d.ts +28 -0
  20. package/dist/api/app.d.ts.map +1 -0
  21. package/dist/api/handlers/conversations.handlers.d.ts +69 -0
  22. package/dist/api/handlers/conversations.handlers.d.ts.map +1 -0
  23. package/dist/api/handlers/http-helpers.d.ts +50 -0
  24. package/dist/api/handlers/http-helpers.d.ts.map +1 -0
  25. package/dist/api/handlers/sessions.handlers.d.ts +230 -0
  26. package/dist/api/handlers/sessions.handlers.d.ts.map +1 -0
  27. package/dist/api/middleware/auth.middleware.d.ts +5 -0
  28. package/dist/api/middleware/auth.middleware.d.ts.map +1 -0
  29. package/dist/api/middleware/cors.middleware.d.ts +5 -0
  30. package/dist/api/middleware/cors.middleware.d.ts.map +1 -0
  31. package/dist/api/middleware/error.middleware.d.ts +4 -0
  32. package/dist/api/middleware/error.middleware.d.ts.map +1 -0
  33. package/dist/api/routes/backup.routes.d.ts +5 -0
  34. package/dist/api/routes/backup.routes.d.ts.map +1 -0
  35. package/dist/api/routes/browse.routes.d.ts +5 -0
  36. package/dist/api/routes/browse.routes.d.ts.map +1 -0
  37. package/dist/api/routes/cacheAlert.routes.d.ts +5 -0
  38. package/dist/api/routes/cacheAlert.routes.d.ts.map +1 -0
  39. package/dist/api/routes/config.routes.d.ts +5 -0
  40. package/dist/api/routes/config.routes.d.ts.map +1 -0
  41. package/dist/api/routes/conversations.routes.d.ts +5 -0
  42. package/dist/api/routes/conversations.routes.d.ts.map +1 -0
  43. package/dist/api/routes/devices.routes.d.ts +17 -0
  44. package/dist/api/routes/devices.routes.d.ts.map +1 -0
  45. package/dist/api/routes/diagnostics.routes.d.ts +5 -0
  46. package/dist/api/routes/diagnostics.routes.d.ts.map +1 -0
  47. package/dist/api/routes/health.routes.d.ts +5 -0
  48. package/dist/api/routes/health.routes.d.ts.map +1 -0
  49. package/dist/api/routes/logs.routes.d.ts +3 -0
  50. package/dist/api/routes/logs.routes.d.ts.map +1 -0
  51. package/dist/api/routes/misc.routes.d.ts +77 -0
  52. package/dist/api/routes/misc.routes.d.ts.map +1 -0
  53. package/dist/api/routes/pair.routes.d.ts +5 -0
  54. package/dist/api/routes/pair.routes.d.ts.map +1 -0
  55. package/dist/api/routes/progress.routes.d.ts +39 -0
  56. package/dist/api/routes/progress.routes.d.ts.map +1 -0
  57. package/dist/api/routes/projects.routes.d.ts +5 -0
  58. package/dist/api/routes/projects.routes.d.ts.map +1 -0
  59. package/dist/api/routes/providers.routes.d.ts +19 -0
  60. package/dist/api/routes/providers.routes.d.ts.map +1 -0
  61. package/dist/api/routes/scanner.routes.d.ts +5 -0
  62. package/dist/api/routes/scanner.routes.d.ts.map +1 -0
  63. package/dist/api/routes/sessions.routes.d.ts +6 -0
  64. package/dist/api/routes/sessions.routes.d.ts.map +1 -0
  65. package/dist/api/routes/ws.routes.d.ts +7 -0
  66. package/dist/api/routes/ws.routes.d.ts.map +1 -0
  67. package/dist/api/types/api-deps.d.ts +114 -0
  68. package/dist/api/types/api-deps.d.ts.map +1 -0
  69. package/dist/auth.d.ts +81 -0
  70. package/dist/auth.d.ts.map +1 -0
  71. package/dist/browse.d.ts +17 -0
  72. package/dist/browse.d.ts.map +1 -0
  73. package/dist/claude-flags.d.ts +76 -0
  74. package/dist/claude-flags.d.ts.map +1 -0
  75. package/dist/cli.cjs +20 -5
  76. package/dist/cli.cjs.map +1 -1
  77. package/dist/codex-pty-runner.d.ts +90 -0
  78. package/dist/codex-pty-runner.d.ts.map +1 -0
  79. package/dist/config/update-config.d.ts +13 -0
  80. package/dist/config/update-config.d.ts.map +1 -0
  81. package/dist/conversation-cache.d.ts +336 -0
  82. package/dist/conversation-cache.d.ts.map +1 -0
  83. package/dist/db/config.d.ts +11 -0
  84. package/dist/db/config.d.ts.map +1 -0
  85. package/dist/db/index.d.ts +5 -0
  86. package/dist/db/index.d.ts.map +1 -0
  87. package/dist/db/migrations.d.ts +3 -0
  88. package/dist/db/migrations.d.ts.map +1 -0
  89. package/dist/db/pool.d.ts +5 -0
  90. package/dist/db/pool.d.ts.map +1 -0
  91. package/dist/db/query-timing.d.ts +52 -0
  92. package/dist/db/query-timing.d.ts.map +1 -0
  93. package/dist/db/repositories/cacheMetadata.repository.d.ts +12 -0
  94. package/dist/db/repositories/cacheMetadata.repository.d.ts.map +1 -0
  95. package/dist/db/repositories/conversations.repository.d.ts +27 -0
  96. package/dist/db/repositories/conversations.repository.d.ts.map +1 -0
  97. package/dist/db/repositories/devices.repository.d.ts +137 -0
  98. package/dist/db/repositories/devices.repository.d.ts.map +1 -0
  99. package/dist/db/repositories/managed-sessions.repository.d.ts +127 -0
  100. package/dist/db/repositories/managed-sessions.repository.d.ts.map +1 -0
  101. package/dist/db/repositories/projects.repository.d.ts +28 -0
  102. package/dist/db/repositories/projects.repository.d.ts.map +1 -0
  103. package/dist/db/repositories/push.repository.d.ts +186 -0
  104. package/dist/db/repositories/push.repository.d.ts.map +1 -0
  105. package/dist/db/repositories/sessions.repository.d.ts +16 -0
  106. package/dist/db/repositories/sessions.repository.d.ts.map +1 -0
  107. package/dist/db/runtime-store.d.ts +80 -0
  108. package/dist/db/runtime-store.d.ts.map +1 -0
  109. package/dist/db/sqlite-migrate.d.ts +13 -0
  110. package/dist/db/sqlite-migrate.d.ts.map +1 -0
  111. package/dist/db/upload-records.d.ts +11 -0
  112. package/dist/db/upload-records.d.ts.map +1 -0
  113. package/dist/e2ee/noise.d.ts +261 -0
  114. package/dist/e2ee/noise.d.ts.map +1 -0
  115. package/dist/e2ee/pair-payload.d.ts +60 -0
  116. package/dist/e2ee/pair-payload.d.ts.map +1 -0
  117. package/dist/e2ee/pair-request.d.ts +24 -0
  118. package/dist/e2ee/pair-request.d.ts.map +1 -0
  119. package/dist/external-tails.d.ts +91 -0
  120. package/dist/external-tails.d.ts.map +1 -0
  121. package/dist/feature-flags.d.ts +212 -0
  122. package/dist/feature-flags.d.ts.map +1 -0
  123. package/dist/handlers/handleListProjects.d.ts +3 -0
  124. package/dist/handlers/handleListProjects.d.ts.map +1 -0
  125. package/dist/index.d.ts +18 -2571
  126. package/dist/index.d.ts.map +1 -0
  127. package/dist/lifecycle/constants.d.ts +21 -0
  128. package/dist/lifecycle/constants.d.ts.map +1 -0
  129. package/dist/lifecycle/process-liveness.d.ts +7 -0
  130. package/dist/lifecycle/process-liveness.d.ts.map +1 -0
  131. package/dist/live-session-manager.d.ts +59 -0
  132. package/dist/live-session-manager.d.ts.map +1 -0
  133. package/dist/logger.d.ts +14 -0
  134. package/dist/logger.d.ts.map +1 -0
  135. package/dist/pair-store.d.ts +60 -0
  136. package/dist/pair-store.d.ts.map +1 -0
  137. package/dist/platform.d.ts +25 -0
  138. package/dist/platform.d.ts.map +1 -0
  139. package/dist/process-discovery.d.ts +35 -0
  140. package/dist/process-discovery.d.ts.map +1 -0
  141. package/dist/providers.d.ts +7 -0
  142. package/dist/providers.d.ts.map +1 -0
  143. package/dist/pty-host/protocol.d.ts +270 -0
  144. package/dist/pty-host/protocol.d.ts.map +1 -0
  145. package/dist/pty-host/remote-session-runner.d.ts +133 -0
  146. package/dist/pty-host/remote-session-runner.d.ts.map +1 -0
  147. package/dist/pty-host/socket.d.ts +54 -0
  148. package/dist/pty-host/socket.d.ts.map +1 -0
  149. package/dist/pty-host/spawn-host.d.ts +30 -0
  150. package/dist/pty-host/spawn-host.d.ts.map +1 -0
  151. package/dist/pty-manager.d.ts +65 -0
  152. package/dist/pty-manager.d.ts.map +1 -0
  153. package/dist/pty-shared.d.ts +27 -0
  154. package/dist/pty-shared.d.ts.map +1 -0
  155. package/dist/scanner-manager.d.ts +126 -0
  156. package/dist/scanner-manager.d.ts.map +1 -0
  157. package/dist/schemas/cacheAlert.schema.d.ts +14 -0
  158. package/dist/schemas/cacheAlert.schema.d.ts.map +1 -0
  159. package/dist/schemas/claudeFlags.schema.d.ts +7 -0
  160. package/dist/schemas/claudeFlags.schema.d.ts.map +1 -0
  161. package/dist/schemas/project.schema.d.ts +18 -0
  162. package/dist/schemas/project.schema.d.ts.map +1 -0
  163. package/dist/schemas/updateConfig.schema.d.ts +19 -0
  164. package/dist/schemas/updateConfig.schema.d.ts.map +1 -0
  165. package/dist/seal.d.ts +14 -0
  166. package/dist/seal.d.ts.map +1 -0
  167. package/dist/server-identity.d.ts +43 -0
  168. package/dist/server-identity.d.ts.map +1 -0
  169. package/dist/server-wiring.d.ts +184 -0
  170. package/dist/server-wiring.d.ts.map +1 -0
  171. package/dist/server.d.ts +390 -0
  172. package/dist/server.d.ts.map +1 -0
  173. package/dist/services/backup/backup.d.ts +103 -0
  174. package/dist/services/backup/backup.d.ts.map +1 -0
  175. package/dist/services/cache/cacheMetadata.d.ts +8 -0
  176. package/dist/services/cache/cacheMetadata.d.ts.map +1 -0
  177. package/dist/services/cache-integrity/alertStore.d.ts +24 -0
  178. package/dist/services/cache-integrity/alertStore.d.ts.map +1 -0
  179. package/dist/services/cache-integrity/backup.d.ts +8 -0
  180. package/dist/services/cache-integrity/backup.d.ts.map +1 -0
  181. package/dist/services/cache-integrity/cacheIntegrityMonitor.d.ts +71 -0
  182. package/dist/services/cache-integrity/cacheIntegrityMonitor.d.ts.map +1 -0
  183. package/dist/services/conversations/conversationWatcher.d.ts +99 -0
  184. package/dist/services/conversations/conversationWatcher.d.ts.map +1 -0
  185. package/dist/services/conversations/findSearchTarget.d.ts +30 -0
  186. package/dist/services/conversations/findSearchTarget.d.ts.map +1 -0
  187. package/dist/services/conversations/isAgentConversation.d.ts +8 -0
  188. package/dist/services/conversations/isAgentConversation.d.ts.map +1 -0
  189. package/dist/services/conversations/pruneAgentConversations.d.ts +8 -0
  190. package/dist/services/conversations/pruneAgentConversations.d.ts.map +1 -0
  191. package/dist/services/conversations/refreshConversationCache.d.ts +27 -0
  192. package/dist/services/conversations/refreshConversationCache.d.ts.map +1 -0
  193. package/dist/services/conversations/shouldRefreshProjectsFromHdd.d.ts +33 -0
  194. package/dist/services/conversations/shouldRefreshProjectsFromHdd.d.ts.map +1 -0
  195. package/dist/services/diagnostics/diagnostics.d.ts +66 -0
  196. package/dist/services/diagnostics/diagnostics.d.ts.map +1 -0
  197. package/dist/services/projectChats/deriveProjectChatTitle.d.ts +15 -0
  198. package/dist/services/projectChats/deriveProjectChatTitle.d.ts.map +1 -0
  199. package/dist/services/projects/ensureProjectsForConversations.d.ts +15 -0
  200. package/dist/services/projects/ensureProjectsForConversations.d.ts.map +1 -0
  201. package/dist/services/providers/capabilities.d.ts +105 -0
  202. package/dist/services/providers/capabilities.d.ts.map +1 -0
  203. package/dist/services/providers/providerHealth.d.ts +72 -0
  204. package/dist/services/providers/providerHealth.d.ts.map +1 -0
  205. package/dist/services/push/apnsClient.d.ts +105 -0
  206. package/dist/services/push/apnsClient.d.ts.map +1 -0
  207. package/dist/services/push/expoPushSender.d.ts +37 -0
  208. package/dist/services/push/expoPushSender.d.ts.map +1 -0
  209. package/dist/services/push/liveActivityContentState.d.ts +60 -0
  210. package/dist/services/push/liveActivityContentState.d.ts.map +1 -0
  211. package/dist/services/push/liveActivityNotifier.d.ts +49 -0
  212. package/dist/services/push/liveActivityNotifier.d.ts.map +1 -0
  213. package/dist/services/push/liveActivityRenewal.d.ts +63 -0
  214. package/dist/services/push/liveActivityRenewal.d.ts.map +1 -0
  215. package/dist/services/push/liveActivitySender.d.ts +88 -0
  216. package/dist/services/push/liveActivitySender.d.ts.map +1 -0
  217. package/dist/services/push/waitingInputNotifier.d.ts +41 -0
  218. package/dist/services/push/waitingInputNotifier.d.ts.map +1 -0
  219. package/dist/services/questions/answersToKeystrokes.d.ts +8 -0
  220. package/dist/services/questions/answersToKeystrokes.d.ts.map +1 -0
  221. package/dist/services/questions/codexGateAnswers.d.ts +14 -0
  222. package/dist/services/questions/codexGateAnswers.d.ts.map +1 -0
  223. package/dist/services/questions/codexScreen.d.ts +74 -0
  224. package/dist/services/questions/codexScreen.d.ts.map +1 -0
  225. package/dist/services/questions/detectAskUserQuestion.d.ts +6 -0
  226. package/dist/services/questions/detectAskUserQuestion.d.ts.map +1 -0
  227. package/dist/services/questions/detectPermissionGate.d.ts +62 -0
  228. package/dist/services/questions/detectPermissionGate.d.ts.map +1 -0
  229. package/dist/services/questions/detectQuestionFromScreen.d.ts +30 -0
  230. package/dist/services/questions/detectQuestionFromScreen.d.ts.map +1 -0
  231. package/dist/services/questions/detectShellPrompt.d.ts +18 -0
  232. package/dist/services/questions/detectShellPrompt.d.ts.map +1 -0
  233. package/dist/services/questions/parseAgentPhase.d.ts +12 -0
  234. package/dist/services/questions/parseAgentPhase.d.ts.map +1 -0
  235. package/dist/services/questions/parseStatusLine.d.ts +19 -0
  236. package/dist/services/questions/parseStatusLine.d.ts.map +1 -0
  237. package/dist/services/questions/questionBroadcast.d.ts +18 -0
  238. package/dist/services/questions/questionBroadcast.d.ts.map +1 -0
  239. package/dist/services/questions/resolveAnswer.d.ts +16 -0
  240. package/dist/services/questions/resolveAnswer.d.ts.map +1 -0
  241. package/dist/services/search/searchQuery.d.ts +78 -0
  242. package/dist/services/search/searchQuery.d.ts.map +1 -0
  243. package/dist/services/security/capabilities.d.ts +50 -0
  244. package/dist/services/security/capabilities.d.ts.map +1 -0
  245. package/dist/services/sessions/autoResumeOnBoot.d.ts +28 -0
  246. package/dist/services/sessions/autoResumeOnBoot.d.ts.map +1 -0
  247. package/dist/services/sessions/codexRolloutOwner.d.ts +55 -0
  248. package/dist/services/sessions/codexRolloutOwner.d.ts.map +1 -0
  249. package/dist/services/sessions/conversationBusy.d.ts +32 -0
  250. package/dist/services/sessions/conversationBusy.d.ts.map +1 -0
  251. package/dist/services/sessions/idempotency.d.ts +67 -0
  252. package/dist/services/sessions/idempotency.d.ts.map +1 -0
  253. package/dist/services/sessions/reconcileSessions.d.ts +63 -0
  254. package/dist/services/sessions/reconcileSessions.d.ts.map +1 -0
  255. package/dist/services/sessions/rehydrateSessions.d.ts +68 -0
  256. package/dist/services/sessions/rehydrateSessions.d.ts.map +1 -0
  257. package/dist/services/sessions/resumeIdentity.d.ts +16 -0
  258. package/dist/services/sessions/resumeIdentity.d.ts.map +1 -0
  259. package/dist/session-registry-boot.d.ts +142 -0
  260. package/dist/session-registry-boot.d.ts.map +1 -0
  261. package/dist/session-store.d.ts +32 -0
  262. package/dist/session-store.d.ts.map +1 -0
  263. package/dist/session-watchers.d.ts +60 -0
  264. package/dist/session-watchers.d.ts.map +1 -0
  265. package/dist/types.d.ts +616 -0
  266. package/dist/types.d.ts.map +1 -0
  267. package/dist/uploads.d.ts +16 -0
  268. package/dist/uploads.d.ts.map +1 -0
  269. package/dist/utils/bootToken.d.ts +3 -0
  270. package/dist/utils/bootToken.d.ts.map +1 -0
  271. package/dist/utils/canonicalizeFilePath.d.ts +75 -0
  272. package/dist/utils/canonicalizeFilePath.d.ts.map +1 -0
  273. package/dist/utils/canonicalizeProjectPath.d.ts +13 -0
  274. package/dist/utils/canonicalizeProjectPath.d.ts.map +1 -0
  275. package/dist/utils/codexConversationLine.d.ts +38 -0
  276. package/dist/utils/codexConversationLine.d.ts.map +1 -0
  277. package/dist/utils/conversationEtag.d.ts +19 -0
  278. package/dist/utils/conversationEtag.d.ts.map +1 -0
  279. package/dist/utils/dates.d.ts +12 -0
  280. package/dist/utils/dates.d.ts.map +1 -0
  281. package/dist/utils/debounce.d.ts +15 -0
  282. package/dist/utils/debounce.d.ts.map +1 -0
  283. package/dist/utils/deriveSessionName.d.ts +10 -0
  284. package/dist/utils/deriveSessionName.d.ts.map +1 -0
  285. package/dist/utils/fileIdentity.d.ts +36 -0
  286. package/dist/utils/fileIdentity.d.ts.map +1 -0
  287. package/dist/utils/isScannedSnapshotStale.d.ts +16 -0
  288. package/dist/utils/isScannedSnapshotStale.d.ts.map +1 -0
  289. package/dist/utils/scanProgressThrottle.d.ts +12 -0
  290. package/dist/utils/scanProgressThrottle.d.ts.map +1 -0
  291. package/dist/version.d.ts +3 -0
  292. package/dist/version.d.ts.map +1 -0
  293. package/dist/ws-hub.d.ts +15 -0
  294. package/dist/ws-hub.d.ts.map +1 -0
  295. package/package.json +3 -4
  296. package/dist/index.d.cts +0 -2571
@@ -0,0 +1,261 @@
1
+ import { type KeyObject } from "crypto";
2
+ export declare const NOISE_PROTOCOL_NAME = "Noise_IKpsk1_25519_ChaChaPoly_SHA256";
3
+ /**
4
+ * Prologue for the PAIRING handshake, mixed into the transcript before any
5
+ * token (spec §5.3, `MixHash(prologue)`).
6
+ *
7
+ * Not in design.md, and added deliberately. The design has two `IK` handshakes:
8
+ * this one at `/api/pair/exchange` with the pair token as PSK, and a later one
9
+ * at `/api/e2ee/open` against stored static keys with no PSK. Without a
10
+ * prologue the two transcripts differ only by the presence of the PSK, so
11
+ * "could a message from one be replayed into the other" becomes a question you
12
+ * answer by reasoning about the PSK. A prologue answers it by construction, and
13
+ * it costs one hash.
14
+ *
15
+ * **`"threadbase-e2ee/<version> <purpose>"` is a namespace, not one string.**
16
+ * Its sibling is `"threadbase-e2ee/1 open"` for `/api/e2ee/open`, named here
17
+ * rather than left for that phase to invent: half a domain separation is a
18
+ * property nobody has, and a convention chosen once with both instances visible
19
+ * is the one that survives a third handshake. The constant itself is not
20
+ * declared until something uses it.
21
+ *
22
+ * Changing this string is a silent, total incompatibility with tb-mobile — it
23
+ * is hashed into `h` before any token, so the only symptom is "decryption
24
+ * failed". It is pinned by a committed vector for that reason.
25
+ */
26
+ export declare const PAIR_PROLOGUE: Buffer<ArrayBuffer>;
27
+ /** `e` (32) + `s` sealed (32 + 16) — everything before message 1's payload. */
28
+ export declare const NOISE_MESSAGE_1_OVERHEAD: number;
29
+ /** `e` (32) — everything before message 2's payload. */
30
+ export declare const NOISE_MESSAGE_2_OVERHEAD: number;
31
+ /**
32
+ * Cap on a handshake message, checked BEFORE anything is allocated or parsed.
33
+ *
34
+ * The Noise spec's own transport limit is 65535 bytes. This is far below it
35
+ * because our payloads are a handful of JSON fields, and because
36
+ * `/api/pair/exchange` is a public, unauthenticated endpoint — the same
37
+ * reasoning D-9 applies to the unseal middleware, reaching this far forward.
38
+ */
39
+ export declare const NOISE_MAX_MESSAGE_BYTES = 4096;
40
+ export declare class NoiseError extends Error {
41
+ constructor(message: string);
42
+ }
43
+ export interface KeyPair {
44
+ publicKey: KeyObject;
45
+ privateKey: KeyObject;
46
+ /** Raw 32 bytes, which is what goes on the wire. */
47
+ publicKeyRaw: Buffer;
48
+ }
49
+ export declare function generateKeyPair(): KeyPair;
50
+ /**
51
+ * A `KeyPair` around a private key this process already holds.
52
+ *
53
+ * The bridge from `loadOrCreateServerIdentity()`, which owns the key file and
54
+ * returns a bare `KeyObject`, to the handshake, which needs the public half
55
+ * alongside it. Kept here rather than in server-identity.ts so that module
56
+ * stays about storing a key rather than about the protocol that uses it.
57
+ */
58
+ export declare function keyPairFrom(privateKey: KeyObject): KeyPair;
59
+ /**
60
+ * Raw 32 bytes out of a KeyObject. JWK OKP `x` is base64url of exactly that.
61
+ *
62
+ * Accepts either half: `createPublicKey` derives the public key from a private
63
+ * one but rejects a KeyObject that is already public, so the type is checked
64
+ * rather than the call being made unconditionally.
65
+ */
66
+ export declare function rawPublicKey(key: KeyObject): Buffer;
67
+ /**
68
+ * A KeyObject from raw bytes, or a `NoiseError`.
69
+ *
70
+ * This is a trust boundary: the bytes are the QR's `spk` on the client side and
71
+ * an attacker-supplied handshake message on the server side. A wrong length is
72
+ * rejected here rather than being padded, truncated, or handed to a DH that
73
+ * would fail somewhere less legible.
74
+ */
75
+ export declare function publicKeyFromRaw(raw: Buffer): KeyObject;
76
+ /**
77
+ * A keypair from a raw 32-byte private scalar.
78
+ *
79
+ * Exists for the committed test vectors: a vector is only a contract if the
80
+ * keys are fixed, and there is no other way to hand Node a chosen X25519
81
+ * private key. Not used by any production path — every real keypair comes from
82
+ * `generateKeyPair()` or the identity key file.
83
+ *
84
+ * Goes through PKCS#8 DER rather than JWK because a JWK OKP private key must
85
+ * carry a matching `x`, and the point here is to supply only `d`. The prefix is
86
+ * the fixed X25519 PrivateKeyInfo header (RFC 8410): SEQUENCE, version 0,
87
+ * AlgorithmIdentifier 1.3.101.110, then an OCTET STRING wrapping the 32-byte
88
+ * scalar.
89
+ */
90
+ export declare function keyPairFromRawPrivate(raw: Buffer): KeyPair;
91
+ /**
92
+ * The pair token as a 32-byte PSK.
93
+ *
94
+ * The token is `pt_<32 hex chars>` — 35 bytes of ASCII, and the spec requires
95
+ * the PSK to be exactly 32. Hashing with a domain-separating label is the
96
+ * standard way to fit it, and the label means this value can never collide with
97
+ * some other use of the same token elsewhere in the system.
98
+ *
99
+ * Both implementations must compute this identically or the handshake fails
100
+ * with no diagnostic beyond "decryption failed", which is why it is pinned by a
101
+ * test vector rather than left as an obvious detail.
102
+ */
103
+ export declare function pskFromPairToken(token: string): Buffer;
104
+ /**
105
+ * Noise's `CipherState` (spec §5.1).
106
+ *
107
+ * Exported for two reasons, neither of them scaffolding. Phase 3's record layer
108
+ * has to build a transport cipher from the buffers `split()` returns, so this is
109
+ * the type it will construct. And the §5.1 rule that a failed decryption must
110
+ * not advance `n` is unobservable through the handshake API — every AEAD
111
+ * operation there runs at `n = 0`, because each is preceded by a `MixKey` that
112
+ * calls `initializeKey`. Without a way to reach a cipher state directly, that
113
+ * rule is protected by nothing: a "simplification" that advances before
114
+ * verifying passes every other test in the suite.
115
+ */
116
+ export declare class CipherState {
117
+ private k;
118
+ private n;
119
+ initializeKey(key: Buffer | null): void;
120
+ hasKey(): boolean;
121
+ encryptWithAd(ad: Buffer, plaintext: Buffer): Buffer;
122
+ decryptWithAd(ad: Buffer, ciphertext: Buffer): Buffer;
123
+ }
124
+ declare class SymmetricState {
125
+ private ck;
126
+ private h;
127
+ private readonly cipher;
128
+ constructor(protocolName: string);
129
+ /**
130
+ * Noise's HKDF (spec §4.3) is RFC 5869 with `salt = chaining_key`,
131
+ * `ikm = input_key_material` and an EMPTY info, so Node's `hkdfSync` is
132
+ * exactly it rather than approximately it. Using the platform's HKDF removes
133
+ * the HMAC chain that would otherwise be the easiest thing here to get
134
+ * subtly wrong.
135
+ */
136
+ private hkdf;
137
+ mixKey(ikm: Buffer): void;
138
+ mixHash(data: Buffer): void;
139
+ /** Spec §5.2. Used only by the `psk` token. */
140
+ mixKeyAndHash(ikm: Buffer): void;
141
+ encryptAndHash(plaintext: Buffer): Buffer;
142
+ decryptAndHash(ciphertext: Buffer): Buffer;
143
+ /** The transcript hash. Both sides must arrive at the same value. */
144
+ handshakeHash(): Buffer;
145
+ /** Spec §5.2 `Split()`: the two directional transport keys. */
146
+ split(): {
147
+ k1: Buffer;
148
+ k2: Buffer;
149
+ };
150
+ }
151
+ export interface HandshakeKeys {
152
+ /** Transcript hash. `ctxId` is derived from it, so both sides agree without a round trip. */
153
+ handshakeHash: Buffer;
154
+ /** Initiator → responder traffic key. */
155
+ clientToServer: Buffer;
156
+ /** Responder → initiator traffic key. */
157
+ serverToClient: Buffer;
158
+ }
159
+ /**
160
+ * Message 1, from the phone.
161
+ *
162
+ * Exported from the *server* module although the server never sends one. It is
163
+ * the only way to drive the responder in a test, and it generates the committed
164
+ * vectors the tb-mobile implementation is checked against — a second, drifting
165
+ * initiator written inside a test file is the precise failure two independent
166
+ * implementations invite.
167
+ */
168
+ export declare function writeMessage1(args: {
169
+ staticKeyPair: KeyPair;
170
+ responderStaticPub: Buffer;
171
+ psk: Buffer;
172
+ payload: Buffer;
173
+ prologue?: Buffer;
174
+ /** Test seam only. Production always generates a fresh ephemeral. */
175
+ ephemeral?: KeyPair;
176
+ }): {
177
+ message: Buffer;
178
+ state: HandshakeInitiatorState;
179
+ };
180
+ export interface HandshakeInitiatorState {
181
+ symmetric: SymmetricState;
182
+ ephemeral: KeyPair;
183
+ staticKeyPair: KeyPair;
184
+ }
185
+ /** Message 2, read by the phone. Completes the handshake. */
186
+ export declare function readMessage2(state: HandshakeInitiatorState, message: Buffer): {
187
+ payload: Buffer;
188
+ keys: HandshakeKeys;
189
+ };
190
+ export interface ResponderResult {
191
+ /** The initiator's static public key, authenticated by the handshake. */
192
+ initiatorStaticPub: Buffer;
193
+ payload: Buffer;
194
+ message2: Buffer;
195
+ keys: HandshakeKeys;
196
+ }
197
+ /**
198
+ * Half-completed responder state, between reading message 1 and writing 2.
199
+ *
200
+ * Deliberately only ever a local: it is handed straight back into
201
+ * `writeMessage2` within the same synchronous stretch of one request, never
202
+ * stored, never keyed by anything a caller supplies. A responder state that
203
+ * outlived a request would be a thing to allocate and expire on a public
204
+ * endpoint, which is what the single-call shape originally avoided.
205
+ */
206
+ export interface HandshakeResponderState {
207
+ symmetric: SymmetricState;
208
+ /** The initiator's static public key, authenticated by message 1. */
209
+ initiatorStaticPub: Buffer;
210
+ /** Message 1's decrypted payload. */
211
+ payload: Buffer;
212
+ initiatorEphemeral: KeyObject;
213
+ initiatorStatic: KeyObject;
214
+ staticKeyPair: KeyPair;
215
+ }
216
+ /**
217
+ * Read message 1. Authenticates the initiator and recovers its static key.
218
+ *
219
+ * Split from `writeMessage2` because the server DOES now have something to do
220
+ * between them: message 2's payload carries the `deviceId`, and the device row
221
+ * cannot be written until the pair token has been spent, which cannot happen
222
+ * until this half has succeeded. The original single-call shape assumed nothing
223
+ * sat in the middle; the pairing handler is the caller that proved otherwise.
224
+ *
225
+ * SYNCHRONOUS on purpose, and it must stay that way: the caller runs this
226
+ * between validating the pair token and consuming it, and `PairTokenStore` has
227
+ * no lock. An `await` in that gap would let two concurrent requests with the
228
+ * same token both pass validation.
229
+ */
230
+ export declare function readMessage1(args: {
231
+ staticKeyPair: KeyPair;
232
+ psk: Buffer;
233
+ message1: Buffer;
234
+ prologue?: Buffer;
235
+ }): HandshakeResponderState;
236
+ /** Write message 2 and derive the transport keys. Completes the handshake. */
237
+ export declare function writeMessage2(state: HandshakeResponderState, responsePayload: Buffer,
238
+ /** Test seam only. Production always generates a fresh ephemeral. */
239
+ ephemeral?: KeyPair): {
240
+ message2: Buffer;
241
+ keys: HandshakeKeys;
242
+ };
243
+ /**
244
+ * Both halves in one call.
245
+ *
246
+ * Kept because most callers — every test, and any future responder with nothing
247
+ * to do in the middle — want the whole handshake, and because the two halves
248
+ * being separable should not force every one of them to sequence it by hand.
249
+ */
250
+ export declare function respond(args: {
251
+ staticKeyPair: KeyPair;
252
+ psk: Buffer;
253
+ message1: Buffer;
254
+ /** Built from the authenticated message-1 payload; sealed into message 2. */
255
+ buildPayload: (initiatorStaticPub: Buffer, payload: Buffer) => Buffer;
256
+ prologue?: Buffer;
257
+ /** Test seam only. */
258
+ ephemeral?: KeyPair;
259
+ }): ResponderResult;
260
+ export {};
261
+ //# sourceMappingURL=noise.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"noise.d.ts","sourceRoot":"","sources":["../../src/e2ee/noise.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * The device-registration inputs message 1 authenticates.
3
+ *
4
+ * `deviceName` is the one optional field in the contract, and is normalised to
5
+ * `null` when absent so both pairing paths hand `devicesRepo.register` the same
6
+ * shape. The 100-character bound is the legacy path's, kept identical rather
7
+ * than re-chosen: authenticating a value is not a reason to stop bounding it.
8
+ */
9
+ export interface E2eePairRegistration {
10
+ version: number;
11
+ deviceName: string | null;
12
+ readOnly: boolean;
13
+ }
14
+ /**
15
+ * Parse message 1's decrypted payload.
16
+ *
17
+ * Throws rather than defaulting. A client that completed the handshake and then
18
+ * sent an unreadable payload is a protocol violation, not an older client — an
19
+ * older client cannot reach this function at all, because it sends no `e2ee`
20
+ * field and no handshake is performed. Defaulting a missing `readOnly` to
21
+ * `false` would silently grant the wider capability preset off a payload the
22
+ * device never actually stated, which is the exact substitution this contract
23
+ * exists to prevent, arrived at through a parser instead of an intermediary.
24
+ *
25
+ * `v` is checked against the same version and with the same ordering as
26
+ * `parseE2eeRequest`: a version mismatch is not a malformed payload, and saying
27
+ * so is what lets a future client fall back deliberately.
28
+ */
29
+ export declare function parseE2eeMsg1Payload(payload: Buffer): E2eePairRegistration;
30
+ /**
31
+ * Every pairing result a new client persists or presents as verified.
32
+ *
33
+ * The outer response still carries compatibility copies of `deviceId`,
34
+ * `deviceToken`, `capabilities`, `publicUrl` and `machineName` for released
35
+ * clients that can read nothing else, and a new client ignores all of them —
36
+ * so this shape is the one that has to be complete. A field missing here is a
37
+ * field the new client would have to take from the unauthenticated outer copy,
38
+ * which is the same as not authenticating it at all.
39
+ *
40
+ * `deviceId` and `deviceToken` are non-nullable on purpose: an E2EE pairing
41
+ * that cannot produce them is a failed pairing, not a success carrying nulls.
42
+ */
43
+ export interface E2eeMsg2Payload {
44
+ v: number;
45
+ deviceId: string;
46
+ deviceToken: string;
47
+ capabilities: string[];
48
+ publicUrl: string | null;
49
+ machineName: string;
50
+ serverVersion: string;
51
+ /**
52
+ * Always `true`. Completing a handshake is what pins the device, and
53
+ * design.md §6.3 says nothing a client sends ever clears it — so this is a
54
+ * literal here rather than an argument, and there is no call site that can
55
+ * pass `false` by accident.
56
+ */
57
+ e2eeRequired: true;
58
+ }
59
+ export declare function encodeE2eeMsg2Payload(fields: Omit<E2eeMsg2Payload, "v" | "e2eeRequired">): Buffer;
60
+ //# sourceMappingURL=pair-payload.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pair-payload.d.ts","sourceRoot":"","sources":["../../src/e2ee/pair-payload.ts"],"names":[],"mappings":"AAoBA;;;;;;;GAOG;AACH,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,QAAQ,EAAE,OAAO,CAAC;CACnB;AAKD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,oBAAoB,CA+C1E;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,eAAe;IAC9B,CAAC,EAAE,MAAM,CAAC;IACV,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB;;;;;OAKG;IACH,YAAY,EAAE,IAAI,CAAC;CACpB;AAED,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,IAAI,CAAC,eAAe,EAAE,GAAG,GAAG,cAAc,CAAC,GAAG,MAAM,CAOjG"}
@@ -0,0 +1,24 @@
1
+ /** Envelope version this build speaks. Matches `E2EE_PROTOCOL_VERSION`. */
2
+ export declare const E2EE_EXCHANGE_VERSION = 1;
3
+ export declare class E2eeRequestError extends Error {
4
+ /** Stable code for the client, so a version mismatch is not a parse failure. */
5
+ readonly code: "E2EE_MALFORMED" | "E2EE_VERSION_UNSUPPORTED";
6
+ constructor(code: E2eeRequestError["code"], message: string);
7
+ }
8
+ export interface E2eeExchangeRequest {
9
+ version: number;
10
+ /** Noise `IK` message 1, decoded. */
11
+ message1: Buffer;
12
+ }
13
+ /**
14
+ * `null` for a client that did not ask for encryption; a parsed request for one
15
+ * that did; a throw for one that asked incoherently.
16
+ *
17
+ * The three are deliberately distinct. Collapsing "absent" into "malformed"
18
+ * breaks every released client, and collapsing "malformed" into "absent" would
19
+ * silently pair a client that believed it was encrypting — which is the
20
+ * downgrade the whole negotiation exists to prevent, arrived at through a
21
+ * parser rather than an attacker.
22
+ */
23
+ export declare function parseE2eeRequest(raw: unknown): E2eeExchangeRequest | null;
24
+ //# sourceMappingURL=pair-request.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pair-request.d.ts","sourceRoot":"","sources":["../../src/e2ee/pair-request.ts"],"names":[],"mappings":"AAgBA,2EAA2E;AAC3E,eAAO,MAAM,qBAAqB,IAAI,CAAC;AAYvC,qBAAa,gBAAiB,SAAQ,KAAK;IACzC,gFAAgF;IAChF,QAAQ,CAAC,IAAI,EAAE,gBAAgB,GAAG,0BAA0B,CAAC;IAE7D,YAAY,IAAI,EAAE,gBAAgB,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,MAAM,EAI1D;CACF;AAED,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,qCAAqC;IACrC,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,OAAO,GAAG,mBAAmB,GAAG,IAAI,CAsCzE"}
@@ -0,0 +1,91 @@
1
+ import { ConversationCache } from "./conversation-cache";
2
+ import type { CacheIntegrityMonitor } from "./services/cache-integrity/cacheIntegrityMonitor";
3
+ import type { ConversationWatcher } from "./services/conversations/conversationWatcher";
4
+ import type { SessionActivity, SessionResponse } from "./types";
5
+ import type { WSHub } from "./ws-hub";
6
+ export declare const EXTERNAL_TAIL_RECENCY_MS = 120000;
7
+ export declare const EXTERNAL_TAIL_MAX = 32;
8
+ export declare const EXTERNAL_TAIL_IDLE_MS = 300000;
9
+ export declare const EXTERNAL_ACTIVE_WRITING_MS = 30000;
10
+ export type ExternalTailEntry = {
11
+ conversationId: string;
12
+ lastActivityAt: number;
13
+ };
14
+ /**
15
+ * Everything ExternalTailManager reads from the server. Nullable collaborators
16
+ * are thunks rather than values because they are opened during listen() and
17
+ * rebound by the integrity monitor's reset-and-rescan — the same reason
18
+ * ApiDeps passes `cache: () => ConversationCache | null`.
19
+ *
20
+ * `tails` and `sessionFileMap` are the server's own Maps, held by reference:
21
+ * the server still owns them as instance fields (both are reached directly
22
+ * elsewhere in server.ts, and the tests read `externalTails` off the server),
23
+ * this class only drives them.
24
+ */
25
+ export type ExternalTailManagerDeps = {
26
+ tails: Map<string, ExternalTailEntry>;
27
+ sessionFileMap: Map<string, string>;
28
+ fileWatcher: ConversationWatcher;
29
+ wsHub: WSHub;
30
+ cache: () => ConversationCache | null;
31
+ cacheMonitor: () => CacheIntegrityMonitor | null;
32
+ broadcastConversationLines: (sessionId: string, lines: string[], seqs?: (number | null)[] | null) => void;
33
+ };
34
+ /**
35
+ * Owns the live tails on JSONLs no PTY session is writing: when one attaches,
36
+ * when it is evicted or swept, the inferred activity it feeds into session
37
+ * responses, and the transcript lines it pushes.
38
+ *
39
+ * Extracted from StreamerServer so external-tail work stops editing the server
40
+ * file, continuing the split in docs/plans/2026-07-12-server-ts-split.md.
41
+ */
42
+ export declare class ExternalTailManager {
43
+ private deps;
44
+ private log;
45
+ constructor(deps: ExternalTailManagerDeps);
46
+ private get externalTails();
47
+ /** True when a managed (PTY) session owns the tail for this canonical path. */
48
+ isManagedTailPath(key: string): boolean;
49
+ /**
50
+ * Attach a live tail to a JSONL nobody is tailing yet, when it was touched
51
+ * recently enough to look actively written by an external agent. Capped at
52
+ * EXTERNAL_TAIL_MAX with LRU eviction.
53
+ */
54
+ maybeAttachExternalTail(filePath: string): void;
55
+ /** Stop tailing an external file and drop its bookkeeping. */
56
+ detachExternalTail(key: string): void;
57
+ /**
58
+ * Shared unlink path for the per-file watcher and the directory watcher.
59
+ * Detaches any external tail and drops the cache row (unless an integrity
60
+ * alert is freezing deletes).
61
+ */
62
+ handleJsonlDeleted(filePath: string): void;
63
+ /** Make room for one more tail by evicting the least recently active ones. */
64
+ evictExternalTailsIfNeeded(): void;
65
+ /**
66
+ * INFERRED activity for an externally-owned conversation, derived purely from
67
+ * how recently its JSONL grew (the external tail's bookkeeping). Returns
68
+ * undefined when we hold no tail for it, so a session we know nothing about
69
+ * reports no activity rather than a fabricated "quiet".
70
+ *
71
+ * This can never distinguish a generating agent from one blocked on a
72
+ * permission gate — gates render on the PTY screen and never reach the JSONL —
73
+ * which is why it is a separate field and not folded into `status`.
74
+ */
75
+ externalActivityFor(conversationId: string, now?: number): SessionActivity | undefined;
76
+ /** Attach inferred `activity` to externally-owned sessions in a response set. */
77
+ withExternalActivity(sessions: readonly SessionResponse[]): readonly SessionResponse[];
78
+ /** Detach external tails idle past EXTERNAL_TAIL_IDLE_MS. */
79
+ sweepIdleExternalTails(now?: number): void;
80
+ /**
81
+ * Push appended lines from an externally-owned conversation. Reuses the exact
82
+ * conversation_events / conversation_event shapes mobile already consumes,
83
+ * keyed by the conversation UUID — an external session has no PTY, so it must
84
+ * never produce terminal_output / terminal_replay / session_ready, and never a
85
+ * session_update whose session.id is a conversation UUID (that would mint a
86
+ * phantom session row in the mobile cache). Question cards are likewise never
87
+ * derived here: with no PTY there is nothing that could deliver an answer.
88
+ */
89
+ broadcastExternalTailLines(filePath: string, lines: string[], seqs?: (number | null)[] | null): void;
90
+ }
91
+ //# sourceMappingURL=external-tails.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"external-tails.d.ts","sourceRoot":"","sources":["../src/external-tails.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAEzD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,kDAAkD,CAAC;AAC9F,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,8CAA8C,CAAC;AAExF,OAAO,KAAK,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAEhE,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,UAAU,CAAC;AAWtC,eAAO,MAAM,wBAAwB,SAAwB,CAAC;AAK9D,eAAO,MAAM,iBAAiB,KAAK,CAAC;AAKpC,eAAO,MAAM,qBAAqB,SAAU,CAAC;AAI7C,eAAO,MAAM,0BAA0B,QAAS,CAAC;AAEjD,MAAM,MAAM,iBAAiB,GAAG;IAAE,cAAc,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,MAAM,CAAA;CAAE,CAAC;AAEnF;;;;;;;;;;GAUG;AACH,MAAM,MAAM,uBAAuB,GAAG;IACpC,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC;IACtC,cAAc,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACpC,WAAW,EAAE,mBAAmB,CAAC;IACjC,KAAK,EAAE,KAAK,CAAC;IACb,KAAK,EAAE,MAAM,iBAAiB,GAAG,IAAI,CAAC;IACtC,YAAY,EAAE,MAAM,qBAAqB,GAAG,IAAI,CAAC;IACjD,0BAA0B,EAAE,CAC1B,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,EAAE,EACf,IAAI,CAAC,EAAE,CAAC,MAAM,GAAG,IAAI,CAAC,EAAE,GAAG,IAAI,KAC5B,IAAI,CAAC;CACX,CAAC;AAEF;;;;;;;GAOG;AACH,qBAAa,mBAAmB;IAGlB,OAAO,CAAC,IAAI;IAFxB,OAAO,CAAC,GAAG,CAAuB;IAElC,YAAoB,IAAI,EAAE,uBAAuB,EAAI;IAErD,OAAO,KAAK,aAAa,GAExB;IAED,+EAA+E;IAC/E,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAKtC;IAED;;;;OAIG;IACH,uBAAuB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAgC9C;IAED,8DAA8D;IAC9D,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAOpC;IAED;;;;OAIG;IACH,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAkBzC;IAED,8EAA8E;IAC9E,0BAA0B,IAAI,IAAI,CAmBjC;IAED;;;;;;;;;OASG;IACH,mBAAmB,CAAC,cAAc,EAAE,MAAM,EAAE,GAAG,SAAa,GAAG,eAAe,GAAG,SAAS,CAWzF;IAED,iFAAiF;IACjF,oBAAoB,CAAC,QAAQ,EAAE,SAAS,eAAe,EAAE,GAAG,SAAS,eAAe,EAAE,CAQrF;IAED,6DAA6D;IAC7D,sBAAsB,CAAC,GAAG,SAAa,GAAG,IAAI,CAS7C;IAED;;;;;;;;OAQG;IACH,0BAA0B,CACxB,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EAAE,EACf,IAAI,CAAC,EAAE,CAAC,MAAM,GAAG,IAAI,CAAC,EAAE,GAAG,IAAI,GAC9B,IAAI,CAyBN;CACF"}
@@ -0,0 +1,212 @@
1
+ /** Spec for one flag in the keyed registry. The id is the object key, not a field. */
2
+ export interface FeatureFlagSpec {
3
+ /** Shipped to clients alongside the values so a UI can render it. */
4
+ description: string;
5
+ default: boolean;
6
+ /** Full env var name. */
7
+ env: string;
8
+ }
9
+ /** Wire shape: the keyed spec plus its id, for GET /api/config/feature-flags. */
10
+ export interface FeatureFlagDefinition extends FeatureFlagSpec {
11
+ /** Stable config/wire key. Used in server.yaml, on the CLI, and over HTTP. */
12
+ id: FeatureFlagId;
13
+ }
14
+ /**
15
+ * Values arriving from a config source. PARTIAL on purpose: server.yaml, the
16
+ * CLI and ServerConfig each speak about the flags they mention and stay silent
17
+ * on the rest, which is what lets the precedence chain fall through.
18
+ */
19
+ export type FeatureFlagValues = Partial<Record<FeatureFlagId, boolean>>;
20
+ /** Every flag, resolved. Total — see resolveFeatureFlags(). */
21
+ export type ResolvedFeatureFlags = Record<FeatureFlagId, boolean>;
22
+ /**
23
+ * Which rung of the precedence chain decided a flag.
24
+ *
25
+ * Kept because the resolved boolean alone cannot answer "why is this on?", and
26
+ * that is the first question asked of a flag that is on when nobody expected it.
27
+ * `override` is the legacy ServerConfig field (see resolveFeatureFlags).
28
+ */
29
+ export type FeatureFlagSource = "override" | "env" | "cli" | "yaml" | "default";
30
+ export interface FeatureFlagResolution {
31
+ values: ResolvedFeatureFlags;
32
+ sources: Record<FeatureFlagId, FeatureFlagSource>;
33
+ }
34
+ /**
35
+ * The registry, keyed by flag name.
36
+ *
37
+ * Prefer `FEATURE_FLAGS.ptyHost` over a string lookup. A typo is a compile
38
+ * error; `findFeatureFlag("…")` is only for untrusted yaml/CLI tokens.
39
+ *
40
+ * `as const` + `keyof` is what makes `flags.ptyHsot` a compile error instead of
41
+ * `undefined`. A TS enum would add a runtime object without a stronger type.
42
+ */
43
+ export declare const FEATURE_FLAGS: {
44
+ readonly codexSystemPrompt: {
45
+ readonly description: string;
46
+ readonly default: false;
47
+ readonly env: "THREADBASE_FEATURE_CODEX_SYSTEM_PROMPT";
48
+ };
49
+ readonly sessionRehydration: {
50
+ readonly description: string;
51
+ readonly default: true;
52
+ readonly env: "THREADBASE_FEATURE_SESSION_REHYDRATION";
53
+ };
54
+ readonly liveActivityPush: {
55
+ readonly description: string;
56
+ readonly default: false;
57
+ readonly env: "THREADBASE_FEATURE_LIVE_ACTIVITY_PUSH";
58
+ };
59
+ readonly e2ee: {
60
+ readonly description: string;
61
+ readonly default: false;
62
+ readonly env: "THREADBASE_FEATURE_E2EE";
63
+ };
64
+ readonly ptyHost: {
65
+ readonly description: string;
66
+ readonly default: false;
67
+ readonly env: "THREADBASE_FEATURE_PTY_HOST";
68
+ };
69
+ };
70
+ /**
71
+ * The registry's ids as a union, derived rather than declared.
72
+ *
73
+ * This is what makes `flags.ptyHsot` a compile error instead of `undefined`.
74
+ * Under the old `Record<string, boolean>` an index signature accepted any key,
75
+ * so a typo in a consumer read as falsy and silently disabled the feature it
76
+ * was meant to gate — the exact failure the "total map" contract exists to
77
+ * prevent, reachable through the one door that contract left open.
78
+ */
79
+ export type FeatureFlagId = keyof typeof FEATURE_FLAGS;
80
+ /**
81
+ * The yaml / `--feature` ids, in registry order.
82
+ *
83
+ * These are the `FEATURE_FLAGS` object keys — not the `THREADBASE_FEATURE_*`
84
+ * env names. `feature_flags: {"ptyHost":true}` is valid;
85
+ * `{"THREADBASE_FEATURE_PTY_HOST":true}` is dropped as unknown.
86
+ */
87
+ export declare const FEATURE_FLAG_IDS: FeatureFlagId[];
88
+ /**
89
+ * Ordered list for iteration and the HTTP registry.
90
+ *
91
+ * The wire shape stays an array of `{ id, description, default, env }` so a
92
+ * keyed in-process registry is not a breaking GET /api/config/feature-flags
93
+ * change. Each `id` is the matching `FEATURE_FLAGS` key.
94
+ */
95
+ export declare const FEATURE_FLAG_LIST: readonly FeatureFlagDefinition[];
96
+ export declare function isFeatureFlagId(id: string): id is FeatureFlagId;
97
+ /** Typed lookup. The flag is always present; unknown names do not type-check. */
98
+ export declare function getFeatureFlag<K extends FeatureFlagId>(id: K): {
99
+ id: K;
100
+ } & {
101
+ readonly codexSystemPrompt: {
102
+ readonly description: string;
103
+ readonly default: false;
104
+ readonly env: "THREADBASE_FEATURE_CODEX_SYSTEM_PROMPT";
105
+ };
106
+ readonly sessionRehydration: {
107
+ readonly description: string;
108
+ readonly default: true;
109
+ readonly env: "THREADBASE_FEATURE_SESSION_REHYDRATION";
110
+ };
111
+ readonly liveActivityPush: {
112
+ readonly description: string;
113
+ readonly default: false;
114
+ readonly env: "THREADBASE_FEATURE_LIVE_ACTIVITY_PUSH";
115
+ };
116
+ readonly e2ee: {
117
+ readonly description: string;
118
+ readonly default: false;
119
+ readonly env: "THREADBASE_FEATURE_E2EE";
120
+ };
121
+ readonly ptyHost: {
122
+ readonly description: string;
123
+ readonly default: false;
124
+ readonly env: "THREADBASE_FEATURE_PTY_HOST";
125
+ };
126
+ }[K];
127
+ /**
128
+ * Look a flag up by an unvalidated string (yaml keys, `--feature` tokens).
129
+ *
130
+ * In-process code should use `FEATURE_FLAGS.ptyHost` or `getFeatureFlag("ptyHost")`.
131
+ */
132
+ export declare function findFeatureFlag(id: string): FeatureFlagDefinition | undefined;
133
+ /**
134
+ * Parse a boolean env var, tri-state.
135
+ *
136
+ * `undefined` means "this variable did not speak" — distinct from `false` — so
137
+ * an unset var lets the next precedence rung (CLI, then yaml) decide instead of
138
+ * silently forcing the flag off.
139
+ *
140
+ * That tri-state return is why this does not reuse one of the existing env
141
+ * parsers (`parseIncludeAgentsEnv` in server.ts, `isTruthy` in
142
+ * agent/agent-config.ts, the inline sets in api/middleware/cors.middleware.ts):
143
+ * all three collapse "absent" into a boolean, which is exactly the distinction
144
+ * the precedence chain needs. Consolidating those three is a separate change.
145
+ */
146
+ export declare function parseBooleanEnv(raw: string | undefined): boolean | undefined;
147
+ /**
148
+ * Drop everything that isn't a known id carrying a boolean.
149
+ *
150
+ * A TRUST BOUNDARY, mirroring validateFlagValues() in claude-flags.ts: values
151
+ * arrive from a user-editable server.yaml. Unknown ids and ill-typed values are
152
+ * dropped with a warning rather than throwing, so one stale or fat-fingered key
153
+ * can never stop the server from booting.
154
+ *
155
+ * Note the deliberate non-coercion: `"true"` (a string) is dropped, not read as
156
+ * true. A value that isn't already a boolean means the writer misunderstood the
157
+ * format, and guessing at intent is how a flag silently ends up on.
158
+ */
159
+ export declare function validateFeatureFlagValues(raw: unknown): FeatureFlagValues;
160
+ /**
161
+ * Parse repeatable `--feature <id=bool>` CLI tokens.
162
+ *
163
+ * Errors are returned rather than thrown so the caller owns the exit: a typo on
164
+ * the command line should print one legible message and stop, not surface a
165
+ * stack trace. Unlike the yaml path this is strict — a CLI typo is a mistake the
166
+ * operator is standing right there to fix, whereas a stale yaml key must not
167
+ * block an unattended boot.
168
+ */
169
+ export declare function parseFeatureFlagArgs(entries: string[]): {
170
+ values: FeatureFlagValues;
171
+ errors: string[];
172
+ };
173
+ /**
174
+ * Resolve every flag once, at boot. Precedence, highest first:
175
+ *
176
+ * override → env → CLI → server.yaml → registry default
177
+ *
178
+ * Env beats the CLI so an operator can flip a flag on a supervised instance
179
+ * (launchd/systemd/Task Scheduler) whose argv is fixed — the same reason
180
+ * THREADBASE_ALLOW_BROWSER_CORS overrides browser_cors: in server.yaml.
181
+ *
182
+ * `override` is the legacy explicit ServerConfig field (codexSystemPromptEnabled),
183
+ * kept so embedders and tests that set it directly keep working. It is a real
184
+ * rung here rather than a mutation applied to the finished map afterwards: the
185
+ * old shape meant the resolver was not actually the single source of truth, and
186
+ * an override was invisible to anything reporting where a value came from.
187
+ *
188
+ * The returned `values` map is TOTAL: every registry id is present, defaults
189
+ * filled in. Callers therefore index it without a `?? default`, and a flag added
190
+ * later cannot reach a boolean branch as `undefined` just because an older
191
+ * server.yaml predates it. `sources` is total for the same reason.
192
+ */
193
+ export declare function resolveFeatureFlags(opts?: {
194
+ override?: FeatureFlagValues;
195
+ cli?: FeatureFlagValues;
196
+ yaml?: FeatureFlagValues;
197
+ env?: NodeJS.ProcessEnv;
198
+ }): FeatureFlagResolution;
199
+ /** Registry ids whose resolved value differs from the registry default. */
200
+ export declare function nonDefaultFeatureFlags(values: ResolvedFeatureFlags): FeatureFlagId[];
201
+ /**
202
+ * One line describing the whole resolution: `id=value(source)`, every flag.
203
+ *
204
+ * Replaces a boot log that printed only the ids differing from their defaults
205
+ * under the heading "Feature flags active". That heading was wrong for any
206
+ * flag whose default is ON — disabling `sessionRehydration` made it appear in
207
+ * a list of active flags, stating the opposite of what had happened. Printing
208
+ * the value removes the ambiguity, and printing every flag means the log
209
+ * answers "what was this process running with" instead of only ever hinting.
210
+ */
211
+ export declare function describeFeatureFlags(resolution: FeatureFlagResolution): string;
212
+ //# sourceMappingURL=feature-flags.d.ts.map