@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
|
@@ -6,19 +6,27 @@ export interface DispatchRecord {
|
|
|
6
6
|
deliveredAt: number | null;
|
|
7
7
|
fromAgentId: string | null;
|
|
8
8
|
id: string;
|
|
9
|
+
label: string | null;
|
|
10
|
+
phase: string | null;
|
|
9
11
|
reportedAt: number | null;
|
|
10
12
|
reportText: string | null;
|
|
11
13
|
sequence: number | null;
|
|
12
14
|
status: DispatchStatus;
|
|
15
|
+
stepIndex: number | null;
|
|
13
16
|
submittedAt: number | null;
|
|
14
17
|
text: string;
|
|
15
18
|
toAgentId: string;
|
|
19
|
+
workflowRunId: string | null;
|
|
16
20
|
workspaceId: string;
|
|
17
21
|
}
|
|
18
22
|
interface CreateDispatchInput {
|
|
19
23
|
fromAgentId?: string;
|
|
24
|
+
label?: string;
|
|
25
|
+
phase?: string;
|
|
26
|
+
stepIndex?: number;
|
|
20
27
|
text: string;
|
|
21
28
|
toAgentId: string;
|
|
29
|
+
workflowRunId?: string;
|
|
22
30
|
workspaceId: string;
|
|
23
31
|
}
|
|
24
32
|
interface ReportDispatchInput {
|
|
@@ -50,6 +58,12 @@ export declare const createDispatchLedgerStore: (db: Database) => {
|
|
|
50
58
|
worker_id: string;
|
|
51
59
|
workspace_id: string;
|
|
52
60
|
}>;
|
|
61
|
+
listOpenDispatchIdsForRun: (runId: string) => string[];
|
|
62
|
+
listOpenWorkflowDispatchesForWorker: (workspaceId: string, workerId: string) => Array<{
|
|
63
|
+
dispatchId: string;
|
|
64
|
+
runId: string;
|
|
65
|
+
}>;
|
|
66
|
+
listWorkflowRunDispatches: (runId: string) => DispatchRecord[];
|
|
53
67
|
listWorkspaceDispatches: (workspaceId: string, options?: ListDispatchesOptions) => DispatchRecord[];
|
|
54
68
|
markCancelled: (input: CancelDispatchInput) => {
|
|
55
69
|
reportedAt: number;
|
|
@@ -60,10 +74,14 @@ export declare const createDispatchLedgerStore: (db: Database) => {
|
|
|
60
74
|
deliveredAt: number | null;
|
|
61
75
|
fromAgentId: string | null;
|
|
62
76
|
id: string;
|
|
77
|
+
label: string | null;
|
|
78
|
+
phase: string | null;
|
|
63
79
|
sequence: number | null;
|
|
80
|
+
stepIndex: number | null;
|
|
64
81
|
submittedAt: number | null;
|
|
65
82
|
text: string;
|
|
66
83
|
toAgentId: string;
|
|
84
|
+
workflowRunId: string | null;
|
|
67
85
|
workspaceId: string;
|
|
68
86
|
} | undefined;
|
|
69
87
|
markReportedByWorker: (input: ReportDispatchInput) => {
|
|
@@ -75,10 +93,14 @@ export declare const createDispatchLedgerStore: (db: Database) => {
|
|
|
75
93
|
deliveredAt: number | null;
|
|
76
94
|
fromAgentId: string | null;
|
|
77
95
|
id: string;
|
|
96
|
+
label: string | null;
|
|
97
|
+
phase: string | null;
|
|
78
98
|
sequence: number | null;
|
|
99
|
+
stepIndex: number | null;
|
|
79
100
|
submittedAt: number | null;
|
|
80
101
|
text: string;
|
|
81
102
|
toAgentId: string;
|
|
103
|
+
workflowRunId: string | null;
|
|
82
104
|
workspaceId: string;
|
|
83
105
|
} | undefined;
|
|
84
106
|
markSubmitted: (dispatchId: string) => void;
|
|
@@ -18,13 +18,17 @@ const toRecord = (row) => ({
|
|
|
18
18
|
deliveredAt: row.delivered_at,
|
|
19
19
|
fromAgentId: row.from_agent_id,
|
|
20
20
|
id: row.id,
|
|
21
|
+
label: row.label ?? null,
|
|
22
|
+
phase: row.phase ?? null,
|
|
21
23
|
reportedAt: row.reported_at,
|
|
22
24
|
reportText: row.report_text,
|
|
23
25
|
sequence: row.sequence,
|
|
24
26
|
status: row.status,
|
|
27
|
+
stepIndex: row.step_index,
|
|
25
28
|
submittedAt: row.submitted_at,
|
|
26
29
|
text: row.text,
|
|
27
30
|
toAgentId: row.to_agent_id,
|
|
31
|
+
workflowRunId: row.workflow_run_id,
|
|
28
32
|
workspaceId: row.workspace_id,
|
|
29
33
|
});
|
|
30
34
|
export const createDispatchLedgerStore = (db) => {
|
|
@@ -35,13 +39,17 @@ export const createDispatchLedgerStore = (db) => {
|
|
|
35
39
|
deliveredAt: null,
|
|
36
40
|
fromAgentId: input.fromAgentId ?? null,
|
|
37
41
|
id: randomUUID(),
|
|
42
|
+
label: input.label ?? null,
|
|
43
|
+
phase: input.phase ?? null,
|
|
38
44
|
reportedAt: null,
|
|
39
45
|
reportText: null,
|
|
40
46
|
sequence: null,
|
|
41
47
|
status: 'queued',
|
|
48
|
+
stepIndex: input.stepIndex ?? null,
|
|
42
49
|
submittedAt: null,
|
|
43
50
|
text: input.text,
|
|
44
51
|
toAgentId: input.toAgentId,
|
|
52
|
+
workflowRunId: input.workflowRunId ?? null,
|
|
45
53
|
workspaceId: input.workspaceId,
|
|
46
54
|
};
|
|
47
55
|
db.prepare(`INSERT INTO dispatches (
|
|
@@ -56,8 +64,12 @@ export const createDispatchLedgerStore = (db) => {
|
|
|
56
64
|
submitted_at,
|
|
57
65
|
reported_at,
|
|
58
66
|
report_text,
|
|
59
|
-
artifacts
|
|
60
|
-
|
|
67
|
+
artifacts,
|
|
68
|
+
workflow_run_id,
|
|
69
|
+
step_index,
|
|
70
|
+
phase,
|
|
71
|
+
label
|
|
72
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(record.id, record.workspaceId, record.fromAgentId, record.toAgentId, record.text, record.status, record.createdAt, record.deliveredAt, record.submittedAt, record.reportedAt, record.reportText, JSON.stringify(record.artifacts), record.workflowRunId, record.stepIndex, record.phase, record.label);
|
|
61
73
|
return record;
|
|
62
74
|
};
|
|
63
75
|
const deleteDispatch = (dispatchId) => {
|
|
@@ -175,8 +187,41 @@ export const createDispatchLedgerStore = (db) => {
|
|
|
175
187
|
db.prepare('DELETE FROM dispatches WHERE workspace_id = ?').run(workspaceId);
|
|
176
188
|
};
|
|
177
189
|
const deleteWorkerDispatches = (workspaceId, workerId) => {
|
|
178
|
-
|
|
190
|
+
// Preserve workflow-run dispatch history (`workflow_run_id IS NOT NULL`)
|
|
191
|
+
// so the UI's run-detail timeline can still show what each ephemeral
|
|
192
|
+
// worker did after the worker itself is dismissed. Orchestrator-issued
|
|
193
|
+
// dispatches are tied to the worker identity and are dropped with it.
|
|
194
|
+
db.prepare(`DELETE FROM dispatches
|
|
195
|
+
WHERE workspace_id = ? AND to_agent_id = ? AND workflow_run_id IS NULL`).run(workspaceId, workerId);
|
|
179
196
|
};
|
|
197
|
+
// Every dispatch fired by a workflow run carries the run id (M1-B added the
|
|
198
|
+
// column; M2-C plumbs it through). This is the timeline query the UI uses to
|
|
199
|
+
// explode a run row into per-worker activity.
|
|
200
|
+
const listWorkflowRunDispatches = (runId) => {
|
|
201
|
+
const rows = db
|
|
202
|
+
.prepare('SELECT * FROM dispatches WHERE workflow_run_id = ? ORDER BY sequence, created_at')
|
|
203
|
+
.all(runId);
|
|
204
|
+
return rows.map(toRecord);
|
|
205
|
+
};
|
|
206
|
+
// Open dispatch ids tied to a workflow run — drives the runner's stop path
|
|
207
|
+
// (each id gets a notifyCancel so the runner's await rejects).
|
|
208
|
+
const listOpenDispatchIdsForRun = (runId) => db
|
|
209
|
+
.prepare(`SELECT id FROM dispatches
|
|
210
|
+
WHERE workflow_run_id = ? AND status IN ('queued', 'submitted')`)
|
|
211
|
+
.all(runId).map((row) => row.id);
|
|
212
|
+
// Open workflow-tagged dispatches addressed to a specific worker. Drives the
|
|
213
|
+
// PTY-exit cancel path (TIER 1 #1): when a workflow-spawned worker dies
|
|
214
|
+
// without calling `team report`, the runner's `awaitReport` would otherwise
|
|
215
|
+
// hang for DEFAULT_TIMEOUT_MS (10 min). The exit handler enumerates these
|
|
216
|
+
// and `notifyCancel`s each so the surrounding `agent()`/`parallel`/`pipeline`
|
|
217
|
+
// sees an immediate reject.
|
|
218
|
+
const listOpenWorkflowDispatchesForWorker = (workspaceId, workerId) => db
|
|
219
|
+
.prepare(`SELECT id, workflow_run_id FROM dispatches
|
|
220
|
+
WHERE workspace_id = ?
|
|
221
|
+
AND to_agent_id = ?
|
|
222
|
+
AND workflow_run_id IS NOT NULL
|
|
223
|
+
AND status IN ('queued', 'submitted')`)
|
|
224
|
+
.all(workspaceId, workerId).map((row) => ({ dispatchId: row.id, runId: row.workflow_run_id }));
|
|
180
225
|
return {
|
|
181
226
|
createDispatch,
|
|
182
227
|
deleteDispatch,
|
|
@@ -185,6 +230,9 @@ export const createDispatchLedgerStore = (db) => {
|
|
|
185
230
|
findOpenDispatch,
|
|
186
231
|
findOpenDispatchById,
|
|
187
232
|
listOpenDispatchKinds,
|
|
233
|
+
listOpenDispatchIdsForRun,
|
|
234
|
+
listOpenWorkflowDispatchesForWorker,
|
|
235
|
+
listWorkflowRunDispatches,
|
|
188
236
|
listWorkspaceDispatches,
|
|
189
237
|
markCancelled,
|
|
190
238
|
markReportedByWorker,
|
|
@@ -4,7 +4,7 @@ import { TASKS_RELATIVE_PATH } from './tasks-file.js';
|
|
|
4
4
|
const TASKS_HEAD_LIMIT = 1024;
|
|
5
5
|
const formatWorkers = (workers) => {
|
|
6
6
|
if (workers.length === 0)
|
|
7
|
-
return ['-
|
|
7
|
+
return ['- (no other workers)'];
|
|
8
8
|
return workers.map((worker) => `- ${worker.name} (${worker.role}, ${worker.status}, pending_task_count: ${worker.pendingTaskCount})`);
|
|
9
9
|
};
|
|
10
10
|
const formatRestartWindow = (messages) => {
|
|
@@ -12,18 +12,18 @@ const formatRestartWindow = (messages) => {
|
|
|
12
12
|
return message.type === 'send';
|
|
13
13
|
});
|
|
14
14
|
if (sends.length === 0)
|
|
15
|
-
return ['-
|
|
15
|
+
return ['- no new dispatches during the restart'];
|
|
16
16
|
return sends.slice(-5).map((message) => `- send -> ${message.to}: ${message.text}`);
|
|
17
17
|
};
|
|
18
18
|
export const buildEnvSyncMessage = ({ agent, tasksContent, workers, workspace, restartWindowMessages, }) => wrapSystemMessage([
|
|
19
|
-
'
|
|
20
|
-
`-
|
|
21
|
-
'-
|
|
19
|
+
'Hive just restarted you. Environment changes during the restart:',
|
|
20
|
+
`- Current workspace: ${workspace.name}`,
|
|
21
|
+
'- Existing workers:',
|
|
22
22
|
...formatWorkers(workers),
|
|
23
|
-
`- ${TASKS_RELATIVE_PATH}
|
|
24
|
-
tasksContent.slice(0, TASKS_HEAD_LIMIT) || '(
|
|
23
|
+
`- Current ${TASKS_RELATIVE_PATH} contents:`,
|
|
24
|
+
tasksContent.slice(0, TASKS_HEAD_LIMIT) || '(empty)',
|
|
25
25
|
...formatRestartWindow(restartWindowMessages),
|
|
26
|
-
agent.role === 'orchestrator' ? '- Hive worker
|
|
26
|
+
agent.role === 'orchestrator' ? '- Hive worker dispatch rules:' : '- Hive worker boundaries:',
|
|
27
27
|
...getHiveTeamRules(agent).map((rule) => ` - ${rule}`),
|
|
28
|
-
|
|
28
|
+
`Continue. If unsure, run team list / Read ${TASKS_RELATIVE_PATH} to self-check, or ask the user.`,
|
|
29
29
|
].join('\n'));
|
|
@@ -22,4 +22,17 @@ export interface FsProbeResponse {
|
|
|
22
22
|
suggested_name: string;
|
|
23
23
|
}
|
|
24
24
|
export declare const browseDirectory: (requestedPath: string) => Promise<FsBrowseResponse>;
|
|
25
|
-
export
|
|
25
|
+
export interface ProbeDirectoryOptions {
|
|
26
|
+
/**
|
|
27
|
+
* When `true` (default), probe rejects paths outside `$HOME` so the
|
|
28
|
+
* in-browser FS tree can't be tricked into revealing arbitrary disk
|
|
29
|
+
* contents via a hand-crafted path string.
|
|
30
|
+
*
|
|
31
|
+
* When `false`, the sandbox check is skipped — callers who already
|
|
32
|
+
* have a user-authorized path (e.g. paths returned by the OS-native
|
|
33
|
+
* folder picker) must use this so a Windows user picking `D:\projects`
|
|
34
|
+
* isn't rejected just because their `$HOME` lives on `C:`.
|
|
35
|
+
*/
|
|
36
|
+
enforceSandbox?: boolean;
|
|
37
|
+
}
|
|
38
|
+
export declare const probeDirectory: (requestedPath: string, options?: ProbeDirectoryOptions) => Promise<FsProbeResponse>;
|
|
@@ -5,6 +5,29 @@ import { promisify } from 'node:util';
|
|
|
5
5
|
import { getFsBrowseRoot, isPathWithinRoot } from './fs-sandbox.js';
|
|
6
6
|
const execFileP = promisify(execFile);
|
|
7
7
|
const GIT_BRANCH_TIMEOUT_MS = 800;
|
|
8
|
+
/**
|
|
9
|
+
* Map a filesystem rejection (from `readdir`, `stat`, etc.) to a string
|
|
10
|
+
* suitable for surfacing in the browse response. The common Windows
|
|
11
|
+
* failure paths — System Volume Information / $Recycle.Bin (EACCES),
|
|
12
|
+
* dangling junctions (EBUSY / EINVAL), paths past MAX_PATH on systems
|
|
13
|
+
* without long-path support (ENAMETOOLONG) — each get a recognizable
|
|
14
|
+
* prefix so the UI does not just show the raw errno.
|
|
15
|
+
*/
|
|
16
|
+
const formatFilesystemError = (error) => {
|
|
17
|
+
if (!(error instanceof Error))
|
|
18
|
+
return 'Failed to read directory';
|
|
19
|
+
const code = error.code;
|
|
20
|
+
if (code === 'EACCES' || code === 'EPERM') {
|
|
21
|
+
return `Permission denied: ${error.message}`;
|
|
22
|
+
}
|
|
23
|
+
if (code === 'ENAMETOOLONG') {
|
|
24
|
+
return `Path is too long for this filesystem: ${error.message}`;
|
|
25
|
+
}
|
|
26
|
+
if (code === 'EBUSY' || code === 'EINVAL') {
|
|
27
|
+
return `Path is busy or unavailable: ${error.message}`;
|
|
28
|
+
}
|
|
29
|
+
return error.message;
|
|
30
|
+
};
|
|
8
31
|
const detectGitRepository = async (entryPath) => {
|
|
9
32
|
try {
|
|
10
33
|
const info = await stat(resolve(entryPath, '.git'));
|
|
@@ -49,7 +72,7 @@ export const browseDirectory = async (requestedPath) => {
|
|
|
49
72
|
return {
|
|
50
73
|
current_path: candidate,
|
|
51
74
|
entries: [],
|
|
52
|
-
error: error
|
|
75
|
+
error: formatFilesystemError(error),
|
|
53
76
|
ok: false,
|
|
54
77
|
parent_path: null,
|
|
55
78
|
root_path: rootPath,
|
|
@@ -65,7 +88,24 @@ export const browseDirectory = async (requestedPath) => {
|
|
|
65
88
|
root_path: rootPath,
|
|
66
89
|
};
|
|
67
90
|
}
|
|
68
|
-
|
|
91
|
+
let rawEntries;
|
|
92
|
+
try {
|
|
93
|
+
rawEntries = await readdir(candidate, { withFileTypes: true });
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
// Windows hits this for System Volume Information, $Recycle.Bin,
|
|
97
|
+
// broken junctions, and long paths on hosts without long-path
|
|
98
|
+
// support. Returning ok:false lets the picker surface a readable
|
|
99
|
+
// message instead of crashing the HTTP handler.
|
|
100
|
+
return {
|
|
101
|
+
current_path: candidate,
|
|
102
|
+
entries: [],
|
|
103
|
+
error: formatFilesystemError(error),
|
|
104
|
+
ok: false,
|
|
105
|
+
parent_path: null,
|
|
106
|
+
root_path: rootPath,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
69
109
|
const directoryEntries = rawEntries
|
|
70
110
|
.filter((entry) => entry.isDirectory() && !entry.name.startsWith('.'))
|
|
71
111
|
.sort((a, b) => a.name.localeCompare(b.name));
|
|
@@ -91,9 +131,12 @@ export const browseDirectory = async (requestedPath) => {
|
|
|
91
131
|
root_path: rootPath,
|
|
92
132
|
};
|
|
93
133
|
};
|
|
94
|
-
export const probeDirectory = async (requestedPath) => {
|
|
134
|
+
export const probeDirectory = async (requestedPath, options = {}) => {
|
|
135
|
+
const enforceSandbox = options.enforceSandbox ?? true;
|
|
95
136
|
const rootPath = getFsBrowseRoot();
|
|
96
|
-
const candidate =
|
|
137
|
+
const candidate = enforceSandbox
|
|
138
|
+
? resolve(rootPath, requestedPath.trim())
|
|
139
|
+
: resolve(requestedPath.trim());
|
|
97
140
|
const base = {
|
|
98
141
|
current_branch: null,
|
|
99
142
|
exists: false,
|
|
@@ -103,7 +146,7 @@ export const probeDirectory = async (requestedPath) => {
|
|
|
103
146
|
path: candidate,
|
|
104
147
|
suggested_name: candidate.split(/[\\/]/).filter(Boolean).pop() ?? '',
|
|
105
148
|
};
|
|
106
|
-
if (!isPathWithinRoot(rootPath, candidate)) {
|
|
149
|
+
if (enforceSandbox && !isPathWithinRoot(rootPath, candidate)) {
|
|
107
150
|
return base;
|
|
108
151
|
}
|
|
109
152
|
try {
|
|
@@ -5,6 +5,13 @@ import { probeDirectory } from './fs-browse.js';
|
|
|
5
5
|
const MACOS_CANCEL_PATTERNS = [/-128/, /-1743/, /user canceled/i, /execution error/i];
|
|
6
6
|
// zenity documents exit code 1 on Cancel. kdialog uses exit code 1 as well.
|
|
7
7
|
const LINUX_CANCEL_EXIT_CODES = new Set([1]);
|
|
8
|
+
// Cap how long we'll wait for a single picker invocation. A reasonable
|
|
9
|
+
// modal-dialog dwell time is well under this — the cap exists to catch
|
|
10
|
+
// genuinely wedged pickers (PowerShell startup hang under restricted
|
|
11
|
+
// execution policy, zenity hung on a missing DBus, osascript blocked on
|
|
12
|
+
// the macOS Accessibility prompt) so the HTTP request returns instead
|
|
13
|
+
// of pinning a connection forever.
|
|
14
|
+
const PICKER_TIMEOUT_MS = 5 * 60 * 1000;
|
|
8
15
|
const defaultRunCommand = (command, args, options) => new Promise((resolve) => {
|
|
9
16
|
const child = execFile(command, args, options, (error, stdout, stderr) => {
|
|
10
17
|
const errno = error;
|
|
@@ -30,10 +37,15 @@ const emptyResponse = (overrides = {}) => ({
|
|
|
30
37
|
...overrides,
|
|
31
38
|
});
|
|
32
39
|
const finalizeWithProbe = async (path) => {
|
|
33
|
-
|
|
40
|
+
// The OS-native folder picker is itself a user-authorization surface
|
|
41
|
+
// — sandboxing again here would reject any drive other than the one
|
|
42
|
+
// hosting `$HOME` (a common Windows case: `D:\projects`, `E:\code`).
|
|
43
|
+
// The in-browser FS tree (fs-browse.ts:browseDirectory) keeps its
|
|
44
|
+
// own sandbox; only the native picker bypasses it.
|
|
45
|
+
const probe = await probeDirectory(path, { enforceSandbox: false });
|
|
34
46
|
if (!probe.ok || !probe.is_dir) {
|
|
35
47
|
return emptyResponse({
|
|
36
|
-
error: 'Selected path is
|
|
48
|
+
error: 'Selected path is not a directory.',
|
|
37
49
|
path,
|
|
38
50
|
probe,
|
|
39
51
|
});
|
|
@@ -42,7 +54,7 @@ const finalizeWithProbe = async (path) => {
|
|
|
42
54
|
};
|
|
43
55
|
const macOsPick = async (run) => {
|
|
44
56
|
const script = 'POSIX path of (choose folder with prompt "Select Hive workspace")';
|
|
45
|
-
const result = await run('osascript', ['-e', script], {});
|
|
57
|
+
const result = await run('osascript', ['-e', script], { timeout: PICKER_TIMEOUT_MS });
|
|
46
58
|
if (result.spawnError?.code === 'ENOENT') {
|
|
47
59
|
return emptyResponse({ error: 'osascript is unavailable on this host.', supported: false });
|
|
48
60
|
}
|
|
@@ -65,7 +77,7 @@ const macOsPick = async (run) => {
|
|
|
65
77
|
return finalizeWithProbe(picked);
|
|
66
78
|
};
|
|
67
79
|
const linuxPick = async (run) => {
|
|
68
|
-
const result = await run('zenity', ['--file-selection', '--directory', '--title=Select Hive workspace'], {});
|
|
80
|
+
const result = await run('zenity', ['--file-selection', '--directory', '--title=Select Hive workspace'], { timeout: PICKER_TIMEOUT_MS });
|
|
69
81
|
if (result.spawnError?.code === 'ENOENT') {
|
|
70
82
|
return emptyResponse({
|
|
71
83
|
error: 'zenity not installed. Install zenity or use Advanced: paste path.',
|
|
@@ -84,16 +96,51 @@ const linuxPick = async (run) => {
|
|
|
84
96
|
return finalizeWithProbe(picked);
|
|
85
97
|
};
|
|
86
98
|
const windowsPick = async (run) => {
|
|
99
|
+
/* Hive's PowerShell child has no visible main window, so a bare
|
|
100
|
+
`$dialog.ShowDialog()` inherits the desktop as IWin32Window parent —
|
|
101
|
+
and ends up below the foreground browser in z-order. To the user
|
|
102
|
+
that looks like a hang ("Add Workspace pops up then nothing"); the
|
|
103
|
+
picker is open, just occluded.
|
|
104
|
+
|
|
105
|
+
Fix: build a TopMost invisible owner Form, `Show()` it so it has a
|
|
106
|
+
real HWND (an unshown Form has none, and ShowDialog silently falls
|
|
107
|
+
back to desktop-parent), then pass the owner to `ShowDialog($owner)`.
|
|
108
|
+
The owner inherits TopMost z-order onto the dialog. The owner itself
|
|
109
|
+
stays invisible — Opacity 0, parked at (-32000, -32000), 1x1 size,
|
|
110
|
+
no taskbar entry — so the user only sees the picker. Dispose in
|
|
111
|
+
`finally` to release the HWND each invocation.
|
|
112
|
+
|
|
113
|
+
`Add-Type -AssemblyName System.Drawing` is required because Point /
|
|
114
|
+
Size live in System.Drawing.dll, not System.Windows.Forms.dll. */
|
|
115
|
+
// PS 5.1 on zh-CN Windows defaults [Console]::OutputEncoding to cp936/GBK;
|
|
116
|
+
// Node decodes stdout as UTF-8 and CJK paths arrive mojibake'd. Force UTF-8
|
|
117
|
+
// before any output. No-op on PS 7+ which already defaults to UTF-8.
|
|
87
118
|
const script = [
|
|
119
|
+
'[Console]::OutputEncoding = [System.Text.Encoding]::UTF8',
|
|
88
120
|
'Add-Type -AssemblyName System.Windows.Forms',
|
|
89
|
-
'
|
|
90
|
-
'$
|
|
91
|
-
|
|
92
|
-
'$
|
|
93
|
-
'
|
|
94
|
-
|
|
121
|
+
'Add-Type -AssemblyName System.Drawing',
|
|
122
|
+
'$owner = New-Object System.Windows.Forms.Form',
|
|
123
|
+
"$owner.FormBorderStyle = 'None'",
|
|
124
|
+
'$owner.Opacity = 0',
|
|
125
|
+
'$owner.ShowInTaskbar = $false',
|
|
126
|
+
"$owner.StartPosition = 'Manual'",
|
|
127
|
+
'$owner.Location = New-Object System.Drawing.Point(-32000, -32000)',
|
|
128
|
+
'$owner.Size = New-Object System.Drawing.Size(1, 1)',
|
|
129
|
+
'$owner.TopMost = $true',
|
|
130
|
+
'$owner.Show()',
|
|
131
|
+
'try {',
|
|
132
|
+
' $dialog = New-Object System.Windows.Forms.FolderBrowserDialog',
|
|
133
|
+
' $dialog.Description = "Select Hive workspace"',
|
|
134
|
+
' $dialog.ShowNewFolderButton = $false',
|
|
135
|
+
' $result = $dialog.ShowDialog($owner)',
|
|
136
|
+
' if ($result -eq [System.Windows.Forms.DialogResult]::OK) { [Console]::Out.WriteLine($dialog.SelectedPath); exit 0 }',
|
|
137
|
+
' exit 1',
|
|
138
|
+
'} finally {',
|
|
139
|
+
' $owner.Close()',
|
|
140
|
+
' $owner.Dispose()',
|
|
141
|
+
'}',
|
|
95
142
|
].join('; ');
|
|
96
|
-
const result = await run('powershell.exe', ['-NoProfile', '-STA', '-ExecutionPolicy', 'Bypass', '-Command', script], {});
|
|
143
|
+
const result = await run('powershell.exe', ['-NoProfile', '-STA', '-ExecutionPolicy', 'Bypass', '-Command', script], { timeout: PICKER_TIMEOUT_MS });
|
|
97
144
|
if (result.spawnError?.code === 'ENOENT') {
|
|
98
145
|
return emptyResponse({
|
|
99
146
|
error: 'PowerShell is unavailable on this host. Use Advanced: paste path.',
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { homedir } from 'node:os';
|
|
2
2
|
import { isAbsolute, relative, resolve, sep } from 'node:path';
|
|
3
|
+
import { realpathNative } from './path-canonicalization.js';
|
|
3
4
|
/**
|
|
4
5
|
* Root directory the FS-browse API is allowed to reveal. We sandbox to
|
|
5
6
|
* `$HOME` (override via `HIVE_FS_BROWSE_ROOT` for tests). Anything outside
|
|
@@ -7,7 +8,19 @@ import { isAbsolute, relative, resolve, sep } from 'node:path';
|
|
|
7
8
|
*/
|
|
8
9
|
export const getFsBrowseRoot = () => {
|
|
9
10
|
const override = process.env.HIVE_FS_BROWSE_ROOT;
|
|
10
|
-
|
|
11
|
+
const root = override && override.length > 0 ? resolve(override) : resolve(homedir());
|
|
12
|
+
try {
|
|
13
|
+
return realpathNative(root);
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return root;
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
const isResolvedPathWithinRoot = (rootPath, candidatePath) => {
|
|
20
|
+
if (candidatePath === rootPath)
|
|
21
|
+
return true;
|
|
22
|
+
const rel = relative(rootPath, candidatePath);
|
|
23
|
+
return rel !== '..' && !rel.startsWith(`..${sep}`) && !isAbsolute(rel);
|
|
11
24
|
};
|
|
12
25
|
/**
|
|
13
26
|
* True when `candidatePath` is `rootPath` itself or a descendant of it.
|
|
@@ -16,10 +29,26 @@ export const getFsBrowseRoot = () => {
|
|
|
16
29
|
* isPathWithinRoot so the semantics match a project we already trust.
|
|
17
30
|
*/
|
|
18
31
|
export const isPathWithinRoot = (rootPath, candidatePath) => {
|
|
19
|
-
const
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
32
|
+
const lexicalRoot = resolve(rootPath);
|
|
33
|
+
const lexicalCandidate = resolve(candidatePath);
|
|
34
|
+
let resolvedRoot = lexicalRoot;
|
|
35
|
+
let resolvedCandidate = lexicalCandidate;
|
|
36
|
+
try {
|
|
37
|
+
resolvedRoot = realpathNative(resolvedRoot);
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
// Missing / inaccessible roots are handled by the caller's readdir/stat path.
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
resolvedCandidate = realpathNative(resolvedCandidate);
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
// Non-existent children still need lexical sandboxing for "create later"
|
|
47
|
+
// probes; existing symlinks/junctions use the realpath branch above.
|
|
48
|
+
if (isResolvedPathWithinRoot(lexicalRoot, lexicalCandidate)) {
|
|
49
|
+
resolvedRoot = lexicalRoot;
|
|
50
|
+
resolvedCandidate = lexicalCandidate;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return isResolvedPathWithinRoot(resolvedRoot, resolvedCandidate);
|
|
25
54
|
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { AgentSummary } from '../shared/types.js';
|
|
2
|
+
import { type WorkflowCliPolicy } from './workflow-cli-policy.js';
|
|
2
3
|
/**
|
|
3
4
|
* Tail reminder appended to every message that flows INTO the orchestrator
|
|
4
5
|
* (worker reports, worker status updates, user chat input). Re-anchors the
|
|
@@ -11,10 +12,12 @@ import type { AgentSummary } from '../shared/types.js';
|
|
|
11
12
|
* banner noise after a few occurrences, but `<...-system-reminder>` tags
|
|
12
13
|
* mirror the out-of-band envelope LLMs are trained to attend to; placement
|
|
13
14
|
* at the tail (right before the agent's reply turn) maximizes recency
|
|
14
|
-
* weighting; phrasing as a
|
|
15
|
-
*
|
|
15
|
+
* weighting; phrasing as a short action menu is more actionable than abstract
|
|
16
|
+
* identity restatement. Kept deliberately SHORT — the full command syntax and
|
|
17
|
+
* the workflow DSL live in `.hive/PROTOCOL.md`, which agents re-read on demand.
|
|
18
|
+
* A long banner on every turn is itself the noise this envelope exists to beat.
|
|
16
19
|
*/
|
|
17
|
-
export declare const
|
|
20
|
+
export declare const buildOrchestratorReminderTail: (workflowsEnabled: boolean) => string;
|
|
18
21
|
/**
|
|
19
22
|
* Tail reminder appended to dispatches sent TO a worker. Reinforces the
|
|
20
23
|
* worker identity (so the agent does not regress into its normal CLI
|
|
@@ -22,12 +25,14 @@ export declare const ORCHESTRATOR_REMINDER_TAIL: string;
|
|
|
22
25
|
* with dispatch_id pre-bound.
|
|
23
26
|
*/
|
|
24
27
|
export declare const buildWorkerReminderTail: (dispatchId: string) => string;
|
|
25
|
-
export declare const getHiveTeamRules: (agent: Pick<AgentSummary, "role"
|
|
28
|
+
export declare const getHiveTeamRules: (agent: Pick<AgentSummary, "role">, workflowsEnabled?: boolean, autostaffEnabled?: boolean) => readonly string[];
|
|
26
29
|
/**
|
|
27
30
|
* Workspace-local protocol cheat sheet written to `.hive/PROTOCOL.md`. Agents
|
|
28
31
|
* are explicitly trained to look at project root markdown when confused, so
|
|
29
32
|
* keeping a single canonical doc next to `.hive/tasks.md` doubles as a
|
|
30
33
|
* "cat-recover" path when both the startup prompt and the in-message
|
|
31
|
-
* reminders fail to anchor.
|
|
34
|
+
* reminders fail to anchor. This is also the single home of the full command
|
|
35
|
+
* syntax and the workflow DSL reference — the always-on injections only carry
|
|
36
|
+
* the lean core rules and point here.
|
|
32
37
|
*/
|
|
33
|
-
export declare const buildProtocolDoc: () => string;
|
|
38
|
+
export declare const buildProtocolDoc: (cliPolicy?: WorkflowCliPolicy, workflowsEnabled?: boolean, autostaffEnabled?: boolean) => string;
|