@tt-a1i/hive 1.4.3 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (180) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/README.en.md +5 -4
  3. package/README.md +9 -1
  4. package/assets/qq-group.jpg +0 -0
  5. package/dist/bin/team.cmd +1 -0
  6. package/dist/src/cli/hive-update.d.ts +57 -0
  7. package/dist/src/cli/hive-update.js +92 -15
  8. package/dist/src/cli/hive.d.ts +57 -0
  9. package/dist/src/cli/hive.js +113 -20
  10. package/dist/src/cli/team.d.ts +1 -0
  11. package/dist/src/cli/team.js +215 -7
  12. package/dist/src/server/agent-command-resolver.d.ts +10 -1
  13. package/dist/src/server/agent-command-resolver.js +32 -4
  14. package/dist/src/server/agent-launch-resolver.js +9 -3
  15. package/dist/src/server/agent-manager-support.d.ts +28 -0
  16. package/dist/src/server/agent-manager-support.js +138 -10
  17. package/dist/src/server/agent-run-bootstrap.d.ts +17 -1
  18. package/dist/src/server/agent-run-bootstrap.js +30 -2
  19. package/dist/src/server/agent-run-starter.d.ts +7 -1
  20. package/dist/src/server/agent-run-starter.js +9 -2
  21. package/dist/src/server/agent-run-store.d.ts +1 -1
  22. package/dist/src/server/agent-runtime-close.d.ts +1 -0
  23. package/dist/src/server/agent-runtime-close.js +25 -1
  24. package/dist/src/server/agent-runtime-contract.d.ts +2 -1
  25. package/dist/src/server/agent-runtime.d.ts +1 -1
  26. package/dist/src/server/agent-runtime.js +8 -2
  27. package/dist/src/server/agent-startup-instructions.d.ts +8 -1
  28. package/dist/src/server/agent-startup-instructions.js +15 -9
  29. package/dist/src/server/agent-stdin-dispatcher.d.ts +12 -5
  30. package/dist/src/server/agent-stdin-dispatcher.js +129 -40
  31. package/dist/src/server/app.d.ts +1 -0
  32. package/dist/src/server/app.js +12 -2
  33. package/dist/src/server/cron-util.d.ts +7 -0
  34. package/dist/src/server/cron-util.js +19 -0
  35. package/dist/src/server/dispatch-ledger-store.d.ts +22 -0
  36. package/dist/src/server/dispatch-ledger-store.js +51 -3
  37. package/dist/src/server/env-sync-message.js +9 -9
  38. package/dist/src/server/fs-browse.d.ts +14 -1
  39. package/dist/src/server/fs-browse.js +48 -5
  40. package/dist/src/server/fs-pick-folder.js +58 -11
  41. package/dist/src/server/fs-sandbox.js +36 -7
  42. package/dist/src/server/hive-team-guidance.d.ts +11 -6
  43. package/dist/src/server/hive-team-guidance.js +252 -70
  44. package/dist/src/server/live-run-registry.d.ts +1 -0
  45. package/dist/src/server/live-run-registry.js +1 -1
  46. package/dist/src/server/open-target-commands.js +29 -4
  47. package/dist/src/server/orchestrator-autostart.d.ts +12 -0
  48. package/dist/src/server/orchestrator-autostart.js +15 -13
  49. package/dist/src/server/path-canonicalization.d.ts +3 -0
  50. package/dist/src/server/path-canonicalization.js +29 -0
  51. package/dist/src/server/platform-path.d.ts +3 -0
  52. package/dist/src/server/platform-path.js +13 -0
  53. package/dist/src/server/post-start-input-writer.d.ts +1 -1
  54. package/dist/src/server/post-start-input-writer.js +116 -16
  55. package/dist/src/server/preset-launch-support.d.ts +1 -1
  56. package/dist/src/server/preset-launch-support.js +33 -2
  57. package/dist/src/server/recovery-summary.d.ts +6 -1
  58. package/dist/src/server/recovery-summary.js +17 -17
  59. package/dist/src/server/restart-policy-support.d.ts +6 -1
  60. package/dist/src/server/restart-policy-support.js +9 -1
  61. package/dist/src/server/restart-policy.d.ts +2 -2
  62. package/dist/src/server/restart-policy.js +3 -1
  63. package/dist/src/server/role-template-store.d.ts +1 -0
  64. package/dist/src/server/role-template-store.js +11 -1
  65. package/dist/src/server/route-types.d.ts +43 -0
  66. package/dist/src/server/routes-runtime.js +2 -1
  67. package/dist/src/server/routes-settings.js +76 -0
  68. package/dist/src/server/routes-team.js +221 -2
  69. package/dist/src/server/routes-workflow-schedules.d.ts +2 -0
  70. package/dist/src/server/routes-workflow-schedules.js +58 -0
  71. package/dist/src/server/routes-workflows.d.ts +2 -0
  72. package/dist/src/server/routes-workflows.js +83 -0
  73. package/dist/src/server/routes.js +4 -0
  74. package/dist/src/server/runtime-restart-policy.d.ts +3 -1
  75. package/dist/src/server/runtime-restart-policy.js +3 -1
  76. package/dist/src/server/runtime-store-contract.d.ts +122 -0
  77. package/dist/src/server/runtime-store-contract.js +1 -0
  78. package/dist/src/server/runtime-store-helpers.d.ts +9 -0
  79. package/dist/src/server/runtime-store-helpers.js +101 -2
  80. package/dist/src/server/runtime-store-workflows.d.ts +6 -0
  81. package/dist/src/server/runtime-store-workflows.js +100 -0
  82. package/dist/src/server/runtime-store.d.ts +3 -70
  83. package/dist/src/server/runtime-store.js +70 -4
  84. package/dist/src/server/session-capture-claude.d.ts +23 -0
  85. package/dist/src/server/session-capture-claude.js +24 -1
  86. package/dist/src/server/session-capture-codex.d.ts +3 -3
  87. package/dist/src/server/session-capture-codex.js +9 -7
  88. package/dist/src/server/session-capture-gemini.d.ts +1 -1
  89. package/dist/src/server/session-capture-gemini.js +6 -3
  90. package/dist/src/server/session-capture-opencode.d.ts +18 -0
  91. package/dist/src/server/session-capture-opencode.js +27 -2
  92. package/dist/src/server/settings-store.d.ts +3 -0
  93. package/dist/src/server/settings-store.js +1 -0
  94. package/dist/src/server/sqlite-schema-v19.d.ts +2 -0
  95. package/dist/src/server/sqlite-schema-v19.js +17 -0
  96. package/dist/src/server/sqlite-schema-v20.d.ts +2 -0
  97. package/dist/src/server/sqlite-schema-v20.js +20 -0
  98. package/dist/src/server/sqlite-schema-v21.d.ts +2 -0
  99. package/dist/src/server/sqlite-schema-v21.js +20 -0
  100. package/dist/src/server/sqlite-schema.d.ts +1 -1
  101. package/dist/src/server/sqlite-schema.js +97 -1
  102. package/dist/src/server/startup-command-parser.d.ts +15 -0
  103. package/dist/src/server/startup-command-parser.js +33 -2
  104. package/dist/src/server/system-message.d.ts +7 -0
  105. package/dist/src/server/system-message.js +8 -1
  106. package/dist/src/server/tasks-file-watcher.d.ts +39 -1
  107. package/dist/src/server/tasks-file-watcher.js +155 -25
  108. package/dist/src/server/tasks-file.d.ts +2 -1
  109. package/dist/src/server/tasks-file.js +32 -9
  110. package/dist/src/server/tasks-websocket-server.js +13 -14
  111. package/dist/src/server/team-authz.d.ts +1 -1
  112. package/dist/src/server/team-authz.js +9 -1
  113. package/dist/src/server/team-autostaff.d.ts +16 -0
  114. package/dist/src/server/team-autostaff.js +16 -0
  115. package/dist/src/server/team-list-serializer.d.ts +1 -1
  116. package/dist/src/server/team-list-serializer.js +3 -1
  117. package/dist/src/server/team-operations.d.ts +20 -2
  118. package/dist/src/server/team-operations.js +160 -14
  119. package/dist/src/server/terminal-input-profile.js +2 -8
  120. package/dist/src/server/terminal-protocol.js +9 -3
  121. package/dist/src/server/terminal-stream-hub.js +16 -10
  122. package/dist/src/server/terminal-ws-server.js +36 -16
  123. package/dist/src/server/websocket-upgrade-safety.d.ts +10 -0
  124. package/dist/src/server/websocket-upgrade-safety.js +35 -0
  125. package/dist/src/server/windows-command-line.d.ts +3 -0
  126. package/dist/src/server/windows-command-line.js +9 -0
  127. package/dist/src/server/windows-filename.d.ts +2 -0
  128. package/dist/src/server/windows-filename.js +33 -0
  129. package/dist/src/server/workflow-cli-policy.d.ts +60 -0
  130. package/dist/src/server/workflow-cli-policy.js +110 -0
  131. package/dist/src/server/workflow-dispatch-awaiter.d.ts +12 -0
  132. package/dist/src/server/workflow-dispatch-awaiter.js +80 -0
  133. package/dist/src/server/workflow-feature.d.ts +15 -0
  134. package/dist/src/server/workflow-feature.js +15 -0
  135. package/dist/src/server/workflow-http-serializers.d.ts +64 -0
  136. package/dist/src/server/workflow-http-serializers.js +58 -0
  137. package/dist/src/server/workflow-run-log-store.d.ts +19 -0
  138. package/dist/src/server/workflow-run-log-store.js +45 -0
  139. package/dist/src/server/workflow-run-store.d.ts +50 -0
  140. package/dist/src/server/workflow-run-store.js +103 -0
  141. package/dist/src/server/workflow-runner.d.ts +147 -0
  142. package/dist/src/server/workflow-runner.js +401 -0
  143. package/dist/src/server/workflow-schedule-create.d.ts +14 -0
  144. package/dist/src/server/workflow-schedule-create.js +41 -0
  145. package/dist/src/server/workflow-schedule-store.d.ts +43 -0
  146. package/dist/src/server/workflow-schedule-store.js +112 -0
  147. package/dist/src/server/workflow-scheduler.d.ts +36 -0
  148. package/dist/src/server/workflow-scheduler.js +97 -0
  149. package/dist/src/server/workflow-script-loader.d.ts +34 -0
  150. package/dist/src/server/workflow-script-loader.js +106 -0
  151. package/dist/src/server/workspace-path-validation.js +16 -4
  152. package/dist/src/server/workspace-shell-runtime.d.ts +5 -0
  153. package/dist/src/server/workspace-shell-runtime.js +24 -2
  154. package/dist/src/server/workspace-store-contract.d.ts +4 -1
  155. package/dist/src/server/workspace-store-hydration.js +23 -7
  156. package/dist/src/server/workspace-store-mutations.js +2 -5
  157. package/dist/src/server/workspace-store-support.d.ts +4 -0
  158. package/dist/src/server/workspace-store-support.js +13 -1
  159. package/dist/src/server/workspace-store.js +38 -4
  160. package/dist/src/shared/types.d.ts +16 -1
  161. package/package.json +4 -2
  162. package/web/dist/assets/{AddWorkerDialog-DmkDOdp6.js → AddWorkerDialog-CcC-7kgG.js} +2 -2
  163. package/web/dist/assets/AddWorkspaceDialog-BDpOTfmt.js +1 -0
  164. package/web/dist/assets/{FirstRunWizard-SAd1wsH4.js → FirstRunWizard-BYX_ocQn.js} +1 -1
  165. package/web/dist/assets/{MarketplaceDrawer-B_8aG2uT.js → MarketplaceDrawer-DUxSk7db.js} +1 -1
  166. package/web/dist/assets/WhatsNewDialog-B_RlCXcV.js +1 -0
  167. package/web/dist/assets/WorkerModal-D9-7YfZZ.js +1 -0
  168. package/web/dist/assets/WorkspaceTaskDrawer-BCKoF7qc.js +1 -0
  169. package/web/dist/assets/{WorkspaceTerminalPanels-BReWh1YL.js → WorkspaceTerminalPanels-Dq8y91t2.js} +1 -1
  170. package/web/dist/assets/index-BiOvKIVw.css +1 -0
  171. package/web/dist/assets/index-DMRUklT3.js +73 -0
  172. package/web/dist/assets/path-join-7MR1s7b1.js +1 -0
  173. package/web/dist/index.html +2 -2
  174. package/web/dist/sw.js +1 -1
  175. package/web/dist/assets/AddWorkspaceDialog-BsVnH3Xe.js +0 -1
  176. package/web/dist/assets/WorkerModal-CQmjiPme.js +0 -1
  177. package/web/dist/assets/WorkspaceTaskDrawer-B0DmCWcV.js +0 -1
  178. package/web/dist/assets/chevron-right-CtLjVEl7.js +0 -1
  179. package/web/dist/assets/index-BEsTmfrO.css +0 -1
  180. package/web/dist/assets/index-Cn8X3get.js +0 -76
@@ -9,38 +9,61 @@ import { buildRuntimeRestartPolicy } from './runtime-restart-policy.js';
9
9
  import { createSettingsStore } from './settings-store.js';
10
10
  import { createTasksFileService } from './tasks-file.js';
11
11
  import { createTasksFileWatcher } from './tasks-file-watcher.js';
12
+ import { AUTOSTAFF_ENABLED_KEY, readAutostaffEnabled } from './team-autostaff.js';
12
13
  import { createTeamOperations } from './team-operations.js';
13
14
  import { resolveTerminalInputProfile } from './terminal-input-profile.js';
14
15
  import { createUiAuth } from './ui-auth.js';
15
16
  import { createWorkerOutputTracker } from './worker-output-tracker.js';
17
+ import { readWorkflowCliPolicy, WORKFLOW_CLI_POLICY_KEY } from './workflow-cli-policy.js';
18
+ import { createWorkflowDispatchAwaiter, } from './workflow-dispatch-awaiter.js';
19
+ import { readWorkflowEnabled, WORKFLOW_ENABLED_KEY } from './workflow-feature.js';
20
+ import { createWorkflowRunLogStore } from './workflow-run-log-store.js';
21
+ import { createWorkflowRunStore } from './workflow-run-store.js';
22
+ import { createWorkflowScheduleStore } from './workflow-schedule-store.js';
16
23
  import { createWorkspaceShellRuntime } from './workspace-shell-runtime.js';
17
24
  import { createWorkspaceStore } from './workspace-store.js';
25
+ import { getOrchestratorId } from './workspace-store-support.js';
18
26
  const notifyTasksUpdated = (callbacks, workspaceId, content) => {
19
27
  for (const callback of callbacks) {
20
28
  callback(workspaceId, content);
21
29
  }
22
30
  };
31
+ export const logTasksFileWatchStartError = (workspaceId, error) => {
32
+ console.error(`[hive] failed to start tasks watcher for workspace ${workspaceId}`, error);
33
+ };
23
34
  export const createRuntimeStoreServices = (options = {}) => {
24
35
  const db = openRuntimeDatabase(options.dataDir);
25
36
  const messageLogStore = createMessageLogStore(db);
26
37
  const dispatchLedgerStore = createDispatchLedgerStore(db);
38
+ const workflowDispatchAwaiter = createWorkflowDispatchAwaiter();
39
+ const workflowRunStore = createWorkflowRunStore(db);
40
+ const workflowRunLogStore = createWorkflowRunLogStore(db);
41
+ const workflowScheduleStore = createWorkflowScheduleStore(db);
27
42
  const agentRunStore = createAgentRunStore(db);
28
43
  const agentSessionStore = createAgentSessionStore(db);
29
44
  const settings = createSettingsStore(db);
45
+ const getWorkflowsEnabled = () => readWorkflowEnabled(settings.getAppState(WORKFLOW_ENABLED_KEY)?.value ?? null);
46
+ const getAutostaffEnabled = () => readAutostaffEnabled(settings.getAppState(AUTOSTAFF_ENABLED_KEY)?.value ?? null);
30
47
  const tasksFileService = createTasksFileService();
31
48
  const tasksFileWatchCallbacks = new Set();
32
49
  const tasksFileWatcher = createTasksFileWatcher({
33
50
  onTasksUpdated: (workspaceId, content) => {
34
51
  notifyTasksUpdated(tasksFileWatchCallbacks, workspaceId, content);
35
52
  },
53
+ getWorkflowCliPolicy: () => readWorkflowCliPolicy(settings.getAppState(WORKFLOW_CLI_POLICY_KEY)?.value ?? null),
54
+ getWorkflowsEnabled,
55
+ getAutostaffEnabled,
36
56
  });
37
57
  const uiAuth = createUiAuth();
38
58
  const shellRuntime = createWorkspaceShellRuntime(options.agentManager);
39
59
  agentRunStore.markUnfinishedRunsStale();
60
+ workflowRunStore.markUnfinishedRunsInterrupted();
40
61
  const workspaceStore = createWorkspaceStore(db, dispatchLedgerStore.listOpenDispatchKinds());
41
62
  const startExistingWorkspaceWatches = () => {
42
63
  for (const workspace of workspaceStore.listWorkspaces()) {
43
- void tasksFileWatcher.start(workspace.id, workspace.path);
64
+ void tasksFileWatcher
65
+ .start(workspace.id, workspace.path)
66
+ .catch((error) => logTasksFileWatchStartError(workspace.id, error));
44
67
  }
45
68
  };
46
69
  const restartPolicy = buildRuntimeRestartPolicy({
@@ -48,6 +71,8 @@ export const createRuntimeStoreServices = (options = {}) => {
48
71
  messageLogStore,
49
72
  tasksFileService,
50
73
  workspaceStore,
74
+ getWorkflowsEnabled,
75
+ getAutostaffEnabled,
51
76
  });
52
77
  const workerOutputTracker = options.agentManager
53
78
  ? createWorkerOutputTracker(options.agentManager.getOutputBus())
@@ -57,7 +82,70 @@ export const createRuntimeStoreServices = (options = {}) => {
57
82
  if (!workspaceStore.hasAgent(workspaceId, agentId))
58
83
  return;
59
84
  workspaceStore.markAgentStopped(workspaceId, agentId);
60
- }, restartPolicy, (workspaceId, agentId) => workspaceStore.getAgent(workspaceId, agentId));
85
+ // TIER 1 #1 if the exiting worker had any open workflow dispatches,
86
+ // tell the workflow awaiter so the runner's `awaitReport` rejects
87
+ // immediately instead of hanging until DEFAULT_TIMEOUT_MS (10 min).
88
+ // This is the only signal the runtime has that an ephemeral
89
+ // workflow worker died without calling `team report`.
90
+ const openWorkflowDispatches = dispatchLedgerStore.listOpenWorkflowDispatchesForWorker(workspaceId, agentId);
91
+ for (const { dispatchId } of openWorkflowDispatches) {
92
+ try {
93
+ const cancelled = dispatchLedgerStore.markCancelled({
94
+ dispatchId,
95
+ reason: 'worker PTY exited before report',
96
+ workspaceId,
97
+ });
98
+ if (!cancelled)
99
+ continue;
100
+ try {
101
+ workspaceStore.markTaskCancelled(workspaceId, agentId);
102
+ }
103
+ catch (error) {
104
+ console.error('[hive] onAgentExit.markTaskCancelled failed', dispatchId, error);
105
+ }
106
+ workflowDispatchAwaiter.notifyCancel(dispatchId, 'worker PTY exited before report');
107
+ }
108
+ catch (error) {
109
+ console.error('[hive] onAgentExit.markCancelled failed', dispatchId, error);
110
+ }
111
+ }
112
+ // Cascade: when the orchestrator's PTY exits, dismiss the ephemeral
113
+ // workers it spawned via `team spawn` (spec §6.3). Workflow-spawned
114
+ // workers are owned by the runner, not the orchestrator.
115
+ if (agentId === getOrchestratorId(workspaceId)) {
116
+ const children = workspaceStore
117
+ .getWorkspaceSnapshot(workspaceId)
118
+ .agents.filter((agent) => agent.ephemeral === true && agent.spawnedBy === 'orchestrator');
119
+ for (const child of children)
120
+ removeWorkerCompletely(workspaceId, child.id);
121
+ }
122
+ }, restartPolicy, (workspaceId, agentId) => workspaceStore.getAgent(workspaceId, agentId), getWorkflowsEnabled, getAutostaffEnabled);
123
+ // Mirrors runtime-store.deleteWorker (stop run → drop launch config → drop
124
+ // dispatches → drop worker row, transactionally). Hoisted `function` so the
125
+ // onAgentExit closure above can reference it; only invoked at runtime, after
126
+ // agentRuntime is assigned.
127
+ function removeWorkerCompletely(workspaceId, workerId) {
128
+ const activeRun = agentRuntime.getActiveRunByAgentId(workspaceId, workerId);
129
+ if (activeRun)
130
+ agentRuntime.stopAgentRun(activeRun.runId);
131
+ agentRuntime.deleteAgentLaunchConfig(workspaceId, workerId);
132
+ db.transaction(() => {
133
+ dispatchLedgerStore.deleteWorkerDispatches(workspaceId, workerId);
134
+ workspaceStore.deleteWorker(workspaceId, workerId);
135
+ })();
136
+ }
137
+ // Boot cleanup: after a runtime restart every ephemeral worker is an orphan
138
+ // (its spawner — a workflow run or an orchestrator PTY — is gone). Remove
139
+ // them so they don't accumulate (spec §6.3).
140
+ const cleanupOrphanEphemeralWorkers = () => {
141
+ for (const workspace of workspaceStore.listWorkspaces()) {
142
+ const ephemeral = workspaceStore
143
+ .getWorkspaceSnapshot(workspace.id)
144
+ .agents.filter((agent) => agent.ephemeral === true);
145
+ for (const agent of ephemeral)
146
+ removeWorkerCompletely(workspace.id, agent.id);
147
+ }
148
+ };
61
149
  const teamOps = createTeamOperations({
62
150
  agentRuntime,
63
151
  createDispatch: dispatchLedgerStore.createDispatch,
@@ -69,8 +157,11 @@ export const createRuntimeStoreServices = (options = {}) => {
69
157
  markDispatchCancelled: dispatchLedgerStore.markCancelled,
70
158
  markDispatchReportedByWorker: dispatchLedgerStore.markReportedByWorker,
71
159
  markDispatchSubmitted: dispatchLedgerStore.markSubmitted,
160
+ workflowDispatchAwaiter,
72
161
  workspaceStore,
162
+ dismissEphemeralWorker: (workspaceId, workerId) => removeWorkerCompletely(workspaceId, workerId),
73
163
  });
164
+ cleanupOrphanEphemeralWorkers();
74
165
  startExistingWorkspaceWatches();
75
166
  return {
76
167
  agentRunStore,
@@ -86,6 +177,10 @@ export const createRuntimeStoreServices = (options = {}) => {
86
177
  teamOps,
87
178
  uiAuth,
88
179
  workerOutputTracker,
180
+ workflowDispatchAwaiter,
181
+ workflowRunLogStore,
182
+ workflowRunStore,
183
+ workflowScheduleStore,
89
184
  workspaceStore,
90
185
  };
91
186
  };
@@ -143,6 +238,10 @@ export const createRuntimeStoreLifecycle = ({ agentManager, services, }) => {
143
238
  };
144
239
  return {
145
240
  close: async () => {
241
+ // Fail in-flight workflow awaiters BEFORE their workers vanish, so the
242
+ // runner's `await agent(...)` rejects with a clear shutdown error
243
+ // instead of hanging on a Promise that can never resolve.
244
+ services.workflowDispatchAwaiter.cancelAll('runtime closing');
146
245
  services.shellRuntime.close();
147
246
  await services.agentRuntime.close();
148
247
  await services.tasksFileWatcher.close();
@@ -0,0 +1,6 @@
1
+ import type { RuntimeStore } from './runtime-store-contract.js';
2
+ import type { RuntimeStoreServices } from './runtime-store-helpers.js';
3
+ export declare const createRuntimeStoreWorkflowRuntime: (services: RuntimeStoreServices, store: RuntimeStore) => {
4
+ runner: import("./workflow-runner.js").WorkflowRunner;
5
+ scheduler: import("./workflow-scheduler.js").WorkflowScheduler;
6
+ };
@@ -0,0 +1,100 @@
1
+ import { readWorkflowCliPolicy, WORKFLOW_CLI_POLICY_KEY } from './workflow-cli-policy.js';
2
+ import { readWorkflowEnabled, WORKFLOW_ENABLED_KEY } from './workflow-feature.js';
3
+ import { createWorkflowRunner } from './workflow-runner.js';
4
+ import { createWorkflowScheduler } from './workflow-scheduler.js';
5
+ export const createRuntimeStoreWorkflowRuntime = (services, store) => {
6
+ const runner = createWorkflowRunner({
7
+ awaiter: services.workflowDispatchAwaiter,
8
+ workflowRunStore: services.workflowRunStore,
9
+ dispatchPort: {
10
+ listOpenDispatchIdsForRun: (runId) => services.dispatchLedgerStore.listOpenDispatchIdsForRun(runId),
11
+ },
12
+ resolveWorkspacePath: (workspaceId) => services.workspaceStore.getWorkspaceSnapshot(workspaceId).summary.path,
13
+ logStore: {
14
+ append: (runId, message, ts) => services.workflowRunLogStore.append(runId, message, ts),
15
+ },
16
+ getWorkflowCliPolicy: () => readWorkflowCliPolicy(services.settings.getAppState(WORKFLOW_CLI_POLICY_KEY)?.value ?? null),
17
+ roleTemplateResolver: {
18
+ findByName: (name) => {
19
+ const t = services.settings.findRoleTemplateByName(name);
20
+ if (!t)
21
+ return undefined;
22
+ return {
23
+ name: t.name,
24
+ roleType: t.roleType,
25
+ defaultCommand: t.defaultCommand,
26
+ defaultArgs: t.defaultArgs,
27
+ };
28
+ },
29
+ },
30
+ onRunFinished: ({ runId, triggeredByAgentId, finalRecord }) => {
31
+ const workspaceId = finalRecord.workspaceId;
32
+ const dispatches = services.dispatchLedgerStore.listWorkflowRunDispatches(runId);
33
+ const errorLine = finalRecord.error ? `\nerror: ${finalRecord.error}` : '';
34
+ const dispatchSummary = dispatches.length === 0
35
+ ? '\n(no agent() calls in this run)'
36
+ : '\n' +
37
+ dispatches
38
+ .map((d) => {
39
+ const reply = d.reportText
40
+ ? d.reportText.length > 200
41
+ ? `${d.reportText.slice(0, 197).trim()}...`
42
+ : d.reportText.trim()
43
+ : `(no report; status=${d.status})`;
44
+ return ` #${d.stepIndex ?? '?'} -> ${reply}`;
45
+ })
46
+ .join('\n');
47
+ const resultBlock = (() => {
48
+ if (finalRecord.result === null || finalRecord.result === undefined)
49
+ return '';
50
+ const serialized = typeof finalRecord.result === 'string'
51
+ ? finalRecord.result
52
+ : JSON.stringify(finalRecord.result, null, 2);
53
+ const truncated = serialized.length > 4000
54
+ ? `${serialized.slice(0, 4000)}\n...(truncated; ${serialized.length - 4000} more chars)`
55
+ : serialized;
56
+ return `\nResult (workflow return value):\n${truncated}`;
57
+ })();
58
+ const logTail = services.workflowRunLogStore.tailForRun(runId, 8);
59
+ const logBlock = logTail.length === 0
60
+ ? ''
61
+ : `\nNarrator (last ${logTail.length} log line${logTail.length === 1 ? '' : 's'}):\n` +
62
+ logTail.map((line) => ` - ${line}`).join('\n');
63
+ const payload = '<hive-system-reminder>\n' +
64
+ `Hive workflow \`${finalRecord.name}\` finished: status=${finalRecord.status}` +
65
+ ` (run_id=${runId}, ${dispatches.length} agent call${dispatches.length === 1 ? '' : 's'}).` +
66
+ errorLine +
67
+ resultBlock +
68
+ logBlock +
69
+ dispatchSummary +
70
+ '\nThis result is what you should synthesize back to the user. ' +
71
+ 'Per-agent transcripts are available via `team workflow show ' +
72
+ runId +
73
+ '` if needed.\n' +
74
+ '</hive-system-reminder>\n';
75
+ try {
76
+ services.agentRuntime.writeSystemMessageToAgent(workspaceId, triggeredByAgentId, payload);
77
+ }
78
+ catch (error) {
79
+ console.error('[hive] workflow.notifyTrigger failed', error);
80
+ }
81
+ },
82
+ store: {
83
+ addWorkerWithLaunch: (ws, input, launch) => store.addWorkerWithLaunch(ws, input, launch),
84
+ deleteWorker: (ws, workerId) => store.deleteWorker(ws, workerId),
85
+ dispatchTaskByWorkerName: async (ws, name, text, input) => {
86
+ const dispatch = await store.dispatchTaskByWorkerName(ws, name, text, input);
87
+ return { id: dispatch.id };
88
+ },
89
+ startAgent: (ws, agentId, input) => store.startAgent(ws, agentId, input),
90
+ },
91
+ });
92
+ const scheduler = createWorkflowScheduler({
93
+ schedules: services.workflowScheduleStore,
94
+ startWorkflow: (input) => store.startWorkflow(input),
95
+ workspaceExists: (workspaceId) => services.workspaceStore.hasWorkspace(workspaceId),
96
+ isWorkflowEnabled: () => readWorkflowEnabled(services.settings.getAppState(WORKFLOW_ENABLED_KEY)?.value ?? null),
97
+ });
98
+ scheduler.start();
99
+ return { runner, scheduler };
100
+ };
@@ -1,71 +1,4 @@
1
- import type { AgentSummary, TeamListItem, WorkspaceSummary } from '../shared/types.js';
2
- import type { AgentManager } from './agent-manager.js';
3
- import type { AgentLaunchConfigInput, PersistedAgentRun } from './agent-run-store.js';
4
- import type { LiveAgentRun } from './agent-runtime-types.js';
5
- import type { DispatchRecord, ListDispatchesOptions } from './dispatch-ledger-store.js';
6
- import type { RecoveryMessage } from './message-log-store.js';
7
- import type { PtyOutputBus } from './pty-output-bus.js';
8
- import type { SettingsStore } from './settings-store.js';
9
- import type { CancelTaskInput, DispatchTaskInput, ReportTaskInput, ReportTaskResult, StatusTaskInput } from './team-operations.js';
10
- import type { TerminalRunSummary } from './terminal-input-profile.js';
11
- import type { WorkerInput, WorkspaceRecord } from './workspace-store.js';
12
- interface RuntimeStore {
13
- close: () => Promise<void>;
14
- createWorkspace: (path: string, name: string) => WorkspaceSummary;
15
- deleteWorkspace: (workspaceId: string) => Promise<void>;
16
- listWorkspaces: () => WorkspaceSummary[];
17
- addWorker: (workspaceId: string, input: WorkerInput) => AgentSummary;
18
- deleteWorker: (workspaceId: string, workerId: string) => void;
19
- renameWorker: (workspaceId: string, workerId: string, name: string) => AgentSummary;
20
- recordUserInput: (workspaceId: string, orchestratorId: string, text: string) => void;
21
- dispatchTask: (workspaceId: string, workerId: string, text: string, input?: DispatchTaskInput) => Promise<DispatchRecord>;
22
- dispatchTaskByWorkerName: (workspaceId: string, workerName: string, text: string, input?: DispatchTaskInput) => Promise<DispatchRecord>;
23
- reportTask: (workspaceId: string, workerId: string, input?: ReportTaskInput) => ReportTaskResult;
24
- statusTask: (workspaceId: string, workerId: string, input?: StatusTaskInput) => ReportTaskResult;
25
- cancelTask: (workspaceId: string, dispatchId: string, input: CancelTaskInput) => ReportTaskResult;
26
- listDispatches: (workspaceId: string, options?: ListDispatchesOptions) => DispatchRecord[];
27
- listWorkers: (workspaceId: string) => TeamListItem[];
28
- getLastPtyLineForAgent: (workspaceId: string, agentId: string) => string | null;
29
- getWorkspaceSnapshot: (workspaceId: string) => WorkspaceRecord;
30
- getWorker: (workspaceId: string, workerId: string) => AgentSummary;
31
- getAgent: (workspaceId: string, agentId: string) => AgentSummary;
32
- getPtyOutputBus: () => PtyOutputBus;
33
- listTerminalRuns: (workspaceId: string) => TerminalRunSummary[];
34
- closeWorkspaceShell: (workspaceId: string, runId: string) => boolean;
35
- startWorkspaceShell: (workspaceId: string) => Promise<LiveAgentRun>;
36
- configureAgentLaunch: (workspaceId: string, agentId: string, input: AgentLaunchConfigInput) => void;
37
- peekAgentLaunchConfig: (workspaceId: string, agentId: string) => AgentLaunchConfigInput | undefined;
38
- startAgent: (workspaceId: string, agentId: string, input: StartAgentOptions) => Promise<LiveAgentRun>;
39
- autostartConfiguredAgents: (input: StartAgentOptions) => Promise<Array<{
40
- agent_id: string;
41
- error: string | null;
42
- ok: boolean;
43
- run_id: string | null;
44
- workspace_id: string;
45
- }>>;
46
- startWorkspaceWatch: (workspaceId: string) => Promise<void>;
47
- getLiveRun: (runId: string) => LiveAgentRun;
48
- getActiveRunByAgentId: (workspaceId: string, agentId: string) => LiveAgentRun | undefined;
49
- registerTasksListener: (listener: (workspaceId: string, content: string) => void) => () => void;
50
- listAgentRuns: (agentId: string) => PersistedAgentRun[];
51
- listMessagesForRecovery: (workspaceId: string, sinceMs: number) => RecoveryMessage[];
52
- peekAgentToken: (agentId: string) => string | undefined;
53
- pauseTerminalRun: (runId: string) => void;
54
- resizeAgentRun: (runId: string, cols: number, rows: number) => void;
55
- resumeTerminalRun: (runId: string) => void;
56
- settings: SettingsStore;
57
- writeRunInput: (runId: string, input: Buffer | string) => void;
58
- getUiToken: () => string;
59
- stopAgentRun: (runId: string) => void;
60
- validateAgentToken: (agentId: string, token: string | undefined) => boolean;
61
- validateUiToken: (token: string | undefined) => boolean;
62
- }
63
- interface RuntimeStoreOptions {
64
- dataDir?: string;
65
- agentManager?: AgentManager;
66
- }
67
- interface StartAgentOptions {
68
- hivePort: string;
69
- }
70
- export type { RuntimeStore };
1
+ import type { RuntimeStore, RuntimeStoreOptions } from './runtime-store-contract.js';
2
+ import type { WorkflowRunRecord } from './workflow-run-store.js';
3
+ export type { RuntimeStore, WorkflowRunRecord };
71
4
  export declare const createRuntimeStore: (options?: RuntimeStoreOptions) => RuntimeStore;
@@ -1,4 +1,6 @@
1
- import { createRuntimeStoreLifecycle, createRuntimeStoreServices } from './runtime-store-helpers.js';
1
+ import { createRuntimeStoreLifecycle, createRuntimeStoreServices, logTasksFileWatchStartError, } from './runtime-store-helpers.js';
2
+ import { createRuntimeStoreWorkflowRuntime } from './runtime-store-workflows.js';
3
+ import { persistWorkflowSchedule } from './workflow-schedule-create.js';
2
4
  export const createRuntimeStore = (options = {}) => {
3
5
  const services = createRuntimeStoreServices(options);
4
6
  const lifecycle = createRuntimeStoreLifecycle(options.agentManager ? { agentManager: options.agentManager, services } : { services });
@@ -9,11 +11,22 @@ export const createRuntimeStore = (options = {}) => {
9
11
  }
10
12
  services.db.transaction(mutation)();
11
13
  };
12
- return {
13
- close: lifecycle.close,
14
+ let workflowRuntime;
15
+ const getWorkflowRuntime = () => {
16
+ if (!workflowRuntime)
17
+ throw new Error('Workflow runtime not initialized');
18
+ return workflowRuntime;
19
+ };
20
+ const store = {
21
+ close: async () => {
22
+ workflowRuntime?.scheduler.close();
23
+ await lifecycle.close();
24
+ },
14
25
  createWorkspace: (path, name) => {
15
26
  const workspace = services.workspaceStore.createWorkspace(path, name);
16
- void lifecycle.startWorkspaceWatch(workspace.id);
27
+ void lifecycle
28
+ .startWorkspaceWatch(workspace.id)
29
+ .catch((error) => logTasksFileWatchStartError(workspace.id, error));
17
30
  return workspace;
18
31
  },
19
32
  listWorkspaces: () => services.workspaceStore.listWorkspaces(),
@@ -36,6 +49,34 @@ export const createRuntimeStore = (options = {}) => {
36
49
  }
37
50
  },
38
51
  addWorker: (workspaceId, input) => services.workspaceStore.addWorker(workspaceId, input),
52
+ addWorkerWithLaunch: (workspaceId, input, launchConfig) => {
53
+ // Atomic spawn: create the worker AND its launch config together so a
54
+ // failure can never persist a worker with no way to start it. The DB
55
+ // transaction rolls back the row; the catch prunes the in-memory worker
56
+ // that addWorker already pushed into the workspace record.
57
+ let worker;
58
+ try {
59
+ runDataMutation(() => {
60
+ worker = store.addWorker(workspaceId, input);
61
+ store.configureAgentLaunch(workspaceId, worker.id, launchConfig);
62
+ });
63
+ }
64
+ catch (error) {
65
+ if (worker) {
66
+ try {
67
+ store.deleteWorker(workspaceId, worker.id);
68
+ }
69
+ catch {
70
+ // The transaction already removed the DB row; this only prunes the
71
+ // stale in-memory entry, which may already be gone.
72
+ }
73
+ }
74
+ throw error;
75
+ }
76
+ if (!worker)
77
+ throw new Error('addWorkerWithLaunch produced no worker');
78
+ return worker;
79
+ },
39
80
  renameWorker: (workspaceId, workerId, name) => services.workspaceStore.renameWorker(workspaceId, workerId, name),
40
81
  deleteWorker: (workspaceId, workerId) => {
41
82
  const activeRun = services.agentRuntime.getActiveRunByAgentId(workspaceId, workerId);
@@ -83,5 +124,30 @@ export const createRuntimeStore = (options = {}) => {
83
124
  stopAgentRun: lifecycle.stopTerminalRun,
84
125
  validateAgentToken: (agentId, token) => services.agentRuntime.validateAgentToken(agentId, token),
85
126
  validateUiToken: (token) => services.uiAuth.validate(token),
127
+ getWorkflowDispatchAwaiter: () => services.workflowDispatchAwaiter,
128
+ runWorkflow: (input) => getWorkflowRuntime().runner.runWorkflow(input),
129
+ startWorkflow: (input) => getWorkflowRuntime().runner.startWorkflow(input),
130
+ startWorkflowInline: (input) => getWorkflowRuntime().runner.startWorkflowInline(input),
131
+ stopWorkflowRun: (runId) => getWorkflowRuntime().runner.stopRun(runId),
132
+ getWorkflowRun: (runId) => services.workflowRunStore.getRun(runId),
133
+ listWorkspaceWorkflowRuns: (workspaceId) => services.workflowRunStore.listWorkspaceRuns(workspaceId),
134
+ listWorkflowRunDispatches: (runId) => services.dispatchLedgerStore.listWorkflowRunDispatches(runId),
135
+ listWorkflowRunLogs: (runId) => services.workflowRunLogStore.listForRun(runId).map((row) => ({
136
+ id: row.id,
137
+ ts: row.ts,
138
+ message: row.message,
139
+ })),
140
+ createWorkflowSchedule: (input) => services.workflowScheduleStore.create(input),
141
+ scheduleWorkflowInline: (input) => persistWorkflowSchedule({
142
+ workspacePath: services.workspaceStore.getWorkspaceSnapshot(input.workspaceId).summary.path,
143
+ scheduleStore: services.workflowScheduleStore,
144
+ ...input,
145
+ }),
146
+ updateWorkflowSchedule: (id, input) => services.workflowScheduleStore.update(id, input),
147
+ getWorkflowSchedule: (id) => services.workflowScheduleStore.get(id),
148
+ listWorkspaceWorkflowSchedules: (workspaceId) => services.workflowScheduleStore.listForWorkspace(workspaceId),
149
+ deleteWorkflowSchedule: (id) => services.workflowScheduleStore.deleteSchedule(id),
86
150
  };
151
+ workflowRuntime = createRuntimeStoreWorkflowRuntime(services, store);
152
+ return store;
87
153
  };
@@ -1,4 +1,27 @@
1
1
  export declare const getClaudeProjectsRoot: (pattern?: string) => string;
2
+ /**
3
+ * Match the directory-name encoding Claude Code itself uses for its project
4
+ * metadata under `~/.claude/projects/`. Empirically (probed via `claude
5
+ * --print "x"` in directories named with each character) Claude Code
6
+ * replaces *every* character outside `[A-Za-z0-9-]` with a single `-`,
7
+ * one-for-one, preserving literal hyphens.
8
+ *
9
+ * The previous regex `[\\/:\s]` only matched `\`, `/`, `:`, and whitespace —
10
+ * leaving `_`, `.`, parens, brackets, `@`, `#`, `&`, `+`, and any non-ASCII
11
+ * character (including CJK usernames) intact, while Claude Code's own
12
+ * encoder replaced them. The mismatch meant Hive looked for sessions under
13
+ * a different directory than the one Claude Code wrote to, so session
14
+ * resume silently failed for any workspace path containing those chars.
15
+ * Windows + CJK Windows usernames (`C:\Users\张三\project`) and
16
+ * underscored Windows project paths (`C:\my_project`) are the most common
17
+ * triggers, but the bug is cross-platform.
18
+ *
19
+ * Backward compatibility: workspaces whose path contains ONLY chars that
20
+ * the old regex already matched (`/\:` + whitespace) get the same encoded
21
+ * dirname as before — no behavior change. Workspaces with any other
22
+ * special chars previously had broken session resume; the fix moves them
23
+ * to the correct (working) directory.
24
+ */
2
25
  export declare const encodeClaudeProjectPath: (cwd: string) => string;
3
26
  interface ClaudeSessionCaptureDiscriminator {
4
27
  contentIncludes?: string | readonly string[];
@@ -18,7 +18,30 @@ export const getClaudeProjectsRoot = (pattern) => {
18
18
  }
19
19
  return root;
20
20
  };
21
- export const encodeClaudeProjectPath = (cwd) => cwd.replace(/[\\/:\s]/g, '-');
21
+ /**
22
+ * Match the directory-name encoding Claude Code itself uses for its project
23
+ * metadata under `~/.claude/projects/`. Empirically (probed via `claude
24
+ * --print "x"` in directories named with each character) Claude Code
25
+ * replaces *every* character outside `[A-Za-z0-9-]` with a single `-`,
26
+ * one-for-one, preserving literal hyphens.
27
+ *
28
+ * The previous regex `[\\/:\s]` only matched `\`, `/`, `:`, and whitespace —
29
+ * leaving `_`, `.`, parens, brackets, `@`, `#`, `&`, `+`, and any non-ASCII
30
+ * character (including CJK usernames) intact, while Claude Code's own
31
+ * encoder replaced them. The mismatch meant Hive looked for sessions under
32
+ * a different directory than the one Claude Code wrote to, so session
33
+ * resume silently failed for any workspace path containing those chars.
34
+ * Windows + CJK Windows usernames (`C:\Users\张三\project`) and
35
+ * underscored Windows project paths (`C:\my_project`) are the most common
36
+ * triggers, but the bug is cross-platform.
37
+ *
38
+ * Backward compatibility: workspaces whose path contains ONLY chars that
39
+ * the old regex already matched (`/\:` + whitespace) get the same encoded
40
+ * dirname as before — no behavior change. Workspaces with any other
41
+ * special chars previously had broken session resume; the fix moves them
42
+ * to the correct (working) directory.
43
+ */
44
+ export const encodeClaudeProjectPath = (cwd) => cwd.replace(/[^A-Za-z0-9-]/g, '-');
22
45
  const listSessionIds = (cwd, projectsRoot = getDefaultProjectsRoot()) => {
23
46
  const projectDir = join(projectsRoot, encodeClaudeProjectPath(cwd));
24
47
  try {
@@ -1,6 +1,6 @@
1
- export declare const getCodexHome: (pattern?: string) => string;
1
+ export declare const getCodexHome: (pattern?: string, platform?: NodeJS.Platform) => string;
2
2
  export declare const readCodexSessionFirstLine: (filePath: string, maxBytes?: number) => string | null;
3
- export declare const hasCodexSession: (cwd: string, sessionId: string, pattern?: string) => boolean;
4
- export declare const snapshotCodexSessionIds: (cwd: string, codexHome?: string) => Set<string>;
3
+ export declare const hasCodexSession: (cwd: string, sessionId: string, pattern?: string, platform?: NodeJS.Platform, codexHome?: string) => boolean;
4
+ export declare const snapshotCodexSessionIds: (cwd: string, codexHome?: string, platform?: NodeJS.Platform) => Set<string>;
5
5
  export declare const captureCodexSessionId: (cwd: string, knownSessionIds: Set<string>, onCapture: (sessionId: string) => void, timeoutMs?: number, intervalMs?: number, codexHome?: string) => Promise<void>;
6
6
  export declare const codexSessionStoreExists: (codexHome?: string) => boolean;
@@ -2,19 +2,21 @@ 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
6
  const CODEX_SESSION_FILE = /^rollout-.*\.jsonl$/i;
7
+ const CODEX_SESSIONS_MARKER = '/sessions/';
6
8
  const CODEX_HEADER_READ_CHUNK_BYTES = 4096;
7
9
  const CODEX_HEADER_MAX_BYTES = 64 * 1024;
8
10
  const getDefaultCodexHome = () => process.env.CODEX_HOME ?? join(homedir(), '.codex');
9
11
  const expandHome = (path) => path === '~' || path.startsWith('~/') ? join(homedir(), path.slice(2)) : path;
10
- export const getCodexHome = (pattern) => {
12
+ export const getCodexHome = (pattern, platform = process.platform) => {
11
13
  if (!pattern)
12
14
  return getDefaultCodexHome();
13
- const markerIndex = pattern.indexOf('/sessions/');
15
+ const markerIndex = indexOfPathMarker(pattern, CODEX_SESSIONS_MARKER, platform);
14
16
  if (markerIndex === -1)
15
17
  return getDefaultCodexHome();
16
18
  const rawRoot = pattern.slice(0, markerIndex);
17
- if (rawRoot === '~/.codex' || rawRoot === '~/.codex/')
19
+ if (arePathsEqual(rawRoot, '~/.codex', platform) || arePathsEqual(rawRoot, '~/.codex/', platform))
18
20
  return getDefaultCodexHome();
19
21
  const root = expandHome(rawRoot);
20
22
  return root || getDefaultCodexHome();
@@ -78,13 +80,13 @@ const parseCodexSession = (filePath) => {
78
80
  const cwd = 'cwd' in payload && typeof payload.cwd === 'string' ? payload.cwd : null;
79
81
  return id && cwd ? { cwd, id } : null;
80
82
  };
81
- const listSessionIds = (cwd, codexHome = getDefaultCodexHome()) => {
83
+ const listSessionIds = (cwd, codexHome = getDefaultCodexHome(), platform = process.platform) => {
82
84
  const sessionsRoot = join(codexHome, 'sessions');
83
85
  return walkSessionFiles(sessionsRoot)
84
86
  .flatMap((filePath) => {
85
87
  try {
86
88
  const session = parseCodexSession(filePath);
87
- return session?.cwd === cwd ? [session.id] : [];
89
+ return session && arePathsEqual(session.cwd, cwd, platform) ? [session.id] : [];
88
90
  }
89
91
  catch {
90
92
  return [];
@@ -92,8 +94,8 @@ const listSessionIds = (cwd, codexHome = getDefaultCodexHome()) => {
92
94
  })
93
95
  .sort((left, right) => left.localeCompare(right));
94
96
  };
95
- export const hasCodexSession = (cwd, sessionId, pattern) => listSessionIds(cwd, getCodexHome(pattern)).includes(sessionId);
96
- export const snapshotCodexSessionIds = (cwd, codexHome = getDefaultCodexHome()) => new Set(listSessionIds(cwd, codexHome));
97
+ export const hasCodexSession = (cwd, sessionId, pattern, platform = process.platform, codexHome = getCodexHome(pattern, platform)) => listSessionIds(cwd, codexHome, platform).includes(sessionId);
98
+ export const snapshotCodexSessionIds = (cwd, codexHome = getDefaultCodexHome(), platform = process.platform) => new Set(listSessionIds(cwd, codexHome, platform));
97
99
  export const captureCodexSessionId = async (cwd, knownSessionIds, onCapture, timeoutMs = 5000, intervalMs = 100, codexHome = getDefaultCodexHome()) => {
98
100
  await captureSessionIdWithCoordinator({
99
101
  intervalMs,
@@ -1,4 +1,4 @@
1
- export declare const getGeminiHome: (pattern?: string) => string;
1
+ export declare const getGeminiHome: (pattern?: string, platform?: NodeJS.Platform) => string;
2
2
  export declare const hasGeminiSession: (cwd: string, sessionId: string, pattern?: string) => boolean;
3
3
  export declare const snapshotGeminiSessionIds: (cwd: string, geminiHome?: string) => Set<string>;
4
4
  export declare const captureGeminiSessionId: (cwd: string, knownSessionIds: Set<string>, onCapture: (sessionId: string) => void, timeoutMs?: number, intervalMs?: number, geminiHome?: string) => Promise<void>;
@@ -2,13 +2,15 @@ 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
6
  const GEMINI_SESSION_FILE = /^session-.*\.json$/i;
7
+ const GEMINI_TMP_MARKER = '/tmp/';
6
8
  const getDefaultGeminiHome = () => process.env.HIVE_GEMINI_HOME ?? join(homedir(), '.gemini');
7
9
  const expandHome = (path) => path === '~' || path.startsWith('~/') ? join(homedir(), path.slice(2)) : path;
8
- export const getGeminiHome = (pattern) => {
10
+ export const getGeminiHome = (pattern, platform = process.platform) => {
9
11
  if (!pattern)
10
12
  return getDefaultGeminiHome();
11
- const markerIndex = pattern.indexOf('/tmp/');
13
+ const markerIndex = indexOfPathMarker(pattern, GEMINI_TMP_MARKER, platform);
12
14
  if (markerIndex === -1)
13
15
  return getDefaultGeminiHome();
14
16
  const rawRoot = pattern.slice(0, markerIndex);
@@ -38,7 +40,8 @@ const listSessionIds = (cwd, geminiHome = getDefaultGeminiHome()) => {
38
40
  .filter((entry) => entry.isDirectory())
39
41
  .flatMap((entry) => {
40
42
  const projectDir = join(tmpRoot, entry.name);
41
- if (readProjectRoot(projectDir) !== cwd)
43
+ const projectRoot = readProjectRoot(projectDir);
44
+ if (projectRoot === null || !arePathsEqual(projectRoot, cwd))
42
45
  return [];
43
46
  const chatsDir = join(projectDir, 'chats');
44
47
  try {
@@ -1,3 +1,21 @@
1
+ /**
2
+ * Resolve the path OpenCode upstream writes `opencode.db` to. Branches
3
+ * by platform because XDG_DATA_HOME is not a Windows convention — the
4
+ * previous unconditional ~/.local/share/opencode/opencode.db default
5
+ * pointed at a path that never exists on Windows, silently breaking
6
+ * Layer A native session resume for OpenCode workers there.
7
+ *
8
+ * Resolution order:
9
+ * 1. HIVE_OPENCODE_DB_PATH override (any platform, for tests/users).
10
+ * 2. Windows: %LOCALAPPDATA%\opencode\opencode.db (with a homedir
11
+ * fallback when LOCALAPPDATA is missing — some shells strip env).
12
+ * 3. POSIX: $XDG_DATA_HOME/opencode/opencode.db, falling back to
13
+ * ~/.local/share/opencode/opencode.db.
14
+ *
15
+ * Exported so the path-resolution rules can be unit-tested without
16
+ * needing to mock the underlying SQLite open.
17
+ */
18
+ export declare const getDefaultOpenCodeDbPath: (platform?: NodeJS.Platform) => string;
1
19
  export declare const getOpenCodeDbPath: (pattern?: string) => string;
2
20
  export declare const hasOpenCodeSession: (cwd: string, sessionId: string, pattern?: string) => boolean;
3
21
  export declare const snapshotOpenCodeSessionIds: (cwd: string, dbPath?: string) => Set<string>;