@tt-a1i/hive 2.1.0 → 2.1.3
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.
- package/CHANGELOG.md +40 -0
- package/README.en.md +5 -460
- package/README.md +338 -187
- package/README.zh.md +389 -0
- package/dist/src/cli/hive-update.d.ts +3 -2
- package/dist/src/cli/hive-update.js +20 -60
- package/dist/src/cli/hive.js +5 -1
- package/dist/src/server/agent-manager-support.js +16 -9
- package/dist/src/server/agent-runtime-contract.d.ts +4 -0
- package/dist/src/server/agent-runtime.js +27 -0
- package/dist/src/server/agent-stdin-dispatcher.d.ts +3 -0
- package/dist/src/server/agent-stdin-dispatcher.js +3 -0
- package/dist/src/server/diagnostics-support-bundle.d.ts +3 -0
- package/dist/src/server/diagnostics-support-bundle.js +3 -0
- package/dist/src/server/hive-team-guidance.js +34 -4
- package/dist/src/server/message-log-store.d.ts +1 -0
- package/dist/src/server/message-log-store.js +13 -0
- package/dist/src/server/orchestrator-autostart.d.ts +2 -0
- package/dist/src/server/orchestrator-autostart.js +30 -11
- package/dist/src/server/post-start-input-writer.d.ts +1 -0
- package/dist/src/server/post-start-input-writer.js +80 -12
- package/dist/src/server/role-templates.d.ts +8 -1
- package/dist/src/server/role-templates.js +102 -48
- package/dist/src/server/route-types.d.ts +6 -0
- package/dist/src/server/routes-scenarios.d.ts +2 -1
- package/dist/src/server/routes-scenarios.js +11 -6
- package/dist/src/server/routes-team.js +21 -15
- package/dist/src/server/routes-workspaces.js +22 -2
- package/dist/src/server/runtime-store-contract.d.ts +2 -0
- package/dist/src/server/runtime-store-helpers.d.ts +11 -2
- package/dist/src/server/runtime-store-helpers.js +29 -5
- package/dist/src/server/runtime-store-workflows.js +20 -0
- package/dist/src/server/runtime-store.js +2 -0
- package/dist/src/server/scenario-presets.d.ts +3 -1
- package/dist/src/server/scenario-presets.js +12 -1
- package/dist/src/server/spawn-worker-defaults.d.ts +2 -0
- package/dist/src/server/spawn-worker-defaults.js +10 -5
- package/dist/src/server/team-operations.d.ts +3 -0
- package/dist/src/server/team-operations.js +97 -29
- package/dist/src/server/terminal-input-profile.d.ts +2 -0
- package/dist/src/server/update-install-plan.d.ts +20 -0
- package/dist/src/server/update-install-plan.js +151 -0
- package/dist/src/server/version-service.d.ts +5 -0
- package/dist/src/server/version-service.js +17 -13
- package/dist/src/server/workflow-agent-call-executor.d.ts +81 -0
- package/dist/src/server/workflow-agent-call-executor.js +259 -0
- package/dist/src/server/workflow-dag-layer-tracker.d.ts +14 -0
- package/dist/src/server/workflow-dag-layer-tracker.js +37 -0
- package/dist/src/server/workflow-dag.d.ts +8 -0
- package/dist/src/server/workflow-dag.js +65 -0
- package/dist/src/server/workflow-dispatch-awaiter.d.ts +7 -1
- package/dist/src/server/workflow-dispatch-awaiter.js +60 -9
- package/dist/src/server/workflow-runner.d.ts +5 -38
- package/dist/src/server/workflow-runner.js +113 -445
- package/dist/src/server/workflow-script-host-dispatcher.d.ts +14 -0
- package/dist/src/server/workflow-script-host-dispatcher.js +42 -0
- package/dist/src/server/workflow-script-loader.d.ts +2 -2
- package/dist/src/server/workflow-script-loader.js +2 -2
- package/dist/src/server/workflow-script-worker.d.ts +14 -0
- package/dist/src/server/workflow-script-worker.js +81 -0
- package/dist/src/server/workflow-vm-worker-source.d.ts +1 -0
- package/dist/src/server/workflow-vm-worker-source.js +1 -0
- package/dist/src/server/workflow-vm-worker.cjs +238 -0
- package/dist/src/server/workspace-ui-language.d.ts +7 -0
- package/dist/src/server/workspace-ui-language.js +9 -0
- package/dist/src/shared/scenario-presets.d.ts +6 -1
- package/dist/src/shared/scenario-presets.js +80 -28
- package/dist/src/shared/types.d.ts +11 -7
- package/dist/src/shared/ui-language.d.ts +4 -0
- package/dist/src/shared/ui-language.js +3 -0
- package/package.json +4 -2
- package/web/dist/assets/{AddWorkerDialog-DBLhwb91.js → AddWorkerDialog-DHwOZ75i.js} +2 -2
- package/web/dist/assets/AddWorkspaceFlow-qOI1CmqC.js +1 -0
- package/web/dist/assets/{FirstRunWizard-DlEPnWWw.js → FirstRunWizard-D5kGMD_V.js} +1 -1
- package/web/dist/assets/{MarketplaceDrawer-CfSiRi8e.js → MarketplaceDrawer-BuCbtqFk.js} +1 -1
- package/web/dist/assets/TaskGraphDrawer-e_bg3f5u.js +1 -0
- package/web/dist/assets/{WhatsNewDialog-vP7buLos.js → WhatsNewDialog-BB9ieq3T.js} +1 -1
- package/web/dist/assets/{WorkerModal-CSorwcdP.js → WorkerModal-CGeWO3Dg.js} +1 -1
- package/web/dist/assets/{WorkflowsDrawer-BXS3w9Uq.js → WorkflowsDrawer-CVvGxxdR.js} +1 -1
- package/web/dist/assets/WorkspaceMemoryDrawer-BamHuJEp.js +1 -0
- package/web/dist/assets/WorkspaceTaskDrawer-CqntbH9-.js +1 -0
- package/web/dist/assets/index-BOpmn5Ij.js +84 -0
- package/web/dist/assets/index-rPYd0MUk.css +1 -0
- package/web/dist/assets/{search-BZw4T67h.js → search-DKgM9IgX.js} +1 -1
- package/web/dist/assets/{square-terminal-B7E57In1.js → square-terminal-DMWazpEv.js} +1 -1
- package/web/dist/index.html +2 -2
- package/web/dist/sw.js +1 -1
- package/web/dist/assets/AddWorkspaceFlow-cxvhVAsT.js +0 -1
- package/web/dist/assets/TaskGraphDrawer-C2JufcPs.js +0 -1
- package/web/dist/assets/WorkspaceMemoryDrawer-D71ivohr.js +0 -1
- package/web/dist/assets/WorkspaceTaskDrawer-CGCTSHKa.js +0 -1
- package/web/dist/assets/index-BcwN8cCw.js +0 -79
- package/web/dist/assets/index-StXTPHls.css +0 -1
|
@@ -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
|
-
...(
|
|
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:
|
|
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
|
|
73
|
-
auto-starting them
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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 =
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
328
|
-
|
|
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
|
-
|
|
331
|
-
|
|
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
|
-
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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;
|