@tt-a1i/hive 1.4.4 → 1.6.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 +47 -0
  2. package/README.en.md +21 -0
  3. package/README.md +16 -0
  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 +45 -17
  7. package/dist/src/cli/hive-update.js +63 -25
  8. package/dist/src/cli/hive.d.ts +25 -0
  9. package/dist/src/cli/hive.js +41 -3
  10. package/dist/src/cli/team.d.ts +1 -0
  11. package/dist/src/cli/team.js +216 -3
  12. package/dist/src/server/agent-command-resolver.js +3 -19
  13. package/dist/src/server/agent-manager-support.d.ts +2 -2
  14. package/dist/src/server/agent-manager-support.js +98 -24
  15. package/dist/src/server/agent-run-starter.d.ts +6 -1
  16. package/dist/src/server/agent-run-starter.js +9 -2
  17. package/dist/src/server/agent-run-store.d.ts +1 -1
  18. package/dist/src/server/agent-runtime-close.d.ts +1 -0
  19. package/dist/src/server/agent-runtime-close.js +25 -1
  20. package/dist/src/server/agent-runtime-contract.d.ts +12 -1
  21. package/dist/src/server/agent-runtime-stop-run.d.ts +1 -1
  22. package/dist/src/server/agent-runtime-stop-run.js +4 -1
  23. package/dist/src/server/agent-runtime.d.ts +2 -1
  24. package/dist/src/server/agent-runtime.js +14 -3
  25. package/dist/src/server/agent-startup-instructions.d.ts +7 -1
  26. package/dist/src/server/agent-startup-instructions.js +17 -9
  27. package/dist/src/server/agent-stdin-dispatcher.d.ts +25 -5
  28. package/dist/src/server/agent-stdin-dispatcher.js +141 -40
  29. package/dist/src/server/cron-util.d.ts +7 -0
  30. package/dist/src/server/cron-util.js +19 -0
  31. package/dist/src/server/dispatch-ledger-store.d.ts +22 -0
  32. package/dist/src/server/dispatch-ledger-store.js +51 -3
  33. package/dist/src/server/env-sync-message.js +9 -9
  34. package/dist/src/server/feature-flags.d.ts +42 -0
  35. package/dist/src/server/feature-flags.js +24 -0
  36. package/dist/src/server/fs-pick-folder.js +4 -0
  37. package/dist/src/server/fs-sandbox.js +36 -7
  38. package/dist/src/server/hive-team-guidance.d.ts +12 -6
  39. package/dist/src/server/hive-team-guidance.js +253 -71
  40. package/dist/src/server/live-run-registry.d.ts +1 -0
  41. package/dist/src/server/live-run-registry.js +1 -1
  42. package/dist/src/server/open-target-commands.js +5 -6
  43. package/dist/src/server/orchestrator-autostart.d.ts +12 -0
  44. package/dist/src/server/orchestrator-autostart.js +15 -13
  45. package/dist/src/server/path-canonicalization.d.ts +3 -0
  46. package/dist/src/server/path-canonicalization.js +29 -0
  47. package/dist/src/server/platform-path.d.ts +3 -0
  48. package/dist/src/server/platform-path.js +13 -0
  49. package/dist/src/server/post-start-input-writer.d.ts +1 -1
  50. package/dist/src/server/post-start-input-writer.js +110 -13
  51. package/dist/src/server/preset-launch-support.d.ts +1 -1
  52. package/dist/src/server/preset-launch-support.js +33 -2
  53. package/dist/src/server/recovery-summary.d.ts +5 -1
  54. package/dist/src/server/recovery-summary.js +18 -17
  55. package/dist/src/server/report-outbox-store.d.ts +36 -0
  56. package/dist/src/server/report-outbox-store.js +33 -0
  57. package/dist/src/server/restart-policy-support.d.ts +5 -1
  58. package/dist/src/server/restart-policy-support.js +9 -1
  59. package/dist/src/server/restart-policy.d.ts +6 -2
  60. package/dist/src/server/restart-policy.js +51 -31
  61. package/dist/src/server/role-template-store.d.ts +1 -0
  62. package/dist/src/server/role-template-store.js +11 -1
  63. package/dist/src/server/route-types.d.ts +43 -0
  64. package/dist/src/server/routes-runtime.js +2 -1
  65. package/dist/src/server/routes-settings.js +76 -0
  66. package/dist/src/server/routes-tasks.js +23 -0
  67. package/dist/src/server/routes-team.js +211 -1
  68. package/dist/src/server/routes-workflow-schedules.d.ts +2 -0
  69. package/dist/src/server/routes-workflow-schedules.js +58 -0
  70. package/dist/src/server/routes-workflows.d.ts +2 -0
  71. package/dist/src/server/routes-workflows.js +83 -0
  72. package/dist/src/server/routes-workspaces.js +5 -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 +2 -1
  76. package/dist/src/server/runtime-store-contract.d.ts +125 -0
  77. package/dist/src/server/runtime-store-contract.js +1 -0
  78. package/dist/src/server/runtime-store-helpers.d.ts +11 -0
  79. package/dist/src/server/runtime-store-helpers.js +106 -2
  80. package/dist/src/server/runtime-store-workflows.d.ts +6 -0
  81. package/dist/src/server/runtime-store-workflows.js +108 -0
  82. package/dist/src/server/runtime-store.d.ts +3 -72
  83. package/dist/src/server/runtime-store.js +71 -4
  84. package/dist/src/server/session-capture-codex.d.ts +3 -3
  85. package/dist/src/server/session-capture-codex.js +9 -7
  86. package/dist/src/server/session-capture-gemini.d.ts +1 -1
  87. package/dist/src/server/session-capture-gemini.js +6 -3
  88. package/dist/src/server/settings-store.d.ts +3 -0
  89. package/dist/src/server/settings-store.js +1 -0
  90. package/dist/src/server/sqlite-schema-v19.d.ts +2 -0
  91. package/dist/src/server/sqlite-schema-v19.js +17 -0
  92. package/dist/src/server/sqlite-schema-v20.d.ts +2 -0
  93. package/dist/src/server/sqlite-schema-v20.js +20 -0
  94. package/dist/src/server/sqlite-schema-v21.d.ts +2 -0
  95. package/dist/src/server/sqlite-schema-v21.js +20 -0
  96. package/dist/src/server/sqlite-schema.d.ts +1 -1
  97. package/dist/src/server/sqlite-schema.js +110 -1
  98. package/dist/src/server/system-message.d.ts +7 -0
  99. package/dist/src/server/system-message.js +8 -1
  100. package/dist/src/server/task-deps.d.ts +32 -0
  101. package/dist/src/server/task-deps.js +40 -0
  102. package/dist/src/server/tasks-file-watcher.d.ts +12 -1
  103. package/dist/src/server/tasks-file-watcher.js +128 -23
  104. package/dist/src/server/tasks-file.d.ts +3 -1
  105. package/dist/src/server/tasks-file.js +33 -9
  106. package/dist/src/server/tasks-websocket-server.js +13 -14
  107. package/dist/src/server/team-authz.d.ts +1 -1
  108. package/dist/src/server/team-authz.js +10 -1
  109. package/dist/src/server/team-autostaff.d.ts +16 -0
  110. package/dist/src/server/team-autostaff.js +16 -0
  111. package/dist/src/server/team-list-serializer.d.ts +1 -1
  112. package/dist/src/server/team-list-serializer.js +3 -1
  113. package/dist/src/server/team-operations.d.ts +21 -1
  114. package/dist/src/server/team-operations.js +183 -16
  115. package/dist/src/server/terminal-protocol.js +9 -3
  116. package/dist/src/server/terminal-stream-hub.js +16 -10
  117. package/dist/src/server/terminal-ws-server.js +10 -8
  118. package/dist/src/server/webhook-notifier.d.ts +34 -0
  119. package/dist/src/server/webhook-notifier.js +47 -0
  120. package/dist/src/server/websocket-upgrade-safety.d.ts +10 -0
  121. package/dist/src/server/websocket-upgrade-safety.js +35 -0
  122. package/dist/src/server/windows-command-line.d.ts +3 -0
  123. package/dist/src/server/windows-command-line.js +9 -0
  124. package/dist/src/server/windows-filename.d.ts +2 -0
  125. package/dist/src/server/windows-filename.js +33 -0
  126. package/dist/src/server/workflow-cli-policy.d.ts +60 -0
  127. package/dist/src/server/workflow-cli-policy.js +110 -0
  128. package/dist/src/server/workflow-dispatch-awaiter.d.ts +12 -0
  129. package/dist/src/server/workflow-dispatch-awaiter.js +80 -0
  130. package/dist/src/server/workflow-feature.d.ts +15 -0
  131. package/dist/src/server/workflow-feature.js +15 -0
  132. package/dist/src/server/workflow-http-serializers.d.ts +64 -0
  133. package/dist/src/server/workflow-http-serializers.js +58 -0
  134. package/dist/src/server/workflow-output-schema.d.ts +18 -0
  135. package/dist/src/server/workflow-output-schema.js +41 -0
  136. package/dist/src/server/workflow-run-log-store.d.ts +19 -0
  137. package/dist/src/server/workflow-run-log-store.js +45 -0
  138. package/dist/src/server/workflow-run-store.d.ts +50 -0
  139. package/dist/src/server/workflow-run-store.js +103 -0
  140. package/dist/src/server/workflow-runner.d.ts +147 -0
  141. package/dist/src/server/workflow-runner.js +411 -0
  142. package/dist/src/server/workflow-schedule-create.d.ts +14 -0
  143. package/dist/src/server/workflow-schedule-create.js +41 -0
  144. package/dist/src/server/workflow-schedule-store.d.ts +43 -0
  145. package/dist/src/server/workflow-schedule-store.js +112 -0
  146. package/dist/src/server/workflow-scheduler.d.ts +36 -0
  147. package/dist/src/server/workflow-scheduler.js +97 -0
  148. package/dist/src/server/workflow-script-loader.d.ts +34 -0
  149. package/dist/src/server/workflow-script-loader.js +106 -0
  150. package/dist/src/server/workspace-path-validation.js +16 -4
  151. package/dist/src/server/workspace-shell-runtime.d.ts +5 -0
  152. package/dist/src/server/workspace-shell-runtime.js +24 -2
  153. package/dist/src/server/workspace-store-contract.d.ts +4 -1
  154. package/dist/src/server/workspace-store-hydration.js +23 -7
  155. package/dist/src/server/workspace-store-mutations.js +2 -5
  156. package/dist/src/server/workspace-store-support.d.ts +4 -0
  157. package/dist/src/server/workspace-store-support.js +13 -1
  158. package/dist/src/server/workspace-store.js +38 -4
  159. package/dist/src/shared/types.d.ts +16 -1
  160. package/package.json +4 -2
  161. package/web/dist/assets/{AddWorkerDialog-DeZhTQLi.js → AddWorkerDialog-CGbaxu0T.js} +2 -2
  162. package/web/dist/assets/AddWorkspaceDialog-CNgExu6b.js +1 -0
  163. package/web/dist/assets/{FirstRunWizard-B5wLcat5.js → FirstRunWizard-DxGApUNc.js} +1 -1
  164. package/web/dist/assets/{MarketplaceDrawer-BC0eBOEW.js → MarketplaceDrawer-Bk6cpukn.js} +1 -1
  165. package/web/dist/assets/WhatsNewDialog-CSGzk-2U.js +1 -0
  166. package/web/dist/assets/WorkerModal-i2F3n3nZ.js +1 -0
  167. package/web/dist/assets/WorkspaceTaskDrawer-C_Ta_K13.js +1 -0
  168. package/web/dist/assets/WorkspaceTerminalPanels-VdDxtrQF.js +1 -0
  169. package/web/dist/assets/index-5zh61jMg.css +1 -0
  170. package/web/dist/assets/index-CAgGM6nb.js +75 -0
  171. package/web/dist/assets/path-join-7MR1s7b1.js +1 -0
  172. package/web/dist/index.html +2 -2
  173. package/web/dist/sw.js +1 -1
  174. package/web/dist/assets/AddWorkspaceDialog-DDpXNEKf.js +0 -1
  175. package/web/dist/assets/WorkerModal-BwMHq-Bi.js +0 -1
  176. package/web/dist/assets/WorkspaceTaskDrawer-CxvT4nqs.js +0 -1
  177. package/web/dist/assets/WorkspaceTerminalPanels-CvibsPSd.js +0 -1
  178. package/web/dist/assets/index-BEsTmfrO.css +0 -1
  179. package/web/dist/assets/index-Ddb7bDN5.js +0 -75
  180. package/web/dist/assets/path-join-S7qkXQtP.js +0 -1
@@ -1,5 +1,5 @@
1
- import { realpathSync } from 'node:fs';
2
1
  import { fileURLToPath } from 'node:url';
2
+ import { sameFilesystemPath } from '../server/path-canonicalization.js';
3
3
  const REQUIRED_ENV_KEYS = [
4
4
  'HIVE_PORT',
5
5
  'HIVE_PROJECT_ID',
@@ -9,7 +9,15 @@ const REQUIRED_ENV_KEYS = [
9
9
  const TEAM_USAGE = [
10
10
  'Usage:',
11
11
  ' team list',
12
+ ' team next (tasks in .hive/tasks.md that are unblocked now — those whose [needs: #n] deps are all done)',
12
13
  ' team send <worker-name> "<task>"',
14
+ ' team spawn <role> [--name <name>] [--cli <claude|codex|opencode|gemini>] [--ephemeral]',
15
+ ' team dismiss <worker-name>',
16
+ " team workflow run --stdin [--args '<JSON>'] (script from stdin — for multi-line scripts)",
17
+ ' team workflow run --inline "<source>" [--args \'<JSON>\']',
18
+ ' team workflow stop <run-id>',
19
+ ' team workflow show <run-id> (full per-agent transcript for one run)',
20
+ ' team workflow schedule --cron "<cron>" --name <n> --stdin (register a recurring run)',
13
21
  ' team cancel --dispatch <dispatch-id> "<reason>"',
14
22
  ' team report "<result>" [--dispatch <dispatch-id>] [--artifact <path>]',
15
23
  ' team report --stdin [--dispatch <dispatch-id>] [--artifact <path>]',
@@ -35,6 +43,15 @@ const getHiveEnv = () => {
35
43
  return values;
36
44
  };
37
45
  const getBaseUrl = (env) => `http://127.0.0.1:${env.HIVE_PORT}`;
46
+ // Read `--flag value` from an argv slice; returns undefined when absent or
47
+ // when the flag is the last token with no following value.
48
+ const readFlag = (args, flag) => {
49
+ const index = args.indexOf(flag);
50
+ if (index === -1)
51
+ return undefined;
52
+ const value = args[index + 1];
53
+ return value && !value.startsWith('--') ? value : undefined;
54
+ };
38
55
  const uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
39
56
  const describeFetchError = (baseUrl, error) => {
40
57
  const cause = error instanceof Error && error.cause instanceof Error ? ` (${error.cause.message})` : '';
@@ -177,6 +194,20 @@ export const parseCancelArgs = (args) => {
177
194
  }
178
195
  return { dispatchId, reason };
179
196
  };
197
+ export const decodeStdinBuffer = (buffer) => {
198
+ if (buffer.length >= 3 && buffer[0] === 0xef && buffer[1] === 0xbb && buffer[2] === 0xbf) {
199
+ return buffer.subarray(3).toString('utf8');
200
+ }
201
+ if (buffer.length >= 2 && buffer[0] === 0xff && buffer[1] === 0xfe) {
202
+ return buffer.subarray(2).toString('utf16le');
203
+ }
204
+ if (buffer.length >= 2 && buffer[0] === 0xfe && buffer[1] === 0xff) {
205
+ const swapped = Buffer.from(buffer.subarray(2));
206
+ swapped.swap16();
207
+ return swapped.toString('utf16le');
208
+ }
209
+ return buffer.toString('utf8');
210
+ };
180
211
  export const readStdinToString = async (command = 'report') => {
181
212
  if (process.stdin.isTTY) {
182
213
  throw new Error(withUsage('--stdin requires piped input, but stdin is a TTY. Did you forget to pipe content in?', command));
@@ -185,7 +216,7 @@ export const readStdinToString = async (command = 'report') => {
185
216
  for await (const chunk of process.stdin) {
186
217
  chunks.push(typeof chunk === 'string' ? Buffer.from(chunk) : chunk);
187
218
  }
188
- const content = Buffer.concat(chunks).toString('utf8');
219
+ const content = decodeStdinBuffer(Buffer.concat(chunks));
189
220
  if (!content.trim()) {
190
221
  throw new Error(withUsage('--stdin received empty input', command));
191
222
  }
@@ -213,6 +244,22 @@ export const runTeamCommand = async (argv) => {
213
244
  console.log(JSON.stringify(await response.json()));
214
245
  return;
215
246
  }
247
+ if (command === 'next') {
248
+ const env = getHiveEnv();
249
+ const baseUrl = getBaseUrl(env);
250
+ const response = await fetchRuntime(baseUrl, `/api/workspaces/${env.HIVE_PROJECT_ID}/tasks/next`, {
251
+ method: 'GET',
252
+ headers: {
253
+ 'x-hive-agent-id': env.HIVE_AGENT_ID,
254
+ 'x-hive-agent-token': env.HIVE_AGENT_TOKEN,
255
+ },
256
+ });
257
+ if (!response.ok) {
258
+ await throwHttpError(response);
259
+ }
260
+ console.log(JSON.stringify(await response.json()));
261
+ return;
262
+ }
216
263
  if (command === 'send') {
217
264
  const [workerName, ...taskParts] = args;
218
265
  const task = taskParts.join(' ').trim();
@@ -241,6 +288,172 @@ export const runTeamCommand = async (argv) => {
241
288
  console.log(JSON.stringify(payload));
242
289
  return;
243
290
  }
291
+ if (command === 'spawn') {
292
+ const role = args[0];
293
+ if (!role || role.startsWith('--')) {
294
+ throw new Error('Usage: team spawn <role> [--name <name>] [--cli <claude|codex|opencode|gemini>] [--ephemeral]\n' +
295
+ ' Default: persistent member (lives until you `team dismiss` it).\n' +
296
+ ' --ephemeral: auto-dismiss after the next dispatch report (one-shot worker).');
297
+ }
298
+ const name = readFlag(args, '--name');
299
+ const cli = readFlag(args, '--cli');
300
+ const ephemeral = args.includes('--ephemeral');
301
+ const env = getHiveEnv();
302
+ const response = await postJson(getBaseUrl(env), '/api/team/spawn', {
303
+ project_id: env.HIVE_PROJECT_ID,
304
+ from_agent_id: env.HIVE_AGENT_ID,
305
+ token: env.HIVE_AGENT_TOKEN,
306
+ role,
307
+ ...(name ? { name } : {}),
308
+ ...(cli ? { cli } : {}),
309
+ ...(ephemeral ? { ephemeral: true } : {}),
310
+ });
311
+ console.log(JSON.stringify(await response.json()));
312
+ return;
313
+ }
314
+ if (command === 'dismiss') {
315
+ const workerName = args[0];
316
+ if (!workerName || workerName.startsWith('--')) {
317
+ throw new Error('Usage: team dismiss <worker-name>');
318
+ }
319
+ const env = getHiveEnv();
320
+ const response = await postJson(getBaseUrl(env), '/api/team/dismiss', {
321
+ project_id: env.HIVE_PROJECT_ID,
322
+ from_agent_id: env.HIVE_AGENT_ID,
323
+ token: env.HIVE_AGENT_TOKEN,
324
+ name: workerName,
325
+ });
326
+ console.log(JSON.stringify(await response.json()));
327
+ return;
328
+ }
329
+ if (command === 'workflow') {
330
+ const sub = args[0];
331
+ const rest = args.slice(1);
332
+ if (sub === 'run') {
333
+ const inlineFlag = rest.indexOf('--inline');
334
+ const stdinFlag = rest.includes('--stdin');
335
+ const name = readFlag(rest, '--name');
336
+ // TIER 2 #8 — `--args '<JSON>'` makes the script's `args` global a
337
+ // real value instead of always undefined. Parses lazily so a bad
338
+ // JSON gives a clear local error before the HTTP round-trip.
339
+ const rawArgs = readFlag(rest, '--args');
340
+ let parsedArgs;
341
+ if (rawArgs !== undefined) {
342
+ try {
343
+ parsedArgs = JSON.parse(rawArgs);
344
+ }
345
+ catch (error) {
346
+ throw new Error(`Usage: team workflow run … --args '<JSON>'\n --args must be valid JSON; got: ${error instanceof Error ? error.message : String(error)}`);
347
+ }
348
+ }
349
+ let source;
350
+ if (inlineFlag !== -1) {
351
+ const literal = rest[inlineFlag + 1];
352
+ if (!literal)
353
+ throw new Error('Usage: team workflow run --inline "<script-source>"');
354
+ source = literal;
355
+ }
356
+ else if (stdinFlag) {
357
+ source = await readStdinToString('workflow run');
358
+ }
359
+ else {
360
+ throw new Error('Usage: team workflow run --stdin | team workflow run --inline "<script-source>"\n' +
361
+ ' Pass workflow source via stdin (POSIX heredoc / `type x.ts |`) or as one inline arg.\n' +
362
+ " Optional: --args '<JSON>' makes the script's `args` global a real value.");
363
+ }
364
+ const env = getHiveEnv();
365
+ const response = await postJson(getBaseUrl(env), '/api/team/workflow/run', {
366
+ project_id: env.HIVE_PROJECT_ID,
367
+ from_agent_id: env.HIVE_AGENT_ID,
368
+ token: env.HIVE_AGENT_TOKEN,
369
+ source,
370
+ ...(name ? { name } : {}),
371
+ ...(parsedArgs !== undefined ? { args: parsedArgs } : {}),
372
+ });
373
+ console.log(JSON.stringify(await response.json()));
374
+ return;
375
+ }
376
+ if (sub === 'stop') {
377
+ const runId = rest[0];
378
+ if (!runId)
379
+ throw new Error('Usage: team workflow stop <run-id>');
380
+ const env = getHiveEnv();
381
+ const response = await postJson(getBaseUrl(env), '/api/team/workflow/stop', {
382
+ project_id: env.HIVE_PROJECT_ID,
383
+ from_agent_id: env.HIVE_AGENT_ID,
384
+ token: env.HIVE_AGENT_TOKEN,
385
+ run_id: runId,
386
+ });
387
+ console.log(JSON.stringify(await response.json()));
388
+ return;
389
+ }
390
+ if (sub === 'show') {
391
+ const runId = rest[0];
392
+ if (!runId)
393
+ throw new Error('Usage: team workflow show <run-id>');
394
+ const env = getHiveEnv();
395
+ const response = await postJson(getBaseUrl(env), '/api/team/workflow/show', {
396
+ project_id: env.HIVE_PROJECT_ID,
397
+ from_agent_id: env.HIVE_AGENT_ID,
398
+ token: env.HIVE_AGENT_TOKEN,
399
+ run_id: runId,
400
+ });
401
+ console.log(JSON.stringify(await response.json()));
402
+ return;
403
+ }
404
+ if (sub === 'schedule') {
405
+ const usage = 'Usage: team workflow schedule --cron "<5-field cron>" --name <name> --stdin\n' +
406
+ ' team workflow schedule --cron "<cron>" --name <name> --inline "<source>" [--args \'<JSON>\']\n' +
407
+ ' Registers a recurring run. Source is persisted so cron can fire it with no orchestrator present.';
408
+ const inlineFlag = rest.indexOf('--inline');
409
+ const stdinFlag = rest.includes('--stdin');
410
+ const cron = readFlag(rest, '--cron');
411
+ const name = readFlag(rest, '--name');
412
+ if (!cron || !name)
413
+ throw new Error(usage);
414
+ const rawArgs = readFlag(rest, '--args');
415
+ let parsedArgs;
416
+ if (rawArgs !== undefined) {
417
+ try {
418
+ parsedArgs = JSON.parse(rawArgs);
419
+ }
420
+ catch (error) {
421
+ throw new Error(`Usage: team workflow schedule … --args '<JSON>'\n --args must be valid JSON; got: ${error instanceof Error ? error.message : String(error)}`);
422
+ }
423
+ }
424
+ let source;
425
+ if (inlineFlag !== -1) {
426
+ const literal = rest[inlineFlag + 1];
427
+ if (!literal)
428
+ throw new Error(usage);
429
+ source = literal;
430
+ }
431
+ else if (stdinFlag) {
432
+ source = await readStdinToString('workflow schedule');
433
+ }
434
+ else {
435
+ throw new Error(usage);
436
+ }
437
+ const env = getHiveEnv();
438
+ const response = await postJson(getBaseUrl(env), '/api/team/workflow/schedule', {
439
+ project_id: env.HIVE_PROJECT_ID,
440
+ from_agent_id: env.HIVE_AGENT_ID,
441
+ token: env.HIVE_AGENT_TOKEN,
442
+ source,
443
+ name,
444
+ cron,
445
+ ...(parsedArgs !== undefined ? { args: parsedArgs } : {}),
446
+ });
447
+ console.log(JSON.stringify(await response.json()));
448
+ return;
449
+ }
450
+ throw new Error('Usage:\n' +
451
+ " team workflow run --stdin [--args '<JSON>'] (read script from stdin)\n" +
452
+ ' team workflow run --inline "<source>" [--args ...] (one-arg form)\n' +
453
+ ' team workflow stop <run-id> (cancel a running workflow)\n' +
454
+ ' team workflow show <run-id> (full per-agent transcript)\n' +
455
+ ' team workflow schedule --cron "<cron>" --name <n> --stdin (register a recurring run)');
456
+ }
244
457
  if (command === 'cancel') {
245
458
  const cancel = parseCancelArgs(args);
246
459
  const env = getHiveEnv();
@@ -294,7 +507,7 @@ export const runTeamCommand = async (argv) => {
294
507
  throw new Error('Unsupported team command');
295
508
  };
296
509
  const isMainModule = process.argv[1]
297
- ? fileURLToPath(import.meta.url) === realpathSync(process.argv[1])
510
+ ? sameFilesystemPath(fileURLToPath(import.meta.url), process.argv[1])
298
511
  : false;
299
512
  if (isMainModule) {
300
513
  void runTeamCommand(process.argv.slice(2)).catch((error) => {
@@ -1,5 +1,6 @@
1
1
  import { accessSync, constants } from 'node:fs';
2
2
  import { basename, delimiter, extname, isAbsolute, join } from 'node:path';
3
+ import { buildCmdCallCommand } from './windows-command-line.js';
3
4
  const hasPathSeparator = (command) => command.includes('/') || command.includes('\\');
4
5
  const canExecute = (path, platform = process.platform) => {
5
6
  try {
@@ -54,28 +55,11 @@ const isWindowsBatchFile = (command) => {
54
55
  const extension = extname(command).toLowerCase();
55
56
  return extension === '.cmd' || extension === '.bat';
56
57
  };
57
- /**
58
- * cmd.exe-style escape: doubles inner `"` (cmd's only literal-quote idiom)
59
- * and only wraps in `"..."` when the token contains whitespace or a cmd
60
- * metachar. Plain alnum/path tokens stay unquoted, which is the form
61
- * cmd.exe parses most predictably.
62
- *
63
- * Crucially, we do NOT use the `\"` form that the previous implementation
64
- * used and that node-pty's `argsToCommandLine` produces: cmd doesn't honor
65
- * backslash-quote escapes in its own command-line parsing.
66
- */
67
- const escapeCmdToken = (value) => {
68
- if (value.length === 0)
69
- return '""';
70
- const doubled = value.replace(/"/g, '""');
71
- return /[\s"&<>|^()]/.test(value) ? `"${doubled}"` : doubled;
72
- };
73
58
  const buildWindowsBatchCommandLine = (command, args) => {
74
- const tokens = [command, ...args].map(escapeCmdToken).join(' ');
75
59
  // `call` is cmd's built-in batch invocation; it handles quoted .cmd / .bat
76
60
  // paths reliably (this is the same pattern Node.js's child_process uses
77
61
  // internally on Windows since the CVE-2024-27980 fix).
78
- return `/d /s /c call ${tokens}`;
62
+ return `/d /s /c ${buildCmdCallCommand(command, args)}`;
79
63
  };
80
64
  /**
81
65
  * Recognize the exact shape that `createStartupCommandLaunch` produces on
@@ -93,7 +77,7 @@ const isCmdExeShellLaunch = (resolvedCommand, args) => basename(resolvedCommand)
93
77
  args.length === 4 &&
94
78
  args[0] === '/d' &&
95
79
  args[1] === '/s' &&
96
- args[2] === '/c';
80
+ (args[2] === '/c' || args[2] === '/k');
97
81
  export const resolveSpawnCommand = (command, cwd, env, args = [], platform = process.platform) => {
98
82
  const resolvedCommand = resolveCommandPath(command, cwd, env, platform);
99
83
  if (platform === 'win32' && isWindowsBatchFile(resolvedCommand)) {
@@ -2,7 +2,7 @@ import type { IPty } from 'node-pty';
2
2
  import type { AgentRunRecord, AgentRunSnapshot } from './agent-manager.js';
3
3
  import type { PtyOutputBus } from './pty-output-bus.js';
4
4
  export declare const MAX_RUN_OUTPUT_LENGTH = 1000000;
5
- type ExecRunner = (cmd: string, args: readonly string[]) => void;
5
+ type ExecRunner = (cmd: string, args: readonly string[], done: (success: boolean) => void) => void;
6
6
  /**
7
7
  * Windows analogue of POSIX `process.kill(-pgid, SIGKILL)`. node-pty on
8
8
  * Windows hands `pty.kill()` to TerminateProcess against the PTY's main
@@ -28,7 +28,7 @@ type ExecRunner = (cmd: string, args: readonly string[]) => void;
28
28
  * Exported for unit testing — the `runner` parameter lets tests assert
29
29
  * the exact argv without mocking node:child_process.
30
30
  */
31
- export declare const taskkillProcessTree: (pid: number, platform?: NodeJS.Platform, runner?: ExecRunner) => boolean;
31
+ export declare const taskkillProcessTree: (pid: number, platform?: NodeJS.Platform, runner?: ExecRunner, onFailure?: () => void) => boolean;
32
32
  export declare const toAgentRunSnapshot: (run: AgentRunRecord) => AgentRunSnapshot;
33
33
  export declare const finishAgentRun: (run: AgentRunRecord, exitCode: number | null, ptyOutputBus: PtyOutputBus) => void;
34
34
  export declare const attachAgentPty: (run: AgentRunRecord, pty: IPty, ptyOutputBus: PtyOutputBus) => void;
@@ -1,8 +1,23 @@
1
- import { execFileSync } from 'node:child_process';
1
+ import { execFile, execFileSync } from 'node:child_process';
2
2
  export const MAX_RUN_OUTPUT_LENGTH = 1_000_000;
3
3
  const FORCE_KILL_DELAY_MS = 750;
4
- const defaultExecRunner = (cmd, args) => {
5
- execFileSync(cmd, [...args], { stdio: 'ignore', windowsHide: true });
4
+ const TASKKILL_TIMEOUT_MS = 3000;
5
+ const PTY_READ_EOF_EXIT_GRACE_MS = 1000;
6
+ const isPtyReadEofError = (error) => {
7
+ const candidate = error;
8
+ return process.platform !== 'win32' && candidate?.code === 'EIO' && candidate.syscall === 'read';
9
+ };
10
+ const serializePtyInput = (input) => Buffer.isBuffer(input) ? input.toString('latin1') : input;
11
+ const defaultExecRunner = (cmd, args, done) => {
12
+ let settled = false;
13
+ const settle = (success) => {
14
+ if (settled)
15
+ return;
16
+ settled = true;
17
+ done(success);
18
+ };
19
+ const child = execFile(cmd, [...args], { maxBuffer: 64 * 1024, timeout: TASKKILL_TIMEOUT_MS, windowsHide: true }, (error) => settle(!error));
20
+ child.once('error', () => settle(false));
6
21
  };
7
22
  /**
8
23
  * Windows analogue of POSIX `process.kill(-pgid, SIGKILL)`. node-pty on
@@ -29,11 +44,14 @@ const defaultExecRunner = (cmd, args) => {
29
44
  * Exported for unit testing — the `runner` parameter lets tests assert
30
45
  * the exact argv without mocking node:child_process.
31
46
  */
32
- export const taskkillProcessTree = (pid, platform = process.platform, runner = defaultExecRunner) => {
47
+ export const taskkillProcessTree = (pid, platform = process.platform, runner = defaultExecRunner, onFailure) => {
33
48
  if (platform !== 'win32' || pid <= 0)
34
49
  return false;
35
50
  try {
36
- runner('taskkill', ['/pid', String(pid), '/t', '/f']);
51
+ runner('taskkill', ['/pid', String(pid), '/t', '/f'], (success) => {
52
+ if (!success)
53
+ onFailure?.();
54
+ });
37
55
  return true;
38
56
  }
39
57
  catch {
@@ -61,6 +79,7 @@ export const finishAgentRun = (run, exitCode, ptyOutputBus) => {
61
79
  export const attachAgentPty = (run, pty, ptyOutputBus) => {
62
80
  let stdinClosed = false;
63
81
  let forceKillTimer;
82
+ let ptyReadEofTimer;
64
83
  const resolveProcessGroupId = () => {
65
84
  if (process.platform === 'win32' || pty.pid <= 0)
66
85
  return null;
@@ -99,26 +118,29 @@ export const attachAgentPty = (run, pty, ptyOutputBus) => {
99
118
  ignoreBestEffortGroupKillError(error);
100
119
  }
101
120
  };
102
- const killPty = (signal) => {
121
+ const killPtyDirect = (signal) => {
103
122
  try {
104
- if (process.platform === 'win32') {
105
- // taskkill /pid <pid> /t /f walks the parent's process tree
106
- // BEFORE terminating it — so we have to run it while the parent
107
- // is still alive. Calling pty.kill() first (the previous
108
- // ordering) detaches the children: taskkill /T then fails with
109
- // "process not found" and the npm-installs / build scripts
110
- // become orphans. taskkill /f also terminates the parent, so
111
- // pty.kill() is the fallback for the rare case where taskkill
112
- // is missing from PATH or refused (e.g. restricted PowerShell).
113
- if (!taskkillProcessTree(pty.pid))
114
- pty.kill();
115
- }
116
- else
117
- pty.kill(signal);
123
+ pty.kill(signal);
118
124
  }
119
125
  catch (error) {
120
126
  ignoreMissingProcess(error);
121
127
  }
128
+ };
129
+ const killPty = (signal) => {
130
+ if (process.platform === 'win32') {
131
+ // taskkill /pid <pid> /t /f walks the parent's process tree
132
+ // BEFORE terminating it — so we have to run it while the parent
133
+ // is still alive. Calling pty.kill() first (the previous
134
+ // ordering) detaches the children: taskkill /T then fails with
135
+ // "process not found" and the npm-installs / build scripts
136
+ // become orphans. taskkill /f also terminates the parent, so
137
+ // pty.kill() is the fallback for the rare case where taskkill
138
+ // is missing from PATH or refused (e.g. restricted PowerShell).
139
+ if (!taskkillProcessTree(pty.pid, process.platform, defaultExecRunner, () => killPtyDirect()))
140
+ killPtyDirect();
141
+ }
142
+ else
143
+ killPtyDirect(signal);
122
144
  killProcessGroup(signal);
123
145
  };
124
146
  const clearForceKillTimer = () => {
@@ -127,8 +149,34 @@ export const attachAgentPty = (run, pty, ptyOutputBus) => {
127
149
  clearTimeout(forceKillTimer);
128
150
  forceKillTimer = undefined;
129
151
  };
152
+ const clearPtyReadEofTimer = () => {
153
+ if (!ptyReadEofTimer)
154
+ return;
155
+ clearTimeout(ptyReadEofTimer);
156
+ ptyReadEofTimer = undefined;
157
+ };
158
+ const schedulePtyReadEofExitGuard = (error) => {
159
+ if (ptyReadEofTimer)
160
+ return;
161
+ ptyReadEofTimer = setTimeout(() => {
162
+ ptyReadEofTimer = undefined;
163
+ if (stopped())
164
+ return;
165
+ console.error(`[hive] PTY read EOF without exit for run ${run.runId}`, error);
166
+ finishAgentRun(run, null, ptyOutputBus);
167
+ try {
168
+ killPty('SIGTERM');
169
+ scheduleForceKill();
170
+ }
171
+ catch (killError) {
172
+ ignoreMissingProcess(killError);
173
+ }
174
+ }, PTY_READ_EOF_EXIT_GRACE_MS);
175
+ ptyReadEofTimer.unref?.();
176
+ };
130
177
  const cleanupProcessGroup = () => {
131
178
  clearForceKillTimer();
179
+ clearPtyReadEofTimer();
132
180
  killProcessGroup('SIGKILL');
133
181
  };
134
182
  const scheduleForceKill = () => {
@@ -141,11 +189,11 @@ export const attachAgentPty = (run, pty, ptyOutputBus) => {
141
189
  // Same ordering as killPty(): tree-kill before terminating the
142
190
  // parent, so taskkill /T can still enumerate the process tree.
143
191
  // pty.kill() is the fallback for taskkill-missing hosts.
144
- if (!taskkillProcessTree(pty.pid))
145
- pty.kill();
192
+ if (!taskkillProcessTree(pty.pid, process.platform, defaultExecRunner, () => killPtyDirect()))
193
+ killPtyDirect();
146
194
  }
147
195
  else
148
- pty.kill('SIGKILL');
196
+ killPtyDirect('SIGKILL');
149
197
  }
150
198
  catch (error) {
151
199
  ignoreMissingProcess(error);
@@ -163,6 +211,9 @@ export const attachAgentPty = (run, pty, ptyOutputBus) => {
163
211
  },
164
212
  pid: pty.pid,
165
213
  resize(cols, rows) {
214
+ if (!Number.isInteger(cols) || !Number.isInteger(rows) || cols <= 0 || rows <= 0) {
215
+ throw new Error(`Invalid terminal size for run: ${run.runId}`);
216
+ }
166
217
  pty.resize(cols, rows);
167
218
  },
168
219
  resume() {
@@ -173,6 +224,7 @@ export const attachAgentPty = (run, pty, ptyOutputBus) => {
173
224
  cleanupProcessGroup();
174
225
  return;
175
226
  }
227
+ clearPtyReadEofTimer();
176
228
  killPty('SIGTERM');
177
229
  stdinClosed = true;
178
230
  scheduleForceKill();
@@ -181,7 +233,7 @@ export const attachAgentPty = (run, pty, ptyOutputBus) => {
181
233
  if (stdinClosed || run.status === 'exited' || run.status === 'error') {
182
234
  throw new Error(`PTY is not active for run: ${run.runId}`);
183
235
  }
184
- pty.write(text);
236
+ pty.write(serializePtyInput(text));
185
237
  },
186
238
  };
187
239
  pty.onData((chunk) => {
@@ -192,8 +244,30 @@ export const attachAgentPty = (run, pty, ptyOutputBus) => {
192
244
  run.output = run.output.slice(-MAX_RUN_OUTPUT_LENGTH);
193
245
  ptyOutputBus.publish(run.runId, chunk);
194
246
  });
247
+ pty.on?.('error', (error) => {
248
+ if (stopped())
249
+ return;
250
+ if (isPtyReadEofError(error)) {
251
+ // Unix PTYs can surface a closed slave as read/EIO just before
252
+ // node-pty delivers the real onExit event. Treat it as EOF, not
253
+ // as the run's terminal status.
254
+ stdinClosed = true;
255
+ schedulePtyReadEofExitGuard(error);
256
+ return;
257
+ }
258
+ console.error(`[hive] PTY error for run ${run.runId}`, error);
259
+ stdinClosed = true;
260
+ finishAgentRun(run, null, ptyOutputBus);
261
+ try {
262
+ killPty('SIGTERM');
263
+ }
264
+ catch (killError) {
265
+ ignoreMissingProcess(killError);
266
+ }
267
+ });
195
268
  pty.onExit((event) => {
196
269
  stdinClosed = true;
270
+ clearPtyReadEofTimer();
197
271
  cleanupProcessGroup();
198
272
  finishAgentRun(run, event.exitCode, ptyOutputBus);
199
273
  });
@@ -6,6 +6,7 @@ import type { AgentSessionStorePort } from './agent-runtime-ports.js';
6
6
  import type { LiveAgentRun } from './agent-runtime-types.js';
7
7
  import type { AgentTokenRegistry } from './agent-tokens.js';
8
8
  import type { CommandPresetRecord } from './command-preset-store.js';
9
+ import { type FeatureFlags } from './feature-flags.js';
9
10
  import type { LiveRunRegistry } from './live-run-registry.js';
10
11
  import type { RestartPolicy } from './restart-policy.js';
11
12
  interface AgentRunStarterInput {
@@ -18,6 +19,10 @@ interface AgentRunStarterInput {
18
19
  getCommandPreset: (id: string) => CommandPresetRecord | undefined;
19
20
  getAgent: ((workspaceId: string, agentId: string) => AgentSummary | undefined) | undefined;
20
21
  restartPolicy: RestartPolicy;
22
+ /** Resolves the live experimental flags for the orchestrator's startup
23
+ * prompt (`workflowsEnabled` gates the `team workflow` line + authoring
24
+ * rule; `autostaffEnabled` gates the team-sizing rule). */
25
+ getFlags?: () => FeatureFlags;
21
26
  }
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>;
27
+ export declare const createAgentRunStarter: ({ agentManager, registry, onAgentExit, store, sessionStore, tokenRegistry, getCommandPreset, getAgent, restartPolicy, getFlags, }: AgentRunStarterInput) => (workspace: WorkspaceSummary, agentId: string, config: AgentLaunchConfigInput, hivePort: string) => Promise<LiveAgentRun>;
23
28
  export {};
@@ -1,8 +1,9 @@
1
1
  import { buildAgentRunBootstrap, startAgentRunCapture } from './agent-run-bootstrap.js';
2
2
  import { handleAgentRunExit } from './agent-run-exit-handler.js';
3
3
  import { buildAgentStartupInstructions } from './agent-startup-instructions.js';
4
+ import { FEATURE_FLAGS_ALL_OFF } from './feature-flags.js';
4
5
  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) => {
6
+ export const createAgentRunStarter = ({ agentManager, registry, onAgentExit, store, sessionStore, tokenRegistry, getCommandPreset, getAgent, restartPolicy, getFlags, }) => async (workspace, agentId, config, hivePort) => {
6
7
  if (!agentManager)
7
8
  throw new Error('Agent manager is required to start agents');
8
9
  const agent = getAgent?.(workspace.id, agentId);
@@ -99,7 +100,13 @@ export const createAgentRunStarter = ({ agentManager, registry, onAgentExit, sto
99
100
  !injectedRestartMessage &&
100
101
  agent &&
101
102
  isInteractiveAgentCommand(startConfig.interactiveCommand ?? startConfig.command)) {
102
- postStartWriter(run.runId, buildAgentStartupInstructions({ agent, workspace }));
103
+ void postStartWriter(run.runId, buildAgentStartupInstructions({
104
+ agent,
105
+ workspace,
106
+ flags: getFlags?.() ?? FEATURE_FLAGS_ALL_OFF,
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,21 @@ 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;
37
+ /** Awaitable report delivery — rejects if the orchestrator PTY write fails,
38
+ * so the caller can persist the report to the redelivery outbox. */
39
+ deliverReportToOrchestrator: (workspaceId: string, workerName: string, text: string, artifacts: string[], input?: {
40
+ requireActiveRun?: boolean;
41
+ }) => Promise<void>;
42
+ /** Awaitable opaque delivery — used to drain the report outbox so an entry
43
+ * is marked delivered only after the PTY write resolves. */
44
+ deliverSystemMessageToAgent: (workspaceId: string, agentId: string, text: string, input?: {
45
+ requireActiveRun?: boolean;
46
+ }) => Promise<void>;
36
47
  }
37
48
  export type { StartAgentOptions };