@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
package/CHANGELOG.md CHANGED
@@ -2,6 +2,50 @@
2
2
 
3
3
  All notable user-facing changes will be documented in this file.
4
4
 
5
+ ## 1.5.0 - 2026-05-31
6
+
7
+ Workflow runtime, experimental team automation, and Codex reliability.
8
+
9
+ - Adds the experimental Hive workflow runtime: Orchestrators can author
10
+ multi-agent workflow scripts that fan out across real Hive PTY workers, show
11
+ runs in the Workflows drawer, stop runs, inspect run details, schedule
12
+ recurring workflows, and route workflow reports back into the Orchestrator.
13
+ - Adds workflow agent CLI policy settings so users can choose which CLI
14
+ workflow-created agents use by default and which CLIs are allowed.
15
+ - Adds the experimental auto-staff setting, letting the Orchestrator size the
16
+ worker roster to the task and prefer task-scoped ephemeral workers when it
17
+ needs temporary coders, testers, or reviewers.
18
+ - Adds an in-app What's New dialog so future upgrades can surface curated
19
+ release highlights without requiring users to read the changelog manually.
20
+ - Improves Codex reliability by waiting for pasted-content acknowledgements on
21
+ long dispatches while submitting short report/status injections quickly,
22
+ avoiding the several-second delay before reports reach the Orchestrator.
23
+ - Hardens Windows and runtime edge cases, including malformed WebSocket frames,
24
+ stale nvm4w Codex node entrypoints, workflow worker exits, and additional
25
+ workflow/runtime cleanup paths.
26
+
27
+ ## 1.4.4 - 2026-05-29
28
+
29
+ Windows portability and team protocol hardening.
30
+
31
+ - Fixes Windows `.cmd` / `.bat` launch handling for built-in and custom startup
32
+ commands, including quoted paths from nvm4w and `Program Files`.
33
+ - Improves Windows runtime shutdown by tearing down WebSocket connections before
34
+ closing the HTTP server and killing worker process trees with `taskkill /T /F`
35
+ before falling back to PTY termination.
36
+ - Makes `hive update`, open-in-editor commands, folder picking, filesystem
37
+ browsing, and port-in-use recovery friendlier on Windows.
38
+ - Preserves CRLF line endings in `.hive/tasks.md` mutations and makes the tasks
39
+ watcher more tolerant of atomic-save editors.
40
+ - Resolves OpenCode session data under `%LOCALAPPDATA%` on Windows and aligns
41
+ Claude session path encoding with Claude Code's project directory format.
42
+ - Hardens `team send` against stale worker names by returning a 409 with the
43
+ current roster and updates orchestrator guidance to refresh the member list
44
+ before dispatching.
45
+ - Expands Windows-focused unit and integration coverage across startup command
46
+ parsing, CLI shims, stdin protocol help, terminal profiles, filesystem
47
+ browsing, process cleanup, and path rendering.
48
+
5
49
  ## 1.4.3 - 2026-05-28
6
50
 
7
51
  Update hardening for multi-Node installs.
package/README.en.md CHANGED
@@ -107,10 +107,11 @@ a separate app from `hive --port 3000`. To uninstall, visit `chrome://apps`,
107
107
  right-click the Hive tile, and choose **Remove from Chrome…**.
108
108
 
109
109
  Hive asks the browser to confirm before closing the tab or PWA window so an
110
- accidental Cmd-W doesn't drop your session. Modern browsers gate that prompt
111
- on prior page interaction if you open the PWA and immediately press Cmd-W
112
- without clicking or typing anywhere first, it still closes cleanly. That's a
113
- browser policy, not a Hive bug.
110
+ accidental close shortcut (Cmd-W on macOS, Ctrl-W on Windows/Linux) doesn't
111
+ drop your session. Modern browsers gate that prompt on prior page interaction
112
+ if you open the PWA and immediately press the close shortcut without
113
+ clicking or typing anywhere first, it still closes cleanly. That's a browser
114
+ policy, not a Hive bug.
114
115
 
115
116
  First-run flow:
116
117
 
package/README.md CHANGED
@@ -74,7 +74,7 @@ hive update
74
74
 
75
75
  PWA 只是 UI 壳,Hive 后端仍需要在终端里跑着。如果启动 PWA 时后端没起,会看到 “Hive 后端未启动” 页面,等你跑起 `hive` 后会自动刷新。PWA 的 install scope 按 origin(含端口)划分,所以 `hive --port 4011` 跟 `hive --port 3000` 在浏览器看来是两个独立应用。卸载方法:浏览器地址栏访问 `chrome://apps`,右键 Hive 图标,选 **从 Chrome 中移除…**。
76
76
 
77
- 关闭 PWA 窗口或 tab 时 Hive 会主动请求浏览器弹原生确认对话框,避免 Cmd+W 误关丢失会话。但现代浏览器要求你跟页面"交互过"(点击 / 滚动 / 输入)才会真的弹这个对话框——刚打开 PWA 立刻按 Cmd+W 仍会直接关闭,这是浏览器策略,不是 Hive 的 bug。
77
+ 关闭 PWA 窗口或 tab 时 Hive 会主动请求浏览器弹原生确认对话框,避免关闭快捷键(macOS 上是 Cmd+W、Windows / Linux 上是 Ctrl+W)误关丢失会话。但现代浏览器要求你跟页面"交互过"(点击 / 滚动 / 输入)才会真的弹这个对话框——刚打开 PWA 立刻按关闭快捷键仍会直接关闭,这是浏览器策略,不是 Hive 的 bug。
78
78
 
79
79
  首次使用流程:
80
80
 
@@ -241,6 +241,14 @@ pnpm release:dry
241
241
 
242
242
  Hive 目前处于 alpha 阶段,核心流程已可用。当前重点是继续打磨多 Agent 协作体验、Windows 支持和更清晰的调度可观测性。欢迎试用、提 issue——反馈会直接影响后续节奏。
243
243
 
244
+ ## 交流群
245
+
246
+ 有问题、想反馈,或者就想聊聊 Agent 协作,欢迎进 QQ 群:**Ai Native 交流群**(群号 `1098836554`)。
247
+
248
+ <p align="center">
249
+ <img src="./assets/qq-group.jpg" width="240" alt="Ai Native 交流群 QQ 群二维码,群号 1098836554" />
250
+ </p>
251
+
244
252
  ## 在路上:跨 Agent 的长时记忆
245
253
 
246
254
  <p align="center">
Binary file
package/dist/bin/team.cmd CHANGED
@@ -1,2 +1,3 @@
1
1
  @echo off
2
+ setlocal DisableDelayedExpansion
2
3
  node "%~dp0..\src\cli\team.js" %*
@@ -4,6 +4,63 @@ export interface RunUpdateResult {
4
4
  spawnError?: Error;
5
5
  }
6
6
  export type RunUpdate = (command: string, args: readonly string[]) => Promise<RunUpdateResult>;
7
+ export interface SpawnInvocationPlan {
8
+ command: string;
9
+ args: string[];
10
+ options: {
11
+ stdio: 'inherit';
12
+ windowsHide?: boolean;
13
+ };
14
+ }
15
+ /**
16
+ * Plan how `defaultRunUpdate` should hand the npm invocation to
17
+ * `child_process.spawn`. Two non-obvious cases collapse here.
18
+ *
19
+ * 1. Node 22+ refuses to spawn `.cmd` / `.bat` files directly after
20
+ * CVE-2024-27980 unless `shell: true` is passed. We do not want
21
+ * `shell: true` though — its arg-stringification path joins argv
22
+ * without quoting, so an install prefix containing spaces (the
23
+ * common Windows case `C:\Program Files\nodejs`) gets word-split
24
+ * by cmd.exe and `--prefix` only sees the first token, so npm
25
+ * silently installs hive to the wrong directory.
26
+ * 2. Wrapping with `cmd.exe /d /s /c <npm.cmd> <args>` solves both:
27
+ * spawning `cmd.exe` (a native exe) avoids the .cmd refusal, and
28
+ * Node's own argv-quoting builds an lpCommandLine where each
29
+ * space-containing arg is wrapped in double quotes that cmd.exe
30
+ * then re-parses correctly. `/d` skips AutoRun, `/s` keeps the
31
+ * quote-handling consistent with `/c`.
32
+ *
33
+ * Detect by filename suffix instead of `process.platform` so unit
34
+ * tests can inject `platform: 'win32'` and exercise the wrap.
35
+ */
36
+ export declare const planSpawnInvocation: (command: string, args: readonly string[], platform?: NodeJS.Platform) => SpawnInvocationPlan;
37
+ /**
38
+ * Signals the upgrade child should receive when the parent runtime is
39
+ * interrupted. Beyond the POSIX-only SIGTERM/SIGINT, SIGHUP is what
40
+ * libuv synthesises from Windows CTRL_CLOSE_EVENT (window X close),
41
+ * and SIGBREAK comes from Windows Ctrl+Break. Without forwarding
42
+ * those two the npm child outlives the runtime on the most common
43
+ * Windows exit paths.
44
+ */
45
+ export declare const FORWARDED_UPDATE_SIGNALS: readonly NodeJS.Signals[];
46
+ /**
47
+ * Forward a parent-process signal to the spawned npm child. On POSIX
48
+ * we hand the signal straight to the child; on Windows there are no
49
+ * real signals, so `child.kill(SIGTERM)` resolves to TerminateProcess
50
+ * against cmd.exe (our wrapper) only — npm itself, plus any install
51
+ * scripts it spawned, become orphans. `taskkill /pid <pid> /t /f`
52
+ * walks the wrapper's process tree so the whole branch dies together.
53
+ * If taskkill is unavailable (restricted PATH, locked-down policy)
54
+ * we fall back to `child.kill` so the wrapper at least exits.
55
+ *
56
+ * Exported so the win32 path can be unit-tested by injecting a stub
57
+ * `killTree` runner — the real `taskkillProcessTree` shells out, and
58
+ * we don't want that running during the test suite.
59
+ */
60
+ export declare const killUpdateChild: (child: {
61
+ pid?: number | undefined;
62
+ kill: (signal: NodeJS.Signals) => boolean;
63
+ }, signal: NodeJS.Signals, platform?: NodeJS.Platform, killTree?: (pid: number, onFailure?: () => void) => boolean) => void;
7
64
  export declare const defaultRunUpdate: RunUpdate;
8
65
  export declare const resolveHiveUpdateInstallArgs: (moduleUrl?: string) => string[];
9
66
  interface RunHiveUpdateOptions {
@@ -2,7 +2,9 @@ import { spawn } from 'node:child_process';
2
2
  import { existsSync, readFileSync } from 'node:fs';
3
3
  import { basename, dirname, join } from 'node:path';
4
4
  import { fileURLToPath } from 'node:url';
5
+ import { taskkillProcessTree } from '../server/agent-manager-support.js';
5
6
  import { getNpmCommand, INSTALL_COMMAND_ARGS, INSTALL_COMMAND_DISPLAY, PACKAGE_NAME, } from '../server/package-version.js';
7
+ import { buildCmdCallCommand } from '../server/windows-command-line.js';
6
8
  export const HIVE_UPDATE_USAGE = [
7
9
  'Usage:',
8
10
  ' hive update',
@@ -19,27 +21,102 @@ export const HIVE_UPDATE_USAGE = [
19
21
  'Options:',
20
22
  ' -h, --help Print this help.',
21
23
  ].join('\n');
24
+ /**
25
+ * Plan how `defaultRunUpdate` should hand the npm invocation to
26
+ * `child_process.spawn`. Two non-obvious cases collapse here.
27
+ *
28
+ * 1. Node 22+ refuses to spawn `.cmd` / `.bat` files directly after
29
+ * CVE-2024-27980 unless `shell: true` is passed. We do not want
30
+ * `shell: true` though — its arg-stringification path joins argv
31
+ * without quoting, so an install prefix containing spaces (the
32
+ * common Windows case `C:\Program Files\nodejs`) gets word-split
33
+ * by cmd.exe and `--prefix` only sees the first token, so npm
34
+ * silently installs hive to the wrong directory.
35
+ * 2. Wrapping with `cmd.exe /d /s /c <npm.cmd> <args>` solves both:
36
+ * spawning `cmd.exe` (a native exe) avoids the .cmd refusal, and
37
+ * Node's own argv-quoting builds an lpCommandLine where each
38
+ * space-containing arg is wrapped in double quotes that cmd.exe
39
+ * then re-parses correctly. `/d` skips AutoRun, `/s` keeps the
40
+ * quote-handling consistent with `/c`.
41
+ *
42
+ * Detect by filename suffix instead of `process.platform` so unit
43
+ * tests can inject `platform: 'win32'` and exercise the wrap.
44
+ */
45
+ export const planSpawnInvocation = (command, args, platform = process.platform) => {
46
+ if (platform === 'win32' && /\.(cmd|bat)$/i.test(command)) {
47
+ return {
48
+ command: 'cmd.exe',
49
+ args: ['/d', '/s', '/c', buildCmdCallCommand(command, args)],
50
+ options: { stdio: 'inherit', windowsHide: false },
51
+ };
52
+ }
53
+ return { command, args: [...args], options: { stdio: 'inherit' } };
54
+ };
55
+ /**
56
+ * Signals the upgrade child should receive when the parent runtime is
57
+ * interrupted. Beyond the POSIX-only SIGTERM/SIGINT, SIGHUP is what
58
+ * libuv synthesises from Windows CTRL_CLOSE_EVENT (window X close),
59
+ * and SIGBREAK comes from Windows Ctrl+Break. Without forwarding
60
+ * those two the npm child outlives the runtime on the most common
61
+ * Windows exit paths.
62
+ */
63
+ export const FORWARDED_UPDATE_SIGNALS = [
64
+ 'SIGINT',
65
+ 'SIGTERM',
66
+ 'SIGHUP',
67
+ 'SIGBREAK',
68
+ ];
69
+ /**
70
+ * Forward a parent-process signal to the spawned npm child. On POSIX
71
+ * we hand the signal straight to the child; on Windows there are no
72
+ * real signals, so `child.kill(SIGTERM)` resolves to TerminateProcess
73
+ * against cmd.exe (our wrapper) only — npm itself, plus any install
74
+ * scripts it spawned, become orphans. `taskkill /pid <pid> /t /f`
75
+ * walks the wrapper's process tree so the whole branch dies together.
76
+ * If taskkill is unavailable (restricted PATH, locked-down policy)
77
+ * we fall back to `child.kill` so the wrapper at least exits.
78
+ *
79
+ * Exported so the win32 path can be unit-tested by injecting a stub
80
+ * `killTree` runner — the real `taskkillProcessTree` shells out, and
81
+ * we don't want that running during the test suite.
82
+ */
83
+ export const killUpdateChild = (child, signal, platform = process.platform, killTree = (pid, onFailure) => taskkillProcessTree(pid, platform, undefined, onFailure)) => {
84
+ const fallback = () => {
85
+ try {
86
+ child.kill(signal);
87
+ }
88
+ catch {
89
+ // child.kill on Windows throws if the signal name isn't
90
+ // implemented; we forward what we can and ignore the rest.
91
+ }
92
+ };
93
+ if (platform === 'win32' && typeof child.pid === 'number' && child.pid > 0) {
94
+ if (killTree(child.pid, fallback))
95
+ return;
96
+ }
97
+ fallback();
98
+ };
22
99
  export const defaultRunUpdate = (command, args) => new Promise((resolve) => {
23
- const child = spawn(command, [...args], { stdio: 'inherit' });
100
+ const plan = planSpawnInvocation(command, args);
101
+ const child = spawn(plan.command, plan.args, plan.options);
24
102
  let resolved = false;
25
- // Forward Ctrl+C / SIGTERM to the npm child so it can clean up rather
26
- // than getting orphaned mid-install. The handlers are registered with
27
- // `once` so they don't accumulate across invocations, and we also
28
- // explicitly remove them when the child exits in case the user only
29
- // sent one signal (Node would otherwise keep the handler alive).
30
- const handleSignal = (signal) => () => {
31
- child.kill(signal);
32
- };
33
- const handleSigint = handleSignal('SIGINT');
34
- const handleSigterm = handleSignal('SIGTERM');
35
- process.once('SIGINT', handleSigint);
36
- process.once('SIGTERM', handleSigterm);
103
+ // Handlers are registered with `once` so they don't accumulate
104
+ // across invocations and explicitly removed at finalize().
105
+ const handlers = new Map();
106
+ for (const signal of FORWARDED_UPDATE_SIGNALS) {
107
+ const handler = () => {
108
+ killUpdateChild(child, signal);
109
+ };
110
+ handlers.set(signal, handler);
111
+ process.once(signal, handler);
112
+ }
37
113
  const finalize = (result) => {
38
114
  if (resolved)
39
115
  return;
40
116
  resolved = true;
41
- process.off('SIGINT', handleSigint);
42
- process.off('SIGTERM', handleSigterm);
117
+ for (const [signal, handler] of handlers) {
118
+ process.off(signal, handler);
119
+ }
43
120
  resolve(result);
44
121
  };
45
122
  child.on('error', (error) => {
@@ -9,7 +9,64 @@ interface RunHiveCommandResult {
9
9
  type RunHiveCommandOptions = {
10
10
  versionService?: VersionService;
11
11
  };
12
+ /**
13
+ * Signals that should drive a graceful shutdown. The interesting ones:
14
+ *
15
+ * SIGINT — Ctrl+C in the runtime terminal (all platforms).
16
+ * SIGTERM — `kill <pid>` on POSIX. Never delivered on Windows.
17
+ * SIGHUP — POSIX: parent shell exits. On Windows libuv synthesises
18
+ * SIGHUP from `CTRL_CLOSE_EVENT`, i.e. the user clicking
19
+ * the X on the runtime's cmd / Terminal window. Without
20
+ * this listener that close path skips the graceful path
21
+ * entirely on Windows.
22
+ * SIGBREAK — Windows: Ctrl+Break. Less common than Ctrl+C but kit
23
+ * scripts and CI hosts still send it.
24
+ *
25
+ * Stale agent_runs from a non-graceful exit are reconciled at next
26
+ * startup via `agentRunStore.markUnfinishedRunsStale()`
27
+ * (runtime-store-helpers.ts), so a dropped signal does not leave the
28
+ * database in an inconsistent state — only the PTY children miss the
29
+ * forwarded SIGTERM. On Windows there's no graceful equivalent for
30
+ * those children anyway (pty.kill is TerminateProcess), so this
31
+ * registration is mostly about giving SQLite a chance to checkpoint.
32
+ */
33
+ export declare const SHUTDOWN_SIGNALS: readonly ["SIGINT", "SIGTERM", "SIGHUP", "SIGBREAK"];
12
34
  export declare const HIVE_USAGE: string;
13
35
  export declare const handleHiveInfoCommand: (argv: string[]) => boolean;
36
+ /**
37
+ * Resolve the directory where Hive persists its SQLite DB and supporting
38
+ * state. Platform-aware because `~/.config/hive` is a hidden dot-directory
39
+ * convention that Windows Explorer treats as second-class — Windows users
40
+ * can't navigate there from the address bar without typing the full path,
41
+ * and the standard roaming-profile location is `%APPDATA%\<app>` instead.
42
+ *
43
+ * Resolution order:
44
+ * 1. HIVE_DATA_DIR override (any platform, for tests / opinionated users).
45
+ * 2. Windows: %APPDATA%\hive — roaming user state, follows the user
46
+ * across machines on a domain profile. APPDATA, not LOCALAPPDATA,
47
+ * because Hive's DB is user data, not a machine-local cache.
48
+ * Falls back to homedir()\AppData\Roaming\hive when APPDATA is
49
+ * stripped from the env (some Windows Task Scheduler configs do this).
50
+ * 3. POSIX: $XDG_CONFIG_HOME/hive, falling back to ~/.config/hive.
51
+ *
52
+ * Migration: pre-fix Windows installs wrote to ~/.config/hive. When that
53
+ * legacy directory exists but the new %APPDATA%\hive does not, prefer the
54
+ * legacy path so an upgrade does not surface as an empty workspace list.
55
+ * This is a one-way ratchet — once the new location is populated, it wins.
56
+ *
57
+ * Exported so the resolution rules are unit-testable without touching env
58
+ * or the real filesystem.
59
+ */
60
+ export declare const resolveDataDir: (platform?: NodeJS.Platform, env?: NodeJS.ProcessEnv, pathExists?: (path: string) => boolean) => string;
61
+ /**
62
+ * Recovery hint formatter for the "port already in use" error. Platform-aware
63
+ * because the lsof / xargs / kill pipeline is POSIX-only; on Windows a user
64
+ * pasting that command into cmd or PowerShell gets nothing useful. The
65
+ * Windows path swaps in `netstat -ano | findstr` + `taskkill /F /PID` which
66
+ * is the documented Microsoft workflow for the same problem.
67
+ *
68
+ * Exported for unit testing.
69
+ */
70
+ export declare const formatPortInUseMessage: (port: number, platform?: NodeJS.Platform) => string;
14
71
  export declare const runHiveCommand: (argv: string[], options?: RunHiveCommandOptions) => Promise<RunHiveCommandResult>;
15
72
  export type { RunHiveCommandResult };
@@ -1,15 +1,38 @@
1
1
  #!/usr/bin/env node
2
2
  import { once } from 'node:events';
3
- import { realpathSync } from 'node:fs';
3
+ import { existsSync } from 'node:fs';
4
4
  import { homedir } from 'node:os';
5
5
  import { join } from 'node:path';
6
6
  import { fileURLToPath } from 'node:url';
7
7
  import { createAgentManager } from '../server/agent-manager.js';
8
8
  import { createApp } from '../server/app.js';
9
9
  import { readPackageVersion } from '../server/package-version.js';
10
+ import { sameFilesystemPath } from '../server/path-canonicalization.js';
10
11
  import { createRuntimeStore } from '../server/runtime-store.js';
11
12
  import { createVersionService } from '../server/version-service.js';
12
13
  import { runHiveUpdateCommand } from './hive-update.js';
14
+ /**
15
+ * Signals that should drive a graceful shutdown. The interesting ones:
16
+ *
17
+ * SIGINT — Ctrl+C in the runtime terminal (all platforms).
18
+ * SIGTERM — `kill <pid>` on POSIX. Never delivered on Windows.
19
+ * SIGHUP — POSIX: parent shell exits. On Windows libuv synthesises
20
+ * SIGHUP from `CTRL_CLOSE_EVENT`, i.e. the user clicking
21
+ * the X on the runtime's cmd / Terminal window. Without
22
+ * this listener that close path skips the graceful path
23
+ * entirely on Windows.
24
+ * SIGBREAK — Windows: Ctrl+Break. Less common than Ctrl+C but kit
25
+ * scripts and CI hosts still send it.
26
+ *
27
+ * Stale agent_runs from a non-graceful exit are reconciled at next
28
+ * startup via `agentRunStore.markUnfinishedRunsStale()`
29
+ * (runtime-store-helpers.ts), so a dropped signal does not leave the
30
+ * database in an inconsistent state — only the PTY children miss the
31
+ * forwarded SIGTERM. On Windows there's no graceful equivalent for
32
+ * those children anyway (pty.kill is TerminateProcess), so this
33
+ * registration is mostly about giving SQLite a chance to checkpoint.
34
+ */
35
+ export const SHUTDOWN_SIGNALS = ['SIGINT', 'SIGTERM', 'SIGHUP', 'SIGBREAK'];
13
36
  export const HIVE_USAGE = [
14
37
  'Usage:',
15
38
  ' hive [--port <port>]',
@@ -58,7 +81,44 @@ const parsePort = (argv) => {
58
81
  }
59
82
  return parsedPort ?? 3000;
60
83
  };
61
- const resolveDataDir = () => process.env.HIVE_DATA_DIR || join(homedir(), '.config', 'hive');
84
+ /**
85
+ * Resolve the directory where Hive persists its SQLite DB and supporting
86
+ * state. Platform-aware because `~/.config/hive` is a hidden dot-directory
87
+ * convention that Windows Explorer treats as second-class — Windows users
88
+ * can't navigate there from the address bar without typing the full path,
89
+ * and the standard roaming-profile location is `%APPDATA%\<app>` instead.
90
+ *
91
+ * Resolution order:
92
+ * 1. HIVE_DATA_DIR override (any platform, for tests / opinionated users).
93
+ * 2. Windows: %APPDATA%\hive — roaming user state, follows the user
94
+ * across machines on a domain profile. APPDATA, not LOCALAPPDATA,
95
+ * because Hive's DB is user data, not a machine-local cache.
96
+ * Falls back to homedir()\AppData\Roaming\hive when APPDATA is
97
+ * stripped from the env (some Windows Task Scheduler configs do this).
98
+ * 3. POSIX: $XDG_CONFIG_HOME/hive, falling back to ~/.config/hive.
99
+ *
100
+ * Migration: pre-fix Windows installs wrote to ~/.config/hive. When that
101
+ * legacy directory exists but the new %APPDATA%\hive does not, prefer the
102
+ * legacy path so an upgrade does not surface as an empty workspace list.
103
+ * This is a one-way ratchet — once the new location is populated, it wins.
104
+ *
105
+ * Exported so the resolution rules are unit-testable without touching env
106
+ * or the real filesystem.
107
+ */
108
+ export const resolveDataDir = (platform = process.platform, env = process.env, pathExists = existsSync) => {
109
+ const override = env.HIVE_DATA_DIR;
110
+ if (override)
111
+ return override;
112
+ if (platform === 'win32') {
113
+ const appData = env.APPDATA ?? join(homedir(), 'AppData', 'Roaming');
114
+ const target = join(appData, 'hive');
115
+ const legacy = join(homedir(), '.config', 'hive');
116
+ if (!pathExists(target) && pathExists(legacy))
117
+ return legacy;
118
+ return target;
119
+ }
120
+ return join(env.XDG_CONFIG_HOME ?? join(homedir(), '.config'), 'hive');
121
+ };
62
122
  const maybePrintUpdateHint = async (versionService) => {
63
123
  const info = await versionService.getVersionInfo();
64
124
  if (!info.update_available)
@@ -66,19 +126,39 @@ const maybePrintUpdateHint = async (versionService) => {
66
126
  console.log(`Hive update available: ${info.current_version} -> ${info.latest_version}. Run: ${info.install_hint}`);
67
127
  };
68
128
  const isListenError = (error) => error instanceof Error && typeof error.code === 'string';
69
- const formatPortInUseMessage = (port) => [
70
- `Hive could not start because port ${port} is already in use.`,
71
- '',
72
- 'Another Hive instance may already be running:',
73
- ` http://127.0.0.1:${port}`,
74
- '',
75
- 'Options:',
76
- ' - Open the existing Hive window.',
77
- ' - Stop the process using that port:',
78
- ` lsof -tiTCP:${port} -sTCP:LISTEN | xargs kill`,
79
- ' - Start Hive on another port:',
80
- ` hive --port ${port + 1}`,
81
- ].join('\n');
129
+ /**
130
+ * Recovery hint formatter for the "port already in use" error. Platform-aware
131
+ * because the lsof / xargs / kill pipeline is POSIX-only; on Windows a user
132
+ * pasting that command into cmd or PowerShell gets nothing useful. The
133
+ * Windows path swaps in `netstat -ano | findstr` + `taskkill /F /PID` which
134
+ * is the documented Microsoft workflow for the same problem.
135
+ *
136
+ * Exported for unit testing.
137
+ */
138
+ export const formatPortInUseMessage = (port, platform = process.platform) => {
139
+ const stopHint = platform === 'win32'
140
+ ? [
141
+ ' - Stop the process using that port:',
142
+ ` netstat -ano | findstr ":${port}"`,
143
+ ' taskkill /PID <pid> /F',
144
+ ]
145
+ : [
146
+ ' - Stop the process using that port:',
147
+ ` lsof -tiTCP:${port} -sTCP:LISTEN | xargs kill`,
148
+ ];
149
+ return [
150
+ `Hive could not start because port ${port} is already in use.`,
151
+ '',
152
+ 'Another Hive instance may already be running:',
153
+ ` http://127.0.0.1:${port}`,
154
+ '',
155
+ 'Options:',
156
+ ' - Open the existing Hive window.',
157
+ ...stopHint,
158
+ ' - Start Hive on another port:',
159
+ ` hive --port ${port + 1}`,
160
+ ].join('\n');
161
+ };
82
162
  const formatListenError = (error, requestedPort) => {
83
163
  if (isListenError(error) && error.code === 'EADDRINUSE') {
84
164
  return new Error(formatPortInUseMessage(error.port ?? requestedPort));
@@ -119,8 +199,20 @@ export const runHiveCommand = async (argv, options = {}) => {
119
199
  return closePromise;
120
200
  }
121
201
  closePromise = (async () => {
122
- process.off('SIGTERM', gracefulShutdown);
123
- process.off('SIGINT', gracefulShutdown);
202
+ for (const signal of SHUTDOWN_SIGNALS) {
203
+ process.off(signal, gracefulShutdown);
204
+ }
205
+ // Tear down the WebSocket layer FIRST. `app.server.close()` waits
206
+ // on every existing socket, including upgraded WebSocket clients
207
+ // that never go idle on their own; `server.closeAllConnections()`
208
+ // alone does NOT terminate already-upgraded WS — only sockets
209
+ // still in the HTTP request/response state machine. The Windows
210
+ // symptom of skipping this step is Ctrl+C in the runtime cmd
211
+ // window hanging the process as long as any browser tab is
212
+ // connected to /ws/terminal/<runId> or /ws/tasks/<workspaceId>.
213
+ app.closeWebSockets();
214
+ // Then force-close any remaining plain-HTTP keep-alive sockets.
215
+ app.server.closeAllConnections();
124
216
  await new Promise((resolve, reject) => {
125
217
  app.server.close((error) => {
126
218
  if (error) {
@@ -144,8 +236,9 @@ export const runHiveCommand = async (argv, options = {}) => {
144
236
  process.exit(1);
145
237
  });
146
238
  };
147
- process.once('SIGTERM', gracefulShutdown);
148
- process.once('SIGINT', gracefulShutdown);
239
+ for (const signal of SHUTDOWN_SIGNALS) {
240
+ process.once(signal, gracefulShutdown);
241
+ }
149
242
  console.log(`Hive running at http://127.0.0.1:${address.port}`);
150
243
  void maybePrintUpdateHint(versionService).catch(() => { });
151
244
  return {
@@ -155,7 +248,7 @@ export const runHiveCommand = async (argv, options = {}) => {
155
248
  };
156
249
  };
157
250
  const isMainModule = process.argv[1]
158
- ? fileURLToPath(import.meta.url) === realpathSync(process.argv[1])
251
+ ? sameFilesystemPath(fileURLToPath(import.meta.url), process.argv[1])
159
252
  : false;
160
253
  if (isMainModule) {
161
254
  const argv = process.argv.slice(2);
@@ -10,6 +10,7 @@ export interface ParsedReportArgs {
10
10
  }
11
11
  export declare const parseReportArgs: (args: string[], command?: string) => ParsedReportArgs;
12
12
  export declare const parseCancelArgs: (args: string[]) => ParsedCancelArgs;
13
+ export declare const decodeStdinBuffer: (buffer: Buffer) => string;
13
14
  export declare const readStdinToString: (command?: string) => Promise<string>;
14
15
  export declare const runTeamCommand: (argv: string[]) => Promise<void>;
15
16
  export {};