@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
@@ -1,5 +1,6 @@
1
1
  import type { IncomingMessage, ServerResponse } from 'node:http';
2
2
  import type { WorkerRole } from '../shared/types.js';
3
+ import type { UiLanguage } from '../shared/ui-language.js';
3
4
  import type { PickFolderResponse } from './fs-pick-folder.js';
4
5
  import type { OpenCommandResult, OpenWorkspaceInput as OpenWorkspaceServiceInput } from './open-target-commands.js';
5
6
  import type { RuntimeStore } from './runtime-store.js';
@@ -24,6 +25,7 @@ export interface SpawnAgentBody {
24
25
  /** Opt-in: auto-dismiss the worker after its next dispatch report. Default
25
26
  * false → persistent member (lives until explicit `team dismiss`). */
26
27
  ephemeral?: boolean;
28
+ locale?: UiLanguage;
27
29
  }
28
30
  export interface DismissAgentBody {
29
31
  hive_port?: string;
@@ -81,6 +83,8 @@ export interface CreateWorkspaceBody {
81
83
  command_preset_id?: string | null;
82
84
  /** Optional full startup command. When set, it overrides the executable only. */
83
85
  startup_command?: string | null;
86
+ /** Browser UI language at creation time; stored per workspace for server-generated prompts. */
87
+ ui_language?: UiLanguage;
84
88
  }
85
89
  export interface CreateWorkerBody {
86
90
  autostart?: boolean;
@@ -90,6 +94,8 @@ export interface CreateWorkerBody {
90
94
  role: WorkerRole;
91
95
  /** Optional full startup command. When set, it overrides the executable only. */
92
96
  startup_command?: string | null;
97
+ /** Browser UI language for server-generated fallback descriptions. */
98
+ ui_language?: UiLanguage;
93
99
  }
94
100
  export interface UserInputBody {
95
101
  text: string;
@@ -3,6 +3,7 @@ import type { RouteDefinition } from './route-types.js';
3
3
  import type { RuntimeStore } from './runtime-store-contract.js';
4
4
  import { type ScenarioPreset } from './scenario-presets.js';
5
5
  import { type CliAvailabilityProbe } from './spawn-cli-resolver.js';
6
+ import { type UiLanguage } from './workspace-ui-language.js';
6
7
  export declare const scenarioRoutes: RouteDefinition[];
7
8
  /**
8
9
  * Materialize the scenario team and hand the goal to the orchestrator.
@@ -18,7 +19,7 @@ export declare const scenarioRoutes: RouteDefinition[];
18
19
  * addWorkerWithLaunch is atomic, but a mid-loop failure (e.g. a name race)
19
20
  * leaves earlier workers in place and surfaces the error to the caller.
20
21
  */
21
- export declare const applyScenario: (store: RuntimeStore, workspaceId: string, scenario: ScenarioPreset, goal: string, isCommandAvailable?: CliAvailabilityProbe) => Array<{
22
+ export declare const applyScenario: (store: RuntimeStore, workspaceId: string, scenario: ScenarioPreset, goal: string, isCommandAvailable?: CliAvailabilityProbe, language?: UiLanguage) => Array<{
22
23
  id: string;
23
24
  name: string;
24
25
  role: WorkerRole;
@@ -1,9 +1,10 @@
1
1
  import { BadRequestError, ConflictError } from './http-errors.js';
2
2
  import { getRequiredParam, readJsonBody, route, sendJson } from './route-helpers.js';
3
- import { buildScenarioKickoffMessage, buildScenarioWorkerName, getScenarioPreset, } from './scenario-presets.js';
3
+ import { buildScenarioKickoffMessage, buildScenarioWorkerName, getScenarioPreset, getScenarioWorkerDescription, } from './scenario-presets.js';
4
4
  import { resolveDefaultSpawnCliLaunchConfig, } from './spawn-cli-resolver.js';
5
5
  import { requireUiTokenFromRequest } from './ui-auth-helpers.js';
6
6
  import { getOrchestratorId } from './workspace-store-support.js';
7
+ import { resolveWorkspaceUiLanguage, writeWorkspaceUiLanguage, } from './workspace-ui-language.js';
7
8
  export const scenarioRoutes = [
8
9
  /**
9
10
  * One-click team assembly: materialize a scenario preset's workers (each
@@ -32,13 +33,15 @@ export const scenarioRoutes = [
32
33
  if (!goal) {
33
34
  throw new BadRequestError('Missing goal');
34
35
  }
36
+ const language = resolveWorkspaceUiLanguage(store.settings, workspaceId, body.locale);
37
+ writeWorkspaceUiLanguage(store.settings, workspaceId, language);
35
38
  // The kickoff is delivered to the orchestrator's live terminal and is NOT
36
39
  // persisted for replay, so an offline orchestrator would silently lose the
37
40
  // goal. Gate up front: no live run, no team assembly.
38
41
  if (!store.getActiveRunByAgentId(workspaceId, getOrchestratorId(workspaceId))) {
39
42
  throw new ConflictError('Start the Orchestrator first — the scenario goal is handed to its terminal');
40
43
  }
41
- const created = applyScenario(store, workspaceId, scenario, goal);
44
+ const created = applyScenario(store, workspaceId, scenario, goal, undefined, language);
42
45
  sendJson(response, 201, {
43
46
  created_workers: created,
44
47
  injected: true,
@@ -59,7 +62,7 @@ export const scenarioRoutes = [
59
62
  * addWorkerWithLaunch is atomic, but a mid-loop failure (e.g. a name race)
60
63
  * leaves earlier workers in place and surfaces the error to the caller.
61
64
  */
62
- export const applyScenario = (store, workspaceId, scenario, goal, isCommandAvailable) => {
65
+ export const applyScenario = (store, workspaceId, scenario, goal, isCommandAvailable, language = 'en') => {
63
66
  const ports = {
64
67
  getCommandPreset: (id) => store.settings.getCommandPreset(id),
65
68
  getOrchestratorLaunchConfig: () => store.peekAgentLaunchConfig(workspaceId, getOrchestratorId(workspaceId)),
@@ -71,18 +74,20 @@ export const applyScenario = (store, workspaceId, scenario, goal, isCommandAvail
71
74
  for (const spec of scenario.workers) {
72
75
  const name = buildScenarioWorkerName(spec, (candidate) => takenNames.has(candidate));
73
76
  takenNames.add(name);
77
+ const description = spec.descriptionOverride !== undefined
78
+ ? getScenarioWorkerDescription(spec, language)
79
+ : undefined;
74
80
  const worker = store.addWorkerWithLaunch(workspaceId, {
75
81
  name,
76
82
  role: spec.role,
77
- ...(spec.descriptionOverride !== undefined
78
- ? { description: spec.descriptionOverride }
79
- : {}),
83
+ ...(description !== undefined ? { description } : {}),
80
84
  }, launchConfig);
81
85
  created.push({ id: worker.id, name: worker.name, role: spec.role });
82
86
  }
83
87
  store.recordUserInput(workspaceId, getOrchestratorId(workspaceId), buildScenarioKickoffMessage({
84
88
  scenarioId: scenario.id,
85
89
  goal,
90
+ language,
86
91
  workers: created.map((worker) => ({ name: worker.name, role: worker.role })),
87
92
  }));
88
93
  return created;
@@ -6,6 +6,8 @@ import { resolveSpawnWorkerDefaults } from './spawn-worker-defaults.js';
6
6
  import { authenticateCliAgent, requireCommandForRole } from './team-authz.js';
7
7
  import { readWorkflowEnabled, WORKFLOW_ENABLED_KEY } from './workflow-feature.js';
8
8
  import { getOrchestratorId } from './workspace-store-support.js';
9
+ import { resolveWorkspaceUiLanguage } from './workspace-ui-language.js';
10
+ const shouldAutoStartTeamSendTarget = (store, targetWorker) => targetWorker?.spawnedBy === 'orchestrator' && store.listAgentRuns(targetWorker.id).length === 0;
9
11
  // Experimental gate: `team workflow run` / `team workflow schedule` only work
10
12
  // when the user has enabled workflows in Settings. Off by default. The PTY
11
13
  // runner itself isn't gated (internal API + the scheduler gate handle that) —
@@ -64,16 +66,20 @@ export const teamRoutes = [
64
66
  workspaceId: projectId,
65
67
  });
66
68
  requireCommandForRole(agent, 'send');
69
+ const targetWorker = store.listWorkers(projectId).find((worker) => worker.name === to);
70
+ const shouldAutoStartWorker = shouldAutoStartTeamSendTarget(store, targetWorker);
67
71
  const dispatch = await store.dispatchTaskByWorkerName(projectId, to, text, {
68
- autoStartWorker: false,
72
+ autoStartWorker: shouldAutoStartWorker,
69
73
  fromAgentId,
70
74
  hivePort: String(request.socket.localPort ?? ''),
71
75
  });
72
- /* The user-facing team send route queues work for stopped workers without
73
- auto-starting them; internal workflow dispatches may still surface
74
- restarted_worker when they intentionally wake a worker. A parked
75
- dispatch is flagged so the orchestrator can tell the user the worker
76
- needs a start (replay delivers it automatically on that start). */
76
+ /* The user-facing team send route queues work for user-managed stopped
77
+ workers without auto-starting them. An orchestrator-spawned worker is
78
+ different only for its first dispatch: `team spawn` is immediately
79
+ followed by `team send`, so the send path wakes a never-started spawned
80
+ worker to keep that natural flow from parking forever. A parked dispatch
81
+ is flagged so the orchestrator can tell the user the worker needs a
82
+ start (replay delivers it automatically on that start). */
77
83
  sendJson(response, 202, {
78
84
  dispatch_id: dispatch.id,
79
85
  ok: true,
@@ -95,7 +101,9 @@ export const teamRoutes = [
95
101
  workspaceId: projectId,
96
102
  });
97
103
  requireCommandForRole(agent, 'spawn');
104
+ const language = resolveWorkspaceUiLanguage(store.settings, projectId, body.locale);
98
105
  const { role, name, description } = resolveSpawnWorkerDefaults({
106
+ language,
99
107
  requestedRole: typeof body.role === 'string' ? body.role : undefined,
100
108
  requestedName: typeof body.name === 'string' ? body.name : undefined,
101
109
  takenNames: new Set(store.listWorkers(projectId).map((worker) => worker.name)),
@@ -106,15 +114,13 @@ export const teamRoutes = [
106
114
  // but orchestrator-spawned). Existing M1-D cascade-on-orchestrator-exit
107
115
  // still applies to whatever ephemeral workers remain.
108
116
  const ephemeral = body.ephemeral === true;
109
- const workerInput = ephemeral
110
- ? {
111
- name,
112
- role,
113
- ...(description !== undefined ? { description } : {}),
114
- ephemeral: true,
115
- spawnedBy: 'orchestrator',
116
- }
117
- : { name, role, ...(description !== undefined ? { description } : {}) };
117
+ const workerInput = {
118
+ name,
119
+ role,
120
+ ...(description !== undefined ? { description } : {}),
121
+ ...(ephemeral ? { ephemeral: true } : {}),
122
+ spawnedBy: 'orchestrator',
123
+ };
118
124
  const worker = store.addWorkerWithLaunch(projectId, workerInput, launchConfig);
119
125
  sendJson(response, 201, {
120
126
  name: worker.name,
@@ -1,6 +1,8 @@
1
1
  import { resolveCommandPresetLaunchConfig, resolveStartupCommandLaunchConfig, } from './agent-launch-resolver.js';
2
+ import { PtyInactiveError } from './http-errors.js';
2
3
  import { autostartAgent, autostartOrchestrator } from './orchestrator-autostart.js';
3
4
  import { seedOrchestratorLaunchConfig } from './orchestrator-launch.js';
5
+ import { getDefaultRoleDescription } from './role-templates.js';
4
6
  import { getRequiredParam, readJsonBody, route, sendJson } from './route-helpers.js';
5
7
  import { authenticateCliAgent, requireCommandForRole } from './team-authz.js';
6
8
  import { enrichTeamList } from './team-list-enrichment.js';
@@ -8,6 +10,7 @@ import { serializeTeamListItem } from './team-list-serializer.js';
8
10
  import { requireUiTokenFromRequest } from './ui-auth-helpers.js';
9
11
  import { validateWorkspacePath } from './workspace-path-validation.js';
10
12
  import { getOrchestratorId, getWorkflowAgentId } from './workspace-store-support.js';
13
+ import { resolveWorkspaceUiLanguage, writeWorkspaceUiLanguage } from './workspace-ui-language.js';
11
14
  /* #35: fold each worker's open dispatches (id, age, status, task preview)
12
15
  into the team list payload. Display-only ages — no timeout or heartbeat is
13
16
  derived from them; `queued` rows age from createdAt (submittedAt is null
@@ -56,6 +59,8 @@ export const workspaceRoutes = [
56
59
  const startupCommand = typeof body.startup_command === 'string' ? body.startup_command : null;
57
60
  const workspacePath = validateWorkspacePath(body.path);
58
61
  const workspace = store.createWorkspace(workspacePath, body.name);
62
+ const language = resolveWorkspaceUiLanguage(store.settings, workspace.id, body.ui_language);
63
+ writeWorkspaceUiLanguage(store.settings, workspace.id, language);
59
64
  seedOrchestratorLaunchConfig(store, store.settings, workspace.id, body.command_preset_id ?? null, startupCommand);
60
65
  const autostart = body.autostart_orchestrator !== false;
61
66
  if (!autostart) {
@@ -120,6 +125,7 @@ export const workspaceRoutes = [
120
125
  const body = await readJsonBody(request);
121
126
  const presetId = body.command_preset_id ?? null;
122
127
  const startupCommand = typeof body.startup_command === 'string' ? body.startup_command : null;
128
+ const language = resolveWorkspaceUiLanguage(store.settings, workspaceId, body.ui_language);
123
129
  const launchConfig = startupCommand?.trim()
124
130
  ? resolveStartupCommandLaunchConfig(store.settings, startupCommand, presetId)
125
131
  : presetId
@@ -128,7 +134,12 @@ export const workspaceRoutes = [
128
134
  if (presetId && !startupCommand?.trim() && !launchConfig) {
129
135
  throw new Error(`Command preset not found: ${presetId}`);
130
136
  }
131
- const worker = store.addWorker(workspaceId, body);
137
+ const worker = store.addWorker(workspaceId, {
138
+ ...body,
139
+ description: typeof body.description === 'string'
140
+ ? body.description
141
+ : getDefaultRoleDescription(body.role, language),
142
+ });
132
143
  if (launchConfig) {
133
144
  try {
134
145
  store.configureAgentLaunch(workspaceId, worker.id, launchConfig);
@@ -181,7 +192,16 @@ export const workspaceRoutes = [
181
192
  }
182
193
  requireUiTokenFromRequest(request, store.validateUiToken, store.authorizeRemoteTunnelRequest);
183
194
  const body = await readJsonBody(request);
184
- store.recordUserInput(workspaceId, `${workspaceId}:orchestrator`, body.text);
195
+ if (typeof body.text !== 'string' || body.text.trim().length === 0) {
196
+ sendJson(response, 400, { error: 'text is required' });
197
+ return;
198
+ }
199
+ const orchestratorId = getOrchestratorId(workspaceId);
200
+ if (!store.getActiveRunByAgentId(workspaceId, orchestratorId)) {
201
+ store.recordUserInput(workspaceId, orchestratorId, body.text);
202
+ throw new PtyInactiveError(`No active run for agent: ${orchestratorId}`);
203
+ }
204
+ await store.deliverUserInput(workspaceId, orchestratorId, body.text);
185
205
  sendJson(response, 202, { ok: true });
186
206
  }),
187
207
  route('POST', '/api/workspaces/:workspaceId/agents/:agentId/start', async ({ params, request, response, store }) => {
@@ -34,6 +34,7 @@ export interface RuntimeStore {
34
34
  deleteWorker: (workspaceId: string, workerId: string) => void;
35
35
  renameWorker: (workspaceId: string, workerId: string, name: string) => AgentSummary;
36
36
  recordUserInput: (workspaceId: string, orchestratorId: string, text: string) => void;
37
+ deliverUserInput: (workspaceId: string, orchestratorId: string, text: string) => Promise<void>;
37
38
  dispatchTask: (workspaceId: string, workerId: string, text: string, input?: DispatchTaskInput) => Promise<DispatchRecord>;
38
39
  dispatchTaskByWorkerName: (workspaceId: string, workerName: string, text: string, input?: DispatchTaskInput) => Promise<DispatchRecord & {
39
40
  restartedWorker: boolean;
@@ -69,6 +70,7 @@ export interface RuntimeStore {
69
70
  }>>;
70
71
  startWorkspaceWatch: (workspaceId: string) => Promise<void>;
71
72
  getLiveRun: (runId: string) => LiveAgentRun;
73
+ waitForRunExit: (runId: string, timeoutMs: number) => Promise<boolean>;
72
74
  getActiveRunByAgentId: (workspaceId: string, agentId: string) => LiveAgentRun | undefined;
73
75
  registerTasksListener: (listener: (workspaceId: string, content: string) => void) => () => void;
74
76
  listAgentRuns: (agentId: string) => PersistedAgentRun[];
@@ -84,14 +84,23 @@ export declare const createRuntimeStoreLifecycle: ({ agentManager, services, }:
84
84
  deleteWorkspaceShell: (workspaceId: string) => void;
85
85
  closeWorkspaceShell: (workspaceId: string, runId: string) => boolean;
86
86
  getLiveRun: (runId: string) => LiveAgentRun;
87
+ waitForRunExit: (runId: string, timeoutMs: number) => Promise<boolean>;
87
88
  getPtyOutputBus: () => PtyOutputBus;
88
- listTerminalRuns: (workspaceId: string) => {
89
+ listTerminalRuns: (workspaceId: string) => ({
89
90
  agent_id: string;
90
91
  agent_name: string;
91
92
  run_id: string;
92
93
  status: import("./agent-manager.js").RunStatus;
94
+ terminal_input_profile: "default";
95
+ } | {
96
+ agent_id: string;
97
+ agent_name: string;
98
+ has_user_input_since_start: boolean | null;
99
+ run_id: string;
100
+ startup_blocked_reason: "first_run_setup" | null;
101
+ status: import("./agent-manager.js").RunStatus;
93
102
  terminal_input_profile: import("./terminal-input-profile.js").TerminalInputProfile;
94
- }[];
103
+ })[];
95
104
  startAgent: (workspaceId: string, agentId: string, input: {
96
105
  hivePort: string;
97
106
  }) => Promise<LiveAgentRun>;
@@ -8,6 +8,7 @@ import { createDispatchLedgerStore } from './dispatch-ledger-store.js';
8
8
  import { readFeatureFlags } from './feature-flags.js';
9
9
  import { createMessageLogStore } from './message-log-store.js';
10
10
  import { seedOrchestratorLaunchConfig } from './orchestrator-launch.js';
11
+ import { hasFirstRunSetupPrompt, isInteractiveAgentCommand } from './post-start-input-writer.js';
11
12
  import { createProtocolEventStats } from './protocol-event-stats.js';
12
13
  import { createRemoteAuditStore } from './remote-audit-store.js';
13
14
  import { REMOTE_DAEMON_ID_KEY, REMOTE_GATEWAY_URL_KEY } from './remote-config-keys.js';
@@ -323,12 +324,25 @@ export const createRuntimeStoreLifecycle = ({ agentManager, services, }) => {
323
324
  // #33: a freshly started worker may have dispatches parked from when
324
325
  // it was stopped — deliver them now (single-shot via claims; no-op
325
326
  // for orchestrator/workflow pseudo-agents, which never hold queued
326
- // worker dispatches addressed to themselves).
327
- try {
328
- services.teamOps.replayQueuedDispatches(workspaceId, agentId);
327
+ // worker dispatches addressed to themselves). Wait for post-start
328
+ // guidance first so startup instructions never race the first task.
329
+ const replayQueuedDispatches = () => {
330
+ try {
331
+ services.teamOps.replayQueuedDispatches(workspaceId, agentId, {
332
+ createdBeforeMs: run.startedAt,
333
+ });
334
+ }
335
+ catch (error) {
336
+ console.error('[hive] swallowed:startAgent.replayQueued', error);
337
+ }
338
+ };
339
+ if (run.postStartInputReady) {
340
+ void run.postStartInputReady.then(replayQueuedDispatches).catch((error) => {
341
+ console.error('[hive] swallowed:startAgent.replayAfterPostStart', error);
342
+ });
329
343
  }
330
- catch (error) {
331
- console.error('[hive] swallowed:startAgent.replayQueued', error);
344
+ else {
345
+ replayQueuedDispatches();
332
346
  }
333
347
  }
334
348
  return run;
@@ -401,6 +415,7 @@ export const createRuntimeStoreLifecycle = ({ agentManager, services, }) => {
401
415
  },
402
416
  closeWorkspaceShell: (workspaceId, runId) => services.shellRuntime.closeRun(workspaceId, runId),
403
417
  getLiveRun: (runId) => services.shellRuntime.getLiveRun(runId) ?? services.agentRuntime.getLiveRun(runId),
418
+ waitForRunExit: (runId, timeoutMs) => services.agentRuntime.waitForRunExit(runId, timeoutMs),
404
419
  getPtyOutputBus: () => {
405
420
  if (!agentManager)
406
421
  throw new Error('Agent manager is required for PTY output subscriptions');
@@ -412,11 +427,20 @@ export const createRuntimeStoreLifecycle = ({ agentManager, services, }) => {
412
427
  if (!run)
413
428
  return [];
414
429
  const launchConfig = services.agentRuntime.peekAgentLaunchConfig(workspaceId, agent.id);
430
+ const interactiveCommand = launchConfig?.interactiveCommand ?? launchConfig?.command;
415
431
  return [
416
432
  {
417
433
  agent_id: agent.id,
418
434
  agent_name: agent.name,
435
+ has_user_input_since_start: agent.role === 'orchestrator'
436
+ ? services.messageLogStore.hasUserInputSince(workspaceId, agent.id, run.startedAt)
437
+ : null,
419
438
  run_id: run.runId,
439
+ startup_blocked_reason: interactiveCommand &&
440
+ isInteractiveAgentCommand(interactiveCommand) &&
441
+ hasFirstRunSetupPrompt(run.output)
442
+ ? 'first_run_setup'
443
+ : null,
420
444
  status: run.status,
421
445
  terminal_input_profile: resolveTerminalInputProfile(launchConfig),
422
446
  },
@@ -10,6 +10,26 @@ export const createRuntimeStoreWorkflowRuntime = (services, store) => {
10
10
  workflowRunStore: services.workflowRunStore,
11
11
  dispatchPort: {
12
12
  listOpenDispatchIdsForRun: (runId) => services.dispatchLedgerStore.listOpenDispatchIdsForRun(runId),
13
+ cancelOpenDispatchForRun: (workspaceId, dispatchId, reason) => {
14
+ const openDispatch = services.dispatchLedgerStore.findOpenDispatchById(workspaceId, dispatchId);
15
+ if (!openDispatch)
16
+ return false;
17
+ const cancelled = services.dispatchLedgerStore.markCancelled({
18
+ dispatchId,
19
+ reason,
20
+ workspaceId,
21
+ });
22
+ if (!cancelled)
23
+ return false;
24
+ services.workspaceStore.markTaskCancelled(workspaceId, openDispatch.toAgentId);
25
+ try {
26
+ services.agentRuntime.writeCancelPrompt(workspaceId, cancelled.toAgentId, cancelled.id, reason);
27
+ }
28
+ catch (error) {
29
+ console.error('[hive] swallowed:workflow.cancelDispatch.forward', error);
30
+ }
31
+ return true;
32
+ },
13
33
  },
14
34
  resolveWorkspacePath: (workspaceId) => services.workspaceStore.getWorkspaceSnapshot(workspaceId).summary.path,
15
35
  logStore: {
@@ -82,6 +82,7 @@ export const createRuntimeStore = (options = {}) => {
82
82
  renameWorker: (workspaceId, workerId, name) => services.workspaceStore.renameWorker(workspaceId, workerId, name),
83
83
  deleteWorker: workerMutations.deleteWorker,
84
84
  recordUserInput: services.teamOps.recordUserInput,
85
+ deliverUserInput: services.teamOps.deliverUserInput,
85
86
  cancelTask: services.teamOps.cancelTask,
86
87
  dispatchTask: services.teamOps.dispatchTask,
87
88
  dispatchTaskByWorkerName: services.teamOps.dispatchTaskByWorkerName,
@@ -106,6 +107,7 @@ export const createRuntimeStore = (options = {}) => {
106
107
  startWorkspaceWatch: lifecycle.startWorkspaceWatch,
107
108
  startWorkspaceShell: lifecycle.startWorkspaceShell,
108
109
  getLiveRun: lifecycle.getLiveRun,
110
+ waitForRunExit: lifecycle.waitForRunExit,
109
111
  getActiveRunByAgentId: (workspaceId, agentId) => services.agentRuntime.getActiveRunByAgentId(workspaceId, agentId),
110
112
  registerTasksListener: lifecycle.registerTasksListener,
111
113
  listAgentRuns: (agentId) => services.agentRuntime.listAgentRuns(agentId),
@@ -1,6 +1,7 @@
1
1
  import type { ScenarioWorkerSpec } from '../shared/scenario-presets.js';
2
2
  import type { WorkerRole } from '../shared/types.js';
3
- export { getScenarioPreset, SCENARIO_PRESETS, type ScenarioId, type ScenarioPreset, type ScenarioWorkerSpec, } from '../shared/scenario-presets.js';
3
+ import type { UiLanguage } from '../shared/ui-language.js';
4
+ export { getScenarioPreset, getScenarioWorkerDescription, SCENARIO_PRESETS, type ScenarioId, type ScenarioPreset, type ScenarioWorkerSpec, } from '../shared/scenario-presets.js';
4
5
  /**
5
6
  * `<stem>-<4 base36 chars>` — short, `team send`-safe (no spaces, lowercase),
6
7
  * and unlikely to collide. The caller passes an `isTaken` probe against the
@@ -10,6 +11,7 @@ export declare const buildScenarioWorkerName: (spec: Pick<ScenarioWorkerSpec, "n
10
11
  export interface ScenarioKickoffInput {
11
12
  scenarioId: string;
12
13
  goal: string;
14
+ language?: UiLanguage;
13
15
  workers: Array<{
14
16
  name: string;
15
17
  role: WorkerRole;
@@ -1,5 +1,5 @@
1
1
  import { randomBytes } from 'node:crypto';
2
- export { getScenarioPreset, SCENARIO_PRESETS, } from '../shared/scenario-presets.js';
2
+ export { getScenarioPreset, getScenarioWorkerDescription, SCENARIO_PRESETS, } from '../shared/scenario-presets.js';
3
3
  /**
4
4
  * `<stem>-<4 base36 chars>` — short, `team send`-safe (no spaces, lowercase),
5
5
  * and unlikely to collide. The caller passes an `isTaken` probe against the
@@ -23,6 +23,17 @@ export const buildScenarioWorkerName = (spec, isTaken, maxAttempts = 16) => {
23
23
  */
24
24
  export const buildScenarioKickoffMessage = (input) => {
25
25
  const roster = input.workers.map((worker) => `- ${worker.name} (${worker.role})`).join('\n');
26
+ if (input.language === 'zh') {
27
+ return [
28
+ `用户选择了 "${input.scenarioId}" 场景,Hive 已经为你创建这些团队成员:`,
29
+ roster,
30
+ '',
31
+ '用户目标:',
32
+ input.goal,
33
+ '',
34
+ '请先运行 `team list` 确认团队成员,然后把目标拆成任务,并用 `team send "<worker-name>" "<task>"` 派发。任务拆分由你决定;只有缺少真实决策时才回问用户。',
35
+ ].join('\n');
36
+ }
26
37
  return [
27
38
  `The user picked the "${input.scenarioId}" scenario and Hive has already created these team members for you:`,
28
39
  roster,
@@ -1,4 +1,5 @@
1
1
  import type { WorkerRole } from '../shared/types.js';
2
+ import type { UiLanguage } from '../shared/ui-language.js';
2
3
  export interface SpawnWorkerDefaults {
3
4
  role: WorkerRole;
4
5
  name: string;
@@ -7,6 +8,7 @@ export interface SpawnWorkerDefaults {
7
8
  description?: string;
8
9
  }
9
10
  export declare const resolveSpawnWorkerDefaults: (input: {
11
+ language?: UiLanguage;
10
12
  requestedRole: string | undefined;
11
13
  requestedName: string | undefined;
12
14
  takenNames: ReadonlySet<string>;
@@ -28,12 +28,17 @@ const MAX_LABEL_IN_FALLBACK = MAX_NAME_LENGTH - 37;
28
28
  export const resolveSpawnWorkerDefaults = (input) => {
29
29
  const label = input.requestedRole?.trim() || 'coder';
30
30
  const role = WORKER_ROLES.has(label) ? label : 'custom';
31
+ const language = input.language ?? 'en';
31
32
  const description = role === 'custom' && label !== 'custom'
32
- ? [
33
- `You are ${label}. Work from the task instructions dispatched by the Orchestrator.`,
34
- `你是 ${label},按 Orchestrator 派发的任务说明工作。`,
35
- 'When done, use `team report` to report results, risks, and blockers. / 完成后用 `team report` 汇报结果、风险和阻塞。',
36
- ].join('\n')
33
+ ? language === 'zh'
34
+ ? [
35
+ `你是 ${label},按 Orchestrator 派发的任务说明工作。`,
36
+ '完成后用 `team report` 汇报结果、风险和阻塞。',
37
+ ].join('\n')
38
+ : [
39
+ `You are ${label}. Work from the task instructions dispatched by the Orchestrator.`,
40
+ 'When done, use `team report` to report results, risks, and blockers.',
41
+ ].join('\n')
37
42
  : undefined;
38
43
  const requestedName = input.requestedName?.trim();
39
44
  const bareLabelAvailable = label.length <= MAX_NAME_LENGTH &&
@@ -99,6 +99,8 @@ export declare const createTeamOperations: ({ agentRuntime, createDispatch, dele
99
99
  dispatchTask: (workspaceId: string, workerId: string, text: string, input?: DispatchTaskInput) => Promise<DispatchRecord>;
100
100
  drainReportOutbox: (workspaceId: string) => void;
101
101
  replayQueuedDispatches: (workspaceId: string, workerId: string, options?: {
102
+ beforeSequence?: number;
103
+ createdBeforeMs?: number;
102
104
  excludeDispatchId?: string;
103
105
  }) => void;
104
106
  notifyIssuersOfDroppedDispatches: (workspaceId: string, workerId: string, reason: string) => void;
@@ -106,6 +108,7 @@ export declare const createTeamOperations: ({ agentRuntime, createDispatch, dele
106
108
  restartedWorker: boolean;
107
109
  }>;
108
110
  recordUserInput(workspaceId: string, orchestratorId: string, text: string): void;
111
+ deliverUserInput(workspaceId: string, orchestratorId: string, text: string): Promise<void>;
109
112
  statusTask(workspaceId: string, workerId: string, input?: StatusTaskInput): {
110
113
  pendingWarning?: string;
111
114
  dispatch: null;