@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
@@ -7,9 +7,26 @@ const MAX_SUBMIT_AFTER_PASTE_DELAY_MS = 1500;
7
7
  const PASTE_CHARS_PER_DELAY_MS = 4;
8
8
  const PASTE_ACK_CHECK_INTERVAL_MS = 50;
9
9
  const PASTE_ACK_SETTLE_DELAY_MS = 100;
10
+ const CODEX_PASTE_ACK_SETTLE_DELAY_MS = 250;
10
11
  const PASTE_ACK_TIMEOUT_MS = 3000;
12
+ // Codex can take several seconds to render "[Pasted Content ...]" on Windows
13
+ // conpty for large bracketed-paste payloads. Keep Claude's shorter fallback,
14
+ // but give Codex enough time to use the ack-gated path in normal operation.
15
+ const CODEX_PASTE_ACK_TIMEOUT_MS = 10000;
16
+ // Codex only emits "[Pasted Content ...]" for sufficiently large pastes. Short
17
+ // Hive report/status/user-input payloads are usually rendered literally in the
18
+ // input box, so waiting for an acknowledgement there degrades into the 10s
19
+ // timeout before Enter is sent.
20
+ const CODEX_PASTE_ACK_MIN_CHARS = 2000;
21
+ const CODEX_SUBMIT_RETRY_DELAY_MS = 500;
11
22
  const COMMANDS_WITH_BRACKETED_PASTE = new Set(['claude', 'codex', 'opencode']);
23
+ const COMMANDS_WAITING_FOR_PASTE_ACK = new Set(['claude', 'codex']);
24
+ const BRACKETED_PASTE_END = '\u001b[201~';
25
+ const PASTE_ACK_PATTERN = /(?:^|[\r\n])\s*(?:[❯›]\s*)?\[(?:Pasted text #\d+[^\]]*|Pasted Content [\d,]+ chars)\]/u;
26
+ const PASTE_ACK_ONLY_DELTA_PATTERN = /^[\s\r\n]*(?:[❯›]\s*)?\[(?:Pasted text #\d+[^\]]*|Pasted Content [\d,]+ chars)\]\s*(?:[❯›]\s*)?$/u;
12
27
  export const toBracketedPasteSubmission = (text) => `\u001b[200~${text}\u001b[201~`;
28
+ const toError = (error) => (error instanceof Error ? error : new Error(String(error)));
29
+ const createRunInactiveError = (runId) => new Error(`Run became inactive before input was submitted: ${runId}`);
13
30
  const getSubmitAfterPasteDelayMs = (text) => Math.min(MAX_SUBMIT_AFTER_PASTE_DELAY_MS, Math.max(MIN_SUBMIT_AFTER_PASTE_DELAY_MS, Math.ceil(text.length / PASTE_CHARS_PER_DELAY_MS)));
14
31
  export const isInteractiveAgentCommand = (command) => {
15
32
  const brand = normalizeExecutableToken(command);
@@ -22,8 +39,24 @@ export const hasInteractivePromptReady = (output, command = '') => {
22
39
  return (/(?:^|[\r\n])\s*[❯›]\s*/u.test(output) ||
23
40
  (commandName === 'gemini' && hasGeminiPromptReady(output)));
24
41
  };
25
- export const hasBracketedPasteAcknowledgement = (output, baselineLength) => /\[Pasted text #\d+/u.test(output.slice(baselineLength));
26
- const isClaudeCommand = (command) => getCommandName(command) === 'claude';
42
+ export const hasBracketedPasteAcknowledgement = (output, baselineLength) => {
43
+ const outputAfterPaste = output.slice(baselineLength);
44
+ const pasteEndIndex = outputAfterPaste.lastIndexOf(BRACKETED_PASTE_END);
45
+ const acknowledgementOutput = pasteEndIndex === -1
46
+ ? outputAfterPaste
47
+ : outputAfterPaste.slice(pasteEndIndex + BRACKETED_PASTE_END.length);
48
+ return PASTE_ACK_PATTERN.test(acknowledgementOutput);
49
+ };
50
+ const onlyPasteAcknowledgementWasAppended = (before, after) => after.startsWith(before) && PASTE_ACK_ONLY_DELTA_PATTERN.test(after.slice(before.length));
51
+ const shouldWaitForPasteAck = (command, text) => {
52
+ const commandName = getCommandName(command);
53
+ if (commandName === 'codex')
54
+ return text.length >= CODEX_PASTE_ACK_MIN_CHARS;
55
+ return COMMANDS_WAITING_FOR_PASTE_ACK.has(commandName);
56
+ };
57
+ const retriesSubmit = (command) => getCommandName(command) === 'codex';
58
+ const getPasteAckSettleDelayMs = (command) => getCommandName(command) === 'codex' ? CODEX_PASTE_ACK_SETTLE_DELAY_MS : PASTE_ACK_SETTLE_DELAY_MS;
59
+ const getPasteAckTimeoutMs = (command) => getCommandName(command) === 'codex' ? CODEX_PASTE_ACK_TIMEOUT_MS : PASTE_ACK_TIMEOUT_MS;
27
60
  const usesBracketedPaste = (command) => COMMANDS_WITH_BRACKETED_PASTE.has(getCommandName(command));
28
61
  const canTimeoutBeforePromptReady = (command) => getCommandName(command) !== 'gemini';
29
62
  const isWritableRunStatus = (status) => status === undefined || status === 'starting' || status === 'running';
@@ -40,7 +73,7 @@ const writeIfRunWritable = (agentManager, runId, text) => {
40
73
  agentManager.writeInput(runId, text);
41
74
  return true;
42
75
  };
43
- const submitPastedInteractiveInput = (agentManager, runId, text, baselineLength, waitForPasteAck) => {
76
+ const submitPastedInteractiveInput = (agentManager, runId, text, baselineLength, waitForPasteAck, pasteAckSettleDelayMs, pasteAckTimeoutMs, retrySubmit, onDone, onError) => {
44
77
  const pastedAt = Date.now();
45
78
  const minDelay = getSubmitAfterPasteDelayMs(text);
46
79
  let acknowledgedAt = null;
@@ -53,30 +86,73 @@ const submitPastedInteractiveInput = (agentManager, runId, text, baselineLength,
53
86
  return null;
54
87
  }
55
88
  };
56
- const submit = () => {
89
+ const getRunOutputState = () => {
57
90
  try {
58
- writeIfRunWritable(agentManager, runId, '\r');
91
+ const run = agentManager.getRun(runId);
92
+ return { output: run.output, writable: isWritableRunStatus(run.status) };
59
93
  }
60
94
  catch {
95
+ return null;
96
+ }
97
+ };
98
+ const submit = (retryAfterSubmit) => {
99
+ try {
100
+ const outputBeforeSubmit = retryAfterSubmit ? getWritableOutput() : null;
101
+ if (!writeIfRunWritable(agentManager, runId, '\r')) {
102
+ onError(createRunInactiveError(runId));
103
+ return;
104
+ }
105
+ if (!retryAfterSubmit) {
106
+ onDone();
107
+ return;
108
+ }
109
+ setTimeout(() => {
110
+ try {
111
+ const runAfterSubmit = getRunOutputState();
112
+ if (runAfterSubmit === null) {
113
+ onDone();
114
+ return;
115
+ }
116
+ if (outputBeforeSubmit !== null &&
117
+ hasBracketedPasteAcknowledgement(runAfterSubmit.output, baselineLength) &&
118
+ onlyPasteAcknowledgementWasAppended(outputBeforeSubmit, runAfterSubmit.output)) {
119
+ if (!runAfterSubmit.writable || !writeIfRunWritable(agentManager, runId, '\r')) {
120
+ onError(createRunInactiveError(runId));
121
+ return;
122
+ }
123
+ }
124
+ }
125
+ catch (error) {
126
+ onError(toError(error));
127
+ return;
128
+ }
129
+ onDone();
130
+ }, CODEX_SUBMIT_RETRY_DELAY_MS);
131
+ }
132
+ catch (error) {
61
133
  // The PTY may have exited between paste and submit.
134
+ onError(toError(error));
62
135
  }
63
136
  };
64
137
  const trySubmit = () => {
65
138
  if (!waitForPasteAck) {
66
- submit();
139
+ submit(false);
67
140
  return;
68
141
  }
69
142
  const output = getWritableOutput();
70
143
  if (output === null) {
144
+ onError(createRunInactiveError(runId));
71
145
  return;
72
146
  }
73
147
  if (acknowledgedAt === null && hasBracketedPasteAcknowledgement(output, baselineLength)) {
74
148
  acknowledgedAt = Date.now();
75
149
  }
76
150
  const elapsed = Date.now() - pastedAt;
77
- const ackSettled = acknowledgedAt !== null && Date.now() - acknowledgedAt >= PASTE_ACK_SETTLE_DELAY_MS;
78
- if ((ackSettled && elapsed >= minDelay) || elapsed >= PASTE_ACK_TIMEOUT_MS) {
79
- submit();
151
+ const ackSettled = acknowledgedAt !== null && Date.now() - acknowledgedAt >= pasteAckSettleDelayMs;
152
+ const submitAfterAck = ackSettled && elapsed >= minDelay;
153
+ const submitAfterTimeout = elapsed >= pasteAckTimeoutMs;
154
+ if (submitAfterAck || submitAfterTimeout) {
155
+ submit(retrySubmit && submitAfterTimeout);
80
156
  return;
81
157
  }
82
158
  setTimeout(trySubmit, PASTE_ACK_CHECK_INTERVAL_MS);
@@ -86,10 +162,21 @@ const submitPastedInteractiveInput = (agentManager, runId, text, baselineLength,
86
162
  export const createPostStartInputWriter = (agentManager, command) => {
87
163
  if (!isInteractiveAgentCommand(command)) {
88
164
  return (runId, text) => {
89
- writeIfRunWritable(agentManager, runId, `${text}\n`);
165
+ // Synchronous write; an EPIPE/inactive failure still throws synchronously
166
+ // (before the promise is returned), preserving the dispatcher's contract.
167
+ if (!writeIfRunWritable(agentManager, runId, `${text}\n`)) {
168
+ throw createRunInactiveError(runId);
169
+ }
170
+ return Promise.resolve();
90
171
  };
91
172
  }
92
173
  return (runId, text) => {
174
+ let resolveDone;
175
+ let rejectDone;
176
+ const rejectableDone = new Promise((resolve, reject) => {
177
+ resolveDone = resolve;
178
+ rejectDone = reject;
179
+ });
93
180
  const startedAt = Date.now();
94
181
  let isInitialAttempt = true;
95
182
  const tryWrite = () => {
@@ -99,33 +186,43 @@ export const createPostStartInputWriter = (agentManager, command) => {
99
186
  output = isWritableRunStatus(run.status) ? run.output : null;
100
187
  }
101
188
  catch {
189
+ rejectDone(createRunInactiveError(runId));
102
190
  return;
103
191
  }
104
- if (output === null)
192
+ if (output === null) {
193
+ rejectDone(createRunInactiveError(runId));
105
194
  return;
195
+ }
106
196
  if (hasInteractivePromptReady(output, command) ||
107
197
  (canTimeoutBeforePromptReady(command) && Date.now() - startedAt >= READY_TIMEOUT_MS)) {
108
198
  const baselineLength = output.length;
109
199
  const input = usesBracketedPaste(command) ? toBracketedPasteSubmission(text) : text;
110
200
  try {
111
- if (!writeIfRunWritable(agentManager, runId, input))
201
+ if (!writeIfRunWritable(agentManager, runId, input)) {
202
+ rejectDone(createRunInactiveError(runId));
112
203
  return;
204
+ }
113
205
  }
114
206
  catch (error) {
115
207
  if (isInitialAttempt)
116
208
  throw error;
209
+ rejectDone(toError(error));
117
210
  return;
118
211
  }
119
- submitPastedInteractiveInput(agentManager, runId, text, baselineLength, isClaudeCommand(command));
212
+ submitPastedInteractiveInput(agentManager, runId, text, baselineLength, shouldWaitForPasteAck(command, text), getPasteAckSettleDelayMs(command), getPasteAckTimeoutMs(command), retriesSubmit(command), resolveDone, rejectDone);
120
213
  return;
121
214
  }
122
215
  setTimeout(tryWrite, READY_CHECK_INTERVAL_MS);
123
216
  };
124
217
  try {
218
+ // Called outside the Promise executor so an initial-attempt throw
219
+ // propagates synchronously to the caller (preserving the dispatcher's
220
+ // synchronous-throw contract) instead of becoming a rejection.
125
221
  tryWrite();
126
222
  }
127
223
  finally {
128
224
  isInitialAttempt = false;
129
225
  }
226
+ return rejectableDone;
130
227
  };
131
228
  };
@@ -1,6 +1,6 @@
1
1
  import type { AgentLaunchConfigInput } from './agent-run-store.js';
2
2
  import type { CommandPresetRecord } from './command-preset-store.js';
3
3
  import type { SessionCaptureSnapshot } from './session-capture.js';
4
- type BoundPreset = Pick<CommandPresetRecord, 'resumeArgsTemplate' | 'sessionIdCapture' | 'yoloArgsTemplate'>;
4
+ type BoundPreset = Pick<CommandPresetRecord, 'resumeArgsTemplate' | 'sessionIdCapture' | 'yoloArgsTemplate'> & Partial<Pick<CommandPresetRecord, 'command' | 'id'>>;
5
5
  export declare const withPresetResumeArgs: (config: AgentLaunchConfigInput, preset: BoundPreset | null | undefined, lastSessionId: string | undefined, cwd?: string, discriminator?: SessionCaptureSnapshot["discriminator"], onInvalidSessionId?: (sessionId: string) => void) => AgentLaunchConfigInput;
6
6
  export {};
@@ -3,6 +3,36 @@ const appendUniqueArgs = (prefix, args) => {
3
3
  const seen = new Set(prefix);
4
4
  return prefix.concat(args.filter((arg) => !seen.has(arg)));
5
5
  };
6
+ const unquoteExecutable = (command) => command.trim().replace(/^"(.+)"$/, '$1');
7
+ const isNodeExecutable = (command) => {
8
+ if (!command)
9
+ return false;
10
+ return /(?:^|[\\/])node(?:\.exe)?$/iu.test(unquoteExecutable(command));
11
+ };
12
+ const isCodexNpmEntrypoint = (arg) => {
13
+ if (!arg)
14
+ return false;
15
+ const normalized = arg.replace(/\\/gu, '/');
16
+ return /(?:^|\/)@openai\/codex\/bin\/codex\.js$/iu.test(normalized);
17
+ };
18
+ const isCodexPreset = (preset) => preset?.id === 'codex' ||
19
+ preset?.command === 'codex' ||
20
+ preset?.sessionIdCapture?.source === 'codex_session_jsonl_dir';
21
+ const normalizeCodexNodeEntrypoint = (config, preset) => {
22
+ if (!isCodexPreset(preset) || !isNodeExecutable(config.command))
23
+ return config;
24
+ const args = config.args ?? [];
25
+ if (!isCodexNpmEntrypoint(args[0]))
26
+ return config;
27
+ return {
28
+ ...config,
29
+ args: args.slice(1),
30
+ command: preset?.command ?? 'codex',
31
+ interactiveCommand: isNodeExecutable(config.interactiveCommand)
32
+ ? 'codex'
33
+ : (config.interactiveCommand ?? 'codex'),
34
+ };
35
+ };
6
36
  const getEffectiveCapture = (config, preset) => config.sessionIdCapture ?? preset?.sessionIdCapture ?? null;
7
37
  const getEffectiveResumeTemplate = (config, preset) => config.resumeArgsTemplate ?? preset?.resumeArgsTemplate ?? null;
8
38
  const withPresetYoloArgs = (config, preset) => {
@@ -35,7 +65,8 @@ const supportsPresetResume = (capture) => capture?.source === 'claude_project_js
35
65
  capture?.source === 'gemini_session_json_dir' ||
36
66
  capture?.source === 'opencode_session_db';
37
67
  export const withPresetResumeArgs = (config, preset, lastSessionId, cwd, discriminator, onInvalidSessionId) => {
38
- let nextConfig = withPresetYoloArgs(config, preset);
68
+ const launchConfig = normalizeCodexNodeEntrypoint(config, preset);
69
+ let nextConfig = withPresetYoloArgs(launchConfig, preset);
39
70
  const sessionIdCapture = getEffectiveCapture(nextConfig, preset);
40
71
  if (sessionIdCapture && sessionIdCapture !== nextConfig.sessionIdCapture) {
41
72
  nextConfig = { ...nextConfig, sessionIdCapture };
@@ -52,7 +83,7 @@ export const withPresetResumeArgs = (config, preset, lastSessionId, cwd, discrim
52
83
  onInvalidSessionId?.(lastSessionId);
53
84
  return nextConfig;
54
85
  }
55
- const args = config.args ?? [];
86
+ const args = launchConfig.args ?? [];
56
87
  if (hasResumeArgs(args))
57
88
  return nextConfig;
58
89
  const yoloArgs = getPresetYoloArgs(preset);
@@ -1,10 +1,15 @@
1
1
  import type { AgentSummary, WorkspaceSummary } from '../shared/types.js';
2
2
  import type { RecoveryMessage } from './message-log-store.js';
3
- export declare const buildRecoverySummary: ({ agent, allTaskMessages, messages, tasksContent, workers, workspace, }: {
3
+ export declare const buildRecoverySummary: ({ agent, allTaskMessages, messages, tasksContent, workers, workspace, workflowsEnabled, autostaffEnabled, }: {
4
4
  agent: AgentSummary;
5
5
  allTaskMessages?: RecoveryMessage[];
6
6
  messages: RecoveryMessage[];
7
7
  tasksContent: string;
8
8
  workers: AgentSummary[];
9
9
  workspace: WorkspaceSummary;
10
+ /** Experimental workflow gate — keeps the recovered handover prompt
11
+ * consistent with the live flag (omits the workflow rule when off). */
12
+ workflowsEnabled?: boolean;
13
+ /** Experimental auto-staff gate (default on) — same, for the team-sizing rule. */
14
+ autostaffEnabled?: boolean;
10
15
  }) => string;
@@ -6,7 +6,7 @@ const formatUserInputs = (messages) => {
6
6
  const userInputs = messages.filter((message) => message.type === 'user_input');
7
7
  return userInputs.length > 0
8
8
  ? userInputs.slice(-5).map((message) => `- user: ${message.text}`)
9
- : ['- (最近 1 小时没有新的 user_input)'];
9
+ : ['- (no new user_input in the last hour)'];
10
10
  };
11
11
  const formatTaskEvents = (messages, agent) => {
12
12
  const taskEvents = messages.filter((message) => {
@@ -28,7 +28,7 @@ const formatTaskEvents = (messages, agent) => {
28
28
  const status = message.status ? ` [${message.status}]` : '';
29
29
  return `- report <- ${message.from}${status}: ${message.text}`;
30
30
  })
31
- : ['- (最近没有任务事件)'];
31
+ : ['- (no recent task events)'];
32
32
  };
33
33
  const getOpenTaskTargets = (agent, workers) => agent.role === 'orchestrator' ? workers : [agent];
34
34
  const formatOpenTasks = (messages, agent, workers) => {
@@ -53,38 +53,38 @@ const formatOpenTasks = (messages, agent, workers) => {
53
53
  lines.push(`- ${target.name}: ${task.text}`);
54
54
  }
55
55
  if (target.pendingTaskCount > queue.length) {
56
- lines.push(`- ${target.name}: ${target.pendingTaskCount - queue.length} pending 无可恢复详情`);
56
+ lines.push(`- ${target.name}: ${target.pendingTaskCount - queue.length} pending with no recoverable detail`);
57
57
  }
58
58
  }
59
- return lines.length > 0 ? lines : ['- (当前没有未完成任务)'];
59
+ return lines.length > 0 ? lines : ['- (no open tasks right now)'];
60
60
  };
61
61
  const formatWorkers = (workers) => {
62
62
  if (workers.length === 0)
63
- return ['- 当前没有其他 worker'];
63
+ return ['- (no other workers)'];
64
64
  return workers.map((worker) => `- ${worker.name} (${worker.role}, ${worker.status}, pending_task_count: ${worker.pendingTaskCount})`);
65
65
  };
66
- const getTaskSectionTitle = (agent) => agent.role === 'orchestrator' ? '## 你已派出的任务' : '## 最近派给你的任务';
67
- export const buildRecoverySummary = ({ agent, allTaskMessages, messages, tasksContent, workers, workspace, }) => wrapSystemMessage([
68
- `你是 ${workspace.name} ${agent.name}(${agent.role})。`,
69
- '你刚被 Hive 重启了,且无法通过原生 session resume 恢复。下面是接力上下文。',
66
+ const getTaskSectionTitle = (agent) => agent.role === 'orchestrator' ? '## Tasks you dispatched' : '## Tasks recently sent to you';
67
+ export const buildRecoverySummary = ({ agent, allTaskMessages, messages, tasksContent, workers, workspace, workflowsEnabled = false, autostaffEnabled = false, }) => wrapSystemMessage([
68
+ `You are ${agent.name} (${agent.role}) in workspace ${workspace.name}.`,
69
+ 'Hive just restarted you and could not recover via native session resume. Here is the handover context.',
70
70
  '',
71
- '## 最近 1 小时与 user 的对话',
71
+ '## Conversation with the user in the last hour',
72
72
  ...formatUserInputs(messages),
73
73
  '',
74
74
  getTaskSectionTitle(agent),
75
75
  ...formatTaskEvents(messages, agent),
76
76
  '',
77
- '## 当前未完成任务',
77
+ '## Open tasks',
78
78
  ...formatOpenTasks(allTaskMessages ?? messages, agent, workers),
79
79
  '',
80
- `## 当前 ${TASKS_RELATIVE_PATH} 状态`,
81
- tasksContent.slice(0, TASKS_HEAD_LIMIT) || '()',
80
+ `## Current ${TASKS_RELATIVE_PATH}`,
81
+ tasksContent.slice(0, TASKS_HEAD_LIMIT) || '(empty)',
82
82
  '',
83
- '## 当前活跃 worker',
83
+ '## Active workers',
84
84
  ...formatWorkers(workers),
85
85
  '',
86
- agent.role === 'orchestrator' ? '## Hive worker 派单规则' : '## Hive worker 边界',
87
- ...getHiveTeamRules(agent),
86
+ agent.role === 'orchestrator' ? '## Hive worker dispatch rules' : '## Hive worker boundaries',
87
+ ...getHiveTeamRules(agent, workflowsEnabled, autostaffEnabled),
88
88
  '',
89
- '请基于此继续。如果不确定,问 user',
89
+ 'Continue from here. If unsure, ask the user.',
90
90
  ].join('\n'));
@@ -11,6 +11,11 @@ export interface RestartPolicyInput {
11
11
  listAgentRuns: (agentId: string) => PersistedAgentRun[];
12
12
  listMessagesForRecovery: (workspaceId: string, sinceMs: number) => RecoveryMessage[];
13
13
  readTasks: (workspacePath: string) => string;
14
+ /** Experimental workflow gate — threaded into the recovery handover prompt
15
+ * so it matches the live flag. Optional; omitted → off. */
16
+ getWorkflowsEnabled?: () => boolean;
17
+ /** Experimental auto-staff gate (default on) — same. */
18
+ getAutostaffEnabled?: () => boolean;
14
19
  }
15
20
  export declare const findPreviousRun: (runs: PersistedAgentRun[], currentRunId: string) => PersistedAgentRun | undefined;
16
21
  export declare const writeSystemMessage: ({ deleteMessage, insertMessage, record, runId, text, writeToRun, }: {
@@ -19,5 +24,5 @@ export declare const writeSystemMessage: ({ deleteMessage, insertMessage, record
19
24
  record: MessageLogRecord;
20
25
  runId: string;
21
26
  text: string;
22
- writeToRun: (runId: string, text: string) => void;
27
+ writeToRun: (runId: string, text: string) => Promise<void>;
23
28
  }) => void;
@@ -2,7 +2,15 @@ export const findPreviousRun = (runs, currentRunId) => runs.find((run) => run.ru
2
2
  export const writeSystemMessage = ({ deleteMessage, insertMessage, record, runId, text, writeToRun, }) => {
3
3
  const handle = insertMessage(record);
4
4
  try {
5
- writeToRun(runId, text);
5
+ void writeToRun(runId, text).catch(() => {
6
+ try {
7
+ deleteMessage(handle);
8
+ }
9
+ catch {
10
+ // The runtime may already be closing; the failed post-start write is
11
+ // non-critical once the run is gone.
12
+ }
13
+ });
6
14
  }
7
15
  catch (error) {
8
16
  deleteMessage(handle);
@@ -7,8 +7,8 @@ export interface RestartPolicy {
7
7
  runId: string;
8
8
  startConfig: AgentLaunchConfigInput;
9
9
  workspace: WorkspaceSummary;
10
- writeToRun: (runId: string, text: string) => void;
10
+ writeToRun: (runId: string, text: string) => Promise<void>;
11
11
  }) => boolean;
12
12
  }
13
13
  export declare const createNoopRestartPolicy: () => RestartPolicy;
14
- export declare const createRestartPolicy: ({ deleteMessage, getWorkspaceSnapshot, insertMessage, listAgentRuns, listMessagesForRecovery, readTasks, }: RestartPolicyInput) => RestartPolicy;
14
+ export declare const createRestartPolicy: ({ deleteMessage, getWorkspaceSnapshot, insertMessage, listAgentRuns, listMessagesForRecovery, readTasks, getWorkflowsEnabled, getAutostaffEnabled, }: RestartPolicyInput) => RestartPolicy;
@@ -7,7 +7,7 @@ export const createNoopRestartPolicy = () => ({
7
7
  return false;
8
8
  },
9
9
  });
10
- export const createRestartPolicy = ({ deleteMessage, getWorkspaceSnapshot, insertMessage, listAgentRuns, listMessagesForRecovery, readTasks, }) => ({
10
+ export const createRestartPolicy = ({ deleteMessage, getWorkspaceSnapshot, insertMessage, listAgentRuns, listMessagesForRecovery, readTasks, getWorkflowsEnabled, getAutostaffEnabled, }) => ({
11
11
  injectPostStartMessage({ agentId, runId, startConfig, workspace, writeToRun }) {
12
12
  const previousRun = findPreviousRun(listAgentRuns(agentId), runId);
13
13
  if (!previousRun)
@@ -27,6 +27,8 @@ export const createRestartPolicy = ({ deleteMessage, getWorkspaceSnapshot, inser
27
27
  tasksContent,
28
28
  workers,
29
29
  workspace,
30
+ workflowsEnabled: getWorkflowsEnabled?.() ?? false,
31
+ autostaffEnabled: getAutostaffEnabled?.() ?? false,
30
32
  });
31
33
  writeSystemMessage({
32
34
  deleteMessage,
@@ -41,4 +41,5 @@ export declare const createRoleTemplateStore: (db: Database) => {
41
41
  id: string;
42
42
  isBuiltin: boolean;
43
43
  };
44
+ findByName: (name: string) => RoleTemplateRecord | undefined;
44
45
  };
@@ -34,6 +34,16 @@ export const createRoleTemplateStore = (db) => {
34
34
  .all()
35
35
  .map((row) => toRecord(row));
36
36
  };
37
+ /* TIER 2 #4 — case-insensitive lookup by template name. The workflow
38
+ runner uses this to resolve `agent('...', { agentType: 'security-reviewer' })`
39
+ against the workspace's custom role library so workflows can target
40
+ curated roles instead of always re-rolling fresh claude defaults. */
41
+ const findByName = (name) => {
42
+ const needle = name.trim().toLowerCase();
43
+ if (!needle)
44
+ return undefined;
45
+ return list().find((template) => template.name.trim().toLowerCase() === needle);
46
+ };
37
47
  const create = (input) => {
38
48
  const record = { id: randomUUID(), ...input, isBuiltin: false };
39
49
  const now = Date.now();
@@ -62,5 +72,5 @@ export const createRoleTemplateStore = (db) => {
62
72
  throw new ConflictError(`Builtin role template is read-only: ${id}`);
63
73
  db.prepare('DELETE FROM role_templates WHERE id = ?').run(id);
64
74
  };
65
- return { create, list, remove, update };
75
+ return { create, list, remove, update, findByName };
66
76
  };
@@ -13,6 +13,49 @@ export interface SendTaskBody {
13
13
  to: string;
14
14
  text: string;
15
15
  }
16
+ export interface SpawnAgentBody {
17
+ hive_port?: string;
18
+ project_id: string;
19
+ from_agent_id: string;
20
+ token?: string;
21
+ role?: string;
22
+ name?: string;
23
+ cli?: string;
24
+ /** Opt-in: auto-dismiss the worker after its next dispatch report. Default
25
+ * false → persistent member (lives until explicit `team dismiss`). */
26
+ ephemeral?: boolean;
27
+ }
28
+ export interface DismissAgentBody {
29
+ hive_port?: string;
30
+ project_id: string;
31
+ from_agent_id: string;
32
+ token?: string;
33
+ name: string;
34
+ }
35
+ export interface WorkflowRunBody {
36
+ hive_port?: string;
37
+ project_id: string;
38
+ from_agent_id: string;
39
+ token?: string;
40
+ source: string;
41
+ name?: string;
42
+ args?: unknown;
43
+ }
44
+ export interface WorkflowControlBody {
45
+ project_id: string;
46
+ from_agent_id: string;
47
+ token?: string;
48
+ run_id: string;
49
+ }
50
+ export interface WorkflowScheduleBody {
51
+ project_id: string;
52
+ from_agent_id: string;
53
+ token?: string;
54
+ source: string;
55
+ name: string;
56
+ cron: string;
57
+ args?: unknown;
58
+ }
16
59
  export interface ReportTaskBody {
17
60
  dispatch_id?: string;
18
61
  project_id: string;
@@ -1,5 +1,6 @@
1
1
  import { getRequiredParam, readJsonBody, route, sendJson } from './route-helpers.js';
2
2
  import { requireUiTokenFromRequest } from './ui-auth-helpers.js';
3
+ import { serializeLiveAgentRun } from './workflow-http-serializers.js';
3
4
  import { getWorkspaceShellAgentId } from './workspace-shell-runtime.js';
4
5
  export const runtimeRoutes = [
5
6
  route('GET', '/api/ui/workspaces/:workspaceId/runs', ({ params, request, response, store }) => {
@@ -73,6 +74,6 @@ export const runtimeRoutes = [
73
74
  return;
74
75
  }
75
76
  requireUiTokenFromRequest(request, store.validateUiToken);
76
- sendJson(response, 200, store.getLiveRun(runId));
77
+ sendJson(response, 200, serializeLiveAgentRun(store.getLiveRun(runId)));
77
78
  }),
78
79
  ];
@@ -1,6 +1,11 @@
1
1
  import { resolveCommandPath } from './agent-command-resolver.js';
2
+ import { BadRequestError } from './http-errors.js';
2
3
  import { getRequiredParam, readJsonBody, route, sendJson } from './route-helpers.js';
4
+ import { ensureProtocolFile } from './tasks-file.js';
5
+ import { AUTOSTAFF_ENABLED_KEY, readAutostaffEnabled, serializeAutostaffEnabled, } from './team-autostaff.js';
3
6
  import { requireUiTokenFromRequest } from './ui-auth-helpers.js';
7
+ import { assertValidWorkflowCliPolicy, CANONICAL_WORKFLOW_CLIS, readWorkflowCliPolicy, WORKFLOW_CLI_POLICY_KEY, } from './workflow-cli-policy.js';
8
+ import { readWorkflowEnabled, serializeWorkflowEnabled, WORKFLOW_ENABLED_KEY, } from './workflow-feature.js';
4
9
  const serializeCommandPreset = (preset) => {
5
10
  let available = false;
6
11
  try {
@@ -35,6 +40,26 @@ const serializeRoleTemplate = (template) => ({
35
40
  default_env: template.defaultEnv,
36
41
  is_builtin: template.isBuiltin,
37
42
  });
43
+ /**
44
+ * Rewrite every open workspace's `.hive/PROTOCOL.md` to match the just-saved
45
+ * workflow feature flag + CLI policy. Without this the doc only refreshes on
46
+ * the next workspace open / watcher start, so toggling the feature would leave
47
+ * stale guidance (workflow DSL still present after disabling, or absent right
48
+ * after enabling). Idempotent: ensureProtocolFile only rewrites on change.
49
+ */
50
+ const refreshWorkflowProtocolDocs = (store) => {
51
+ const policy = readWorkflowCliPolicy(store.settings.getAppState(WORKFLOW_CLI_POLICY_KEY)?.value ?? null);
52
+ const enabled = readWorkflowEnabled(store.settings.getAppState(WORKFLOW_ENABLED_KEY)?.value ?? null);
53
+ const autostaff = readAutostaffEnabled(store.settings.getAppState(AUTOSTAFF_ENABLED_KEY)?.value ?? null);
54
+ for (const workspace of store.listWorkspaces()) {
55
+ try {
56
+ ensureProtocolFile(workspace.path, policy, enabled, autostaff);
57
+ }
58
+ catch (error) {
59
+ console.error('[hive] swallowed:settings.refreshProtocol', error);
60
+ }
61
+ }
62
+ };
38
63
  const readCommandPresetBody = async (request) => {
39
64
  const body = await readJsonBody(request);
40
65
  return {
@@ -134,4 +159,55 @@ export const settingsRoutes = [
134
159
  response.statusCode = 204;
135
160
  response.end();
136
161
  }),
162
+ route('GET', '/api/settings/workflow-cli-policy', ({ request, response, store }) => {
163
+ requireUiTokenFromRequest(request, store.validateUiToken);
164
+ const policy = readWorkflowCliPolicy(store.settings.getAppState(WORKFLOW_CLI_POLICY_KEY)?.value ?? null);
165
+ sendJson(response, 200, { ...policy, supported: [...CANONICAL_WORKFLOW_CLIS] });
166
+ }),
167
+ route('PUT', '/api/settings/workflow-cli-policy', async ({ request, response, store }) => {
168
+ requireUiTokenFromRequest(request, store.validateUiToken);
169
+ const body = await readJsonBody(request);
170
+ // Strict validation: a bad payload is rejected (400) rather than persisted.
171
+ const clean = (() => {
172
+ try {
173
+ return assertValidWorkflowCliPolicy(body);
174
+ }
175
+ catch (error) {
176
+ throw new BadRequestError(error instanceof Error ? error.message : String(error));
177
+ }
178
+ })();
179
+ store.settings.setAppState(WORKFLOW_CLI_POLICY_KEY, JSON.stringify(clean));
180
+ refreshWorkflowProtocolDocs(store);
181
+ sendJson(response, 200, { ...clean, supported: [...CANONICAL_WORKFLOW_CLIS] });
182
+ }),
183
+ route('GET', '/api/settings/workflow-feature', ({ request, response, store }) => {
184
+ requireUiTokenFromRequest(request, store.validateUiToken);
185
+ const enabled = readWorkflowEnabled(store.settings.getAppState(WORKFLOW_ENABLED_KEY)?.value ?? null);
186
+ sendJson(response, 200, { enabled });
187
+ }),
188
+ route('PUT', '/api/settings/workflow-feature', async ({ request, response, store }) => {
189
+ requireUiTokenFromRequest(request, store.validateUiToken);
190
+ const body = await readJsonBody(request);
191
+ if (typeof body.enabled !== 'boolean') {
192
+ throw new BadRequestError('workflow-feature requires { enabled: boolean }');
193
+ }
194
+ store.settings.setAppState(WORKFLOW_ENABLED_KEY, serializeWorkflowEnabled(body.enabled));
195
+ refreshWorkflowProtocolDocs(store);
196
+ sendJson(response, 200, { enabled: body.enabled });
197
+ }),
198
+ route('GET', '/api/settings/team-autostaff', ({ request, response, store }) => {
199
+ requireUiTokenFromRequest(request, store.validateUiToken);
200
+ const enabled = readAutostaffEnabled(store.settings.getAppState(AUTOSTAFF_ENABLED_KEY)?.value ?? null);
201
+ sendJson(response, 200, { enabled });
202
+ }),
203
+ route('PUT', '/api/settings/team-autostaff', async ({ request, response, store }) => {
204
+ requireUiTokenFromRequest(request, store.validateUiToken);
205
+ const body = await readJsonBody(request);
206
+ if (typeof body.enabled !== 'boolean') {
207
+ throw new BadRequestError('team-autostaff requires { enabled: boolean }');
208
+ }
209
+ store.settings.setAppState(AUTOSTAFF_ENABLED_KEY, serializeAutostaffEnabled(body.enabled));
210
+ refreshWorkflowProtocolDocs(store);
211
+ sendJson(response, 200, { enabled: body.enabled });
212
+ }),
137
213
  ];