@tt-a1i/hive 1.7.0 → 2.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (251) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/README.en.md +73 -11
  3. package/README.md +41 -8
  4. package/dist/src/cli/hive-remote.d.ts +46 -0
  5. package/dist/src/cli/hive-remote.js +257 -0
  6. package/dist/src/cli/hive-update.js +7 -2
  7. package/dist/src/cli/hive.d.ts +6 -0
  8. package/dist/src/cli/hive.js +64 -0
  9. package/dist/src/cli/team.d.ts +22 -0
  10. package/dist/src/cli/team.js +255 -5
  11. package/dist/src/server/agent-command-resolver.js +10 -3
  12. package/dist/src/server/agent-exit-classification.d.ts +6 -0
  13. package/dist/src/server/agent-exit-classification.js +6 -0
  14. package/dist/src/server/agent-manager-support.d.ts +2 -1
  15. package/dist/src/server/agent-manager-support.js +59 -15
  16. package/dist/src/server/agent-manager.d.ts +3 -0
  17. package/dist/src/server/agent-manager.js +22 -7
  18. package/dist/src/server/agent-run-bootstrap.d.ts +14 -0
  19. package/dist/src/server/agent-run-bootstrap.js +11 -4
  20. package/dist/src/server/agent-run-exit-handler.js +14 -8
  21. package/dist/src/server/agent-run-starter.d.ts +3 -1
  22. package/dist/src/server/agent-run-starter.js +22 -5
  23. package/dist/src/server/agent-run-sync.js +13 -5
  24. package/dist/src/server/agent-runtime-types.d.ts +1 -0
  25. package/dist/src/server/agent-runtime.d.ts +2 -1
  26. package/dist/src/server/agent-runtime.js +9 -2
  27. package/dist/src/server/agent-startup-instructions.d.ts +2 -1
  28. package/dist/src/server/agent-startup-instructions.js +8 -4
  29. package/dist/src/server/agent-stdin-dispatcher.d.ts +4 -2
  30. package/dist/src/server/agent-stdin-dispatcher.js +35 -3
  31. package/dist/src/server/command-preset-defaults.d.ts +6 -1
  32. package/dist/src/server/command-preset-defaults.js +56 -0
  33. package/dist/src/server/fs-browse.d.ts +2 -0
  34. package/dist/src/server/fs-browse.js +165 -31
  35. package/dist/src/server/fs-pick-folder.js +6 -69
  36. package/dist/src/server/fs-sandbox.d.ts +5 -3
  37. package/dist/src/server/fs-sandbox.js +5 -3
  38. package/dist/src/server/hive-team-guidance.js +18 -6
  39. package/dist/src/server/machine-name.d.ts +2 -0
  40. package/dist/src/server/machine-name.js +13 -0
  41. package/dist/src/server/open-target-commands.d.ts +1 -0
  42. package/dist/src/server/open-target-commands.js +4 -1
  43. package/dist/src/server/orchestrator-autostart.js +1 -1
  44. package/dist/src/server/platform-path.d.ts +1 -0
  45. package/dist/src/server/platform-path.js +14 -1
  46. package/dist/src/server/post-start-input-writer.js +50 -13
  47. package/dist/src/server/preset-launch-support.js +1 -0
  48. package/dist/src/server/recovery-summary.d.ts +2 -1
  49. package/dist/src/server/recovery-summary.js +2 -1
  50. package/dist/src/server/remote-audit-store.d.ts +51 -0
  51. package/dist/src/server/remote-audit-store.js +108 -0
  52. package/dist/src/server/remote-config-keys.d.ts +17 -0
  53. package/dist/src/server/remote-config-keys.js +27 -0
  54. package/dist/src/server/remote-control-constants.d.ts +30 -0
  55. package/dist/src/server/remote-control-constants.js +29 -0
  56. package/dist/src/server/remote-device-session.d.ts +40 -0
  57. package/dist/src/server/remote-device-session.js +22 -0
  58. package/dist/src/server/remote-device-store.d.ts +36 -0
  59. package/dist/src/server/remote-device-store.js +67 -0
  60. package/dist/src/server/remote-frame-bridge.d.ts +102 -0
  61. package/dist/src/server/remote-frame-bridge.js +791 -0
  62. package/dist/src/server/remote-gateway-client.d.ts +14 -0
  63. package/dist/src/server/remote-gateway-client.js +36 -0
  64. package/dist/src/server/remote-loopback-auth.d.ts +6 -0
  65. package/dist/src/server/remote-loopback-auth.js +112 -0
  66. package/dist/src/server/remote-pairing-tunnel.d.ts +59 -0
  67. package/dist/src/server/remote-pairing-tunnel.js +146 -0
  68. package/dist/src/server/remote-pairing.d.ts +58 -0
  69. package/dist/src/server/remote-pairing.js +237 -0
  70. package/dist/src/server/remote-tunnel.d.ts +113 -0
  71. package/dist/src/server/remote-tunnel.js +514 -0
  72. package/dist/src/server/restart-policy-support.d.ts +4 -1
  73. package/dist/src/server/restart-policy-support.js +3 -1
  74. package/dist/src/server/restart-policy.d.ts +1 -1
  75. package/dist/src/server/restart-policy.js +19 -3
  76. package/dist/src/server/route-types.d.ts +1 -1
  77. package/dist/src/server/routes-dispatches.js +1 -1
  78. package/dist/src/server/routes-fs.js +3 -3
  79. package/dist/src/server/routes-marketplace.js +2 -2
  80. package/dist/src/server/routes-open-workspace.js +1 -1
  81. package/dist/src/server/routes-remote.d.ts +2 -0
  82. package/dist/src/server/routes-remote.js +166 -0
  83. package/dist/src/server/routes-runtime.js +6 -6
  84. package/dist/src/server/routes-settings.js +16 -16
  85. package/dist/src/server/routes-tasks.js +2 -2
  86. package/dist/src/server/routes-team-memory.d.ts +2 -0
  87. package/dist/src/server/routes-team-memory.js +154 -0
  88. package/dist/src/server/routes-team-recall.d.ts +2 -0
  89. package/dist/src/server/routes-team-recall.js +119 -0
  90. package/dist/src/server/routes-team.js +31 -9
  91. package/dist/src/server/routes-ui.js +11 -1
  92. package/dist/src/server/routes-workflow-schedules.js +3 -3
  93. package/dist/src/server/routes-workflows.js +5 -5
  94. package/dist/src/server/routes-workspace-memory-dreams.d.ts +2 -0
  95. package/dist/src/server/routes-workspace-memory-dreams.js +105 -0
  96. package/dist/src/server/routes-workspace-memory.d.ts +2 -0
  97. package/dist/src/server/routes-workspace-memory.js +215 -0
  98. package/dist/src/server/routes-workspaces.js +9 -9
  99. package/dist/src/server/routes.js +10 -0
  100. package/dist/src/server/runtime-database.d.ts +1 -0
  101. package/dist/src/server/runtime-database.js +27 -2
  102. package/dist/src/server/runtime-restart-policy.d.ts +3 -1
  103. package/dist/src/server/runtime-restart-policy.js +2 -1
  104. package/dist/src/server/runtime-store-contract.d.ts +37 -0
  105. package/dist/src/server/runtime-store-dream.d.ts +23 -0
  106. package/dist/src/server/runtime-store-dream.js +16 -0
  107. package/dist/src/server/runtime-store-helpers.d.ts +20 -0
  108. package/dist/src/server/runtime-store-helpers.js +81 -7
  109. package/dist/src/server/runtime-store-memory.d.ts +33 -0
  110. package/dist/src/server/runtime-store-memory.js +37 -0
  111. package/dist/src/server/runtime-store-remote.d.ts +5 -0
  112. package/dist/src/server/runtime-store-remote.js +45 -0
  113. package/dist/src/server/runtime-store-workflows.js +2 -0
  114. package/dist/src/server/runtime-store.js +14 -3
  115. package/dist/src/server/session-capture-claude.d.ts +1 -1
  116. package/dist/src/server/session-capture-claude.js +7 -4
  117. package/dist/src/server/session-capture-codex.js +4 -5
  118. package/dist/src/server/session-capture-gemini.js +4 -5
  119. package/dist/src/server/session-capture-opencode.d.ts +4 -4
  120. package/dist/src/server/session-capture-opencode.js +20 -12
  121. package/dist/src/server/session-capture-qwen.d.ts +5 -0
  122. package/dist/src/server/session-capture-qwen.js +104 -0
  123. package/dist/src/server/session-capture.d.ts +17 -0
  124. package/dist/src/server/session-capture.js +16 -0
  125. package/dist/src/server/sqlite-schema-v23.d.ts +2 -0
  126. package/dist/src/server/sqlite-schema-v23.js +43 -0
  127. package/dist/src/server/sqlite-schema-v24.d.ts +2 -0
  128. package/dist/src/server/sqlite-schema-v24.js +34 -0
  129. package/dist/src/server/sqlite-schema-v25.d.ts +2 -0
  130. package/dist/src/server/sqlite-schema-v25.js +127 -0
  131. package/dist/src/server/sqlite-schema-v26.d.ts +2 -0
  132. package/dist/src/server/sqlite-schema-v26.js +56 -0
  133. package/dist/src/server/sqlite-schema-v27.d.ts +6 -0
  134. package/dist/src/server/sqlite-schema-v27.js +92 -0
  135. package/dist/src/server/sqlite-schema-v28.d.ts +2 -0
  136. package/dist/src/server/sqlite-schema-v28.js +19 -0
  137. package/dist/src/server/sqlite-schema-v29.d.ts +2 -0
  138. package/dist/src/server/sqlite-schema-v29.js +27 -0
  139. package/dist/src/server/sqlite-schema-v30.d.ts +2 -0
  140. package/dist/src/server/sqlite-schema-v30.js +27 -0
  141. package/dist/src/server/sqlite-schema-v31.d.ts +2 -0
  142. package/dist/src/server/sqlite-schema-v31.js +30 -0
  143. package/dist/src/server/sqlite-schema.d.ts +1 -1
  144. package/dist/src/server/sqlite-schema.js +49 -1
  145. package/dist/src/server/startup-command-parser.js +5 -1
  146. package/dist/src/server/tasks-file-watcher.d.ts +2 -0
  147. package/dist/src/server/tasks-file-watcher.js +15 -6
  148. package/dist/src/server/tasks-file.js +30 -5
  149. package/dist/src/server/tasks-websocket-server.js +4 -0
  150. package/dist/src/server/team-authz.d.ts +1 -1
  151. package/dist/src/server/team-authz.js +13 -1
  152. package/dist/src/server/team-list-enrichment.js +3 -1
  153. package/dist/src/server/team-memory-digest.d.ts +52 -0
  154. package/dist/src/server/team-memory-digest.js +200 -0
  155. package/dist/src/server/team-memory-dream-applier.d.ts +5 -0
  156. package/dist/src/server/team-memory-dream-applier.js +234 -0
  157. package/dist/src/server/team-memory-dream-http-serializers.d.ts +13 -0
  158. package/dist/src/server/team-memory-dream-http-serializers.js +12 -0
  159. package/dist/src/server/team-memory-dream-ops.d.ts +40 -0
  160. package/dist/src/server/team-memory-dream-ops.js +153 -0
  161. package/dist/src/server/team-memory-dream-reverter.d.ts +22 -0
  162. package/dist/src/server/team-memory-dream-reverter.js +221 -0
  163. package/dist/src/server/team-memory-dream-run-store.d.ts +23 -0
  164. package/dist/src/server/team-memory-dream-run-store.js +211 -0
  165. package/dist/src/server/team-memory-dream-runner.d.ts +37 -0
  166. package/dist/src/server/team-memory-dream-runner.js +178 -0
  167. package/dist/src/server/team-memory-dream-scheduler.d.ts +32 -0
  168. package/dist/src/server/team-memory-dream-scheduler.js +115 -0
  169. package/dist/src/server/team-memory-dream-store.d.ts +19 -0
  170. package/dist/src/server/team-memory-dream-store.js +16 -0
  171. package/dist/src/server/team-memory-dream-types.d.ts +104 -0
  172. package/dist/src/server/team-memory-dream-types.js +23 -0
  173. package/dist/src/server/team-memory-export.d.ts +22 -0
  174. package/dist/src/server/team-memory-export.js +220 -0
  175. package/dist/src/server/team-memory-feature.d.ts +12 -0
  176. package/dist/src/server/team-memory-feature.js +12 -0
  177. package/dist/src/server/team-memory-http-serializers.d.ts +102 -0
  178. package/dist/src/server/team-memory-http-serializers.js +46 -0
  179. package/dist/src/server/team-memory-injection.d.ts +31 -0
  180. package/dist/src/server/team-memory-injection.js +49 -0
  181. package/dist/src/server/team-memory-store.d.ts +116 -0
  182. package/dist/src/server/team-memory-store.js +513 -0
  183. package/dist/src/server/team-operations.d.ts +5 -1
  184. package/dist/src/server/team-operations.js +46 -16
  185. package/dist/src/server/team-recall-store.d.ts +38 -0
  186. package/dist/src/server/team-recall-store.js +205 -0
  187. package/dist/src/server/terminal-input-profile.d.ts +1 -1
  188. package/dist/src/server/terminal-input-profile.js +18 -0
  189. package/dist/src/server/terminal-ws-server.js +6 -0
  190. package/dist/src/server/ui-auth-helpers.d.ts +1 -1
  191. package/dist/src/server/ui-auth-helpers.js +7 -1
  192. package/dist/src/server/ui-auth.d.ts +3 -0
  193. package/dist/src/server/ui-auth.js +21 -1
  194. package/dist/src/server/workflow-cli-policy.d.ts +2 -3
  195. package/dist/src/server/workflow-cli-policy.js +3 -3
  196. package/dist/src/server/workflow-runner.d.ts +1 -0
  197. package/dist/src/server/workflow-runner.js +9 -4
  198. package/dist/src/server/workspace-path-validation.js +6 -2
  199. package/dist/src/server/workspace-store.d.ts +1 -1
  200. package/dist/src/server/workspace-store.js +35 -9
  201. package/dist/src/shared/fs-browse.d.ts +1 -0
  202. package/dist/src/shared/fs-browse.js +1 -0
  203. package/dist/src/shared/path-input.d.ts +12 -0
  204. package/dist/src/shared/path-input.js +22 -0
  205. package/dist/src/shared/remote-bridge-routing.d.ts +19 -0
  206. package/dist/src/shared/remote-bridge-routing.js +141 -0
  207. package/dist/src/shared/remote-crypto.d.ts +138 -0
  208. package/dist/src/shared/remote-crypto.js +427 -0
  209. package/dist/src/shared/remote-pairing-code.d.ts +7 -0
  210. package/dist/src/shared/remote-pairing-code.js +47 -0
  211. package/dist/src/shared/remote-protocol.d.ts +160 -0
  212. package/dist/src/shared/remote-protocol.js +526 -0
  213. package/dist/src/shared/team-memory.d.ts +11 -0
  214. package/dist/src/shared/team-memory.js +10 -0
  215. package/dist/src/shared/team-recall.d.ts +1 -0
  216. package/dist/src/shared/team-recall.js +1 -0
  217. package/dist/src/shared/types.d.ts +4 -5
  218. package/package.json +12 -5
  219. package/scripts/postinstall-native-artifacts.mjs +113 -0
  220. package/web/dist/assets/AddWorkerDialog-CbV75qUX.js +2 -0
  221. package/web/dist/assets/AddWorkspaceFlow-CwV-7wPx.js +1 -0
  222. package/web/dist/assets/FirstRunWizard-a6PWIK3x.js +1 -0
  223. package/web/dist/assets/MarketplaceDrawer-Dd8WIA8T.js +67 -0
  224. package/web/dist/assets/TaskGraphDrawer-Bk5WFIk_.js +1 -0
  225. package/web/dist/assets/{WhatsNewDialog-CHkZeINH.js → WhatsNewDialog-C2VZaip0.js} +1 -1
  226. package/web/dist/assets/WorkerModal-DucW-9YT.js +1 -0
  227. package/web/dist/assets/WorkflowsDrawer-Bjf4olbR.js +1 -0
  228. package/web/dist/assets/WorkspaceMemoryDrawer-DglCy_5f.js +1 -0
  229. package/web/dist/assets/WorkspaceTaskDrawer-BIWwISvA.js +1 -0
  230. package/web/dist/assets/index-BAiLYajK.css +1 -0
  231. package/web/dist/assets/index-BV2k9Dts.js +73 -0
  232. package/web/dist/assets/search-Bk2HQvO7.js +1 -0
  233. package/web/dist/assets/square-terminal-D93m9hfY.js +1 -0
  234. package/web/dist/cli-icons/agy.png +0 -0
  235. package/web/dist/cli-icons/cursor.ico +0 -0
  236. package/web/dist/cli-icons/grok.ico +0 -0
  237. package/web/dist/cli-icons/qwen.png +0 -0
  238. package/web/dist/index.html +8 -3
  239. package/web/dist/sw.js +1 -1
  240. package/scripts/fix-runtime-artifacts.mjs +0 -33
  241. package/web/dist/assets/AddWorkerDialog-BRUxpa3f.js +0 -2
  242. package/web/dist/assets/AddWorkspaceDialog-D56x5JCb.js +0 -1
  243. package/web/dist/assets/FirstRunWizard-BFVaMIsE.js +0 -1
  244. package/web/dist/assets/MarketplaceDrawer-DeEZ35dN.js +0 -76
  245. package/web/dist/assets/WorkerModal-BBCuMLIa.js +0 -1
  246. package/web/dist/assets/WorkspaceTaskDrawer-CpZHAcj1.js +0 -1
  247. package/web/dist/assets/WorkspaceTerminalPanels-7If2mDyp.js +0 -1
  248. package/web/dist/assets/WorkspaceTerminalPanels-DDGTF8rc.css +0 -1
  249. package/web/dist/assets/index-5zh61jMg.css +0 -1
  250. package/web/dist/assets/index-CxNL0O-C.js +0 -73
  251. package/web/dist/assets/path-join-7MR1s7b1.js +0 -1
@@ -1,3 +1,4 @@
1
+ import { resolveCommandPresetLaunchConfig } from './agent-launch-resolver.js';
1
2
  import { readWorkflowCliPolicy, WORKFLOW_CLI_POLICY_KEY } from './workflow-cli-policy.js';
2
3
  import { readWorkflowEnabled, WORKFLOW_ENABLED_KEY } from './workflow-feature.js';
3
4
  import { createWorkflowRunner } from './workflow-runner.js';
@@ -14,6 +15,7 @@ export const createRuntimeStoreWorkflowRuntime = (services, store) => {
14
15
  append: (runId, message, ts) => services.workflowRunLogStore.append(runId, message, ts),
15
16
  },
16
17
  getWorkflowCliPolicy: () => readWorkflowCliPolicy(services.settings.getAppState(WORKFLOW_CLI_POLICY_KEY)?.value ?? null),
18
+ resolveCliLaunchConfig: (cli) => resolveCommandPresetLaunchConfig(services.settings, cli),
17
19
  roleTemplateResolver: {
18
20
  findByName: (name) => {
19
21
  const t = services.settings.findRoleTemplateByName(name);
@@ -1,4 +1,7 @@
1
+ import { createRuntimeStoreDreamMethods } from './runtime-store-dream.js';
1
2
  import { createRuntimeStoreLifecycle, createRuntimeStoreServices, logTasksFileWatchStartError, } from './runtime-store-helpers.js';
3
+ import { createRuntimeStoreMemoryMethods } from './runtime-store-memory.js';
4
+ import { createRuntimeStoreRemoteMethods } from './runtime-store-remote.js';
2
5
  import { createRuntimeStoreWorkflowRuntime } from './runtime-store-workflows.js';
3
6
  import { persistWorkflowSchedule } from './workflow-schedule-create.js';
4
7
  export const createRuntimeStore = (options = {}) => {
@@ -20,6 +23,7 @@ export const createRuntimeStore = (options = {}) => {
20
23
  const store = {
21
24
  close: async () => {
22
25
  workflowRuntime?.scheduler.close();
26
+ await services.teamMemoryDreamScheduler.close();
23
27
  await lifecycle.close();
24
28
  },
25
29
  createWorkspace: (path, name) => {
@@ -40,8 +44,11 @@ export const createRuntimeStore = (options = {}) => {
40
44
  services.agentRuntime.deleteAgentLaunchConfig(workspaceId, agent.id);
41
45
  }
42
46
  await services.tasksFileWatcher.stop(workspaceId);
47
+ services.teamMemoryExport.cancel(workspaceId);
43
48
  runDataMutation(() => {
44
49
  services.dispatchLedgerStore.deleteWorkspaceDispatches(workspaceId);
50
+ services.teamMemoryStore.deleteWorkspaceMemories(workspaceId);
51
+ services.teamMemoryDreamStore.deleteWorkspaceDreamRuns(workspaceId);
45
52
  services.workspaceStore.deleteWorkspace(workspaceId);
46
53
  });
47
54
  if (services.settings.getAppState('active_workspace_id')?.value === workspaceId) {
@@ -80,13 +87,13 @@ export const createRuntimeStore = (options = {}) => {
80
87
  renameWorker: (workspaceId, workerId, name) => services.workspaceStore.renameWorker(workspaceId, workerId, name),
81
88
  deleteWorker: (workspaceId, workerId) => {
82
89
  const activeRun = services.agentRuntime.getActiveRunByAgentId(workspaceId, workerId);
83
- if (activeRun)
84
- services.agentRuntime.stopAgentRun(activeRun.runId);
85
- services.agentRuntime.deleteAgentLaunchConfig(workspaceId, workerId);
86
90
  runDataMutation(() => {
87
91
  services.dispatchLedgerStore.deleteWorkerDispatches(workspaceId, workerId);
88
92
  services.workspaceStore.deleteWorker(workspaceId, workerId);
89
93
  });
94
+ services.agentRuntime.deleteAgentLaunchConfig(workspaceId, workerId);
95
+ if (activeRun)
96
+ services.agentRuntime.stopAgentRun(activeRun.runId);
90
97
  },
91
98
  recordUserInput: services.teamOps.recordUserInput,
92
99
  cancelTask: services.teamOps.cancelTask,
@@ -115,6 +122,9 @@ export const createRuntimeStore = (options = {}) => {
115
122
  registerTasksListener: lifecycle.registerTasksListener,
116
123
  listAgentRuns: (agentId) => services.agentRuntime.listAgentRuns(agentId),
117
124
  listMessagesForRecovery: (workspaceId, sinceMs) => services.messageLogStore.listMessagesForRecovery(workspaceId, sinceMs),
125
+ recallMessages: (workspaceId, query, options) => services.teamRecallStore.recallMessages(workspaceId, query, options),
126
+ ...createRuntimeStoreMemoryMethods(services),
127
+ ...createRuntimeStoreDreamMethods(services),
118
128
  peekAgentToken: (agentId) => services.agentRuntime.peekAgentToken(agentId),
119
129
  pauseTerminalRun: lifecycle.pauseTerminalRun,
120
130
  resizeAgentRun: lifecycle.resizeTerminalRun,
@@ -125,6 +135,7 @@ export const createRuntimeStore = (options = {}) => {
125
135
  stopAgentRun: lifecycle.stopTerminalRun,
126
136
  validateAgentToken: (agentId, token) => services.agentRuntime.validateAgentToken(agentId, token),
127
137
  validateUiToken: (token) => services.uiAuth.validate(token),
138
+ ...createRuntimeStoreRemoteMethods(services),
128
139
  getWorkflowDispatchAwaiter: () => services.workflowDispatchAwaiter,
129
140
  runWorkflow: (input) => getWorkflowRuntime().runner.runWorkflow(input),
130
141
  startWorkflow: (input) => getWorkflowRuntime().runner.startWorkflow(input),
@@ -1,4 +1,4 @@
1
- export declare const getClaudeProjectsRoot: (pattern?: string) => string;
1
+ export declare const getClaudeProjectsRoot: (pattern?: string, platform?: NodeJS.Platform) => string;
2
2
  /**
3
3
  * Match the directory-name encoding Claude Code itself uses for its project
4
4
  * metadata under `~/.claude/projects/`. Empirically (probed via `claude
@@ -2,9 +2,10 @@ import { existsSync, readdirSync, readFileSync } from 'node:fs';
2
2
  import { homedir } from 'node:os';
3
3
  import { join } from 'node:path';
4
4
  import { captureSessionIdWithCoordinator, resetSessionCaptureCoordinatorForTests, } from './claude-session-coordinator.js';
5
+ import { arePathsEqual, expandHomePath } from './platform-path.js';
5
6
  const SESSION_FILE = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}\.jsonl$/i;
6
7
  const getDefaultProjectsRoot = () => process.env.HIVE_CLAUDE_PROJECTS_DIR ?? join(homedir(), '.claude/projects');
7
- export const getClaudeProjectsRoot = (pattern) => {
8
+ export const getClaudeProjectsRoot = (pattern, platform = process.platform) => {
8
9
  if (!pattern)
9
10
  return getDefaultProjectsRoot();
10
11
  const markerIndex = pattern.indexOf('{encoded_cwd}');
@@ -13,10 +14,12 @@ export const getClaudeProjectsRoot = (pattern) => {
13
14
  const root = pattern.slice(0, markerIndex).replace(/[\\/]+$/, '');
14
15
  if (!root)
15
16
  return getDefaultProjectsRoot();
16
- if (root === '~' || root.startsWith('~/')) {
17
- return process.env.HIVE_CLAUDE_PROJECTS_DIR ?? join(homedir(), '.claude', 'projects');
17
+ const builtInProjectsRoot = join(homedir(), '.claude', 'projects');
18
+ const expandedRoot = expandHomePath(root);
19
+ if (root === '~' || arePathsEqual(expandedRoot, builtInProjectsRoot, platform)) {
20
+ return getDefaultProjectsRoot();
18
21
  }
19
- return root;
22
+ return expandedRoot;
20
23
  };
21
24
  /**
22
25
  * Match the directory-name encoding Claude Code itself uses for its project
@@ -2,23 +2,22 @@ import { closeSync, existsSync, openSync, readdirSync, readSync } from 'node:fs'
2
2
  import { homedir } from 'node:os';
3
3
  import { join } from 'node:path';
4
4
  import { captureSessionIdWithCoordinator } from './claude-session-coordinator.js';
5
- import { arePathsEqual, indexOfPathMarker } from './platform-path.js';
5
+ import { arePathsEqual, expandHomePath, indexOfPathMarker } from './platform-path.js';
6
6
  const CODEX_SESSION_FILE = /^rollout-.*\.jsonl$/i;
7
7
  const CODEX_SESSIONS_MARKER = '/sessions/';
8
8
  const CODEX_HEADER_READ_CHUNK_BYTES = 4096;
9
9
  const CODEX_HEADER_MAX_BYTES = 64 * 1024;
10
10
  const getDefaultCodexHome = () => process.env.CODEX_HOME ?? join(homedir(), '.codex');
11
- const expandHome = (path) => path === '~' || path.startsWith('~/') ? join(homedir(), path.slice(2)) : path;
12
11
  export const getCodexHome = (pattern, platform = process.platform) => {
13
12
  if (!pattern)
14
13
  return getDefaultCodexHome();
15
14
  const markerIndex = indexOfPathMarker(pattern, CODEX_SESSIONS_MARKER, platform);
16
15
  if (markerIndex === -1)
17
16
  return getDefaultCodexHome();
18
- const rawRoot = pattern.slice(0, markerIndex);
19
- if (arePathsEqual(rawRoot, '~/.codex', platform) || arePathsEqual(rawRoot, '~/.codex/', platform))
17
+ const rawRoot = pattern.slice(0, markerIndex).replace(/[\\/]+$/u, '');
18
+ const root = expandHomePath(rawRoot);
19
+ if (arePathsEqual(root, join(homedir(), '.codex'), platform))
20
20
  return getDefaultCodexHome();
21
- const root = expandHome(rawRoot);
22
21
  return root || getDefaultCodexHome();
23
22
  };
24
23
  const walkSessionFiles = (dir) => {
@@ -2,21 +2,20 @@ import { existsSync, readdirSync, readFileSync } from 'node:fs';
2
2
  import { homedir } from 'node:os';
3
3
  import { join } from 'node:path';
4
4
  import { captureSessionIdWithCoordinator } from './claude-session-coordinator.js';
5
- import { arePathsEqual, indexOfPathMarker } from './platform-path.js';
5
+ import { arePathsEqual, expandHomePath, indexOfPathMarker } from './platform-path.js';
6
6
  const GEMINI_SESSION_FILE = /^session-.*\.json$/i;
7
7
  const GEMINI_TMP_MARKER = '/tmp/';
8
8
  const getDefaultGeminiHome = () => process.env.HIVE_GEMINI_HOME ?? join(homedir(), '.gemini');
9
- const expandHome = (path) => path === '~' || path.startsWith('~/') ? join(homedir(), path.slice(2)) : path;
10
9
  export const getGeminiHome = (pattern, platform = process.platform) => {
11
10
  if (!pattern)
12
11
  return getDefaultGeminiHome();
13
12
  const markerIndex = indexOfPathMarker(pattern, GEMINI_TMP_MARKER, platform);
14
13
  if (markerIndex === -1)
15
14
  return getDefaultGeminiHome();
16
- const rawRoot = pattern.slice(0, markerIndex);
17
- if (rawRoot === '~/.gemini' || rawRoot === '~/.gemini/')
15
+ const rawRoot = pattern.slice(0, markerIndex).replace(/[\\/]+$/u, '');
16
+ const root = expandHomePath(rawRoot);
17
+ if (arePathsEqual(root, join(homedir(), '.gemini'), platform))
18
18
  return getDefaultGeminiHome();
19
- const root = expandHome(rawRoot);
20
19
  return root || getDefaultGeminiHome();
21
20
  };
22
21
  const readProjectRoot = (projectDir) => {
@@ -16,7 +16,7 @@
16
16
  * needing to mock the underlying SQLite open.
17
17
  */
18
18
  export declare const getDefaultOpenCodeDbPath: (platform?: NodeJS.Platform) => string;
19
- export declare const getOpenCodeDbPath: (pattern?: string) => string;
20
- export declare const hasOpenCodeSession: (cwd: string, sessionId: string, pattern?: string) => boolean;
21
- export declare const snapshotOpenCodeSessionIds: (cwd: string, dbPath?: string) => Set<string>;
22
- export declare const captureOpenCodeSessionId: (cwd: string, knownSessionIds: Set<string>, onCapture: (sessionId: string) => void, timeoutMs?: number, intervalMs?: number, dbPath?: string) => Promise<void>;
19
+ export declare const getOpenCodeDbPath: (pattern?: string, platform?: NodeJS.Platform) => string;
20
+ export declare const hasOpenCodeSession: (cwd: string, sessionId: string, pattern?: string, platform?: NodeJS.Platform, dbPath?: string) => boolean;
21
+ export declare const snapshotOpenCodeSessionIds: (cwd: string, dbPath?: string, platform?: NodeJS.Platform) => Set<string>;
22
+ export declare const captureOpenCodeSessionId: (cwd: string, knownSessionIds: Set<string>, onCapture: (sessionId: string) => void, timeoutMs?: number, intervalMs?: number, dbPath?: string, platform?: NodeJS.Platform) => Promise<void>;
@@ -3,7 +3,7 @@ import { homedir } from 'node:os';
3
3
  import { join } from 'node:path';
4
4
  import Database from 'better-sqlite3';
5
5
  import { captureSessionIdWithCoordinator } from './claude-session-coordinator.js';
6
- const expandHome = (path) => path === '~' || path.startsWith('~/') ? join(homedir(), path.slice(2)) : path;
6
+ import { arePathsEqual, expandHomePath } from './platform-path.js';
7
7
  /**
8
8
  * Resolve the path OpenCode upstream writes `opencode.db` to. Branches
9
9
  * by platform because XDG_DATA_HOME is not a Windows convention — the
@@ -31,20 +31,28 @@ export const getDefaultOpenCodeDbPath = (platform = process.platform) => {
31
31
  }
32
32
  return join(process.env.XDG_DATA_HOME ?? join(homedir(), '.local', 'share'), 'opencode', 'opencode.db');
33
33
  };
34
- export const getOpenCodeDbPath = (pattern) => pattern === '~/.local/share/opencode/opencode.db' || !pattern
35
- ? getDefaultOpenCodeDbPath()
36
- : expandHome(pattern);
37
- const listSessionIds = (cwd, dbPath = getDefaultOpenCodeDbPath()) => {
34
+ export const getOpenCodeDbPath = (pattern, platform = process.platform) => {
35
+ if (!pattern)
36
+ return getDefaultOpenCodeDbPath(platform);
37
+ const expanded = expandHomePath(pattern);
38
+ if (arePathsEqual(expanded, join(homedir(), '.local', 'share', 'opencode', 'opencode.db'), platform)) {
39
+ return getDefaultOpenCodeDbPath(platform);
40
+ }
41
+ return expanded;
42
+ };
43
+ const listSessionIds = (cwd, dbPath = getDefaultOpenCodeDbPath(), platform = process.platform) => {
38
44
  if (!existsSync(dbPath))
39
45
  return [];
40
46
  let db;
41
47
  try {
42
48
  db = new Database(dbPath, { fileMustExist: true, readonly: true });
43
49
  return db
44
- .prepare(`SELECT id FROM session
45
- WHERE directory = ? AND time_archived IS NULL
50
+ .prepare(`SELECT id, directory FROM session
51
+ WHERE time_archived IS NULL
46
52
  ORDER BY rowid ASC`)
47
- .all(cwd).map((row) => row.id);
53
+ .all()
54
+ .filter((row) => arePathsEqual(row.directory, cwd, platform))
55
+ .map((row) => row.id);
48
56
  }
49
57
  catch {
50
58
  return [];
@@ -53,13 +61,13 @@ const listSessionIds = (cwd, dbPath = getDefaultOpenCodeDbPath()) => {
53
61
  db?.close();
54
62
  }
55
63
  };
56
- export const hasOpenCodeSession = (cwd, sessionId, pattern) => listSessionIds(cwd, getOpenCodeDbPath(pattern)).includes(sessionId);
57
- export const snapshotOpenCodeSessionIds = (cwd, dbPath = getDefaultOpenCodeDbPath()) => new Set(listSessionIds(cwd, dbPath));
58
- export const captureOpenCodeSessionId = async (cwd, knownSessionIds, onCapture, timeoutMs = 5000, intervalMs = 100, dbPath = getDefaultOpenCodeDbPath()) => {
64
+ export const hasOpenCodeSession = (cwd, sessionId, pattern, platform = process.platform, dbPath = getOpenCodeDbPath(pattern, platform)) => listSessionIds(cwd, dbPath, platform).includes(sessionId);
65
+ export const snapshotOpenCodeSessionIds = (cwd, dbPath = getDefaultOpenCodeDbPath(), platform = process.platform) => new Set(listSessionIds(cwd, dbPath, platform));
66
+ export const captureOpenCodeSessionId = async (cwd, knownSessionIds, onCapture, timeoutMs = 5000, intervalMs = 100, dbPath = getDefaultOpenCodeDbPath(), platform = process.platform) => {
59
67
  await captureSessionIdWithCoordinator({
60
68
  intervalMs,
61
69
  knownSessionIds,
62
- listSessionIds: () => listSessionIds(cwd, dbPath),
70
+ listSessionIds: () => listSessionIds(cwd, dbPath, platform),
63
71
  onCapture,
64
72
  projectKey: `${dbPath}:${cwd}`,
65
73
  timeoutMs,
@@ -0,0 +1,5 @@
1
+ export declare const getQwenHome: (pattern?: string, platform?: NodeJS.Platform) => string;
2
+ export declare const hasQwenSession: (cwd: string, sessionId: string, pattern?: string, platform?: NodeJS.Platform, qwenHome?: string) => boolean;
3
+ export declare const snapshotQwenSessionIds: (cwd: string, qwenHome?: string, platform?: NodeJS.Platform) => Set<string>;
4
+ export declare const captureQwenSessionId: (cwd: string, knownSessionIds: Set<string>, onCapture: (sessionId: string) => void, timeoutMs?: number, intervalMs?: number, qwenHome?: string) => Promise<void>;
5
+ export declare const qwenSessionStoreExists: (qwenHome?: string) => boolean;
@@ -0,0 +1,104 @@
1
+ import { closeSync, existsSync, openSync, readdirSync, readSync } from 'node:fs';
2
+ import { homedir } from 'node:os';
3
+ import { join } from 'node:path';
4
+ import { captureSessionIdWithCoordinator } from './claude-session-coordinator.js';
5
+ import { arePathsEqual, expandHomePath, indexOfPathMarker } from './platform-path.js';
6
+ const QWEN_SESSION_FILE = /\.json$/i;
7
+ const QWEN_SESSIONS_MARKER = '/sessions/';
8
+ const QWEN_HEADER_READ_CHUNK_BYTES = 4096;
9
+ const QWEN_HEADER_MAX_BYTES = 256 * 1024;
10
+ const getDefaultQwenHome = () => process.env.HIVE_QWEN_HOME ?? join(homedir(), '.qwen');
11
+ export const getQwenHome = (pattern, platform = process.platform) => {
12
+ if (!pattern)
13
+ return getDefaultQwenHome();
14
+ const markerIndex = indexOfPathMarker(pattern, QWEN_SESSIONS_MARKER, platform);
15
+ if (markerIndex === -1)
16
+ return getDefaultQwenHome();
17
+ const rawRoot = pattern.slice(0, markerIndex).replace(/[\\/]+$/u, '');
18
+ const root = expandHomePath(rawRoot);
19
+ if (arePathsEqual(root, join(homedir(), '.qwen'), platform))
20
+ return getDefaultQwenHome();
21
+ return root || getDefaultQwenHome();
22
+ };
23
+ const walkSessionFiles = (dir) => {
24
+ try {
25
+ return readdirSync(dir, { withFileTypes: true }).flatMap((entry) => {
26
+ const path = join(dir, entry.name);
27
+ if (entry.isDirectory())
28
+ return walkSessionFiles(path);
29
+ return entry.isFile() && QWEN_SESSION_FILE.test(entry.name) ? [path] : [];
30
+ });
31
+ }
32
+ catch {
33
+ return [];
34
+ }
35
+ };
36
+ const readQwenSessionHeader = (filePath, maxBytes = QWEN_HEADER_MAX_BYTES) => {
37
+ const fd = openSync(filePath, 'r');
38
+ try {
39
+ const chunks = [];
40
+ let totalBytes = 0;
41
+ let position = 0;
42
+ while (totalBytes < maxBytes) {
43
+ const bytesToRead = Math.min(QWEN_HEADER_READ_CHUNK_BYTES, maxBytes - totalBytes);
44
+ const buffer = Buffer.allocUnsafe(bytesToRead);
45
+ const bytesRead = readSync(fd, buffer, 0, bytesToRead, position);
46
+ if (bytesRead === 0)
47
+ break;
48
+ const slice = buffer.subarray(0, bytesRead);
49
+ chunks.push(slice);
50
+ totalBytes += bytesRead;
51
+ position += bytesRead;
52
+ }
53
+ return Buffer.concat(chunks).toString('utf8');
54
+ }
55
+ finally {
56
+ closeSync(fd);
57
+ }
58
+ };
59
+ const readJsonStringField = (input, key) => {
60
+ const regex = new RegExp(`"${key}"\\s*:\\s*("(?:\\\\.|[^"\\\\])*")`, 'u');
61
+ const raw = regex.exec(input)?.[1];
62
+ if (!raw)
63
+ return null;
64
+ try {
65
+ const parsed = JSON.parse(raw);
66
+ return typeof parsed === 'string' ? parsed : null;
67
+ }
68
+ catch {
69
+ return null;
70
+ }
71
+ };
72
+ const parseQwenSession = (filePath) => {
73
+ const header = readQwenSessionHeader(filePath);
74
+ const id = readJsonStringField(header, 'sessionId');
75
+ const cwd = readJsonStringField(header, 'projectRoot');
76
+ return id && cwd ? { cwd, id } : null;
77
+ };
78
+ const listSessionIds = (cwd, qwenHome = getDefaultQwenHome(), platform = process.platform) => {
79
+ const sessionsRoot = join(qwenHome, 'sessions');
80
+ return walkSessionFiles(sessionsRoot)
81
+ .flatMap((filePath) => {
82
+ try {
83
+ const session = parseQwenSession(filePath);
84
+ return session && arePathsEqual(session.cwd, cwd, platform) ? [session.id] : [];
85
+ }
86
+ catch {
87
+ return [];
88
+ }
89
+ })
90
+ .sort((left, right) => left.localeCompare(right));
91
+ };
92
+ export const hasQwenSession = (cwd, sessionId, pattern, platform = process.platform, qwenHome = getQwenHome(pattern, platform)) => listSessionIds(cwd, qwenHome, platform).includes(sessionId);
93
+ export const snapshotQwenSessionIds = (cwd, qwenHome = getDefaultQwenHome(), platform = process.platform) => new Set(listSessionIds(cwd, qwenHome, platform));
94
+ export const captureQwenSessionId = async (cwd, knownSessionIds, onCapture, timeoutMs = 5000, intervalMs = 100, qwenHome = getDefaultQwenHome()) => {
95
+ await captureSessionIdWithCoordinator({
96
+ intervalMs,
97
+ knownSessionIds,
98
+ listSessionIds: () => listSessionIds(cwd, qwenHome),
99
+ onCapture,
100
+ projectKey: join(qwenHome, 'sessions', cwd),
101
+ timeoutMs,
102
+ });
103
+ };
104
+ export const qwenSessionStoreExists = (qwenHome = getDefaultQwenHome()) => existsSync(join(qwenHome, 'sessions'));
@@ -10,6 +10,9 @@ export type SessionIdCaptureConfig = {
10
10
  } | {
11
11
  source: 'opencode_session_db';
12
12
  pattern: string;
13
+ } | {
14
+ source: 'qwen_session_json_dir';
15
+ pattern: string;
13
16
  } | {
14
17
  source: 'stdout_regex';
15
18
  pattern: string;
@@ -36,6 +39,7 @@ export declare const snapshotSessionIdsForCapture: (cwd: string, capture: Sessio
36
39
  CODEX_HOME?: never;
37
40
  HIVE_GEMINI_HOME?: never;
38
41
  HIVE_OPENCODE_DB_PATH?: never;
42
+ HIVE_QWEN_HOME?: never;
39
43
  };
40
44
  knownSessionIds: Set<string>;
41
45
  root: string;
@@ -45,6 +49,7 @@ export declare const snapshotSessionIdsForCapture: (cwd: string, capture: Sessio
45
49
  HIVE_CLAUDE_PROJECTS_DIR?: never;
46
50
  HIVE_GEMINI_HOME?: never;
47
51
  HIVE_OPENCODE_DB_PATH?: never;
52
+ HIVE_QWEN_HOME?: never;
48
53
  };
49
54
  knownSessionIds: Set<string>;
50
55
  root: string;
@@ -54,6 +59,7 @@ export declare const snapshotSessionIdsForCapture: (cwd: string, capture: Sessio
54
59
  HIVE_CLAUDE_PROJECTS_DIR?: never;
55
60
  CODEX_HOME?: never;
56
61
  HIVE_OPENCODE_DB_PATH?: never;
62
+ HIVE_QWEN_HOME?: never;
57
63
  };
58
64
  knownSessionIds: Set<string>;
59
65
  root: string;
@@ -63,6 +69,17 @@ export declare const snapshotSessionIdsForCapture: (cwd: string, capture: Sessio
63
69
  HIVE_CLAUDE_PROJECTS_DIR?: never;
64
70
  CODEX_HOME?: never;
65
71
  HIVE_GEMINI_HOME?: never;
72
+ HIVE_QWEN_HOME?: never;
73
+ };
74
+ knownSessionIds: Set<string>;
75
+ root: string;
76
+ } | {
77
+ env: {
78
+ HIVE_QWEN_HOME: string;
79
+ HIVE_CLAUDE_PROJECTS_DIR?: never;
80
+ CODEX_HOME?: never;
81
+ HIVE_GEMINI_HOME?: never;
82
+ HIVE_OPENCODE_DB_PATH?: never;
66
83
  };
67
84
  knownSessionIds: Set<string>;
68
85
  root: string;
@@ -2,6 +2,7 @@ import { captureClaudeSessionId, getClaudeProjectsRoot, hasClaudeSessionFile, sn
2
2
  import { captureCodexSessionId, getCodexHome, hasCodexSession, snapshotCodexSessionIds, } from './session-capture-codex.js';
3
3
  import { captureGeminiSessionId, getGeminiHome, hasGeminiSession, snapshotGeminiSessionIds, } from './session-capture-gemini.js';
4
4
  import { captureOpenCodeSessionId, getOpenCodeDbPath, hasOpenCodeSession, snapshotOpenCodeSessionIds, } from './session-capture-opencode.js';
5
+ import { captureQwenSessionId, getQwenHome, hasQwenSession, snapshotQwenSessionIds, } from './session-capture-qwen.js';
5
6
  const hasSource = (value) => Boolean(value && typeof value === 'object' && 'source' in value);
6
7
  export const parseSessionIdCapture = (value) => {
7
8
  if (!hasSource(value))
@@ -13,6 +14,7 @@ export const parseSessionIdCapture = (value) => {
13
14
  value.source === 'codex_session_jsonl_dir' ||
14
15
  value.source === 'gemini_session_json_dir' ||
15
16
  value.source === 'opencode_session_db' ||
17
+ value.source === 'qwen_session_json_dir' ||
16
18
  value.source === 'stdout_regex') {
17
19
  return typeof pattern === 'string' ? { pattern, source: value.source } : null;
18
20
  }
@@ -57,6 +59,14 @@ export const snapshotSessionIdsForCapture = (cwd, capture, discriminator) => {
57
59
  root: dbPath,
58
60
  };
59
61
  }
62
+ if (capture.source === 'qwen_session_json_dir') {
63
+ const qwenHome = getQwenHome(capture.pattern);
64
+ return {
65
+ env: { HIVE_QWEN_HOME: qwenHome },
66
+ knownSessionIds: snapshotQwenSessionIds(cwd, qwenHome),
67
+ root: qwenHome,
68
+ };
69
+ }
60
70
  if (capture.source === 'stdout_regex') {
61
71
  return {
62
72
  knownSessionIds: new Set(),
@@ -78,6 +88,9 @@ export const captureSessionIdForCapture = async (cwd, capture, snapshot, onCaptu
78
88
  if (capture.source === 'opencode_session_db') {
79
89
  await captureOpenCodeSessionId(cwd, snapshot.knownSessionIds, onCapture, timeoutMs, intervalMs, snapshot.root);
80
90
  }
91
+ if (capture.source === 'qwen_session_json_dir') {
92
+ await captureQwenSessionId(cwd, snapshot.knownSessionIds, onCapture, timeoutMs, intervalMs, snapshot.root);
93
+ }
81
94
  if (capture.source === 'stdout_regex') {
82
95
  await captureStdoutRegexSessionId(capture.pattern, snapshot.getOutput, snapshot.knownSessionIds, onCapture, timeoutMs, intervalMs);
83
96
  }
@@ -95,6 +108,9 @@ export const doesCapturedSessionExist = (cwd, capture, sessionId, discriminator)
95
108
  if (capture.source === 'opencode_session_db') {
96
109
  return hasOpenCodeSession(cwd, sessionId, capture.pattern);
97
110
  }
111
+ if (capture.source === 'qwen_session_json_dir') {
112
+ return hasQwenSession(cwd, sessionId, capture.pattern);
113
+ }
98
114
  return false;
99
115
  };
100
116
  const compileCaptureRegex = (pattern) => {
@@ -0,0 +1,2 @@
1
+ import type { Database } from 'better-sqlite3';
2
+ export declare const applySchemaVersion23: (db: Database) => void;
@@ -0,0 +1,43 @@
1
+ // v23 — remote-access (mobile) audit trail. The tunnel/bridge layer is the
2
+ // SINGLE collection point for every remote HTTP request + WS-input event
3
+ // (see the plan's 审计规格): one place to record so no per-route handler has
4
+ // to know about remote at all. Rows are append-only and written async off the
5
+ // forwarding path (remote-audit-store.ts), so a slow disk never stalls a frame.
6
+ //
7
+ // Schema notes:
8
+ // - remote_device_id is the M1 session's device id; it's an audit/revocation
9
+ // tag, NEVER a permission branch (Authority Model: paired device == local).
10
+ // - action is a coarse category, not a full URL ('http' for bridged API
11
+ // requests, 'ws_input' for terminal/tasks stdin, plus lifecycle/control
12
+ // actions like 'session_open' / 'revoke' / 'reject'). endpoint holds the
13
+ // whitelisted path ('/api/...' '/ws/...') when there is one.
14
+ // - result is 'ok' | 'rejected' | 'error'. reject_reason is set ONLY on a
15
+ // rejection (off-whitelist path, revoked device, forged secret, …) so the
16
+ // security tests can assert the reason text, not just that a row exists.
17
+ // - WS input is summarised: byte_count + a short truncated preview. We NEVER
18
+ // persist full stdin (that's the user typing into a YOLO terminal). The
19
+ // preview is bounded by the store before it reaches here.
20
+ //
21
+ // Index covers the Settings audit-stream view: newest-first, optionally scoped
22
+ // to one device.
23
+ export const applySchemaVersion23 = (db) => {
24
+ db.exec(`
25
+ CREATE TABLE IF NOT EXISTS remote_audit (
26
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
27
+ remote_device_id TEXT,
28
+ ts INTEGER NOT NULL,
29
+ workspace_id TEXT,
30
+ action TEXT NOT NULL,
31
+ endpoint TEXT,
32
+ result TEXT NOT NULL,
33
+ reject_reason TEXT,
34
+ byte_count INTEGER,
35
+ preview TEXT
36
+ );
37
+
38
+ CREATE INDEX IF NOT EXISTS idx_remote_audit_recent
39
+ ON remote_audit (id DESC);
40
+ CREATE INDEX IF NOT EXISTS idx_remote_audit_device
41
+ ON remote_audit (remote_device_id, id DESC);
42
+ `);
43
+ };
@@ -0,0 +1,2 @@
1
+ import type { Database } from 'better-sqlite3';
2
+ export declare const applySchemaVersion24: (db: Database) => void;
@@ -0,0 +1,34 @@
1
+ // v24 — paired remote devices. A row exists ONLY after a human confirmed the pairing at the desktop
2
+ // (Authority Model trust root): the pending handshake lives in memory in remote-pairing.ts and is
3
+ // INSERTed here on confirm, NEVER before.
4
+ //
5
+ // key_d2p/key_p2d are the M3 DeviceSession.keys — M6.1 reinterprets these as the directional ROOT keys:
6
+ // the bridge derives a fresh per-connection AEAD key from them on every connect (deriveConnectionKeys)
7
+ // and never seals/opens under the stored bytes directly. The at-rest format is byte-identical — no
8
+ // schema change, no version bump; v24 rows are forward-compatible (their stored material IS the root).
9
+ // They are a stored secret in the sense that they decrypt all of that device's E2E traffic, but the
10
+ // protection here is
11
+ // exactly the same filesystem-local posture as remote_daemon_token (plaintext in app_state, see
12
+ // remote-config-keys.ts): the runtime.sqlite file lives next to the daemon on 127.0.0.1. This is NOT
13
+ // encryption-at-rest. If at-rest encryption is later wanted it should cover daemon_token + these keys
14
+ // together as a single hardening item, not be implied here.
15
+ //
16
+ // revoked_at != NULL = dead device: the persistent provider returns null for it at once (live streams
17
+ // drop on the next frame). Rows are kept (soft tombstone) so the device list / audit can still show a
18
+ // revoked device.
19
+ export const applySchemaVersion24 = (db) => {
20
+ db.exec(`
21
+ CREATE TABLE IF NOT EXISTS remote_devices (
22
+ id TEXT PRIMARY KEY,
23
+ name TEXT NOT NULL,
24
+ key_d2p TEXT NOT NULL, -- base64url(32) daemon->phone (daemon SEALS)
25
+ key_p2d TEXT NOT NULL, -- base64url(32) phone->daemon (daemon OPENS)
26
+ device_pubkey TEXT NOT NULL, -- base64url(32); audit/debug, not secret
27
+ created_at INTEGER NOT NULL,
28
+ last_active INTEGER,
29
+ revoked_at INTEGER
30
+ );
31
+ CREATE INDEX IF NOT EXISTS idx_remote_devices_active
32
+ ON remote_devices (revoked_at, created_at DESC);
33
+ `);
34
+ };
@@ -0,0 +1,2 @@
1
+ import type { Database } from 'better-sqlite3';
2
+ export declare const applySchemaVersion25: (db: Database) => void;