@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
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export const applySchemaVersion21 = (db) => {
|
|
2
|
+
db.exec(`
|
|
3
|
+
CREATE TABLE IF NOT EXISTS workflow_schedules (
|
|
4
|
+
id TEXT PRIMARY KEY,
|
|
5
|
+
workspace_id TEXT NOT NULL,
|
|
6
|
+
script_path TEXT NOT NULL,
|
|
7
|
+
cron TEXT NOT NULL,
|
|
8
|
+
args TEXT,
|
|
9
|
+
enabled INTEGER NOT NULL DEFAULT 1,
|
|
10
|
+
last_run_at INTEGER,
|
|
11
|
+
next_run_at INTEGER NOT NULL,
|
|
12
|
+
created_at INTEGER NOT NULL,
|
|
13
|
+
updated_at INTEGER NOT NULL
|
|
14
|
+
);
|
|
15
|
+
CREATE INDEX IF NOT EXISTS idx_workflow_schedules_due
|
|
16
|
+
ON workflow_schedules (enabled, next_run_at);
|
|
17
|
+
CREATE INDEX IF NOT EXISTS idx_workflow_schedules_workspace
|
|
18
|
+
ON workflow_schedules (workspace_id, created_at);
|
|
19
|
+
`);
|
|
20
|
+
};
|
|
@@ -11,7 +11,19 @@ import { applySchemaVersion15 } from './sqlite-schema-v15.js';
|
|
|
11
11
|
import { applySchemaVersion16 } from './sqlite-schema-v16.js';
|
|
12
12
|
import { applySchemaVersion17 } from './sqlite-schema-v17.js';
|
|
13
13
|
import { applySchemaVersion18 } from './sqlite-schema-v18.js';
|
|
14
|
-
|
|
14
|
+
import { applySchemaVersion19 } from './sqlite-schema-v19.js';
|
|
15
|
+
import { applySchemaVersion20 } from './sqlite-schema-v20.js';
|
|
16
|
+
import { applySchemaVersion21 } from './sqlite-schema-v21.js';
|
|
17
|
+
export const CURRENT_SCHEMA_VERSION = 21;
|
|
18
|
+
// Idempotent column-add helper. SQLite doesn't have `ALTER TABLE … ADD COLUMN
|
|
19
|
+
// IF NOT EXISTS`, so PRAGMA-check first. Safe to call on every init; required
|
|
20
|
+
// for foreign-built DBs where the version-gated migration is skipped.
|
|
21
|
+
const ensureColumn = (db, table, column, definition) => {
|
|
22
|
+
const present = new Set(db.prepare(`PRAGMA table_info(${table})`).all().map((r) => r.name));
|
|
23
|
+
if (!present.has(column)) {
|
|
24
|
+
db.exec(`ALTER TABLE ${table} ADD COLUMN ${column} ${definition}`);
|
|
25
|
+
}
|
|
26
|
+
};
|
|
15
27
|
export const initializeRuntimeDatabase = (db) => {
|
|
16
28
|
db.exec(`
|
|
17
29
|
CREATE TABLE IF NOT EXISTS schema_version (
|
|
@@ -105,7 +117,79 @@ export const initializeRuntimeDatabase = (db) => {
|
|
|
105
117
|
|
|
106
118
|
CREATE INDEX IF NOT EXISTS idx_dispatches_open_by_worker
|
|
107
119
|
ON dispatches (workspace_id, to_agent_id, status, sequence);
|
|
120
|
+
|
|
121
|
+
CREATE TABLE IF NOT EXISTS workflow_runs (
|
|
122
|
+
id TEXT PRIMARY KEY,
|
|
123
|
+
workspace_id TEXT NOT NULL,
|
|
124
|
+
script_path TEXT NOT NULL,
|
|
125
|
+
script_hash TEXT,
|
|
126
|
+
name TEXT NOT NULL,
|
|
127
|
+
status TEXT NOT NULL,
|
|
128
|
+
phase TEXT,
|
|
129
|
+
args TEXT,
|
|
130
|
+
started_at INTEGER NOT NULL,
|
|
131
|
+
finished_at INTEGER,
|
|
132
|
+
error TEXT,
|
|
133
|
+
created_at INTEGER NOT NULL
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
CREATE INDEX IF NOT EXISTS idx_workflow_runs_workspace
|
|
137
|
+
ON workflow_runs (workspace_id, created_at);
|
|
138
|
+
|
|
139
|
+
CREATE TABLE IF NOT EXISTS workflow_schedules (
|
|
140
|
+
id TEXT PRIMARY KEY,
|
|
141
|
+
workspace_id TEXT NOT NULL,
|
|
142
|
+
script_path TEXT NOT NULL,
|
|
143
|
+
cron TEXT NOT NULL,
|
|
144
|
+
args TEXT,
|
|
145
|
+
enabled INTEGER NOT NULL DEFAULT 1,
|
|
146
|
+
last_run_at INTEGER,
|
|
147
|
+
next_run_at INTEGER NOT NULL,
|
|
148
|
+
created_at INTEGER NOT NULL,
|
|
149
|
+
updated_at INTEGER NOT NULL
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
CREATE INDEX IF NOT EXISTS idx_workflow_schedules_due
|
|
153
|
+
ON workflow_schedules (enabled, next_run_at);
|
|
154
|
+
CREATE INDEX IF NOT EXISTS idx_workflow_schedules_workspace
|
|
155
|
+
ON workflow_schedules (workspace_id, created_at);
|
|
108
156
|
`);
|
|
157
|
+
// Idempotent column additions — run on every init regardless of
|
|
158
|
+
// schema_version. The v19 migration adds these columns but is gated on
|
|
159
|
+
// !appliedVersions.has(19); a foreign-built DB whose v19 was a DIFFERENT
|
|
160
|
+
// migration leaves these absent. PRAGMA + ALTER here makes the writes safe.
|
|
161
|
+
ensureColumn(db, 'workers', 'ephemeral', 'INTEGER NOT NULL DEFAULT 0');
|
|
162
|
+
ensureColumn(db, 'workers', 'spawned_by', 'TEXT');
|
|
163
|
+
ensureColumn(db, 'dispatches', 'workflow_run_id', 'TEXT');
|
|
164
|
+
ensureColumn(db, 'dispatches', 'step_index', 'INTEGER');
|
|
165
|
+
// M9 — phase + label on dispatches so the workflow UI can render the
|
|
166
|
+
// phase tree + agent fleet view (mirrors Claude Code's /workflows view).
|
|
167
|
+
ensureColumn(db, 'dispatches', 'phase', 'TEXT');
|
|
168
|
+
ensureColumn(db, 'dispatches', 'label', 'TEXT');
|
|
169
|
+
// M10: capture the workflow script's `return` value so the UI can render
|
|
170
|
+
// a single canonical "Result" panel and the orchestrator notification can
|
|
171
|
+
// include it.
|
|
172
|
+
ensureColumn(db, 'workflow_runs', 'result', 'TEXT');
|
|
173
|
+
// TIER 2 #5: parent_run_id lets the Drawer render nested workflow() calls
|
|
174
|
+
// as a tree (child runs indented under their parent). Without it,
|
|
175
|
+
// nested runs were flat and the user couldn't tell which child
|
|
176
|
+
// belonged to which parent. Null on top-level runs.
|
|
177
|
+
ensureColumn(db, 'workflow_runs', 'parent_run_id', 'TEXT');
|
|
178
|
+
// TIER 2 #3: log() narrator pipeline. Authors call `log('Discovered 47
|
|
179
|
+
// endpoints')` from a workflow script; rows live here and stream to
|
|
180
|
+
// the Drawer's narrator lane + the last few lines are appended to
|
|
181
|
+
// the orchestrator's completion notification.
|
|
182
|
+
db.exec(`
|
|
183
|
+
CREATE TABLE IF NOT EXISTS workflow_run_logs (
|
|
184
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
185
|
+
run_id TEXT NOT NULL,
|
|
186
|
+
ts INTEGER NOT NULL,
|
|
187
|
+
message TEXT NOT NULL
|
|
188
|
+
);
|
|
189
|
+
CREATE INDEX IF NOT EXISTS idx_workflow_run_logs_run
|
|
190
|
+
ON workflow_run_logs (run_id, id);
|
|
191
|
+
`);
|
|
192
|
+
db.exec('CREATE INDEX IF NOT EXISTS idx_dispatches_workflow ON dispatches (workflow_run_id, step_index)');
|
|
109
193
|
const versions = db
|
|
110
194
|
.prepare('SELECT version FROM schema_version ORDER BY version ASC')
|
|
111
195
|
.all();
|
|
@@ -203,4 +287,16 @@ export const initializeRuntimeDatabase = (db) => {
|
|
|
203
287
|
applySchemaVersion18(db);
|
|
204
288
|
db.prepare('INSERT INTO schema_version (version, applied_at) VALUES (?, ?)').run(18, Date.now());
|
|
205
289
|
}
|
|
290
|
+
if (!appliedVersions.has(19)) {
|
|
291
|
+
applySchemaVersion19(db);
|
|
292
|
+
db.prepare('INSERT INTO schema_version (version, applied_at) VALUES (?, ?)').run(19, Date.now());
|
|
293
|
+
}
|
|
294
|
+
if (!appliedVersions.has(20)) {
|
|
295
|
+
applySchemaVersion20(db);
|
|
296
|
+
db.prepare('INSERT INTO schema_version (version, applied_at) VALUES (?, ?)').run(20, Date.now());
|
|
297
|
+
}
|
|
298
|
+
if (!appliedVersions.has(21)) {
|
|
299
|
+
applySchemaVersion21(db);
|
|
300
|
+
db.prepare('INSERT INTO schema_version (version, applied_at) VALUES (?, ?)').run(21, Date.now());
|
|
301
|
+
}
|
|
206
302
|
};
|
|
@@ -1 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wraps a system-injected message body in an out-of-band XML envelope so the
|
|
3
|
+
* agent attends to it as system content rather than mistaking it for user
|
|
4
|
+
* input or its own output. `<hive-system-reminder>` is reserved for the short
|
|
5
|
+
* re-anchoring action menu appended at a message tail; this `<hive-system-message>`
|
|
6
|
+
* tag carries larger injected bodies (crash-recovery / restart env-sync).
|
|
7
|
+
*/
|
|
1
8
|
export declare const wrapSystemMessage: (content: string) => string;
|
|
@@ -1 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Wraps a system-injected message body in an out-of-band XML envelope so the
|
|
3
|
+
* agent attends to it as system content rather than mistaking it for user
|
|
4
|
+
* input or its own output. `<hive-system-reminder>` is reserved for the short
|
|
5
|
+
* re-anchoring action menu appended at a message tail; this `<hive-system-message>`
|
|
6
|
+
* tag carries larger injected bodies (crash-recovery / restart env-sync).
|
|
7
|
+
*/
|
|
8
|
+
export const wrapSystemMessage = (content) => `<hive-system-message>\n${content}\n</hive-system-message>`;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type ChokidarOptions } from 'chokidar';
|
|
2
|
+
import type { WorkflowCliPolicy } from './workflow-cli-policy.js';
|
|
2
3
|
/**
|
|
3
4
|
* Watcher configuration. The atomic-save option matters on Windows: VS
|
|
4
5
|
* Code, Cursor, Notepad++, and the editor inside Hive itself all save
|
|
@@ -24,11 +25,22 @@ import { type ChokidarOptions } from 'chokidar';
|
|
|
24
25
|
* Exported so the configuration is testable in isolation.
|
|
25
26
|
*/
|
|
26
27
|
export declare const TASKS_WATCHER_OPTIONS: ChokidarOptions;
|
|
28
|
+
export declare const buildTasksWatcherOptions: (workspacePath: string, platform?: NodeJS.Platform) => ChokidarOptions;
|
|
27
29
|
export interface TasksFileWatcher {
|
|
28
30
|
close: () => Promise<void>;
|
|
29
31
|
start: (workspaceId: string, workspacePath: string) => Promise<void>;
|
|
30
32
|
stop: (workspaceId: string) => Promise<void>;
|
|
31
33
|
}
|
|
32
|
-
export declare const createTasksFileWatcher: ({ onTasksUpdated, }: {
|
|
34
|
+
export declare const createTasksFileWatcher: ({ onTasksUpdated, getWorkflowCliPolicy, getWorkflowsEnabled, getAutostaffEnabled, }: {
|
|
33
35
|
onTasksUpdated: (workspaceId: string, content: string) => void;
|
|
36
|
+
/** Lets the freshly-written `.hive/PROTOCOL.md` state the workspace's
|
|
37
|
+
* workflow CLI default + allowlist. Optional: omitted → the doc renders
|
|
38
|
+
* the unrestricted default. */
|
|
39
|
+
getWorkflowCliPolicy?: () => WorkflowCliPolicy;
|
|
40
|
+
/** Whether the experimental workflow feature is on. Off → PROTOCOL.md omits
|
|
41
|
+
* the workflow DSL + `team workflow` commands entirely. Defaults to off. */
|
|
42
|
+
getWorkflowsEnabled?: () => boolean;
|
|
43
|
+
/** Whether the experimental auto-staff feature is on (default on). Off →
|
|
44
|
+
* PROTOCOL.md omits the team-sizing rule. */
|
|
45
|
+
getAutostaffEnabled?: () => boolean;
|
|
34
46
|
}) => TasksFileWatcher;
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs';
|
|
2
2
|
import { readFile } from 'node:fs/promises';
|
|
3
|
+
import { basename, dirname, normalize } from 'node:path';
|
|
3
4
|
import chokidar from 'chokidar';
|
|
4
|
-
import { ensureProtocolFile, ensureTasksFile, getTasksFilePath } from './tasks-file.js';
|
|
5
|
+
import { ensureProtocolFile, ensureTasksFile, getTasksFilePath, TASKS_FILE_NAME, } from './tasks-file.js';
|
|
5
6
|
const DEBOUNCE_MS = 100;
|
|
7
|
+
const WATCHER_RETRY_MS = 5000;
|
|
8
|
+
const WATCHER_CLOSE_TIMEOUT_MS = 2000;
|
|
9
|
+
const WATCHER_READY_TIMEOUT_MS = 15000;
|
|
6
10
|
/**
|
|
7
11
|
* Watcher configuration. The atomic-save option matters on Windows: VS
|
|
8
12
|
* Code, Cursor, Notepad++, and the editor inside Hive itself all save
|
|
@@ -31,9 +35,45 @@ export const TASKS_WATCHER_OPTIONS = {
|
|
|
31
35
|
atomic: 100,
|
|
32
36
|
ignoreInitial: true,
|
|
33
37
|
};
|
|
34
|
-
|
|
38
|
+
const isWindowsUncPath = (path, platform = process.platform) => platform === 'win32' && /^[\\/]{2}[^\\/]+[\\/]+[^\\/]+/u.test(path);
|
|
39
|
+
export const buildTasksWatcherOptions = (workspacePath, platform = process.platform) => ({
|
|
40
|
+
...TASKS_WATCHER_OPTIONS,
|
|
41
|
+
...(platform === 'win32' || isWindowsUncPath(workspacePath, platform)
|
|
42
|
+
? { interval: 500, usePolling: true }
|
|
43
|
+
: {}),
|
|
44
|
+
});
|
|
45
|
+
const closeWatcherWithTimeout = async (watcher) => {
|
|
46
|
+
if (!watcher)
|
|
47
|
+
return;
|
|
48
|
+
let timer;
|
|
49
|
+
try {
|
|
50
|
+
await Promise.race([
|
|
51
|
+
watcher.close(),
|
|
52
|
+
new Promise((resolve) => {
|
|
53
|
+
timer = setTimeout(resolve, WATCHER_CLOSE_TIMEOUT_MS);
|
|
54
|
+
timer.unref?.();
|
|
55
|
+
}),
|
|
56
|
+
]);
|
|
57
|
+
}
|
|
58
|
+
finally {
|
|
59
|
+
if (timer)
|
|
60
|
+
clearTimeout(timer);
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
const isTasksFileEvent = (tasksPath, changedPath) => {
|
|
64
|
+
if (!changedPath)
|
|
65
|
+
return true;
|
|
66
|
+
const text = Buffer.isBuffer(changedPath) ? changedPath.toString() : changedPath;
|
|
67
|
+
return normalize(text) === normalize(tasksPath) || basename(text) === TASKS_FILE_NAME;
|
|
68
|
+
};
|
|
69
|
+
export const createTasksFileWatcher = ({ onTasksUpdated, getWorkflowCliPolicy, getWorkflowsEnabled, getAutostaffEnabled, }) => {
|
|
35
70
|
const watchers = new Map();
|
|
36
71
|
const timers = new Map();
|
|
72
|
+
const retryTimers = new Map();
|
|
73
|
+
let closed = false;
|
|
74
|
+
const logWatcherError = (workspaceId, error) => {
|
|
75
|
+
console.error(`[hive] tasks watcher error for workspace ${workspaceId}`, error);
|
|
76
|
+
};
|
|
37
77
|
const clearTimer = (workspaceId) => {
|
|
38
78
|
const timer = timers.get(workspaceId);
|
|
39
79
|
if (!timer)
|
|
@@ -41,6 +81,13 @@ export const createTasksFileWatcher = ({ onTasksUpdated, }) => {
|
|
|
41
81
|
clearTimeout(timer);
|
|
42
82
|
timers.delete(workspaceId);
|
|
43
83
|
};
|
|
84
|
+
const clearRetryTimer = (workspaceId) => {
|
|
85
|
+
const timer = retryTimers.get(workspaceId);
|
|
86
|
+
if (!timer)
|
|
87
|
+
return;
|
|
88
|
+
clearTimeout(timer);
|
|
89
|
+
retryTimers.delete(workspaceId);
|
|
90
|
+
};
|
|
44
91
|
const emitCurrentContent = async (workspaceId, workspacePath) => {
|
|
45
92
|
const tasksPath = getTasksFilePath(workspacePath);
|
|
46
93
|
try {
|
|
@@ -48,39 +95,96 @@ export const createTasksFileWatcher = ({ onTasksUpdated, }) => {
|
|
|
48
95
|
onTasksUpdated(workspaceId, content);
|
|
49
96
|
}
|
|
50
97
|
catch (error) {
|
|
51
|
-
if (error.code !== 'ENOENT')
|
|
52
|
-
|
|
98
|
+
if (error.code !== 'ENOENT') {
|
|
99
|
+
logWatcherError(workspaceId, error);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
53
102
|
onTasksUpdated(workspaceId, '');
|
|
54
103
|
}
|
|
55
104
|
};
|
|
56
105
|
const stop = async (workspaceId) => {
|
|
57
106
|
clearTimer(workspaceId);
|
|
107
|
+
clearRetryTimer(workspaceId);
|
|
58
108
|
const watcher = watchers.get(workspaceId);
|
|
59
109
|
watchers.delete(workspaceId);
|
|
60
|
-
await watcher
|
|
110
|
+
await closeWatcherWithTimeout(watcher);
|
|
111
|
+
};
|
|
112
|
+
const scheduleRetry = (workspaceId, workspacePath) => {
|
|
113
|
+
if (closed || retryTimers.has(workspaceId))
|
|
114
|
+
return;
|
|
115
|
+
const timer = setTimeout(() => {
|
|
116
|
+
retryTimers.delete(workspaceId);
|
|
117
|
+
void start(workspaceId, workspacePath).catch((error) => logWatcherError(workspaceId, error));
|
|
118
|
+
}, WATCHER_RETRY_MS);
|
|
119
|
+
timer.unref?.();
|
|
120
|
+
retryTimers.set(workspaceId, timer);
|
|
121
|
+
};
|
|
122
|
+
const waitForReady = async (watcher) => await new Promise((resolve, reject) => {
|
|
123
|
+
const cleanup = () => {
|
|
124
|
+
watcher.off('ready', handleReady);
|
|
125
|
+
watcher.off('error', handleError);
|
|
126
|
+
clearTimeout(timeout);
|
|
127
|
+
};
|
|
128
|
+
const handleReady = () => {
|
|
129
|
+
cleanup();
|
|
130
|
+
resolve();
|
|
131
|
+
};
|
|
132
|
+
const handleError = (error) => {
|
|
133
|
+
cleanup();
|
|
134
|
+
reject(error);
|
|
135
|
+
};
|
|
136
|
+
const timeout = setTimeout(() => {
|
|
137
|
+
cleanup();
|
|
138
|
+
reject(new Error(`Timed out waiting for tasks watcher ready after ${WATCHER_READY_TIMEOUT_MS}ms`));
|
|
139
|
+
}, WATCHER_READY_TIMEOUT_MS);
|
|
140
|
+
timeout.unref?.();
|
|
141
|
+
watcher.once('ready', handleReady);
|
|
142
|
+
watcher.once('error', handleError);
|
|
143
|
+
});
|
|
144
|
+
const start = async (workspaceId, workspacePath) => {
|
|
145
|
+
closed = false;
|
|
146
|
+
await stop(workspaceId);
|
|
147
|
+
ensureTasksFile(workspacePath);
|
|
148
|
+
ensureProtocolFile(workspacePath, getWorkflowCliPolicy?.(), getWorkflowsEnabled?.() ?? false, getAutostaffEnabled?.() ?? false);
|
|
149
|
+
const tasksPath = getTasksFilePath(workspacePath);
|
|
150
|
+
const watcher = chokidar.watch(dirname(tasksPath), buildTasksWatcherOptions(workspacePath));
|
|
151
|
+
const scheduleEmit = (changedPath) => {
|
|
152
|
+
if (!isTasksFileEvent(tasksPath, changedPath))
|
|
153
|
+
return;
|
|
154
|
+
clearTimer(workspaceId);
|
|
155
|
+
timers.set(workspaceId, setTimeout(() => {
|
|
156
|
+
timers.delete(workspaceId);
|
|
157
|
+
void emitCurrentContent(workspaceId, workspacePath);
|
|
158
|
+
}, DEBOUNCE_MS));
|
|
159
|
+
};
|
|
160
|
+
watcher.on('add', scheduleEmit);
|
|
161
|
+
watcher.on('change', scheduleEmit);
|
|
162
|
+
watcher.on('unlink', scheduleEmit);
|
|
163
|
+
watcher.on('error', (error) => {
|
|
164
|
+
logWatcherError(workspaceId, error);
|
|
165
|
+
void stop(workspaceId)
|
|
166
|
+
.catch((closeError) => logWatcherError(workspaceId, closeError))
|
|
167
|
+
.finally(() => scheduleRetry(workspaceId, workspacePath));
|
|
168
|
+
});
|
|
169
|
+
watchers.set(workspaceId, watcher);
|
|
170
|
+
try {
|
|
171
|
+
await waitForReady(watcher);
|
|
172
|
+
}
|
|
173
|
+
catch (error) {
|
|
174
|
+
watchers.delete(workspaceId);
|
|
175
|
+
await closeWatcherWithTimeout(watcher);
|
|
176
|
+
scheduleRetry(workspaceId, workspacePath);
|
|
177
|
+
throw error;
|
|
178
|
+
}
|
|
61
179
|
};
|
|
62
180
|
return {
|
|
63
181
|
close: async () => {
|
|
182
|
+
closed = true;
|
|
183
|
+
for (const workspaceId of retryTimers.keys())
|
|
184
|
+
clearRetryTimer(workspaceId);
|
|
64
185
|
await Promise.all(Array.from(watchers.keys(), (workspaceId) => stop(workspaceId)));
|
|
65
186
|
},
|
|
66
|
-
start
|
|
67
|
-
await stop(workspaceId);
|
|
68
|
-
ensureTasksFile(workspacePath);
|
|
69
|
-
ensureProtocolFile(workspacePath);
|
|
70
|
-
const watcher = chokidar.watch(getTasksFilePath(workspacePath), TASKS_WATCHER_OPTIONS);
|
|
71
|
-
const scheduleEmit = () => {
|
|
72
|
-
clearTimer(workspaceId);
|
|
73
|
-
timers.set(workspaceId, setTimeout(() => {
|
|
74
|
-
timers.delete(workspaceId);
|
|
75
|
-
void emitCurrentContent(workspaceId, workspacePath);
|
|
76
|
-
}, DEBOUNCE_MS));
|
|
77
|
-
};
|
|
78
|
-
watcher.on('add', scheduleEmit);
|
|
79
|
-
watcher.on('change', scheduleEmit);
|
|
80
|
-
watcher.on('unlink', scheduleEmit);
|
|
81
|
-
watchers.set(workspaceId, watcher);
|
|
82
|
-
await new Promise((resolve) => watcher.once('ready', () => resolve()));
|
|
83
|
-
},
|
|
187
|
+
start,
|
|
84
188
|
stop,
|
|
85
189
|
};
|
|
86
190
|
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { WorkflowCliPolicy } from './workflow-cli-policy.js';
|
|
1
2
|
interface TasksFileService {
|
|
2
3
|
readTasks: (workspacePath: string) => string;
|
|
3
4
|
writeTasks: (workspacePath: string, content: string) => void;
|
|
@@ -16,6 +17,6 @@ export declare const ensureTasksFile: (workspacePath: string) => string;
|
|
|
16
17
|
* on every workspace open means a Hive version bump that changes the rules
|
|
17
18
|
* propagates without manual intervention.
|
|
18
19
|
*/
|
|
19
|
-
export declare const ensureProtocolFile: (workspacePath: string) => string;
|
|
20
|
+
export declare const ensureProtocolFile: (workspacePath: string, cliPolicy?: WorkflowCliPolicy, workflowsEnabled?: boolean, autostaffEnabled?: boolean) => string;
|
|
20
21
|
export declare const createTasksFileService: () => TasksFileService;
|
|
21
22
|
export type { TasksFileService };
|
|
@@ -9,18 +9,39 @@ export const PROTOCOL_RELATIVE_PATH = `${HIVE_DIR_NAME}/${PROTOCOL_FILE_NAME}`;
|
|
|
9
9
|
export const getTasksFilePath = (workspacePath) => join(workspacePath, HIVE_DIR_NAME, TASKS_FILE_NAME);
|
|
10
10
|
export const getProtocolFilePath = (workspacePath) => join(workspacePath, HIVE_DIR_NAME, PROTOCOL_FILE_NAME);
|
|
11
11
|
const getLegacyTasksFilePath = (workspacePath) => join(workspacePath, TASKS_FILE_NAME);
|
|
12
|
+
const RETRYABLE_TASKS_FS_ERROR_CODES = new Set(['EACCES', 'EBUSY', 'EPERM']);
|
|
13
|
+
const TASKS_FS_RETRY_DELAYS_MS = [20, 50, 100];
|
|
14
|
+
const sleepSync = (ms) => {
|
|
15
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
16
|
+
};
|
|
17
|
+
const runRetryableTasksFileOperation = (operation) => {
|
|
18
|
+
for (let attempt = 0;; attempt += 1) {
|
|
19
|
+
try {
|
|
20
|
+
return operation();
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
const code = error?.code;
|
|
24
|
+
const delay = TASKS_FS_RETRY_DELAYS_MS[attempt];
|
|
25
|
+
if (!code || !RETRYABLE_TASKS_FS_ERROR_CODES.has(code) || delay === undefined)
|
|
26
|
+
throw error;
|
|
27
|
+
sleepSync(delay);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
};
|
|
12
31
|
const ensureTasksDir = (workspacePath) => {
|
|
13
|
-
mkdirSync(dirname(getTasksFilePath(workspacePath)), { recursive: true });
|
|
32
|
+
runRetryableTasksFileOperation(() => mkdirSync(dirname(getTasksFilePath(workspacePath)), { recursive: true }));
|
|
14
33
|
};
|
|
15
34
|
export const ensureTasksFile = (workspacePath) => {
|
|
16
35
|
ensureTasksDir(workspacePath);
|
|
17
36
|
const tasksFilePath = getTasksFilePath(workspacePath);
|
|
18
37
|
if (existsSync(tasksFilePath)) {
|
|
19
|
-
return readFileSync(tasksFilePath, 'utf8');
|
|
38
|
+
return runRetryableTasksFileOperation(() => readFileSync(tasksFilePath, 'utf8'));
|
|
20
39
|
}
|
|
21
40
|
const legacyTasksFilePath = getLegacyTasksFilePath(workspacePath);
|
|
22
|
-
const content = existsSync(legacyTasksFilePath)
|
|
23
|
-
|
|
41
|
+
const content = existsSync(legacyTasksFilePath)
|
|
42
|
+
? runRetryableTasksFileOperation(() => readFileSync(legacyTasksFilePath, 'utf8'))
|
|
43
|
+
: '';
|
|
44
|
+
runRetryableTasksFileOperation(() => writeFileSync(tasksFilePath, content, 'utf8'));
|
|
24
45
|
return content;
|
|
25
46
|
};
|
|
26
47
|
/**
|
|
@@ -29,14 +50,16 @@ export const ensureTasksFile = (workspacePath) => {
|
|
|
29
50
|
* on every workspace open means a Hive version bump that changes the rules
|
|
30
51
|
* propagates without manual intervention.
|
|
31
52
|
*/
|
|
32
|
-
export const ensureProtocolFile = (workspacePath) => {
|
|
53
|
+
export const ensureProtocolFile = (workspacePath, cliPolicy, workflowsEnabled = false, autostaffEnabled = false) => {
|
|
33
54
|
ensureTasksDir(workspacePath);
|
|
34
55
|
const protocolFilePath = getProtocolFilePath(workspacePath);
|
|
35
|
-
const desired = buildProtocolDoc();
|
|
36
|
-
const current = existsSync(protocolFilePath)
|
|
56
|
+
const desired = buildProtocolDoc(cliPolicy, workflowsEnabled, autostaffEnabled);
|
|
57
|
+
const current = existsSync(protocolFilePath)
|
|
58
|
+
? runRetryableTasksFileOperation(() => readFileSync(protocolFilePath, 'utf8'))
|
|
59
|
+
: null;
|
|
37
60
|
if (current === desired)
|
|
38
61
|
return desired;
|
|
39
|
-
writeFileSync(protocolFilePath, desired, 'utf8');
|
|
62
|
+
runRetryableTasksFileOperation(() => writeFileSync(protocolFilePath, desired, 'utf8'));
|
|
40
63
|
return desired;
|
|
41
64
|
};
|
|
42
65
|
export const createTasksFileService = () => {
|
|
@@ -46,7 +69,7 @@ export const createTasksFileService = () => {
|
|
|
46
69
|
},
|
|
47
70
|
writeTasks(workspacePath, content) {
|
|
48
71
|
ensureTasksDir(workspacePath);
|
|
49
|
-
writeFileSync(getTasksFilePath(workspacePath), content, 'utf8');
|
|
72
|
+
runRetryableTasksFileOperation(() => writeFileSync(getTasksFilePath(workspacePath), content, 'utf8'));
|
|
50
73
|
},
|
|
51
74
|
};
|
|
52
75
|
};
|
|
@@ -1,17 +1,15 @@
|
|
|
1
1
|
import { WebSocketServer } from 'ws';
|
|
2
2
|
import { getLocalRequestRejection } from './local-request-guard.js';
|
|
3
3
|
import { readCookie } from './ui-auth-helpers.js';
|
|
4
|
+
import { attachRawSocketErrorHandler, attachWebSocketErrorHandler, attachWebSocketServerErrorHandler, rejectWebSocketUpgrade, sendWebSocketMessage, } from './websocket-upgrade-safety.js';
|
|
4
5
|
const matchTasksPath = (pathname) => {
|
|
5
6
|
const match = /^\/ws\/tasks\/(?<workspaceId>[^/]+)$/.exec(pathname);
|
|
6
7
|
const workspaceId = match?.groups?.workspaceId;
|
|
7
8
|
return workspaceId ? decodeURIComponent(workspaceId) : null;
|
|
8
9
|
};
|
|
9
|
-
const rejectUpgrade = (socket, status) => {
|
|
10
|
-
socket.write(`HTTP/1.1 ${status}\r\n\r\n`);
|
|
11
|
-
socket.destroy();
|
|
12
|
-
};
|
|
13
10
|
export const createTasksWebSocketServer = (server, store, tasksFileService) => {
|
|
14
11
|
const wss = new WebSocketServer({ noServer: true });
|
|
12
|
+
attachWebSocketServerErrorHandler(wss, 'tasks');
|
|
15
13
|
const socketsByWorkspaceId = new Map();
|
|
16
14
|
const validateUpgradeSession = (request) => {
|
|
17
15
|
const cookieHeader = Array.isArray(request.headers.cookie)
|
|
@@ -25,12 +23,13 @@ export const createTasksWebSocketServer = (server, store, tasksFileService) => {
|
|
|
25
23
|
const workspaceId = matchTasksPath(url.pathname);
|
|
26
24
|
if (!workspaceId)
|
|
27
25
|
return;
|
|
26
|
+
const detachRawSocketErrorHandler = attachRawSocketErrorHandler(socket, 'tasks upgrade');
|
|
28
27
|
if (getLocalRequestRejection(request)) {
|
|
29
|
-
|
|
28
|
+
rejectWebSocketUpgrade(socket, '403 Forbidden');
|
|
30
29
|
return;
|
|
31
30
|
}
|
|
32
31
|
if (!validateUpgradeSession(request)) {
|
|
33
|
-
|
|
32
|
+
rejectWebSocketUpgrade(socket, '401 Unauthorized');
|
|
34
33
|
return;
|
|
35
34
|
}
|
|
36
35
|
let workspacePath = '';
|
|
@@ -38,10 +37,12 @@ export const createTasksWebSocketServer = (server, store, tasksFileService) => {
|
|
|
38
37
|
workspacePath = store.getWorkspaceSnapshot(workspaceId).summary.path;
|
|
39
38
|
}
|
|
40
39
|
catch {
|
|
41
|
-
|
|
40
|
+
rejectWebSocketUpgrade(socket, '404 Not Found');
|
|
42
41
|
return;
|
|
43
42
|
}
|
|
44
43
|
wss.handleUpgrade(request, socket, head, (ws) => {
|
|
44
|
+
detachRawSocketErrorHandler();
|
|
45
|
+
attachWebSocketErrorHandler(ws, `tasks ${workspaceId}`);
|
|
45
46
|
const sockets = socketsByWorkspaceId.get(workspaceId) ?? new Set();
|
|
46
47
|
sockets.add(ws);
|
|
47
48
|
socketsByWorkspaceId.set(workspaceId, sockets);
|
|
@@ -55,14 +56,14 @@ export const createTasksWebSocketServer = (server, store, tasksFileService) => {
|
|
|
55
56
|
if (ws.readyState !== ws.OPEN)
|
|
56
57
|
return;
|
|
57
58
|
try {
|
|
58
|
-
ws
|
|
59
|
+
sendWebSocketMessage(ws, JSON.stringify({
|
|
59
60
|
type: 'tasks-snapshot',
|
|
60
61
|
content: tasksFileService.readTasks(workspacePath),
|
|
61
|
-
}));
|
|
62
|
+
}), `tasks ${workspaceId} snapshot`);
|
|
62
63
|
}
|
|
63
64
|
catch {
|
|
64
65
|
if (ws.readyState === ws.OPEN) {
|
|
65
|
-
ws
|
|
66
|
+
sendWebSocketMessage(ws, JSON.stringify({ type: 'tasks-error', error: 'Failed to read tasks file' }), `tasks ${workspaceId} snapshot error`);
|
|
66
67
|
}
|
|
67
68
|
}
|
|
68
69
|
});
|
|
@@ -72,7 +73,7 @@ export const createTasksWebSocketServer = (server, store, tasksFileService) => {
|
|
|
72
73
|
close: () => {
|
|
73
74
|
for (const sockets of socketsByWorkspaceId.values()) {
|
|
74
75
|
for (const socket of sockets)
|
|
75
|
-
socket.
|
|
76
|
+
socket.terminate();
|
|
76
77
|
}
|
|
77
78
|
socketsByWorkspaceId.clear();
|
|
78
79
|
wss.close();
|
|
@@ -83,9 +84,7 @@ export const createTasksWebSocketServer = (server, store, tasksFileService) => {
|
|
|
83
84
|
return;
|
|
84
85
|
const payload = JSON.stringify({ type: 'tasks-updated', content });
|
|
85
86
|
for (const socket of sockets) {
|
|
86
|
-
|
|
87
|
-
socket.send(payload);
|
|
88
|
-
}
|
|
87
|
+
sendWebSocketMessage(socket, payload, `tasks ${workspaceId} publish`);
|
|
89
88
|
}
|
|
90
89
|
},
|
|
91
90
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AgentSummary } from '../shared/types.js';
|
|
2
|
-
export type TeamCommand = 'send' | 'list' | 'report' | 'status' | 'cancel' | 'help';
|
|
2
|
+
export type TeamCommand = 'send' | 'list' | 'report' | 'status' | 'cancel' | 'help' | 'spawn' | 'dismiss' | 'workflow';
|
|
3
3
|
export declare const commandAllowedForRole: (role: AgentSummary["role"], command: TeamCommand) => boolean;
|
|
4
4
|
interface AuthenticateInput {
|
|
5
5
|
fromAgentId: string | undefined;
|
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import { ForbiddenError, UnauthorizedError } from './http-errors.js';
|
|
2
|
-
const ORCHESTRATOR_COMMANDS = new Set([
|
|
2
|
+
const ORCHESTRATOR_COMMANDS = new Set([
|
|
3
|
+
'send',
|
|
4
|
+
'list',
|
|
5
|
+
'cancel',
|
|
6
|
+
'help',
|
|
7
|
+
'spawn',
|
|
8
|
+
'dismiss',
|
|
9
|
+
'workflow',
|
|
10
|
+
]);
|
|
3
11
|
const WORKER_COMMANDS = new Set(['report', 'status', 'help']);
|
|
4
12
|
const WORKER_ROLES = new Set(['coder', 'reviewer', 'tester', 'custom']);
|
|
5
13
|
export const commandAllowedForRole = (role, command) => {
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-staff experimental feature gate.
|
|
3
|
+
*
|
|
4
|
+
* The orchestrator can already create workers (`team spawn`); auto-staff is a
|
|
5
|
+
* guidance layer that grants + encourages it to size the team to the task up
|
|
6
|
+
* front (e.g. 2 coders + 1 reviewer + 1 tester in one go) rather than adding
|
|
7
|
+
* workers one at a time. It's purely additive orchestrator guidance — no new
|
|
8
|
+
* execution path — so unlike workflows it ships ON by default; a user opts
|
|
9
|
+
* OUT from Settings.
|
|
10
|
+
*
|
|
11
|
+
* Stored GLOBALLY in `app_state` under AUTOSTAFF_ENABLED_KEY. Only the exact
|
|
12
|
+
* string "false" disables it; absent / anything else reads back as enabled.
|
|
13
|
+
*/
|
|
14
|
+
export declare const AUTOSTAFF_ENABLED_KEY = "team.autostaff";
|
|
15
|
+
export declare const readAutostaffEnabled: (raw: string | null | undefined) => boolean;
|
|
16
|
+
export declare const serializeAutostaffEnabled: (enabled: boolean) => string;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-staff experimental feature gate.
|
|
3
|
+
*
|
|
4
|
+
* The orchestrator can already create workers (`team spawn`); auto-staff is a
|
|
5
|
+
* guidance layer that grants + encourages it to size the team to the task up
|
|
6
|
+
* front (e.g. 2 coders + 1 reviewer + 1 tester in one go) rather than adding
|
|
7
|
+
* workers one at a time. It's purely additive orchestrator guidance — no new
|
|
8
|
+
* execution path — so unlike workflows it ships ON by default; a user opts
|
|
9
|
+
* OUT from Settings.
|
|
10
|
+
*
|
|
11
|
+
* Stored GLOBALLY in `app_state` under AUTOSTAFF_ENABLED_KEY. Only the exact
|
|
12
|
+
* string "false" disables it; absent / anything else reads back as enabled.
|
|
13
|
+
*/
|
|
14
|
+
export const AUTOSTAFF_ENABLED_KEY = 'team.autostaff';
|
|
15
|
+
export const readAutostaffEnabled = (raw) => raw !== 'false';
|
|
16
|
+
export const serializeAutostaffEnabled = (enabled) => (enabled ? 'true' : 'false');
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import type { TeamListItem, TeamListItemPayload } from '../shared/types.js';
|
|
2
|
-
export declare const serializeTeamListItem: ({ commandPresetId, id, lastPtyLine, name, pendingTaskCount, role, status, }: TeamListItem) => TeamListItemPayload;
|
|
2
|
+
export declare const serializeTeamListItem: ({ commandPresetId, ephemeral, id, lastPtyLine, name, pendingTaskCount, role, spawnedBy, status, }: TeamListItem) => TeamListItemPayload;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export const serializeTeamListItem = ({ commandPresetId, id, lastPtyLine, name, pendingTaskCount, role, status, }) => ({
|
|
1
|
+
export const serializeTeamListItem = ({ commandPresetId, ephemeral, id, lastPtyLine, name, pendingTaskCount, role, spawnedBy, status, }) => ({
|
|
2
2
|
id,
|
|
3
3
|
name,
|
|
4
4
|
role,
|
|
@@ -6,4 +6,6 @@ export const serializeTeamListItem = ({ commandPresetId, id, lastPtyLine, name,
|
|
|
6
6
|
pending_task_count: pendingTaskCount,
|
|
7
7
|
last_pty_line: lastPtyLine ?? null,
|
|
8
8
|
command_preset_id: commandPresetId ?? null,
|
|
9
|
+
...(ephemeral === true ? { ephemeral: true } : {}),
|
|
10
|
+
...(spawnedBy ? { spawned_by: spawnedBy } : {}),
|
|
9
11
|
});
|