@tt-a1i/hive 1.4.4 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (180) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/README.en.md +21 -0
  3. package/README.md +16 -0
  4. package/assets/qq-group.jpg +0 -0
  5. package/dist/bin/team.cmd +1 -0
  6. package/dist/src/cli/hive-update.d.ts +45 -17
  7. package/dist/src/cli/hive-update.js +63 -25
  8. package/dist/src/cli/hive.d.ts +25 -0
  9. package/dist/src/cli/hive.js +41 -3
  10. package/dist/src/cli/team.d.ts +1 -0
  11. package/dist/src/cli/team.js +216 -3
  12. package/dist/src/server/agent-command-resolver.js +3 -19
  13. package/dist/src/server/agent-manager-support.d.ts +2 -2
  14. package/dist/src/server/agent-manager-support.js +98 -24
  15. package/dist/src/server/agent-run-starter.d.ts +6 -1
  16. package/dist/src/server/agent-run-starter.js +9 -2
  17. package/dist/src/server/agent-run-store.d.ts +1 -1
  18. package/dist/src/server/agent-runtime-close.d.ts +1 -0
  19. package/dist/src/server/agent-runtime-close.js +25 -1
  20. package/dist/src/server/agent-runtime-contract.d.ts +12 -1
  21. package/dist/src/server/agent-runtime-stop-run.d.ts +1 -1
  22. package/dist/src/server/agent-runtime-stop-run.js +4 -1
  23. package/dist/src/server/agent-runtime.d.ts +2 -1
  24. package/dist/src/server/agent-runtime.js +14 -3
  25. package/dist/src/server/agent-startup-instructions.d.ts +7 -1
  26. package/dist/src/server/agent-startup-instructions.js +17 -9
  27. package/dist/src/server/agent-stdin-dispatcher.d.ts +25 -5
  28. package/dist/src/server/agent-stdin-dispatcher.js +141 -40
  29. package/dist/src/server/cron-util.d.ts +7 -0
  30. package/dist/src/server/cron-util.js +19 -0
  31. package/dist/src/server/dispatch-ledger-store.d.ts +22 -0
  32. package/dist/src/server/dispatch-ledger-store.js +51 -3
  33. package/dist/src/server/env-sync-message.js +9 -9
  34. package/dist/src/server/feature-flags.d.ts +42 -0
  35. package/dist/src/server/feature-flags.js +24 -0
  36. package/dist/src/server/fs-pick-folder.js +4 -0
  37. package/dist/src/server/fs-sandbox.js +36 -7
  38. package/dist/src/server/hive-team-guidance.d.ts +12 -6
  39. package/dist/src/server/hive-team-guidance.js +253 -71
  40. package/dist/src/server/live-run-registry.d.ts +1 -0
  41. package/dist/src/server/live-run-registry.js +1 -1
  42. package/dist/src/server/open-target-commands.js +5 -6
  43. package/dist/src/server/orchestrator-autostart.d.ts +12 -0
  44. package/dist/src/server/orchestrator-autostart.js +15 -13
  45. package/dist/src/server/path-canonicalization.d.ts +3 -0
  46. package/dist/src/server/path-canonicalization.js +29 -0
  47. package/dist/src/server/platform-path.d.ts +3 -0
  48. package/dist/src/server/platform-path.js +13 -0
  49. package/dist/src/server/post-start-input-writer.d.ts +1 -1
  50. package/dist/src/server/post-start-input-writer.js +110 -13
  51. package/dist/src/server/preset-launch-support.d.ts +1 -1
  52. package/dist/src/server/preset-launch-support.js +33 -2
  53. package/dist/src/server/recovery-summary.d.ts +5 -1
  54. package/dist/src/server/recovery-summary.js +18 -17
  55. package/dist/src/server/report-outbox-store.d.ts +36 -0
  56. package/dist/src/server/report-outbox-store.js +33 -0
  57. package/dist/src/server/restart-policy-support.d.ts +5 -1
  58. package/dist/src/server/restart-policy-support.js +9 -1
  59. package/dist/src/server/restart-policy.d.ts +6 -2
  60. package/dist/src/server/restart-policy.js +51 -31
  61. package/dist/src/server/role-template-store.d.ts +1 -0
  62. package/dist/src/server/role-template-store.js +11 -1
  63. package/dist/src/server/route-types.d.ts +43 -0
  64. package/dist/src/server/routes-runtime.js +2 -1
  65. package/dist/src/server/routes-settings.js +76 -0
  66. package/dist/src/server/routes-tasks.js +23 -0
  67. package/dist/src/server/routes-team.js +211 -1
  68. package/dist/src/server/routes-workflow-schedules.d.ts +2 -0
  69. package/dist/src/server/routes-workflow-schedules.js +58 -0
  70. package/dist/src/server/routes-workflows.d.ts +2 -0
  71. package/dist/src/server/routes-workflows.js +83 -0
  72. package/dist/src/server/routes-workspaces.js +5 -0
  73. package/dist/src/server/routes.js +4 -0
  74. package/dist/src/server/runtime-restart-policy.d.ts +3 -1
  75. package/dist/src/server/runtime-restart-policy.js +2 -1
  76. package/dist/src/server/runtime-store-contract.d.ts +125 -0
  77. package/dist/src/server/runtime-store-contract.js +1 -0
  78. package/dist/src/server/runtime-store-helpers.d.ts +11 -0
  79. package/dist/src/server/runtime-store-helpers.js +106 -2
  80. package/dist/src/server/runtime-store-workflows.d.ts +6 -0
  81. package/dist/src/server/runtime-store-workflows.js +108 -0
  82. package/dist/src/server/runtime-store.d.ts +3 -72
  83. package/dist/src/server/runtime-store.js +71 -4
  84. package/dist/src/server/session-capture-codex.d.ts +3 -3
  85. package/dist/src/server/session-capture-codex.js +9 -7
  86. package/dist/src/server/session-capture-gemini.d.ts +1 -1
  87. package/dist/src/server/session-capture-gemini.js +6 -3
  88. package/dist/src/server/settings-store.d.ts +3 -0
  89. package/dist/src/server/settings-store.js +1 -0
  90. package/dist/src/server/sqlite-schema-v19.d.ts +2 -0
  91. package/dist/src/server/sqlite-schema-v19.js +17 -0
  92. package/dist/src/server/sqlite-schema-v20.d.ts +2 -0
  93. package/dist/src/server/sqlite-schema-v20.js +20 -0
  94. package/dist/src/server/sqlite-schema-v21.d.ts +2 -0
  95. package/dist/src/server/sqlite-schema-v21.js +20 -0
  96. package/dist/src/server/sqlite-schema.d.ts +1 -1
  97. package/dist/src/server/sqlite-schema.js +110 -1
  98. package/dist/src/server/system-message.d.ts +7 -0
  99. package/dist/src/server/system-message.js +8 -1
  100. package/dist/src/server/task-deps.d.ts +32 -0
  101. package/dist/src/server/task-deps.js +40 -0
  102. package/dist/src/server/tasks-file-watcher.d.ts +12 -1
  103. package/dist/src/server/tasks-file-watcher.js +128 -23
  104. package/dist/src/server/tasks-file.d.ts +3 -1
  105. package/dist/src/server/tasks-file.js +33 -9
  106. package/dist/src/server/tasks-websocket-server.js +13 -14
  107. package/dist/src/server/team-authz.d.ts +1 -1
  108. package/dist/src/server/team-authz.js +10 -1
  109. package/dist/src/server/team-autostaff.d.ts +16 -0
  110. package/dist/src/server/team-autostaff.js +16 -0
  111. package/dist/src/server/team-list-serializer.d.ts +1 -1
  112. package/dist/src/server/team-list-serializer.js +3 -1
  113. package/dist/src/server/team-operations.d.ts +21 -1
  114. package/dist/src/server/team-operations.js +183 -16
  115. package/dist/src/server/terminal-protocol.js +9 -3
  116. package/dist/src/server/terminal-stream-hub.js +16 -10
  117. package/dist/src/server/terminal-ws-server.js +10 -8
  118. package/dist/src/server/webhook-notifier.d.ts +34 -0
  119. package/dist/src/server/webhook-notifier.js +47 -0
  120. package/dist/src/server/websocket-upgrade-safety.d.ts +10 -0
  121. package/dist/src/server/websocket-upgrade-safety.js +35 -0
  122. package/dist/src/server/windows-command-line.d.ts +3 -0
  123. package/dist/src/server/windows-command-line.js +9 -0
  124. package/dist/src/server/windows-filename.d.ts +2 -0
  125. package/dist/src/server/windows-filename.js +33 -0
  126. package/dist/src/server/workflow-cli-policy.d.ts +60 -0
  127. package/dist/src/server/workflow-cli-policy.js +110 -0
  128. package/dist/src/server/workflow-dispatch-awaiter.d.ts +12 -0
  129. package/dist/src/server/workflow-dispatch-awaiter.js +80 -0
  130. package/dist/src/server/workflow-feature.d.ts +15 -0
  131. package/dist/src/server/workflow-feature.js +15 -0
  132. package/dist/src/server/workflow-http-serializers.d.ts +64 -0
  133. package/dist/src/server/workflow-http-serializers.js +58 -0
  134. package/dist/src/server/workflow-output-schema.d.ts +18 -0
  135. package/dist/src/server/workflow-output-schema.js +41 -0
  136. package/dist/src/server/workflow-run-log-store.d.ts +19 -0
  137. package/dist/src/server/workflow-run-log-store.js +45 -0
  138. package/dist/src/server/workflow-run-store.d.ts +50 -0
  139. package/dist/src/server/workflow-run-store.js +103 -0
  140. package/dist/src/server/workflow-runner.d.ts +147 -0
  141. package/dist/src/server/workflow-runner.js +411 -0
  142. package/dist/src/server/workflow-schedule-create.d.ts +14 -0
  143. package/dist/src/server/workflow-schedule-create.js +41 -0
  144. package/dist/src/server/workflow-schedule-store.d.ts +43 -0
  145. package/dist/src/server/workflow-schedule-store.js +112 -0
  146. package/dist/src/server/workflow-scheduler.d.ts +36 -0
  147. package/dist/src/server/workflow-scheduler.js +97 -0
  148. package/dist/src/server/workflow-script-loader.d.ts +34 -0
  149. package/dist/src/server/workflow-script-loader.js +106 -0
  150. package/dist/src/server/workspace-path-validation.js +16 -4
  151. package/dist/src/server/workspace-shell-runtime.d.ts +5 -0
  152. package/dist/src/server/workspace-shell-runtime.js +24 -2
  153. package/dist/src/server/workspace-store-contract.d.ts +4 -1
  154. package/dist/src/server/workspace-store-hydration.js +23 -7
  155. package/dist/src/server/workspace-store-mutations.js +2 -5
  156. package/dist/src/server/workspace-store-support.d.ts +4 -0
  157. package/dist/src/server/workspace-store-support.js +13 -1
  158. package/dist/src/server/workspace-store.js +38 -4
  159. package/dist/src/shared/types.d.ts +16 -1
  160. package/package.json +4 -2
  161. package/web/dist/assets/{AddWorkerDialog-DeZhTQLi.js → AddWorkerDialog-CGbaxu0T.js} +2 -2
  162. package/web/dist/assets/AddWorkspaceDialog-CNgExu6b.js +1 -0
  163. package/web/dist/assets/{FirstRunWizard-B5wLcat5.js → FirstRunWizard-DxGApUNc.js} +1 -1
  164. package/web/dist/assets/{MarketplaceDrawer-BC0eBOEW.js → MarketplaceDrawer-Bk6cpukn.js} +1 -1
  165. package/web/dist/assets/WhatsNewDialog-CSGzk-2U.js +1 -0
  166. package/web/dist/assets/WorkerModal-i2F3n3nZ.js +1 -0
  167. package/web/dist/assets/WorkspaceTaskDrawer-C_Ta_K13.js +1 -0
  168. package/web/dist/assets/WorkspaceTerminalPanels-VdDxtrQF.js +1 -0
  169. package/web/dist/assets/index-5zh61jMg.css +1 -0
  170. package/web/dist/assets/index-CAgGM6nb.js +75 -0
  171. package/web/dist/assets/path-join-7MR1s7b1.js +1 -0
  172. package/web/dist/index.html +2 -2
  173. package/web/dist/sw.js +1 -1
  174. package/web/dist/assets/AddWorkspaceDialog-DDpXNEKf.js +0 -1
  175. package/web/dist/assets/WorkerModal-BwMHq-Bi.js +0 -1
  176. package/web/dist/assets/WorkspaceTaskDrawer-CxvT4nqs.js +0 -1
  177. package/web/dist/assets/WorkspaceTerminalPanels-CvibsPSd.js +0 -1
  178. package/web/dist/assets/index-BEsTmfrO.css +0 -1
  179. package/web/dist/assets/index-Ddb7bDN5.js +0 -75
  180. package/web/dist/assets/path-join-S7qkXQtP.js +0 -1
@@ -1,6 +1,6 @@
1
- export declare const getCodexHome: (pattern?: string) => string;
1
+ export declare const getCodexHome: (pattern?: string, platform?: NodeJS.Platform) => string;
2
2
  export declare const readCodexSessionFirstLine: (filePath: string, maxBytes?: number) => string | null;
3
- export declare const hasCodexSession: (cwd: string, sessionId: string, pattern?: string) => boolean;
4
- export declare const snapshotCodexSessionIds: (cwd: string, codexHome?: string) => Set<string>;
3
+ export declare const hasCodexSession: (cwd: string, sessionId: string, pattern?: string, platform?: NodeJS.Platform, codexHome?: string) => boolean;
4
+ export declare const snapshotCodexSessionIds: (cwd: string, codexHome?: string, platform?: NodeJS.Platform) => Set<string>;
5
5
  export declare const captureCodexSessionId: (cwd: string, knownSessionIds: Set<string>, onCapture: (sessionId: string) => void, timeoutMs?: number, intervalMs?: number, codexHome?: string) => Promise<void>;
6
6
  export declare const codexSessionStoreExists: (codexHome?: string) => boolean;
@@ -2,19 +2,21 @@ import { closeSync, existsSync, openSync, readdirSync, readSync } from 'node:fs'
2
2
  import { homedir } from 'node:os';
3
3
  import { join } from 'node:path';
4
4
  import { captureSessionIdWithCoordinator } from './claude-session-coordinator.js';
5
+ import { arePathsEqual, indexOfPathMarker } from './platform-path.js';
5
6
  const CODEX_SESSION_FILE = /^rollout-.*\.jsonl$/i;
7
+ const CODEX_SESSIONS_MARKER = '/sessions/';
6
8
  const CODEX_HEADER_READ_CHUNK_BYTES = 4096;
7
9
  const CODEX_HEADER_MAX_BYTES = 64 * 1024;
8
10
  const getDefaultCodexHome = () => process.env.CODEX_HOME ?? join(homedir(), '.codex');
9
11
  const expandHome = (path) => path === '~' || path.startsWith('~/') ? join(homedir(), path.slice(2)) : path;
10
- export const getCodexHome = (pattern) => {
12
+ export const getCodexHome = (pattern, platform = process.platform) => {
11
13
  if (!pattern)
12
14
  return getDefaultCodexHome();
13
- const markerIndex = pattern.indexOf('/sessions/');
15
+ const markerIndex = indexOfPathMarker(pattern, CODEX_SESSIONS_MARKER, platform);
14
16
  if (markerIndex === -1)
15
17
  return getDefaultCodexHome();
16
18
  const rawRoot = pattern.slice(0, markerIndex);
17
- if (rawRoot === '~/.codex' || rawRoot === '~/.codex/')
19
+ if (arePathsEqual(rawRoot, '~/.codex', platform) || arePathsEqual(rawRoot, '~/.codex/', platform))
18
20
  return getDefaultCodexHome();
19
21
  const root = expandHome(rawRoot);
20
22
  return root || getDefaultCodexHome();
@@ -78,13 +80,13 @@ const parseCodexSession = (filePath) => {
78
80
  const cwd = 'cwd' in payload && typeof payload.cwd === 'string' ? payload.cwd : null;
79
81
  return id && cwd ? { cwd, id } : null;
80
82
  };
81
- const listSessionIds = (cwd, codexHome = getDefaultCodexHome()) => {
83
+ const listSessionIds = (cwd, codexHome = getDefaultCodexHome(), platform = process.platform) => {
82
84
  const sessionsRoot = join(codexHome, 'sessions');
83
85
  return walkSessionFiles(sessionsRoot)
84
86
  .flatMap((filePath) => {
85
87
  try {
86
88
  const session = parseCodexSession(filePath);
87
- return session?.cwd === cwd ? [session.id] : [];
89
+ return session && arePathsEqual(session.cwd, cwd, platform) ? [session.id] : [];
88
90
  }
89
91
  catch {
90
92
  return [];
@@ -92,8 +94,8 @@ const listSessionIds = (cwd, codexHome = getDefaultCodexHome()) => {
92
94
  })
93
95
  .sort((left, right) => left.localeCompare(right));
94
96
  };
95
- export const hasCodexSession = (cwd, sessionId, pattern) => listSessionIds(cwd, getCodexHome(pattern)).includes(sessionId);
96
- export const snapshotCodexSessionIds = (cwd, codexHome = getDefaultCodexHome()) => new Set(listSessionIds(cwd, codexHome));
97
+ export const hasCodexSession = (cwd, sessionId, pattern, platform = process.platform, codexHome = getCodexHome(pattern, platform)) => listSessionIds(cwd, codexHome, platform).includes(sessionId);
98
+ export const snapshotCodexSessionIds = (cwd, codexHome = getDefaultCodexHome(), platform = process.platform) => new Set(listSessionIds(cwd, codexHome, platform));
97
99
  export const captureCodexSessionId = async (cwd, knownSessionIds, onCapture, timeoutMs = 5000, intervalMs = 100, codexHome = getDefaultCodexHome()) => {
98
100
  await captureSessionIdWithCoordinator({
99
101
  intervalMs,
@@ -1,4 +1,4 @@
1
- export declare const getGeminiHome: (pattern?: string) => string;
1
+ export declare const getGeminiHome: (pattern?: string, platform?: NodeJS.Platform) => string;
2
2
  export declare const hasGeminiSession: (cwd: string, sessionId: string, pattern?: string) => boolean;
3
3
  export declare const snapshotGeminiSessionIds: (cwd: string, geminiHome?: string) => Set<string>;
4
4
  export declare const captureGeminiSessionId: (cwd: string, knownSessionIds: Set<string>, onCapture: (sessionId: string) => void, timeoutMs?: number, intervalMs?: number, geminiHome?: string) => Promise<void>;
@@ -2,13 +2,15 @@ import { existsSync, readdirSync, readFileSync } from 'node:fs';
2
2
  import { homedir } from 'node:os';
3
3
  import { join } from 'node:path';
4
4
  import { captureSessionIdWithCoordinator } from './claude-session-coordinator.js';
5
+ import { arePathsEqual, indexOfPathMarker } from './platform-path.js';
5
6
  const GEMINI_SESSION_FILE = /^session-.*\.json$/i;
7
+ const GEMINI_TMP_MARKER = '/tmp/';
6
8
  const getDefaultGeminiHome = () => process.env.HIVE_GEMINI_HOME ?? join(homedir(), '.gemini');
7
9
  const expandHome = (path) => path === '~' || path.startsWith('~/') ? join(homedir(), path.slice(2)) : path;
8
- export const getGeminiHome = (pattern) => {
10
+ export const getGeminiHome = (pattern, platform = process.platform) => {
9
11
  if (!pattern)
10
12
  return getDefaultGeminiHome();
11
- const markerIndex = pattern.indexOf('/tmp/');
13
+ const markerIndex = indexOfPathMarker(pattern, GEMINI_TMP_MARKER, platform);
12
14
  if (markerIndex === -1)
13
15
  return getDefaultGeminiHome();
14
16
  const rawRoot = pattern.slice(0, markerIndex);
@@ -38,7 +40,8 @@ const listSessionIds = (cwd, geminiHome = getDefaultGeminiHome()) => {
38
40
  .filter((entry) => entry.isDirectory())
39
41
  .flatMap((entry) => {
40
42
  const projectDir = join(tmpRoot, entry.name);
41
- if (readProjectRoot(projectDir) !== cwd)
43
+ const projectRoot = readProjectRoot(projectDir);
44
+ if (projectRoot === null || !arePathsEqual(projectRoot, cwd))
42
45
  return [];
43
46
  const chatsDir = join(projectDir, 'chats');
44
47
  try {
@@ -7,6 +7,9 @@ export interface SettingsStore {
7
7
  createRoleTemplate: (input: RoleTemplateInput) => RoleTemplateRecord;
8
8
  deleteCommandPreset: (id: string) => void;
9
9
  deleteRoleTemplate: (id: string) => void;
10
+ /** TIER 2 #4 — workflow runner uses this to resolve a non-built-in
11
+ * agentType against the user's curated role library. */
12
+ findRoleTemplateByName: (name: string) => RoleTemplateRecord | undefined;
10
13
  getAppState: (key: string) => AppStateRecord | undefined;
11
14
  getCommandPreset: (id: string) => CommandPresetRecord | undefined;
12
15
  listCommandPresets: () => CommandPresetRecord[];
@@ -10,6 +10,7 @@ export const createSettingsStore = (db) => {
10
10
  createRoleTemplate: roleTemplateStore.create,
11
11
  deleteCommandPreset: commandPresetStore.remove,
12
12
  deleteRoleTemplate: roleTemplateStore.remove,
13
+ findRoleTemplateByName: roleTemplateStore.findByName,
13
14
  getAppState: appStateStore.get,
14
15
  getCommandPreset: commandPresetStore.get,
15
16
  listCommandPresets: commandPresetStore.list,
@@ -0,0 +1,2 @@
1
+ import type { Database } from 'better-sqlite3';
2
+ export declare const applySchemaVersion19: (db: Database) => void;
@@ -0,0 +1,17 @@
1
+ export const applySchemaVersion19 = (db) => {
2
+ const workerColumns = new Set(db.prepare('PRAGMA table_info(workers)').all().map((c) => c.name));
3
+ if (!workerColumns.has('ephemeral')) {
4
+ db.exec('ALTER TABLE workers ADD COLUMN ephemeral INTEGER NOT NULL DEFAULT 0');
5
+ }
6
+ if (!workerColumns.has('spawned_by')) {
7
+ db.exec('ALTER TABLE workers ADD COLUMN spawned_by TEXT');
8
+ }
9
+ const dispatchColumns = new Set(db.prepare('PRAGMA table_info(dispatches)').all().map((c) => c.name));
10
+ if (!dispatchColumns.has('workflow_run_id')) {
11
+ db.exec('ALTER TABLE dispatches ADD COLUMN workflow_run_id TEXT');
12
+ }
13
+ if (!dispatchColumns.has('step_index')) {
14
+ db.exec('ALTER TABLE dispatches ADD COLUMN step_index INTEGER');
15
+ }
16
+ db.exec('CREATE INDEX IF NOT EXISTS idx_dispatches_workflow ON dispatches (workflow_run_id, step_index)');
17
+ };
@@ -0,0 +1,2 @@
1
+ import type { Database } from 'better-sqlite3';
2
+ export declare const applySchemaVersion20: (db: Database) => void;
@@ -0,0 +1,20 @@
1
+ export const applySchemaVersion20 = (db) => {
2
+ db.exec(`
3
+ CREATE TABLE IF NOT EXISTS workflow_runs (
4
+ id TEXT PRIMARY KEY,
5
+ workspace_id TEXT NOT NULL,
6
+ script_path TEXT NOT NULL,
7
+ script_hash TEXT,
8
+ name TEXT NOT NULL,
9
+ status TEXT NOT NULL,
10
+ phase TEXT,
11
+ args TEXT,
12
+ started_at INTEGER NOT NULL,
13
+ finished_at INTEGER,
14
+ error TEXT,
15
+ created_at INTEGER NOT NULL
16
+ );
17
+ CREATE INDEX IF NOT EXISTS idx_workflow_runs_workspace
18
+ ON workflow_runs (workspace_id, created_at);
19
+ `);
20
+ };
@@ -0,0 +1,2 @@
1
+ import type { Database } from 'better-sqlite3';
2
+ export declare const applySchemaVersion21: (db: Database) => void;
@@ -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
+ };
@@ -1,3 +1,3 @@
1
1
  import type { Database } from 'better-sqlite3';
2
- export declare const CURRENT_SCHEMA_VERSION = 18;
2
+ export declare const CURRENT_SCHEMA_VERSION = 21;
3
3
  export declare const initializeRuntimeDatabase: (db: Database) => void;
@@ -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
- export const CURRENT_SCHEMA_VERSION = 18;
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,92 @@ 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 report_outbox (
122
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
123
+ workspace_id TEXT NOT NULL,
124
+ target_agent_id TEXT NOT NULL,
125
+ dispatch_id TEXT NOT NULL UNIQUE,
126
+ payload TEXT NOT NULL,
127
+ created_at INTEGER NOT NULL,
128
+ delivered_at INTEGER
129
+ );
130
+
131
+ CREATE INDEX IF NOT EXISTS idx_report_outbox_pending
132
+ ON report_outbox (workspace_id, target_agent_id, delivered_at, created_at);
133
+
134
+ CREATE TABLE IF NOT EXISTS workflow_runs (
135
+ id TEXT PRIMARY KEY,
136
+ workspace_id TEXT NOT NULL,
137
+ script_path TEXT NOT NULL,
138
+ script_hash TEXT,
139
+ name TEXT NOT NULL,
140
+ status TEXT NOT NULL,
141
+ phase TEXT,
142
+ args TEXT,
143
+ started_at INTEGER NOT NULL,
144
+ finished_at INTEGER,
145
+ error TEXT,
146
+ created_at INTEGER NOT NULL
147
+ );
148
+
149
+ CREATE INDEX IF NOT EXISTS idx_workflow_runs_workspace
150
+ ON workflow_runs (workspace_id, created_at);
151
+
152
+ CREATE TABLE IF NOT EXISTS workflow_schedules (
153
+ id TEXT PRIMARY KEY,
154
+ workspace_id TEXT NOT NULL,
155
+ script_path TEXT NOT NULL,
156
+ cron TEXT NOT NULL,
157
+ args TEXT,
158
+ enabled INTEGER NOT NULL DEFAULT 1,
159
+ last_run_at INTEGER,
160
+ next_run_at INTEGER NOT NULL,
161
+ created_at INTEGER NOT NULL,
162
+ updated_at INTEGER NOT NULL
163
+ );
164
+
165
+ CREATE INDEX IF NOT EXISTS idx_workflow_schedules_due
166
+ ON workflow_schedules (enabled, next_run_at);
167
+ CREATE INDEX IF NOT EXISTS idx_workflow_schedules_workspace
168
+ ON workflow_schedules (workspace_id, created_at);
169
+ `);
170
+ // Idempotent column additions — run on every init regardless of
171
+ // schema_version. The v19 migration adds these columns but is gated on
172
+ // !appliedVersions.has(19); a foreign-built DB whose v19 was a DIFFERENT
173
+ // migration leaves these absent. PRAGMA + ALTER here makes the writes safe.
174
+ ensureColumn(db, 'workers', 'ephemeral', 'INTEGER NOT NULL DEFAULT 0');
175
+ ensureColumn(db, 'workers', 'spawned_by', 'TEXT');
176
+ ensureColumn(db, 'dispatches', 'workflow_run_id', 'TEXT');
177
+ ensureColumn(db, 'dispatches', 'step_index', 'INTEGER');
178
+ // M9 — phase + label on dispatches so the workflow UI can render the
179
+ // phase tree + agent fleet view (mirrors Claude Code's /workflows view).
180
+ ensureColumn(db, 'dispatches', 'phase', 'TEXT');
181
+ ensureColumn(db, 'dispatches', 'label', 'TEXT');
182
+ // M10: capture the workflow script's `return` value so the UI can render
183
+ // a single canonical "Result" panel and the orchestrator notification can
184
+ // include it.
185
+ ensureColumn(db, 'workflow_runs', 'result', 'TEXT');
186
+ // TIER 2 #5: parent_run_id lets the Drawer render nested workflow() calls
187
+ // as a tree (child runs indented under their parent). Without it,
188
+ // nested runs were flat and the user couldn't tell which child
189
+ // belonged to which parent. Null on top-level runs.
190
+ ensureColumn(db, 'workflow_runs', 'parent_run_id', 'TEXT');
191
+ // TIER 2 #3: log() narrator pipeline. Authors call `log('Discovered 47
192
+ // endpoints')` from a workflow script; rows live here and stream to
193
+ // the Drawer's narrator lane + the last few lines are appended to
194
+ // the orchestrator's completion notification.
195
+ db.exec(`
196
+ CREATE TABLE IF NOT EXISTS workflow_run_logs (
197
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
198
+ run_id TEXT NOT NULL,
199
+ ts INTEGER NOT NULL,
200
+ message TEXT NOT NULL
201
+ );
202
+ CREATE INDEX IF NOT EXISTS idx_workflow_run_logs_run
203
+ ON workflow_run_logs (run_id, id);
108
204
  `);
205
+ db.exec('CREATE INDEX IF NOT EXISTS idx_dispatches_workflow ON dispatches (workflow_run_id, step_index)');
109
206
  const versions = db
110
207
  .prepare('SELECT version FROM schema_version ORDER BY version ASC')
111
208
  .all();
@@ -203,4 +300,16 @@ export const initializeRuntimeDatabase = (db) => {
203
300
  applySchemaVersion18(db);
204
301
  db.prepare('INSERT INTO schema_version (version, applied_at) VALUES (?, ?)').run(18, Date.now());
205
302
  }
303
+ if (!appliedVersions.has(19)) {
304
+ applySchemaVersion19(db);
305
+ db.prepare('INSERT INTO schema_version (version, applied_at) VALUES (?, ?)').run(19, Date.now());
306
+ }
307
+ if (!appliedVersions.has(20)) {
308
+ applySchemaVersion20(db);
309
+ db.prepare('INSERT INTO schema_version (version, applied_at) VALUES (?, ?)').run(20, Date.now());
310
+ }
311
+ if (!appliedVersions.has(21)) {
312
+ applySchemaVersion21(db);
313
+ db.prepare('INSERT INTO schema_version (version, applied_at) VALUES (?, ?)').run(21, Date.now());
314
+ }
206
315
  };
@@ -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
- export const wrapSystemMessage = (content) => `[Hive 系统消息:${content}]`;
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>`;
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Optional, read-only task-dependency support for `.hive/tasks.md`.
3
+ *
4
+ * A task line may carry a trailing `[needs: #2, #5]` annotation declaring that
5
+ * it depends on other tasks (referenced by their 1-based position among the
6
+ * GFM task-list items). `team next` uses this to surface the tasks that are
7
+ * currently runnable — not done, and with every dependency already checked off.
8
+ *
9
+ * Deliberately scoped: parsing happens here on the server only and is purely a
10
+ * READ — Hive never auto-dispatches a runnable task and never writes the
11
+ * annotation back into tasks.md (that file stays a human/orchestrator-edited,
12
+ * git-mergeable artifact). The web task renderer is intentionally left
13
+ * untouched, so the annotation just shows as literal text in the UI.
14
+ */
15
+ export interface ParsedTaskLine {
16
+ /** 1-based position among task-list items — the `#n` that `[needs:]` uses. */
17
+ index: number;
18
+ done: boolean;
19
+ text: string;
20
+ needs: number[];
21
+ }
22
+ export interface RunnableTask {
23
+ index: number;
24
+ text: string;
25
+ }
26
+ export declare const parseTasksWithDeps: (markdown: string) => ParsedTaskLine[];
27
+ /**
28
+ * Tasks that can be worked right now: not yet done, and every `[needs:]`
29
+ * dependency is a task that exists AND is checked off. A dependency on a
30
+ * non-existent `#n`, or on a task that isn't done, withholds the task.
31
+ */
32
+ export declare const computeRunnableTasks: (markdown: string) => RunnableTask[];
@@ -0,0 +1,40 @@
1
+ const TASK_LINE = /^\s*[-*]\s+\[([ xX])\]\s+(.*)$/;
2
+ const NEEDS = /\[needs:\s*([#\d,\s]+)\]\s*$/i;
3
+ export const parseTasksWithDeps = (markdown) => {
4
+ const tasks = [];
5
+ let index = 0;
6
+ for (const line of markdown.split('\n')) {
7
+ const match = TASK_LINE.exec(line);
8
+ if (!match)
9
+ continue;
10
+ index += 1;
11
+ const done = (match[1] ?? ' ').toLowerCase() === 'x';
12
+ let text = (match[2] ?? '').trim();
13
+ const needs = [];
14
+ const needsMatch = NEEDS.exec(text);
15
+ if (needsMatch) {
16
+ for (const token of (needsMatch[1] ?? '').split(',')) {
17
+ const parsed = Number.parseInt(token.replace('#', '').trim(), 10);
18
+ if (Number.isInteger(parsed) && parsed > 0)
19
+ needs.push(parsed);
20
+ }
21
+ // Drop the trailing annotation from the human-readable text.
22
+ text = text.slice(0, needsMatch.index).trim();
23
+ }
24
+ tasks.push({ index, done, text, needs });
25
+ }
26
+ return tasks;
27
+ };
28
+ /**
29
+ * Tasks that can be worked right now: not yet done, and every `[needs:]`
30
+ * dependency is a task that exists AND is checked off. A dependency on a
31
+ * non-existent `#n`, or on a task that isn't done, withholds the task.
32
+ */
33
+ export const computeRunnableTasks = (markdown) => {
34
+ const tasks = parseTasksWithDeps(markdown);
35
+ const doneByIndex = new Map(tasks.map((task) => [task.index, task.done]));
36
+ return tasks
37
+ .filter((task) => !task.done)
38
+ .filter((task) => task.needs.every((dep) => doneByIndex.get(dep) === true))
39
+ .map((task) => ({ index: task.index, text: task.text }));
40
+ };
@@ -1,4 +1,6 @@
1
1
  import { type ChokidarOptions } from 'chokidar';
2
+ import { type FeatureFlags } from './feature-flags.js';
3
+ import type { WorkflowCliPolicy } from './workflow-cli-policy.js';
2
4
  /**
3
5
  * Watcher configuration. The atomic-save option matters on Windows: VS
4
6
  * Code, Cursor, Notepad++, and the editor inside Hive itself all save
@@ -24,11 +26,20 @@ import { type ChokidarOptions } from 'chokidar';
24
26
  * Exported so the configuration is testable in isolation.
25
27
  */
26
28
  export declare const TASKS_WATCHER_OPTIONS: ChokidarOptions;
29
+ export declare const buildTasksWatcherOptions: (workspacePath: string, platform?: NodeJS.Platform) => ChokidarOptions;
27
30
  export interface TasksFileWatcher {
28
31
  close: () => Promise<void>;
29
32
  start: (workspaceId: string, workspacePath: string) => Promise<void>;
30
33
  stop: (workspaceId: string) => Promise<void>;
31
34
  }
32
- export declare const createTasksFileWatcher: ({ onTasksUpdated, }: {
35
+ export declare const createTasksFileWatcher: ({ onTasksUpdated, getWorkflowCliPolicy, getFlags, }: {
33
36
  onTasksUpdated: (workspaceId: string, content: string) => void;
37
+ /** Lets the freshly-written `.hive/PROTOCOL.md` state the workspace's
38
+ * workflow CLI default + allowlist. Optional: omitted → the doc renders
39
+ * the unrestricted default. */
40
+ getWorkflowCliPolicy?: () => WorkflowCliPolicy;
41
+ /** Resolves the live experimental flags. PROTOCOL.md omits the workflow DSL
42
+ * + `team workflow` commands when `workflowsEnabled` is off, and the
43
+ * team-sizing rule when `autostaffEnabled` is off. Omitted → all off. */
44
+ getFlags?: () => FeatureFlags;
34
45
  }) => TasksFileWatcher;
@@ -1,8 +1,13 @@
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 { FEATURE_FLAGS_ALL_OFF } from './feature-flags.js';
6
+ import { ensureProtocolFile, ensureTasksFile, getTasksFilePath, TASKS_FILE_NAME, } from './tasks-file.js';
5
7
  const DEBOUNCE_MS = 100;
8
+ const WATCHER_RETRY_MS = 5000;
9
+ const WATCHER_CLOSE_TIMEOUT_MS = 2000;
10
+ const WATCHER_READY_TIMEOUT_MS = 15000;
6
11
  /**
7
12
  * Watcher configuration. The atomic-save option matters on Windows: VS
8
13
  * Code, Cursor, Notepad++, and the editor inside Hive itself all save
@@ -31,9 +36,45 @@ export const TASKS_WATCHER_OPTIONS = {
31
36
  atomic: 100,
32
37
  ignoreInitial: true,
33
38
  };
34
- export const createTasksFileWatcher = ({ onTasksUpdated, }) => {
39
+ const isWindowsUncPath = (path, platform = process.platform) => platform === 'win32' && /^[\\/]{2}[^\\/]+[\\/]+[^\\/]+/u.test(path);
40
+ export const buildTasksWatcherOptions = (workspacePath, platform = process.platform) => ({
41
+ ...TASKS_WATCHER_OPTIONS,
42
+ ...(platform === 'win32' || isWindowsUncPath(workspacePath, platform)
43
+ ? { interval: 500, usePolling: true }
44
+ : {}),
45
+ });
46
+ const closeWatcherWithTimeout = async (watcher) => {
47
+ if (!watcher)
48
+ return;
49
+ let timer;
50
+ try {
51
+ await Promise.race([
52
+ watcher.close(),
53
+ new Promise((resolve) => {
54
+ timer = setTimeout(resolve, WATCHER_CLOSE_TIMEOUT_MS);
55
+ timer.unref?.();
56
+ }),
57
+ ]);
58
+ }
59
+ finally {
60
+ if (timer)
61
+ clearTimeout(timer);
62
+ }
63
+ };
64
+ const isTasksFileEvent = (tasksPath, changedPath) => {
65
+ if (!changedPath)
66
+ return true;
67
+ const text = Buffer.isBuffer(changedPath) ? changedPath.toString() : changedPath;
68
+ return normalize(text) === normalize(tasksPath) || basename(text) === TASKS_FILE_NAME;
69
+ };
70
+ export const createTasksFileWatcher = ({ onTasksUpdated, getWorkflowCliPolicy, getFlags, }) => {
35
71
  const watchers = new Map();
36
72
  const timers = new Map();
73
+ const retryTimers = new Map();
74
+ let closed = false;
75
+ const logWatcherError = (workspaceId, error) => {
76
+ console.error(`[hive] tasks watcher error for workspace ${workspaceId}`, error);
77
+ };
37
78
  const clearTimer = (workspaceId) => {
38
79
  const timer = timers.get(workspaceId);
39
80
  if (!timer)
@@ -41,6 +82,13 @@ export const createTasksFileWatcher = ({ onTasksUpdated, }) => {
41
82
  clearTimeout(timer);
42
83
  timers.delete(workspaceId);
43
84
  };
85
+ const clearRetryTimer = (workspaceId) => {
86
+ const timer = retryTimers.get(workspaceId);
87
+ if (!timer)
88
+ return;
89
+ clearTimeout(timer);
90
+ retryTimers.delete(workspaceId);
91
+ };
44
92
  const emitCurrentContent = async (workspaceId, workspacePath) => {
45
93
  const tasksPath = getTasksFilePath(workspacePath);
46
94
  try {
@@ -48,39 +96,96 @@ export const createTasksFileWatcher = ({ onTasksUpdated, }) => {
48
96
  onTasksUpdated(workspaceId, content);
49
97
  }
50
98
  catch (error) {
51
- if (error.code !== 'ENOENT')
52
- throw error;
99
+ if (error.code !== 'ENOENT') {
100
+ logWatcherError(workspaceId, error);
101
+ return;
102
+ }
53
103
  onTasksUpdated(workspaceId, '');
54
104
  }
55
105
  };
56
106
  const stop = async (workspaceId) => {
57
107
  clearTimer(workspaceId);
108
+ clearRetryTimer(workspaceId);
58
109
  const watcher = watchers.get(workspaceId);
59
110
  watchers.delete(workspaceId);
60
- await watcher?.close();
111
+ await closeWatcherWithTimeout(watcher);
112
+ };
113
+ const scheduleRetry = (workspaceId, workspacePath) => {
114
+ if (closed || retryTimers.has(workspaceId))
115
+ return;
116
+ const timer = setTimeout(() => {
117
+ retryTimers.delete(workspaceId);
118
+ void start(workspaceId, workspacePath).catch((error) => logWatcherError(workspaceId, error));
119
+ }, WATCHER_RETRY_MS);
120
+ timer.unref?.();
121
+ retryTimers.set(workspaceId, timer);
122
+ };
123
+ const waitForReady = async (watcher) => await new Promise((resolve, reject) => {
124
+ const cleanup = () => {
125
+ watcher.off('ready', handleReady);
126
+ watcher.off('error', handleError);
127
+ clearTimeout(timeout);
128
+ };
129
+ const handleReady = () => {
130
+ cleanup();
131
+ resolve();
132
+ };
133
+ const handleError = (error) => {
134
+ cleanup();
135
+ reject(error);
136
+ };
137
+ const timeout = setTimeout(() => {
138
+ cleanup();
139
+ reject(new Error(`Timed out waiting for tasks watcher ready after ${WATCHER_READY_TIMEOUT_MS}ms`));
140
+ }, WATCHER_READY_TIMEOUT_MS);
141
+ timeout.unref?.();
142
+ watcher.once('ready', handleReady);
143
+ watcher.once('error', handleError);
144
+ });
145
+ const start = async (workspaceId, workspacePath) => {
146
+ closed = false;
147
+ await stop(workspaceId);
148
+ ensureTasksFile(workspacePath);
149
+ ensureProtocolFile(workspacePath, getWorkflowCliPolicy?.(), getFlags?.() ?? FEATURE_FLAGS_ALL_OFF);
150
+ const tasksPath = getTasksFilePath(workspacePath);
151
+ const watcher = chokidar.watch(dirname(tasksPath), buildTasksWatcherOptions(workspacePath));
152
+ const scheduleEmit = (changedPath) => {
153
+ if (!isTasksFileEvent(tasksPath, changedPath))
154
+ return;
155
+ clearTimer(workspaceId);
156
+ timers.set(workspaceId, setTimeout(() => {
157
+ timers.delete(workspaceId);
158
+ void emitCurrentContent(workspaceId, workspacePath);
159
+ }, DEBOUNCE_MS));
160
+ };
161
+ watcher.on('add', scheduleEmit);
162
+ watcher.on('change', scheduleEmit);
163
+ watcher.on('unlink', scheduleEmit);
164
+ watcher.on('error', (error) => {
165
+ logWatcherError(workspaceId, error);
166
+ void stop(workspaceId)
167
+ .catch((closeError) => logWatcherError(workspaceId, closeError))
168
+ .finally(() => scheduleRetry(workspaceId, workspacePath));
169
+ });
170
+ watchers.set(workspaceId, watcher);
171
+ try {
172
+ await waitForReady(watcher);
173
+ }
174
+ catch (error) {
175
+ watchers.delete(workspaceId);
176
+ await closeWatcherWithTimeout(watcher);
177
+ scheduleRetry(workspaceId, workspacePath);
178
+ throw error;
179
+ }
61
180
  };
62
181
  return {
63
182
  close: async () => {
183
+ closed = true;
184
+ for (const workspaceId of retryTimers.keys())
185
+ clearRetryTimer(workspaceId);
64
186
  await Promise.all(Array.from(watchers.keys(), (workspaceId) => stop(workspaceId)));
65
187
  },
66
- start: async (workspaceId, workspacePath) => {
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
- },
188
+ start,
84
189
  stop,
85
190
  };
86
191
  };