@tt-a1i/hive 2.1.1 → 2.1.4

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 (93) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/README.en.md +5 -460
  3. package/README.md +338 -187
  4. package/README.zh.md +389 -0
  5. package/dist/src/cli/hive-update.d.ts +3 -2
  6. package/dist/src/cli/hive-update.js +20 -60
  7. package/dist/src/cli/hive.js +5 -1
  8. package/dist/src/server/agent-manager-support.js +16 -9
  9. package/dist/src/server/agent-runtime-contract.d.ts +4 -0
  10. package/dist/src/server/agent-runtime.js +27 -0
  11. package/dist/src/server/agent-stdin-dispatcher.d.ts +3 -0
  12. package/dist/src/server/agent-stdin-dispatcher.js +3 -0
  13. package/dist/src/server/diagnostics-support-bundle.d.ts +3 -0
  14. package/dist/src/server/diagnostics-support-bundle.js +3 -0
  15. package/dist/src/server/hive-team-guidance.js +34 -4
  16. package/dist/src/server/message-log-store.d.ts +1 -0
  17. package/dist/src/server/message-log-store.js +13 -0
  18. package/dist/src/server/orchestrator-autostart.d.ts +2 -0
  19. package/dist/src/server/orchestrator-autostart.js +30 -11
  20. package/dist/src/server/post-start-input-writer.d.ts +1 -0
  21. package/dist/src/server/post-start-input-writer.js +78 -12
  22. package/dist/src/server/role-templates.d.ts +8 -1
  23. package/dist/src/server/role-templates.js +102 -48
  24. package/dist/src/server/route-types.d.ts +6 -0
  25. package/dist/src/server/routes-scenarios.d.ts +2 -1
  26. package/dist/src/server/routes-scenarios.js +11 -6
  27. package/dist/src/server/routes-team.js +21 -15
  28. package/dist/src/server/routes-workspaces.js +22 -2
  29. package/dist/src/server/runtime-store-contract.d.ts +2 -0
  30. package/dist/src/server/runtime-store-helpers.d.ts +11 -2
  31. package/dist/src/server/runtime-store-helpers.js +29 -5
  32. package/dist/src/server/runtime-store-workflows.js +20 -0
  33. package/dist/src/server/runtime-store.js +2 -0
  34. package/dist/src/server/scenario-presets.d.ts +3 -1
  35. package/dist/src/server/scenario-presets.js +12 -1
  36. package/dist/src/server/spawn-worker-defaults.d.ts +2 -0
  37. package/dist/src/server/spawn-worker-defaults.js +10 -5
  38. package/dist/src/server/team-operations.d.ts +3 -0
  39. package/dist/src/server/team-operations.js +97 -29
  40. package/dist/src/server/terminal-input-profile.d.ts +2 -0
  41. package/dist/src/server/update-install-plan.d.ts +20 -0
  42. package/dist/src/server/update-install-plan.js +151 -0
  43. package/dist/src/server/version-service.d.ts +5 -0
  44. package/dist/src/server/version-service.js +17 -13
  45. package/dist/src/server/workflow-agent-call-executor.d.ts +81 -0
  46. package/dist/src/server/workflow-agent-call-executor.js +259 -0
  47. package/dist/src/server/workflow-dag-layer-tracker.d.ts +14 -0
  48. package/dist/src/server/workflow-dag-layer-tracker.js +37 -0
  49. package/dist/src/server/workflow-dag.d.ts +8 -0
  50. package/dist/src/server/workflow-dag.js +65 -0
  51. package/dist/src/server/workflow-dispatch-awaiter.d.ts +7 -1
  52. package/dist/src/server/workflow-dispatch-awaiter.js +60 -9
  53. package/dist/src/server/workflow-runner.d.ts +5 -38
  54. package/dist/src/server/workflow-runner.js +113 -445
  55. package/dist/src/server/workflow-script-host-dispatcher.d.ts +14 -0
  56. package/dist/src/server/workflow-script-host-dispatcher.js +42 -0
  57. package/dist/src/server/workflow-script-loader.d.ts +2 -2
  58. package/dist/src/server/workflow-script-loader.js +2 -2
  59. package/dist/src/server/workflow-script-worker.d.ts +14 -0
  60. package/dist/src/server/workflow-script-worker.js +81 -0
  61. package/dist/src/server/workflow-vm-worker-source.d.ts +1 -0
  62. package/dist/src/server/workflow-vm-worker-source.js +1 -0
  63. package/dist/src/server/workflow-vm-worker.cjs +238 -0
  64. package/dist/src/server/workspace-ui-language.d.ts +7 -0
  65. package/dist/src/server/workspace-ui-language.js +9 -0
  66. package/dist/src/shared/scenario-presets.d.ts +6 -1
  67. package/dist/src/shared/scenario-presets.js +80 -28
  68. package/dist/src/shared/types.d.ts +11 -7
  69. package/dist/src/shared/ui-language.d.ts +4 -0
  70. package/dist/src/shared/ui-language.js +3 -0
  71. package/package.json +6 -3
  72. package/web/dist/assets/{AddWorkerDialog-CTOsPurT.js → AddWorkerDialog-CkCGKa0N.js} +2 -2
  73. package/web/dist/assets/AddWorkspaceFlow-Cd73iChz.js +1 -0
  74. package/web/dist/assets/{FirstRunWizard-C24MUssx.js → FirstRunWizard-Db4HWbBQ.js} +1 -1
  75. package/web/dist/assets/{MarketplaceDrawer-7gVUjgAx.js → MarketplaceDrawer-DHwl1if1.js} +1 -1
  76. package/web/dist/assets/TaskGraphDrawer-B_OibVQO.js +1 -0
  77. package/web/dist/assets/{WhatsNewDialog-vnShQWjg.js → WhatsNewDialog-9QXlLVfy.js} +1 -1
  78. package/web/dist/assets/{WorkerModal-DcxIBPlu.js → WorkerModal-BGdtte3-.js} +1 -1
  79. package/web/dist/assets/{WorkflowsDrawer-C8csID1I.js → WorkflowsDrawer-DRuS0Ao5.js} +1 -1
  80. package/web/dist/assets/WorkspaceMemoryDrawer-C8PuHo0x.js +1 -0
  81. package/web/dist/assets/WorkspaceTaskDrawer-6mbgRoOq.js +1 -0
  82. package/web/dist/assets/index-BiGD5NhO.js +84 -0
  83. package/web/dist/assets/index-CG2TurwT.css +1 -0
  84. package/web/dist/assets/{search-BRQbevR2.js → search-BHvXxsdK.js} +1 -1
  85. package/web/dist/assets/{square-terminal-Co4pmGzn.js → square-terminal-CzkQzjCU.js} +1 -1
  86. package/web/dist/index.html +2 -2
  87. package/web/dist/sw.js +1 -1
  88. package/web/dist/assets/AddWorkspaceFlow-D_fSv-cO.js +0 -1
  89. package/web/dist/assets/TaskGraphDrawer-Beh95yUu.js +0 -1
  90. package/web/dist/assets/WorkspaceMemoryDrawer-C_Aq9-o6.js +0 -1
  91. package/web/dist/assets/WorkspaceTaskDrawer-CIMYzlpt.js +0 -1
  92. package/web/dist/assets/index-3Hc1oXlt.css +0 -1
  93. package/web/dist/assets/index-D9zUE0W6.js +0 -79
@@ -45,6 +45,7 @@ export const formatNoOpenDispatchError = (workerName, requestedDispatchId, openD
45
45
  'if you have progress or standby info to share, use `team status "<state>"` instead.');
46
46
  };
47
47
  const reportForwardErrorMessage = (error) => error instanceof Error ? error.message : String(error);
48
+ const isPromptReadinessTimeoutError = (error) => error instanceof Error && /^Timed out waiting for .+ prompt readiness:/u.test(error.message);
48
49
  const buildPendingWarning = (workerName, pendingTaskCount, action) => pendingTaskCount > 0
49
50
  ? `Hive recorded the ${action}, but ${workerName} still has ${pendingTaskCount} open dispatch${pendingTaskCount === 1 ? '' : 'es'}. ` +
50
51
  'A worker stays working until every dispatch is closed with `team report --dispatch <id>` or `team cancel --dispatch <id>`.'
@@ -72,8 +73,9 @@ export const createTeamOperations = ({ agentRuntime, createDispatch, deleteDispa
72
73
  }
73
74
  };
74
75
  const ensureWorkerRun = async (workspaceId, workerId, hivePort) => {
75
- if (agentRuntime.getActiveRunByAgentId(workspaceId, workerId)) {
76
- return;
76
+ const activeRun = agentRuntime.getActiveRunByAgentId(workspaceId, workerId);
77
+ if (activeRun) {
78
+ return activeRun;
77
79
  }
78
80
  const config = agentRuntime.peekAgentLaunchConfig(workspaceId, workerId);
79
81
  if (!config) {
@@ -86,6 +88,7 @@ export const createTeamOperations = ({ agentRuntime, createDispatch, deleteDispa
86
88
  workspaceStore.markAgentStopped(workspaceId, workerId);
87
89
  throw new ConflictError(`${config.command} failed to start`);
88
90
  }
91
+ return run;
89
92
  }
90
93
  catch (error) {
91
94
  workspaceStore.markAgentStopped(workspaceId, workerId);
@@ -191,6 +194,9 @@ export const createTeamOperations = ({ agentRuntime, createDispatch, deleteDispa
191
194
  item.status === 'queued' &&
192
195
  item.workflowRunId === null &&
193
196
  item.fromAgentId !== getWorkflowAgentId(workspaceId) &&
197
+ (options.createdBeforeMs === undefined || item.createdAt < options.createdBeforeMs) &&
198
+ (options.beforeSequence === undefined ||
199
+ (item.sequence !== null && item.sequence < options.beforeSequence)) &&
194
200
  item.id !== options.excludeDispatchId);
195
201
  if (queued.length === 0)
196
202
  return;
@@ -206,10 +212,20 @@ export const createTeamOperations = ({ agentRuntime, createDispatch, deleteDispa
206
212
  to parked for the next start instead of being cancelled: one bad start
207
213
  must not destroy parked work. If the row is no longer 'submitted'
208
214
  (reported/cancelled meanwhile), the repark loses and we leave it be. */
209
- const failDispatch = (dispatchId, error) => {
215
+ const failDispatch = (item, error) => {
216
+ if (isPromptReadinessTimeoutError(error)) {
217
+ try {
218
+ cancelUndeliveredDispatch(workspaceId, workerId, item.id, reportForwardErrorMessage(error), item.workflowRunId ?? undefined, item.fromAgentId ?? undefined);
219
+ }
220
+ catch (cancelError) {
221
+ console.error('[hive] swallowed:teamReplay.cancelUndelivered', cancelError);
222
+ }
223
+ console.error('[hive] swallowed:teamReplay.writePrompt', error);
224
+ return;
225
+ }
210
226
  try {
211
- if (!reparkClaimedDispatch(dispatchId)) {
212
- console.error('[hive] swallowed:teamReplay.reparkLost', dispatchId);
227
+ if (!reparkClaimedDispatch(item.id)) {
228
+ console.error('[hive] swallowed:teamReplay.reparkLost', item.id);
213
229
  }
214
230
  }
215
231
  catch (reparkError) {
@@ -231,10 +247,10 @@ export const createTeamOperations = ({ agentRuntime, createDispatch, deleteDispa
231
247
  }
232
248
  try {
233
249
  const writePrompt = agentRuntime.writeSendPrompt(workspaceId, workerId, item.id, senderName, worker.description, item.text);
234
- void writePrompt.catch((error) => failDispatch(item.id, error));
250
+ void writePrompt.catch((error) => failDispatch(item, error));
235
251
  }
236
252
  catch (error) {
237
- failDispatch(item.id, error);
253
+ failDispatch(item, error);
238
254
  }
239
255
  }
240
256
  };
@@ -268,13 +284,12 @@ export const createTeamOperations = ({ agentRuntime, createDispatch, deleteDispa
268
284
  const dispatchId = dispatch.id;
269
285
  if (fromAgentId && sender) {
270
286
  const shouldAutoStartWorker = input.autoStartWorker !== false;
271
- const hadActiveRun = Boolean(agentRuntime.getActiveRunByAgentId(workspaceId, workerId));
272
- if (!hadActiveRun && shouldAutoStartWorker) {
273
- await ensureWorkerRun(workspaceId, workerId, input.hivePort ?? '');
274
- // The fresh run may also have OLDER parked dispatches waiting —
275
- // deliver them ahead of this one. Claims keep delivery single-shot;
276
- // the current dispatch is claimed by this same path just below.
277
- replayQueuedDispatches(workspaceId, workerId, { excludeDispatchId: dispatchId });
287
+ let activeRun = agentRuntime.getActiveRunByAgentId(workspaceId, workerId);
288
+ if (!activeRun && shouldAutoStartWorker) {
289
+ activeRun = await ensureWorkerRun(workspaceId, workerId, input.hivePort ?? '');
290
+ // Revalidate before accepting the task: a worker deleted while the
291
+ // PTY was starting must not leave an accepted-but-undeliverable row.
292
+ workspaceStore.getWorker(workspaceId, workerId);
278
293
  }
279
294
  if (!shouldAutoStartWorker && !agentRuntime.getActiveRunByAgentId(workspaceId, workerId)) {
280
295
  workspaceStore.markTaskDispatched(workspaceId, workerId);
@@ -284,19 +299,9 @@ export const createTeamOperations = ({ agentRuntime, createDispatch, deleteDispa
284
299
  return Object.assign(dispatch, { queuedForStoppedWorker: true });
285
300
  }
286
301
  const isWorkflowDispatch = input.workflowRunId !== undefined || input.fromAgentId === getWorkflowAgentId(workspaceId);
287
- // Revalidate the worker before claiming: it may have been deleted
288
- // while ensureWorkerRun was awaited (R2 hardening) — a vanished worker
289
- // must not leave a claimed-but-undeliverable dispatch behind.
290
- workspaceStore.getWorker(workspaceId, workerId);
291
- // Claim (queued → submitted) instead of a blind mark: a UI-initiated
292
- // start can replay-deliver this dispatch while ensureWorkerRun above
293
- // was awaited — losing the claim means delivery is already owned.
294
- const claimed = claimQueuedDispatch(dispatchId);
295
- workspaceStore.markTaskDispatched(workspaceId, workerId);
296
- pendingMarked = true;
297
- if (claimed)
302
+ const writeClaimedDispatch = (currentWorker) => {
298
303
  try {
299
- const writePrompt = agentRuntime.writeSendPrompt(workspaceId, workerId, dispatchId, sender.name, worker.description, text);
304
+ const writePrompt = agentRuntime.writeSendPrompt(workspaceId, workerId, dispatchId, sender.name, currentWorker.description, text);
300
305
  void writePrompt.catch((error) => {
301
306
  // `team send` is intentionally asynchronous (§3.3). A worker that
302
307
  // exits during paste-submit did not receive actionable work, so
@@ -323,6 +328,62 @@ export const createTeamOperations = ({ agentRuntime, createDispatch, deleteDispa
323
328
  if (!isWorkflowDispatch)
324
329
  console.error('[hive] swallowed:teamDispatch.writePrompt', error);
325
330
  }
331
+ };
332
+ const claimAndWriteDispatch = () => {
333
+ const currentWorker = workspaceStore.getWorker(workspaceId, workerId);
334
+ const claimed = claimQueuedDispatch(dispatchId);
335
+ if (claimed)
336
+ writeClaimedDispatch(currentWorker);
337
+ };
338
+ // A PTY can flip from starting -> running on the first stdout chunk
339
+ // before Hermes/OpenCode is actually ready for our startup injection.
340
+ // The post-start barrier, not the coarse run status, is the source of
341
+ // truth for when the first task may be pasted.
342
+ const deferUntilPostStartReady = activeRun?.postStartInputReady;
343
+ if (deferUntilPostStartReady) {
344
+ workspaceStore.markTaskDispatched(workspaceId, workerId);
345
+ pendingMarked = true;
346
+ const cancelDeferredDispatch = (error) => {
347
+ try {
348
+ cancelUndeliveredDispatch(workspaceId, workerId, dispatchId, reportForwardErrorMessage(error), input.workflowRunId, isWorkflowDispatch ? undefined : fromAgentId);
349
+ }
350
+ catch (cancelError) {
351
+ if (!isWorkflowDispatch)
352
+ console.error('[hive] swallowed:teamDispatch.cancelUndelivered', cancelError);
353
+ }
354
+ if (!isWorkflowDispatch)
355
+ console.error('[hive] swallowed:teamDispatch.deferredWrite', error);
356
+ };
357
+ void deferUntilPostStartReady
358
+ .then(() => {
359
+ const current = listOpenWorkspaceDispatches(workspaceId).find((item) => item.id === dispatchId);
360
+ if (!current || current.status !== 'queued')
361
+ return;
362
+ // Deliver older parked work first, but do not let later sends
363
+ // jump ahead of this dispatch while a starting PTY settles.
364
+ const replayOptions = {
365
+ excludeDispatchId: dispatchId,
366
+ };
367
+ if (current.sequence !== null)
368
+ replayOptions.beforeSequence = current.sequence;
369
+ replayQueuedDispatches(workspaceId, workerId, replayOptions);
370
+ claimAndWriteDispatch();
371
+ })
372
+ .catch(cancelDeferredDispatch);
373
+ return dispatch;
374
+ }
375
+ // Revalidate the worker before claiming: it may have been deleted
376
+ // while ensureWorkerRun was awaited (R2 hardening) — a vanished worker
377
+ // must not leave a claimed-but-undeliverable dispatch behind.
378
+ workspaceStore.getWorker(workspaceId, workerId);
379
+ // Claim (queued → submitted) instead of a blind mark: a UI-initiated
380
+ // start can replay-deliver this dispatch while ensureWorkerRun above
381
+ // was awaited — losing the claim means delivery is already owned.
382
+ const claimed = claimQueuedDispatch(dispatchId);
383
+ workspaceStore.markTaskDispatched(workspaceId, workerId);
384
+ pendingMarked = true;
385
+ if (claimed)
386
+ writeClaimedDispatch(worker);
326
387
  }
327
388
  else {
328
389
  workspaceStore.markTaskDispatched(workspaceId, workerId);
@@ -397,9 +458,9 @@ export const createTeamOperations = ({ agentRuntime, createDispatch, deleteDispa
397
458
  throw new ConflictError(formatUnknownWorkerError(workerName, roster));
398
459
  }
399
460
  /* Capture the active-run state *before* dispatchTask runs. Internal
400
- workflow calls may auto-start a stopped worker; explicit CLI
401
- `team send` passes autoStartWorker:false so the worker remains
402
- stopped with a queued dispatch until the user restarts it. */
461
+ workflow calls and orchestrator-owned `team spawn` follow-up sends may
462
+ auto-start a stopped worker; user-managed stopped workers remain
463
+ stopped with a queued dispatch until the user restarts them. */
403
464
  const restartedWorker = input.fromAgentId !== undefined &&
404
465
  input.autoStartWorker !== false &&
405
466
  !agentRuntime.getActiveRunByAgentId(workspaceId, worker.id);
@@ -411,6 +472,13 @@ export const createTeamOperations = ({ agentRuntime, createDispatch, deleteDispa
411
472
  agentRuntime.writeUserInputPrompt(workspaceId, text);
412
473
  insertMessage(createUserInputMessage(workspaceId, orchestratorId, text));
413
474
  },
475
+ async deliverUserInput(workspaceId, orchestratorId, text) {
476
+ workspaceStore.getAgent(workspaceId, orchestratorId);
477
+ await agentRuntime.deliverUserInputToOrchestrator(workspaceId, text, {
478
+ requireActiveRun: true,
479
+ });
480
+ insertMessage(createUserInputMessage(workspaceId, orchestratorId, text));
481
+ },
414
482
  statusTask(workspaceId, workerId, input = {}) {
415
483
  const text = input.text ?? '';
416
484
  const artifacts = input.artifacts ?? [];
@@ -3,7 +3,9 @@ export type TerminalInputProfile = 'codex' | 'default' | 'grok' | 'opencode';
3
3
  export interface TerminalRunSummary {
4
4
  agent_id: string;
5
5
  agent_name: string;
6
+ has_user_input_since_start?: boolean | null;
6
7
  run_id: string;
8
+ startup_blocked_reason?: 'first_run_setup' | null;
7
9
  status: string;
8
10
  terminal_input_profile: TerminalInputProfile;
9
11
  }
@@ -0,0 +1,20 @@
1
+ export type UpdateInstallSource = 'bun-global' | 'bunx' | 'npm-global' | 'npm-prefix' | 'npx' | 'pnpm-dlx' | 'pnpm-global' | 'source-checkout' | 'unknown' | 'yarn-dlx' | 'yarn-global';
2
+ export interface UpdateInstallPlan {
3
+ canRunHiveUpdate: boolean;
4
+ installArgs: string[];
5
+ installCommand: string;
6
+ installSource: UpdateInstallSource;
7
+ manualCommand: string;
8
+ note: string;
9
+ }
10
+ interface UpdateInstallPlanOptions {
11
+ env?: NodeJS.ProcessEnv | undefined;
12
+ moduleUrl?: string | undefined;
13
+ platform?: NodeJS.Platform | undefined;
14
+ }
15
+ export declare const formatUpdateCommand: (command: string, args: readonly string[], platform?: NodeJS.Platform) => string;
16
+ export declare const findPackageRoot: (moduleUrl: string) => string | undefined;
17
+ export declare const findPrefixFromPackageRoot: (packageRoot: string) => string | undefined;
18
+ export declare const resolveHiveUpdateInstallArgs: (moduleUrl?: string) => string[];
19
+ export declare const createUpdateInstallPlan: (options?: UpdateInstallPlanOptions) => UpdateInstallPlan;
20
+ export {};
@@ -0,0 +1,151 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { basename, dirname, join } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { INSTALL_COMMAND_ARGS, PACKAGE_NAME } from './package-version.js';
5
+ const shellQuote = (value) => {
6
+ if (/^[A-Za-z0-9_/:=.,@+-]+$/.test(value))
7
+ return value;
8
+ return `'${value.replaceAll("'", "'\\''")}'`;
9
+ };
10
+ const windowsQuote = (value) => {
11
+ if (/^[-A-Za-z0-9_/:=.,@+\\]+$/.test(value))
12
+ return value;
13
+ return `"${value.replace(/"/g, '""')}"`;
14
+ };
15
+ export const formatUpdateCommand = (command, args, platform = process.platform) => `${command} ${args.map(platform === 'win32' ? windowsQuote : shellQuote).join(' ')}`;
16
+ const normalizePath = (value) => value.replaceAll('\\', '/');
17
+ const includesPathSegment = (path, segment) => normalizePath(path).split('/').includes(segment);
18
+ export const findPackageRoot = (moduleUrl) => {
19
+ let dir = dirname(fileURLToPath(moduleUrl));
20
+ for (let depth = 0; depth < 12; depth += 1) {
21
+ const candidate = join(dir, 'package.json');
22
+ if (existsSync(candidate)) {
23
+ try {
24
+ const parsed = JSON.parse(readFileSync(candidate, 'utf8'));
25
+ if (parsed.name === PACKAGE_NAME)
26
+ return dir;
27
+ }
28
+ catch {
29
+ // Keep walking. A malformed unrelated package.json should not break update planning.
30
+ }
31
+ }
32
+ const parent = dirname(dir);
33
+ if (parent === dir)
34
+ return undefined;
35
+ dir = parent;
36
+ }
37
+ return undefined;
38
+ };
39
+ export const findPrefixFromPackageRoot = (packageRoot) => {
40
+ let dir = packageRoot;
41
+ for (let depth = 0; depth < 8; depth += 1) {
42
+ if (basename(dir) === 'node_modules') {
43
+ const parent = dirname(dir);
44
+ return basename(parent) === 'lib' ? dirname(parent) : parent;
45
+ }
46
+ const parent = dirname(dir);
47
+ if (parent === dir)
48
+ return undefined;
49
+ dir = parent;
50
+ }
51
+ return undefined;
52
+ };
53
+ export const resolveHiveUpdateInstallArgs = (moduleUrl = import.meta.url) => {
54
+ const packageRoot = findPackageRoot(moduleUrl);
55
+ const prefix = packageRoot ? findPrefixFromPackageRoot(packageRoot) : undefined;
56
+ if (!prefix)
57
+ return [...INSTALL_COMMAND_ARGS];
58
+ return [...INSTALL_COMMAND_ARGS, '--prefix', prefix];
59
+ };
60
+ const hasHint = (packageRoot, env, matcher) => {
61
+ const hints = [
62
+ packageRoot,
63
+ env._,
64
+ env.npm_command,
65
+ env.npm_config_user_agent,
66
+ env.npm_execpath,
67
+ env.npm_lifecycle_event,
68
+ ];
69
+ return hints.some((value) => typeof value === 'string' && matcher(normalizePath(value)));
70
+ };
71
+ const hasEnvHint = (env, matcher) => {
72
+ const hints = [
73
+ env._,
74
+ env.npm_command,
75
+ env.npm_config_user_agent,
76
+ env.npm_execpath,
77
+ env.npm_lifecycle_event,
78
+ ];
79
+ return hints.some((value) => typeof value === 'string' && matcher(normalizePath(value)));
80
+ };
81
+ const isBunGlobalHint = (value) => value.includes('/.bun/install/global/') ||
82
+ value.startsWith('bun/') ||
83
+ value.endsWith('/bun') ||
84
+ value.endsWith('/bun.exe');
85
+ const commandPlan = (source, command, args, note, platform) => ({
86
+ canRunHiveUpdate: false,
87
+ installArgs: [],
88
+ installCommand: formatUpdateCommand(command, args, platform),
89
+ installSource: source,
90
+ manualCommand: formatUpdateCommand(command, args, platform),
91
+ note,
92
+ });
93
+ export const createUpdateInstallPlan = (options = {}) => {
94
+ const env = options.env ?? process.env;
95
+ const moduleUrl = options.moduleUrl ?? import.meta.url;
96
+ const platform = options.platform ?? process.platform;
97
+ const packageRoot = findPackageRoot(moduleUrl);
98
+ const packageRootPath = packageRoot ? normalizePath(packageRoot) : '';
99
+ if (packageRootPath.includes('/_npx/')) {
100
+ return commandPlan('npx', 'npx', [`${PACKAGE_NAME}@latest`], 'This Hive process is running from an npx cache; re-run npx with @latest instead of installing a global copy.', platform);
101
+ }
102
+ if (hasHint(packageRoot, env, (value) => value.includes('bunx'))) {
103
+ return commandPlan('bunx', 'bunx', [`${PACKAGE_NAME}@latest`], 'This Hive process is running from bunx; re-run bunx with @latest instead of installing a global copy.', platform);
104
+ }
105
+ if (hasHint(packageRoot, env, (value) => (value.includes('pnpm') && value.includes('dlx')) || value.includes('/dlx-'))) {
106
+ return commandPlan('pnpm-dlx', 'pnpm', ['dlx', `${PACKAGE_NAME}@latest`], 'This Hive process is running from pnpm dlx; re-run pnpm dlx with @latest instead of installing a global copy.', platform);
107
+ }
108
+ if (hasHint(packageRoot, env, (value) => value.includes('yarn') && value.includes('dlx'))) {
109
+ return commandPlan('yarn-dlx', 'yarn', ['dlx', `${PACKAGE_NAME}@latest`], 'This Hive process is running from yarn dlx; re-run yarn dlx with @latest instead of installing a global copy.', platform);
110
+ }
111
+ const prefix = packageRoot ? findPrefixFromPackageRoot(packageRoot) : undefined;
112
+ if (!prefix && packageRoot && !includesPathSegment(packageRoot, 'node_modules')) {
113
+ return commandPlan('source-checkout', 'sh', ['-lc', 'git pull && pnpm install && pnpm build'], 'This Hive process is running from a source checkout; update the checkout and rebuild instead of installing a global copy.', platform);
114
+ }
115
+ if (packageRootPath.includes('/.bun/install/global/') ||
116
+ (!prefix && hasEnvHint(env, isBunGlobalHint))) {
117
+ return commandPlan('bun-global', 'bun', ['add', '-g', `${PACKAGE_NAME}@latest`], 'Hive appears to be installed through bun; update it with bun so the active binary changes.', platform);
118
+ }
119
+ if (packageRootPath.includes('/.pnpm/') ||
120
+ (!prefix && hasEnvHint(env, (value) => value.includes('pnpm')))) {
121
+ return commandPlan('pnpm-global', 'pnpm', ['add', '-g', `${PACKAGE_NAME}@latest`], 'Hive appears to be installed through pnpm; update it with pnpm so npm does not create a shadow global install.', platform);
122
+ }
123
+ if (packageRootPath.includes('/yarn/global/') ||
124
+ packageRootPath.includes('/.config/yarn/global/') ||
125
+ (!prefix &&
126
+ hasEnvHint(env, (value) => value.includes('/yarn/global/') || value.includes('/.config/yarn/global/')))) {
127
+ return commandPlan('yarn-global', 'yarn', ['global', 'add', `${PACKAGE_NAME}@latest`], 'Hive appears to be installed through yarn; update it with yarn so npm does not create a shadow global install.', platform);
128
+ }
129
+ const installArgs = resolveHiveUpdateInstallArgs(moduleUrl);
130
+ const manualCommand = formatUpdateCommand('npm', installArgs, platform);
131
+ if (!packageRoot) {
132
+ return {
133
+ canRunHiveUpdate: false,
134
+ installArgs: [],
135
+ installCommand: '',
136
+ installSource: 'unknown',
137
+ manualCommand: '',
138
+ note: 'Hive could not determine how this process was installed; update it with the same package manager and install target you originally used.',
139
+ };
140
+ }
141
+ return {
142
+ canRunHiveUpdate: true,
143
+ installArgs,
144
+ installCommand: 'hive update',
145
+ installSource: prefix ? 'npm-prefix' : 'npm-global',
146
+ manualCommand,
147
+ note: prefix
148
+ ? 'Hive appears to be installed through npm with a custom prefix; hive update will update that same prefix.'
149
+ : 'Hive appears to be installed through npm; hive update will run the matching npm upgrade.',
150
+ };
151
+ };
@@ -1,9 +1,13 @@
1
+ import { type UpdateInstallPlan, type UpdateInstallSource } from './update-install-plan.js';
1
2
  export interface VersionInfoPayload {
3
+ can_run_hive_update: boolean;
2
4
  current_version: string;
3
5
  install_hint: string;
6
+ install_source: UpdateInstallSource;
4
7
  latest_version: string;
5
8
  package_name: string;
6
9
  release_url: string;
10
+ update_note: string;
7
11
  update_available: boolean;
8
12
  }
9
13
  export interface VersionService {
@@ -11,6 +15,7 @@ export interface VersionService {
11
15
  }
12
16
  export declare const compareVersions: (left: string, right: string) => number;
13
17
  export declare const createVersionService: (options?: {
18
+ createUpdateInstallPlan?: () => UpdateInstallPlan;
14
19
  fetchLatestVersion?: () => Promise<string>;
15
20
  now?: () => number;
16
21
  }) => VersionService;
@@ -1,4 +1,5 @@
1
1
  import { PACKAGE_NAME, readPackageVersion } from './package-version.js';
2
+ import { createUpdateInstallPlan, } from './update-install-plan.js';
2
3
  const VERSION_CACHE_MS = 6 * 60 * 60 * 1000;
3
4
  const REGISTRY_URL = 'https://registry.npmjs.org/@tt-a1i%2Fhive/latest';
4
5
  const parseVersion = (version) => {
@@ -25,20 +26,23 @@ export const compareVersions = (left, right) => {
25
26
  return -1;
26
27
  return a.prerelease.localeCompare(b.prerelease, undefined, { numeric: true });
27
28
  };
28
- const buildVersionInfo = (currentVersion, latestVersion) => ({
29
- current_version: currentVersion,
30
- // Show `hive update` as the primary upgrade path now that the subcommand
31
- // exists; it prints the npm fallback if its own spawn fails, so users
32
- // running on Windows / Linux without a working npm in PATH still recover.
33
- install_hint: 'hive update',
34
- latest_version: latestVersion,
35
- package_name: PACKAGE_NAME,
36
- release_url: `https://www.npmjs.com/package/${PACKAGE_NAME}/v/${latestVersion}`,
37
- update_available: compareVersions(latestVersion, currentVersion) > 0,
38
- });
29
+ const buildVersionInfo = (currentVersion, latestVersion, updatePlan) => {
30
+ return {
31
+ can_run_hive_update: updatePlan.canRunHiveUpdate,
32
+ current_version: currentVersion,
33
+ install_hint: updatePlan.installCommand,
34
+ install_source: updatePlan.installSource,
35
+ latest_version: latestVersion,
36
+ package_name: PACKAGE_NAME,
37
+ release_url: `https://www.npmjs.com/package/${PACKAGE_NAME}/v/${latestVersion}`,
38
+ update_available: compareVersions(latestVersion, currentVersion) > 0,
39
+ update_note: updatePlan.note,
40
+ };
41
+ };
39
42
  export const createVersionService = (options = {}) => {
40
43
  const currentVersion = readPackageVersion();
41
44
  const now = options.now ?? Date.now;
45
+ const planUpdateInstall = options.createUpdateInstallPlan ?? createUpdateInstallPlan;
42
46
  const fetchLatestVersion = options.fetchLatestVersion ??
43
47
  (async () => {
44
48
  const response = await fetch(REGISTRY_URL, { signal: AbortSignal.timeout(1500) });
@@ -58,12 +62,12 @@ export const createVersionService = (options = {}) => {
58
62
  return cached.info;
59
63
  try {
60
64
  const latestVersion = await fetchLatestVersion();
61
- const info = buildVersionInfo(currentVersion, latestVersion);
65
+ const info = buildVersionInfo(currentVersion, latestVersion, planUpdateInstall());
62
66
  cached = { expiresAt: currentTime + VERSION_CACHE_MS, info };
63
67
  return info;
64
68
  }
65
69
  catch {
66
- const info = buildVersionInfo(currentVersion, currentVersion);
70
+ const info = buildVersionInfo(currentVersion, currentVersion, planUpdateInstall());
67
71
  cached = { expiresAt: currentTime + VERSION_CACHE_MS, info };
68
72
  return info;
69
73
  }
@@ -0,0 +1,81 @@
1
+ import type { WorkerRole } from '../shared/types.js';
2
+ import type { AgentLaunchConfigInput } from './agent-run-store.js';
3
+ import { type WorkflowCliPolicy } from './workflow-cli-policy.js';
4
+ import type { WorkflowDispatchAwaiter } from './workflow-dispatch-awaiter.js';
5
+ export interface WorkflowAgentOptions {
6
+ label?: string;
7
+ /** Built-in role name OR the name of a workspace-defined custom role template. */
8
+ agentType?: WorkerRole | string;
9
+ cli?: string;
10
+ timeoutMs?: number;
11
+ /** Per-call model override, appended as `--model <id>` after template args. */
12
+ model?: string;
13
+ outputSchema?: Record<string, unknown>;
14
+ __hiveDagLayerId?: string;
15
+ }
16
+ export interface WorkflowRoleTemplateResolver {
17
+ findByName(name: string): {
18
+ name: string;
19
+ roleType: WorkerRole | 'orchestrator';
20
+ defaultCommand: string;
21
+ defaultArgs: string[];
22
+ } | undefined;
23
+ }
24
+ export interface WorkflowAgentExecutorStorePort {
25
+ addWorkerWithLaunch: (workspaceId: string, input: {
26
+ name: string;
27
+ role: WorkerRole;
28
+ ephemeral: true;
29
+ spawnedBy: 'workflow';
30
+ }, launchConfig: AgentLaunchConfigInput) => {
31
+ id: string;
32
+ name: string;
33
+ };
34
+ startAgent: (workspaceId: string, agentId: string, input: {
35
+ hivePort: string;
36
+ }) => Promise<{
37
+ postStartInputReady?: Promise<void>;
38
+ }>;
39
+ dispatchTaskByWorkerName: (workspaceId: string, workerName: string, text: string, input: {
40
+ fromAgentId: string;
41
+ hivePort: string;
42
+ workflowRunId: string;
43
+ stepIndex: number;
44
+ phase?: string;
45
+ label?: string;
46
+ }) => Promise<{
47
+ id: string;
48
+ }>;
49
+ deleteWorker: (workspaceId: string, workerId: string) => void;
50
+ }
51
+ interface CreateWorkflowAgentCallExecutorInput {
52
+ assertRunActive: () => void;
53
+ awaiter: WorkflowDispatchAwaiter;
54
+ cancelOpenDispatch: (dispatchId: string, reason: string) => void;
55
+ cliPolicy: WorkflowCliPolicy;
56
+ getCurrentPhaseTitle: () => string | null;
57
+ hivePort: string;
58
+ isRunStopped: () => boolean;
59
+ maxAgentCalls?: number;
60
+ maxConcurrentAgents?: number;
61
+ registerDagDispatch: (layerId: string | null, dispatchId: string) => void;
62
+ resolveCliLaunchConfig: (cli: string) => AgentLaunchConfigInput | undefined;
63
+ roleTemplateResolver: WorkflowRoleTemplateResolver;
64
+ runId: string;
65
+ store: WorkflowAgentExecutorStorePort;
66
+ workflowAgentId: string;
67
+ workflowName: string;
68
+ workspaceId: string;
69
+ }
70
+ export declare const createWorkflowAgentCallExecutor: ({ assertRunActive, awaiter, cancelOpenDispatch, cliPolicy, getCurrentPhaseTitle, hivePort, isRunStopped, maxAgentCalls, maxConcurrentAgents: configuredMaxConcurrentAgents, registerDagDispatch, resolveCliLaunchConfig, roleTemplateResolver, runId, store, workflowAgentId, workflowName, workspaceId, }: CreateWorkflowAgentCallExecutorInput) => {
71
+ agent: (prompt: string, opts?: WorkflowAgentOptions) => Promise<string | Record<string, unknown>>;
72
+ cancelQueuedAgentCallsForDagLayer: (layerId: string, reason: string) => void;
73
+ catchPerItem: <U>(value: unknown) => U | null;
74
+ deleteSpawnedWorkers: () => void;
75
+ forceCancelActiveDispatchWaiters: (reason: string) => void;
76
+ waitForActiveCalls: (timeoutMs?: number) => Promise<{
77
+ settled: boolean;
78
+ activeCount: number;
79
+ }>;
80
+ };
81
+ export {};