@tt-a1i/hive 1.3.0 → 1.3.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.
- package/CHANGELOG.md +42 -14
- package/README.en.md +30 -8
- package/README.md +21 -6
- package/assets/hive-team-view.png +0 -0
- package/dist/src/cli/team.d.ts +6 -0
- package/dist/src/cli/team.js +48 -0
- package/dist/src/server/agent-launch-cache.js +25 -6
- package/dist/src/server/agent-manager.d.ts +2 -2
- package/dist/src/server/agent-runtime-contract.d.ts +3 -0
- package/dist/src/server/agent-runtime.js +3 -0
- package/dist/src/server/agent-startup-instructions.js +1 -1
- package/dist/src/server/agent-stdin-dispatcher.d.ts +4 -0
- package/dist/src/server/agent-stdin-dispatcher.js +12 -0
- package/dist/src/server/app.js +1 -1
- package/dist/src/server/dispatch-ledger-store.d.ts +22 -1
- package/dist/src/server/dispatch-ledger-store.js +34 -3
- package/dist/src/server/hive-team-guidance.js +3 -1
- package/dist/src/server/route-types.d.ts +7 -0
- package/dist/src/server/routes-dispatches.js +4 -2
- package/dist/src/server/routes-runtime.js +1 -0
- package/dist/src/server/routes-team.js +22 -0
- package/dist/src/server/runtime-store-helpers.d.ts +2 -1
- package/dist/src/server/runtime-store-helpers.js +14 -4
- package/dist/src/server/runtime-store.d.ts +5 -8
- package/dist/src/server/runtime-store.js +1 -0
- package/dist/src/server/tasks-websocket-server.d.ts +2 -1
- package/dist/src/server/tasks-websocket-server.js +18 -2
- package/dist/src/server/team-authz.d.ts +1 -1
- package/dist/src/server/team-authz.js +1 -1
- package/dist/src/server/team-operations.d.ts +16 -1
- package/dist/src/server/team-operations.js +28 -1
- package/dist/src/server/terminal-input-profile.d.ts +10 -0
- package/dist/src/server/terminal-input-profile.js +15 -0
- package/dist/src/server/terminal-stream-hub.js +10 -2
- package/dist/src/server/terminal-ws-server.d.ts +2 -1
- package/dist/src/server/terminal-ws-server.js +2 -2
- package/dist/src/server/workspace-shell-runtime.d.ts +2 -1
- package/dist/src/server/workspace-shell-runtime.js +3 -2
- package/dist/src/server/workspace-store-contract.d.ts +1 -0
- package/dist/src/server/workspace-store-mutations.d.ts +1 -0
- package/dist/src/server/workspace-store-mutations.js +1 -0
- package/dist/src/server/workspace-store.js +2 -1
- package/package.json +2 -2
- package/web/dist/assets/AddWorkerDialog-D6-K1wJm.js +1 -0
- package/web/dist/assets/AddWorkspaceDialog-Du0lndJ0.js +1 -0
- package/web/dist/assets/FirstRunWizard-B8k7S5De.js +1 -0
- package/web/dist/assets/WorkerModal-B3XhIvAX.js +1 -0
- package/web/dist/assets/WorkspaceTaskDrawer-CjEoLJvS.js +1 -0
- package/web/dist/assets/chevron-right-BvbSCniy.js +1 -0
- package/web/dist/assets/index-DB5fHAMI.js +81 -0
- package/web/dist/assets/index-Sbdu6Se0.css +1 -0
- package/web/dist/index.html +2 -2
- package/web/dist/sw.js +1 -1
- package/web/dist/assets/index-CSEt-Qiy.js +0 -66
- package/web/dist/assets/index-RsXXnrVz.css +0 -1
|
@@ -22,6 +22,13 @@ export interface ReportTaskBody {
|
|
|
22
22
|
status?: string;
|
|
23
23
|
artifacts?: unknown[];
|
|
24
24
|
}
|
|
25
|
+
export interface CancelTaskBody {
|
|
26
|
+
dispatch_id?: string;
|
|
27
|
+
project_id: string;
|
|
28
|
+
from_agent_id: string;
|
|
29
|
+
token?: string;
|
|
30
|
+
reason?: string;
|
|
31
|
+
}
|
|
25
32
|
export interface CreateWorkspaceBody {
|
|
26
33
|
path: string;
|
|
27
34
|
name: string;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { serializeDispatchRecord } from './dispatch-ledger-serializer.js';
|
|
2
2
|
import { getRequiredParam, route, sendJson } from './route-helpers.js';
|
|
3
3
|
import { requireUiTokenFromRequest } from './ui-auth-helpers.js';
|
|
4
|
-
const DISPATCH_STATUSES = new Set(['queued', 'submitted', 'reported']);
|
|
4
|
+
const DISPATCH_STATUSES = new Set(['queued', 'submitted', 'reported', 'cancelled']);
|
|
5
5
|
const MAX_DISPATCH_LIMIT = 100;
|
|
6
6
|
const MAX_DISPATCH_OFFSET = 100_000;
|
|
7
7
|
const readBoundedInt = (response, value, name, fallback, max) => {
|
|
@@ -33,7 +33,9 @@ export const dispatchRoutes = [
|
|
|
33
33
|
}
|
|
34
34
|
const state = url.searchParams.get('state');
|
|
35
35
|
if (state !== null && !isDispatchStatus(state)) {
|
|
36
|
-
sendJson(response, 400, {
|
|
36
|
+
sendJson(response, 400, {
|
|
37
|
+
error: 'state must be queued, submitted, reported, or cancelled',
|
|
38
|
+
});
|
|
37
39
|
return;
|
|
38
40
|
}
|
|
39
41
|
const limit = readBoundedInt(response, url.searchParams.get('limit'), 'limit', MAX_DISPATCH_LIMIT, MAX_DISPATCH_LIMIT);
|
|
@@ -25,6 +25,7 @@ export const runtimeRoutes = [
|
|
|
25
25
|
agent_name: summary?.agent_name ?? 'Shell',
|
|
26
26
|
run_id: run.runId,
|
|
27
27
|
status: run.status,
|
|
28
|
+
terminal_input_profile: summary?.terminal_input_profile ?? 'default',
|
|
28
29
|
});
|
|
29
30
|
}),
|
|
30
31
|
route('DELETE', '/api/workspaces/:workspaceId/shell/:runId', ({ params, request, response, store }) => {
|
|
@@ -29,6 +29,28 @@ export const teamRoutes = [
|
|
|
29
29
|
});
|
|
30
30
|
sendJson(response, 202, { dispatch_id: dispatch.id, ok: true });
|
|
31
31
|
}),
|
|
32
|
+
route('POST', '/api/team/cancel', async ({ request, response, store }) => {
|
|
33
|
+
const body = await readJsonBody(request);
|
|
34
|
+
const projectId = requireNonEmptyString(body.project_id, 'project_id');
|
|
35
|
+
const fromAgentId = requireNonEmptyString(body.from_agent_id, 'from_agent_id');
|
|
36
|
+
const dispatchId = requireNonEmptyString(body.dispatch_id, 'dispatch_id');
|
|
37
|
+
const reason = requireNonEmptyString(body.reason, 'reason');
|
|
38
|
+
const agent = authenticateCliAgent({
|
|
39
|
+
fromAgentId,
|
|
40
|
+
getAgent: store.getAgent,
|
|
41
|
+
token: body.token,
|
|
42
|
+
validateToken: store.validateAgentToken,
|
|
43
|
+
workspaceId: projectId,
|
|
44
|
+
});
|
|
45
|
+
requireCommandForRole(agent, 'cancel');
|
|
46
|
+
const result = store.cancelTask(projectId, dispatchId, { fromAgentId, reason });
|
|
47
|
+
sendJson(response, 202, {
|
|
48
|
+
dispatch_id: result.dispatch?.id ?? null,
|
|
49
|
+
forward_error: result.forwardError,
|
|
50
|
+
forwarded: result.forwarded,
|
|
51
|
+
ok: true,
|
|
52
|
+
});
|
|
53
|
+
}),
|
|
32
54
|
route('POST', '/api/team/report', async ({ request, response, store }) => {
|
|
33
55
|
const body = await readJsonBody(request);
|
|
34
56
|
const projectId = requireNonEmptyString(body.project_id, 'project_id');
|
|
@@ -52,6 +52,7 @@ export declare const createRuntimeStoreLifecycle: ({ agentManager, services, }:
|
|
|
52
52
|
agent_name: string;
|
|
53
53
|
run_id: string;
|
|
54
54
|
status: import("./agent-manager.js").RunStatus;
|
|
55
|
+
terminal_input_profile: import("./terminal-input-profile.js").TerminalInputProfile;
|
|
55
56
|
}[];
|
|
56
57
|
startAgent: (workspaceId: string, agentId: string, input: {
|
|
57
58
|
hivePort: string;
|
|
@@ -74,7 +75,7 @@ export declare const createRuntimeStoreLifecycle: ({ agentManager, services, }:
|
|
|
74
75
|
})[]>;
|
|
75
76
|
registerTasksListener: (listener: (workspaceId: string, content: string) => void) => () => void;
|
|
76
77
|
startWorkspaceWatch: (workspaceId: string) => Promise<void>;
|
|
77
|
-
writeRunInput: (runId: string,
|
|
78
|
+
writeRunInput: (runId: string, input: Buffer | string) => void;
|
|
78
79
|
pauseTerminalRun: (runId: string) => void;
|
|
79
80
|
resizeTerminalRun: (runId: string, cols: number, rows: number) => void;
|
|
80
81
|
resumeTerminalRun: (runId: string) => void;
|
|
@@ -10,6 +10,7 @@ import { createSettingsStore } from './settings-store.js';
|
|
|
10
10
|
import { createTasksFileService } from './tasks-file.js';
|
|
11
11
|
import { createTasksFileWatcher } from './tasks-file-watcher.js';
|
|
12
12
|
import { createTeamOperations } from './team-operations.js';
|
|
13
|
+
import { resolveTerminalInputProfile } from './terminal-input-profile.js';
|
|
13
14
|
import { createUiAuth } from './ui-auth.js';
|
|
14
15
|
import { createWorkerOutputTracker } from './worker-output-tracker.js';
|
|
15
16
|
import { createWorkspaceShellRuntime } from './workspace-shell-runtime.js';
|
|
@@ -63,7 +64,9 @@ export const createRuntimeStoreServices = (options = {}) => {
|
|
|
63
64
|
deleteDispatch: dispatchLedgerStore.deleteDispatch,
|
|
64
65
|
deleteMessage: messageLogStore.deleteMessage,
|
|
65
66
|
findOpenDispatch: dispatchLedgerStore.findOpenDispatch,
|
|
67
|
+
findOpenDispatchById: dispatchLedgerStore.findOpenDispatchById,
|
|
66
68
|
insertMessage: messageLogStore.insertMessage,
|
|
69
|
+
markDispatchCancelled: dispatchLedgerStore.markCancelled,
|
|
67
70
|
markDispatchReportedByWorker: dispatchLedgerStore.markReportedByWorker,
|
|
68
71
|
markDispatchSubmitted: dispatchLedgerStore.markSubmitted,
|
|
69
72
|
workspaceStore,
|
|
@@ -167,8 +170,15 @@ export const createRuntimeStoreLifecycle = ({ agentManager, services, }) => {
|
|
|
167
170
|
const run = services.agentRuntime.getActiveRunByAgentId(workspaceId, agent.id);
|
|
168
171
|
if (!run)
|
|
169
172
|
return [];
|
|
173
|
+
const launchConfig = services.agentRuntime.peekAgentLaunchConfig(workspaceId, agent.id);
|
|
170
174
|
return [
|
|
171
|
-
{
|
|
175
|
+
{
|
|
176
|
+
agent_id: agent.id,
|
|
177
|
+
agent_name: agent.name,
|
|
178
|
+
run_id: run.runId,
|
|
179
|
+
status: run.status,
|
|
180
|
+
terminal_input_profile: resolveTerminalInputProfile(launchConfig),
|
|
181
|
+
},
|
|
172
182
|
];
|
|
173
183
|
}),
|
|
174
184
|
...services.shellRuntime.listTerminalRuns(workspaceId),
|
|
@@ -186,14 +196,14 @@ export const createRuntimeStoreLifecycle = ({ agentManager, services, }) => {
|
|
|
186
196
|
const workspace = services.workspaceStore.getWorkspaceSnapshot(workspaceId);
|
|
187
197
|
await services.tasksFileWatcher.start(workspaceId, workspace.summary.path);
|
|
188
198
|
},
|
|
189
|
-
writeRunInput: (runId,
|
|
199
|
+
writeRunInput: (runId, input) => {
|
|
190
200
|
if (!agentManager)
|
|
191
201
|
throw new Error('Agent manager is required for PTY stdin writes');
|
|
192
202
|
if (services.shellRuntime.hasRun(runId)) {
|
|
193
|
-
services.shellRuntime.writeInput(runId,
|
|
203
|
+
services.shellRuntime.writeInput(runId, input);
|
|
194
204
|
return;
|
|
195
205
|
}
|
|
196
|
-
agentManager.writeInput(runId,
|
|
206
|
+
agentManager.writeInput(runId, input);
|
|
197
207
|
},
|
|
198
208
|
pauseTerminalRun: (runId) => {
|
|
199
209
|
if (services.shellRuntime.hasRun(runId))
|
|
@@ -6,7 +6,8 @@ import type { DispatchRecord, ListDispatchesOptions } from './dispatch-ledger-st
|
|
|
6
6
|
import type { RecoveryMessage } from './message-log-store.js';
|
|
7
7
|
import type { PtyOutputBus } from './pty-output-bus.js';
|
|
8
8
|
import type { SettingsStore } from './settings-store.js';
|
|
9
|
-
import type { DispatchTaskInput, ReportTaskInput, ReportTaskResult, StatusTaskInput } from './team-operations.js';
|
|
9
|
+
import type { CancelTaskInput, DispatchTaskInput, ReportTaskInput, ReportTaskResult, StatusTaskInput } from './team-operations.js';
|
|
10
|
+
import type { TerminalRunSummary } from './terminal-input-profile.js';
|
|
10
11
|
import type { WorkerInput, WorkspaceRecord } from './workspace-store.js';
|
|
11
12
|
interface RuntimeStore {
|
|
12
13
|
close: () => Promise<void>;
|
|
@@ -21,6 +22,7 @@ interface RuntimeStore {
|
|
|
21
22
|
dispatchTaskByWorkerName: (workspaceId: string, workerName: string, text: string, input?: DispatchTaskInput) => Promise<DispatchRecord>;
|
|
22
23
|
reportTask: (workspaceId: string, workerId: string, input?: ReportTaskInput) => ReportTaskResult;
|
|
23
24
|
statusTask: (workspaceId: string, workerId: string, input?: StatusTaskInput) => ReportTaskResult;
|
|
25
|
+
cancelTask: (workspaceId: string, dispatchId: string, input: CancelTaskInput) => ReportTaskResult;
|
|
24
26
|
listDispatches: (workspaceId: string, options?: ListDispatchesOptions) => DispatchRecord[];
|
|
25
27
|
listWorkers: (workspaceId: string) => TeamListItem[];
|
|
26
28
|
getLastPtyLineForAgent: (workspaceId: string, agentId: string) => string | null;
|
|
@@ -28,12 +30,7 @@ interface RuntimeStore {
|
|
|
28
30
|
getWorker: (workspaceId: string, workerId: string) => AgentSummary;
|
|
29
31
|
getAgent: (workspaceId: string, agentId: string) => AgentSummary;
|
|
30
32
|
getPtyOutputBus: () => PtyOutputBus;
|
|
31
|
-
listTerminalRuns: (workspaceId: string) =>
|
|
32
|
-
agent_id: string;
|
|
33
|
-
agent_name: string;
|
|
34
|
-
run_id: string;
|
|
35
|
-
status: string;
|
|
36
|
-
}>;
|
|
33
|
+
listTerminalRuns: (workspaceId: string) => TerminalRunSummary[];
|
|
37
34
|
closeWorkspaceShell: (workspaceId: string, runId: string) => boolean;
|
|
38
35
|
startWorkspaceShell: (workspaceId: string) => Promise<LiveAgentRun>;
|
|
39
36
|
configureAgentLaunch: (workspaceId: string, agentId: string, input: AgentLaunchConfigInput) => void;
|
|
@@ -57,7 +54,7 @@ interface RuntimeStore {
|
|
|
57
54
|
resizeAgentRun: (runId: string, cols: number, rows: number) => void;
|
|
58
55
|
resumeTerminalRun: (runId: string) => void;
|
|
59
56
|
settings: SettingsStore;
|
|
60
|
-
writeRunInput: (runId: string,
|
|
57
|
+
writeRunInput: (runId: string, input: Buffer | string) => void;
|
|
61
58
|
getUiToken: () => string;
|
|
62
59
|
stopAgentRun: (runId: string) => void;
|
|
63
60
|
validateAgentToken: (agentId: string, token: string | undefined) => boolean;
|
|
@@ -48,6 +48,7 @@ export const createRuntimeStore = (options = {}) => {
|
|
|
48
48
|
});
|
|
49
49
|
},
|
|
50
50
|
recordUserInput: services.teamOps.recordUserInput,
|
|
51
|
+
cancelTask: services.teamOps.cancelTask,
|
|
51
52
|
dispatchTask: services.teamOps.dispatchTask,
|
|
52
53
|
dispatchTaskByWorkerName: services.teamOps.dispatchTaskByWorkerName,
|
|
53
54
|
reportTask: services.teamOps.reportTask,
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { Server } from 'node:http';
|
|
2
2
|
import type { RuntimeStore } from './runtime-store.js';
|
|
3
|
+
import type { TasksFileService } from './tasks-file.js';
|
|
3
4
|
export interface TasksWebSocketServer {
|
|
4
5
|
close: () => void;
|
|
5
6
|
publish: (workspaceId: string, content: string) => void;
|
|
6
7
|
}
|
|
7
|
-
export declare const createTasksWebSocketServer: (server: Server, store: RuntimeStore) => TasksWebSocketServer;
|
|
8
|
+
export declare const createTasksWebSocketServer: (server: Server, store: RuntimeStore, tasksFileService: Pick<TasksFileService, "readTasks">) => TasksWebSocketServer;
|
|
@@ -10,7 +10,7 @@ const rejectUpgrade = (socket, status) => {
|
|
|
10
10
|
socket.write(`HTTP/1.1 ${status}\r\n\r\n`);
|
|
11
11
|
socket.destroy();
|
|
12
12
|
};
|
|
13
|
-
export const createTasksWebSocketServer = (server, store) => {
|
|
13
|
+
export const createTasksWebSocketServer = (server, store, tasksFileService) => {
|
|
14
14
|
const wss = new WebSocketServer({ noServer: true });
|
|
15
15
|
const socketsByWorkspaceId = new Map();
|
|
16
16
|
const validateUpgradeSession = (request) => {
|
|
@@ -33,8 +33,9 @@ export const createTasksWebSocketServer = (server, store) => {
|
|
|
33
33
|
rejectUpgrade(socket, '401 Unauthorized');
|
|
34
34
|
return;
|
|
35
35
|
}
|
|
36
|
+
let workspacePath = '';
|
|
36
37
|
try {
|
|
37
|
-
store.getWorkspaceSnapshot(workspaceId);
|
|
38
|
+
workspacePath = store.getWorkspaceSnapshot(workspaceId).summary.path;
|
|
38
39
|
}
|
|
39
40
|
catch {
|
|
40
41
|
rejectUpgrade(socket, '404 Not Found');
|
|
@@ -50,6 +51,21 @@ export const createTasksWebSocketServer = (server, store) => {
|
|
|
50
51
|
socketsByWorkspaceId.delete(workspaceId);
|
|
51
52
|
}
|
|
52
53
|
});
|
|
54
|
+
setImmediate(() => {
|
|
55
|
+
if (ws.readyState !== ws.OPEN)
|
|
56
|
+
return;
|
|
57
|
+
try {
|
|
58
|
+
ws.send(JSON.stringify({
|
|
59
|
+
type: 'tasks-snapshot',
|
|
60
|
+
content: tasksFileService.readTasks(workspacePath),
|
|
61
|
+
}));
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
if (ws.readyState === ws.OPEN) {
|
|
65
|
+
ws.send(JSON.stringify({ type: 'tasks-snapshot', content: '' }));
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
});
|
|
53
69
|
});
|
|
54
70
|
});
|
|
55
71
|
return {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AgentSummary } from '../shared/types.js';
|
|
2
|
-
export type TeamCommand = 'send' | 'list' | 'report' | 'status' | 'help';
|
|
2
|
+
export type TeamCommand = 'send' | 'list' | 'report' | 'status' | 'cancel' | 'help';
|
|
3
3
|
export declare const commandAllowedForRole: (role: AgentSummary["role"], command: TeamCommand) => boolean;
|
|
4
4
|
interface AuthenticateInput {
|
|
5
5
|
fromAgentId: string | undefined;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ForbiddenError, UnauthorizedError } from './http-errors.js';
|
|
2
|
-
const ORCHESTRATOR_COMMANDS = new Set(['send', 'list', 'help']);
|
|
2
|
+
const ORCHESTRATOR_COMMANDS = new Set(['send', 'list', 'cancel', 'help']);
|
|
3
3
|
const WORKER_COMMANDS = new Set(['report', 'status', 'help']);
|
|
4
4
|
const WORKER_ROLES = new Set(['coder', 'reviewer', 'tester', 'custom']);
|
|
5
5
|
export const commandAllowedForRole = (role, command) => {
|
|
@@ -13,7 +13,13 @@ export interface TeamOperationsInput {
|
|
|
13
13
|
deleteDispatch: (dispatchId: string) => void;
|
|
14
14
|
deleteMessage: (handle: MessageLogHandle) => void;
|
|
15
15
|
findOpenDispatch: (workspaceId: string, toAgentId: string, dispatchId?: string) => DispatchRecord | undefined;
|
|
16
|
+
findOpenDispatchById: (workspaceId: string, dispatchId: string) => DispatchRecord | undefined;
|
|
16
17
|
insertMessage: (record: MessageLogRecord) => MessageLogHandle;
|
|
18
|
+
markDispatchCancelled: (input: {
|
|
19
|
+
dispatchId: string;
|
|
20
|
+
reason: string;
|
|
21
|
+
workspaceId: string;
|
|
22
|
+
}) => DispatchRecord | undefined;
|
|
17
23
|
markDispatchReportedByWorker: (input: {
|
|
18
24
|
artifacts: string[];
|
|
19
25
|
dispatchId?: string;
|
|
@@ -40,12 +46,21 @@ export interface StatusTaskInput {
|
|
|
40
46
|
requireActiveRun?: boolean;
|
|
41
47
|
text?: string;
|
|
42
48
|
}
|
|
49
|
+
export interface CancelTaskInput {
|
|
50
|
+
fromAgentId: string;
|
|
51
|
+
reason: string;
|
|
52
|
+
}
|
|
43
53
|
export interface ReportTaskResult {
|
|
44
54
|
dispatch: DispatchRecord | null;
|
|
45
55
|
forwardError: string | null;
|
|
46
56
|
forwarded: boolean;
|
|
47
57
|
}
|
|
48
|
-
export declare const createTeamOperations: ({ agentRuntime, createDispatch, deleteDispatch, deleteMessage, findOpenDispatch, insertMessage, markDispatchReportedByWorker, markDispatchSubmitted, workspaceStore, }: TeamOperationsInput) => {
|
|
58
|
+
export declare const createTeamOperations: ({ agentRuntime, createDispatch, deleteDispatch, deleteMessage, findOpenDispatch, findOpenDispatchById, insertMessage, markDispatchCancelled, markDispatchReportedByWorker, markDispatchSubmitted, workspaceStore, }: TeamOperationsInput) => {
|
|
59
|
+
cancelTask(workspaceId: string, dispatchId: string, input: CancelTaskInput): {
|
|
60
|
+
dispatch: DispatchRecord;
|
|
61
|
+
forwardError: string | null;
|
|
62
|
+
forwarded: boolean;
|
|
63
|
+
};
|
|
49
64
|
dispatchTask: (workspaceId: string, workerId: string, text: string, input?: DispatchTaskInput) => Promise<DispatchRecord>;
|
|
50
65
|
dispatchTaskByWorkerName(workspaceId: string, workerName: string, text: string, input?: DispatchTaskInput): Promise<DispatchRecord>;
|
|
51
66
|
recordUserInput(workspaceId: string, orchestratorId: string, text: string): void;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ConflictError, PtyInactiveError } from './http-errors.js';
|
|
2
2
|
import { createReportMessage, createSendMessage, createStatusMessage, createUserInputMessage, } from './runtime-message-builders.js';
|
|
3
3
|
const reportForwardErrorMessage = (error) => error instanceof Error ? error.message : String(error);
|
|
4
|
-
export const createTeamOperations = ({ agentRuntime, createDispatch, deleteDispatch, deleteMessage, findOpenDispatch, insertMessage, markDispatchReportedByWorker, markDispatchSubmitted, workspaceStore, }) => {
|
|
4
|
+
export const createTeamOperations = ({ agentRuntime, createDispatch, deleteDispatch, deleteMessage, findOpenDispatch, findOpenDispatchById, insertMessage, markDispatchCancelled, markDispatchReportedByWorker, markDispatchSubmitted, workspaceStore, }) => {
|
|
5
5
|
const ensureWorkerRun = async (workspaceId, workerId, hivePort) => {
|
|
6
6
|
if (agentRuntime.getActiveRunByAgentId(workspaceId, workerId)) {
|
|
7
7
|
return;
|
|
@@ -54,6 +54,33 @@ export const createTeamOperations = ({ agentRuntime, createDispatch, deleteDispa
|
|
|
54
54
|
}
|
|
55
55
|
};
|
|
56
56
|
return {
|
|
57
|
+
cancelTask(workspaceId, dispatchId, input) {
|
|
58
|
+
workspaceStore.getAgent(workspaceId, input.fromAgentId);
|
|
59
|
+
const openDispatch = findOpenDispatchById(workspaceId, dispatchId);
|
|
60
|
+
if (!openDispatch) {
|
|
61
|
+
throw new ConflictError(`No open dispatch: ${dispatchId}`);
|
|
62
|
+
}
|
|
63
|
+
const dispatch = markDispatchCancelled({
|
|
64
|
+
dispatchId,
|
|
65
|
+
reason: input.reason,
|
|
66
|
+
workspaceId,
|
|
67
|
+
});
|
|
68
|
+
if (!dispatch) {
|
|
69
|
+
throw new ConflictError(`No open dispatch: ${dispatchId}`);
|
|
70
|
+
}
|
|
71
|
+
workspaceStore.markTaskCancelled(workspaceId, dispatch.toAgentId);
|
|
72
|
+
let forwardError = null;
|
|
73
|
+
let forwarded = false;
|
|
74
|
+
try {
|
|
75
|
+
agentRuntime.writeCancelPrompt(workspaceId, dispatch.toAgentId, dispatch.id, input.reason);
|
|
76
|
+
forwarded = true;
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
forwardError = reportForwardErrorMessage(error);
|
|
80
|
+
console.error('[hive] swallowed:teamCancel.forward', error);
|
|
81
|
+
}
|
|
82
|
+
return { dispatch, forwardError, forwarded };
|
|
83
|
+
},
|
|
57
84
|
dispatchTask,
|
|
58
85
|
dispatchTaskByWorkerName(workspaceId, workerName, text, input = {}) {
|
|
59
86
|
const worker = workspaceStore.getWorkerByName(workspaceId, workerName);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { AgentLaunchConfigInput } from './agent-run-store.js';
|
|
2
|
+
export type TerminalInputProfile = 'default' | 'opencode';
|
|
3
|
+
export interface TerminalRunSummary {
|
|
4
|
+
agent_id: string;
|
|
5
|
+
agent_name: string;
|
|
6
|
+
run_id: string;
|
|
7
|
+
status: string;
|
|
8
|
+
terminal_input_profile: TerminalInputProfile;
|
|
9
|
+
}
|
|
10
|
+
export declare const resolveTerminalInputProfile: (config: AgentLaunchConfigInput | undefined) => TerminalInputProfile;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { basename } from 'node:path';
|
|
2
|
+
const normalizeExecutable = (value) => {
|
|
3
|
+
if (!value)
|
|
4
|
+
return null;
|
|
5
|
+
const normalized = basename(value).toLowerCase();
|
|
6
|
+
return normalized.replace(/\.(cmd|exe)$/u, '');
|
|
7
|
+
};
|
|
8
|
+
export const resolveTerminalInputProfile = (config) => {
|
|
9
|
+
if (!config)
|
|
10
|
+
return 'default';
|
|
11
|
+
if (config.commandPresetId === 'opencode')
|
|
12
|
+
return 'opencode';
|
|
13
|
+
const executable = normalizeExecutable(config.interactiveCommand) ?? normalizeExecutable(config.command);
|
|
14
|
+
return executable === 'opencode' ? 'opencode' : 'default';
|
|
15
|
+
};
|
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import { createTerminalOutputFlow } from './terminal-flow-control.js';
|
|
2
2
|
import { parseTerminalControlMessage, serializeTerminalError, serializeTerminalExit, serializeTerminalRestore, } from './terminal-protocol.js';
|
|
3
3
|
import { TerminalStateMirror } from './terminal-state-mirror.js';
|
|
4
|
+
const normalizeTerminalInput = (raw, isBinary) => {
|
|
5
|
+
const bytes = Buffer.isBuffer(raw)
|
|
6
|
+
? raw
|
|
7
|
+
: Array.isArray(raw)
|
|
8
|
+
? Buffer.concat(raw)
|
|
9
|
+
: Buffer.from(raw);
|
|
10
|
+
return isBinary ? Buffer.from(bytes) : bytes.toString();
|
|
11
|
+
};
|
|
4
12
|
export const createTerminalStreamHub = (store) => {
|
|
5
13
|
const runStates = new Map();
|
|
6
14
|
const maybeResumeRun = (runId, state, clientId) => {
|
|
@@ -149,8 +157,8 @@ export const createTerminalStreamHub = (store) => {
|
|
|
149
157
|
maybeResumeRun(runId, state, clientId);
|
|
150
158
|
},
|
|
151
159
|
});
|
|
152
|
-
socket.on('message', (raw) => {
|
|
153
|
-
store.writeRunInput(runId, raw
|
|
160
|
+
socket.on('message', (raw, isBinary) => {
|
|
161
|
+
store.writeRunInput(runId, normalizeTerminalInput(raw, isBinary));
|
|
154
162
|
});
|
|
155
163
|
socket.on('close', () => {
|
|
156
164
|
if (viewer.ioSocket === socket)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Server } from 'node:http';
|
|
2
2
|
import type { RuntimeStore } from './runtime-store.js';
|
|
3
|
-
|
|
3
|
+
import type { TasksFileService } from './tasks-file.js';
|
|
4
|
+
export declare const createTerminalWebSocketServer: (server: Server, store: RuntimeStore, tasksFileService: Pick<TasksFileService, "readTasks">) => {
|
|
4
5
|
close: () => void;
|
|
5
6
|
};
|
|
@@ -28,10 +28,10 @@ const rejectUpgrade = (socket, status) => {
|
|
|
28
28
|
socket.write(`HTTP/1.1 ${status}\r\n\r\n`);
|
|
29
29
|
socket.destroy();
|
|
30
30
|
};
|
|
31
|
-
export const createTerminalWebSocketServer = (server, store) => {
|
|
31
|
+
export const createTerminalWebSocketServer = (server, store, tasksFileService) => {
|
|
32
32
|
const ioWss = new WebSocketServer({ noServer: true });
|
|
33
33
|
const controlWss = new WebSocketServer({ noServer: true });
|
|
34
|
-
const tasksWss = createTasksWebSocketServer(server, store);
|
|
34
|
+
const tasksWss = createTasksWebSocketServer(server, store, tasksFileService);
|
|
35
35
|
const hub = createTerminalStreamHub(store);
|
|
36
36
|
const disposeTasksListener = store.registerTasksListener((workspaceId, content) => {
|
|
37
37
|
tasksWss.publish(workspaceId, content);
|
|
@@ -18,12 +18,13 @@ export declare const createWorkspaceShellRuntime: (agentManager: AgentManager |
|
|
|
18
18
|
agent_name: string;
|
|
19
19
|
run_id: string;
|
|
20
20
|
status: import("./agent-manager.js").RunStatus;
|
|
21
|
+
terminal_input_profile: "default";
|
|
21
22
|
}[];
|
|
22
23
|
pauseRun(runId: string): void;
|
|
23
24
|
resizeRun(runId: string, cols: number, rows: number): void;
|
|
24
25
|
resumeRun(runId: string): void;
|
|
25
26
|
start(workspace: WorkspaceSummary): Promise<LiveAgentRun>;
|
|
26
27
|
stopRun(runId: string): void;
|
|
27
|
-
writeInput(runId: string,
|
|
28
|
+
writeInput(runId: string, input: Buffer | string): void;
|
|
28
29
|
};
|
|
29
30
|
export type WorkspaceShellRuntime = ReturnType<typeof createWorkspaceShellRuntime>;
|
|
@@ -153,6 +153,7 @@ export const createWorkspaceShellRuntime = (agentManager) => {
|
|
|
153
153
|
agent_name: labelsByRunId.get(runId) ?? 'Shell',
|
|
154
154
|
run_id: run.runId,
|
|
155
155
|
status: run.status,
|
|
156
|
+
terminal_input_profile: 'default',
|
|
156
157
|
},
|
|
157
158
|
];
|
|
158
159
|
}
|
|
@@ -197,9 +198,9 @@ export const createWorkspaceShellRuntime = (agentManager) => {
|
|
|
197
198
|
if (hasRun(runId))
|
|
198
199
|
stopPtyRun(runId);
|
|
199
200
|
},
|
|
200
|
-
writeInput(runId,
|
|
201
|
+
writeInput(runId, input) {
|
|
201
202
|
if (hasRun(runId))
|
|
202
|
-
requireManager().writeInput(runId,
|
|
203
|
+
requireManager().writeInput(runId, input);
|
|
203
204
|
},
|
|
204
205
|
};
|
|
205
206
|
};
|
|
@@ -24,5 +24,6 @@ export interface WorkspaceStore {
|
|
|
24
24
|
markAgentStarted: (workspaceId: string, agentId: string) => void;
|
|
25
25
|
markAgentStopped: (workspaceId: string, agentId: string) => void;
|
|
26
26
|
markTaskDispatched: (workspaceId: string, workerId: string) => void;
|
|
27
|
+
markTaskCancelled: (workspaceId: string, workerId: string) => void;
|
|
27
28
|
markTaskReported: (workspaceId: string, workerId: string) => void;
|
|
28
29
|
}
|
|
@@ -10,4 +10,5 @@ export declare const markAgentStarted: (workspaces: WorkspaceMap, workspaceId: s
|
|
|
10
10
|
export declare const markAgentStopped: (workspaces: WorkspaceMap, workspaceId: string, agentId: string) => void;
|
|
11
11
|
export declare const markTaskDispatched: (workspaces: WorkspaceMap, workspaceId: string, workerId: string) => void;
|
|
12
12
|
export declare const markTaskReported: (workspaces: WorkspaceMap, workspaceId: string, workerId: string) => void;
|
|
13
|
+
export declare const markTaskCancelled: (workspaces: WorkspaceMap, workspaceId: string, workerId: string) => void;
|
|
13
14
|
export {};
|
|
@@ -2,7 +2,7 @@ import { randomUUID } from 'node:crypto';
|
|
|
2
2
|
import { ConflictError } from './http-errors.js';
|
|
3
3
|
import { getDefaultRoleDescription } from './role-templates.js';
|
|
4
4
|
import { hydrateWorkspaceFromDb, seedWorkspacesFromDb } from './workspace-store-hydration.js';
|
|
5
|
-
import { getAgentRecord, getWorkerByNameRecord, getWorkerRecord, markAgentStarted, markAgentStopped, markTaskDispatched, markTaskReported, } from './workspace-store-mutations.js';
|
|
5
|
+
import { getAgentRecord, getWorkerByNameRecord, getWorkerRecord, markAgentStarted, markAgentStopped, markTaskCancelled, markTaskDispatched, markTaskReported, } from './workspace-store-mutations.js';
|
|
6
6
|
import { createOrchestrator, isWorkerAgent, } from './workspace-store-support.js';
|
|
7
7
|
const normalizeWorkerName = (name) => {
|
|
8
8
|
const trimmed = name.trim();
|
|
@@ -113,6 +113,7 @@ export const createWorkspaceStore = (db, messageKinds) => {
|
|
|
113
113
|
markAgentStarted: (workspaceId, agentId) => markAgentStarted(workspaces, workspaceId, agentId),
|
|
114
114
|
markAgentStopped: (workspaceId, agentId) => markAgentStopped(workspaces, workspaceId, agentId),
|
|
115
115
|
markTaskDispatched: (workspaceId, workerId) => markTaskDispatched(workspaces, workspaceId, workerId),
|
|
116
|
+
markTaskCancelled: (workspaceId, workerId) => markTaskCancelled(workspaces, workspaceId, workerId),
|
|
116
117
|
markTaskReported: (workspaceId, workerId) => markTaskReported(workspaces, workspaceId, workerId),
|
|
117
118
|
};
|
|
118
119
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tt-a1i/hive",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.4",
|
|
4
4
|
"description": "Browser-native hive-mind for CLI coding agents — Claude Code, Codex, Gemini, and OpenCode collaborate as real PTY processes via a team protocol.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"packageManager": "pnpm@10.30.3",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
],
|
|
18
18
|
"repository": {
|
|
19
19
|
"type": "git",
|
|
20
|
-
"url": "git+https://github.com/tt-a1i/hive
|
|
20
|
+
"url": "git+https://github.com/tt-a1i/hive.git"
|
|
21
21
|
},
|
|
22
22
|
"bugs": {
|
|
23
23
|
"url": "https://github.com/tt-a1i/hive/issues"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{c as S,j as e,A as H,u as k,r as x,C as z,e as L,h as K,x as Q,y as V,z as G,k as J,m as U,O as X,n as Y,o as Z,D as R,a as ee}from"./index-DB5fHAMI.js";const te=[["path",{d:"M12 7v6",key:"lw1j43"}],["path",{d:"M15 10H9",key:"o6yqo3"}],["path",{d:"M17 3a2 2 0 0 1 2 2v15a1 1 0 0 1-1.496.868l-4.512-2.578a2 2 0 0 0-1.984 0l-4.512 2.578A1 1 0 0 1 5 20V5a2 2 0 0 1 2-2z",key:"oz39mx"}]],ae=S("bookmark-plus",te);const re=[["rect",{width:"12",height:"12",x:"2",y:"10",rx:"2",ry:"2",key:"6agr2n"}],["path",{d:"m17.92 14 3.5-3.5a2.24 2.24 0 0 0 0-3l-5-4.92a2.24 2.24 0 0 0-3 0L10 6",key:"1o487t"}],["path",{d:"M6 18h.01",key:"uhywen"}],["path",{d:"M10 14h.01",key:"ssrbsk"}],["path",{d:"M15 6h.01",key:"cblpky"}],["path",{d:"M18 9h.01",key:"2061c0"}]],se=S("dices",re);const ne=[["path",{d:"m21 21-4.34-4.34",key:"14j7rj"}],["circle",{cx:"11",cy:"11",r:"8",key:"4ej97u"}]],le=S("search",ne),oe={orchestrator:"Or",coder:"Co",reviewer:"Re",tester:"Te",custom:"Cu"},ie={orchestrator:"var(--accent)",coder:"var(--status-blue)",reviewer:"var(--status-purple)",tester:"var(--status-orange)",custom:"var(--text-secondary)"},de={working:"var(--status-green)",idle:"var(--text-tertiary)",stopped:"var(--status-red)"},ce=({role:t,size:a=32,statusRing:n="none"})=>e.jsx(H,{size:a,color:ie[t],fontRatio:.34,mono:!0,ringColor:n==="none"?null:de[n],ringSurface:"var(--bg-2)",testId:"role-avatar",data:{role:t,"status-ring":n},children:oe[t]}),ue=[{value:"coder"},{value:"reviewer"},{value:"tester"},{value:"custom",dashed:!0}],T=t=>`role.${t}`,C=({children:t})=>e.jsx("span",{className:"text-sm font-medium text-sec",children:t}),xe=({active:t,spec:a,onSelect:n})=>{const{t:l}=k();return e.jsxs("button",{type:"button",onClick:n,"aria-pressed":t,"data-testid":`role-card-${a.value}`,className:`selectable-card${a.dashed?" selectable-card--dashed":""} flex items-center gap-3 px-3 py-2`,children:[e.jsx(ce,{role:a.value,size:20}),e.jsx("span",{className:"flex-1 text-left text-base font-medium text-pri",children:l(T(a.value))}),t?e.jsx(L,{size:14,className:"shrink-0 text-accent","aria-hidden":!0}):null]})},me=({onRoleChange:t,workerRole:a})=>{const{t:n}=k();return e.jsxs("div",{className:"flex flex-col gap-2",children:[e.jsx(C,{children:n("addWorker.role")}),e.jsx("div",{className:"grid grid-cols-2 gap-2",children:ue.map(l=>e.jsx(xe,{active:a===l.value,spec:l,onSelect:()=>t(l.value)},l.value))})]})},pe=({customTemplates:t,disabledReason:a,onDeleteTemplate:n,onSelect:l,selectedTemplateId:o})=>{const{t:i}=k(),[p,u]=x.useState(!1),[m,d]=x.useState(""),[h,v]=x.useState(null),y=x.useRef(null),f=x.useMemo(()=>t.find(r=>r.id===o)??null,[t,o]),g=x.useMemo(()=>{const r=m.trim().toLowerCase();return r?t.filter(s=>s.name.toLowerCase().includes(r)||s.description.toLowerCase().includes(r)):t},[t,m]);return x.useEffect(()=>{if(!p)return;const r=b=>{b.key==="Escape"&&u(!1)},s=b=>{const W=y.current;W&&!W.contains(b.target)&&u(!1)};return document.addEventListener("keydown",r),document.addEventListener("pointerdown",s),()=>{document.removeEventListener("keydown",r),document.removeEventListener("pointerdown",s)}},[p]),e.jsxs("div",{className:"flex flex-col gap-2",children:[e.jsx(C,{children:i("addWorker.template")}),e.jsxs("div",{ref:y,className:"relative",children:[e.jsxs("button",{type:"button","aria-haspopup":"listbox","aria-expanded":p,"data-testid":"role-template-picker-trigger",onClick:()=>u(r=>!r),className:"flex w-full items-center justify-between gap-2 rounded border px-3 py-2 text-left text-sm transition-colors hover:bg-3",style:{borderColor:"var(--border)",background:"var(--bg-1)"},children:[e.jsx("span",{className:"min-w-0 flex-1 truncate text-pri",children:f?f.name:i("addWorker.templatePickPlaceholder")}),e.jsx(z,{size:14,className:"shrink-0 text-ter","aria-hidden":!0})]}),p?e.jsxs("div",{role:"listbox","aria-label":i("addWorker.template"),"data-testid":"role-template-picker-menu",className:"elev-2 absolute left-0 right-0 top-full z-30 mt-1 flex max-h-72 flex-col overflow-hidden rounded border",style:{background:"var(--bg-elevated)",borderColor:"var(--border-bright)"},children:[e.jsxs("div",{className:"flex items-center gap-2 border-b px-2 py-1.5",style:{borderColor:"var(--border)"},children:[e.jsx(le,{size:14,className:"text-ter","aria-hidden":!0}),e.jsx("input",{value:m,onChange:r=>d(r.currentTarget.value),placeholder:i("addWorker.templateSearchPlaceholder"),"data-testid":"role-template-search-input",className:"w-full bg-transparent text-sm text-pri outline-none placeholder:text-ter",spellCheck:!1})]}),e.jsx("div",{className:"flex-1 overflow-y-auto py-1",children:t.length===0?e.jsx("div",{"data-testid":"role-template-empty-state",className:"px-3 py-3 text-center text-sm text-ter",children:i("addWorker.templateEmpty")}):g.length===0?e.jsx("div",{className:"px-3 py-3 text-center text-sm text-ter",children:i("addWorker.templateNoMatch")}):g.map(r=>{const s=r.id===o;return e.jsxs("div",{className:"relative",children:[e.jsxs("button",{type:"button",role:"option","aria-selected":s,"data-testid":`role-template-option-${r.id}`,onClick:()=>{l(r.id),u(!1),d("")},className:"flex w-full items-center gap-2 px-3 py-1.5 pr-9 text-left text-sm text-pri hover:bg-3",style:s?{background:"var(--bg-3)"}:void 0,children:[e.jsx("span",{className:"min-w-0 flex-1 truncate",children:r.name}),s?e.jsx(L,{size:14,className:"shrink-0 text-accent","aria-hidden":!0}):null]}),e.jsx("button",{type:"button","aria-label":i("addWorker.templateDeleteAria",{name:r.name}),"data-testid":`role-template-delete-${r.id}`,disabled:!!a,title:a??void 0,onClick:b=>{b.preventDefault(),b.stopPropagation(),!a&&v(r)},className:"absolute right-1 top-1/2 flex h-6 w-6 -translate-y-1/2 items-center justify-center rounded text-ter transition-colors hover:bg-3 hover:text-pri",children:e.jsx(K,{size:14,"aria-hidden":!0})})]},r.id)})}),o!==null?e.jsx("button",{type:"button","data-testid":"role-template-clear",onClick:()=>{l(null),u(!1),d("")},className:"border-t px-3 py-1.5 text-left text-sm text-ter transition-colors hover:bg-3 hover:text-pri",style:{borderColor:"var(--border)"},children:i("addWorker.templateClear")}):null]}):null]}),e.jsx(Q,{open:h!==null,onOpenChange:r=>{r||v(null)},title:i("addWorker.templateDeleteTitle"),description:h?i("addWorker.templateDeleteConfirm",{name:h.name}):"",confirmLabel:i("addWorker.templateDeleteConfirmLabel"),confirmKind:"danger",onConfirm:()=>{if(!h||a)return;const r=h.id;v(null),n(r)}})]})},he=({canSaveAsTemplate:t,modified:a,onChange:n,onReset:l,onSaveAsTemplate:o,roleDescription:i,templateBusy:p,workerRole:u,writeDisabledReason:m})=>{const{t:d}=k(),[h,v]=x.useState(!1),[y,f]=x.useState(!1),[g,r]=x.useState("");return x.useEffect(()=>{(u==="custom"||a)&&v(!0)},[a,u]),x.useEffect(()=>{t||(f(!1),r(""))},[t]),e.jsxs("details",{open:h,onToggle:s=>v(s.currentTarget.open),className:"group flex flex-col gap-2",children:[e.jsxs("summary",{className:"flex cursor-pointer select-none items-center justify-between gap-2 list-none",children:[e.jsxs("span",{className:"flex items-center gap-1.5",children:[e.jsx(z,{size:12,"aria-hidden":!0,className:"-rotate-90 text-ter transition-transform duration-150 group-open:rotate-0"}),e.jsx(C,{children:d("addWorker.roleInstructions")}),a?e.jsxs("span",{className:"text-sm text-ter",children:["· ",d("addWorker.modifiedFrom",{role:d(T(u))})]}):null]}),e.jsxs("div",{className:"flex items-center gap-1",children:[t&&!y?e.jsxs("button",{type:"button","data-testid":"role-template-save",disabled:!!m,title:m??void 0,onClick:s=>{s.preventDefault(),s.stopPropagation(),f(!0)},className:"flex items-center gap-1 rounded px-2 py-0.5 text-xs font-medium transition-colors hover:opacity-80 disabled:cursor-not-allowed disabled:opacity-50",style:{color:"var(--accent)",background:"color-mix(in oklab, var(--accent) 14%, transparent)"},children:[e.jsx(ae,{size:12,"aria-hidden":!0}),d("addWorker.saveAsTemplate")]}):null,a?e.jsxs("button",{type:"button",className:"flex items-center gap-1 rounded px-1.5 py-0.5 text-xs text-ter transition-colors hover:bg-3 hover:text-sec",onClick:s=>{s.preventDefault(),s.stopPropagation(),l()},children:[e.jsx(V,{size:12,"aria-hidden":!0}),d("addWorker.reset")]}):null]})]}),e.jsx("textarea",{"aria-label":"Role instructions",id:"add-worker-role-instructions",value:i,rows:5,onChange:s=>n(s.currentTarget.value),placeholder:u==="custom"?d("addWorker.customPlaceholder"):void 0,title:d("addWorker.roleInstructionsTitle"),className:"input mono resize-y text-sm",style:{minHeight:150},"data-testid":"role-instructions-textarea"}),t&&y?e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("input",{autoFocus:!0,value:g,onChange:s=>r(s.currentTarget.value),placeholder:d("addWorker.templateNamePlaceholder"),"data-testid":"role-template-save-name",className:"input flex-1 text-sm"}),e.jsx("button",{type:"button",disabled:p||!g.trim()||!!m,title:m??void 0,"data-testid":"role-template-save-confirm",onClick:async()=>{if(m)return;const s=g.trim();if(s)try{await o(s),f(!1),r("")}catch{}},className:"icon-btn icon-btn--primary text-xs",children:d("addWorker.templateSaveConfirm")}),e.jsx("button",{type:"button","data-testid":"role-template-save-cancel",onClick:()=>{f(!1),r("")},className:"icon-btn text-xs",children:d("common.cancel")})]}):null]})},B=({active:t,command:a,displayName:n,notFound:l=!1,testId:o,onSelect:i})=>{const{t:p}=k();return e.jsxs("button",{type:"button",onClick:i,"aria-pressed":t,"data-testid":o,className:"selectable-card flex items-center justify-between gap-2 px-3 py-2",children:[e.jsxs("span",{className:"flex min-w-0 flex-col items-start gap-0.5",children:[e.jsx("span",{className:"truncate text-base font-medium text-pri",children:n}),e.jsxs("span",{className:"mono truncate text-xs text-ter",children:[a,l?` · ${p("addWorker.agentNotFound")}`:""]})]}),t?e.jsx(L,{size:14,className:"shrink-0 text-accent","aria-hidden":!0}):null]})},fe=({active:t,preset:a,onSelect:n})=>e.jsx(B,{active:t,command:a.command,displayName:a.displayName,notFound:a.available===!1,testId:`agent-radio-${a.id}`,onSelect:n}),ve=({commandPresetId:t,commandPresets:a,onPresetChange:n})=>e.jsx(ge,{commandPresetId:t,commandPresets:a,onPresetChange:n}),ge=({commandPresetId:t,commandPresets:a,onPresetChange:n})=>{const{t:l}=k();return e.jsxs("div",{className:"flex flex-col gap-2",children:[e.jsx(C,{children:l("addWorker.agentCli")}),a.length===0?e.jsx("div",{className:"text-sm text-ter",children:l("addWorker.loadingPresets")}):e.jsxs("div",{className:"grid grid-cols-2 gap-2",children:[a.map(o=>e.jsx(fe,{active:t===o.id,preset:o,onSelect:()=>n(o.id)},o.id)),e.jsx(B,{active:t==="",command:l("addWorker.genericCommand"),displayName:l("addWorker.genericAgent"),testId:"agent-radio-generic",onSelect:()=>n("")})]})]})},be=({onChange:t,value:a})=>{const{t:n}=k(),l=a.trim();return e.jsxs("details",{className:"group flex flex-col gap-2",children:[e.jsx("summary",{className:"flex cursor-pointer select-none items-center justify-between gap-2 list-none",children:e.jsxs("span",{className:"flex min-w-0 items-center gap-1.5",children:[e.jsx(z,{size:12,"aria-hidden":!0,className:"-rotate-90 shrink-0 text-ter transition-transform duration-150 group-open:rotate-0"}),e.jsx(C,{children:n("addWorker.startupCommand")}),l?e.jsxs("span",{className:"truncate text-sm text-ter",children:["· ",n("addWorker.startupOverrides")]}):null]})}),e.jsxs("div",{className:"flex flex-col gap-2 rounded border bg-2 p-3",style:{borderColor:"var(--border)"},children:[e.jsx("input",{"aria-label":"Startup command",value:a,onChange:o=>t(o.currentTarget.value),placeholder:"qwen --model qwen3-coder",className:"input mono text-sm",spellCheck:!1}),e.jsx("p",{className:"text-sm leading-5 text-ter",children:n("addWorker.startupHelp",{example:"claude --resume <session-id>"})})]})]})},ke=({commandPresets:t,commandPresetId:a,creating:n=!1,customTemplates:l,onClose:o,onDeleteTemplate:i,onNameChange:p,onPresetChange:u,onRandomName:m,onRoleDescriptionChange:d,onRoleDescriptionReset:h,onRoleChange:v,onSaveAsTemplate:y,onStartupCommandChange:f,onSubmit:g,onTemplateChange:r,roleDescription:s,roleDescriptionDefault:b,selectedTemplateId:W,startupCommand:P,templateBusy:$,workerName:A,workerRole:w,writeDisabledReason:N})=>{const{t:c}=k(),F=G(),_=j=>{j||o()},q=s!==b,E=t.find(j=>j.id===a),M=P.trim(),D=()=>N||(A.trim()?!a&&!M?c("addWorker.pickCliOrStartup"):E?.available===!1&&!M?c("addWorker.unavailable",{name:E.displayName}):s.trim()?null:c("addWorker.emptyInstructions"):c("addWorker.enterName")),I=j=>{const O=D();if(O){j.preventDefault(),F.show({kind:"warning",message:O});return}g(j)};return e.jsx(J,{open:!0,onOpenChange:_,children:e.jsxs(U,{children:[e.jsx(X,{"data-testid":"add-worker-overlay",className:"app-overlay fixed inset-0 z-40"}),e.jsx("div",{className:"pointer-events-none fixed inset-0 z-50 grid place-items-center p-4",children:e.jsx(Y,{"data-testid":"add-worker-content",className:"dialog-scale-pop elev-2 pointer-events-auto flex max-h-[calc(100vh-32px)] w-[560px] max-w-full flex-col rounded-lg border",style:{background:"var(--bg-elevated)",borderColor:"var(--border-bright)"},children:e.jsxs("form",{onSubmit:I,"aria-label":c("addWorker.title"),className:"flex flex-col",children:[e.jsxs("div",{className:"flex shrink-0 flex-col gap-0.5 border-b px-5 py-4",style:{borderColor:"var(--border)"},children:[e.jsx(Z,{className:"text-lg font-semibold text-pri",children:c("addWorker.title")}),e.jsx(R,{className:"text-sm text-ter",children:c("addWorker.description",{command:"team send"})})]}),e.jsxs("div",{className:"flex flex-col gap-4 overflow-y-auto px-5 py-4",children:[e.jsxs("label",{className:"flex flex-col gap-2",children:[e.jsxs("div",{className:"flex items-baseline justify-between gap-2",children:[e.jsx(C,{children:c("addWorker.name")}),e.jsx(ee,{label:c("addWorker.randomTooltip"),children:e.jsxs("button",{type:"button","aria-label":c("addWorker.randomAria"),className:"flex cursor-pointer items-center gap-1 rounded px-1.5 py-0.5 text-xs text-ter transition-colors hover:bg-3 hover:text-sec",onClick:m,"data-testid":"random-worker-name",children:[e.jsx(se,{size:12,"aria-hidden":!0}),c("addWorker.random")]})})]}),e.jsx("input",{autoFocus:!0,value:A,onChange:j=>p(j.target.value),placeholder:c("addWorker.namePlaceholder"),className:"input"})]}),e.jsx(me,{workerRole:w,onRoleChange:v}),w==="custom"?e.jsx(pe,{customTemplates:l,disabledReason:N,onDeleteTemplate:i,onSelect:r,selectedTemplateId:W}):null,e.jsx(he,{canSaveAsTemplate:w==="custom"&&!W&&s.trim().length>0,modified:q,onChange:d,onReset:h,onSaveAsTemplate:y,roleDescription:s,templateBusy:$,workerRole:w,writeDisabledReason:N}),e.jsx(ve,{commandPresetId:a,commandPresets:t,onPresetChange:u}),e.jsx(be,{value:P,onChange:f})]}),e.jsxs("div",{className:"flex shrink-0 items-center justify-end gap-2 border-t px-5 py-3",style:{borderColor:"var(--border)",background:"var(--bg-2)"},children:[e.jsx("button",{type:"button",onClick:o,className:"icon-btn","data-testid":"add-worker-cancel",children:c("addWorker.cancel")}),e.jsx("button",{type:"submit",disabled:n||!!N,title:N??void 0,className:"icon-btn icon-btn--primary","data-testid":"add-worker-submit",children:c(n?"addWorker.creating":"addWorker.create")})]})]})})})]})})};export{ke as AddWorkerDialog};
|