@tt-a1i/hive 1.4.3 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +44 -0
- package/README.en.md +5 -4
- package/README.md +9 -1
- package/assets/qq-group.jpg +0 -0
- package/dist/bin/team.cmd +1 -0
- package/dist/src/cli/hive-update.d.ts +57 -0
- package/dist/src/cli/hive-update.js +92 -15
- package/dist/src/cli/hive.d.ts +57 -0
- package/dist/src/cli/hive.js +113 -20
- package/dist/src/cli/team.d.ts +1 -0
- package/dist/src/cli/team.js +215 -7
- package/dist/src/server/agent-command-resolver.d.ts +10 -1
- package/dist/src/server/agent-command-resolver.js +32 -4
- package/dist/src/server/agent-launch-resolver.js +9 -3
- package/dist/src/server/agent-manager-support.d.ts +28 -0
- package/dist/src/server/agent-manager-support.js +138 -10
- package/dist/src/server/agent-run-bootstrap.d.ts +17 -1
- package/dist/src/server/agent-run-bootstrap.js +30 -2
- package/dist/src/server/agent-run-starter.d.ts +7 -1
- package/dist/src/server/agent-run-starter.js +9 -2
- package/dist/src/server/agent-run-store.d.ts +1 -1
- package/dist/src/server/agent-runtime-close.d.ts +1 -0
- package/dist/src/server/agent-runtime-close.js +25 -1
- package/dist/src/server/agent-runtime-contract.d.ts +2 -1
- package/dist/src/server/agent-runtime.d.ts +1 -1
- package/dist/src/server/agent-runtime.js +8 -2
- package/dist/src/server/agent-startup-instructions.d.ts +8 -1
- package/dist/src/server/agent-startup-instructions.js +15 -9
- package/dist/src/server/agent-stdin-dispatcher.d.ts +12 -5
- package/dist/src/server/agent-stdin-dispatcher.js +129 -40
- package/dist/src/server/app.d.ts +1 -0
- package/dist/src/server/app.js +12 -2
- package/dist/src/server/cron-util.d.ts +7 -0
- package/dist/src/server/cron-util.js +19 -0
- package/dist/src/server/dispatch-ledger-store.d.ts +22 -0
- package/dist/src/server/dispatch-ledger-store.js +51 -3
- package/dist/src/server/env-sync-message.js +9 -9
- package/dist/src/server/fs-browse.d.ts +14 -1
- package/dist/src/server/fs-browse.js +48 -5
- package/dist/src/server/fs-pick-folder.js +58 -11
- package/dist/src/server/fs-sandbox.js +36 -7
- package/dist/src/server/hive-team-guidance.d.ts +11 -6
- package/dist/src/server/hive-team-guidance.js +252 -70
- package/dist/src/server/live-run-registry.d.ts +1 -0
- package/dist/src/server/live-run-registry.js +1 -1
- package/dist/src/server/open-target-commands.js +29 -4
- package/dist/src/server/orchestrator-autostart.d.ts +12 -0
- package/dist/src/server/orchestrator-autostart.js +15 -13
- package/dist/src/server/path-canonicalization.d.ts +3 -0
- package/dist/src/server/path-canonicalization.js +29 -0
- package/dist/src/server/platform-path.d.ts +3 -0
- package/dist/src/server/platform-path.js +13 -0
- package/dist/src/server/post-start-input-writer.d.ts +1 -1
- package/dist/src/server/post-start-input-writer.js +116 -16
- package/dist/src/server/preset-launch-support.d.ts +1 -1
- package/dist/src/server/preset-launch-support.js +33 -2
- package/dist/src/server/recovery-summary.d.ts +6 -1
- package/dist/src/server/recovery-summary.js +17 -17
- package/dist/src/server/restart-policy-support.d.ts +6 -1
- package/dist/src/server/restart-policy-support.js +9 -1
- package/dist/src/server/restart-policy.d.ts +2 -2
- package/dist/src/server/restart-policy.js +3 -1
- package/dist/src/server/role-template-store.d.ts +1 -0
- package/dist/src/server/role-template-store.js +11 -1
- package/dist/src/server/route-types.d.ts +43 -0
- package/dist/src/server/routes-runtime.js +2 -1
- package/dist/src/server/routes-settings.js +76 -0
- package/dist/src/server/routes-team.js +221 -2
- package/dist/src/server/routes-workflow-schedules.d.ts +2 -0
- package/dist/src/server/routes-workflow-schedules.js +58 -0
- package/dist/src/server/routes-workflows.d.ts +2 -0
- package/dist/src/server/routes-workflows.js +83 -0
- package/dist/src/server/routes.js +4 -0
- package/dist/src/server/runtime-restart-policy.d.ts +3 -1
- package/dist/src/server/runtime-restart-policy.js +3 -1
- package/dist/src/server/runtime-store-contract.d.ts +122 -0
- package/dist/src/server/runtime-store-contract.js +1 -0
- package/dist/src/server/runtime-store-helpers.d.ts +9 -0
- package/dist/src/server/runtime-store-helpers.js +101 -2
- package/dist/src/server/runtime-store-workflows.d.ts +6 -0
- package/dist/src/server/runtime-store-workflows.js +100 -0
- package/dist/src/server/runtime-store.d.ts +3 -70
- package/dist/src/server/runtime-store.js +70 -4
- package/dist/src/server/session-capture-claude.d.ts +23 -0
- package/dist/src/server/session-capture-claude.js +24 -1
- package/dist/src/server/session-capture-codex.d.ts +3 -3
- package/dist/src/server/session-capture-codex.js +9 -7
- package/dist/src/server/session-capture-gemini.d.ts +1 -1
- package/dist/src/server/session-capture-gemini.js +6 -3
- package/dist/src/server/session-capture-opencode.d.ts +18 -0
- package/dist/src/server/session-capture-opencode.js +27 -2
- package/dist/src/server/settings-store.d.ts +3 -0
- package/dist/src/server/settings-store.js +1 -0
- package/dist/src/server/sqlite-schema-v19.d.ts +2 -0
- package/dist/src/server/sqlite-schema-v19.js +17 -0
- package/dist/src/server/sqlite-schema-v20.d.ts +2 -0
- package/dist/src/server/sqlite-schema-v20.js +20 -0
- package/dist/src/server/sqlite-schema-v21.d.ts +2 -0
- package/dist/src/server/sqlite-schema-v21.js +20 -0
- package/dist/src/server/sqlite-schema.d.ts +1 -1
- package/dist/src/server/sqlite-schema.js +97 -1
- package/dist/src/server/startup-command-parser.d.ts +15 -0
- package/dist/src/server/startup-command-parser.js +33 -2
- package/dist/src/server/system-message.d.ts +7 -0
- package/dist/src/server/system-message.js +8 -1
- package/dist/src/server/tasks-file-watcher.d.ts +39 -1
- package/dist/src/server/tasks-file-watcher.js +155 -25
- package/dist/src/server/tasks-file.d.ts +2 -1
- package/dist/src/server/tasks-file.js +32 -9
- package/dist/src/server/tasks-websocket-server.js +13 -14
- package/dist/src/server/team-authz.d.ts +1 -1
- package/dist/src/server/team-authz.js +9 -1
- package/dist/src/server/team-autostaff.d.ts +16 -0
- package/dist/src/server/team-autostaff.js +16 -0
- package/dist/src/server/team-list-serializer.d.ts +1 -1
- package/dist/src/server/team-list-serializer.js +3 -1
- package/dist/src/server/team-operations.d.ts +20 -2
- package/dist/src/server/team-operations.js +160 -14
- package/dist/src/server/terminal-input-profile.js +2 -8
- package/dist/src/server/terminal-protocol.js +9 -3
- package/dist/src/server/terminal-stream-hub.js +16 -10
- package/dist/src/server/terminal-ws-server.js +36 -16
- package/dist/src/server/websocket-upgrade-safety.d.ts +10 -0
- package/dist/src/server/websocket-upgrade-safety.js +35 -0
- package/dist/src/server/windows-command-line.d.ts +3 -0
- package/dist/src/server/windows-command-line.js +9 -0
- package/dist/src/server/windows-filename.d.ts +2 -0
- package/dist/src/server/windows-filename.js +33 -0
- package/dist/src/server/workflow-cli-policy.d.ts +60 -0
- package/dist/src/server/workflow-cli-policy.js +110 -0
- package/dist/src/server/workflow-dispatch-awaiter.d.ts +12 -0
- package/dist/src/server/workflow-dispatch-awaiter.js +80 -0
- package/dist/src/server/workflow-feature.d.ts +15 -0
- package/dist/src/server/workflow-feature.js +15 -0
- package/dist/src/server/workflow-http-serializers.d.ts +64 -0
- package/dist/src/server/workflow-http-serializers.js +58 -0
- package/dist/src/server/workflow-run-log-store.d.ts +19 -0
- package/dist/src/server/workflow-run-log-store.js +45 -0
- package/dist/src/server/workflow-run-store.d.ts +50 -0
- package/dist/src/server/workflow-run-store.js +103 -0
- package/dist/src/server/workflow-runner.d.ts +147 -0
- package/dist/src/server/workflow-runner.js +401 -0
- package/dist/src/server/workflow-schedule-create.d.ts +14 -0
- package/dist/src/server/workflow-schedule-create.js +41 -0
- package/dist/src/server/workflow-schedule-store.d.ts +43 -0
- package/dist/src/server/workflow-schedule-store.js +112 -0
- package/dist/src/server/workflow-scheduler.d.ts +36 -0
- package/dist/src/server/workflow-scheduler.js +97 -0
- package/dist/src/server/workflow-script-loader.d.ts +34 -0
- package/dist/src/server/workflow-script-loader.js +106 -0
- package/dist/src/server/workspace-path-validation.js +16 -4
- package/dist/src/server/workspace-shell-runtime.d.ts +5 -0
- package/dist/src/server/workspace-shell-runtime.js +24 -2
- package/dist/src/server/workspace-store-contract.d.ts +4 -1
- package/dist/src/server/workspace-store-hydration.js +23 -7
- package/dist/src/server/workspace-store-mutations.js +2 -5
- package/dist/src/server/workspace-store-support.d.ts +4 -0
- package/dist/src/server/workspace-store-support.js +13 -1
- package/dist/src/server/workspace-store.js +38 -4
- package/dist/src/shared/types.d.ts +16 -1
- package/package.json +4 -2
- package/web/dist/assets/{AddWorkerDialog-DmkDOdp6.js → AddWorkerDialog-CcC-7kgG.js} +2 -2
- package/web/dist/assets/AddWorkspaceDialog-BDpOTfmt.js +1 -0
- package/web/dist/assets/{FirstRunWizard-SAd1wsH4.js → FirstRunWizard-BYX_ocQn.js} +1 -1
- package/web/dist/assets/{MarketplaceDrawer-B_8aG2uT.js → MarketplaceDrawer-DUxSk7db.js} +1 -1
- package/web/dist/assets/WhatsNewDialog-B_RlCXcV.js +1 -0
- package/web/dist/assets/WorkerModal-D9-7YfZZ.js +1 -0
- package/web/dist/assets/WorkspaceTaskDrawer-BCKoF7qc.js +1 -0
- package/web/dist/assets/{WorkspaceTerminalPanels-BReWh1YL.js → WorkspaceTerminalPanels-Dq8y91t2.js} +1 -1
- package/web/dist/assets/index-BiOvKIVw.css +1 -0
- package/web/dist/assets/index-DMRUklT3.js +73 -0
- package/web/dist/assets/path-join-7MR1s7b1.js +1 -0
- package/web/dist/index.html +2 -2
- package/web/dist/sw.js +1 -1
- package/web/dist/assets/AddWorkspaceDialog-BsVnH3Xe.js +0 -1
- package/web/dist/assets/WorkerModal-CQmjiPme.js +0 -1
- package/web/dist/assets/WorkspaceTaskDrawer-B0DmCWcV.js +0 -1
- package/web/dist/assets/chevron-right-CtLjVEl7.js +0 -1
- package/web/dist/assets/index-BEsTmfrO.css +0 -1
- package/web/dist/assets/index-Cn8X3get.js +0 -76
|
@@ -9,18 +9,39 @@ export const PROTOCOL_RELATIVE_PATH = `${HIVE_DIR_NAME}/${PROTOCOL_FILE_NAME}`;
|
|
|
9
9
|
export const getTasksFilePath = (workspacePath) => join(workspacePath, HIVE_DIR_NAME, TASKS_FILE_NAME);
|
|
10
10
|
export const getProtocolFilePath = (workspacePath) => join(workspacePath, HIVE_DIR_NAME, PROTOCOL_FILE_NAME);
|
|
11
11
|
const getLegacyTasksFilePath = (workspacePath) => join(workspacePath, TASKS_FILE_NAME);
|
|
12
|
+
const RETRYABLE_TASKS_FS_ERROR_CODES = new Set(['EACCES', 'EBUSY', 'EPERM']);
|
|
13
|
+
const TASKS_FS_RETRY_DELAYS_MS = [20, 50, 100];
|
|
14
|
+
const sleepSync = (ms) => {
|
|
15
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
16
|
+
};
|
|
17
|
+
const runRetryableTasksFileOperation = (operation) => {
|
|
18
|
+
for (let attempt = 0;; attempt += 1) {
|
|
19
|
+
try {
|
|
20
|
+
return operation();
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
const code = error?.code;
|
|
24
|
+
const delay = TASKS_FS_RETRY_DELAYS_MS[attempt];
|
|
25
|
+
if (!code || !RETRYABLE_TASKS_FS_ERROR_CODES.has(code) || delay === undefined)
|
|
26
|
+
throw error;
|
|
27
|
+
sleepSync(delay);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
};
|
|
12
31
|
const ensureTasksDir = (workspacePath) => {
|
|
13
|
-
mkdirSync(dirname(getTasksFilePath(workspacePath)), { recursive: true });
|
|
32
|
+
runRetryableTasksFileOperation(() => mkdirSync(dirname(getTasksFilePath(workspacePath)), { recursive: true }));
|
|
14
33
|
};
|
|
15
34
|
export const ensureTasksFile = (workspacePath) => {
|
|
16
35
|
ensureTasksDir(workspacePath);
|
|
17
36
|
const tasksFilePath = getTasksFilePath(workspacePath);
|
|
18
37
|
if (existsSync(tasksFilePath)) {
|
|
19
|
-
return readFileSync(tasksFilePath, 'utf8');
|
|
38
|
+
return runRetryableTasksFileOperation(() => readFileSync(tasksFilePath, 'utf8'));
|
|
20
39
|
}
|
|
21
40
|
const legacyTasksFilePath = getLegacyTasksFilePath(workspacePath);
|
|
22
|
-
const content = existsSync(legacyTasksFilePath)
|
|
23
|
-
|
|
41
|
+
const content = existsSync(legacyTasksFilePath)
|
|
42
|
+
? runRetryableTasksFileOperation(() => readFileSync(legacyTasksFilePath, 'utf8'))
|
|
43
|
+
: '';
|
|
44
|
+
runRetryableTasksFileOperation(() => writeFileSync(tasksFilePath, content, 'utf8'));
|
|
24
45
|
return content;
|
|
25
46
|
};
|
|
26
47
|
/**
|
|
@@ -29,14 +50,16 @@ export const ensureTasksFile = (workspacePath) => {
|
|
|
29
50
|
* on every workspace open means a Hive version bump that changes the rules
|
|
30
51
|
* propagates without manual intervention.
|
|
31
52
|
*/
|
|
32
|
-
export const ensureProtocolFile = (workspacePath) => {
|
|
53
|
+
export const ensureProtocolFile = (workspacePath, cliPolicy, workflowsEnabled = false, autostaffEnabled = false) => {
|
|
33
54
|
ensureTasksDir(workspacePath);
|
|
34
55
|
const protocolFilePath = getProtocolFilePath(workspacePath);
|
|
35
|
-
const desired = buildProtocolDoc();
|
|
36
|
-
const current = existsSync(protocolFilePath)
|
|
56
|
+
const desired = buildProtocolDoc(cliPolicy, workflowsEnabled, autostaffEnabled);
|
|
57
|
+
const current = existsSync(protocolFilePath)
|
|
58
|
+
? runRetryableTasksFileOperation(() => readFileSync(protocolFilePath, 'utf8'))
|
|
59
|
+
: null;
|
|
37
60
|
if (current === desired)
|
|
38
61
|
return desired;
|
|
39
|
-
writeFileSync(protocolFilePath, desired, 'utf8');
|
|
62
|
+
runRetryableTasksFileOperation(() => writeFileSync(protocolFilePath, desired, 'utf8'));
|
|
40
63
|
return desired;
|
|
41
64
|
};
|
|
42
65
|
export const createTasksFileService = () => {
|
|
@@ -46,7 +69,7 @@ export const createTasksFileService = () => {
|
|
|
46
69
|
},
|
|
47
70
|
writeTasks(workspacePath, content) {
|
|
48
71
|
ensureTasksDir(workspacePath);
|
|
49
|
-
writeFileSync(getTasksFilePath(workspacePath), content, 'utf8');
|
|
72
|
+
runRetryableTasksFileOperation(() => writeFileSync(getTasksFilePath(workspacePath), content, 'utf8'));
|
|
50
73
|
},
|
|
51
74
|
};
|
|
52
75
|
};
|
|
@@ -1,17 +1,15 @@
|
|
|
1
1
|
import { WebSocketServer } from 'ws';
|
|
2
2
|
import { getLocalRequestRejection } from './local-request-guard.js';
|
|
3
3
|
import { readCookie } from './ui-auth-helpers.js';
|
|
4
|
+
import { attachRawSocketErrorHandler, attachWebSocketErrorHandler, attachWebSocketServerErrorHandler, rejectWebSocketUpgrade, sendWebSocketMessage, } from './websocket-upgrade-safety.js';
|
|
4
5
|
const matchTasksPath = (pathname) => {
|
|
5
6
|
const match = /^\/ws\/tasks\/(?<workspaceId>[^/]+)$/.exec(pathname);
|
|
6
7
|
const workspaceId = match?.groups?.workspaceId;
|
|
7
8
|
return workspaceId ? decodeURIComponent(workspaceId) : null;
|
|
8
9
|
};
|
|
9
|
-
const rejectUpgrade = (socket, status) => {
|
|
10
|
-
socket.write(`HTTP/1.1 ${status}\r\n\r\n`);
|
|
11
|
-
socket.destroy();
|
|
12
|
-
};
|
|
13
10
|
export const createTasksWebSocketServer = (server, store, tasksFileService) => {
|
|
14
11
|
const wss = new WebSocketServer({ noServer: true });
|
|
12
|
+
attachWebSocketServerErrorHandler(wss, 'tasks');
|
|
15
13
|
const socketsByWorkspaceId = new Map();
|
|
16
14
|
const validateUpgradeSession = (request) => {
|
|
17
15
|
const cookieHeader = Array.isArray(request.headers.cookie)
|
|
@@ -25,12 +23,13 @@ export const createTasksWebSocketServer = (server, store, tasksFileService) => {
|
|
|
25
23
|
const workspaceId = matchTasksPath(url.pathname);
|
|
26
24
|
if (!workspaceId)
|
|
27
25
|
return;
|
|
26
|
+
const detachRawSocketErrorHandler = attachRawSocketErrorHandler(socket, 'tasks upgrade');
|
|
28
27
|
if (getLocalRequestRejection(request)) {
|
|
29
|
-
|
|
28
|
+
rejectWebSocketUpgrade(socket, '403 Forbidden');
|
|
30
29
|
return;
|
|
31
30
|
}
|
|
32
31
|
if (!validateUpgradeSession(request)) {
|
|
33
|
-
|
|
32
|
+
rejectWebSocketUpgrade(socket, '401 Unauthorized');
|
|
34
33
|
return;
|
|
35
34
|
}
|
|
36
35
|
let workspacePath = '';
|
|
@@ -38,10 +37,12 @@ export const createTasksWebSocketServer = (server, store, tasksFileService) => {
|
|
|
38
37
|
workspacePath = store.getWorkspaceSnapshot(workspaceId).summary.path;
|
|
39
38
|
}
|
|
40
39
|
catch {
|
|
41
|
-
|
|
40
|
+
rejectWebSocketUpgrade(socket, '404 Not Found');
|
|
42
41
|
return;
|
|
43
42
|
}
|
|
44
43
|
wss.handleUpgrade(request, socket, head, (ws) => {
|
|
44
|
+
detachRawSocketErrorHandler();
|
|
45
|
+
attachWebSocketErrorHandler(ws, `tasks ${workspaceId}`);
|
|
45
46
|
const sockets = socketsByWorkspaceId.get(workspaceId) ?? new Set();
|
|
46
47
|
sockets.add(ws);
|
|
47
48
|
socketsByWorkspaceId.set(workspaceId, sockets);
|
|
@@ -55,14 +56,14 @@ export const createTasksWebSocketServer = (server, store, tasksFileService) => {
|
|
|
55
56
|
if (ws.readyState !== ws.OPEN)
|
|
56
57
|
return;
|
|
57
58
|
try {
|
|
58
|
-
ws
|
|
59
|
+
sendWebSocketMessage(ws, JSON.stringify({
|
|
59
60
|
type: 'tasks-snapshot',
|
|
60
61
|
content: tasksFileService.readTasks(workspacePath),
|
|
61
|
-
}));
|
|
62
|
+
}), `tasks ${workspaceId} snapshot`);
|
|
62
63
|
}
|
|
63
64
|
catch {
|
|
64
65
|
if (ws.readyState === ws.OPEN) {
|
|
65
|
-
ws
|
|
66
|
+
sendWebSocketMessage(ws, JSON.stringify({ type: 'tasks-error', error: 'Failed to read tasks file' }), `tasks ${workspaceId} snapshot error`);
|
|
66
67
|
}
|
|
67
68
|
}
|
|
68
69
|
});
|
|
@@ -72,7 +73,7 @@ export const createTasksWebSocketServer = (server, store, tasksFileService) => {
|
|
|
72
73
|
close: () => {
|
|
73
74
|
for (const sockets of socketsByWorkspaceId.values()) {
|
|
74
75
|
for (const socket of sockets)
|
|
75
|
-
socket.
|
|
76
|
+
socket.terminate();
|
|
76
77
|
}
|
|
77
78
|
socketsByWorkspaceId.clear();
|
|
78
79
|
wss.close();
|
|
@@ -83,9 +84,7 @@ export const createTasksWebSocketServer = (server, store, tasksFileService) => {
|
|
|
83
84
|
return;
|
|
84
85
|
const payload = JSON.stringify({ type: 'tasks-updated', content });
|
|
85
86
|
for (const socket of sockets) {
|
|
86
|
-
|
|
87
|
-
socket.send(payload);
|
|
88
|
-
}
|
|
87
|
+
sendWebSocketMessage(socket, payload, `tasks ${workspaceId} publish`);
|
|
89
88
|
}
|
|
90
89
|
},
|
|
91
90
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AgentSummary } from '../shared/types.js';
|
|
2
|
-
export type TeamCommand = 'send' | 'list' | 'report' | 'status' | 'cancel' | 'help';
|
|
2
|
+
export type TeamCommand = 'send' | 'list' | 'report' | 'status' | 'cancel' | 'help' | 'spawn' | 'dismiss' | 'workflow';
|
|
3
3
|
export declare const commandAllowedForRole: (role: AgentSummary["role"], command: TeamCommand) => boolean;
|
|
4
4
|
interface AuthenticateInput {
|
|
5
5
|
fromAgentId: string | undefined;
|
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import { ForbiddenError, UnauthorizedError } from './http-errors.js';
|
|
2
|
-
const ORCHESTRATOR_COMMANDS = new Set([
|
|
2
|
+
const ORCHESTRATOR_COMMANDS = new Set([
|
|
3
|
+
'send',
|
|
4
|
+
'list',
|
|
5
|
+
'cancel',
|
|
6
|
+
'help',
|
|
7
|
+
'spawn',
|
|
8
|
+
'dismiss',
|
|
9
|
+
'workflow',
|
|
10
|
+
]);
|
|
3
11
|
const WORKER_COMMANDS = new Set(['report', 'status', 'help']);
|
|
4
12
|
const WORKER_ROLES = new Set(['coder', 'reviewer', 'tester', 'custom']);
|
|
5
13
|
export const commandAllowedForRole = (role, command) => {
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-staff experimental feature gate.
|
|
3
|
+
*
|
|
4
|
+
* The orchestrator can already create workers (`team spawn`); auto-staff is a
|
|
5
|
+
* guidance layer that grants + encourages it to size the team to the task up
|
|
6
|
+
* front (e.g. 2 coders + 1 reviewer + 1 tester in one go) rather than adding
|
|
7
|
+
* workers one at a time. It's purely additive orchestrator guidance — no new
|
|
8
|
+
* execution path — so unlike workflows it ships ON by default; a user opts
|
|
9
|
+
* OUT from Settings.
|
|
10
|
+
*
|
|
11
|
+
* Stored GLOBALLY in `app_state` under AUTOSTAFF_ENABLED_KEY. Only the exact
|
|
12
|
+
* string "false" disables it; absent / anything else reads back as enabled.
|
|
13
|
+
*/
|
|
14
|
+
export declare const AUTOSTAFF_ENABLED_KEY = "team.autostaff";
|
|
15
|
+
export declare const readAutostaffEnabled: (raw: string | null | undefined) => boolean;
|
|
16
|
+
export declare const serializeAutostaffEnabled: (enabled: boolean) => string;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-staff experimental feature gate.
|
|
3
|
+
*
|
|
4
|
+
* The orchestrator can already create workers (`team spawn`); auto-staff is a
|
|
5
|
+
* guidance layer that grants + encourages it to size the team to the task up
|
|
6
|
+
* front (e.g. 2 coders + 1 reviewer + 1 tester in one go) rather than adding
|
|
7
|
+
* workers one at a time. It's purely additive orchestrator guidance — no new
|
|
8
|
+
* execution path — so unlike workflows it ships ON by default; a user opts
|
|
9
|
+
* OUT from Settings.
|
|
10
|
+
*
|
|
11
|
+
* Stored GLOBALLY in `app_state` under AUTOSTAFF_ENABLED_KEY. Only the exact
|
|
12
|
+
* string "false" disables it; absent / anything else reads back as enabled.
|
|
13
|
+
*/
|
|
14
|
+
export const AUTOSTAFF_ENABLED_KEY = 'team.autostaff';
|
|
15
|
+
export const readAutostaffEnabled = (raw) => raw !== 'false';
|
|
16
|
+
export const serializeAutostaffEnabled = (enabled) => (enabled ? 'true' : 'false');
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import type { TeamListItem, TeamListItemPayload } from '../shared/types.js';
|
|
2
|
-
export declare const serializeTeamListItem: ({ commandPresetId, id, lastPtyLine, name, pendingTaskCount, role, status, }: TeamListItem) => TeamListItemPayload;
|
|
2
|
+
export declare const serializeTeamListItem: ({ commandPresetId, ephemeral, id, lastPtyLine, name, pendingTaskCount, role, spawnedBy, status, }: TeamListItem) => TeamListItemPayload;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export const serializeTeamListItem = ({ commandPresetId, id, lastPtyLine, name, pendingTaskCount, role, status, }) => ({
|
|
1
|
+
export const serializeTeamListItem = ({ commandPresetId, ephemeral, id, lastPtyLine, name, pendingTaskCount, role, spawnedBy, status, }) => ({
|
|
2
2
|
id,
|
|
3
3
|
name,
|
|
4
4
|
role,
|
|
@@ -6,4 +6,6 @@ export const serializeTeamListItem = ({ commandPresetId, id, lastPtyLine, name,
|
|
|
6
6
|
pending_task_count: pendingTaskCount,
|
|
7
7
|
last_pty_line: lastPtyLine ?? null,
|
|
8
8
|
command_preset_id: commandPresetId ?? null,
|
|
9
|
+
...(ephemeral === true ? { ephemeral: true } : {}),
|
|
10
|
+
...(spawnedBy ? { spawned_by: spawnedBy } : {}),
|
|
9
11
|
});
|
|
@@ -1,13 +1,20 @@
|
|
|
1
|
+
import type { TeamListItem } from '../shared/types.js';
|
|
1
2
|
import type { AgentRuntime } from './agent-runtime.js';
|
|
2
3
|
import type { DispatchRecord } from './dispatch-ledger-store.js';
|
|
3
4
|
import type { MessageLogHandle, MessageLogRecord } from './message-log-store.js';
|
|
5
|
+
import type { WorkflowDispatchAwaiter } from './workflow-dispatch-awaiter.js';
|
|
4
6
|
import type { WorkspaceStore } from './workspace-store.js';
|
|
7
|
+
export declare const formatUnknownWorkerError: (workerName: string, roster: readonly TeamListItem[]) => string;
|
|
5
8
|
export interface TeamOperationsInput {
|
|
6
9
|
agentRuntime: AgentRuntime;
|
|
7
10
|
createDispatch: (input: {
|
|
8
11
|
fromAgentId?: string;
|
|
12
|
+
label?: string;
|
|
13
|
+
phase?: string;
|
|
14
|
+
stepIndex?: number;
|
|
9
15
|
text: string;
|
|
10
16
|
toAgentId: string;
|
|
17
|
+
workflowRunId?: string;
|
|
11
18
|
workspaceId: string;
|
|
12
19
|
}) => DispatchRecord;
|
|
13
20
|
deleteDispatch: (dispatchId: string) => void;
|
|
@@ -28,11 +35,20 @@ export interface TeamOperationsInput {
|
|
|
28
35
|
workspaceId: string;
|
|
29
36
|
}) => DispatchRecord | undefined;
|
|
30
37
|
markDispatchSubmitted: (dispatchId: string) => void;
|
|
38
|
+
workflowDispatchAwaiter: WorkflowDispatchAwaiter;
|
|
31
39
|
workspaceStore: WorkspaceStore;
|
|
40
|
+
/** Auto-dismiss an ephemeral orchestrator-spawned worker after its
|
|
41
|
+
* dispatch report. Wired in runtime-store-helpers to remove the worker
|
|
42
|
+
* completely (stop run, drop launch config, drop the row). M11. */
|
|
43
|
+
dismissEphemeralWorker?: (workspaceId: string, workerId: string) => void;
|
|
32
44
|
}
|
|
33
45
|
export interface DispatchTaskInput {
|
|
34
46
|
fromAgentId?: string;
|
|
35
47
|
hivePort?: string;
|
|
48
|
+
workflowRunId?: string;
|
|
49
|
+
stepIndex?: number;
|
|
50
|
+
phase?: string;
|
|
51
|
+
label?: string;
|
|
36
52
|
}
|
|
37
53
|
export interface ReportTaskInput {
|
|
38
54
|
artifacts?: string[];
|
|
@@ -55,14 +71,16 @@ export interface ReportTaskResult {
|
|
|
55
71
|
forwardError: string | null;
|
|
56
72
|
forwarded: boolean;
|
|
57
73
|
}
|
|
58
|
-
export declare const createTeamOperations: ({ agentRuntime, createDispatch, deleteDispatch, deleteMessage, findOpenDispatch, findOpenDispatchById, insertMessage, markDispatchCancelled, markDispatchReportedByWorker, markDispatchSubmitted, workspaceStore, }: TeamOperationsInput) => {
|
|
74
|
+
export declare const createTeamOperations: ({ agentRuntime, createDispatch, deleteDispatch, deleteMessage, findOpenDispatch, findOpenDispatchById, insertMessage, markDispatchCancelled, markDispatchReportedByWorker, markDispatchSubmitted, workflowDispatchAwaiter, workspaceStore, dismissEphemeralWorker, }: TeamOperationsInput) => {
|
|
59
75
|
cancelTask(workspaceId: string, dispatchId: string, input: CancelTaskInput): {
|
|
60
76
|
dispatch: DispatchRecord;
|
|
61
77
|
forwardError: string | null;
|
|
62
78
|
forwarded: boolean;
|
|
63
79
|
};
|
|
64
80
|
dispatchTask: (workspaceId: string, workerId: string, text: string, input?: DispatchTaskInput) => Promise<DispatchRecord>;
|
|
65
|
-
dispatchTaskByWorkerName(workspaceId: string, workerName: string, text: string, input?: DispatchTaskInput): Promise<DispatchRecord
|
|
81
|
+
dispatchTaskByWorkerName(workspaceId: string, workerName: string, text: string, input?: DispatchTaskInput): Promise<DispatchRecord & {
|
|
82
|
+
restartedWorker: boolean;
|
|
83
|
+
}>;
|
|
66
84
|
recordUserInput(workspaceId: string, orchestratorId: string, text: string): void;
|
|
67
85
|
statusTask(workspaceId: string, workerId: string, input?: StatusTaskInput): {
|
|
68
86
|
dispatch: null;
|
|
@@ -1,7 +1,30 @@
|
|
|
1
1
|
import { ConflictError, PtyInactiveError } from './http-errors.js';
|
|
2
2
|
import { createReportMessage, createSendMessage, createStatusMessage, createUserInputMessage, } from './runtime-message-builders.js';
|
|
3
|
+
import { getWorkflowAgentId } from './workspace-store-support.js';
|
|
4
|
+
/* Roster snapshot embedded in the 409 the orchestrator sees when it
|
|
5
|
+
dispatches to a missing name. Format prioritizes the orchestrator's
|
|
6
|
+
parsing path: bullet-per-member with role + live status + pending
|
|
7
|
+
count, plus an explicit retry hint that names both fix-up paths
|
|
8
|
+
(direct send to a listed name, or re-`team list` if it doubts the
|
|
9
|
+
snapshot). Keeping role/status/pending inline saves a follow-up
|
|
10
|
+
`team list` round-trip — the orchestrator can re-pick in the same
|
|
11
|
+
turn. */
|
|
12
|
+
export const formatUnknownWorkerError = (workerName, roster) => {
|
|
13
|
+
if (roster.length === 0) {
|
|
14
|
+
return [
|
|
15
|
+
`Unknown worker "${workerName}": this workspace currently has no workers.`,
|
|
16
|
+
'Ask the user to add a worker in the Hive UI (Add Worker), then run `team list` and retry.',
|
|
17
|
+
].join('\n');
|
|
18
|
+
}
|
|
19
|
+
const lines = roster.map((entry) => ` - ${entry.name} (${entry.role}, ${entry.status}, ${entry.pendingTaskCount} pending)`);
|
|
20
|
+
return [
|
|
21
|
+
`Unknown worker "${workerName}" in this workspace. Current members:`,
|
|
22
|
+
...lines,
|
|
23
|
+
'Retry with `team send "<one of the names above>" "<task>"`, or run `team list` to refresh the roster.',
|
|
24
|
+
].join('\n');
|
|
25
|
+
};
|
|
3
26
|
const reportForwardErrorMessage = (error) => error instanceof Error ? error.message : String(error);
|
|
4
|
-
export const createTeamOperations = ({ agentRuntime, createDispatch, deleteDispatch, deleteMessage, findOpenDispatch, findOpenDispatchById, insertMessage, markDispatchCancelled, markDispatchReportedByWorker, markDispatchSubmitted, workspaceStore, }) => {
|
|
27
|
+
export const createTeamOperations = ({ agentRuntime, createDispatch, deleteDispatch, deleteMessage, findOpenDispatch, findOpenDispatchById, insertMessage, markDispatchCancelled, markDispatchReportedByWorker, markDispatchSubmitted, workflowDispatchAwaiter, workspaceStore, dismissEphemeralWorker, }) => {
|
|
5
28
|
const ensureWorkerRun = async (workspaceId, workerId, hivePort) => {
|
|
6
29
|
if (agentRuntime.getActiveRunByAgentId(workspaceId, workerId)) {
|
|
7
30
|
return;
|
|
@@ -23,10 +46,25 @@ export const createTeamOperations = ({ agentRuntime, createDispatch, deleteDispa
|
|
|
23
46
|
throw error;
|
|
24
47
|
}
|
|
25
48
|
};
|
|
49
|
+
const cancelUndeliveredDispatch = (workspaceId, workerId, dispatchId, reason, workflowRunId) => {
|
|
50
|
+
const cancelled = markDispatchCancelled({ dispatchId, reason, workspaceId });
|
|
51
|
+
if (!cancelled)
|
|
52
|
+
return;
|
|
53
|
+
try {
|
|
54
|
+
workspaceStore.markTaskCancelled(workspaceId, workerId);
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
console.error('[hive] swallowed:teamDispatch.markTaskCancelled', error);
|
|
58
|
+
}
|
|
59
|
+
if (workflowRunId !== undefined) {
|
|
60
|
+
workflowDispatchAwaiter.notifyCancel(dispatchId, reason);
|
|
61
|
+
}
|
|
62
|
+
};
|
|
26
63
|
const dispatchTask = async (workspaceId, workerId, text, input = {}) => {
|
|
27
64
|
const message = createSendMessage(workspaceId, workerId, text, input.fromAgentId);
|
|
28
65
|
const messageHandle = insertMessage(message);
|
|
29
66
|
let dispatch;
|
|
67
|
+
let pendingMarked = false;
|
|
30
68
|
try {
|
|
31
69
|
const dispatchInput = {
|
|
32
70
|
text,
|
|
@@ -35,18 +73,69 @@ export const createTeamOperations = ({ agentRuntime, createDispatch, deleteDispa
|
|
|
35
73
|
};
|
|
36
74
|
if (input.fromAgentId)
|
|
37
75
|
dispatchInput.fromAgentId = input.fromAgentId;
|
|
76
|
+
if (input.workflowRunId !== undefined)
|
|
77
|
+
dispatchInput.workflowRunId = input.workflowRunId;
|
|
78
|
+
if (input.stepIndex !== undefined)
|
|
79
|
+
dispatchInput.stepIndex = input.stepIndex;
|
|
80
|
+
if (input.phase !== undefined)
|
|
81
|
+
dispatchInput.phase = input.phase;
|
|
82
|
+
if (input.label !== undefined)
|
|
83
|
+
dispatchInput.label = input.label;
|
|
38
84
|
dispatch = createDispatch(dispatchInput);
|
|
85
|
+
const dispatchId = dispatch.id;
|
|
39
86
|
if (input.fromAgentId) {
|
|
40
87
|
const sender = workspaceStore.getAgent(workspaceId, input.fromAgentId);
|
|
41
88
|
await ensureWorkerRun(workspaceId, workerId, input.hivePort ?? '');
|
|
42
89
|
const worker = workspaceStore.getWorker(workspaceId, workerId);
|
|
43
|
-
|
|
44
|
-
|
|
90
|
+
const isWorkflowDispatch = input.workflowRunId !== undefined || input.fromAgentId === getWorkflowAgentId(workspaceId);
|
|
91
|
+
markDispatchSubmitted(dispatchId);
|
|
92
|
+
workspaceStore.markTaskDispatched(workspaceId, workerId);
|
|
93
|
+
pendingMarked = true;
|
|
94
|
+
try {
|
|
95
|
+
const writePrompt = agentRuntime.writeSendPrompt(workspaceId, workerId, dispatchId, sender.name, worker.description, text);
|
|
96
|
+
void writePrompt.catch((error) => {
|
|
97
|
+
// `team send` is intentionally asynchronous (§3.3). A worker that
|
|
98
|
+
// exits during paste-submit did not receive actionable work, so
|
|
99
|
+
// close the open dispatch instead of leaving a fake pending task.
|
|
100
|
+
try {
|
|
101
|
+
cancelUndeliveredDispatch(workspaceId, workerId, dispatchId, reportForwardErrorMessage(error), input.workflowRunId);
|
|
102
|
+
}
|
|
103
|
+
catch (cancelError) {
|
|
104
|
+
if (!isWorkflowDispatch)
|
|
105
|
+
console.error('[hive] swallowed:teamDispatch.cancelUndelivered', cancelError);
|
|
106
|
+
}
|
|
107
|
+
if (!isWorkflowDispatch)
|
|
108
|
+
console.error('[hive] swallowed:teamDispatch.writePrompt', error);
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
try {
|
|
113
|
+
cancelUndeliveredDispatch(workspaceId, workerId, dispatchId, reportForwardErrorMessage(error), input.workflowRunId);
|
|
114
|
+
}
|
|
115
|
+
catch (cancelError) {
|
|
116
|
+
if (!isWorkflowDispatch)
|
|
117
|
+
console.error('[hive] swallowed:teamDispatch.cancelUndelivered', cancelError);
|
|
118
|
+
}
|
|
119
|
+
if (!isWorkflowDispatch)
|
|
120
|
+
console.error('[hive] swallowed:teamDispatch.writePrompt', error);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
workspaceStore.markTaskDispatched(workspaceId, workerId);
|
|
125
|
+
pendingMarked = true;
|
|
45
126
|
}
|
|
46
|
-
workspaceStore.markTaskDispatched(workspaceId, workerId);
|
|
47
127
|
return dispatch;
|
|
48
128
|
}
|
|
49
129
|
catch (error) {
|
|
130
|
+
if (pendingMarked) {
|
|
131
|
+
try {
|
|
132
|
+
workspaceStore.markTaskCancelled(workspaceId, workerId);
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
// Best-effort compensation for the in-memory pending count; the
|
|
136
|
+
// durable send message is deleted below.
|
|
137
|
+
}
|
|
138
|
+
}
|
|
50
139
|
if (dispatch)
|
|
51
140
|
deleteDispatch(dispatch.id);
|
|
52
141
|
deleteMessage(messageHandle);
|
|
@@ -82,9 +171,28 @@ export const createTeamOperations = ({ agentRuntime, createDispatch, deleteDispa
|
|
|
82
171
|
return { dispatch, forwardError, forwarded };
|
|
83
172
|
},
|
|
84
173
|
dispatchTask,
|
|
85
|
-
dispatchTaskByWorkerName(workspaceId, workerName, text, input = {}) {
|
|
86
|
-
|
|
87
|
-
|
|
174
|
+
async dispatchTaskByWorkerName(workspaceId, workerName, text, input = {}) {
|
|
175
|
+
/* Build the roster once so a missing-name path can surface it without
|
|
176
|
+
a second store call. We deliberately don't go through
|
|
177
|
+
`getWorkerByName` because its underlying record helper throws a
|
|
178
|
+
bare Error — that bubbles as HTTP 500 and looks like a server bug
|
|
179
|
+
to the orchestrator, instead of the self-healing 409 we want. */
|
|
180
|
+
const roster = workspaceStore.listWorkers(workspaceId);
|
|
181
|
+
const worker = roster.find((entry) => entry.name === workerName);
|
|
182
|
+
if (!worker) {
|
|
183
|
+
throw new ConflictError(formatUnknownWorkerError(workerName, roster));
|
|
184
|
+
}
|
|
185
|
+
/* Capture the active-run state *before* dispatchTask runs, because
|
|
186
|
+
dispatchTask calls ensureWorkerRun on authenticated team-send calls
|
|
187
|
+
and may silently auto-start a stopped worker. Surfacing
|
|
188
|
+
`restartedWorker=true` to the orchestrator turns that silent state
|
|
189
|
+
change into a transparent one. Internal calls without `fromAgentId`
|
|
190
|
+
only queue work and do not wake a PTY, so they must not report a
|
|
191
|
+
restart. */
|
|
192
|
+
const restartedWorker = input.fromAgentId !== undefined &&
|
|
193
|
+
!agentRuntime.getActiveRunByAgentId(workspaceId, worker.id);
|
|
194
|
+
const dispatch = await dispatchTask(workspaceId, worker.id, text, input);
|
|
195
|
+
return Object.assign(dispatch, { restartedWorker });
|
|
88
196
|
},
|
|
89
197
|
recordUserInput(workspaceId, orchestratorId, text) {
|
|
90
198
|
workspaceStore.getAgent(workspaceId, orchestratorId);
|
|
@@ -123,17 +231,19 @@ export const createTeamOperations = ({ agentRuntime, createDispatch, deleteDispa
|
|
|
123
231
|
const status = input.status;
|
|
124
232
|
const artifacts = input.artifacts ?? [];
|
|
125
233
|
const worker = workspaceStore.getWorker(workspaceId, workerId);
|
|
126
|
-
if (input.requireActiveRun === true &&
|
|
127
|
-
!agentRuntime.getActiveRunByAgentId(workspaceId, `${workspaceId}:orchestrator`)) {
|
|
128
|
-
throw new PtyInactiveError(`No active run for agent: ${workspaceId}:orchestrator`);
|
|
129
|
-
}
|
|
130
234
|
const openDispatch = findOpenDispatch(workspaceId, workerId, input.dispatchId);
|
|
131
|
-
if (!openDispatch && input.dispatchId) {
|
|
132
|
-
throw new ConflictError(`No open dispatch for worker: ${worker.name}`);
|
|
133
|
-
}
|
|
134
235
|
if (!openDispatch) {
|
|
135
236
|
throw new ConflictError(`No open dispatch for worker: ${worker.name}`);
|
|
136
237
|
}
|
|
238
|
+
const isWorkflowDispatch = openDispatch.fromAgentId === getWorkflowAgentId(workspaceId);
|
|
239
|
+
// Pre-check the orchestrator PTY only when the report is heading there.
|
|
240
|
+
// Workflow-sourced dispatches don't need an orchestrator: the runner
|
|
241
|
+
// is in-process and will resolve its awaiter directly.
|
|
242
|
+
if (input.requireActiveRun === true && !isWorkflowDispatch) {
|
|
243
|
+
if (!agentRuntime.getActiveRunByAgentId(workspaceId, `${workspaceId}:orchestrator`)) {
|
|
244
|
+
throw new PtyInactiveError(`No active run for agent: ${workspaceId}:orchestrator`);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
137
247
|
const messageHandle = insertMessage(createReportMessage(workspaceId, workerId, text, status, artifacts));
|
|
138
248
|
try {
|
|
139
249
|
const dispatch = markDispatchReportedByWorker({
|
|
@@ -149,6 +259,24 @@ export const createTeamOperations = ({ agentRuntime, createDispatch, deleteDispa
|
|
|
149
259
|
workspaceStore.markTaskReported(workspaceId, workerId);
|
|
150
260
|
let forwardError = null;
|
|
151
261
|
let forwarded = false;
|
|
262
|
+
// Workflow-sourced dispatches: the source is the in-process runner, not a
|
|
263
|
+
// PTY. Resolve its awaiting Promise instead of injecting into orchestrator
|
|
264
|
+
// stdin (which would do nothing — `__workflow__` has no PTY).
|
|
265
|
+
if (dispatch.fromAgentId === getWorkflowAgentId(workspaceId)) {
|
|
266
|
+
try {
|
|
267
|
+
workflowDispatchAwaiter.notifyReport(dispatch.id, {
|
|
268
|
+
artifacts,
|
|
269
|
+
text,
|
|
270
|
+
...(status ? { status } : {}),
|
|
271
|
+
});
|
|
272
|
+
forwarded = true;
|
|
273
|
+
}
|
|
274
|
+
catch (error) {
|
|
275
|
+
forwardError = reportForwardErrorMessage(error);
|
|
276
|
+
console.error('[hive] swallowed:teamReport.workflowForward', error);
|
|
277
|
+
}
|
|
278
|
+
return { dispatch, forwardError, forwarded };
|
|
279
|
+
}
|
|
152
280
|
if (input.requireActiveRun === true) {
|
|
153
281
|
try {
|
|
154
282
|
agentRuntime.writeReportPrompt(workspaceId, worker.name, workerId, text, artifacts, {
|
|
@@ -161,6 +289,24 @@ export const createTeamOperations = ({ agentRuntime, createDispatch, deleteDispa
|
|
|
161
289
|
console.error('[hive] swallowed:teamReport.forward', error);
|
|
162
290
|
}
|
|
163
291
|
}
|
|
292
|
+
// M11: if this worker was spawned with `team spawn --ephemeral`, this
|
|
293
|
+
// first successful report is its trigger to auto-dismiss. Deferred via
|
|
294
|
+
// queueMicrotask so the orchestrator's forward write lands BEFORE the
|
|
295
|
+
// worker's PTY is torn down (otherwise the inject + dismiss race).
|
|
296
|
+
// Skipped for workflow dispatches — workflow workers are managed by
|
|
297
|
+
// the runner's own finally block.
|
|
298
|
+
if (worker.ephemeral === true &&
|
|
299
|
+
worker.spawnedBy === 'orchestrator' &&
|
|
300
|
+
dismissEphemeralWorker) {
|
|
301
|
+
queueMicrotask(() => {
|
|
302
|
+
try {
|
|
303
|
+
dismissEphemeralWorker(workspaceId, workerId);
|
|
304
|
+
}
|
|
305
|
+
catch (error) {
|
|
306
|
+
console.error('[hive] swallowed:teamReport.ephemeralDismiss', error);
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
}
|
|
164
310
|
return { dispatch, forwardError, forwarded };
|
|
165
311
|
}
|
|
166
312
|
catch (error) {
|
|
@@ -1,15 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
const normalizeExecutable = (value) => {
|
|
3
|
-
if (!value)
|
|
4
|
-
return null;
|
|
5
|
-
const normalized = basename(value).toLowerCase();
|
|
6
|
-
return normalized.replace(/\.(cmd|exe)$/u, '');
|
|
7
|
-
};
|
|
1
|
+
import { normalizeExecutableToken } from './startup-command-parser.js';
|
|
8
2
|
export const resolveTerminalInputProfile = (config) => {
|
|
9
3
|
if (!config)
|
|
10
4
|
return 'default';
|
|
11
5
|
if (config.commandPresetId === 'opencode')
|
|
12
6
|
return 'opencode';
|
|
13
|
-
const executable =
|
|
7
|
+
const executable = normalizeExecutableToken(config.interactiveCommand) ?? normalizeExecutableToken(config.command);
|
|
14
8
|
return executable === 'opencode' ? 'opencode' : 'default';
|
|
15
9
|
};
|
|
@@ -14,7 +14,11 @@ export const parseTerminalControlMessage = (raw) => {
|
|
|
14
14
|
}
|
|
15
15
|
const resizeCols = asInteger(cols);
|
|
16
16
|
const resizeRows = asInteger(rows);
|
|
17
|
-
if (parsed.type === 'resize' &&
|
|
17
|
+
if (parsed.type === 'resize' &&
|
|
18
|
+
resizeCols !== undefined &&
|
|
19
|
+
resizeRows !== undefined &&
|
|
20
|
+
resizeCols > 0 &&
|
|
21
|
+
resizeRows > 0) {
|
|
18
22
|
const message = {
|
|
19
23
|
type: 'resize',
|
|
20
24
|
cols: resizeCols,
|
|
@@ -22,10 +26,12 @@ export const parseTerminalControlMessage = (raw) => {
|
|
|
22
26
|
};
|
|
23
27
|
const parsedPixelWidth = asInteger(pixelWidth);
|
|
24
28
|
const parsedPixelHeight = asInteger(pixelHeight);
|
|
25
|
-
if (parsedPixelWidth !== undefined)
|
|
29
|
+
if (parsedPixelWidth !== undefined && parsedPixelWidth >= 0) {
|
|
26
30
|
message.pixelWidth = parsedPixelWidth;
|
|
27
|
-
|
|
31
|
+
}
|
|
32
|
+
if (parsedPixelHeight !== undefined && parsedPixelHeight >= 0) {
|
|
28
33
|
message.pixelHeight = parsedPixelHeight;
|
|
34
|
+
}
|
|
29
35
|
return message;
|
|
30
36
|
}
|
|
31
37
|
throw new Error('Invalid terminal control message');
|