uxnan-bridge 0.0.1-alpha.20260621

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 (255) hide show
  1. package/README.md +150 -0
  2. package/dist/src/account-status.d.ts +13 -0
  3. package/dist/src/account-status.js +78 -0
  4. package/dist/src/account-status.js.map +1 -0
  5. package/dist/src/adapters/base-adapter.d.ts +18 -0
  6. package/dist/src/adapters/base-adapter.js +15 -0
  7. package/dist/src/adapters/base-adapter.js.map +1 -0
  8. package/dist/src/adapters/claude-adapter.d.ts +102 -0
  9. package/dist/src/adapters/claude-adapter.js +486 -0
  10. package/dist/src/adapters/claude-adapter.js.map +1 -0
  11. package/dist/src/adapters/claude-tools.d.ts +25 -0
  12. package/dist/src/adapters/claude-tools.js +146 -0
  13. package/dist/src/adapters/claude-tools.js.map +1 -0
  14. package/dist/src/adapters/codex-adapter.d.ts +116 -0
  15. package/dist/src/adapters/codex-adapter.js +912 -0
  16. package/dist/src/adapters/codex-adapter.js.map +1 -0
  17. package/dist/src/adapters/codex-app-server.d.ts +74 -0
  18. package/dist/src/adapters/codex-app-server.js +225 -0
  19. package/dist/src/adapters/codex-app-server.js.map +1 -0
  20. package/dist/src/adapters/codex-approval.d.ts +88 -0
  21. package/dist/src/adapters/codex-approval.js +160 -0
  22. package/dist/src/adapters/codex-approval.js.map +1 -0
  23. package/dist/src/adapters/codex-tools.d.ts +18 -0
  24. package/dist/src/adapters/codex-tools.js +106 -0
  25. package/dist/src/adapters/codex-tools.js.map +1 -0
  26. package/dist/src/adapters/content-blocks.d.ts +68 -0
  27. package/dist/src/adapters/content-blocks.js +205 -0
  28. package/dist/src/adapters/content-blocks.js.map +1 -0
  29. package/dist/src/adapters/echo-agent-adapter.d.ts +23 -0
  30. package/dist/src/adapters/echo-agent-adapter.js +72 -0
  31. package/dist/src/adapters/echo-agent-adapter.js.map +1 -0
  32. package/dist/src/adapters/gemini-adapter.d.ts +87 -0
  33. package/dist/src/adapters/gemini-adapter.js +594 -0
  34. package/dist/src/adapters/gemini-adapter.js.map +1 -0
  35. package/dist/src/adapters/gemini-tools.d.ts +4 -0
  36. package/dist/src/adapters/gemini-tools.js +48 -0
  37. package/dist/src/adapters/gemini-tools.js.map +1 -0
  38. package/dist/src/adapters/opencode-adapter.d.ts +74 -0
  39. package/dist/src/adapters/opencode-adapter.js +418 -0
  40. package/dist/src/adapters/opencode-adapter.js.map +1 -0
  41. package/dist/src/adapters/opencode-tools.d.ts +2 -0
  42. package/dist/src/adapters/opencode-tools.js +41 -0
  43. package/dist/src/adapters/opencode-tools.js.map +1 -0
  44. package/dist/src/adapters/pi-adapter.d.ts +92 -0
  45. package/dist/src/adapters/pi-adapter.js +467 -0
  46. package/dist/src/adapters/pi-adapter.js.map +1 -0
  47. package/dist/src/adapters/pi-tools.d.ts +10 -0
  48. package/dist/src/adapters/pi-tools.js +72 -0
  49. package/dist/src/adapters/pi-tools.js.map +1 -0
  50. package/dist/src/adapters/process-agent-adapter.d.ts +24 -0
  51. package/dist/src/adapters/process-agent-adapter.js +111 -0
  52. package/dist/src/adapters/process-agent-adapter.js.map +1 -0
  53. package/dist/src/adapters/resolve-claude.d.ts +13 -0
  54. package/dist/src/adapters/resolve-claude.js +57 -0
  55. package/dist/src/adapters/resolve-claude.js.map +1 -0
  56. package/dist/src/adapters/resolve-codex.d.ts +13 -0
  57. package/dist/src/adapters/resolve-codex.js +48 -0
  58. package/dist/src/adapters/resolve-codex.js.map +1 -0
  59. package/dist/src/adapters/resolve-gemini.d.ts +13 -0
  60. package/dist/src/adapters/resolve-gemini.js +47 -0
  61. package/dist/src/adapters/resolve-gemini.js.map +1 -0
  62. package/dist/src/adapters/resolve-opencode.d.ts +11 -0
  63. package/dist/src/adapters/resolve-opencode.js +49 -0
  64. package/dist/src/adapters/resolve-opencode.js.map +1 -0
  65. package/dist/src/adapters/resolve-pi.d.ts +13 -0
  66. package/dist/src/adapters/resolve-pi.js +46 -0
  67. package/dist/src/adapters/resolve-pi.js.map +1 -0
  68. package/dist/src/adapters/run-options.d.ts +22 -0
  69. package/dist/src/adapters/run-options.js +48 -0
  70. package/dist/src/adapters/run-options.js.map +1 -0
  71. package/dist/src/adapters/spawn.d.ts +20 -0
  72. package/dist/src/adapters/spawn.js +16 -0
  73. package/dist/src/adapters/spawn.js.map +1 -0
  74. package/dist/src/agents/agent-manager.d.ts +98 -0
  75. package/dist/src/agents/agent-manager.js +433 -0
  76. package/dist/src/agents/agent-manager.js.map +1 -0
  77. package/dist/src/agents/attachments.d.ts +28 -0
  78. package/dist/src/agents/attachments.js +121 -0
  79. package/dist/src/agents/attachments.js.map +1 -0
  80. package/dist/src/bridge-context.d.ts +45 -0
  81. package/dist/src/bridge-context.js +2 -0
  82. package/dist/src/bridge-context.js.map +1 -0
  83. package/dist/src/bridge-status.d.ts +12 -0
  84. package/dist/src/bridge-status.js +17 -0
  85. package/dist/src/bridge-status.js.map +1 -0
  86. package/dist/src/bridge.d.ts +37 -0
  87. package/dist/src/bridge.js +446 -0
  88. package/dist/src/bridge.js.map +1 -0
  89. package/dist/src/cli.d.ts +2 -0
  90. package/dist/src/cli.js +194 -0
  91. package/dist/src/cli.js.map +1 -0
  92. package/dist/src/conversation/session-history.d.ts +27 -0
  93. package/dist/src/conversation/session-history.js +1082 -0
  94. package/dist/src/conversation/session-history.js.map +1 -0
  95. package/dist/src/conversation/thread-store.d.ts +74 -0
  96. package/dist/src/conversation/thread-store.js +366 -0
  97. package/dist/src/conversation/thread-store.js.map +1 -0
  98. package/dist/src/daemon-config.d.ts +123 -0
  99. package/dist/src/daemon-config.js +64 -0
  100. package/dist/src/daemon-config.js.map +1 -0
  101. package/dist/src/daemon-state.d.ts +27 -0
  102. package/dist/src/daemon-state.js +76 -0
  103. package/dist/src/daemon-state.js.map +1 -0
  104. package/dist/src/git/git-runner.d.ts +24 -0
  105. package/dist/src/git/git-runner.js +63 -0
  106. package/dist/src/git/git-runner.js.map +1 -0
  107. package/dist/src/git/git-service.d.ts +76 -0
  108. package/dist/src/git/git-service.js +435 -0
  109. package/dist/src/git/git-service.js.map +1 -0
  110. package/dist/src/handler-router.d.ts +34 -0
  111. package/dist/src/handler-router.js +67 -0
  112. package/dist/src/handler-router.js.map +1 -0
  113. package/dist/src/handlers/account-handler.d.ts +4 -0
  114. package/dist/src/handlers/account-handler.js +27 -0
  115. package/dist/src/handlers/account-handler.js.map +1 -0
  116. package/dist/src/handlers/agent-handler.d.ts +2 -0
  117. package/dist/src/handlers/agent-handler.js +8 -0
  118. package/dist/src/handlers/agent-handler.js.map +1 -0
  119. package/dist/src/handlers/bridge-control-handler.d.ts +2 -0
  120. package/dist/src/handlers/bridge-control-handler.js +64 -0
  121. package/dist/src/handlers/bridge-control-handler.js.map +1 -0
  122. package/dist/src/handlers/desktop-handler.d.ts +12 -0
  123. package/dist/src/handlers/desktop-handler.js +5 -0
  124. package/dist/src/handlers/desktop-handler.js.map +1 -0
  125. package/dist/src/handlers/git-handler.d.ts +2 -0
  126. package/dist/src/handlers/git-handler.js +82 -0
  127. package/dist/src/handlers/git-handler.js.map +1 -0
  128. package/dist/src/handlers/index.d.ts +8 -0
  129. package/dist/src/handlers/index.js +22 -0
  130. package/dist/src/handlers/index.js.map +1 -0
  131. package/dist/src/handlers/not-implemented.d.ts +10 -0
  132. package/dist/src/handlers/not-implemented.js +21 -0
  133. package/dist/src/handlers/not-implemented.js.map +1 -0
  134. package/dist/src/handlers/notifications-handler.d.ts +2 -0
  135. package/dist/src/handlers/notifications-handler.js +62 -0
  136. package/dist/src/handlers/notifications-handler.js.map +1 -0
  137. package/dist/src/handlers/params.d.ts +11 -0
  138. package/dist/src/handlers/params.js +72 -0
  139. package/dist/src/handlers/params.js.map +1 -0
  140. package/dist/src/handlers/project-handler.d.ts +2 -0
  141. package/dist/src/handlers/project-handler.js +6 -0
  142. package/dist/src/handlers/project-handler.js.map +1 -0
  143. package/dist/src/handlers/thread-context-handler.d.ts +2 -0
  144. package/dist/src/handlers/thread-context-handler.js +211 -0
  145. package/dist/src/handlers/thread-context-handler.js.map +1 -0
  146. package/dist/src/handlers/workspace-handler.d.ts +2 -0
  147. package/dist/src/handlers/workspace-handler.js +101 -0
  148. package/dist/src/handlers/workspace-handler.js.map +1 -0
  149. package/dist/src/hooks/claude-approval-hook.d.ts +7 -0
  150. package/dist/src/hooks/claude-approval-hook.js +95 -0
  151. package/dist/src/hooks/claude-approval-hook.js.map +1 -0
  152. package/dist/src/hooks/gemini-approval-hook.d.ts +7 -0
  153. package/dist/src/hooks/gemini-approval-hook.js +113 -0
  154. package/dist/src/hooks/gemini-approval-hook.js.map +1 -0
  155. package/dist/src/index.d.ts +62 -0
  156. package/dist/src/index.js +65 -0
  157. package/dist/src/index.js.map +1 -0
  158. package/dist/src/keyring-secret-store.d.ts +36 -0
  159. package/dist/src/keyring-secret-store.js +70 -0
  160. package/dist/src/keyring-secret-store.js.map +1 -0
  161. package/dist/src/lock-file.d.ts +18 -0
  162. package/dist/src/lock-file.js +60 -0
  163. package/dist/src/lock-file.js.map +1 -0
  164. package/dist/src/logger.d.ts +28 -0
  165. package/dist/src/logger.js +99 -0
  166. package/dist/src/logger.js.map +1 -0
  167. package/dist/src/pairing/pairing-code-service.d.ts +45 -0
  168. package/dist/src/pairing/pairing-code-service.js +183 -0
  169. package/dist/src/pairing/pairing-code-service.js.map +1 -0
  170. package/dist/src/projects/project-registry.d.ts +14 -0
  171. package/dist/src/projects/project-registry.js +60 -0
  172. package/dist/src/projects/project-registry.js.map +1 -0
  173. package/dist/src/push/push-sender.d.ts +21 -0
  174. package/dist/src/push/push-sender.js +96 -0
  175. package/dist/src/push/push-sender.js.map +1 -0
  176. package/dist/src/push/push-service.d.ts +122 -0
  177. package/dist/src/push/push-service.js +260 -0
  178. package/dist/src/push/push-service.js.map +1 -0
  179. package/dist/src/qr.d.ts +17 -0
  180. package/dist/src/qr.js +31 -0
  181. package/dist/src/qr.js.map +1 -0
  182. package/dist/src/secret-store.d.ts +23 -0
  183. package/dist/src/secret-store.js +27 -0
  184. package/dist/src/secret-store.js.map +1 -0
  185. package/dist/src/secure-device-state.d.ts +16 -0
  186. package/dist/src/secure-device-state.js +63 -0
  187. package/dist/src/secure-device-state.js.map +1 -0
  188. package/dist/src/service-installer.d.ts +57 -0
  189. package/dist/src/service-installer.js +254 -0
  190. package/dist/src/service-installer.js.map +1 -0
  191. package/dist/src/session-state.d.ts +14 -0
  192. package/dist/src/session-state.js +19 -0
  193. package/dist/src/session-state.js.map +1 -0
  194. package/dist/src/transport/crypto.d.ts +43 -0
  195. package/dist/src/transport/crypto.js +78 -0
  196. package/dist/src/transport/crypto.js.map +1 -0
  197. package/dist/src/transport/lan-server.d.ts +33 -0
  198. package/dist/src/transport/lan-server.js +105 -0
  199. package/dist/src/transport/lan-server.js.map +1 -0
  200. package/dist/src/transport/local-hosts.d.ts +17 -0
  201. package/dist/src/transport/local-hosts.js +30 -0
  202. package/dist/src/transport/local-hosts.js.map +1 -0
  203. package/dist/src/transport/mdns-advertiser.d.ts +83 -0
  204. package/dist/src/transport/mdns-advertiser.js +282 -0
  205. package/dist/src/transport/mdns-advertiser.js.map +1 -0
  206. package/dist/src/transport/message-io.d.ts +33 -0
  207. package/dist/src/transport/message-io.js +87 -0
  208. package/dist/src/transport/message-io.js.map +1 -0
  209. package/dist/src/transport/outbound-log.d.ts +24 -0
  210. package/dist/src/transport/outbound-log.js +78 -0
  211. package/dist/src/transport/outbound-log.js.map +1 -0
  212. package/dist/src/transport/relay-client.d.ts +19 -0
  213. package/dist/src/transport/relay-client.js +27 -0
  214. package/dist/src/transport/relay-client.js.map +1 -0
  215. package/dist/src/transport/secure-channel.d.ts +33 -0
  216. package/dist/src/transport/secure-channel.js +81 -0
  217. package/dist/src/transport/secure-channel.js.map +1 -0
  218. package/dist/src/transport/server-handshake.d.ts +49 -0
  219. package/dist/src/transport/server-handshake.js +137 -0
  220. package/dist/src/transport/server-handshake.js.map +1 -0
  221. package/dist/src/transport/session-handler.d.ts +19 -0
  222. package/dist/src/transport/session-handler.js +134 -0
  223. package/dist/src/transport/session-handler.js.map +1 -0
  224. package/dist/src/transport/session-registry.d.ts +58 -0
  225. package/dist/src/transport/session-registry.js +91 -0
  226. package/dist/src/transport/session-registry.js.map +1 -0
  227. package/dist/src/transport/trust-store.d.ts +23 -0
  228. package/dist/src/transport/trust-store.js +33 -0
  229. package/dist/src/transport/trust-store.js.map +1 -0
  230. package/dist/src/transport/ws-adapter.d.ts +7 -0
  231. package/dist/src/transport/ws-adapter.js +16 -0
  232. package/dist/src/transport/ws-adapter.js.map +1 -0
  233. package/dist/src/version.d.ts +1 -0
  234. package/dist/src/version.js +13 -0
  235. package/dist/src/version.js.map +1 -0
  236. package/dist/src/workspace/browse-service.d.ts +10 -0
  237. package/dist/src/workspace/browse-service.js +97 -0
  238. package/dist/src/workspace/browse-service.js.map +1 -0
  239. package/dist/src/workspace/checkpoint-service.d.ts +21 -0
  240. package/dist/src/workspace/checkpoint-service.js +219 -0
  241. package/dist/src/workspace/checkpoint-service.js.map +1 -0
  242. package/dist/src/workspace/path-guard.d.ts +7 -0
  243. package/dist/src/workspace/path-guard.js +51 -0
  244. package/dist/src/workspace/path-guard.js.map +1 -0
  245. package/dist/src/workspace/workspace-service.d.ts +8 -0
  246. package/dist/src/workspace/workspace-service.js +111 -0
  247. package/dist/src/workspace/workspace-service.js.map +1 -0
  248. package/package.json +46 -0
  249. package/scripts/extract-gemini-hook.mjs +16 -0
  250. package/scripts/fake-approval-bridge.mjs +23 -0
  251. package/scripts/install-service-linux.sh +38 -0
  252. package/scripts/install-service-macos.sh +38 -0
  253. package/scripts/install-service-windows.ps1 +26 -0
  254. package/scripts/test-gemini-hook-e2e.mjs +168 -0
  255. package/scripts/write-gemini-settings.mjs +31 -0
@@ -0,0 +1,211 @@
1
+ /**
2
+ * Thread/turn JSON-RPC handlers — conversation CRUD backed by the
3
+ * {@link ThreadStore}, with `turn/send` driving the {@link AgentManager}.
4
+ * Streaming output reaches the phone via `stream/*` notifications.
5
+ *
6
+ * Source: architecture/02a-system-architecture.md §5.8.8.
7
+ */
8
+ import { RpcError } from '@uxnan/shared';
9
+ import { optionalBoolean, optionalNumber, optionalString, requireString } from './params.js';
10
+ export function registerThreadHandlers(router) {
11
+ router.register('thread/list', (p, ctx) => ctx.threadStore.listThreads(optionalString(p, 'projectId')));
12
+ router.register('thread/read', (p, ctx) => ctx.threadStore.getThread(requireString(p, 'threadId')));
13
+ router.register('thread/start', (p, ctx) => {
14
+ const projectId = requireString(p, 'projectId');
15
+ // The phone provides the cwd (e.g. a folder-browser directory, which
16
+ // `project/resolve` SYNTHESIZES into a project that is NOT in
17
+ // workspaceRoots). Use that cwd directly; only resolve the project by id to
18
+ // get a cwd fallback when none is given — otherwise a browsed folder failed
19
+ // `byId` with "unknown project", and the thread was never created.
20
+ const cwd = optionalString(p, 'cwd') ?? ctx.projects.byId(projectId).cwd;
21
+ // Per-project pin: when the phone omits agent/model, fall back to the
22
+ // project's configured agent (then the bridge's global default). The pinned
23
+ // model only applies when the resolved agent IS the pinned one, so we never
24
+ // force one agent's model onto a thread the phone steered to another agent.
25
+ const pin = ctx.projects.agentConfigFor(cwd);
26
+ const explicitAgent = optionalString(p, 'agentId');
27
+ const agentId = explicitAgent ?? pin?.agentId ?? ctx.agentManager.defaultAgent;
28
+ const explicitModel = optionalString(p, 'model');
29
+ const model = explicitModel ?? (pin && agentId === pin.agentId ? pin.model : undefined);
30
+ return ctx.threadStore.startThread({
31
+ projectId,
32
+ ...(optionalString(p, 'title') !== undefined ? { title: optionalString(p, 'title') } : {}),
33
+ agentId,
34
+ ...(model !== undefined ? { model } : {}),
35
+ cwd,
36
+ }, ctx.now());
37
+ });
38
+ router.register('thread/resume', (p, ctx) => ctx.threadStore.resumeThread(requireString(p, 'threadId'), ctx.now()));
39
+ router.register('thread/fork', (p, ctx) => ctx.threadStore.forkThread(requireString(p, 'threadId'), ctx.now()));
40
+ router.register('thread/setModel', async (p, ctx) => {
41
+ await ctx.threadStore.setModel(requireString(p, 'threadId'), requireString(p, 'model'), ctx.now());
42
+ return null;
43
+ });
44
+ router.register('thread/rename', (p, ctx) => ctx.threadStore.renameThread(requireString(p, 'threadId'), requireString(p, 'title'), ctx.now()));
45
+ router.register('thread/setAccessMode', (p, ctx) => ctx.threadStore.setAccessMode(requireString(p, 'threadId'), parseAccessMode(requireString(p, 'mode')), ctx.now()));
46
+ router.register('thread/archive', (p, ctx) => ctx.threadStore.archiveThread(requireString(p, 'threadId'), ctx.now()));
47
+ router.register('thread/unarchive', (p, ctx) => ctx.threadStore.unarchiveThread(requireString(p, 'threadId'), ctx.now()));
48
+ router.register('thread/delete', async (p, ctx) => {
49
+ await ctx.threadStore.deleteThread(requireString(p, 'threadId'));
50
+ return null;
51
+ });
52
+ router.register('turn/list', async (p, ctx) => {
53
+ const threadId = requireString(p, 'threadId');
54
+ const cursor = optionalString(p, 'cursor');
55
+ const limit = optionalNumber(p, 'limit');
56
+ const fromEnd = optionalBoolean(p, 'fromEnd') ?? false;
57
+ const stored = await ctx.threadStore.listTurns(threadId, cursor, limit, fromEnd);
58
+ // Fallback (§5.8.8): when the store has nothing for this thread, read the
59
+ // agent's own on-disk session log so the phone can still show history (e.g.
60
+ // after the bridge missed the turns, or threads.json was lost).
61
+ if (stored.turns.length > 0 || stored.nextCursor)
62
+ return stored;
63
+ const source = await ctx.threadStore.getHistorySource(threadId);
64
+ const turns = await ctx.sessionHistory.readTurns(source, threadId);
65
+ if (!turns || turns.length === 0)
66
+ return stored;
67
+ return paginateTurns(turns, cursor, limit, fromEnd);
68
+ });
69
+ router.register('turn/read', (p, ctx) => ctx.threadStore.getTurn(requireString(p, 'turnId')));
70
+ router.register('turn/send', async (p, ctx) => {
71
+ const threadId = requireString(p, 'threadId');
72
+ // A `turn/send` may instead be a control-only reply to a pending approval:
73
+ // no new turn is created — the decision is routed to the agent adapter.
74
+ const approval = optionalApprovalResponse(p);
75
+ if (approval) {
76
+ return ctx.agentManager.respondApproval(threadId, approval.approvalId, approval.decision);
77
+ }
78
+ // `text` is OPTIONAL: an image-only message (empty text + attachments) is
79
+ // valid, so we no longer hard-require a non-empty string. Reject only when
80
+ // there is neither text nor an attachment to act on.
81
+ const text = optionalString(p, 'text') ?? '';
82
+ const attachments = optionalAttachments(p);
83
+ if (text.length === 0 && attachments.length === 0) {
84
+ throw RpcError.invalidParams('turn/send requires non-empty text or attachments');
85
+ }
86
+ const runtime = await ctx.threadStore.getThreadRuntime(threadId);
87
+ // A turn runs with the thread's agent/model/cwd; explicit params override.
88
+ const service = optionalString(p, 'service') ?? runtime.model;
89
+ const options = {
90
+ ...(runtime.agentId !== undefined ? { agentId: runtime.agentId } : {}),
91
+ ...(service !== undefined ? { service } : {}),
92
+ ...optionalEffort(p),
93
+ ...optionalRunOptions(p),
94
+ ...(attachments.length > 0 ? { attachments } : {}),
95
+ ...(runtime.cwd !== undefined ? { cwd: runtime.cwd } : {}),
96
+ // Apply the thread's persisted access mode to this turn (adapters map it
97
+ // to their permission flag; absent → the adapter's configured posture).
98
+ ...(runtime.accessMode !== undefined ? { accessMode: runtime.accessMode } : {}),
99
+ };
100
+ return ctx.agentManager.sendTurn(threadId, text, options);
101
+ });
102
+ router.register('turn/cancel', async (p, ctx) => {
103
+ await ctx.agentManager.cancelTurn(requireString(p, 'threadId'), requireString(p, 'turnId'));
104
+ return null;
105
+ });
106
+ }
107
+ /**
108
+ * Page a full turn list (from the on-disk history fallback) the same way the
109
+ * store does: numeric cursor offset + limit, with a `nextCursor` when more remain.
110
+ */
111
+ const ACCESS_MODES = ['requestApproval', 'approveForMe', 'fullAccess'];
112
+ /** Validates a wire `mode` string against the {@link AccessMode} union. */
113
+ function parseAccessMode(mode) {
114
+ if (ACCESS_MODES.includes(mode))
115
+ return mode;
116
+ throw RpcError.invalidParams(`mode must be one of ${ACCESS_MODES.join(' | ')}`);
117
+ }
118
+ function paginateTurns(turns, cursor, limit, fromEnd = false) {
119
+ const total = turns.length;
120
+ const size = limit && limit > 0 ? limit : 20;
121
+ const start = fromEnd ? Math.max(0, total - size) : cursor ? Number.parseInt(cursor, 10) || 0 : 0;
122
+ const result = { turns: turns.slice(start, start + size), total };
123
+ if (start + size < total)
124
+ result.nextCursor = String(start + size);
125
+ return result;
126
+ }
127
+ function optionalEffort(params) {
128
+ const value = optionalString(params, 'effort');
129
+ return value === undefined ? {} : { effort: value };
130
+ }
131
+ const APPROVAL_DECISIONS = new Set(['approve', 'reject', 'approveSession']);
132
+ /**
133
+ * Extracts the `approvalResponse` from `turn/send` params, or `undefined` when
134
+ * absent. Validates the shape (`approvalId` non-empty + a known `decision`) and
135
+ * throws `invalidParams` on a malformed one — an approval reply is control data,
136
+ * so a garbled one should surface rather than silently start a turn.
137
+ */
138
+ function optionalApprovalResponse(params) {
139
+ if (!params || typeof params !== 'object')
140
+ return undefined;
141
+ const raw = params['approvalResponse'];
142
+ if (raw === undefined || raw === null)
143
+ return undefined;
144
+ if (typeof raw !== 'object') {
145
+ throw RpcError.invalidParams('approvalResponse must be an object');
146
+ }
147
+ const obj = raw;
148
+ const approvalId = obj['approvalId'];
149
+ const decision = obj['decision'];
150
+ if (typeof approvalId !== 'string' || approvalId.length === 0) {
151
+ throw RpcError.invalidParams('approvalResponse.approvalId must be a non-empty string');
152
+ }
153
+ if (typeof decision !== 'string' || !APPROVAL_DECISIONS.has(decision)) {
154
+ throw RpcError.invalidParams('approvalResponse.decision must be approve | reject | approveSession');
155
+ }
156
+ return { approvalId, decision: decision };
157
+ }
158
+ /**
159
+ * Extracts the inline `attachments` from `turn/send` params, keeping only
160
+ * well-formed image entries (a `mimeType` plus at least one of
161
+ * `base64Data`/`path`). Tolerant — malformed entries are dropped, never thrown,
162
+ * so an older/garbled client degrades to a text turn instead of an error.
163
+ */
164
+ function optionalAttachments(params) {
165
+ if (!params || typeof params !== 'object')
166
+ return [];
167
+ const raw = params['attachments'];
168
+ if (!Array.isArray(raw))
169
+ return [];
170
+ const out = [];
171
+ for (const item of raw) {
172
+ if (!item || typeof item !== 'object')
173
+ continue;
174
+ const obj = item;
175
+ const mimeType = typeof obj['mimeType'] === 'string' ? obj['mimeType'] : 'application/octet-stream';
176
+ const base64Data = typeof obj['base64Data'] === 'string' ? obj['base64Data'] : undefined;
177
+ const path = typeof obj['path'] === 'string' ? obj['path'] : undefined;
178
+ if (base64Data === undefined && path === undefined)
179
+ continue;
180
+ const att = { type: 'image', mimeType };
181
+ if (base64Data !== undefined)
182
+ att.base64Data = base64Data;
183
+ if (path !== undefined)
184
+ att.path = path;
185
+ if (typeof obj['width'] === 'number')
186
+ att.width = obj['width'];
187
+ if (typeof obj['height'] === 'number')
188
+ att.height = obj['height'];
189
+ out.push(att);
190
+ }
191
+ return out;
192
+ }
193
+ /**
194
+ * Extracts the per-model run-option values (`{ options: { key: value } }`) from
195
+ * `turn/send` params, keeping only string/boolean values (tolerant — unknown
196
+ * shapes are dropped, never thrown).
197
+ */
198
+ function optionalRunOptions(params) {
199
+ if (!params || typeof params !== 'object')
200
+ return {};
201
+ const raw = params['options'];
202
+ if (!raw || typeof raw !== 'object' || Array.isArray(raw))
203
+ return {};
204
+ const out = {};
205
+ for (const [key, value] of Object.entries(raw)) {
206
+ if (typeof value === 'string' || typeof value === 'boolean')
207
+ out[key] = value;
208
+ }
209
+ return Object.keys(out).length > 0 ? { options: out } : {};
210
+ }
211
+ //# sourceMappingURL=thread-context-handler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"thread-context-handler.js","sourceRoot":"","sources":["../../../src/handlers/thread-context-handler.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import type { HandlerRouter } from '../handler-router.js';
2
+ export declare function registerWorkspaceHandlers(router: HandlerRouter): void;
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Workspace JSON-RPC handlers — file reads, listing and patch application,
3
+ * confined to the project root with sensitive files excluded.
4
+ *
5
+ * Checkpoints (capture/diff/apply) are backed by {@link CheckpointService};
6
+ * `capture` prunes old checkpoints per the configured retention.
7
+ *
8
+ * Source: architecture/02a-system-architecture.md §5.8.7 / §5.8.9.
9
+ */
10
+ import { JsonRpcErrorCode, RpcError, } from '@uxnan/shared';
11
+ import { access, stat } from 'node:fs/promises';
12
+ import { join } from 'node:path';
13
+ import { WorkspaceService } from '../workspace/workspace-service.js';
14
+ import { CheckpointService } from '../workspace/checkpoint-service.js';
15
+ import { GitCommandError } from '../git/git-runner.js';
16
+ import { asObject, optionalString, requireArray, requireString } from './params.js';
17
+ /** Map a git failure inside a checkpoint op to -32003 (RpcErrors pass through). */
18
+ function checkpointOp(fn) {
19
+ return fn().catch((err) => {
20
+ if (err instanceof GitCommandError) {
21
+ throw new RpcError(JsonRpcErrorCode.GitOperationFailed, err.message, { stderr: err.stderr });
22
+ }
23
+ throw err;
24
+ });
25
+ }
26
+ /** A checkpoint service wired with the daemon's configured retention bounds. */
27
+ function checkpoints(ctx) {
28
+ return new CheckpointService(ctx.state, {
29
+ maxPerProject: ctx.config.checkpointMaxPerProject,
30
+ ttlDays: ctx.config.checkpointTtlDays,
31
+ });
32
+ }
33
+ export function registerWorkspaceHandlers(router) {
34
+ const ws = new WorkspaceService();
35
+ router.register('workspace/readFile', (p) => ws.readFile(requireString(p, 'cwd'), requireString(p, 'path')));
36
+ router.register('workspace/readImage', (p) => ws.readImage(requireString(p, 'cwd'), requireString(p, 'path')));
37
+ router.register('workspace/list', (p) => ws.list(requireString(p, 'cwd')));
38
+ router.register('workspace/browseDirs', (p, ctx) => {
39
+ const params = p ?? {};
40
+ return ctx.browse.browse(optionalString(params, 'rootId'), optionalString(params, 'path'));
41
+ });
42
+ router.register('workspace/applyPatch', (p) => ws.applyPatch(requireString(p, 'cwd'), parseChanges(p)));
43
+ // Probe whether a thread's cwd still exists (folders/worktrees can be removed
44
+ // outside the app), so the phone can mark the thread unavailable. Never throws.
45
+ router.register('workspace/exists', (p) => workspaceExists(requireString(p, 'cwd')));
46
+ router.register('workspace/checkpoint', (p, ctx) => {
47
+ const cwd = requireString(p, 'cwd');
48
+ const options = {
49
+ now: ctx.now(),
50
+ ...optionalField(p, 'label'),
51
+ ...optionalThreadId(p),
52
+ };
53
+ return checkpointOp(() => checkpoints(ctx).capture(cwd, options));
54
+ });
55
+ router.register('workspace/diffCheckpoint', (p, ctx) => checkpointOp(() => checkpoints(ctx).diff(requireString(p, 'id'))));
56
+ router.register('workspace/applyCheckpoint', (p, ctx) => checkpointOp(() => checkpoints(ctx).apply(requireString(p, 'id'))));
57
+ }
58
+ /** Whether [cwd] is an existing directory, and (if so) whether it's a git repo/worktree. */
59
+ async function workspaceExists(cwd) {
60
+ try {
61
+ const info = await stat(cwd);
62
+ if (!info.isDirectory())
63
+ return { exists: false };
64
+ }
65
+ catch {
66
+ return { exists: false };
67
+ }
68
+ // A regular repo has a `.git` directory; a worktree has a `.git` FILE — both
69
+ // are accessible, so this is true for either.
70
+ try {
71
+ await access(join(cwd, '.git'));
72
+ return { exists: true, isGitRepo: true };
73
+ }
74
+ catch {
75
+ return { exists: true, isGitRepo: false };
76
+ }
77
+ }
78
+ function optionalField(params, key) {
79
+ const value = optionalString(params, key);
80
+ return value === undefined ? {} : { label: value };
81
+ }
82
+ function optionalThreadId(params) {
83
+ const value = optionalString(params, 'threadId');
84
+ return value === undefined ? {} : { threadId: value };
85
+ }
86
+ function parseChanges(params) {
87
+ return requireArray(params, 'changes').map((raw, index) => {
88
+ const item = asObject(raw);
89
+ const op = item['op'];
90
+ if (op !== 'add' && op !== 'modify' && op !== 'delete') {
91
+ throw RpcError.invalidParams(`changes[${index}].op must be add|modify|delete`);
92
+ }
93
+ const path = requireString(item, 'path');
94
+ const content = optionalString(item, 'content');
95
+ const change = { op, path };
96
+ if (content !== undefined)
97
+ change.content = content;
98
+ return change;
99
+ });
100
+ }
101
+ //# sourceMappingURL=workspace-handler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspace-handler.js","sourceRoot":"","sources":["../../../src/handlers/workspace-handler.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Writes the hook script to [scriptPath] (creating its directory) and returns
3
+ * the path. Idempotent — overwrites each call so an updated bridge ships a fresh
4
+ * hook. Best-effort caller should handle write failures.
5
+ */
6
+ export declare function writeClaudeApprovalHook(scriptPath: string): Promise<string>;
7
+ export declare const CLAUDE_APPROVAL_HOOK_SCRIPT: string;
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Claude Code PreToolUse approval hook.
3
+ *
4
+ * Headless `claude -p` has no interactive-approval channel (validated against
5
+ * claude 2.1.177), but a **PreToolUse hook** injected via `--settings` DOES gate
6
+ * every tool in print mode. This module ships that hook as a standalone CommonJS
7
+ * script: the Claude adapter writes it to `~/.uxnan/hooks/` and references its
8
+ * path in the `--settings` it passes per turn.
9
+ *
10
+ * The hook reads the PreToolUse payload (`{ tool_name, tool_input, ... }`) on
11
+ * stdin, POSTs it to the bridge's `POST /agent-hook/approval` endpoint (URL +
12
+ * token + threadId from env the adapter injects), and the bridge HOLDS the
13
+ * response until the phone answers (`turn/send { approvalResponse }`). The hook
14
+ * then prints the permission decision Claude consumes:
15
+ * { hookSpecificOutput: { hookEventName, permissionDecision: 'allow'|'deny', … } }
16
+ *
17
+ * Fail-safe: any error (bad URL, unreachable bridge, malformed response) → deny.
18
+ * The script is dependency-free (only `node:http`/`node:url`).
19
+ */
20
+ import { mkdir, writeFile } from 'node:fs/promises';
21
+ import { dirname } from 'node:path';
22
+ /**
23
+ * Writes the hook script to [scriptPath] (creating its directory) and returns
24
+ * the path. Idempotent — overwrites each call so an updated bridge ships a fresh
25
+ * hook. Best-effort caller should handle write failures.
26
+ */
27
+ export async function writeClaudeApprovalHook(scriptPath) {
28
+ await mkdir(dirname(scriptPath), { recursive: true });
29
+ await writeFile(scriptPath, CLAUDE_APPROVAL_HOOK_SCRIPT, 'utf-8');
30
+ return scriptPath;
31
+ }
32
+ export const CLAUDE_APPROVAL_HOOK_SCRIPT = String.raw `'use strict';
33
+ const http = require('node:http');
34
+ let data = '';
35
+ process.stdin.on('data', (c) => (data += c));
36
+ process.stdin.on('end', () => {
37
+ let payload = {};
38
+ try { payload = JSON.parse(data || '{}'); } catch (e) {}
39
+ const url = process.env.UXNAN_HOOK_URL;
40
+ const token = process.env.UXNAN_HOOK_TOKEN || '';
41
+ const threadId = process.env.UXNAN_HOOK_THREAD_ID || '';
42
+ const finish = (decision, reason) => {
43
+ process.stdout.write(JSON.stringify({
44
+ hookSpecificOutput: {
45
+ hookEventName: 'PreToolUse',
46
+ permissionDecision: decision,
47
+ permissionDecisionReason: reason,
48
+ },
49
+ }));
50
+ process.exit(0);
51
+ };
52
+ // Not in approval mode (hook present but no endpoint): don't block the agent.
53
+ if (!url) return finish('allow', 'no bridge approval endpoint');
54
+ let u;
55
+ try { u = new URL(url); } catch (e) { return finish('deny', 'bad bridge hook url'); }
56
+ const body = JSON.stringify({
57
+ threadId,
58
+ toolName: payload.tool_name,
59
+ input: payload.tool_input,
60
+ toolUseId: payload.tool_use_id,
61
+ });
62
+ const req = http.request(
63
+ {
64
+ hostname: u.hostname,
65
+ port: u.port,
66
+ path: u.pathname,
67
+ method: 'POST',
68
+ headers: {
69
+ 'content-type': 'application/json',
70
+ 'content-length': Buffer.byteLength(body),
71
+ 'x-uxnan-hook-token': token,
72
+ },
73
+ },
74
+ (res) => {
75
+ let out = '';
76
+ res.on('data', (c) => (out += c));
77
+ res.on('end', () => {
78
+ try {
79
+ const j = JSON.parse(out);
80
+ if (j && j.decision === 'allow') return finish('allow', 'approved on your phone');
81
+ return finish('deny', (j && j.reason) || 'rejected on your phone');
82
+ } catch (e) {
83
+ return finish('deny', 'bridge approval error');
84
+ }
85
+ });
86
+ },
87
+ );
88
+ req.on('error', () => finish('deny', 'bridge unreachable'));
89
+ // No client timeout: the bridge holds the response until the user answers or
90
+ // its own timeout fires (returning deny).
91
+ req.write(body);
92
+ req.end();
93
+ });
94
+ `;
95
+ //# sourceMappingURL=claude-approval-hook.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claude-approval-hook.js","sourceRoot":"","sources":["../../../src/hooks/claude-approval-hook.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAAC,UAAkB;IAC9D,MAAM,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACtD,MAAM,SAAS,CAAC,UAAU,EAAE,2BAA2B,EAAE,OAAO,CAAC,CAAC;IAClE,OAAO,UAAU,CAAC;AACpB,CAAC;AAED,MAAM,CAAC,MAAM,2BAA2B,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8DpD,CAAC"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Writes the Gemini hook script to [scriptPath] (creating its directory) and
3
+ * returns the path. Idempotent — overwrites each call so an updated bridge
4
+ * ships a fresh script. Best-effort caller should handle write failures.
5
+ */
6
+ export declare function writeGeminiApprovalHook(scriptPath: string): Promise<string>;
7
+ export declare const GEMINI_APPROVAL_HOOK_SCRIPT: string;
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Gemini CLI `BeforeTool` approval hook.
3
+ *
4
+ * Gemini CLI uses the same hook contract as Claude Code (the CLI ships a
5
+ * `gemini hooks migrate` command that imports Claude hook settings). This
6
+ * module ships a hook script that the bridge writes to
7
+ * `~/.uxnan/hooks/gemini-approval-hook.cjs`, then the adapter injects a
8
+ * matching `BeforeTool` entry in `<cwd>/.gemini/settings.json` so EVERY tool
9
+ * Gemini wants to run is round-tripped to the bridge's local HTTP endpoint.
10
+ *
11
+ * The hook reads the BeforeTool payload (`{ tool_name, tool_input, ... }`) on
12
+ * stdin, POSTs it to `POST /agent-hook/approval` on the bridge (URL + token +
13
+ * threadId from env the adapter injects per turn), and prints the decision
14
+ * Gemini consumes:
15
+ * { "decision": "deny", "reason": "..." } — tool is denied
16
+ * exit 2 — same as deny, stderr is the reason
17
+ * exit 0 + (no output) — tool is allowed
18
+ *
19
+ * Fail-safe: any error (bad URL, unreachable bridge, malformed response) → deny
20
+ * with a clear reason. The script is dependency-free (only `node:http`/`node:url`).
21
+ *
22
+ * Source: bridge/CHANGELOG.md (2026-06 entry) + `architecture/02a` §6.2.
23
+ */
24
+ import { mkdir, writeFile } from 'node:fs/promises';
25
+ import { dirname } from 'node:path';
26
+ /**
27
+ * Writes the Gemini hook script to [scriptPath] (creating its directory) and
28
+ * returns the path. Idempotent — overwrites each call so an updated bridge
29
+ * ships a fresh script. Best-effort caller should handle write failures.
30
+ */
31
+ export async function writeGeminiApprovalHook(scriptPath) {
32
+ await mkdir(dirname(scriptPath), { recursive: true });
33
+ await writeFile(scriptPath, GEMINI_APPROVAL_HOOK_SCRIPT, 'utf-8');
34
+ return scriptPath;
35
+ }
36
+ export const GEMINI_APPROVAL_HOOK_SCRIPT = String.raw `'use strict';
37
+ // Gemini CLI BeforeTool approval hook — the bridge writes this script under
38
+ // ~/.uxnan/hooks/ and references it from <cwd>/.gemini/settings.json when
39
+ // the user enables interactive approvals (agents.gemini-cli.interactiveApprovals).
40
+ // Fail-safe: any error → deny.
41
+ const http = require('node:http');
42
+ let data = '';
43
+ process.stdin.setEncoding('utf-8');
44
+ process.stdin.on('data', (c) => (data += c));
45
+ process.stdin.on('end', () => {
46
+ let payload = {};
47
+ try { payload = JSON.parse(data || '{}'); } catch (e) { /* default {} */ }
48
+ const url = process.env.UXNAN_HOOK_URL;
49
+ const token = process.env.UXNAN_HOOK_TOKEN || '';
50
+ const threadId = process.env.UXNAN_HOOK_THREAD_ID || '';
51
+ const toolName = typeof payload.tool_name === 'string' ? payload.tool_name : 'tool';
52
+ const toolInput = (payload.tool_input && typeof payload.tool_input === 'object')
53
+ ? payload.tool_input
54
+ : {};
55
+ const toolCallId = typeof payload.tool_call_id === 'string' ? payload.tool_call_id : '';
56
+
57
+ // Allow the tool when the bridge has no approval endpoint wired (defensive —
58
+ // the bridge only injects the hook when the endpoint exists, but a misconfig
59
+ // shouldn't silently deny every tool).
60
+ const finish = (decision, reason) => {
61
+ if (decision === 'deny') {
62
+ // Gemini's documented shape: exit 0 with { decision, reason }.
63
+ process.stdout.write(JSON.stringify({ decision: 'deny', reason }));
64
+ } else if (decision === 'block') {
65
+ // Exit 2 = system block; stderr is the reason the agent sees as an error.
66
+ process.stderr.write(reason || 'blocked by bridge');
67
+ process.exit(2);
68
+ }
69
+ process.exit(0);
70
+ };
71
+ if (!url) return finish('allow', 'no bridge approval endpoint');
72
+ let u;
73
+ try { u = new URL(url); } catch (e) { return finish('deny', 'bad bridge hook url'); }
74
+ const body = JSON.stringify({
75
+ threadId,
76
+ toolName,
77
+ input: toolInput,
78
+ toolCallId,
79
+ });
80
+ const req = http.request(
81
+ {
82
+ hostname: u.hostname,
83
+ port: u.port,
84
+ path: u.pathname,
85
+ method: 'POST',
86
+ headers: {
87
+ 'content-type': 'application/json',
88
+ 'content-length': Buffer.byteLength(body),
89
+ 'x-uxnan-hook-token': token,
90
+ },
91
+ },
92
+ (res) => {
93
+ let out = '';
94
+ res.on('data', (c) => (out += c));
95
+ res.on('end', () => {
96
+ try {
97
+ const j = JSON.parse(out);
98
+ if (j && j.decision === 'allow') return finish('allow', '');
99
+ return finish('deny', (j && j.reason) || 'rejected on your phone');
100
+ } catch (e) {
101
+ return finish('deny', 'bridge approval error');
102
+ }
103
+ });
104
+ },
105
+ );
106
+ req.on('error', () => finish('deny', 'bridge unreachable'));
107
+ // No client timeout: the bridge holds the response until the user answers
108
+ // (or its own 5-min timeout fires, returning deny).
109
+ req.write(body);
110
+ req.end();
111
+ });
112
+ `;
113
+ //# sourceMappingURL=gemini-approval-hook.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gemini-approval-hook.js","sourceRoot":"","sources":["../../../src/hooks/gemini-approval-hook.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAAC,UAAkB;IAC9D,MAAM,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACtD,MAAM,SAAS,CAAC,UAAU,EAAE,2BAA2B,EAAE,OAAO,CAAC,CAAC;IAClE,OAAO,UAAU,CAAC;AACpB,CAAC;AAED,MAAM,CAAC,MAAM,2BAA2B,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4EpD,CAAC"}
@@ -0,0 +1,62 @@
1
+ /**
2
+ * uxnan-bridge — public API.
3
+ *
4
+ * `startBridge()` boots the daemon core (state, identity, JSON-RPC router).
5
+ * The live transport and agent runtimes are added in later increments.
6
+ */
7
+ export { startBridge, type Bridge, type StartBridgeOptions } from './bridge.js';
8
+ export { BRIDGE_VERSION } from './version.js';
9
+ export { HandlerRouter, type RpcHandler } from './handler-router.js';
10
+ export type { BridgeContext } from './bridge-context.js';
11
+ export { DaemonState, DAEMON_FILES } from './daemon-state.js';
12
+ export { DEFAULT_DAEMON_CONFIG, resolveDaemonConfig, type DaemonConfig, type AgentSettings, type AgentModelSpec, } from './daemon-config.js';
13
+ export { ProjectRegistry, projectIdFor } from './projects/project-registry.js';
14
+ export { PushService, type PushServiceOptions, type RegisterPushParams, type TurnEndInfo as PushTurnEndInfo, } from './push/push-service.js';
15
+ export { createBridgePushSender, defaultServiceAccountPath, type PushSender, type PushPayload, } from './push/push-sender.js';
16
+ export { SessionHistoryReader, type HistorySource, type SessionHistoryOptions, } from './conversation/session-history.js';
17
+ export { PairingCodeService, type PairingCodeServiceOptions, } from './pairing/pairing-code-service.js';
18
+ export { MdnsAdvertiser, parseQuestions, buildMessage, encodeName, type MdnsAdvertiserOptions, type UdpSocketLike, } from './transport/mdns-advertiser.js';
19
+ export { SecureDeviceState, type PublicIdentity } from './secure-device-state.js';
20
+ export { InMemorySecretStore, type SecretStore } from './secret-store.js';
21
+ export { KeyringSecretStore, createDefaultSecretStore, loadNativeKeyringBackend, type KeyringBackend, } from './keyring-secret-store.js';
22
+ export { SessionState } from './session-state.js';
23
+ export { LockFile, isProcessAlive, type LockInfo } from './lock-file.js';
24
+ export { buildServicePlan, buildWindowsStartupPlan, installService, uninstallService, currentServiceEnv, isServicePlatformSupported, type ServiceEnv, type ServicePlan, type ServiceCommand, type ServicePlatform, } from './service-installer.js';
25
+ export { buildBridgeStatus, type BridgeStatusInput } from './bridge-status.js';
26
+ export { getAuthStatus, type AccountStatusDeps } from './account-status.js';
27
+ export { generatePairingPayload, renderPairingQr, type GeneratePairingOptions } from './qr.js';
28
+ export { createLogger, createFileLogger, redactSecrets, logFileFor, type Logger, type LogLevel, type FileLoggerOptions, } from './logger.js';
29
+ export { BaseAgentAdapter } from './adapters/base-adapter.js';
30
+ export { CodexAdapter, codexUsageTokens, parseCodexConfigModels, parseCodexModelList, parseCodexModelWindows, parseCodexReasoning, type CodexAdapterOptions, type CodexEvent, type SpawnedAppServer, type CodexPermissionMode, } from './adapters/codex-adapter.js';
31
+ export { resolveCodexBinary, type ResolvedCodex } from './adapters/resolve-codex.js';
32
+ export { PiAdapter, parsePiLine, parsePiModelList, parsePiUsageTokens, parsePiContextWindow, type PiAdapterOptions, type PiEvent, type PiPermissionMode, } from './adapters/pi-adapter.js';
33
+ export { resolvePiBinary, type ResolvedPi } from './adapters/resolve-pi.js';
34
+ export { GeminiAdapter, parseGeminiLine, type GeminiAdapterOptions, type GeminiEvent, type GeminiPermissionMode, } from './adapters/gemini-adapter.js';
35
+ export { resolveGeminiBinary, type ResolvedGemini } from './adapters/resolve-gemini.js';
36
+ export { OpenCodeAdapter, parseOpenCodeLine, parseModelList, parseOpenCodeModelWindows, openCodeUsageTokens, type OpenCodeAdapterOptions, type OpenCodeEvent, } from './adapters/opencode-adapter.js';
37
+ export { resolveOpenCodeBinary, type ResolvedOpenCode } from './adapters/resolve-opencode.js';
38
+ export { defaultSpawn, type SpawnFn, type SpawnedProcess } from './adapters/spawn.js';
39
+ export { ClaudeCodeAdapter, claudeContextWindow, claudeUsageTokens, parseClaudeLine, type ClaudeCodeAdapterOptions, type ClaudeEvent, type ClaudeModelSpec, type ClaudePermissionMode, } from './adapters/claude-adapter.js';
40
+ export { resolveClaudeBinary, type ResolvedClaude } from './adapters/resolve-claude.js';
41
+ export { EchoAgentAdapter } from './adapters/echo-agent-adapter.js';
42
+ export { ProcessAgentAdapter, type ProcessAdapterOptions, } from './adapters/process-agent-adapter.js';
43
+ export { ThreadStore, type StartTurnResult, type StartThreadInput, type ThreadRuntime, } from './conversation/thread-store.js';
44
+ export { AgentManager, type AgentManagerOptions, type AgentMeta, type SendTurnOptions as AgentManagerSendTurnOptions, } from './agents/agent-manager.js';
45
+ export { type MessageIO, MessageQueue, queueFor, createInMemoryIoPair, } from './transport/message-io.js';
46
+ export { generateEphemeralKeyPair, deriveSessionKey, randomHex, verifyEd25519, aesGcmEncrypt, aesGcmDecrypt, type EphemeralKeyPair, type AesGcmParts, } from './transport/crypto.js';
47
+ export { BridgeSecureChannel, ReplayError } from './transport/secure-channel.js';
48
+ export { performServerHandshake, HandshakeError, type ServerHandshakeResult, type ServerHandshakeOptions, } from './transport/server-handshake.js';
49
+ export { handleSecureConnection, type SecureConnectionOptions, } from './transport/session-handler.js';
50
+ export { FileTrustStore, type TrustStore } from './transport/trust-store.js';
51
+ export { connectRelayAsMac, type ConnectRelayOptions, type RelayConnection, } from './transport/relay-client.js';
52
+ export { startLanServer, type LanServerOptions, type LanServerHandle, } from './transport/lan-server.js';
53
+ export { wsToMessageIO, rawDataToBuffer } from './transport/ws-adapter.js';
54
+ export { localHostPorts, type InterfaceMap } from './transport/local-hosts.js';
55
+ export { OutboundLog, type OutboundLogEntry } from './transport/outbound-log.js';
56
+ export { SessionRegistry, type SessionSink } from './transport/session-registry.js';
57
+ export { GitService } from './git/git-service.js';
58
+ export { runGit, GitCommandError, sanitizePaths, type RunGitResult } from './git/git-runner.js';
59
+ export { WorkspaceService } from './workspace/workspace-service.js';
60
+ export { BrowseService, browseRootIdFor } from './workspace/browse-service.js';
61
+ export { CheckpointService, type CaptureOptions } from './workspace/checkpoint-service.js';
62
+ export { resolveWithinRoot, isSensitiveName } from './workspace/path-guard.js';