@tt-a1i/hive 1.4.4 → 1.6.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 +47 -0
- package/README.en.md +21 -0
- package/README.md +16 -0
- package/assets/qq-group.jpg +0 -0
- package/dist/bin/team.cmd +1 -0
- package/dist/src/cli/hive-update.d.ts +45 -17
- package/dist/src/cli/hive-update.js +63 -25
- package/dist/src/cli/hive.d.ts +25 -0
- package/dist/src/cli/hive.js +41 -3
- package/dist/src/cli/team.d.ts +1 -0
- package/dist/src/cli/team.js +216 -3
- package/dist/src/server/agent-command-resolver.js +3 -19
- package/dist/src/server/agent-manager-support.d.ts +2 -2
- package/dist/src/server/agent-manager-support.js +98 -24
- package/dist/src/server/agent-run-starter.d.ts +6 -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 +12 -1
- package/dist/src/server/agent-runtime-stop-run.d.ts +1 -1
- package/dist/src/server/agent-runtime-stop-run.js +4 -1
- package/dist/src/server/agent-runtime.d.ts +2 -1
- package/dist/src/server/agent-runtime.js +14 -3
- package/dist/src/server/agent-startup-instructions.d.ts +7 -1
- package/dist/src/server/agent-startup-instructions.js +17 -9
- package/dist/src/server/agent-stdin-dispatcher.d.ts +25 -5
- package/dist/src/server/agent-stdin-dispatcher.js +141 -40
- 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/feature-flags.d.ts +42 -0
- package/dist/src/server/feature-flags.js +24 -0
- package/dist/src/server/fs-pick-folder.js +4 -0
- package/dist/src/server/fs-sandbox.js +36 -7
- package/dist/src/server/hive-team-guidance.d.ts +12 -6
- package/dist/src/server/hive-team-guidance.js +253 -71
- 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 +5 -6
- 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 +110 -13
- 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 +5 -1
- package/dist/src/server/recovery-summary.js +18 -17
- package/dist/src/server/report-outbox-store.d.ts +36 -0
- package/dist/src/server/report-outbox-store.js +33 -0
- package/dist/src/server/restart-policy-support.d.ts +5 -1
- package/dist/src/server/restart-policy-support.js +9 -1
- package/dist/src/server/restart-policy.d.ts +6 -2
- package/dist/src/server/restart-policy.js +51 -31
- 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-tasks.js +23 -0
- package/dist/src/server/routes-team.js +211 -1
- 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-workspaces.js +5 -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 +2 -1
- package/dist/src/server/runtime-store-contract.d.ts +125 -0
- package/dist/src/server/runtime-store-contract.js +1 -0
- package/dist/src/server/runtime-store-helpers.d.ts +11 -0
- package/dist/src/server/runtime-store-helpers.js +106 -2
- package/dist/src/server/runtime-store-workflows.d.ts +6 -0
- package/dist/src/server/runtime-store-workflows.js +108 -0
- package/dist/src/server/runtime-store.d.ts +3 -72
- package/dist/src/server/runtime-store.js +71 -4
- 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/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 +110 -1
- package/dist/src/server/system-message.d.ts +7 -0
- package/dist/src/server/system-message.js +8 -1
- package/dist/src/server/task-deps.d.ts +32 -0
- package/dist/src/server/task-deps.js +40 -0
- package/dist/src/server/tasks-file-watcher.d.ts +12 -1
- package/dist/src/server/tasks-file-watcher.js +128 -23
- package/dist/src/server/tasks-file.d.ts +3 -1
- package/dist/src/server/tasks-file.js +33 -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 +10 -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 +21 -1
- package/dist/src/server/team-operations.js +183 -16
- 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 +10 -8
- package/dist/src/server/webhook-notifier.d.ts +34 -0
- package/dist/src/server/webhook-notifier.js +47 -0
- 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-output-schema.d.ts +18 -0
- package/dist/src/server/workflow-output-schema.js +41 -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 +411 -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-DeZhTQLi.js → AddWorkerDialog-CGbaxu0T.js} +2 -2
- package/web/dist/assets/AddWorkspaceDialog-CNgExu6b.js +1 -0
- package/web/dist/assets/{FirstRunWizard-B5wLcat5.js → FirstRunWizard-DxGApUNc.js} +1 -1
- package/web/dist/assets/{MarketplaceDrawer-BC0eBOEW.js → MarketplaceDrawer-Bk6cpukn.js} +1 -1
- package/web/dist/assets/WhatsNewDialog-CSGzk-2U.js +1 -0
- package/web/dist/assets/WorkerModal-i2F3n3nZ.js +1 -0
- package/web/dist/assets/WorkspaceTaskDrawer-C_Ta_K13.js +1 -0
- package/web/dist/assets/WorkspaceTerminalPanels-VdDxtrQF.js +1 -0
- package/web/dist/assets/index-5zh61jMg.css +1 -0
- package/web/dist/assets/index-CAgGM6nb.js +75 -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-DDpXNEKf.js +0 -1
- package/web/dist/assets/WorkerModal-BwMHq-Bi.js +0 -1
- package/web/dist/assets/WorkspaceTaskDrawer-CxvT4nqs.js +0 -1
- package/web/dist/assets/WorkspaceTerminalPanels-CvibsPSd.js +0 -1
- package/web/dist/assets/index-BEsTmfrO.css +0 -1
- package/web/dist/assets/index-Ddb7bDN5.js +0 -75
- package/web/dist/assets/path-join-S7qkXQtP.js +0 -1
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { type FeatureFlags } from './feature-flags.js';
|
|
2
|
+
import type { WorkflowCliPolicy } from './workflow-cli-policy.js';
|
|
1
3
|
interface TasksFileService {
|
|
2
4
|
readTasks: (workspacePath: string) => string;
|
|
3
5
|
writeTasks: (workspacePath: string, content: string) => void;
|
|
@@ -16,6 +18,6 @@ export declare const ensureTasksFile: (workspacePath: string) => string;
|
|
|
16
18
|
* on every workspace open means a Hive version bump that changes the rules
|
|
17
19
|
* propagates without manual intervention.
|
|
18
20
|
*/
|
|
19
|
-
export declare const ensureProtocolFile: (workspacePath: string) => string;
|
|
21
|
+
export declare const ensureProtocolFile: (workspacePath: string, cliPolicy?: WorkflowCliPolicy, flags?: FeatureFlags) => string;
|
|
20
22
|
export declare const createTasksFileService: () => TasksFileService;
|
|
21
23
|
export type { TasksFileService };
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import { dirname, join } from 'node:path';
|
|
3
|
+
import { FEATURE_FLAGS_ALL_OFF } from './feature-flags.js';
|
|
3
4
|
import { buildProtocolDoc } from './hive-team-guidance.js';
|
|
4
5
|
export const HIVE_DIR_NAME = '.hive';
|
|
5
6
|
export const TASKS_FILE_NAME = 'tasks.md';
|
|
@@ -9,18 +10,39 @@ export const PROTOCOL_RELATIVE_PATH = `${HIVE_DIR_NAME}/${PROTOCOL_FILE_NAME}`;
|
|
|
9
10
|
export const getTasksFilePath = (workspacePath) => join(workspacePath, HIVE_DIR_NAME, TASKS_FILE_NAME);
|
|
10
11
|
export const getProtocolFilePath = (workspacePath) => join(workspacePath, HIVE_DIR_NAME, PROTOCOL_FILE_NAME);
|
|
11
12
|
const getLegacyTasksFilePath = (workspacePath) => join(workspacePath, TASKS_FILE_NAME);
|
|
13
|
+
const RETRYABLE_TASKS_FS_ERROR_CODES = new Set(['EACCES', 'EBUSY', 'EPERM']);
|
|
14
|
+
const TASKS_FS_RETRY_DELAYS_MS = [20, 50, 100];
|
|
15
|
+
const sleepSync = (ms) => {
|
|
16
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
17
|
+
};
|
|
18
|
+
const runRetryableTasksFileOperation = (operation) => {
|
|
19
|
+
for (let attempt = 0;; attempt += 1) {
|
|
20
|
+
try {
|
|
21
|
+
return operation();
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
const code = error?.code;
|
|
25
|
+
const delay = TASKS_FS_RETRY_DELAYS_MS[attempt];
|
|
26
|
+
if (!code || !RETRYABLE_TASKS_FS_ERROR_CODES.has(code) || delay === undefined)
|
|
27
|
+
throw error;
|
|
28
|
+
sleepSync(delay);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
};
|
|
12
32
|
const ensureTasksDir = (workspacePath) => {
|
|
13
|
-
mkdirSync(dirname(getTasksFilePath(workspacePath)), { recursive: true });
|
|
33
|
+
runRetryableTasksFileOperation(() => mkdirSync(dirname(getTasksFilePath(workspacePath)), { recursive: true }));
|
|
14
34
|
};
|
|
15
35
|
export const ensureTasksFile = (workspacePath) => {
|
|
16
36
|
ensureTasksDir(workspacePath);
|
|
17
37
|
const tasksFilePath = getTasksFilePath(workspacePath);
|
|
18
38
|
if (existsSync(tasksFilePath)) {
|
|
19
|
-
return readFileSync(tasksFilePath, 'utf8');
|
|
39
|
+
return runRetryableTasksFileOperation(() => readFileSync(tasksFilePath, 'utf8'));
|
|
20
40
|
}
|
|
21
41
|
const legacyTasksFilePath = getLegacyTasksFilePath(workspacePath);
|
|
22
|
-
const content = existsSync(legacyTasksFilePath)
|
|
23
|
-
|
|
42
|
+
const content = existsSync(legacyTasksFilePath)
|
|
43
|
+
? runRetryableTasksFileOperation(() => readFileSync(legacyTasksFilePath, 'utf8'))
|
|
44
|
+
: '';
|
|
45
|
+
runRetryableTasksFileOperation(() => writeFileSync(tasksFilePath, content, 'utf8'));
|
|
24
46
|
return content;
|
|
25
47
|
};
|
|
26
48
|
/**
|
|
@@ -29,14 +51,16 @@ export const ensureTasksFile = (workspacePath) => {
|
|
|
29
51
|
* on every workspace open means a Hive version bump that changes the rules
|
|
30
52
|
* propagates without manual intervention.
|
|
31
53
|
*/
|
|
32
|
-
export const ensureProtocolFile = (workspacePath) => {
|
|
54
|
+
export const ensureProtocolFile = (workspacePath, cliPolicy, flags = FEATURE_FLAGS_ALL_OFF) => {
|
|
33
55
|
ensureTasksDir(workspacePath);
|
|
34
56
|
const protocolFilePath = getProtocolFilePath(workspacePath);
|
|
35
|
-
const desired = buildProtocolDoc();
|
|
36
|
-
const current = existsSync(protocolFilePath)
|
|
57
|
+
const desired = buildProtocolDoc(cliPolicy, flags);
|
|
58
|
+
const current = existsSync(protocolFilePath)
|
|
59
|
+
? runRetryableTasksFileOperation(() => readFileSync(protocolFilePath, 'utf8'))
|
|
60
|
+
: null;
|
|
37
61
|
if (current === desired)
|
|
38
62
|
return desired;
|
|
39
|
-
writeFileSync(protocolFilePath, desired, 'utf8');
|
|
63
|
+
runRetryableTasksFileOperation(() => writeFileSync(protocolFilePath, desired, 'utf8'));
|
|
40
64
|
return desired;
|
|
41
65
|
};
|
|
42
66
|
export const createTasksFileService = () => {
|
|
@@ -46,7 +70,7 @@ export const createTasksFileService = () => {
|
|
|
46
70
|
},
|
|
47
71
|
writeTasks(workspacePath, content) {
|
|
48
72
|
ensureTasksDir(workspacePath);
|
|
49
|
-
writeFileSync(getTasksFilePath(workspacePath), content, 'utf8');
|
|
73
|
+
runRetryableTasksFileOperation(() => writeFileSync(getTasksFilePath(workspacePath), content, 'utf8'));
|
|
50
74
|
},
|
|
51
75
|
};
|
|
52
76
|
};
|
|
@@ -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' | 'next' | '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,14 @@
|
|
|
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
|
+
'next',
|
|
6
|
+
'cancel',
|
|
7
|
+
'help',
|
|
8
|
+
'spawn',
|
|
9
|
+
'dismiss',
|
|
10
|
+
'workflow',
|
|
11
|
+
]);
|
|
3
12
|
const WORKER_COMMANDS = new Set(['report', 'status', 'help']);
|
|
4
13
|
const WORKER_ROLES = new Set(['coder', 'reviewer', 'tester', 'custom']);
|
|
5
14
|
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
|
});
|
|
@@ -2,14 +2,21 @@ import type { TeamListItem } from '../shared/types.js';
|
|
|
2
2
|
import type { AgentRuntime } from './agent-runtime.js';
|
|
3
3
|
import type { DispatchRecord } from './dispatch-ledger-store.js';
|
|
4
4
|
import type { MessageLogHandle, MessageLogRecord } from './message-log-store.js';
|
|
5
|
+
import type { ReportOutboxStore } from './report-outbox-store.js';
|
|
6
|
+
import type { WebhookEvent } from './webhook-notifier.js';
|
|
7
|
+
import type { WorkflowDispatchAwaiter } from './workflow-dispatch-awaiter.js';
|
|
5
8
|
import type { WorkspaceStore } from './workspace-store.js';
|
|
6
9
|
export declare const formatUnknownWorkerError: (workerName: string, roster: readonly TeamListItem[]) => string;
|
|
7
10
|
export interface TeamOperationsInput {
|
|
8
11
|
agentRuntime: AgentRuntime;
|
|
9
12
|
createDispatch: (input: {
|
|
10
13
|
fromAgentId?: string;
|
|
14
|
+
label?: string;
|
|
15
|
+
phase?: string;
|
|
16
|
+
stepIndex?: number;
|
|
11
17
|
text: string;
|
|
12
18
|
toAgentId: string;
|
|
19
|
+
workflowRunId?: string;
|
|
13
20
|
workspaceId: string;
|
|
14
21
|
}) => DispatchRecord;
|
|
15
22
|
deleteDispatch: (dispatchId: string) => void;
|
|
@@ -30,11 +37,23 @@ export interface TeamOperationsInput {
|
|
|
30
37
|
workspaceId: string;
|
|
31
38
|
}) => DispatchRecord | undefined;
|
|
32
39
|
markDispatchSubmitted: (dispatchId: string) => void;
|
|
40
|
+
reportOutbox: ReportOutboxStore;
|
|
41
|
+
/** Fire an outbound completion webhook (best-effort) when a worker reports. */
|
|
42
|
+
notifyWebhook?: (event: WebhookEvent) => void;
|
|
43
|
+
workflowDispatchAwaiter: WorkflowDispatchAwaiter;
|
|
33
44
|
workspaceStore: WorkspaceStore;
|
|
45
|
+
/** Auto-dismiss an ephemeral orchestrator-spawned worker after its
|
|
46
|
+
* dispatch report. Wired in runtime-store-helpers to remove the worker
|
|
47
|
+
* completely (stop run, drop launch config, drop the row). M11. */
|
|
48
|
+
dismissEphemeralWorker?: (workspaceId: string, workerId: string) => void;
|
|
34
49
|
}
|
|
35
50
|
export interface DispatchTaskInput {
|
|
36
51
|
fromAgentId?: string;
|
|
37
52
|
hivePort?: string;
|
|
53
|
+
workflowRunId?: string;
|
|
54
|
+
stepIndex?: number;
|
|
55
|
+
phase?: string;
|
|
56
|
+
label?: string;
|
|
38
57
|
}
|
|
39
58
|
export interface ReportTaskInput {
|
|
40
59
|
artifacts?: string[];
|
|
@@ -57,13 +76,14 @@ export interface ReportTaskResult {
|
|
|
57
76
|
forwardError: string | null;
|
|
58
77
|
forwarded: boolean;
|
|
59
78
|
}
|
|
60
|
-
export declare const createTeamOperations: ({ agentRuntime, createDispatch, deleteDispatch, deleteMessage, findOpenDispatch, findOpenDispatchById, insertMessage, markDispatchCancelled, markDispatchReportedByWorker, markDispatchSubmitted, workspaceStore, }: TeamOperationsInput) => {
|
|
79
|
+
export declare const createTeamOperations: ({ agentRuntime, createDispatch, deleteDispatch, deleteMessage, findOpenDispatch, findOpenDispatchById, insertMessage, markDispatchCancelled, markDispatchReportedByWorker, markDispatchSubmitted, reportOutbox, notifyWebhook, workflowDispatchAwaiter, workspaceStore, dismissEphemeralWorker, }: TeamOperationsInput) => {
|
|
61
80
|
cancelTask(workspaceId: string, dispatchId: string, input: CancelTaskInput): {
|
|
62
81
|
dispatch: DispatchRecord;
|
|
63
82
|
forwardError: string | null;
|
|
64
83
|
forwarded: boolean;
|
|
65
84
|
};
|
|
66
85
|
dispatchTask: (workspaceId: string, workerId: string, text: string, input?: DispatchTaskInput) => Promise<DispatchRecord>;
|
|
86
|
+
drainReportOutbox: (workspaceId: string) => void;
|
|
67
87
|
dispatchTaskByWorkerName(workspaceId: string, workerName: string, text: string, input?: DispatchTaskInput): Promise<DispatchRecord & {
|
|
68
88
|
restartedWorker: boolean;
|
|
69
89
|
}>;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { buildOrchestratorReportPayload } from './agent-stdin-dispatcher.js';
|
|
2
|
+
import { ConflictError } from './http-errors.js';
|
|
2
3
|
import { createReportMessage, createSendMessage, createStatusMessage, createUserInputMessage, } from './runtime-message-builders.js';
|
|
4
|
+
import { getWorkflowAgentId } from './workspace-store-support.js';
|
|
3
5
|
/* Roster snapshot embedded in the 409 the orchestrator sees when it
|
|
4
6
|
dispatches to a missing name. Format prioritizes the orchestrator's
|
|
5
7
|
parsing path: bullet-per-member with role + live status + pending
|
|
@@ -23,7 +25,27 @@ export const formatUnknownWorkerError = (workerName, roster) => {
|
|
|
23
25
|
].join('\n');
|
|
24
26
|
};
|
|
25
27
|
const reportForwardErrorMessage = (error) => error instanceof Error ? error.message : String(error);
|
|
26
|
-
export const createTeamOperations = ({ agentRuntime, createDispatch, deleteDispatch, deleteMessage, findOpenDispatch, findOpenDispatchById, insertMessage, markDispatchCancelled, markDispatchReportedByWorker, markDispatchSubmitted, workspaceStore, }) => {
|
|
28
|
+
export const createTeamOperations = ({ agentRuntime, createDispatch, deleteDispatch, deleteMessage, findOpenDispatch, findOpenDispatchById, insertMessage, markDispatchCancelled, markDispatchReportedByWorker, markDispatchSubmitted, reportOutbox, notifyWebhook, workflowDispatchAwaiter, workspaceStore, dismissEphemeralWorker, }) => {
|
|
29
|
+
// Best-effort redelivery of reports a prior orchestrator outage stranded.
|
|
30
|
+
// Called when a fresh report confirms the orchestrator is reachable and
|
|
31
|
+
// when the orchestrator polls `team list` (its natural post-restart wakeup).
|
|
32
|
+
// An entry is marked delivered only after its PTY write actually resolves,
|
|
33
|
+
// so a still-down orchestrator just leaves the backlog pending.
|
|
34
|
+
const drainReportOutbox = (workspaceId) => {
|
|
35
|
+
const orchestratorId = `${workspaceId}:orchestrator`;
|
|
36
|
+
if (!agentRuntime.getActiveRunByAgentId(workspaceId, orchestratorId))
|
|
37
|
+
return;
|
|
38
|
+
for (const entry of reportOutbox.listPending(workspaceId, orchestratorId)) {
|
|
39
|
+
void agentRuntime
|
|
40
|
+
.deliverSystemMessageToAgent(workspaceId, orchestratorId, entry.payload, {
|
|
41
|
+
requireActiveRun: true,
|
|
42
|
+
})
|
|
43
|
+
.then(() => reportOutbox.markDelivered(entry.id))
|
|
44
|
+
.catch((error) => {
|
|
45
|
+
console.error('[hive] swallowed:teamReport.outboxDrain', error);
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
};
|
|
27
49
|
const ensureWorkerRun = async (workspaceId, workerId, hivePort) => {
|
|
28
50
|
if (agentRuntime.getActiveRunByAgentId(workspaceId, workerId)) {
|
|
29
51
|
return;
|
|
@@ -45,10 +67,25 @@ export const createTeamOperations = ({ agentRuntime, createDispatch, deleteDispa
|
|
|
45
67
|
throw error;
|
|
46
68
|
}
|
|
47
69
|
};
|
|
70
|
+
const cancelUndeliveredDispatch = (workspaceId, workerId, dispatchId, reason, workflowRunId) => {
|
|
71
|
+
const cancelled = markDispatchCancelled({ dispatchId, reason, workspaceId });
|
|
72
|
+
if (!cancelled)
|
|
73
|
+
return;
|
|
74
|
+
try {
|
|
75
|
+
workspaceStore.markTaskCancelled(workspaceId, workerId);
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
console.error('[hive] swallowed:teamDispatch.markTaskCancelled', error);
|
|
79
|
+
}
|
|
80
|
+
if (workflowRunId !== undefined) {
|
|
81
|
+
workflowDispatchAwaiter.notifyCancel(dispatchId, reason);
|
|
82
|
+
}
|
|
83
|
+
};
|
|
48
84
|
const dispatchTask = async (workspaceId, workerId, text, input = {}) => {
|
|
49
85
|
const message = createSendMessage(workspaceId, workerId, text, input.fromAgentId);
|
|
50
86
|
const messageHandle = insertMessage(message);
|
|
51
87
|
let dispatch;
|
|
88
|
+
let pendingMarked = false;
|
|
52
89
|
try {
|
|
53
90
|
const dispatchInput = {
|
|
54
91
|
text,
|
|
@@ -57,18 +94,69 @@ export const createTeamOperations = ({ agentRuntime, createDispatch, deleteDispa
|
|
|
57
94
|
};
|
|
58
95
|
if (input.fromAgentId)
|
|
59
96
|
dispatchInput.fromAgentId = input.fromAgentId;
|
|
97
|
+
if (input.workflowRunId !== undefined)
|
|
98
|
+
dispatchInput.workflowRunId = input.workflowRunId;
|
|
99
|
+
if (input.stepIndex !== undefined)
|
|
100
|
+
dispatchInput.stepIndex = input.stepIndex;
|
|
101
|
+
if (input.phase !== undefined)
|
|
102
|
+
dispatchInput.phase = input.phase;
|
|
103
|
+
if (input.label !== undefined)
|
|
104
|
+
dispatchInput.label = input.label;
|
|
60
105
|
dispatch = createDispatch(dispatchInput);
|
|
106
|
+
const dispatchId = dispatch.id;
|
|
61
107
|
if (input.fromAgentId) {
|
|
62
108
|
const sender = workspaceStore.getAgent(workspaceId, input.fromAgentId);
|
|
63
109
|
await ensureWorkerRun(workspaceId, workerId, input.hivePort ?? '');
|
|
64
110
|
const worker = workspaceStore.getWorker(workspaceId, workerId);
|
|
65
|
-
|
|
66
|
-
|
|
111
|
+
const isWorkflowDispatch = input.workflowRunId !== undefined || input.fromAgentId === getWorkflowAgentId(workspaceId);
|
|
112
|
+
markDispatchSubmitted(dispatchId);
|
|
113
|
+
workspaceStore.markTaskDispatched(workspaceId, workerId);
|
|
114
|
+
pendingMarked = true;
|
|
115
|
+
try {
|
|
116
|
+
const writePrompt = agentRuntime.writeSendPrompt(workspaceId, workerId, dispatchId, sender.name, worker.description, text);
|
|
117
|
+
void writePrompt.catch((error) => {
|
|
118
|
+
// `team send` is intentionally asynchronous (§3.3). A worker that
|
|
119
|
+
// exits during paste-submit did not receive actionable work, so
|
|
120
|
+
// close the open dispatch instead of leaving a fake pending task.
|
|
121
|
+
try {
|
|
122
|
+
cancelUndeliveredDispatch(workspaceId, workerId, dispatchId, reportForwardErrorMessage(error), input.workflowRunId);
|
|
123
|
+
}
|
|
124
|
+
catch (cancelError) {
|
|
125
|
+
if (!isWorkflowDispatch)
|
|
126
|
+
console.error('[hive] swallowed:teamDispatch.cancelUndelivered', cancelError);
|
|
127
|
+
}
|
|
128
|
+
if (!isWorkflowDispatch)
|
|
129
|
+
console.error('[hive] swallowed:teamDispatch.writePrompt', error);
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
catch (error) {
|
|
133
|
+
try {
|
|
134
|
+
cancelUndeliveredDispatch(workspaceId, workerId, dispatchId, reportForwardErrorMessage(error), input.workflowRunId);
|
|
135
|
+
}
|
|
136
|
+
catch (cancelError) {
|
|
137
|
+
if (!isWorkflowDispatch)
|
|
138
|
+
console.error('[hive] swallowed:teamDispatch.cancelUndelivered', cancelError);
|
|
139
|
+
}
|
|
140
|
+
if (!isWorkflowDispatch)
|
|
141
|
+
console.error('[hive] swallowed:teamDispatch.writePrompt', error);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
workspaceStore.markTaskDispatched(workspaceId, workerId);
|
|
146
|
+
pendingMarked = true;
|
|
67
147
|
}
|
|
68
|
-
workspaceStore.markTaskDispatched(workspaceId, workerId);
|
|
69
148
|
return dispatch;
|
|
70
149
|
}
|
|
71
150
|
catch (error) {
|
|
151
|
+
if (pendingMarked) {
|
|
152
|
+
try {
|
|
153
|
+
workspaceStore.markTaskCancelled(workspaceId, workerId);
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
// Best-effort compensation for the in-memory pending count; the
|
|
157
|
+
// durable send message is deleted below.
|
|
158
|
+
}
|
|
159
|
+
}
|
|
72
160
|
if (dispatch)
|
|
73
161
|
deleteDispatch(dispatch.id);
|
|
74
162
|
deleteMessage(messageHandle);
|
|
@@ -104,6 +192,7 @@ export const createTeamOperations = ({ agentRuntime, createDispatch, deleteDispa
|
|
|
104
192
|
return { dispatch, forwardError, forwarded };
|
|
105
193
|
},
|
|
106
194
|
dispatchTask,
|
|
195
|
+
drainReportOutbox,
|
|
107
196
|
async dispatchTaskByWorkerName(workspaceId, workerName, text, input = {}) {
|
|
108
197
|
/* Build the roster once so a missing-name path can surface it without
|
|
109
198
|
a second store call. We deliberately don't go through
|
|
@@ -164,14 +253,7 @@ export const createTeamOperations = ({ agentRuntime, createDispatch, deleteDispa
|
|
|
164
253
|
const status = input.status;
|
|
165
254
|
const artifacts = input.artifacts ?? [];
|
|
166
255
|
const worker = workspaceStore.getWorker(workspaceId, workerId);
|
|
167
|
-
if (input.requireActiveRun === true &&
|
|
168
|
-
!agentRuntime.getActiveRunByAgentId(workspaceId, `${workspaceId}:orchestrator`)) {
|
|
169
|
-
throw new PtyInactiveError(`No active run for agent: ${workspaceId}:orchestrator`);
|
|
170
|
-
}
|
|
171
256
|
const openDispatch = findOpenDispatch(workspaceId, workerId, input.dispatchId);
|
|
172
|
-
if (!openDispatch && input.dispatchId) {
|
|
173
|
-
throw new ConflictError(`No open dispatch for worker: ${worker.name}`);
|
|
174
|
-
}
|
|
175
257
|
if (!openDispatch) {
|
|
176
258
|
throw new ConflictError(`No open dispatch for worker: ${worker.name}`);
|
|
177
259
|
}
|
|
@@ -190,17 +272,102 @@ export const createTeamOperations = ({ agentRuntime, createDispatch, deleteDispa
|
|
|
190
272
|
workspaceStore.markTaskReported(workspaceId, workerId);
|
|
191
273
|
let forwardError = null;
|
|
192
274
|
let forwarded = false;
|
|
193
|
-
|
|
275
|
+
// Workflow-sourced dispatches: the source is the in-process runner, not a
|
|
276
|
+
// PTY. Resolve its awaiting Promise instead of injecting into orchestrator
|
|
277
|
+
// stdin (which would do nothing — `__workflow__` has no PTY).
|
|
278
|
+
if (dispatch.fromAgentId === getWorkflowAgentId(workspaceId)) {
|
|
194
279
|
try {
|
|
195
|
-
|
|
196
|
-
|
|
280
|
+
workflowDispatchAwaiter.notifyReport(dispatch.id, {
|
|
281
|
+
artifacts,
|
|
282
|
+
text,
|
|
283
|
+
...(status ? { status } : {}),
|
|
197
284
|
});
|
|
198
285
|
forwarded = true;
|
|
199
286
|
}
|
|
200
287
|
catch (error) {
|
|
201
288
|
forwardError = reportForwardErrorMessage(error);
|
|
202
|
-
console.error('[hive] swallowed:teamReport.
|
|
289
|
+
console.error('[hive] swallowed:teamReport.workflowForward', error);
|
|
203
290
|
}
|
|
291
|
+
return { dispatch, forwardError, forwarded };
|
|
292
|
+
}
|
|
293
|
+
// Real worker reported (not a workflow-internal step) — fire the
|
|
294
|
+
// outbound completion webhook. Best-effort; never blocks the report.
|
|
295
|
+
notifyWebhook?.({
|
|
296
|
+
type: 'report_received',
|
|
297
|
+
workspaceId,
|
|
298
|
+
agentId: workerId,
|
|
299
|
+
agentName: worker.name,
|
|
300
|
+
summary: text.slice(0, 280),
|
|
301
|
+
at: Date.now(),
|
|
302
|
+
});
|
|
303
|
+
if (input.requireActiveRun === true) {
|
|
304
|
+
const orchestratorId = `${workspaceId}:orchestrator`;
|
|
305
|
+
// A fresh report proves the orchestrator is reachable — flush any
|
|
306
|
+
// backlog a prior outage stranded first, in arrival order, before
|
|
307
|
+
// this one (both ride the dispatcher's per-agent serial queue).
|
|
308
|
+
drainReportOutbox(workspaceId);
|
|
309
|
+
const payload = buildOrchestratorReportPayload(worker.name, text, artifacts);
|
|
310
|
+
if (agentRuntime.getActiveRunByAgentId(workspaceId, orchestratorId)) {
|
|
311
|
+
try {
|
|
312
|
+
const delivery = agentRuntime.deliverReportToOrchestrator(workspaceId, worker.name, text, artifacts, { requireActiveRun: true });
|
|
313
|
+
forwarded = true;
|
|
314
|
+
// The dispatch is already marked reported. If the PTY dies
|
|
315
|
+
// mid-write the ledger would say "reported" while the
|
|
316
|
+
// orchestrator never received it — persist for redelivery so it
|
|
317
|
+
// isn't silently lost.
|
|
318
|
+
void delivery.catch((error) => {
|
|
319
|
+
console.error('[hive] swallowed:teamReport.forward', error);
|
|
320
|
+
reportOutbox.enqueue({
|
|
321
|
+
workspaceId,
|
|
322
|
+
targetAgentId: orchestratorId,
|
|
323
|
+
dispatchId: dispatch.id,
|
|
324
|
+
payload,
|
|
325
|
+
});
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
catch (error) {
|
|
329
|
+
// TOCTOU: orchestrator vanished between the active-run check and
|
|
330
|
+
// the write. Same fix — queue it.
|
|
331
|
+
console.error('[hive] swallowed:teamReport.forward', error);
|
|
332
|
+
forwardError = reportForwardErrorMessage(error);
|
|
333
|
+
reportOutbox.enqueue({
|
|
334
|
+
workspaceId,
|
|
335
|
+
targetAgentId: orchestratorId,
|
|
336
|
+
dispatchId: dispatch.id,
|
|
337
|
+
payload,
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
else {
|
|
342
|
+
// Orchestrator is down. Queue the report instead of dropping it;
|
|
343
|
+
// the CLI surfaces forwarded:false and the backlog drains on the
|
|
344
|
+
// orchestrator's next `team list` after it restarts.
|
|
345
|
+
forwardError = 'Orchestrator is not running; report queued for delivery.';
|
|
346
|
+
reportOutbox.enqueue({
|
|
347
|
+
workspaceId,
|
|
348
|
+
targetAgentId: orchestratorId,
|
|
349
|
+
dispatchId: dispatch.id,
|
|
350
|
+
payload,
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
// M11: if this worker was spawned with `team spawn --ephemeral`, this
|
|
355
|
+
// first successful report is its trigger to auto-dismiss. Deferred via
|
|
356
|
+
// queueMicrotask so the orchestrator's forward write lands BEFORE the
|
|
357
|
+
// worker's PTY is torn down (otherwise the inject + dismiss race).
|
|
358
|
+
// Skipped for workflow dispatches — workflow workers are managed by
|
|
359
|
+
// the runner's own finally block.
|
|
360
|
+
if (worker.ephemeral === true &&
|
|
361
|
+
worker.spawnedBy === 'orchestrator' &&
|
|
362
|
+
dismissEphemeralWorker) {
|
|
363
|
+
queueMicrotask(() => {
|
|
364
|
+
try {
|
|
365
|
+
dismissEphemeralWorker(workspaceId, workerId);
|
|
366
|
+
}
|
|
367
|
+
catch (error) {
|
|
368
|
+
console.error('[hive] swallowed:teamReport.ephemeralDismiss', error);
|
|
369
|
+
}
|
|
370
|
+
});
|
|
204
371
|
}
|
|
205
372
|
return { dispatch, forwardError, forwarded };
|
|
206
373
|
}
|
|
@@ -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');
|