@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
@@ -1,4 +1,4 @@
1
- import { delimiter, dirname, resolve, sep } from 'node:path';
1
+ import { dirname, posix, resolve, sep, win32 } from 'node:path';
2
2
  import { fileURLToPath } from 'node:url';
3
3
  import { buildAgentLegacyIdentityMarker, buildAgentSessionBindingMarker, } from './agent-startup-instructions.js';
4
4
  import { withPresetResumeArgs } from './preset-launch-support.js';
@@ -12,6 +12,34 @@ const resolveHiveBinDir = () => {
12
12
  };
13
13
  const HIVE_BIN_DIR = resolveHiveBinDir();
14
14
  const SESSION_CAPTURE_TIMEOUT_MS = 30_000;
15
+ /**
16
+ * Builds a `{ <PATH-key>: <new-value> }` object for the spawn env override.
17
+ * Critical on Windows: the OS env block reports PATH under its native casing
18
+ * (typically `Path`). Writing to a literal `PATH` key would, after spread
19
+ * with `process.env`, leave two entries — `Path` carrying the original value
20
+ * and `PATH` carrying our prepend. CreateProcess then sees both and the
21
+ * effective lookup order is undefined; in practice the child PTY often falls
22
+ * back to the original `Path` and never sees `HIVE_BIN_DIR`, breaking every
23
+ * `team` shim resolution.
24
+ *
25
+ * We detect the existing key (case-insensitive on Windows) and overwrite IT,
26
+ * so the merge produces exactly one PATH entry.
27
+ *
28
+ * Exported for unit testing — `buildAgentRunBootstrap` is the only in-tree
29
+ * caller.
30
+ */
31
+ export const buildSpawnPathEnvEntry = (parentEnv, hiveBinDir, platform) => {
32
+ const existingKey = platform === 'win32'
33
+ ? Object.keys(parentEnv).find((key) => key.toLowerCase() === 'path')
34
+ : undefined;
35
+ const key = existingKey ?? 'PATH';
36
+ const existingValue = existingKey ? parentEnv[existingKey] : parentEnv.PATH;
37
+ // Target platform's delimiter — Windows uses `;`, POSIX `:` — independent
38
+ // of where this function is running (tests on macOS verify the win32 path).
39
+ const platformDelimiter = platform === 'win32' ? win32.delimiter : posix.delimiter;
40
+ const value = existingValue ? `${hiveBinDir}${platformDelimiter}${existingValue}` : hiveBinDir;
41
+ return { [key]: value };
42
+ };
15
43
  const resolveLaunchPreset = (config, getCommandPreset) => {
16
44
  if (config.presetAugmentationDisabled)
17
45
  return undefined;
@@ -52,7 +80,7 @@ export const buildAgentRunBootstrap = (workspace, agentId, config, sessionStore,
52
80
  HIVE_PROJECT_ID: workspace.id,
53
81
  HIVE_AGENT_ID: agentId,
54
82
  HIVE_AGENT_TOKEN: '',
55
- PATH: `${HIVE_BIN_DIR}${delimiter}${process.env.PATH ?? ''}`,
83
+ ...buildSpawnPathEnvEntry(process.env, HIVE_BIN_DIR, process.platform),
56
84
  },
57
85
  };
58
86
  };
@@ -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 特殊字符 / heredoc)一律走 `--stdin`,并用 *quoted* heredoc(`<<'EOF'`)防止 shell 展开 $vars / 反引号 / 命令替换:", " 例:`team report --stdin --dispatch <id> <<'EOF'`", ' `... 长报告(含 $VAR、`backtick`、"引号" 都按字面量保留)...`', ' `EOF`', '- 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 {};
@@ -1,91 +1,180 @@
1
- import { buildWorkerReminderTail, ORCHESTRATOR_REMINDER_TAIL } from './hive-team-guidance.js';
1
+ import { buildOrchestratorReminderTail, buildWorkerReminderTail } from './hive-team-guidance.js';
2
2
  import { PtyInactiveError } from './http-errors.js';
3
3
  import { createPostStartInputWriter } from './post-start-input-writer.js';
4
- export const buildOrchestratorReportPayload = (workerName, text, artifacts) => {
5
- const lines = [`[Hive 系统消息:来自 @${workerName} 的汇报]`, text];
4
+ export const buildOrchestratorReportPayload = (workerName, text, artifacts, workflowsEnabled = false) => {
5
+ const lines = [`<hive-message kind="report" from="@${workerName}">`, text];
6
6
  for (const artifact of artifacts)
7
7
  lines.push(`artifact: ${artifact}`);
8
- lines.push('', ORCHESTRATOR_REMINDER_TAIL, '');
8
+ lines.push('</hive-message>', '', buildOrchestratorReminderTail(workflowsEnabled), '');
9
9
  return lines.join('\n');
10
10
  };
11
- export const buildOrchestratorStatusPayload = (workerName, text, artifacts) => {
12
- const lines = [`[Hive 系统消息:来自 @${workerName} 的状态更新]`, text];
11
+ export const buildOrchestratorStatusPayload = (workerName, text, artifacts, workflowsEnabled = false) => {
12
+ const lines = [`<hive-message kind="status" from="@${workerName}">`, text];
13
13
  for (const artifact of artifacts)
14
14
  lines.push(`artifact: ${artifact}`);
15
- lines.push('', ORCHESTRATOR_REMINDER_TAIL, '');
15
+ lines.push('</hive-message>', '', buildOrchestratorReminderTail(workflowsEnabled), '');
16
16
  return lines.join('\n');
17
17
  };
18
- export const buildOrchestratorUserInputPayload = (text) => [text, '', ORCHESTRATOR_REMINDER_TAIL, ''].join('\n');
18
+ export const buildOrchestratorUserInputPayload = (text, workflowsEnabled = false) => [text, '', buildOrchestratorReminderTail(workflowsEnabled), ''].join('\n');
19
19
  export const buildWorkerDispatchPayload = (fromAgentName, workerDescription, dispatchId, text) => [
20
- `[Hive 系统消息:来自 @${fromAgentName} 的派单]`,
20
+ `<hive-message kind="dispatch" from="@${fromAgentName}">`,
21
21
  '',
22
- `你的角色:${workerDescription}`,
22
+ `Your role: ${workerDescription}`,
23
23
  '',
24
- '你必须遵守:',
25
- `- 完成、失败、阻塞或部分完成后,执行 \`team report "<result>" --dispatch ${dispatchId}\``,
26
- '- 不要做无关的事,做完就 report',
24
+ 'You must:',
25
+ `- When the task is done, failed, blocked, or partially done, run \`team report "<result>" --dispatch ${dispatchId}\``,
26
+ '- Do not do unrelated work; report as soon as you are done',
27
27
  '',
28
28
  `dispatch_id: ${dispatchId}`,
29
29
  '',
30
- '任务内容:',
30
+ 'Task:',
31
31
  text,
32
+ '</hive-message>',
32
33
  '',
33
34
  buildWorkerReminderTail(dispatchId),
34
35
  '',
35
36
  ].join('\n');
36
37
  export const buildWorkerCancelPayload = (dispatchId, reason) => [
37
- `[Hive 系统消息:dispatch ${dispatchId} 已取消]`,
38
+ `<hive-message kind="cancel" dispatch="${dispatchId}">`,
38
39
  '',
39
- '请停止执行这条派单,不要再为它调用 team report',
40
+ 'Stop working on this dispatch and do not call team report for it.',
40
41
  '',
41
- '取消原因:',
42
+ 'Cancellation reason:',
42
43
  reason,
44
+ '</hive-message>',
43
45
  '',
44
46
  ].join('\n');
45
- export const createAgentStdinDispatcher = ({ agentManager, getLaunchConfig, getWorkspaceId, registry, syncRun, }) => {
46
- const writeToActiveAgentRun = (workspaceId, agentId, text, input = {}) => {
47
- const run = registry
48
- .list()
49
- .filter((item) => item.agentId === agentId && getWorkspaceId(item.agentId) === workspaceId)
50
- .sort((left, right) => right.startedAt - left.startedAt)
51
- .find((item) => {
52
- const status = syncRun(item).status;
53
- return status === 'starting' || status === 'running';
54
- });
55
- if (!run) {
47
+ export const createAgentStdinDispatcher = ({ agentManager, getLaunchConfig, getWorkspaceId, registry, syncRun, getWorkflowsEnabled, }) => {
48
+ const workflowsEnabled = () => getWorkflowsEnabled?.() ?? false;
49
+ const chains = new Map();
50
+ const getChain = (agentId) => {
51
+ let chain = chains.get(agentId);
52
+ if (!chain) {
53
+ chain = { busy: false, queue: [] };
54
+ chains.set(agentId, chain);
55
+ }
56
+ return chain;
57
+ };
58
+ const resolveActiveRun = (workspaceId, agentId) => registry
59
+ .list()
60
+ .filter((item) => item.agentId === agentId && getWorkspaceId(item.agentId) === workspaceId)
61
+ .sort((left, right) => right.startedAt - left.startedAt)
62
+ .find((item) => {
63
+ const status = syncRun(item).status;
64
+ return status === 'starting' || status === 'running';
65
+ });
66
+ // Synchronously enforce requireActiveRun (so writeSendPrompt still throws in
67
+ // the caller's stack when there is no live run), then return a thunk that
68
+ // re-resolves the run at EXECUTION time and performs the actual write,
69
+ // returning a promise that settles when the paste→submit sequence is done.
70
+ const prepareWrite = (workspaceId, agentId, text, input) => {
71
+ if (!resolveActiveRun(workspaceId, agentId)) {
56
72
  if (input.requireActiveRun) {
57
73
  throw new PtyInactiveError(`No active run for agent: ${agentId}`);
58
74
  }
59
- return;
75
+ return () => Promise.resolve();
60
76
  }
61
- try {
62
- const config = getLaunchConfig(workspaceId, agentId);
63
- if (agentManager && config) {
64
- createPostStartInputWriter(agentManager, config.interactiveCommand ?? config.command)(run.runId, text);
77
+ return () => {
78
+ const run = resolveActiveRun(workspaceId, agentId);
79
+ if (!run) {
80
+ if (input.requireActiveRun) {
81
+ throw new PtyInactiveError(`No active run for agent: ${agentId}`);
82
+ }
83
+ return Promise.resolve();
65
84
  }
66
- else {
85
+ try {
86
+ const config = getLaunchConfig(workspaceId, agentId);
87
+ if (agentManager && config) {
88
+ return (createPostStartInputWriter(agentManager, config.interactiveCommand ?? config.command)(run.runId, text).catch((error) => {
89
+ throw new PtyInactiveError(error instanceof Error ? error.message : String(error));
90
+ }) ?? Promise.resolve());
91
+ }
67
92
  agentManager?.writeInput(run.runId, text);
93
+ return Promise.resolve();
68
94
  }
95
+ catch (error) {
96
+ throw new PtyInactiveError(error instanceof Error ? error.message : String(error));
97
+ }
98
+ };
99
+ };
100
+ const settle = (agentId, promise) => promise.finally(() => {
101
+ const chain = chains.get(agentId);
102
+ if (!chain)
103
+ return;
104
+ chain.busy = false;
105
+ drain(agentId);
106
+ });
107
+ const runQueuedWrite = (agentId, write) => {
108
+ try {
109
+ void settle(agentId, write.run()).then(write.resolve, write.reject);
110
+ }
111
+ catch (error) {
112
+ write.reject(error);
113
+ const chain = chains.get(agentId);
114
+ if (chain)
115
+ chain.busy = false;
116
+ drain(agentId);
117
+ }
118
+ };
119
+ function drain(agentId) {
120
+ const chain = chains.get(agentId);
121
+ if (!chain)
122
+ return;
123
+ if (chain.busy)
124
+ return;
125
+ const next = chain.queue.shift();
126
+ if (!next) {
127
+ chains.delete(agentId);
128
+ return;
129
+ }
130
+ chain.busy = true;
131
+ runQueuedWrite(agentId, next);
132
+ }
133
+ const writeToActiveAgentRun = (workspaceId, agentId, text, input = {}) => {
134
+ const thunk = prepareWrite(workspaceId, agentId, text, input);
135
+ const chain = getChain(agentId);
136
+ if (chain.busy) {
137
+ return new Promise((resolve, reject) => {
138
+ chain.queue.push({ reject, resolve, run: thunk });
139
+ });
140
+ }
141
+ chain.busy = true;
142
+ try {
143
+ return settle(agentId, thunk()); // uncontended: run now; immediate failures still throw
69
144
  }
70
145
  catch (error) {
71
- throw new PtyInactiveError(error instanceof Error ? error.message : String(error));
146
+ chain.busy = false;
147
+ drain(agentId);
148
+ throw error;
72
149
  }
73
150
  };
151
+ const swallowQueuedFailure = (promise) => {
152
+ void promise.catch(() => {
153
+ // Deferred prompt writes can fail if the PTY exits while queued. Calls
154
+ // that require foreground error reporting use writeSendPrompt's promise.
155
+ });
156
+ };
74
157
  return {
75
158
  writeReportPrompt(workspaceId, workerName, text, artifacts, input = {}) {
76
- writeToActiveAgentRun(workspaceId, `${workspaceId}:orchestrator`, buildOrchestratorReportPayload(workerName, text, artifacts), input);
159
+ swallowQueuedFailure(writeToActiveAgentRun(workspaceId, `${workspaceId}:orchestrator`, buildOrchestratorReportPayload(workerName, text, artifacts, workflowsEnabled()), input));
77
160
  },
78
161
  writeStatusPrompt(workspaceId, workerName, text, artifacts, input = {}) {
79
- writeToActiveAgentRun(workspaceId, `${workspaceId}:orchestrator`, buildOrchestratorStatusPayload(workerName, text, artifacts), input);
162
+ swallowQueuedFailure(writeToActiveAgentRun(workspaceId, `${workspaceId}:orchestrator`, buildOrchestratorStatusPayload(workerName, text, artifacts, workflowsEnabled()), input));
80
163
  },
81
164
  writeSendPrompt(workspaceId, workerId, dispatchId, fromAgentName, workerDescription, text) {
82
- writeToActiveAgentRun(workspaceId, workerId, buildWorkerDispatchPayload(fromAgentName, workerDescription, dispatchId, text), { requireActiveRun: true });
165
+ return writeToActiveAgentRun(workspaceId, workerId, buildWorkerDispatchPayload(fromAgentName, workerDescription, dispatchId, text), { requireActiveRun: true });
83
166
  },
84
167
  writeCancelPrompt(workspaceId, workerId, dispatchId, reason, input = {}) {
85
- writeToActiveAgentRun(workspaceId, workerId, buildWorkerCancelPayload(dispatchId, reason), input);
168
+ swallowQueuedFailure(writeToActiveAgentRun(workspaceId, workerId, buildWorkerCancelPayload(dispatchId, reason), input));
86
169
  },
87
170
  writeUserInputPrompt(workspaceId, text) {
88
- writeToActiveAgentRun(workspaceId, `${workspaceId}:orchestrator`, buildOrchestratorUserInputPayload(text));
171
+ swallowQueuedFailure(writeToActiveAgentRun(workspaceId, `${workspaceId}:orchestrator`, buildOrchestratorUserInputPayload(text, workflowsEnabled())));
172
+ },
173
+ /** Generic: deliver an opaque text block to a specific agent's PTY.
174
+ * Used by the workflow runner to notify the triggering orchestrator
175
+ * when a run finishes (mirrors Claude Code's <task-notification>). */
176
+ writeSystemMessageToAgent(workspaceId, agentId, text) {
177
+ swallowQueuedFailure(writeToActiveAgentRun(workspaceId, agentId, text));
89
178
  },
90
179
  };
91
180
  };
@@ -15,5 +15,6 @@ interface CreateAppOptions {
15
15
  export declare const createApp: ({ store, pickFolderService, openWorkspaceService, packageVersionReader, tasksFileService, versionService, }: CreateAppOptions) => {
16
16
  server: import("http").Server<typeof IncomingMessage, typeof ServerResponse>;
17
17
  store: RuntimeStore;
18
+ closeWebSockets: () => void;
18
19
  };
19
20
  export type { CreateAppOptions };
@@ -196,6 +196,16 @@ export const createApp = ({ store, pickFolderService = pickFolder, openWorkspace
196
196
  sendJson(response, 500, { error: message });
197
197
  }
198
198
  });
199
- createTerminalWebSocketServer(server, store, tasksFileService);
200
- return { server, store };
199
+ const wsServer = createTerminalWebSocketServer(server, store, tasksFileService);
200
+ return {
201
+ server,
202
+ store,
203
+ // Tear-down for the WebSocket layer. Callers must invoke this
204
+ // BEFORE awaiting `server.close()` if they want a prompt return:
205
+ // `server.close()` waits on every existing socket, and Node's
206
+ // `closeAllConnections()` does NOT terminate already-upgraded
207
+ // WebSocket clients. Without this hook a Ctrl+C in the Hive
208
+ // runtime hangs as long as any browser tab is connected.
209
+ closeWebSockets: wsServer.close,
210
+ };
201
211
  };
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Validate a 5-field cron string and return its next fire time (ms-epoch).
3
+ * Throws BadRequestError on an unparseable expression. Shared by the UI
4
+ * schedule PATCH route and the agent `team workflow schedule` route so cron
5
+ * validation behaves identically on both paths. UTC, like the scheduler.
6
+ */
7
+ export declare const validateCronNextRunAt: (cron: string) => number;
@@ -0,0 +1,19 @@
1
+ import { CronExpressionParser } from 'cron-parser';
2
+ import { BadRequestError } from './http-errors.js';
3
+ /**
4
+ * Validate a 5-field cron string and return its next fire time (ms-epoch).
5
+ * Throws BadRequestError on an unparseable expression. Shared by the UI
6
+ * schedule PATCH route and the agent `team workflow schedule` route so cron
7
+ * validation behaves identically on both paths. UTC, like the scheduler.
8
+ */
9
+ export const validateCronNextRunAt = (cron) => {
10
+ try {
11
+ return CronExpressionParser.parse(cron, { currentDate: new Date(), tz: 'UTC' })
12
+ .next()
13
+ .toDate()
14
+ .getTime();
15
+ }
16
+ catch (error) {
17
+ throw new BadRequestError(`Invalid cron expression: ${error instanceof Error ? error.message : String(error)}`);
18
+ }
19
+ };