@tt-a1i/hive 1.4.4 → 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 +22 -0
- package/README.md +8 -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 +199 -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 +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/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-pick-folder.js +4 -0
- 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 -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 +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 +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.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 -72
- package/dist/src/server/runtime-store.js +70 -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 +97 -1
- 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 +13 -1
- package/dist/src/server/tasks-file-watcher.js +127 -23
- 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 +15 -1
- package/dist/src/server/team-operations.js +116 -11
- 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/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-DeZhTQLi.js → AddWorkerDialog-CcC-7kgG.js} +2 -2
- package/web/dist/assets/AddWorkspaceDialog-BDpOTfmt.js +1 -0
- package/web/dist/assets/{FirstRunWizard-B5wLcat5.js → FirstRunWizard-BYX_ocQn.js} +1 -1
- package/web/dist/assets/{MarketplaceDrawer-BC0eBOEW.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-CvibsPSd.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-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/index-BEsTmfrO.css +0 -1
- package/web/dist/assets/index-Ddb7bDN5.js +0 -75
- package/web/dist/assets/path-join-S7qkXQtP.js +0 -1
|
@@ -2,7 +2,7 @@ import type { IPty } from 'node-pty';
|
|
|
2
2
|
import type { AgentRunRecord, AgentRunSnapshot } from './agent-manager.js';
|
|
3
3
|
import type { PtyOutputBus } from './pty-output-bus.js';
|
|
4
4
|
export declare const MAX_RUN_OUTPUT_LENGTH = 1000000;
|
|
5
|
-
type ExecRunner = (cmd: string, args: readonly string[]) => void;
|
|
5
|
+
type ExecRunner = (cmd: string, args: readonly string[], done: (success: boolean) => void) => void;
|
|
6
6
|
/**
|
|
7
7
|
* Windows analogue of POSIX `process.kill(-pgid, SIGKILL)`. node-pty on
|
|
8
8
|
* Windows hands `pty.kill()` to TerminateProcess against the PTY's main
|
|
@@ -28,7 +28,7 @@ type ExecRunner = (cmd: string, args: readonly string[]) => void;
|
|
|
28
28
|
* Exported for unit testing — the `runner` parameter lets tests assert
|
|
29
29
|
* the exact argv without mocking node:child_process.
|
|
30
30
|
*/
|
|
31
|
-
export declare const taskkillProcessTree: (pid: number, platform?: NodeJS.Platform, runner?: ExecRunner) => boolean;
|
|
31
|
+
export declare const taskkillProcessTree: (pid: number, platform?: NodeJS.Platform, runner?: ExecRunner, onFailure?: () => void) => boolean;
|
|
32
32
|
export declare const toAgentRunSnapshot: (run: AgentRunRecord) => AgentRunSnapshot;
|
|
33
33
|
export declare const finishAgentRun: (run: AgentRunRecord, exitCode: number | null, ptyOutputBus: PtyOutputBus) => void;
|
|
34
34
|
export declare const attachAgentPty: (run: AgentRunRecord, pty: IPty, ptyOutputBus: PtyOutputBus) => void;
|
|
@@ -1,8 +1,23 @@
|
|
|
1
|
-
import { execFileSync } from 'node:child_process';
|
|
1
|
+
import { execFile, execFileSync } from 'node:child_process';
|
|
2
2
|
export const MAX_RUN_OUTPUT_LENGTH = 1_000_000;
|
|
3
3
|
const FORCE_KILL_DELAY_MS = 750;
|
|
4
|
-
const
|
|
5
|
-
|
|
4
|
+
const TASKKILL_TIMEOUT_MS = 3000;
|
|
5
|
+
const PTY_READ_EOF_EXIT_GRACE_MS = 1000;
|
|
6
|
+
const isPtyReadEofError = (error) => {
|
|
7
|
+
const candidate = error;
|
|
8
|
+
return process.platform !== 'win32' && candidate?.code === 'EIO' && candidate.syscall === 'read';
|
|
9
|
+
};
|
|
10
|
+
const serializePtyInput = (input) => Buffer.isBuffer(input) ? input.toString('latin1') : input;
|
|
11
|
+
const defaultExecRunner = (cmd, args, done) => {
|
|
12
|
+
let settled = false;
|
|
13
|
+
const settle = (success) => {
|
|
14
|
+
if (settled)
|
|
15
|
+
return;
|
|
16
|
+
settled = true;
|
|
17
|
+
done(success);
|
|
18
|
+
};
|
|
19
|
+
const child = execFile(cmd, [...args], { maxBuffer: 64 * 1024, timeout: TASKKILL_TIMEOUT_MS, windowsHide: true }, (error) => settle(!error));
|
|
20
|
+
child.once('error', () => settle(false));
|
|
6
21
|
};
|
|
7
22
|
/**
|
|
8
23
|
* Windows analogue of POSIX `process.kill(-pgid, SIGKILL)`. node-pty on
|
|
@@ -29,11 +44,14 @@ const defaultExecRunner = (cmd, args) => {
|
|
|
29
44
|
* Exported for unit testing — the `runner` parameter lets tests assert
|
|
30
45
|
* the exact argv without mocking node:child_process.
|
|
31
46
|
*/
|
|
32
|
-
export const taskkillProcessTree = (pid, platform = process.platform, runner = defaultExecRunner) => {
|
|
47
|
+
export const taskkillProcessTree = (pid, platform = process.platform, runner = defaultExecRunner, onFailure) => {
|
|
33
48
|
if (platform !== 'win32' || pid <= 0)
|
|
34
49
|
return false;
|
|
35
50
|
try {
|
|
36
|
-
runner('taskkill', ['/pid', String(pid), '/t', '/f'])
|
|
51
|
+
runner('taskkill', ['/pid', String(pid), '/t', '/f'], (success) => {
|
|
52
|
+
if (!success)
|
|
53
|
+
onFailure?.();
|
|
54
|
+
});
|
|
37
55
|
return true;
|
|
38
56
|
}
|
|
39
57
|
catch {
|
|
@@ -61,6 +79,7 @@ export const finishAgentRun = (run, exitCode, ptyOutputBus) => {
|
|
|
61
79
|
export const attachAgentPty = (run, pty, ptyOutputBus) => {
|
|
62
80
|
let stdinClosed = false;
|
|
63
81
|
let forceKillTimer;
|
|
82
|
+
let ptyReadEofTimer;
|
|
64
83
|
const resolveProcessGroupId = () => {
|
|
65
84
|
if (process.platform === 'win32' || pty.pid <= 0)
|
|
66
85
|
return null;
|
|
@@ -99,26 +118,29 @@ export const attachAgentPty = (run, pty, ptyOutputBus) => {
|
|
|
99
118
|
ignoreBestEffortGroupKillError(error);
|
|
100
119
|
}
|
|
101
120
|
};
|
|
102
|
-
const
|
|
121
|
+
const killPtyDirect = (signal) => {
|
|
103
122
|
try {
|
|
104
|
-
|
|
105
|
-
// taskkill /pid <pid> /t /f walks the parent's process tree
|
|
106
|
-
// BEFORE terminating it — so we have to run it while the parent
|
|
107
|
-
// is still alive. Calling pty.kill() first (the previous
|
|
108
|
-
// ordering) detaches the children: taskkill /T then fails with
|
|
109
|
-
// "process not found" and the npm-installs / build scripts
|
|
110
|
-
// become orphans. taskkill /f also terminates the parent, so
|
|
111
|
-
// pty.kill() is the fallback for the rare case where taskkill
|
|
112
|
-
// is missing from PATH or refused (e.g. restricted PowerShell).
|
|
113
|
-
if (!taskkillProcessTree(pty.pid))
|
|
114
|
-
pty.kill();
|
|
115
|
-
}
|
|
116
|
-
else
|
|
117
|
-
pty.kill(signal);
|
|
123
|
+
pty.kill(signal);
|
|
118
124
|
}
|
|
119
125
|
catch (error) {
|
|
120
126
|
ignoreMissingProcess(error);
|
|
121
127
|
}
|
|
128
|
+
};
|
|
129
|
+
const killPty = (signal) => {
|
|
130
|
+
if (process.platform === 'win32') {
|
|
131
|
+
// taskkill /pid <pid> /t /f walks the parent's process tree
|
|
132
|
+
// BEFORE terminating it — so we have to run it while the parent
|
|
133
|
+
// is still alive. Calling pty.kill() first (the previous
|
|
134
|
+
// ordering) detaches the children: taskkill /T then fails with
|
|
135
|
+
// "process not found" and the npm-installs / build scripts
|
|
136
|
+
// become orphans. taskkill /f also terminates the parent, so
|
|
137
|
+
// pty.kill() is the fallback for the rare case where taskkill
|
|
138
|
+
// is missing from PATH or refused (e.g. restricted PowerShell).
|
|
139
|
+
if (!taskkillProcessTree(pty.pid, process.platform, defaultExecRunner, () => killPtyDirect()))
|
|
140
|
+
killPtyDirect();
|
|
141
|
+
}
|
|
142
|
+
else
|
|
143
|
+
killPtyDirect(signal);
|
|
122
144
|
killProcessGroup(signal);
|
|
123
145
|
};
|
|
124
146
|
const clearForceKillTimer = () => {
|
|
@@ -127,8 +149,34 @@ export const attachAgentPty = (run, pty, ptyOutputBus) => {
|
|
|
127
149
|
clearTimeout(forceKillTimer);
|
|
128
150
|
forceKillTimer = undefined;
|
|
129
151
|
};
|
|
152
|
+
const clearPtyReadEofTimer = () => {
|
|
153
|
+
if (!ptyReadEofTimer)
|
|
154
|
+
return;
|
|
155
|
+
clearTimeout(ptyReadEofTimer);
|
|
156
|
+
ptyReadEofTimer = undefined;
|
|
157
|
+
};
|
|
158
|
+
const schedulePtyReadEofExitGuard = (error) => {
|
|
159
|
+
if (ptyReadEofTimer)
|
|
160
|
+
return;
|
|
161
|
+
ptyReadEofTimer = setTimeout(() => {
|
|
162
|
+
ptyReadEofTimer = undefined;
|
|
163
|
+
if (stopped())
|
|
164
|
+
return;
|
|
165
|
+
console.error(`[hive] PTY read EOF without exit for run ${run.runId}`, error);
|
|
166
|
+
finishAgentRun(run, null, ptyOutputBus);
|
|
167
|
+
try {
|
|
168
|
+
killPty('SIGTERM');
|
|
169
|
+
scheduleForceKill();
|
|
170
|
+
}
|
|
171
|
+
catch (killError) {
|
|
172
|
+
ignoreMissingProcess(killError);
|
|
173
|
+
}
|
|
174
|
+
}, PTY_READ_EOF_EXIT_GRACE_MS);
|
|
175
|
+
ptyReadEofTimer.unref?.();
|
|
176
|
+
};
|
|
130
177
|
const cleanupProcessGroup = () => {
|
|
131
178
|
clearForceKillTimer();
|
|
179
|
+
clearPtyReadEofTimer();
|
|
132
180
|
killProcessGroup('SIGKILL');
|
|
133
181
|
};
|
|
134
182
|
const scheduleForceKill = () => {
|
|
@@ -141,11 +189,11 @@ export const attachAgentPty = (run, pty, ptyOutputBus) => {
|
|
|
141
189
|
// Same ordering as killPty(): tree-kill before terminating the
|
|
142
190
|
// parent, so taskkill /T can still enumerate the process tree.
|
|
143
191
|
// pty.kill() is the fallback for taskkill-missing hosts.
|
|
144
|
-
if (!taskkillProcessTree(pty.pid))
|
|
145
|
-
|
|
192
|
+
if (!taskkillProcessTree(pty.pid, process.platform, defaultExecRunner, () => killPtyDirect()))
|
|
193
|
+
killPtyDirect();
|
|
146
194
|
}
|
|
147
195
|
else
|
|
148
|
-
|
|
196
|
+
killPtyDirect('SIGKILL');
|
|
149
197
|
}
|
|
150
198
|
catch (error) {
|
|
151
199
|
ignoreMissingProcess(error);
|
|
@@ -163,6 +211,9 @@ export const attachAgentPty = (run, pty, ptyOutputBus) => {
|
|
|
163
211
|
},
|
|
164
212
|
pid: pty.pid,
|
|
165
213
|
resize(cols, rows) {
|
|
214
|
+
if (!Number.isInteger(cols) || !Number.isInteger(rows) || cols <= 0 || rows <= 0) {
|
|
215
|
+
throw new Error(`Invalid terminal size for run: ${run.runId}`);
|
|
216
|
+
}
|
|
166
217
|
pty.resize(cols, rows);
|
|
167
218
|
},
|
|
168
219
|
resume() {
|
|
@@ -173,6 +224,7 @@ export const attachAgentPty = (run, pty, ptyOutputBus) => {
|
|
|
173
224
|
cleanupProcessGroup();
|
|
174
225
|
return;
|
|
175
226
|
}
|
|
227
|
+
clearPtyReadEofTimer();
|
|
176
228
|
killPty('SIGTERM');
|
|
177
229
|
stdinClosed = true;
|
|
178
230
|
scheduleForceKill();
|
|
@@ -181,7 +233,7 @@ export const attachAgentPty = (run, pty, ptyOutputBus) => {
|
|
|
181
233
|
if (stdinClosed || run.status === 'exited' || run.status === 'error') {
|
|
182
234
|
throw new Error(`PTY is not active for run: ${run.runId}`);
|
|
183
235
|
}
|
|
184
|
-
pty.write(text);
|
|
236
|
+
pty.write(serializePtyInput(text));
|
|
185
237
|
},
|
|
186
238
|
};
|
|
187
239
|
pty.onData((chunk) => {
|
|
@@ -192,8 +244,30 @@ export const attachAgentPty = (run, pty, ptyOutputBus) => {
|
|
|
192
244
|
run.output = run.output.slice(-MAX_RUN_OUTPUT_LENGTH);
|
|
193
245
|
ptyOutputBus.publish(run.runId, chunk);
|
|
194
246
|
});
|
|
247
|
+
pty.on?.('error', (error) => {
|
|
248
|
+
if (stopped())
|
|
249
|
+
return;
|
|
250
|
+
if (isPtyReadEofError(error)) {
|
|
251
|
+
// Unix PTYs can surface a closed slave as read/EIO just before
|
|
252
|
+
// node-pty delivers the real onExit event. Treat it as EOF, not
|
|
253
|
+
// as the run's terminal status.
|
|
254
|
+
stdinClosed = true;
|
|
255
|
+
schedulePtyReadEofExitGuard(error);
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
console.error(`[hive] PTY error for run ${run.runId}`, error);
|
|
259
|
+
stdinClosed = true;
|
|
260
|
+
finishAgentRun(run, null, ptyOutputBus);
|
|
261
|
+
try {
|
|
262
|
+
killPty('SIGTERM');
|
|
263
|
+
}
|
|
264
|
+
catch (killError) {
|
|
265
|
+
ignoreMissingProcess(killError);
|
|
266
|
+
}
|
|
267
|
+
});
|
|
195
268
|
pty.onExit((event) => {
|
|
196
269
|
stdinClosed = true;
|
|
270
|
+
clearPtyReadEofTimer();
|
|
197
271
|
cleanupProcessGroup();
|
|
198
272
|
finishAgentRun(run, event.exitCode, ptyOutputBus);
|
|
199
273
|
});
|
|
@@ -18,6 +18,12 @@ interface AgentRunStarterInput {
|
|
|
18
18
|
getCommandPreset: (id: string) => CommandPresetRecord | undefined;
|
|
19
19
|
getAgent: ((workspaceId: string, agentId: string) => AgentSummary | undefined) | undefined;
|
|
20
20
|
restartPolicy: RestartPolicy;
|
|
21
|
+
/** Experimental workflow gate — gates the `team workflow` line + the
|
|
22
|
+
* workflow-authoring rule in the orchestrator's startup prompt. */
|
|
23
|
+
getWorkflowsEnabled?: () => boolean;
|
|
24
|
+
/** Experimental auto-staff gate (default on) — gates the team-sizing rule
|
|
25
|
+
* in the orchestrator's startup prompt. */
|
|
26
|
+
getAutostaffEnabled?: () => boolean;
|
|
21
27
|
}
|
|
22
|
-
export declare const createAgentRunStarter: ({ agentManager, registry, onAgentExit, store, sessionStore, tokenRegistry, getCommandPreset, getAgent, restartPolicy, }: AgentRunStarterInput) => (workspace: WorkspaceSummary, agentId: string, config: AgentLaunchConfigInput, hivePort: string) => Promise<LiveAgentRun>;
|
|
28
|
+
export declare const createAgentRunStarter: ({ agentManager, registry, onAgentExit, store, sessionStore, tokenRegistry, getCommandPreset, getAgent, restartPolicy, getWorkflowsEnabled, getAutostaffEnabled, }: AgentRunStarterInput) => (workspace: WorkspaceSummary, agentId: string, config: AgentLaunchConfigInput, hivePort: string) => Promise<LiveAgentRun>;
|
|
23
29
|
export {};
|
|
@@ -2,7 +2,7 @@ import { buildAgentRunBootstrap, startAgentRunCapture } from './agent-run-bootst
|
|
|
2
2
|
import { handleAgentRunExit } from './agent-run-exit-handler.js';
|
|
3
3
|
import { buildAgentStartupInstructions } from './agent-startup-instructions.js';
|
|
4
4
|
import { createPostStartInputWriter, isInteractiveAgentCommand } from './post-start-input-writer.js';
|
|
5
|
-
export const createAgentRunStarter = ({ agentManager, registry, onAgentExit, store, sessionStore, tokenRegistry, getCommandPreset, getAgent, restartPolicy, }) => async (workspace, agentId, config, hivePort) => {
|
|
5
|
+
export const createAgentRunStarter = ({ agentManager, registry, onAgentExit, store, sessionStore, tokenRegistry, getCommandPreset, getAgent, restartPolicy, getWorkflowsEnabled, getAutostaffEnabled, }) => async (workspace, agentId, config, hivePort) => {
|
|
6
6
|
if (!agentManager)
|
|
7
7
|
throw new Error('Agent manager is required to start agents');
|
|
8
8
|
const agent = getAgent?.(workspace.id, agentId);
|
|
@@ -99,7 +99,14 @@ export const createAgentRunStarter = ({ agentManager, registry, onAgentExit, sto
|
|
|
99
99
|
!injectedRestartMessage &&
|
|
100
100
|
agent &&
|
|
101
101
|
isInteractiveAgentCommand(startConfig.interactiveCommand ?? startConfig.command)) {
|
|
102
|
-
postStartWriter(run.runId, buildAgentStartupInstructions({
|
|
102
|
+
void postStartWriter(run.runId, buildAgentStartupInstructions({
|
|
103
|
+
agent,
|
|
104
|
+
workspace,
|
|
105
|
+
workflowsEnabled: getWorkflowsEnabled?.() ?? false,
|
|
106
|
+
autostaffEnabled: getAutostaffEnabled?.() ?? false,
|
|
107
|
+
})).catch(() => {
|
|
108
|
+
// The agent may have exited before post-start guidance could be written.
|
|
109
|
+
});
|
|
103
110
|
}
|
|
104
111
|
}
|
|
105
112
|
catch {
|
|
@@ -27,7 +27,7 @@ export declare const createAgentRunStore: (db: Database) => {
|
|
|
27
27
|
runId: string;
|
|
28
28
|
agentId: string;
|
|
29
29
|
pid: number | null;
|
|
30
|
-
status: "
|
|
30
|
+
status: "starting" | "running" | "exited" | "error";
|
|
31
31
|
exitCode: number | null;
|
|
32
32
|
startedAt: number;
|
|
33
33
|
endedAt: number | null;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { AgentManager } from './agent-manager.js';
|
|
2
2
|
import type { LiveAgentRun } from './agent-runtime-types.js';
|
|
3
3
|
import type { LiveRunRegistry } from './live-run-registry.js';
|
|
4
|
+
export declare const AGENT_RUNTIME_CLOSE_TIMEOUT_MS = 5000;
|
|
4
5
|
export declare const closeAgentRuntime: (agentManager: AgentManager | undefined, registry: LiveRunRegistry, syncRun: (run: LiveAgentRun) => LiveAgentRun) => Promise<void>;
|
|
@@ -1,10 +1,34 @@
|
|
|
1
|
+
export const AGENT_RUNTIME_CLOSE_TIMEOUT_MS = 5000;
|
|
2
|
+
const waitForExitEntries = async (entries, timeoutMs = AGENT_RUNTIME_CLOSE_TIMEOUT_MS) => {
|
|
3
|
+
if (entries.length === 0)
|
|
4
|
+
return;
|
|
5
|
+
let timer;
|
|
6
|
+
const timeout = new Promise((resolve) => {
|
|
7
|
+
timer = setTimeout(() => resolve('timeout'), timeoutMs);
|
|
8
|
+
timer.unref?.();
|
|
9
|
+
});
|
|
10
|
+
try {
|
|
11
|
+
const result = await Promise.race([
|
|
12
|
+
Promise.all(entries.map((entry) => entry.promise)).then(() => 'done'),
|
|
13
|
+
timeout,
|
|
14
|
+
]);
|
|
15
|
+
if (result === 'timeout') {
|
|
16
|
+
const runIds = entries.map((entry) => entry.runId).join(', ');
|
|
17
|
+
console.error(`[hive] timed out waiting for agent exit during shutdown: ${runIds}`);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
finally {
|
|
21
|
+
if (timer)
|
|
22
|
+
clearTimeout(timer);
|
|
23
|
+
}
|
|
24
|
+
};
|
|
1
25
|
export const closeAgentRuntime = async (agentManager, registry, syncRun) => {
|
|
2
26
|
const runs = registry.list();
|
|
3
27
|
for (const run of runs) {
|
|
4
28
|
syncRun(run);
|
|
5
29
|
agentManager?.stopRun(run.runId);
|
|
6
30
|
}
|
|
7
|
-
await
|
|
31
|
+
await waitForExitEntries(registry.listExitEntries());
|
|
8
32
|
for (const run of registry.list()) {
|
|
9
33
|
agentManager?.removeRun(run.runId);
|
|
10
34
|
registry.remove(run.runId);
|
|
@@ -28,10 +28,11 @@ export interface AgentRuntime {
|
|
|
28
28
|
writeStatusPrompt: (workspaceId: string, workerName: string, workerId: string, text: string, artifacts: string[], input?: {
|
|
29
29
|
requireActiveRun?: boolean;
|
|
30
30
|
}) => void;
|
|
31
|
-
writeSendPrompt: (workspaceId: string, workerId: string, dispatchId: string, fromAgentName: string, workerDescription: string, text: string) => void
|
|
31
|
+
writeSendPrompt: (workspaceId: string, workerId: string, dispatchId: string, fromAgentName: string, workerDescription: string, text: string) => Promise<void>;
|
|
32
32
|
writeCancelPrompt: (workspaceId: string, workerId: string, dispatchId: string, reason: string, input?: {
|
|
33
33
|
requireActiveRun?: boolean;
|
|
34
34
|
}) => void;
|
|
35
35
|
writeUserInputPrompt: (workspaceId: string, text: string) => void;
|
|
36
|
+
writeSystemMessageToAgent: (workspaceId: string, agentId: string, text: string) => void;
|
|
36
37
|
}
|
|
37
38
|
export type { StartAgentOptions };
|
|
@@ -4,5 +4,5 @@ import type { AgentRuntime } from './agent-runtime-contract.js';
|
|
|
4
4
|
import type { AgentRunStorePort, AgentSessionStorePort } from './agent-runtime-ports.js';
|
|
5
5
|
import type { CommandPresetRecord } from './command-preset-store.js';
|
|
6
6
|
import { type RestartPolicy } from './restart-policy.js';
|
|
7
|
-
export declare const createAgentRuntime: (agentManager: AgentManager | undefined, agentRunStore: AgentRunStorePort, sessionStore: AgentSessionStorePort, getCommandPreset: (id: string) => CommandPresetRecord | undefined, onAgentExit: (workspaceId: string, agentId: string) => void, restartPolicy?: RestartPolicy, getAgent?: (workspaceId: string, agentId: string) => AgentSummary | undefined) => AgentRuntime;
|
|
7
|
+
export declare const createAgentRuntime: (agentManager: AgentManager | undefined, agentRunStore: AgentRunStorePort, sessionStore: AgentSessionStorePort, getCommandPreset: (id: string) => CommandPresetRecord | undefined, onAgentExit: (workspaceId: string, agentId: string) => void, restartPolicy?: RestartPolicy, getAgent?: (workspaceId: string, agentId: string) => AgentSummary | undefined, getWorkflowsEnabled?: () => boolean, getAutostaffEnabled?: () => boolean) => AgentRuntime;
|
|
8
8
|
export type { AgentRuntime };
|
|
@@ -10,7 +10,7 @@ import { createAgentStdinDispatcher } from './agent-stdin-dispatcher.js';
|
|
|
10
10
|
import { createAgentTokenRegistry } from './agent-tokens.js';
|
|
11
11
|
import { createLiveRunRegistry } from './live-run-registry.js';
|
|
12
12
|
import { createNoopRestartPolicy } from './restart-policy.js';
|
|
13
|
-
export const createAgentRuntime = (agentManager, agentRunStore, sessionStore, getCommandPreset, onAgentExit, restartPolicy = createNoopRestartPolicy(), getAgent) => {
|
|
13
|
+
export const createAgentRuntime = (agentManager, agentRunStore, sessionStore, getCommandPreset, onAgentExit, restartPolicy = createNoopRestartPolicy(), getAgent, getWorkflowsEnabled, getAutostaffEnabled) => {
|
|
14
14
|
const registry = createLiveRunRegistry();
|
|
15
15
|
const launchCache = createAgentLaunchCache(agentRunStore);
|
|
16
16
|
const tokenRegistry = createAgentTokenRegistry();
|
|
@@ -29,6 +29,7 @@ export const createAgentRuntime = (agentManager, agentRunStore, sessionStore, ge
|
|
|
29
29
|
getWorkspaceId: launchCache.getWorkspaceId,
|
|
30
30
|
registry,
|
|
31
31
|
syncRun,
|
|
32
|
+
...(getWorkflowsEnabled ? { getWorkflowsEnabled } : {}),
|
|
32
33
|
});
|
|
33
34
|
const startLiveRun = createAgentRunStarter({
|
|
34
35
|
agentManager,
|
|
@@ -40,6 +41,8 @@ export const createAgentRuntime = (agentManager, agentRunStore, sessionStore, ge
|
|
|
40
41
|
getCommandPreset,
|
|
41
42
|
getAgent,
|
|
42
43
|
restartPolicy,
|
|
44
|
+
...(getWorkflowsEnabled ? { getWorkflowsEnabled } : {}),
|
|
45
|
+
...(getAutostaffEnabled ? { getAutostaffEnabled } : {}),
|
|
43
46
|
});
|
|
44
47
|
return {
|
|
45
48
|
async close() {
|
|
@@ -113,7 +116,7 @@ export const createAgentRuntime = (agentManager, agentRunStore, sessionStore, ge
|
|
|
113
116
|
stdinDispatcher.writeStatusPrompt(workspaceId, workerName, text, artifacts, input);
|
|
114
117
|
},
|
|
115
118
|
writeSendPrompt(workspaceId, workerId, dispatchId, fromAgentName, workerDescription, text) {
|
|
116
|
-
stdinDispatcher.writeSendPrompt(workspaceId, workerId, dispatchId, fromAgentName, workerDescription, text);
|
|
119
|
+
return stdinDispatcher.writeSendPrompt(workspaceId, workerId, dispatchId, fromAgentName, workerDescription, text);
|
|
117
120
|
},
|
|
118
121
|
writeCancelPrompt(workspaceId, workerId, dispatchId, reason, input = {}) {
|
|
119
122
|
stdinDispatcher.writeCancelPrompt(workspaceId, workerId, dispatchId, reason, input);
|
|
@@ -121,5 +124,8 @@ export const createAgentRuntime = (agentManager, agentRunStore, sessionStore, ge
|
|
|
121
124
|
writeUserInputPrompt(workspaceId, text) {
|
|
122
125
|
stdinDispatcher.writeUserInputPrompt(workspaceId, text);
|
|
123
126
|
},
|
|
127
|
+
writeSystemMessageToAgent(workspaceId, agentId, text) {
|
|
128
|
+
stdinDispatcher.writeSystemMessageToAgent(workspaceId, agentId, text);
|
|
129
|
+
},
|
|
124
130
|
};
|
|
125
131
|
};
|
|
@@ -7,7 +7,14 @@ export declare const buildAgentLegacyIdentityMarker: ({ agent, workspace, }: {
|
|
|
7
7
|
agent: AgentSummary;
|
|
8
8
|
workspace: WorkspaceSummary;
|
|
9
9
|
}) => string;
|
|
10
|
-
export declare const buildAgentStartupInstructions: ({ agent, workspace, }: {
|
|
10
|
+
export declare const buildAgentStartupInstructions: ({ agent, workspace, workflowsEnabled, autostaffEnabled, }: {
|
|
11
11
|
agent: AgentSummary;
|
|
12
12
|
workspace: WorkspaceSummary;
|
|
13
|
+
/** Experimental workflow gate. When off, the startup prompt omits the
|
|
14
|
+
* `team workflow` command + the workflow-authoring rule so a fresh
|
|
15
|
+
* orchestrator isn't told to run a command the runtime rejects. */
|
|
16
|
+
workflowsEnabled?: boolean;
|
|
17
|
+
/** Experimental auto-staff gate (default on). When on, the orchestrator is
|
|
18
|
+
* told it may size the team to the task up front. */
|
|
19
|
+
autostaffEnabled?: boolean;
|
|
13
20
|
}) => string;
|
|
@@ -1,25 +1,31 @@
|
|
|
1
1
|
import { getHiveTeamRules } from './hive-team-guidance.js';
|
|
2
2
|
import { TASKS_RELATIVE_PATH } from './tasks-file.js';
|
|
3
3
|
export const buildAgentSessionBindingMarker = ({ agent, workspace, }) => `Hive session binding: workspace_id=${workspace.id}; agent_id=${agent.id}`;
|
|
4
|
-
export const buildAgentLegacyIdentityMarker = ({ agent, workspace, }) =>
|
|
5
|
-
export const buildAgentStartupInstructions = ({ agent, workspace, }) => {
|
|
4
|
+
export const buildAgentLegacyIdentityMarker = ({ agent, workspace, }) => `You are ${agent.name} (${agent.role}) in workspace ${workspace.name}.`;
|
|
5
|
+
export const buildAgentStartupInstructions = ({ agent, workspace, workflowsEnabled = false, autostaffEnabled = false, }) => {
|
|
6
6
|
const lines = [
|
|
7
|
-
'
|
|
7
|
+
'<hive-message kind="startup">',
|
|
8
8
|
'',
|
|
9
9
|
buildAgentLegacyIdentityMarker({ agent, workspace }),
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
`Current workspace: ${workspace.name}`,
|
|
11
|
+
`Project path: ${workspace.path}`,
|
|
12
12
|
buildAgentSessionBindingMarker({ agent, workspace }),
|
|
13
13
|
'',
|
|
14
|
-
|
|
14
|
+
// agent.description is the role contract — it follows the user's chosen
|
|
15
|
+
// language / custom edit and is intentionally left as authored.
|
|
16
|
+
`Your role: ${agent.description}`,
|
|
15
17
|
'',
|
|
16
18
|
];
|
|
17
19
|
if (agent.role === 'orchestrator') {
|
|
18
|
-
lines.push('
|
|
20
|
+
lines.push('Your responsibilities:', '- Respond to the user directly; clarify the goal and break it into dispatchable tasks', `- Maintain ${TASKS_RELATIVE_PATH}`, '- Dispatch by worker name and drive the next step from each report', '', 'Available team commands:', '- team list', '- team send "<worker-name>" "<task>"', '- team spawn <role> [--name <n>] [--cli claude|codex|opencode|gemini] [--ephemeral]', '- team cancel --dispatch <id> "<reason>"', ...(workflowsEnabled
|
|
21
|
+
? [
|
|
22
|
+
'- team workflow run --stdin (fan-out / staged work — see .hive/PROTOCOL.md for the DSL)',
|
|
23
|
+
]
|
|
24
|
+
: []), '', 'Always dispatch by worker name, never by worker id.', 'Always cancel an open dispatch by its dispatch id.', '', 'Hive worker dispatch rules:', ...getHiveTeamRules(agent, workflowsEnabled, autostaffEnabled));
|
|
19
25
|
}
|
|
20
26
|
else {
|
|
21
|
-
lines.push('
|
|
27
|
+
lines.push('Available team commands:', '- team report "<result>" [--dispatch <id>] [--artifact <path>] report done / failed / blocked', '- team report --stdin [--dispatch <id>] [--artifact <path>] same, body read from stdin (multi-line / quotes / special chars)', '- team status "<state>" [--artifact <path>] mid-task progress / standby / connected status', '- team status --stdin [--artifact <path>] same, body read from stdin', '- team list list the workspace workers (with status)', '- team --help command syntax only; NOT a way to report', '', 'Syntax notes:', '- The body is the first positional argument; flag order is free: `team report "result" --dispatch X` and `team report --dispatch X "result"` are both valid.', "- Long bodies (multi-line / quotes / shell metacharacters) always go through `--stdin`, piped in via your shell (POSIX: a quoted heredoc `<<'EOF' … EOF` to stop $var / backtick expansion; Windows cmd: `type body.txt |` or `< body.txt`). `--stdin` only reads from stdin and relies on no shell syntax of its own.", '- On error the CLI also prints USAGE — fix the arguments against it.', '', 'When a task is done you MUST run `team report "<result>"`.', 'Failure, blocked, or partial completion are also reported with `team report "<current state and reason>"`.', 'When no task is in progress, report connection / standby / blocked state with `team status "<state>"`.', 'Do not call team send; workers cannot dispatch to each other.', '', 'Hive worker boundaries:', ...getHiveTeamRules(agent, workflowsEnabled, autostaffEnabled));
|
|
22
28
|
}
|
|
23
|
-
lines.push('');
|
|
29
|
+
lines.push('', '</hive-message>', '');
|
|
24
30
|
return lines.join('\n');
|
|
25
31
|
};
|
|
@@ -8,23 +8,30 @@ interface AgentStdinDispatcherInput {
|
|
|
8
8
|
getWorkspaceId: (agentId: string) => string | undefined;
|
|
9
9
|
registry: LiveRunRegistry;
|
|
10
10
|
syncRun: (run: LiveAgentRun) => LiveAgentRun;
|
|
11
|
+
/** Whether the experimental workflow feature is on. Controls whether the
|
|
12
|
+
* orchestrator reminder tail mentions `team workflow`. Defaults to off. */
|
|
13
|
+
getWorkflowsEnabled?: () => boolean;
|
|
11
14
|
}
|
|
12
|
-
export declare const buildOrchestratorReportPayload: (workerName: string, text: string, artifacts: string[]) => string;
|
|
13
|
-
export declare const buildOrchestratorStatusPayload: (workerName: string, text: string, artifacts: string[]) => string;
|
|
14
|
-
export declare const buildOrchestratorUserInputPayload: (text: string) => string;
|
|
15
|
+
export declare const buildOrchestratorReportPayload: (workerName: string, text: string, artifacts: string[], workflowsEnabled?: boolean) => string;
|
|
16
|
+
export declare const buildOrchestratorStatusPayload: (workerName: string, text: string, artifacts: string[], workflowsEnabled?: boolean) => string;
|
|
17
|
+
export declare const buildOrchestratorUserInputPayload: (text: string, workflowsEnabled?: boolean) => string;
|
|
15
18
|
export declare const buildWorkerDispatchPayload: (fromAgentName: string, workerDescription: string, dispatchId: string, text: string) => string;
|
|
16
19
|
export declare const buildWorkerCancelPayload: (dispatchId: string, reason: string) => string;
|
|
17
|
-
export declare const createAgentStdinDispatcher: ({ agentManager, getLaunchConfig, getWorkspaceId, registry, syncRun, }: AgentStdinDispatcherInput) => {
|
|
20
|
+
export declare const createAgentStdinDispatcher: ({ agentManager, getLaunchConfig, getWorkspaceId, registry, syncRun, getWorkflowsEnabled, }: AgentStdinDispatcherInput) => {
|
|
18
21
|
writeReportPrompt(workspaceId: string, workerName: string, text: string, artifacts: string[], input?: {
|
|
19
22
|
requireActiveRun?: boolean;
|
|
20
23
|
}): void;
|
|
21
24
|
writeStatusPrompt(workspaceId: string, workerName: string, text: string, artifacts: string[], input?: {
|
|
22
25
|
requireActiveRun?: boolean;
|
|
23
26
|
}): void;
|
|
24
|
-
writeSendPrompt(workspaceId: string, workerId: string, dispatchId: string, fromAgentName: string, workerDescription: string, text: string): void
|
|
27
|
+
writeSendPrompt(workspaceId: string, workerId: string, dispatchId: string, fromAgentName: string, workerDescription: string, text: string): Promise<void>;
|
|
25
28
|
writeCancelPrompt(workspaceId: string, workerId: string, dispatchId: string, reason: string, input?: {
|
|
26
29
|
requireActiveRun?: boolean;
|
|
27
30
|
}): void;
|
|
28
31
|
writeUserInputPrompt(workspaceId: string, text: string): void;
|
|
32
|
+
/** Generic: deliver an opaque text block to a specific agent's PTY.
|
|
33
|
+
* Used by the workflow runner to notify the triggering orchestrator
|
|
34
|
+
* when a run finishes (mirrors Claude Code's <task-notification>). */
|
|
35
|
+
writeSystemMessageToAgent(workspaceId: string, agentId: string, text: string): void;
|
|
29
36
|
};
|
|
30
37
|
export {};
|