@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
@@ -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
  };
@@ -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
+ };
@@ -6,19 +6,27 @@ export interface DispatchRecord {
6
6
  deliveredAt: number | null;
7
7
  fromAgentId: string | null;
8
8
  id: string;
9
+ label: string | null;
10
+ phase: string | null;
9
11
  reportedAt: number | null;
10
12
  reportText: string | null;
11
13
  sequence: number | null;
12
14
  status: DispatchStatus;
15
+ stepIndex: number | null;
13
16
  submittedAt: number | null;
14
17
  text: string;
15
18
  toAgentId: string;
19
+ workflowRunId: string | null;
16
20
  workspaceId: string;
17
21
  }
18
22
  interface CreateDispatchInput {
19
23
  fromAgentId?: string;
24
+ label?: string;
25
+ phase?: string;
26
+ stepIndex?: number;
20
27
  text: string;
21
28
  toAgentId: string;
29
+ workflowRunId?: string;
22
30
  workspaceId: string;
23
31
  }
24
32
  interface ReportDispatchInput {
@@ -50,6 +58,12 @@ export declare const createDispatchLedgerStore: (db: Database) => {
50
58
  worker_id: string;
51
59
  workspace_id: string;
52
60
  }>;
61
+ listOpenDispatchIdsForRun: (runId: string) => string[];
62
+ listOpenWorkflowDispatchesForWorker: (workspaceId: string, workerId: string) => Array<{
63
+ dispatchId: string;
64
+ runId: string;
65
+ }>;
66
+ listWorkflowRunDispatches: (runId: string) => DispatchRecord[];
53
67
  listWorkspaceDispatches: (workspaceId: string, options?: ListDispatchesOptions) => DispatchRecord[];
54
68
  markCancelled: (input: CancelDispatchInput) => {
55
69
  reportedAt: number;
@@ -60,10 +74,14 @@ export declare const createDispatchLedgerStore: (db: Database) => {
60
74
  deliveredAt: number | null;
61
75
  fromAgentId: string | null;
62
76
  id: string;
77
+ label: string | null;
78
+ phase: string | null;
63
79
  sequence: number | null;
80
+ stepIndex: number | null;
64
81
  submittedAt: number | null;
65
82
  text: string;
66
83
  toAgentId: string;
84
+ workflowRunId: string | null;
67
85
  workspaceId: string;
68
86
  } | undefined;
69
87
  markReportedByWorker: (input: ReportDispatchInput) => {
@@ -75,10 +93,14 @@ export declare const createDispatchLedgerStore: (db: Database) => {
75
93
  deliveredAt: number | null;
76
94
  fromAgentId: string | null;
77
95
  id: string;
96
+ label: string | null;
97
+ phase: string | null;
78
98
  sequence: number | null;
99
+ stepIndex: number | null;
79
100
  submittedAt: number | null;
80
101
  text: string;
81
102
  toAgentId: string;
103
+ workflowRunId: string | null;
82
104
  workspaceId: string;
83
105
  } | undefined;
84
106
  markSubmitted: (dispatchId: string) => void;
@@ -18,13 +18,17 @@ const toRecord = (row) => ({
18
18
  deliveredAt: row.delivered_at,
19
19
  fromAgentId: row.from_agent_id,
20
20
  id: row.id,
21
+ label: row.label ?? null,
22
+ phase: row.phase ?? null,
21
23
  reportedAt: row.reported_at,
22
24
  reportText: row.report_text,
23
25
  sequence: row.sequence,
24
26
  status: row.status,
27
+ stepIndex: row.step_index,
25
28
  submittedAt: row.submitted_at,
26
29
  text: row.text,
27
30
  toAgentId: row.to_agent_id,
31
+ workflowRunId: row.workflow_run_id,
28
32
  workspaceId: row.workspace_id,
29
33
  });
30
34
  export const createDispatchLedgerStore = (db) => {
@@ -35,13 +39,17 @@ export const createDispatchLedgerStore = (db) => {
35
39
  deliveredAt: null,
36
40
  fromAgentId: input.fromAgentId ?? null,
37
41
  id: randomUUID(),
42
+ label: input.label ?? null,
43
+ phase: input.phase ?? null,
38
44
  reportedAt: null,
39
45
  reportText: null,
40
46
  sequence: null,
41
47
  status: 'queued',
48
+ stepIndex: input.stepIndex ?? null,
42
49
  submittedAt: null,
43
50
  text: input.text,
44
51
  toAgentId: input.toAgentId,
52
+ workflowRunId: input.workflowRunId ?? null,
45
53
  workspaceId: input.workspaceId,
46
54
  };
47
55
  db.prepare(`INSERT INTO dispatches (
@@ -56,8 +64,12 @@ export const createDispatchLedgerStore = (db) => {
56
64
  submitted_at,
57
65
  reported_at,
58
66
  report_text,
59
- artifacts
60
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(record.id, record.workspaceId, record.fromAgentId, record.toAgentId, record.text, record.status, record.createdAt, record.deliveredAt, record.submittedAt, record.reportedAt, record.reportText, JSON.stringify(record.artifacts));
67
+ artifacts,
68
+ workflow_run_id,
69
+ step_index,
70
+ phase,
71
+ label
72
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(record.id, record.workspaceId, record.fromAgentId, record.toAgentId, record.text, record.status, record.createdAt, record.deliveredAt, record.submittedAt, record.reportedAt, record.reportText, JSON.stringify(record.artifacts), record.workflowRunId, record.stepIndex, record.phase, record.label);
61
73
  return record;
62
74
  };
63
75
  const deleteDispatch = (dispatchId) => {
@@ -175,8 +187,41 @@ export const createDispatchLedgerStore = (db) => {
175
187
  db.prepare('DELETE FROM dispatches WHERE workspace_id = ?').run(workspaceId);
176
188
  };
177
189
  const deleteWorkerDispatches = (workspaceId, workerId) => {
178
- db.prepare('DELETE FROM dispatches WHERE workspace_id = ? AND to_agent_id = ?').run(workspaceId, workerId);
190
+ // Preserve workflow-run dispatch history (`workflow_run_id IS NOT NULL`)
191
+ // so the UI's run-detail timeline can still show what each ephemeral
192
+ // worker did after the worker itself is dismissed. Orchestrator-issued
193
+ // dispatches are tied to the worker identity and are dropped with it.
194
+ db.prepare(`DELETE FROM dispatches
195
+ WHERE workspace_id = ? AND to_agent_id = ? AND workflow_run_id IS NULL`).run(workspaceId, workerId);
179
196
  };
197
+ // Every dispatch fired by a workflow run carries the run id (M1-B added the
198
+ // column; M2-C plumbs it through). This is the timeline query the UI uses to
199
+ // explode a run row into per-worker activity.
200
+ const listWorkflowRunDispatches = (runId) => {
201
+ const rows = db
202
+ .prepare('SELECT * FROM dispatches WHERE workflow_run_id = ? ORDER BY sequence, created_at')
203
+ .all(runId);
204
+ return rows.map(toRecord);
205
+ };
206
+ // Open dispatch ids tied to a workflow run — drives the runner's stop path
207
+ // (each id gets a notifyCancel so the runner's await rejects).
208
+ const listOpenDispatchIdsForRun = (runId) => db
209
+ .prepare(`SELECT id FROM dispatches
210
+ WHERE workflow_run_id = ? AND status IN ('queued', 'submitted')`)
211
+ .all(runId).map((row) => row.id);
212
+ // Open workflow-tagged dispatches addressed to a specific worker. Drives the
213
+ // PTY-exit cancel path (TIER 1 #1): when a workflow-spawned worker dies
214
+ // without calling `team report`, the runner's `awaitReport` would otherwise
215
+ // hang for DEFAULT_TIMEOUT_MS (10 min). The exit handler enumerates these
216
+ // and `notifyCancel`s each so the surrounding `agent()`/`parallel`/`pipeline`
217
+ // sees an immediate reject.
218
+ const listOpenWorkflowDispatchesForWorker = (workspaceId, workerId) => db
219
+ .prepare(`SELECT id, workflow_run_id FROM dispatches
220
+ WHERE workspace_id = ?
221
+ AND to_agent_id = ?
222
+ AND workflow_run_id IS NOT NULL
223
+ AND status IN ('queued', 'submitted')`)
224
+ .all(workspaceId, workerId).map((row) => ({ dispatchId: row.id, runId: row.workflow_run_id }));
180
225
  return {
181
226
  createDispatch,
182
227
  deleteDispatch,
@@ -185,6 +230,9 @@ export const createDispatchLedgerStore = (db) => {
185
230
  findOpenDispatch,
186
231
  findOpenDispatchById,
187
232
  listOpenDispatchKinds,
233
+ listOpenDispatchIdsForRun,
234
+ listOpenWorkflowDispatchesForWorker,
235
+ listWorkflowRunDispatches,
188
236
  listWorkspaceDispatches,
189
237
  markCancelled,
190
238
  markReportedByWorker,
@@ -4,7 +4,7 @@ import { TASKS_RELATIVE_PATH } from './tasks-file.js';
4
4
  const TASKS_HEAD_LIMIT = 1024;
5
5
  const formatWorkers = (workers) => {
6
6
  if (workers.length === 0)
7
- return ['- 当前没有其他 worker'];
7
+ return ['- (no other workers)'];
8
8
  return workers.map((worker) => `- ${worker.name} (${worker.role}, ${worker.status}, pending_task_count: ${worker.pendingTaskCount})`);
9
9
  };
10
10
  const formatRestartWindow = (messages) => {
@@ -12,18 +12,18 @@ const formatRestartWindow = (messages) => {
12
12
  return message.type === 'send';
13
13
  });
14
14
  if (sends.length === 0)
15
- return ['- 重启期间未派新单'];
15
+ return ['- no new dispatches during the restart'];
16
16
  return sends.slice(-5).map((message) => `- send -> ${message.to}: ${message.text}`);
17
17
  };
18
18
  export const buildEnvSyncMessage = ({ agent, tasksContent, workers, workspace, restartWindowMessages, }) => wrapSystemMessage([
19
- '你刚被 Hive 重启了。期间环境变化:',
20
- `- 当前 workspace: ${workspace.name}`,
21
- '- 现有 worker:',
19
+ 'Hive just restarted you. Environment changes during the restart:',
20
+ `- Current workspace: ${workspace.name}`,
21
+ '- Existing workers:',
22
22
  ...formatWorkers(workers),
23
- `- ${TASKS_RELATIVE_PATH} 当前内容:`,
24
- tasksContent.slice(0, TASKS_HEAD_LIMIT) || '()',
23
+ `- Current ${TASKS_RELATIVE_PATH} contents:`,
24
+ tasksContent.slice(0, TASKS_HEAD_LIMIT) || '(empty)',
25
25
  ...formatRestartWindow(restartWindowMessages),
26
- agent.role === 'orchestrator' ? '- Hive worker 派单规则:' : '- Hive worker 边界:',
26
+ agent.role === 'orchestrator' ? '- Hive worker dispatch rules:' : '- Hive worker boundaries:',
27
27
  ...getHiveTeamRules(agent).map((rule) => ` - ${rule}`),
28
- `请继续。如果不确定,用 team list / Read ${TASKS_RELATIVE_PATH} 自查或问 user。`,
28
+ `Continue. If unsure, run team list / Read ${TASKS_RELATIVE_PATH} to self-check, or ask the user.`,
29
29
  ].join('\n'));
@@ -112,7 +112,11 @@ const windowsPick = async (run) => {
112
112
 
113
113
  `Add-Type -AssemblyName System.Drawing` is required because Point /
114
114
  Size live in System.Drawing.dll, not System.Windows.Forms.dll. */
115
+ // PS 5.1 on zh-CN Windows defaults [Console]::OutputEncoding to cp936/GBK;
116
+ // Node decodes stdout as UTF-8 and CJK paths arrive mojibake'd. Force UTF-8
117
+ // before any output. No-op on PS 7+ which already defaults to UTF-8.
115
118
  const script = [
119
+ '[Console]::OutputEncoding = [System.Text.Encoding]::UTF8',
116
120
  'Add-Type -AssemblyName System.Windows.Forms',
117
121
  'Add-Type -AssemblyName System.Drawing',
118
122
  '$owner = New-Object System.Windows.Forms.Form',
@@ -1,5 +1,6 @@
1
1
  import { homedir } from 'node:os';
2
2
  import { isAbsolute, relative, resolve, sep } from 'node:path';
3
+ import { realpathNative } from './path-canonicalization.js';
3
4
  /**
4
5
  * Root directory the FS-browse API is allowed to reveal. We sandbox to
5
6
  * `$HOME` (override via `HIVE_FS_BROWSE_ROOT` for tests). Anything outside
@@ -7,7 +8,19 @@ import { isAbsolute, relative, resolve, sep } from 'node:path';
7
8
  */
8
9
  export const getFsBrowseRoot = () => {
9
10
  const override = process.env.HIVE_FS_BROWSE_ROOT;
10
- return override && override.length > 0 ? resolve(override) : resolve(homedir());
11
+ const root = override && override.length > 0 ? resolve(override) : resolve(homedir());
12
+ try {
13
+ return realpathNative(root);
14
+ }
15
+ catch {
16
+ return root;
17
+ }
18
+ };
19
+ const isResolvedPathWithinRoot = (rootPath, candidatePath) => {
20
+ if (candidatePath === rootPath)
21
+ return true;
22
+ const rel = relative(rootPath, candidatePath);
23
+ return rel !== '..' && !rel.startsWith(`..${sep}`) && !isAbsolute(rel);
11
24
  };
12
25
  /**
13
26
  * True when `candidatePath` is `rootPath` itself or a descendant of it.
@@ -16,10 +29,26 @@ export const getFsBrowseRoot = () => {
16
29
  * isPathWithinRoot so the semantics match a project we already trust.
17
30
  */
18
31
  export const isPathWithinRoot = (rootPath, candidatePath) => {
19
- const resolvedRoot = resolve(rootPath);
20
- const resolvedCandidate = resolve(candidatePath);
21
- if (resolvedCandidate === resolvedRoot)
22
- return true;
23
- const rel = relative(resolvedRoot, resolvedCandidate);
24
- return rel !== '..' && !rel.startsWith(`..${sep}`) && !isAbsolute(rel);
32
+ const lexicalRoot = resolve(rootPath);
33
+ const lexicalCandidate = resolve(candidatePath);
34
+ let resolvedRoot = lexicalRoot;
35
+ let resolvedCandidate = lexicalCandidate;
36
+ try {
37
+ resolvedRoot = realpathNative(resolvedRoot);
38
+ }
39
+ catch {
40
+ // Missing / inaccessible roots are handled by the caller's readdir/stat path.
41
+ }
42
+ try {
43
+ resolvedCandidate = realpathNative(resolvedCandidate);
44
+ }
45
+ catch {
46
+ // Non-existent children still need lexical sandboxing for "create later"
47
+ // probes; existing symlinks/junctions use the realpath branch above.
48
+ if (isResolvedPathWithinRoot(lexicalRoot, lexicalCandidate)) {
49
+ resolvedRoot = lexicalRoot;
50
+ resolvedCandidate = lexicalCandidate;
51
+ }
52
+ }
53
+ return isResolvedPathWithinRoot(resolvedRoot, resolvedCandidate);
25
54
  };
@@ -1,4 +1,5 @@
1
1
  import type { AgentSummary } from '../shared/types.js';
2
+ import { type WorkflowCliPolicy } from './workflow-cli-policy.js';
2
3
  /**
3
4
  * Tail reminder appended to every message that flows INTO the orchestrator
4
5
  * (worker reports, worker status updates, user chat input). Re-anchors the
@@ -11,10 +12,12 @@ import type { AgentSummary } from '../shared/types.js';
11
12
  * banner noise after a few occurrences, but `<...-system-reminder>` tags
12
13
  * mirror the out-of-band envelope LLMs are trained to attend to; placement
13
14
  * at the tail (right before the agent's reply turn) maximizes recency
14
- * weighting; phrasing as a two-option action menu is more actionable than
15
- * abstract identity restatement.
15
+ * weighting; phrasing as a short action menu is more actionable than abstract
16
+ * identity restatement. Kept deliberately SHORT — the full command syntax and
17
+ * the workflow DSL live in `.hive/PROTOCOL.md`, which agents re-read on demand.
18
+ * A long banner on every turn is itself the noise this envelope exists to beat.
16
19
  */
17
- export declare const ORCHESTRATOR_REMINDER_TAIL: string;
20
+ export declare const buildOrchestratorReminderTail: (workflowsEnabled: boolean) => string;
18
21
  /**
19
22
  * Tail reminder appended to dispatches sent TO a worker. Reinforces the
20
23
  * worker identity (so the agent does not regress into its normal CLI
@@ -22,12 +25,14 @@ export declare const ORCHESTRATOR_REMINDER_TAIL: string;
22
25
  * with dispatch_id pre-bound.
23
26
  */
24
27
  export declare const buildWorkerReminderTail: (dispatchId: string) => string;
25
- export declare const getHiveTeamRules: (agent: Pick<AgentSummary, "role">) => string[];
28
+ export declare const getHiveTeamRules: (agent: Pick<AgentSummary, "role">, workflowsEnabled?: boolean, autostaffEnabled?: boolean) => readonly string[];
26
29
  /**
27
30
  * Workspace-local protocol cheat sheet written to `.hive/PROTOCOL.md`. Agents
28
31
  * are explicitly trained to look at project root markdown when confused, so
29
32
  * keeping a single canonical doc next to `.hive/tasks.md` doubles as a
30
33
  * "cat-recover" path when both the startup prompt and the in-message
31
- * reminders fail to anchor.
34
+ * reminders fail to anchor. This is also the single home of the full command
35
+ * syntax and the workflow DSL reference — the always-on injections only carry
36
+ * the lean core rules and point here.
32
37
  */
33
- export declare const buildProtocolDoc: () => string;
38
+ export declare const buildProtocolDoc: (cliPolicy?: WorkflowCliPolicy, workflowsEnabled?: boolean, autostaffEnabled?: boolean) => string;