@tt-a1i/hive 1.4.4 → 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 (164) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +8 -0
  3. package/assets/qq-group.jpg +0 -0
  4. package/dist/bin/team.cmd +1 -0
  5. package/dist/src/cli/hive-update.d.ts +45 -17
  6. package/dist/src/cli/hive-update.js +63 -25
  7. package/dist/src/cli/hive.d.ts +25 -0
  8. package/dist/src/cli/hive.js +41 -3
  9. package/dist/src/cli/team.d.ts +1 -0
  10. package/dist/src/cli/team.js +199 -3
  11. package/dist/src/server/agent-command-resolver.js +3 -19
  12. package/dist/src/server/agent-manager-support.d.ts +2 -2
  13. package/dist/src/server/agent-manager-support.js +98 -24
  14. package/dist/src/server/agent-run-starter.d.ts +7 -1
  15. package/dist/src/server/agent-run-starter.js +9 -2
  16. package/dist/src/server/agent-run-store.d.ts +1 -1
  17. package/dist/src/server/agent-runtime-close.d.ts +1 -0
  18. package/dist/src/server/agent-runtime-close.js +25 -1
  19. package/dist/src/server/agent-runtime-contract.d.ts +2 -1
  20. package/dist/src/server/agent-runtime.d.ts +1 -1
  21. package/dist/src/server/agent-runtime.js +8 -2
  22. package/dist/src/server/agent-startup-instructions.d.ts +8 -1
  23. package/dist/src/server/agent-startup-instructions.js +15 -9
  24. package/dist/src/server/agent-stdin-dispatcher.d.ts +12 -5
  25. package/dist/src/server/agent-stdin-dispatcher.js +129 -40
  26. package/dist/src/server/cron-util.d.ts +7 -0
  27. package/dist/src/server/cron-util.js +19 -0
  28. package/dist/src/server/dispatch-ledger-store.d.ts +22 -0
  29. package/dist/src/server/dispatch-ledger-store.js +51 -3
  30. package/dist/src/server/env-sync-message.js +9 -9
  31. package/dist/src/server/fs-pick-folder.js +4 -0
  32. package/dist/src/server/fs-sandbox.js +36 -7
  33. package/dist/src/server/hive-team-guidance.d.ts +11 -6
  34. package/dist/src/server/hive-team-guidance.js +252 -71
  35. package/dist/src/server/live-run-registry.d.ts +1 -0
  36. package/dist/src/server/live-run-registry.js +1 -1
  37. package/dist/src/server/open-target-commands.js +5 -6
  38. package/dist/src/server/orchestrator-autostart.d.ts +12 -0
  39. package/dist/src/server/orchestrator-autostart.js +15 -13
  40. package/dist/src/server/path-canonicalization.d.ts +3 -0
  41. package/dist/src/server/path-canonicalization.js +29 -0
  42. package/dist/src/server/platform-path.d.ts +3 -0
  43. package/dist/src/server/platform-path.js +13 -0
  44. package/dist/src/server/post-start-input-writer.d.ts +1 -1
  45. package/dist/src/server/post-start-input-writer.js +110 -13
  46. package/dist/src/server/preset-launch-support.d.ts +1 -1
  47. package/dist/src/server/preset-launch-support.js +33 -2
  48. package/dist/src/server/recovery-summary.d.ts +6 -1
  49. package/dist/src/server/recovery-summary.js +17 -17
  50. package/dist/src/server/restart-policy-support.d.ts +6 -1
  51. package/dist/src/server/restart-policy-support.js +9 -1
  52. package/dist/src/server/restart-policy.d.ts +2 -2
  53. package/dist/src/server/restart-policy.js +3 -1
  54. package/dist/src/server/role-template-store.d.ts +1 -0
  55. package/dist/src/server/role-template-store.js +11 -1
  56. package/dist/src/server/route-types.d.ts +43 -0
  57. package/dist/src/server/routes-runtime.js +2 -1
  58. package/dist/src/server/routes-settings.js +76 -0
  59. package/dist/src/server/routes-team.js +211 -1
  60. package/dist/src/server/routes-workflow-schedules.d.ts +2 -0
  61. package/dist/src/server/routes-workflow-schedules.js +58 -0
  62. package/dist/src/server/routes-workflows.d.ts +2 -0
  63. package/dist/src/server/routes-workflows.js +83 -0
  64. package/dist/src/server/routes.js +4 -0
  65. package/dist/src/server/runtime-restart-policy.d.ts +3 -1
  66. package/dist/src/server/runtime-restart-policy.js +3 -1
  67. package/dist/src/server/runtime-store-contract.d.ts +122 -0
  68. package/dist/src/server/runtime-store-contract.js +1 -0
  69. package/dist/src/server/runtime-store-helpers.d.ts +9 -0
  70. package/dist/src/server/runtime-store-helpers.js +101 -2
  71. package/dist/src/server/runtime-store-workflows.d.ts +6 -0
  72. package/dist/src/server/runtime-store-workflows.js +100 -0
  73. package/dist/src/server/runtime-store.d.ts +3 -72
  74. package/dist/src/server/runtime-store.js +70 -4
  75. package/dist/src/server/session-capture-codex.d.ts +3 -3
  76. package/dist/src/server/session-capture-codex.js +9 -7
  77. package/dist/src/server/session-capture-gemini.d.ts +1 -1
  78. package/dist/src/server/session-capture-gemini.js +6 -3
  79. package/dist/src/server/settings-store.d.ts +3 -0
  80. package/dist/src/server/settings-store.js +1 -0
  81. package/dist/src/server/sqlite-schema-v19.d.ts +2 -0
  82. package/dist/src/server/sqlite-schema-v19.js +17 -0
  83. package/dist/src/server/sqlite-schema-v20.d.ts +2 -0
  84. package/dist/src/server/sqlite-schema-v20.js +20 -0
  85. package/dist/src/server/sqlite-schema-v21.d.ts +2 -0
  86. package/dist/src/server/sqlite-schema-v21.js +20 -0
  87. package/dist/src/server/sqlite-schema.d.ts +1 -1
  88. package/dist/src/server/sqlite-schema.js +97 -1
  89. package/dist/src/server/system-message.d.ts +7 -0
  90. package/dist/src/server/system-message.js +8 -1
  91. package/dist/src/server/tasks-file-watcher.d.ts +13 -1
  92. package/dist/src/server/tasks-file-watcher.js +127 -23
  93. package/dist/src/server/tasks-file.d.ts +2 -1
  94. package/dist/src/server/tasks-file.js +32 -9
  95. package/dist/src/server/tasks-websocket-server.js +13 -14
  96. package/dist/src/server/team-authz.d.ts +1 -1
  97. package/dist/src/server/team-authz.js +9 -1
  98. package/dist/src/server/team-autostaff.d.ts +16 -0
  99. package/dist/src/server/team-autostaff.js +16 -0
  100. package/dist/src/server/team-list-serializer.d.ts +1 -1
  101. package/dist/src/server/team-list-serializer.js +3 -1
  102. package/dist/src/server/team-operations.d.ts +15 -1
  103. package/dist/src/server/team-operations.js +116 -11
  104. package/dist/src/server/terminal-protocol.js +9 -3
  105. package/dist/src/server/terminal-stream-hub.js +16 -10
  106. package/dist/src/server/terminal-ws-server.js +10 -8
  107. package/dist/src/server/websocket-upgrade-safety.d.ts +10 -0
  108. package/dist/src/server/websocket-upgrade-safety.js +35 -0
  109. package/dist/src/server/windows-command-line.d.ts +3 -0
  110. package/dist/src/server/windows-command-line.js +9 -0
  111. package/dist/src/server/windows-filename.d.ts +2 -0
  112. package/dist/src/server/windows-filename.js +33 -0
  113. package/dist/src/server/workflow-cli-policy.d.ts +60 -0
  114. package/dist/src/server/workflow-cli-policy.js +110 -0
  115. package/dist/src/server/workflow-dispatch-awaiter.d.ts +12 -0
  116. package/dist/src/server/workflow-dispatch-awaiter.js +80 -0
  117. package/dist/src/server/workflow-feature.d.ts +15 -0
  118. package/dist/src/server/workflow-feature.js +15 -0
  119. package/dist/src/server/workflow-http-serializers.d.ts +64 -0
  120. package/dist/src/server/workflow-http-serializers.js +58 -0
  121. package/dist/src/server/workflow-run-log-store.d.ts +19 -0
  122. package/dist/src/server/workflow-run-log-store.js +45 -0
  123. package/dist/src/server/workflow-run-store.d.ts +50 -0
  124. package/dist/src/server/workflow-run-store.js +103 -0
  125. package/dist/src/server/workflow-runner.d.ts +147 -0
  126. package/dist/src/server/workflow-runner.js +401 -0
  127. package/dist/src/server/workflow-schedule-create.d.ts +14 -0
  128. package/dist/src/server/workflow-schedule-create.js +41 -0
  129. package/dist/src/server/workflow-schedule-store.d.ts +43 -0
  130. package/dist/src/server/workflow-schedule-store.js +112 -0
  131. package/dist/src/server/workflow-scheduler.d.ts +36 -0
  132. package/dist/src/server/workflow-scheduler.js +97 -0
  133. package/dist/src/server/workflow-script-loader.d.ts +34 -0
  134. package/dist/src/server/workflow-script-loader.js +106 -0
  135. package/dist/src/server/workspace-path-validation.js +16 -4
  136. package/dist/src/server/workspace-shell-runtime.d.ts +5 -0
  137. package/dist/src/server/workspace-shell-runtime.js +24 -2
  138. package/dist/src/server/workspace-store-contract.d.ts +4 -1
  139. package/dist/src/server/workspace-store-hydration.js +23 -7
  140. package/dist/src/server/workspace-store-mutations.js +2 -5
  141. package/dist/src/server/workspace-store-support.d.ts +4 -0
  142. package/dist/src/server/workspace-store-support.js +13 -1
  143. package/dist/src/server/workspace-store.js +38 -4
  144. package/dist/src/shared/types.d.ts +16 -1
  145. package/package.json +4 -2
  146. package/web/dist/assets/{AddWorkerDialog-DeZhTQLi.js → AddWorkerDialog-CcC-7kgG.js} +2 -2
  147. package/web/dist/assets/AddWorkspaceDialog-BDpOTfmt.js +1 -0
  148. package/web/dist/assets/{FirstRunWizard-B5wLcat5.js → FirstRunWizard-BYX_ocQn.js} +1 -1
  149. package/web/dist/assets/{MarketplaceDrawer-BC0eBOEW.js → MarketplaceDrawer-DUxSk7db.js} +1 -1
  150. package/web/dist/assets/WhatsNewDialog-B_RlCXcV.js +1 -0
  151. package/web/dist/assets/WorkerModal-D9-7YfZZ.js +1 -0
  152. package/web/dist/assets/WorkspaceTaskDrawer-BCKoF7qc.js +1 -0
  153. package/web/dist/assets/{WorkspaceTerminalPanels-CvibsPSd.js → WorkspaceTerminalPanels-Dq8y91t2.js} +1 -1
  154. package/web/dist/assets/index-BiOvKIVw.css +1 -0
  155. package/web/dist/assets/index-DMRUklT3.js +73 -0
  156. package/web/dist/assets/path-join-7MR1s7b1.js +1 -0
  157. package/web/dist/index.html +2 -2
  158. package/web/dist/sw.js +1 -1
  159. package/web/dist/assets/AddWorkspaceDialog-DDpXNEKf.js +0 -1
  160. package/web/dist/assets/WorkerModal-BwMHq-Bi.js +0 -1
  161. package/web/dist/assets/WorkspaceTaskDrawer-CxvT4nqs.js +0 -1
  162. package/web/dist/assets/index-BEsTmfrO.css +0 -1
  163. package/web/dist/assets/index-Ddb7bDN5.js +0 -75
  164. package/web/dist/assets/path-join-S7qkXQtP.js +0 -1
@@ -2,7 +2,7 @@ import type { IPty } from 'node-pty';
2
2
  import type { AgentRunRecord, AgentRunSnapshot } from './agent-manager.js';
3
3
  import type { PtyOutputBus } from './pty-output-bus.js';
4
4
  export declare const MAX_RUN_OUTPUT_LENGTH = 1000000;
5
- type ExecRunner = (cmd: string, args: readonly string[]) => void;
5
+ type ExecRunner = (cmd: string, args: readonly string[], done: (success: boolean) => void) => void;
6
6
  /**
7
7
  * Windows analogue of POSIX `process.kill(-pgid, SIGKILL)`. node-pty on
8
8
  * Windows hands `pty.kill()` to TerminateProcess against the PTY's main
@@ -28,7 +28,7 @@ type ExecRunner = (cmd: string, args: readonly string[]) => void;
28
28
  * Exported for unit testing — the `runner` parameter lets tests assert
29
29
  * the exact argv without mocking node:child_process.
30
30
  */
31
- export declare const taskkillProcessTree: (pid: number, platform?: NodeJS.Platform, runner?: ExecRunner) => boolean;
31
+ export declare const taskkillProcessTree: (pid: number, platform?: NodeJS.Platform, runner?: ExecRunner, onFailure?: () => void) => boolean;
32
32
  export declare const toAgentRunSnapshot: (run: AgentRunRecord) => AgentRunSnapshot;
33
33
  export declare const finishAgentRun: (run: AgentRunRecord, exitCode: number | null, ptyOutputBus: PtyOutputBus) => void;
34
34
  export declare const attachAgentPty: (run: AgentRunRecord, pty: IPty, ptyOutputBus: PtyOutputBus) => void;
@@ -1,8 +1,23 @@
1
- import { execFileSync } from 'node:child_process';
1
+ import { execFile, execFileSync } from 'node:child_process';
2
2
  export const MAX_RUN_OUTPUT_LENGTH = 1_000_000;
3
3
  const FORCE_KILL_DELAY_MS = 750;
4
- const defaultExecRunner = (cmd, args) => {
5
- execFileSync(cmd, [...args], { stdio: 'ignore', windowsHide: true });
4
+ const TASKKILL_TIMEOUT_MS = 3000;
5
+ const PTY_READ_EOF_EXIT_GRACE_MS = 1000;
6
+ const isPtyReadEofError = (error) => {
7
+ const candidate = error;
8
+ return process.platform !== 'win32' && candidate?.code === 'EIO' && candidate.syscall === 'read';
9
+ };
10
+ const serializePtyInput = (input) => Buffer.isBuffer(input) ? input.toString('latin1') : input;
11
+ const defaultExecRunner = (cmd, args, done) => {
12
+ let settled = false;
13
+ const settle = (success) => {
14
+ if (settled)
15
+ return;
16
+ settled = true;
17
+ done(success);
18
+ };
19
+ const child = execFile(cmd, [...args], { maxBuffer: 64 * 1024, timeout: TASKKILL_TIMEOUT_MS, windowsHide: true }, (error) => settle(!error));
20
+ child.once('error', () => settle(false));
6
21
  };
7
22
  /**
8
23
  * Windows analogue of POSIX `process.kill(-pgid, SIGKILL)`. node-pty on
@@ -29,11 +44,14 @@ const defaultExecRunner = (cmd, args) => {
29
44
  * Exported for unit testing — the `runner` parameter lets tests assert
30
45
  * the exact argv without mocking node:child_process.
31
46
  */
32
- export const taskkillProcessTree = (pid, platform = process.platform, runner = defaultExecRunner) => {
47
+ export const taskkillProcessTree = (pid, platform = process.platform, runner = defaultExecRunner, onFailure) => {
33
48
  if (platform !== 'win32' || pid <= 0)
34
49
  return false;
35
50
  try {
36
- runner('taskkill', ['/pid', String(pid), '/t', '/f']);
51
+ runner('taskkill', ['/pid', String(pid), '/t', '/f'], (success) => {
52
+ if (!success)
53
+ onFailure?.();
54
+ });
37
55
  return true;
38
56
  }
39
57
  catch {
@@ -61,6 +79,7 @@ export const finishAgentRun = (run, exitCode, ptyOutputBus) => {
61
79
  export const attachAgentPty = (run, pty, ptyOutputBus) => {
62
80
  let stdinClosed = false;
63
81
  let forceKillTimer;
82
+ let ptyReadEofTimer;
64
83
  const resolveProcessGroupId = () => {
65
84
  if (process.platform === 'win32' || pty.pid <= 0)
66
85
  return null;
@@ -99,26 +118,29 @@ export const attachAgentPty = (run, pty, ptyOutputBus) => {
99
118
  ignoreBestEffortGroupKillError(error);
100
119
  }
101
120
  };
102
- const killPty = (signal) => {
121
+ const killPtyDirect = (signal) => {
103
122
  try {
104
- if (process.platform === 'win32') {
105
- // taskkill /pid <pid> /t /f walks the parent's process tree
106
- // BEFORE terminating it — so we have to run it while the parent
107
- // is still alive. Calling pty.kill() first (the previous
108
- // ordering) detaches the children: taskkill /T then fails with
109
- // "process not found" and the npm-installs / build scripts
110
- // become orphans. taskkill /f also terminates the parent, so
111
- // pty.kill() is the fallback for the rare case where taskkill
112
- // is missing from PATH or refused (e.g. restricted PowerShell).
113
- if (!taskkillProcessTree(pty.pid))
114
- pty.kill();
115
- }
116
- else
117
- pty.kill(signal);
123
+ pty.kill(signal);
118
124
  }
119
125
  catch (error) {
120
126
  ignoreMissingProcess(error);
121
127
  }
128
+ };
129
+ const killPty = (signal) => {
130
+ if (process.platform === 'win32') {
131
+ // taskkill /pid <pid> /t /f walks the parent's process tree
132
+ // BEFORE terminating it — so we have to run it while the parent
133
+ // is still alive. Calling pty.kill() first (the previous
134
+ // ordering) detaches the children: taskkill /T then fails with
135
+ // "process not found" and the npm-installs / build scripts
136
+ // become orphans. taskkill /f also terminates the parent, so
137
+ // pty.kill() is the fallback for the rare case where taskkill
138
+ // is missing from PATH or refused (e.g. restricted PowerShell).
139
+ if (!taskkillProcessTree(pty.pid, process.platform, defaultExecRunner, () => killPtyDirect()))
140
+ killPtyDirect();
141
+ }
142
+ else
143
+ killPtyDirect(signal);
122
144
  killProcessGroup(signal);
123
145
  };
124
146
  const clearForceKillTimer = () => {
@@ -127,8 +149,34 @@ export const attachAgentPty = (run, pty, ptyOutputBus) => {
127
149
  clearTimeout(forceKillTimer);
128
150
  forceKillTimer = undefined;
129
151
  };
152
+ const clearPtyReadEofTimer = () => {
153
+ if (!ptyReadEofTimer)
154
+ return;
155
+ clearTimeout(ptyReadEofTimer);
156
+ ptyReadEofTimer = undefined;
157
+ };
158
+ const schedulePtyReadEofExitGuard = (error) => {
159
+ if (ptyReadEofTimer)
160
+ return;
161
+ ptyReadEofTimer = setTimeout(() => {
162
+ ptyReadEofTimer = undefined;
163
+ if (stopped())
164
+ return;
165
+ console.error(`[hive] PTY read EOF without exit for run ${run.runId}`, error);
166
+ finishAgentRun(run, null, ptyOutputBus);
167
+ try {
168
+ killPty('SIGTERM');
169
+ scheduleForceKill();
170
+ }
171
+ catch (killError) {
172
+ ignoreMissingProcess(killError);
173
+ }
174
+ }, PTY_READ_EOF_EXIT_GRACE_MS);
175
+ ptyReadEofTimer.unref?.();
176
+ };
130
177
  const cleanupProcessGroup = () => {
131
178
  clearForceKillTimer();
179
+ clearPtyReadEofTimer();
132
180
  killProcessGroup('SIGKILL');
133
181
  };
134
182
  const scheduleForceKill = () => {
@@ -141,11 +189,11 @@ export const attachAgentPty = (run, pty, ptyOutputBus) => {
141
189
  // Same ordering as killPty(): tree-kill before terminating the
142
190
  // parent, so taskkill /T can still enumerate the process tree.
143
191
  // pty.kill() is the fallback for taskkill-missing hosts.
144
- if (!taskkillProcessTree(pty.pid))
145
- pty.kill();
192
+ if (!taskkillProcessTree(pty.pid, process.platform, defaultExecRunner, () => killPtyDirect()))
193
+ killPtyDirect();
146
194
  }
147
195
  else
148
- pty.kill('SIGKILL');
196
+ killPtyDirect('SIGKILL');
149
197
  }
150
198
  catch (error) {
151
199
  ignoreMissingProcess(error);
@@ -163,6 +211,9 @@ export const attachAgentPty = (run, pty, ptyOutputBus) => {
163
211
  },
164
212
  pid: pty.pid,
165
213
  resize(cols, rows) {
214
+ if (!Number.isInteger(cols) || !Number.isInteger(rows) || cols <= 0 || rows <= 0) {
215
+ throw new Error(`Invalid terminal size for run: ${run.runId}`);
216
+ }
166
217
  pty.resize(cols, rows);
167
218
  },
168
219
  resume() {
@@ -173,6 +224,7 @@ export const attachAgentPty = (run, pty, ptyOutputBus) => {
173
224
  cleanupProcessGroup();
174
225
  return;
175
226
  }
227
+ clearPtyReadEofTimer();
176
228
  killPty('SIGTERM');
177
229
  stdinClosed = true;
178
230
  scheduleForceKill();
@@ -181,7 +233,7 @@ export const attachAgentPty = (run, pty, ptyOutputBus) => {
181
233
  if (stdinClosed || run.status === 'exited' || run.status === 'error') {
182
234
  throw new Error(`PTY is not active for run: ${run.runId}`);
183
235
  }
184
- pty.write(text);
236
+ pty.write(serializePtyInput(text));
185
237
  },
186
238
  };
187
239
  pty.onData((chunk) => {
@@ -192,8 +244,30 @@ export const attachAgentPty = (run, pty, ptyOutputBus) => {
192
244
  run.output = run.output.slice(-MAX_RUN_OUTPUT_LENGTH);
193
245
  ptyOutputBus.publish(run.runId, chunk);
194
246
  });
247
+ pty.on?.('error', (error) => {
248
+ if (stopped())
249
+ return;
250
+ if (isPtyReadEofError(error)) {
251
+ // Unix PTYs can surface a closed slave as read/EIO just before
252
+ // node-pty delivers the real onExit event. Treat it as EOF, not
253
+ // as the run's terminal status.
254
+ stdinClosed = true;
255
+ schedulePtyReadEofExitGuard(error);
256
+ return;
257
+ }
258
+ console.error(`[hive] PTY error for run ${run.runId}`, error);
259
+ stdinClosed = true;
260
+ finishAgentRun(run, null, ptyOutputBus);
261
+ try {
262
+ killPty('SIGTERM');
263
+ }
264
+ catch (killError) {
265
+ ignoreMissingProcess(killError);
266
+ }
267
+ });
195
268
  pty.onExit((event) => {
196
269
  stdinClosed = true;
270
+ clearPtyReadEofTimer();
197
271
  cleanupProcessGroup();
198
272
  finishAgentRun(run, event.exitCode, ptyOutputBus);
199
273
  });
@@ -18,6 +18,12 @@ interface AgentRunStarterInput {
18
18
  getCommandPreset: (id: string) => CommandPresetRecord | undefined;
19
19
  getAgent: ((workspaceId: string, agentId: string) => AgentSummary | undefined) | undefined;
20
20
  restartPolicy: RestartPolicy;
21
+ /** Experimental workflow gate — gates the `team workflow` line + the
22
+ * workflow-authoring rule in the orchestrator's startup prompt. */
23
+ getWorkflowsEnabled?: () => boolean;
24
+ /** Experimental auto-staff gate (default on) — gates the team-sizing rule
25
+ * in the orchestrator's startup prompt. */
26
+ getAutostaffEnabled?: () => boolean;
21
27
  }
22
- export declare const createAgentRunStarter: ({ agentManager, registry, onAgentExit, store, sessionStore, tokenRegistry, getCommandPreset, getAgent, restartPolicy, }: AgentRunStarterInput) => (workspace: WorkspaceSummary, agentId: string, config: AgentLaunchConfigInput, hivePort: string) => Promise<LiveAgentRun>;
28
+ export declare const createAgentRunStarter: ({ agentManager, registry, onAgentExit, store, sessionStore, tokenRegistry, getCommandPreset, getAgent, restartPolicy, getWorkflowsEnabled, getAutostaffEnabled, }: AgentRunStarterInput) => (workspace: WorkspaceSummary, agentId: string, config: AgentLaunchConfigInput, hivePort: string) => Promise<LiveAgentRun>;
23
29
  export {};
@@ -2,7 +2,7 @@ import { buildAgentRunBootstrap, startAgentRunCapture } from './agent-run-bootst
2
2
  import { handleAgentRunExit } from './agent-run-exit-handler.js';
3
3
  import { buildAgentStartupInstructions } from './agent-startup-instructions.js';
4
4
  import { createPostStartInputWriter, isInteractiveAgentCommand } from './post-start-input-writer.js';
5
- export const createAgentRunStarter = ({ agentManager, registry, onAgentExit, store, sessionStore, tokenRegistry, getCommandPreset, getAgent, restartPolicy, }) => async (workspace, agentId, config, hivePort) => {
5
+ export const createAgentRunStarter = ({ agentManager, registry, onAgentExit, store, sessionStore, tokenRegistry, getCommandPreset, getAgent, restartPolicy, getWorkflowsEnabled, getAutostaffEnabled, }) => async (workspace, agentId, config, hivePort) => {
6
6
  if (!agentManager)
7
7
  throw new Error('Agent manager is required to start agents');
8
8
  const agent = getAgent?.(workspace.id, agentId);
@@ -99,7 +99,14 @@ export const createAgentRunStarter = ({ agentManager, registry, onAgentExit, sto
99
99
  !injectedRestartMessage &&
100
100
  agent &&
101
101
  isInteractiveAgentCommand(startConfig.interactiveCommand ?? startConfig.command)) {
102
- postStartWriter(run.runId, buildAgentStartupInstructions({ agent, workspace }));
102
+ void postStartWriter(run.runId, buildAgentStartupInstructions({
103
+ agent,
104
+ workspace,
105
+ workflowsEnabled: getWorkflowsEnabled?.() ?? false,
106
+ autostaffEnabled: getAutostaffEnabled?.() ?? false,
107
+ })).catch(() => {
108
+ // The agent may have exited before post-start guidance could be written.
109
+ });
103
110
  }
104
111
  }
105
112
  catch {
@@ -27,7 +27,7 @@ export declare const createAgentRunStore: (db: Database) => {
27
27
  runId: string;
28
28
  agentId: string;
29
29
  pid: number | null;
30
- status: "error" | "starting" | "running" | "exited";
30
+ status: "starting" | "running" | "exited" | "error";
31
31
  exitCode: number | null;
32
32
  startedAt: number;
33
33
  endedAt: number | null;
@@ -1,4 +1,5 @@
1
1
  import type { AgentManager } from './agent-manager.js';
2
2
  import type { LiveAgentRun } from './agent-runtime-types.js';
3
3
  import type { LiveRunRegistry } from './live-run-registry.js';
4
+ export declare const AGENT_RUNTIME_CLOSE_TIMEOUT_MS = 5000;
4
5
  export declare const closeAgentRuntime: (agentManager: AgentManager | undefined, registry: LiveRunRegistry, syncRun: (run: LiveAgentRun) => LiveAgentRun) => Promise<void>;
@@ -1,10 +1,34 @@
1
+ export const AGENT_RUNTIME_CLOSE_TIMEOUT_MS = 5000;
2
+ const waitForExitEntries = async (entries, timeoutMs = AGENT_RUNTIME_CLOSE_TIMEOUT_MS) => {
3
+ if (entries.length === 0)
4
+ return;
5
+ let timer;
6
+ const timeout = new Promise((resolve) => {
7
+ timer = setTimeout(() => resolve('timeout'), timeoutMs);
8
+ timer.unref?.();
9
+ });
10
+ try {
11
+ const result = await Promise.race([
12
+ Promise.all(entries.map((entry) => entry.promise)).then(() => 'done'),
13
+ timeout,
14
+ ]);
15
+ if (result === 'timeout') {
16
+ const runIds = entries.map((entry) => entry.runId).join(', ');
17
+ console.error(`[hive] timed out waiting for agent exit during shutdown: ${runIds}`);
18
+ }
19
+ }
20
+ finally {
21
+ if (timer)
22
+ clearTimeout(timer);
23
+ }
24
+ };
1
25
  export const closeAgentRuntime = async (agentManager, registry, syncRun) => {
2
26
  const runs = registry.list();
3
27
  for (const run of runs) {
4
28
  syncRun(run);
5
29
  agentManager?.stopRun(run.runId);
6
30
  }
7
- await Promise.all(registry.listExitEntries().map((entry) => entry.promise));
31
+ await waitForExitEntries(registry.listExitEntries());
8
32
  for (const run of registry.list()) {
9
33
  agentManager?.removeRun(run.runId);
10
34
  registry.remove(run.runId);
@@ -28,10 +28,11 @@ export interface AgentRuntime {
28
28
  writeStatusPrompt: (workspaceId: string, workerName: string, workerId: string, text: string, artifacts: string[], input?: {
29
29
  requireActiveRun?: boolean;
30
30
  }) => void;
31
- writeSendPrompt: (workspaceId: string, workerId: string, dispatchId: string, fromAgentName: string, workerDescription: string, text: string) => void;
31
+ writeSendPrompt: (workspaceId: string, workerId: string, dispatchId: string, fromAgentName: string, workerDescription: string, text: string) => Promise<void>;
32
32
  writeCancelPrompt: (workspaceId: string, workerId: string, dispatchId: string, reason: string, input?: {
33
33
  requireActiveRun?: boolean;
34
34
  }) => void;
35
35
  writeUserInputPrompt: (workspaceId: string, text: string) => void;
36
+ writeSystemMessageToAgent: (workspaceId: string, agentId: string, text: string) => void;
36
37
  }
37
38
  export type { StartAgentOptions };
@@ -4,5 +4,5 @@ import type { AgentRuntime } from './agent-runtime-contract.js';
4
4
  import type { AgentRunStorePort, AgentSessionStorePort } from './agent-runtime-ports.js';
5
5
  import type { CommandPresetRecord } from './command-preset-store.js';
6
6
  import { type RestartPolicy } from './restart-policy.js';
7
- export declare const createAgentRuntime: (agentManager: AgentManager | undefined, agentRunStore: AgentRunStorePort, sessionStore: AgentSessionStorePort, getCommandPreset: (id: string) => CommandPresetRecord | undefined, onAgentExit: (workspaceId: string, agentId: string) => void, restartPolicy?: RestartPolicy, getAgent?: (workspaceId: string, agentId: string) => AgentSummary | undefined) => AgentRuntime;
7
+ export declare const createAgentRuntime: (agentManager: AgentManager | undefined, agentRunStore: AgentRunStorePort, sessionStore: AgentSessionStorePort, getCommandPreset: (id: string) => CommandPresetRecord | undefined, onAgentExit: (workspaceId: string, agentId: string) => void, restartPolicy?: RestartPolicy, getAgent?: (workspaceId: string, agentId: string) => AgentSummary | undefined, getWorkflowsEnabled?: () => boolean, getAutostaffEnabled?: () => boolean) => AgentRuntime;
8
8
  export type { AgentRuntime };
@@ -10,7 +10,7 @@ import { createAgentStdinDispatcher } from './agent-stdin-dispatcher.js';
10
10
  import { createAgentTokenRegistry } from './agent-tokens.js';
11
11
  import { createLiveRunRegistry } from './live-run-registry.js';
12
12
  import { createNoopRestartPolicy } from './restart-policy.js';
13
- export const createAgentRuntime = (agentManager, agentRunStore, sessionStore, getCommandPreset, onAgentExit, restartPolicy = createNoopRestartPolicy(), getAgent) => {
13
+ export const createAgentRuntime = (agentManager, agentRunStore, sessionStore, getCommandPreset, onAgentExit, restartPolicy = createNoopRestartPolicy(), getAgent, getWorkflowsEnabled, getAutostaffEnabled) => {
14
14
  const registry = createLiveRunRegistry();
15
15
  const launchCache = createAgentLaunchCache(agentRunStore);
16
16
  const tokenRegistry = createAgentTokenRegistry();
@@ -29,6 +29,7 @@ export const createAgentRuntime = (agentManager, agentRunStore, sessionStore, ge
29
29
  getWorkspaceId: launchCache.getWorkspaceId,
30
30
  registry,
31
31
  syncRun,
32
+ ...(getWorkflowsEnabled ? { getWorkflowsEnabled } : {}),
32
33
  });
33
34
  const startLiveRun = createAgentRunStarter({
34
35
  agentManager,
@@ -40,6 +41,8 @@ export const createAgentRuntime = (agentManager, agentRunStore, sessionStore, ge
40
41
  getCommandPreset,
41
42
  getAgent,
42
43
  restartPolicy,
44
+ ...(getWorkflowsEnabled ? { getWorkflowsEnabled } : {}),
45
+ ...(getAutostaffEnabled ? { getAutostaffEnabled } : {}),
43
46
  });
44
47
  return {
45
48
  async close() {
@@ -113,7 +116,7 @@ export const createAgentRuntime = (agentManager, agentRunStore, sessionStore, ge
113
116
  stdinDispatcher.writeStatusPrompt(workspaceId, workerName, text, artifacts, input);
114
117
  },
115
118
  writeSendPrompt(workspaceId, workerId, dispatchId, fromAgentName, workerDescription, text) {
116
- stdinDispatcher.writeSendPrompt(workspaceId, workerId, dispatchId, fromAgentName, workerDescription, text);
119
+ return stdinDispatcher.writeSendPrompt(workspaceId, workerId, dispatchId, fromAgentName, workerDescription, text);
117
120
  },
118
121
  writeCancelPrompt(workspaceId, workerId, dispatchId, reason, input = {}) {
119
122
  stdinDispatcher.writeCancelPrompt(workspaceId, workerId, dispatchId, reason, input);
@@ -121,5 +124,8 @@ export const createAgentRuntime = (agentManager, agentRunStore, sessionStore, ge
121
124
  writeUserInputPrompt(workspaceId, text) {
122
125
  stdinDispatcher.writeUserInputPrompt(workspaceId, text);
123
126
  },
127
+ writeSystemMessageToAgent(workspaceId, agentId, text) {
128
+ stdinDispatcher.writeSystemMessageToAgent(workspaceId, agentId, text);
129
+ },
124
130
  };
125
131
  };
@@ -7,7 +7,14 @@ export declare const buildAgentLegacyIdentityMarker: ({ agent, workspace, }: {
7
7
  agent: AgentSummary;
8
8
  workspace: WorkspaceSummary;
9
9
  }) => string;
10
- export declare const buildAgentStartupInstructions: ({ agent, workspace, }: {
10
+ export declare const buildAgentStartupInstructions: ({ agent, workspace, workflowsEnabled, autostaffEnabled, }: {
11
11
  agent: AgentSummary;
12
12
  workspace: WorkspaceSummary;
13
+ /** Experimental workflow gate. When off, the startup prompt omits the
14
+ * `team workflow` command + the workflow-authoring rule so a fresh
15
+ * orchestrator isn't told to run a command the runtime rejects. */
16
+ workflowsEnabled?: boolean;
17
+ /** Experimental auto-staff gate (default on). When on, the orchestrator is
18
+ * told it may size the team to the task up front. */
19
+ autostaffEnabled?: boolean;
13
20
  }) => string;
@@ -1,25 +1,31 @@
1
1
  import { getHiveTeamRules } from './hive-team-guidance.js';
2
2
  import { TASKS_RELATIVE_PATH } from './tasks-file.js';
3
3
  export const buildAgentSessionBindingMarker = ({ agent, workspace, }) => `Hive session binding: workspace_id=${workspace.id}; agent_id=${agent.id}`;
4
- export const buildAgentLegacyIdentityMarker = ({ agent, workspace, }) => `你是 ${workspace.name} ${agent.name}(${agent.role})。`;
5
- export const buildAgentStartupInstructions = ({ agent, workspace, }) => {
4
+ export const buildAgentLegacyIdentityMarker = ({ agent, workspace, }) => `You are ${agent.name} (${agent.role}) in workspace ${workspace.name}.`;
5
+ export const buildAgentStartupInstructions = ({ agent, workspace, workflowsEnabled = false, autostaffEnabled = false, }) => {
6
6
  const lines = [
7
- '[Hive 系统消息:启动说明]',
7
+ '<hive-message kind="startup">',
8
8
  '',
9
9
  buildAgentLegacyIdentityMarker({ agent, workspace }),
10
- `当前 workspace: ${workspace.name}`,
11
- `项目路径: ${workspace.path}`,
10
+ `Current workspace: ${workspace.name}`,
11
+ `Project path: ${workspace.path}`,
12
12
  buildAgentSessionBindingMarker({ agent, workspace }),
13
13
  '',
14
- `你的角色:${agent.description}`,
14
+ // agent.description is the role contract — it follows the user's chosen
15
+ // language / custom edit and is intentionally left as authored.
16
+ `Your role: ${agent.description}`,
15
17
  '',
16
18
  ];
17
19
  if (agent.role === 'orchestrator') {
18
- lines.push('你的职责:', '- 直接响应 user,澄清需求并拆解任务', `- 维护 ${TASKS_RELATIVE_PATH}`, '- worker 名称派单,并根据汇报推进下一步', '', '可用 team 命令:', '- team list', '- team send <worker-name> "<task>"', '- team cancel --dispatch <id> "<reason>"', '', '派单时必须使用 worker name,不要使用 worker id。', '取消未完成派单时必须使用 dispatch id。', '', 'Hive worker 派单规则:', ...getHiveTeamRules(agent));
20
+ lines.push('Your responsibilities:', '- Respond to the user directly; clarify the goal and break it into dispatchable tasks', `- Maintain ${TASKS_RELATIVE_PATH}`, '- Dispatch by worker name and drive the next step from each report', '', 'Available team commands:', '- team list', '- team send "<worker-name>" "<task>"', '- team spawn <role> [--name <n>] [--cli claude|codex|opencode|gemini] [--ephemeral]', '- team cancel --dispatch <id> "<reason>"', ...(workflowsEnabled
21
+ ? [
22
+ '- team workflow run --stdin (fan-out / staged work — see .hive/PROTOCOL.md for the DSL)',
23
+ ]
24
+ : []), '', 'Always dispatch by worker name, never by worker id.', 'Always cancel an open dispatch by its dispatch id.', '', 'Hive worker dispatch rules:', ...getHiveTeamRules(agent, workflowsEnabled, autostaffEnabled));
19
25
  }
20
26
  else {
21
- lines.push('可用 team 命令:', '- team report "<完整汇报>" [--dispatch <id>] [--artifact <path>] 完成/失败/阻塞汇报', '- team report --stdin [--dispatch <id>] [--artifact <path>] 同上,从 stdin 读正文(适合多行/含引号/特殊字符)', '- team status "<当前状态>" [--artifact <path>] 中段进度/待命/接入状态', '- team status --stdin [--artifact <path>] 同上,从 stdin 读正文', '- team list 查看 workspace 内的 worker(含状态)', '- team --help 仅查命令用法;**不是**汇报手段', '', '语法要点:', '- 正文是第一个 positional argumentflag 顺序任意:`team report "结论" --dispatch X` `team report --dispatch X "结论"` 都成立。', '- 长正文(多行 / 含引号 / shell 特殊字符)一律走 `--stdin`,通过你所在 shell 的管道喂给它(POSIX quoted heredoc `<<\'EOF\' … EOF` 防止 $var/反引号展开,Windows cmd `type body.txt |` 或临时文件 `< body.txt`)。`--stdin` 自己只负责"从 stdin 读",不依赖任何 shell 语法。', '- CLI 报错会同时打印 USAGE,可直接对照修正参数。', '', '完成任务后必须执行 `team report "<结论>"`。', '失败、阻塞或部分完成也用 `team report "<当前状态与原因>"` 汇报。', '没有进行中的任务时,用 `team status "<当前状态>"` 汇报接入、待命或阻塞状态。', '不要调用 team send;worker 之间不能直接派单。', '', 'Hive worker 边界:', ...getHiveTeamRules(agent));
27
+ lines.push('Available team commands:', '- team report "<result>" [--dispatch <id>] [--artifact <path>] report done / failed / blocked', '- team report --stdin [--dispatch <id>] [--artifact <path>] same, body read from stdin (multi-line / quotes / special chars)', '- team status "<state>" [--artifact <path>] mid-task progress / standby / connected status', '- team status --stdin [--artifact <path>] same, body read from stdin', '- team list list the workspace workers (with status)', '- team --help command syntax only; NOT a way to report', '', 'Syntax notes:', '- The body is the first positional argument; flag order is free: `team report "result" --dispatch X` and `team report --dispatch X "result"` are both valid.', "- Long bodies (multi-line / quotes / shell metacharacters) always go through `--stdin`, piped in via your shell (POSIX: a quoted heredoc `<<'EOF' … EOF` to stop $var / backtick expansion; Windows cmd: `type body.txt |` or `< body.txt`). `--stdin` only reads from stdin and relies on no shell syntax of its own.", '- On error the CLI also prints USAGE — fix the arguments against it.', '', 'When a task is done you MUST run `team report "<result>"`.', 'Failure, blocked, or partial completion are also reported with `team report "<current state and reason>"`.', 'When no task is in progress, report connection / standby / blocked state with `team status "<state>"`.', 'Do not call team send; workers cannot dispatch to each other.', '', 'Hive worker boundaries:', ...getHiveTeamRules(agent, workflowsEnabled, autostaffEnabled));
22
28
  }
23
- lines.push('');
29
+ lines.push('', '</hive-message>', '');
24
30
  return lines.join('\n');
25
31
  };
@@ -8,23 +8,30 @@ interface AgentStdinDispatcherInput {
8
8
  getWorkspaceId: (agentId: string) => string | undefined;
9
9
  registry: LiveRunRegistry;
10
10
  syncRun: (run: LiveAgentRun) => LiveAgentRun;
11
+ /** Whether the experimental workflow feature is on. Controls whether the
12
+ * orchestrator reminder tail mentions `team workflow`. Defaults to off. */
13
+ getWorkflowsEnabled?: () => boolean;
11
14
  }
12
- export declare const buildOrchestratorReportPayload: (workerName: string, text: string, artifacts: string[]) => string;
13
- export declare const buildOrchestratorStatusPayload: (workerName: string, text: string, artifacts: string[]) => string;
14
- export declare const buildOrchestratorUserInputPayload: (text: string) => string;
15
+ export declare const buildOrchestratorReportPayload: (workerName: string, text: string, artifacts: string[], workflowsEnabled?: boolean) => string;
16
+ export declare const buildOrchestratorStatusPayload: (workerName: string, text: string, artifacts: string[], workflowsEnabled?: boolean) => string;
17
+ export declare const buildOrchestratorUserInputPayload: (text: string, workflowsEnabled?: boolean) => string;
15
18
  export declare const buildWorkerDispatchPayload: (fromAgentName: string, workerDescription: string, dispatchId: string, text: string) => string;
16
19
  export declare const buildWorkerCancelPayload: (dispatchId: string, reason: string) => string;
17
- export declare const createAgentStdinDispatcher: ({ agentManager, getLaunchConfig, getWorkspaceId, registry, syncRun, }: AgentStdinDispatcherInput) => {
20
+ export declare const createAgentStdinDispatcher: ({ agentManager, getLaunchConfig, getWorkspaceId, registry, syncRun, getWorkflowsEnabled, }: AgentStdinDispatcherInput) => {
18
21
  writeReportPrompt(workspaceId: string, workerName: string, text: string, artifacts: string[], input?: {
19
22
  requireActiveRun?: boolean;
20
23
  }): void;
21
24
  writeStatusPrompt(workspaceId: string, workerName: string, text: string, artifacts: string[], input?: {
22
25
  requireActiveRun?: boolean;
23
26
  }): void;
24
- writeSendPrompt(workspaceId: string, workerId: string, dispatchId: string, fromAgentName: string, workerDescription: string, text: string): void;
27
+ writeSendPrompt(workspaceId: string, workerId: string, dispatchId: string, fromAgentName: string, workerDescription: string, text: string): Promise<void>;
25
28
  writeCancelPrompt(workspaceId: string, workerId: string, dispatchId: string, reason: string, input?: {
26
29
  requireActiveRun?: boolean;
27
30
  }): void;
28
31
  writeUserInputPrompt(workspaceId: string, text: string): void;
32
+ /** Generic: deliver an opaque text block to a specific agent's PTY.
33
+ * Used by the workflow runner to notify the triggering orchestrator
34
+ * when a run finishes (mirrors Claude Code's <task-notification>). */
35
+ writeSystemMessageToAgent(workspaceId: string, agentId: string, text: string): void;
29
36
  };
30
37
  export {};