@tt-a1i/hive 1.7.0 → 2.0.2

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 (251) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/README.en.md +73 -11
  3. package/README.md +41 -8
  4. package/dist/src/cli/hive-remote.d.ts +46 -0
  5. package/dist/src/cli/hive-remote.js +257 -0
  6. package/dist/src/cli/hive-update.js +7 -2
  7. package/dist/src/cli/hive.d.ts +6 -0
  8. package/dist/src/cli/hive.js +64 -0
  9. package/dist/src/cli/team.d.ts +22 -0
  10. package/dist/src/cli/team.js +255 -5
  11. package/dist/src/server/agent-command-resolver.js +10 -3
  12. package/dist/src/server/agent-exit-classification.d.ts +6 -0
  13. package/dist/src/server/agent-exit-classification.js +6 -0
  14. package/dist/src/server/agent-manager-support.d.ts +2 -1
  15. package/dist/src/server/agent-manager-support.js +59 -15
  16. package/dist/src/server/agent-manager.d.ts +3 -0
  17. package/dist/src/server/agent-manager.js +22 -7
  18. package/dist/src/server/agent-run-bootstrap.d.ts +14 -0
  19. package/dist/src/server/agent-run-bootstrap.js +11 -4
  20. package/dist/src/server/agent-run-exit-handler.js +14 -8
  21. package/dist/src/server/agent-run-starter.d.ts +3 -1
  22. package/dist/src/server/agent-run-starter.js +22 -5
  23. package/dist/src/server/agent-run-sync.js +13 -5
  24. package/dist/src/server/agent-runtime-types.d.ts +1 -0
  25. package/dist/src/server/agent-runtime.d.ts +2 -1
  26. package/dist/src/server/agent-runtime.js +9 -2
  27. package/dist/src/server/agent-startup-instructions.d.ts +2 -1
  28. package/dist/src/server/agent-startup-instructions.js +8 -4
  29. package/dist/src/server/agent-stdin-dispatcher.d.ts +4 -2
  30. package/dist/src/server/agent-stdin-dispatcher.js +35 -3
  31. package/dist/src/server/command-preset-defaults.d.ts +6 -1
  32. package/dist/src/server/command-preset-defaults.js +56 -0
  33. package/dist/src/server/fs-browse.d.ts +2 -0
  34. package/dist/src/server/fs-browse.js +165 -31
  35. package/dist/src/server/fs-pick-folder.js +6 -69
  36. package/dist/src/server/fs-sandbox.d.ts +5 -3
  37. package/dist/src/server/fs-sandbox.js +5 -3
  38. package/dist/src/server/hive-team-guidance.js +18 -6
  39. package/dist/src/server/machine-name.d.ts +2 -0
  40. package/dist/src/server/machine-name.js +13 -0
  41. package/dist/src/server/open-target-commands.d.ts +1 -0
  42. package/dist/src/server/open-target-commands.js +4 -1
  43. package/dist/src/server/orchestrator-autostart.js +1 -1
  44. package/dist/src/server/platform-path.d.ts +1 -0
  45. package/dist/src/server/platform-path.js +14 -1
  46. package/dist/src/server/post-start-input-writer.js +50 -13
  47. package/dist/src/server/preset-launch-support.js +1 -0
  48. package/dist/src/server/recovery-summary.d.ts +2 -1
  49. package/dist/src/server/recovery-summary.js +2 -1
  50. package/dist/src/server/remote-audit-store.d.ts +51 -0
  51. package/dist/src/server/remote-audit-store.js +108 -0
  52. package/dist/src/server/remote-config-keys.d.ts +17 -0
  53. package/dist/src/server/remote-config-keys.js +27 -0
  54. package/dist/src/server/remote-control-constants.d.ts +30 -0
  55. package/dist/src/server/remote-control-constants.js +29 -0
  56. package/dist/src/server/remote-device-session.d.ts +40 -0
  57. package/dist/src/server/remote-device-session.js +22 -0
  58. package/dist/src/server/remote-device-store.d.ts +36 -0
  59. package/dist/src/server/remote-device-store.js +67 -0
  60. package/dist/src/server/remote-frame-bridge.d.ts +102 -0
  61. package/dist/src/server/remote-frame-bridge.js +791 -0
  62. package/dist/src/server/remote-gateway-client.d.ts +14 -0
  63. package/dist/src/server/remote-gateway-client.js +36 -0
  64. package/dist/src/server/remote-loopback-auth.d.ts +6 -0
  65. package/dist/src/server/remote-loopback-auth.js +112 -0
  66. package/dist/src/server/remote-pairing-tunnel.d.ts +59 -0
  67. package/dist/src/server/remote-pairing-tunnel.js +146 -0
  68. package/dist/src/server/remote-pairing.d.ts +58 -0
  69. package/dist/src/server/remote-pairing.js +237 -0
  70. package/dist/src/server/remote-tunnel.d.ts +113 -0
  71. package/dist/src/server/remote-tunnel.js +514 -0
  72. package/dist/src/server/restart-policy-support.d.ts +4 -1
  73. package/dist/src/server/restart-policy-support.js +3 -1
  74. package/dist/src/server/restart-policy.d.ts +1 -1
  75. package/dist/src/server/restart-policy.js +19 -3
  76. package/dist/src/server/route-types.d.ts +1 -1
  77. package/dist/src/server/routes-dispatches.js +1 -1
  78. package/dist/src/server/routes-fs.js +3 -3
  79. package/dist/src/server/routes-marketplace.js +2 -2
  80. package/dist/src/server/routes-open-workspace.js +1 -1
  81. package/dist/src/server/routes-remote.d.ts +2 -0
  82. package/dist/src/server/routes-remote.js +166 -0
  83. package/dist/src/server/routes-runtime.js +6 -6
  84. package/dist/src/server/routes-settings.js +16 -16
  85. package/dist/src/server/routes-tasks.js +2 -2
  86. package/dist/src/server/routes-team-memory.d.ts +2 -0
  87. package/dist/src/server/routes-team-memory.js +154 -0
  88. package/dist/src/server/routes-team-recall.d.ts +2 -0
  89. package/dist/src/server/routes-team-recall.js +119 -0
  90. package/dist/src/server/routes-team.js +31 -9
  91. package/dist/src/server/routes-ui.js +11 -1
  92. package/dist/src/server/routes-workflow-schedules.js +3 -3
  93. package/dist/src/server/routes-workflows.js +5 -5
  94. package/dist/src/server/routes-workspace-memory-dreams.d.ts +2 -0
  95. package/dist/src/server/routes-workspace-memory-dreams.js +105 -0
  96. package/dist/src/server/routes-workspace-memory.d.ts +2 -0
  97. package/dist/src/server/routes-workspace-memory.js +215 -0
  98. package/dist/src/server/routes-workspaces.js +9 -9
  99. package/dist/src/server/routes.js +10 -0
  100. package/dist/src/server/runtime-database.d.ts +1 -0
  101. package/dist/src/server/runtime-database.js +27 -2
  102. package/dist/src/server/runtime-restart-policy.d.ts +3 -1
  103. package/dist/src/server/runtime-restart-policy.js +2 -1
  104. package/dist/src/server/runtime-store-contract.d.ts +37 -0
  105. package/dist/src/server/runtime-store-dream.d.ts +23 -0
  106. package/dist/src/server/runtime-store-dream.js +16 -0
  107. package/dist/src/server/runtime-store-helpers.d.ts +20 -0
  108. package/dist/src/server/runtime-store-helpers.js +81 -7
  109. package/dist/src/server/runtime-store-memory.d.ts +33 -0
  110. package/dist/src/server/runtime-store-memory.js +37 -0
  111. package/dist/src/server/runtime-store-remote.d.ts +5 -0
  112. package/dist/src/server/runtime-store-remote.js +45 -0
  113. package/dist/src/server/runtime-store-workflows.js +2 -0
  114. package/dist/src/server/runtime-store.js +14 -3
  115. package/dist/src/server/session-capture-claude.d.ts +1 -1
  116. package/dist/src/server/session-capture-claude.js +7 -4
  117. package/dist/src/server/session-capture-codex.js +4 -5
  118. package/dist/src/server/session-capture-gemini.js +4 -5
  119. package/dist/src/server/session-capture-opencode.d.ts +4 -4
  120. package/dist/src/server/session-capture-opencode.js +20 -12
  121. package/dist/src/server/session-capture-qwen.d.ts +5 -0
  122. package/dist/src/server/session-capture-qwen.js +104 -0
  123. package/dist/src/server/session-capture.d.ts +17 -0
  124. package/dist/src/server/session-capture.js +16 -0
  125. package/dist/src/server/sqlite-schema-v23.d.ts +2 -0
  126. package/dist/src/server/sqlite-schema-v23.js +43 -0
  127. package/dist/src/server/sqlite-schema-v24.d.ts +2 -0
  128. package/dist/src/server/sqlite-schema-v24.js +34 -0
  129. package/dist/src/server/sqlite-schema-v25.d.ts +2 -0
  130. package/dist/src/server/sqlite-schema-v25.js +127 -0
  131. package/dist/src/server/sqlite-schema-v26.d.ts +2 -0
  132. package/dist/src/server/sqlite-schema-v26.js +56 -0
  133. package/dist/src/server/sqlite-schema-v27.d.ts +6 -0
  134. package/dist/src/server/sqlite-schema-v27.js +92 -0
  135. package/dist/src/server/sqlite-schema-v28.d.ts +2 -0
  136. package/dist/src/server/sqlite-schema-v28.js +19 -0
  137. package/dist/src/server/sqlite-schema-v29.d.ts +2 -0
  138. package/dist/src/server/sqlite-schema-v29.js +27 -0
  139. package/dist/src/server/sqlite-schema-v30.d.ts +2 -0
  140. package/dist/src/server/sqlite-schema-v30.js +27 -0
  141. package/dist/src/server/sqlite-schema-v31.d.ts +2 -0
  142. package/dist/src/server/sqlite-schema-v31.js +30 -0
  143. package/dist/src/server/sqlite-schema.d.ts +1 -1
  144. package/dist/src/server/sqlite-schema.js +49 -1
  145. package/dist/src/server/startup-command-parser.js +5 -1
  146. package/dist/src/server/tasks-file-watcher.d.ts +2 -0
  147. package/dist/src/server/tasks-file-watcher.js +15 -6
  148. package/dist/src/server/tasks-file.js +30 -5
  149. package/dist/src/server/tasks-websocket-server.js +4 -0
  150. package/dist/src/server/team-authz.d.ts +1 -1
  151. package/dist/src/server/team-authz.js +13 -1
  152. package/dist/src/server/team-list-enrichment.js +3 -1
  153. package/dist/src/server/team-memory-digest.d.ts +52 -0
  154. package/dist/src/server/team-memory-digest.js +200 -0
  155. package/dist/src/server/team-memory-dream-applier.d.ts +5 -0
  156. package/dist/src/server/team-memory-dream-applier.js +234 -0
  157. package/dist/src/server/team-memory-dream-http-serializers.d.ts +13 -0
  158. package/dist/src/server/team-memory-dream-http-serializers.js +12 -0
  159. package/dist/src/server/team-memory-dream-ops.d.ts +40 -0
  160. package/dist/src/server/team-memory-dream-ops.js +153 -0
  161. package/dist/src/server/team-memory-dream-reverter.d.ts +22 -0
  162. package/dist/src/server/team-memory-dream-reverter.js +221 -0
  163. package/dist/src/server/team-memory-dream-run-store.d.ts +23 -0
  164. package/dist/src/server/team-memory-dream-run-store.js +211 -0
  165. package/dist/src/server/team-memory-dream-runner.d.ts +37 -0
  166. package/dist/src/server/team-memory-dream-runner.js +178 -0
  167. package/dist/src/server/team-memory-dream-scheduler.d.ts +32 -0
  168. package/dist/src/server/team-memory-dream-scheduler.js +115 -0
  169. package/dist/src/server/team-memory-dream-store.d.ts +19 -0
  170. package/dist/src/server/team-memory-dream-store.js +16 -0
  171. package/dist/src/server/team-memory-dream-types.d.ts +104 -0
  172. package/dist/src/server/team-memory-dream-types.js +23 -0
  173. package/dist/src/server/team-memory-export.d.ts +22 -0
  174. package/dist/src/server/team-memory-export.js +220 -0
  175. package/dist/src/server/team-memory-feature.d.ts +12 -0
  176. package/dist/src/server/team-memory-feature.js +12 -0
  177. package/dist/src/server/team-memory-http-serializers.d.ts +102 -0
  178. package/dist/src/server/team-memory-http-serializers.js +46 -0
  179. package/dist/src/server/team-memory-injection.d.ts +31 -0
  180. package/dist/src/server/team-memory-injection.js +49 -0
  181. package/dist/src/server/team-memory-store.d.ts +116 -0
  182. package/dist/src/server/team-memory-store.js +513 -0
  183. package/dist/src/server/team-operations.d.ts +5 -1
  184. package/dist/src/server/team-operations.js +46 -16
  185. package/dist/src/server/team-recall-store.d.ts +38 -0
  186. package/dist/src/server/team-recall-store.js +205 -0
  187. package/dist/src/server/terminal-input-profile.d.ts +1 -1
  188. package/dist/src/server/terminal-input-profile.js +18 -0
  189. package/dist/src/server/terminal-ws-server.js +6 -0
  190. package/dist/src/server/ui-auth-helpers.d.ts +1 -1
  191. package/dist/src/server/ui-auth-helpers.js +7 -1
  192. package/dist/src/server/ui-auth.d.ts +3 -0
  193. package/dist/src/server/ui-auth.js +21 -1
  194. package/dist/src/server/workflow-cli-policy.d.ts +2 -3
  195. package/dist/src/server/workflow-cli-policy.js +3 -3
  196. package/dist/src/server/workflow-runner.d.ts +1 -0
  197. package/dist/src/server/workflow-runner.js +9 -4
  198. package/dist/src/server/workspace-path-validation.js +6 -2
  199. package/dist/src/server/workspace-store.d.ts +1 -1
  200. package/dist/src/server/workspace-store.js +35 -9
  201. package/dist/src/shared/fs-browse.d.ts +1 -0
  202. package/dist/src/shared/fs-browse.js +1 -0
  203. package/dist/src/shared/path-input.d.ts +12 -0
  204. package/dist/src/shared/path-input.js +22 -0
  205. package/dist/src/shared/remote-bridge-routing.d.ts +19 -0
  206. package/dist/src/shared/remote-bridge-routing.js +141 -0
  207. package/dist/src/shared/remote-crypto.d.ts +138 -0
  208. package/dist/src/shared/remote-crypto.js +427 -0
  209. package/dist/src/shared/remote-pairing-code.d.ts +7 -0
  210. package/dist/src/shared/remote-pairing-code.js +47 -0
  211. package/dist/src/shared/remote-protocol.d.ts +160 -0
  212. package/dist/src/shared/remote-protocol.js +526 -0
  213. package/dist/src/shared/team-memory.d.ts +11 -0
  214. package/dist/src/shared/team-memory.js +10 -0
  215. package/dist/src/shared/team-recall.d.ts +1 -0
  216. package/dist/src/shared/team-recall.js +1 -0
  217. package/dist/src/shared/types.d.ts +4 -5
  218. package/package.json +12 -5
  219. package/scripts/postinstall-native-artifacts.mjs +113 -0
  220. package/web/dist/assets/AddWorkerDialog-CbV75qUX.js +2 -0
  221. package/web/dist/assets/AddWorkspaceFlow-CwV-7wPx.js +1 -0
  222. package/web/dist/assets/FirstRunWizard-a6PWIK3x.js +1 -0
  223. package/web/dist/assets/MarketplaceDrawer-Dd8WIA8T.js +67 -0
  224. package/web/dist/assets/TaskGraphDrawer-Bk5WFIk_.js +1 -0
  225. package/web/dist/assets/{WhatsNewDialog-CHkZeINH.js → WhatsNewDialog-C2VZaip0.js} +1 -1
  226. package/web/dist/assets/WorkerModal-DucW-9YT.js +1 -0
  227. package/web/dist/assets/WorkflowsDrawer-Bjf4olbR.js +1 -0
  228. package/web/dist/assets/WorkspaceMemoryDrawer-DglCy_5f.js +1 -0
  229. package/web/dist/assets/WorkspaceTaskDrawer-BIWwISvA.js +1 -0
  230. package/web/dist/assets/index-BAiLYajK.css +1 -0
  231. package/web/dist/assets/index-BV2k9Dts.js +73 -0
  232. package/web/dist/assets/search-Bk2HQvO7.js +1 -0
  233. package/web/dist/assets/square-terminal-D93m9hfY.js +1 -0
  234. package/web/dist/cli-icons/agy.png +0 -0
  235. package/web/dist/cli-icons/cursor.ico +0 -0
  236. package/web/dist/cli-icons/grok.ico +0 -0
  237. package/web/dist/cli-icons/qwen.png +0 -0
  238. package/web/dist/index.html +8 -3
  239. package/web/dist/sw.js +1 -1
  240. package/scripts/fix-runtime-artifacts.mjs +0 -33
  241. package/web/dist/assets/AddWorkerDialog-BRUxpa3f.js +0 -2
  242. package/web/dist/assets/AddWorkspaceDialog-D56x5JCb.js +0 -1
  243. package/web/dist/assets/FirstRunWizard-BFVaMIsE.js +0 -1
  244. package/web/dist/assets/MarketplaceDrawer-DeEZ35dN.js +0 -76
  245. package/web/dist/assets/WorkerModal-BBCuMLIa.js +0 -1
  246. package/web/dist/assets/WorkspaceTaskDrawer-CpZHAcj1.js +0 -1
  247. package/web/dist/assets/WorkspaceTerminalPanels-7If2mDyp.js +0 -1
  248. package/web/dist/assets/WorkspaceTerminalPanels-DDGTF8rc.css +0 -1
  249. package/web/dist/assets/index-5zh61jMg.css +0 -1
  250. package/web/dist/assets/index-CxNL0O-C.js +0 -73
  251. package/web/dist/assets/path-join-7MR1s7b1.js +0 -1
@@ -0,0 +1,38 @@
1
+ import type { Database } from 'better-sqlite3';
2
+ export interface RecallContextMessage {
3
+ createdAt: number;
4
+ fromAgentId: string | null;
5
+ sourceSequence: number;
6
+ text: string;
7
+ toAgentId: string | null;
8
+ type: string;
9
+ workerId: string;
10
+ }
11
+ export interface RecallResult {
12
+ context: RecallContextMessage[];
13
+ createdAt: number;
14
+ dispatchId: string | null;
15
+ dispatchStatus: string | null;
16
+ fromAgentId: string | null;
17
+ indexName: 'like' | 'trigram' | 'unicode';
18
+ memoryConfidence?: number | null;
19
+ memoryId?: string | null;
20
+ memoryKind?: string | null;
21
+ memoryStatus?: string | null;
22
+ memoryTags?: string[];
23
+ messageType: string | null;
24
+ reportText: string | null;
25
+ score: number;
26
+ sourceSequence: number;
27
+ sourceType: 'dispatch' | 'memory' | 'message';
28
+ text: string;
29
+ toAgentId: string | null;
30
+ workerId: string | null;
31
+ }
32
+ export interface RecallOptions {
33
+ limit?: number;
34
+ window?: number;
35
+ }
36
+ export declare const createTeamRecallStore: (db: Database) => {
37
+ recallMessages: (workspaceId: string, query: string, options?: RecallOptions) => RecallResult[];
38
+ };
@@ -0,0 +1,205 @@
1
+ import { RECALL_QUERY_MAX_CHARS } from '../shared/team-recall.js';
2
+ const DEFAULT_LIMIT = 10;
3
+ const DEFAULT_WINDOW = 2;
4
+ const MAX_LIMIT = 50;
5
+ const MAX_WINDOW = 10;
6
+ const clampInt = (value, fallback, max) => {
7
+ if (value === undefined || !Number.isInteger(value) || value < 0)
8
+ return fallback;
9
+ return Math.min(value, max);
10
+ };
11
+ const quoteFtsToken = (value) => `"${value.replaceAll('"', '""')}"`;
12
+ const toSearchTerms = (query) => query.trim().split(/\s+/).filter(Boolean);
13
+ const toFtsQuery = (query) => toSearchTerms(query).map(quoteFtsToken).join(' AND ');
14
+ const hasShortTerm = (terms) => terms.some((term) => [...term].length < 3);
15
+ const escapeLike = (value) => `%${value.replaceAll('\\', '\\\\').replaceAll('%', '\\%').replaceAll('_', '\\_')}%`;
16
+ const mapContext = (row) => ({
17
+ createdAt: row.created_at,
18
+ fromAgentId: row.from_agent_id,
19
+ sourceSequence: row.sequence,
20
+ text: row.text ?? '',
21
+ toAgentId: row.to_agent_id,
22
+ type: row.type,
23
+ workerId: row.worker_id,
24
+ });
25
+ const mapRow = (row) => ({
26
+ context: [],
27
+ createdAt: row.created_at,
28
+ dispatchId: row.dispatch_id,
29
+ dispatchStatus: row.dispatch_status,
30
+ fromAgentId: row.from_agent_id,
31
+ indexName: row.index_name,
32
+ messageType: row.message_type,
33
+ reportText: row.report_text,
34
+ score: row.score,
35
+ sourceSequence: row.source_sequence,
36
+ sourceType: row.source_type,
37
+ text: row.text ?? '',
38
+ toAgentId: row.to_agent_id,
39
+ workerId: row.worker_id,
40
+ });
41
+ export const createTeamRecallStore = (db) => {
42
+ const searchMessages = (table, workspaceId, ftsQuery) => {
43
+ const indexName = table === 'messages_fts' ? 'unicode' : 'trigram';
44
+ return db
45
+ .prepare(`SELECT
46
+ 'message' AS source_type,
47
+ ? AS index_name,
48
+ m.sequence AS source_sequence,
49
+ NULL AS dispatch_id,
50
+ NULL AS dispatch_status,
51
+ m.type AS message_type,
52
+ m.worker_id,
53
+ m.from_agent_id,
54
+ m.to_agent_id,
55
+ m.text,
56
+ NULL AS report_text,
57
+ m.created_at,
58
+ bm25(${table}) AS score
59
+ FROM ${table}
60
+ JOIN messages m ON m.sequence = ${table}.rowid
61
+ WHERE ${table} MATCH ?
62
+ AND m.workspace_id = ?`)
63
+ .all(indexName, ftsQuery, workspaceId);
64
+ };
65
+ const searchDispatches = (table, workspaceId, ftsQuery) => {
66
+ const indexName = table === 'dispatches_fts' ? 'unicode' : 'trigram';
67
+ return db
68
+ .prepare(`SELECT
69
+ 'dispatch' AS source_type,
70
+ ? AS index_name,
71
+ d.sequence AS source_sequence,
72
+ d.id AS dispatch_id,
73
+ d.status AS dispatch_status,
74
+ NULL AS message_type,
75
+ NULL AS worker_id,
76
+ d.from_agent_id,
77
+ d.to_agent_id,
78
+ d.text,
79
+ d.report_text,
80
+ COALESCE(d.reported_at, d.submitted_at, d.created_at) AS created_at,
81
+ bm25(${table}) AS score
82
+ FROM ${table}
83
+ JOIN dispatches d ON d.sequence = ${table}.rowid
84
+ WHERE ${table} MATCH ?
85
+ AND d.workspace_id = ?`)
86
+ .all(indexName, ftsQuery, workspaceId);
87
+ };
88
+ const searchMessagesLike = (workspaceId, terms) => {
89
+ if (terms.length === 0)
90
+ return [];
91
+ const predicates = terms.map(() => "m.text LIKE ? ESCAPE '\\'").join(' AND ');
92
+ return db
93
+ .prepare(`SELECT
94
+ 'message' AS source_type,
95
+ 'like' AS index_name,
96
+ m.sequence AS source_sequence,
97
+ NULL AS dispatch_id,
98
+ NULL AS dispatch_status,
99
+ m.type AS message_type,
100
+ m.worker_id,
101
+ m.from_agent_id,
102
+ m.to_agent_id,
103
+ m.text,
104
+ NULL AS report_text,
105
+ m.created_at,
106
+ 0 AS score
107
+ FROM messages m
108
+ WHERE m.workspace_id = ?
109
+ AND ${predicates}`)
110
+ .all(workspaceId, ...terms.map(escapeLike));
111
+ };
112
+ const searchDispatchesLike = (workspaceId, terms) => {
113
+ if (terms.length === 0)
114
+ return [];
115
+ const predicates = terms
116
+ .map(() => "(d.text LIKE ? ESCAPE '\\' OR d.report_text LIKE ? ESCAPE '\\')")
117
+ .join(' AND ');
118
+ return db
119
+ .prepare(`SELECT
120
+ 'dispatch' AS source_type,
121
+ 'like' AS index_name,
122
+ d.sequence AS source_sequence,
123
+ d.id AS dispatch_id,
124
+ d.status AS dispatch_status,
125
+ NULL AS message_type,
126
+ NULL AS worker_id,
127
+ d.from_agent_id,
128
+ d.to_agent_id,
129
+ d.text,
130
+ d.report_text,
131
+ COALESCE(d.reported_at, d.submitted_at, d.created_at) AS created_at,
132
+ 0 AS score
133
+ FROM dispatches d
134
+ WHERE d.workspace_id = ?
135
+ AND ${predicates}`)
136
+ .all(workspaceId, ...terms.flatMap((term) => [escapeLike(term), escapeLike(term)]));
137
+ };
138
+ const listContext = (workspaceId, sequence, window) => {
139
+ if (window <= 0)
140
+ return [];
141
+ const previousRows = db
142
+ .prepare(`SELECT sequence, worker_id, type, from_agent_id, to_agent_id, text, created_at
143
+ FROM messages
144
+ WHERE workspace_id = ?
145
+ AND sequence < ?
146
+ ORDER BY sequence DESC
147
+ LIMIT ?`)
148
+ .all(workspaceId, sequence, window);
149
+ const anchorRow = db
150
+ .prepare(`SELECT sequence, worker_id, type, from_agent_id, to_agent_id, text, created_at
151
+ FROM messages
152
+ WHERE workspace_id = ?
153
+ AND sequence = ?
154
+ LIMIT 1`)
155
+ .get(workspaceId, sequence);
156
+ const nextRows = db
157
+ .prepare(`SELECT sequence, worker_id, type, from_agent_id, to_agent_id, text, created_at
158
+ FROM messages
159
+ WHERE workspace_id = ?
160
+ AND sequence > ?
161
+ ORDER BY sequence ASC
162
+ LIMIT ?`)
163
+ .all(workspaceId, sequence, window);
164
+ const rows = [...previousRows.slice().reverse(), ...(anchorRow ? [anchorRow] : []), ...nextRows];
165
+ return rows.map(mapContext);
166
+ };
167
+ const recallMessages = (workspaceId, query, options = {}) => {
168
+ if ([...query].length > RECALL_QUERY_MAX_CHARS)
169
+ return [];
170
+ const ftsQuery = toFtsQuery(query);
171
+ if (!ftsQuery)
172
+ return [];
173
+ const terms = toSearchTerms(query);
174
+ const likeRows = hasShortTerm(terms)
175
+ ? [...searchMessagesLike(workspaceId, terms), ...searchDispatchesLike(workspaceId, terms)]
176
+ : [];
177
+ const limit = clampInt(options.limit, DEFAULT_LIMIT, MAX_LIMIT);
178
+ const window = clampInt(options.window, DEFAULT_WINDOW, MAX_WINDOW);
179
+ const byKey = new Map();
180
+ const rows = [
181
+ ...searchMessages('messages_fts', workspaceId, ftsQuery),
182
+ ...searchMessages('messages_fts_trigram', workspaceId, ftsQuery),
183
+ ...searchDispatches('dispatches_fts', workspaceId, ftsQuery),
184
+ ...searchDispatches('dispatches_fts_trigram', workspaceId, ftsQuery),
185
+ ...likeRows,
186
+ ];
187
+ for (const row of rows) {
188
+ const result = mapRow(row);
189
+ const key = `${result.sourceType}:${result.sourceSequence}`;
190
+ const previous = byKey.get(key);
191
+ if (!previous || result.score < previous.score) {
192
+ byKey.set(key, result);
193
+ }
194
+ }
195
+ return [...byKey.values()]
196
+ .sort((a, b) => a.score - b.score || b.createdAt - a.createdAt)
197
+ .slice(0, limit)
198
+ .map((result) => result.sourceType === 'message'
199
+ ? { ...result, context: listContext(workspaceId, result.sourceSequence, window) }
200
+ : result);
201
+ };
202
+ return {
203
+ recallMessages,
204
+ };
205
+ };
@@ -1,5 +1,5 @@
1
1
  import type { AgentLaunchConfigInput } from './agent-run-store.js';
2
- export type TerminalInputProfile = 'default' | 'opencode';
2
+ export type TerminalInputProfile = 'codex' | 'default' | 'grok' | 'opencode';
3
3
  export interface TerminalRunSummary {
4
4
  agent_id: string;
5
5
  agent_name: string;
@@ -1,9 +1,27 @@
1
1
  import { normalizeExecutableToken } from './startup-command-parser.js';
2
+ const isCodexNpmEntrypoint = (arg) => {
3
+ if (!arg)
4
+ return false;
5
+ const normalized = arg.replace(/\\/gu, '/');
6
+ return /(?:^|\/)@openai\/codex\/bin\/codex\.js$/iu.test(normalized);
7
+ };
2
8
  export const resolveTerminalInputProfile = (config) => {
3
9
  if (!config)
4
10
  return 'default';
11
+ if (config.commandPresetId === 'codex')
12
+ return 'codex';
13
+ if (config.commandPresetId === 'grok')
14
+ return 'grok';
5
15
  if (config.commandPresetId === 'opencode')
6
16
  return 'opencode';
17
+ if (config.sessionIdCapture?.source === 'codex_session_jsonl_dir')
18
+ return 'codex';
7
19
  const executable = normalizeExecutableToken(config.interactiveCommand) ?? normalizeExecutableToken(config.command);
20
+ if (executable === 'codex')
21
+ return 'codex';
22
+ if (executable === 'grok')
23
+ return 'grok';
24
+ if (isCodexNpmEntrypoint(config.args?.[0]))
25
+ return 'codex';
8
26
  return executable === 'opencode' ? 'opencode' : 'default';
9
27
  };
@@ -36,6 +36,12 @@ export const createTerminalWebSocketServer = (server, store, tasksFileService) =
36
36
  tasksWss.publish(workspaceId, content);
37
37
  });
38
38
  const validateUpgradeSession = (request) => {
39
+ // Tunnel-originated upgrades carry the per-boot secret (invariant 2). A
40
+ // request with no secret header falls straight to the cookie path, so
41
+ // browser behavior is unchanged. getLocalRequestRejection still runs in
42
+ // front (loopback Host), so this is reachable only from 127.0.0.1.
43
+ if (store.authorizeRemoteTunnelRequest(request))
44
+ return true;
39
45
  const cookieHeader = Array.isArray(request.headers.cookie)
40
46
  ? request.headers.cookie.join('; ')
41
47
  : request.headers.cookie;
@@ -1,4 +1,4 @@
1
1
  import type { IncomingMessage } from 'node:http';
2
2
  import type { RuntimeStore } from './runtime-store.js';
3
3
  export declare const readCookie: (cookieHeader: string | undefined, name: string) => string | undefined;
4
- export declare const requireUiTokenFromRequest: (request: IncomingMessage, validateUiToken: RuntimeStore["validateUiToken"]) => void;
4
+ export declare const requireUiTokenFromRequest: (request: IncomingMessage, validateUiToken: RuntimeStore["validateUiToken"], authorizeTunnel?: RuntimeStore["authorizeRemoteTunnelRequest"]) => void;
@@ -11,7 +11,13 @@ export const readCookie = (cookieHeader, name) => {
11
11
  }
12
12
  return undefined;
13
13
  };
14
- export const requireUiTokenFromRequest = (request, validateUiToken) => {
14
+ export const requireUiTokenFromRequest = (request, validateUiToken, authorizeTunnel) => {
15
+ // Tunnel-originated requests carry the per-boot secret and short-circuit here
16
+ // (invariant 2). A request with no secret header — every browser, every
17
+ // existing test — gets authorizeTunnel === false and falls straight to the
18
+ // cookie path, so cookie behavior is byte-identical (invariant 4).
19
+ if (authorizeTunnel?.(request))
20
+ return;
15
21
  const cookieHeader = Array.isArray(request.headers.cookie)
16
22
  ? request.headers.cookie.join('; ')
17
23
  : request.headers.cookie;
@@ -1,5 +1,8 @@
1
+ import type { IncomingMessage } from 'node:http';
1
2
  export interface UiAuth {
2
3
  getToken: () => string;
3
4
  validate: (token: string | undefined) => boolean;
5
+ isTunnelRequest: (request: IncomingMessage) => boolean;
6
+ getTunnelSecret: () => string;
4
7
  }
5
8
  export declare const createUiAuth: () => UiAuth;
@@ -1,6 +1,11 @@
1
- import { randomUUID } from 'node:crypto';
1
+ import { randomBytes, randomUUID, timingSafeEqual } from 'node:crypto';
2
+ import { HIVE_REMOTE_SECRET_HEADER } from './remote-loopback-auth.js';
2
3
  export const createUiAuth = () => {
3
4
  const token = randomUUID();
5
+ // 32 bytes of CSPRNG entropy, fresh every boot. base64url so it travels as a
6
+ // clean header value. Held only in this closure.
7
+ const tunnelSecret = randomBytes(32).toString('base64url');
8
+ const expected = Buffer.from(tunnelSecret);
4
9
  return {
5
10
  getToken() {
6
11
  return token;
@@ -8,5 +13,20 @@ export const createUiAuth = () => {
8
13
  validate(input) {
9
14
  return input === token;
10
15
  },
16
+ isTunnelRequest(request) {
17
+ const raw = request.headers[HIVE_REMOTE_SECRET_HEADER];
18
+ const got = Array.isArray(raw) ? raw[0] : raw;
19
+ if (typeof got !== 'string' || got.length === 0)
20
+ return false;
21
+ const candidate = Buffer.from(got);
22
+ // length check first: timingSafeEqual throws on mismatched lengths, and a
23
+ // length difference is not secret anyway.
24
+ if (candidate.length !== expected.length)
25
+ return false;
26
+ return timingSafeEqual(candidate, expected);
27
+ },
28
+ getTunnelSecret() {
29
+ return tunnelSecret;
30
+ },
11
31
  };
12
32
  };
@@ -12,9 +12,8 @@
12
12
  * unrestricted and defaults to `claude` — i.e. exactly the old behavior, so
13
13
  * upgrading without configuring anything changes nothing.
14
14
  */
15
- /** Canonical CLI set — mirrors the built-in command preset ids
16
- * (see command-preset-defaults.ts). The allowlist is a subset of these. */
17
- export declare const CANONICAL_WORKFLOW_CLIS: readonly ["claude", "codex", "opencode", "gemini", "hermes"];
15
+ /** Canonical CLI set — mirrors the built-in command preset ids. The allowlist is a subset of these. */
16
+ export declare const CANONICAL_WORKFLOW_CLIS: readonly ["claude", "codex", "opencode", "gemini", "hermes", "qwen", "agy", "cursor", "grok"];
18
17
  export type WorkflowCli = (typeof CANONICAL_WORKFLOW_CLIS)[number];
19
18
  export declare const WORKFLOW_CLI_POLICY_KEY = "workflow.cli-policy";
20
19
  export interface WorkflowCliPolicy {
@@ -12,9 +12,9 @@
12
12
  * unrestricted and defaults to `claude` — i.e. exactly the old behavior, so
13
13
  * upgrading without configuring anything changes nothing.
14
14
  */
15
- /** Canonical CLI set mirrors the built-in command preset ids
16
- * (see command-preset-defaults.ts). The allowlist is a subset of these. */
17
- export const CANONICAL_WORKFLOW_CLIS = ['claude', 'codex', 'opencode', 'gemini', 'hermes'];
15
+ import { BUILTIN_COMMAND_PRESET_IDS } from './command-preset-defaults.js';
16
+ /** Canonical CLI set — mirrors the built-in command preset ids. The allowlist is a subset of these. */
17
+ export const CANONICAL_WORKFLOW_CLIS = BUILTIN_COMMAND_PRESET_IDS;
18
18
  export const WORKFLOW_CLI_POLICY_KEY = 'workflow.cli-policy';
19
19
  export const DEFAULT_WORKFLOW_CLI_POLICY = {
20
20
  default: 'claude',
@@ -134,6 +134,7 @@ export declare const createWorkflowRunner: (deps: {
134
134
  * the user's configured default, and an explicit `cli` outside the
135
135
  * allowlist fails the call with a clear, fixable error. */
136
136
  getWorkflowCliPolicy: () => WorkflowCliPolicy;
137
+ resolveCliLaunchConfig: (cli: string) => AgentLaunchConfigInput | undefined;
137
138
  /** Called when a run reaches a terminal state (completed/failed/stopped).
138
139
  * The runtime uses this to inject a `<hive-system-reminder>` into the
139
140
  * triggering agent's PTY so the orchestrator picks the result back up,
@@ -19,7 +19,7 @@ const isBuiltInWorkerRole = (value) => BUILT_IN_WORKER_ROLES.has(value);
19
19
  const buildModelArgs = (_cli, model) => {
20
20
  if (!model?.trim())
21
21
  return [];
22
- /* All supported CLIs (claude / codex / opencode / gemini / hermes) accept
22
+ /* All supported CLIs accept
23
23
  `--model <id>` as a positional flag. Keeping the mapping centralised
24
24
  here so future CLI quirks (e.g. opencode wanting `-m` instead) can
25
25
  be patched without touching the runner body. */
@@ -34,7 +34,7 @@ const toNestedWorkflowFilename = (scriptName) => {
34
34
  return filename;
35
35
  };
36
36
  export const createWorkflowRunner = (deps) => {
37
- const { store, workflowRunStore, awaiter, dispatchPort, resolveWorkspacePath, roleTemplateResolver, logStore, getWorkflowCliPolicy, } = deps;
37
+ const { store, workflowRunStore, awaiter, dispatchPort, resolveWorkspacePath, roleTemplateResolver, logStore, resolveCliLaunchConfig, getWorkflowCliPolicy, } = deps;
38
38
  const stoppedRuns = new Set();
39
39
  // In-memory map: runId → triggering agent. Lost on restart; the spec already
40
40
  // doesn't auto-resume interrupted runs, so this is consistent.
@@ -139,15 +139,20 @@ export const createWorkflowRunner = (deps) => {
139
139
  });
140
140
  }
141
141
  const name = opts.label ?? `${requestedType}-${myStep}-${randomUUID()}`;
142
+ const baseLaunchConfig = resolveCliLaunchConfig(command) ?? { command, args: [] };
142
143
  /* Model flag goes AFTER the template's own args so an explicit
143
144
  opts.model overrides any --model the template baked in. */
144
- const launchArgs = [...templateArgs, ...buildModelArgs(command, opts.model)];
145
+ const launchArgs = [
146
+ ...(baseLaunchConfig.args ?? []),
147
+ ...templateArgs,
148
+ ...buildModelArgs(baseLaunchConfig.command, opts.model),
149
+ ];
145
150
  // TIER 2 #2 — semaphore. Holding the slot across the full
146
151
  // dispatch+await means a parallel(100) fan-out gets paced at
147
152
  // min(16, cores-2) concurrent PTYs instead of 100 simultaneous
148
153
  // process spawns.
149
154
  const releaseSlot = await acquireSlot();
150
- const worker = store.addWorkerWithLaunch(workspaceId, { name, role, ephemeral: true, spawnedBy: 'workflow' }, { command, args: launchArgs });
155
+ const worker = store.addWorkerWithLaunch(workspaceId, { name, role, ephemeral: true, spawnedBy: 'workflow' }, { ...baseLaunchConfig, args: launchArgs });
151
156
  spawnedWorkers.push(worker.id);
152
157
  try {
153
158
  await store.startAgent(workspaceId, worker.id, { hivePort });
@@ -1,11 +1,15 @@
1
1
  import { statSync } from 'node:fs';
2
+ import { sanitizePastedPath } from '../shared/path-input.js';
2
3
  import { BadRequestError } from './http-errors.js';
3
4
  import { realpathNative } from './path-canonicalization.js';
4
5
  export const validateWorkspacePath = (path) => {
5
- if (typeof path !== 'string' || path.trim().length === 0) {
6
+ if (typeof path !== 'string') {
7
+ throw new BadRequestError('Workspace path is required');
8
+ }
9
+ const candidate = sanitizePastedPath(path);
10
+ if (candidate.length === 0) {
6
11
  throw new BadRequestError('Workspace path is required');
7
12
  }
8
- const candidate = path.trim();
9
13
  let resolved;
10
14
  try {
11
15
  resolved = realpathNative(candidate);
@@ -2,4 +2,4 @@ import type { Database } from 'better-sqlite3';
2
2
  import type { WorkerInput, WorkspaceRecord, WorkspaceStore } from './workspace-store-contract.js';
3
3
  import { type MessageKindRecord } from './workspace-store-support.js';
4
4
  export type { WorkerInput, WorkspaceRecord, WorkspaceStore };
5
- export declare const createWorkspaceStore: (db: Database, messageKinds: MessageKindRecord[]) => WorkspaceStore;
5
+ export declare const createWorkspaceStore: (db: Database, listOpenDispatchKinds: () => MessageKindRecord[]) => WorkspaceStore;
@@ -4,7 +4,7 @@ import { sameFilesystemPath } from './path-canonicalization.js';
4
4
  import { getDefaultRoleDescription } from './role-templates.js';
5
5
  import { hydrateWorkspaceFromDb, seedWorkspacesFromDb } from './workspace-store-hydration.js';
6
6
  import { getAgentRecord, getWorkerByNameRecord, getWorkerRecord, markAgentStarted, markAgentStopped, markTaskCancelled, markTaskDispatched, markTaskReported, } from './workspace-store-mutations.js';
7
- import { createOrchestrator, createWorkflowAgent, isWorkerAgent, } from './workspace-store-support.js';
7
+ import { createOrchestrator, createWorkflowAgent, getStatusFromPendingCount, isWorkerAgent, } from './workspace-store-support.js';
8
8
  const normalizeWorkerName = (name) => {
9
9
  const trimmed = name.trim();
10
10
  if (!trimmed)
@@ -13,14 +13,31 @@ const normalizeWorkerName = (name) => {
13
13
  throw new Error('Worker name must be 64 characters or fewer');
14
14
  return trimmed;
15
15
  };
16
- export const createWorkspaceStore = (db, messageKinds) => {
16
+ export const createWorkspaceStore = (db, listOpenDispatchKinds) => {
17
17
  const workspaces = new Map();
18
- seedWorkspacesFromDb(db, workspaces, messageKinds);
18
+ seedWorkspacesFromDb(db, workspaces, listOpenDispatchKinds());
19
+ const syncPendingFromDispatchLedger = (workspace) => {
20
+ const counts = new Map();
21
+ for (const item of listOpenDispatchKinds()) {
22
+ if (item.workspace_id !== workspace.summary.id)
23
+ continue;
24
+ counts.set(item.worker_id, (counts.get(item.worker_id) ?? 0) + 1);
25
+ }
26
+ for (const agent of workspace.agents) {
27
+ if (!isWorkerAgent(agent))
28
+ continue;
29
+ const pendingTaskCount = counts.get(agent.id) ?? 0;
30
+ agent.pendingTaskCount = pendingTaskCount;
31
+ if (agent.status !== 'stopped')
32
+ agent.status = getStatusFromPendingCount(pendingTaskCount);
33
+ }
34
+ };
19
35
  const getWorkspace = (workspaceId) => {
20
- hydrateWorkspaceFromDb(db, workspaces, messageKinds, workspaceId);
36
+ hydrateWorkspaceFromDb(db, workspaces, listOpenDispatchKinds(), workspaceId);
21
37
  const workspace = workspaces.get(workspaceId);
22
38
  if (!workspace)
23
39
  throw new Error(`Workspace not found: ${workspaceId}`);
40
+ syncPendingFromDispatchLedger(workspace);
24
41
  return workspace;
25
42
  };
26
43
  return {
@@ -90,11 +107,11 @@ export const createWorkspaceStore = (db, messageKinds) => {
90
107
  workspaces.delete(workspaceId);
91
108
  },
92
109
  renameWorker(workspaceId, workerId, name) {
110
+ const workspace = getWorkspace(workspaceId);
93
111
  const worker = getWorkerRecord(workspaces, workspaceId, workerId);
94
112
  const trimmed = normalizeWorkerName(name);
95
113
  if (trimmed === worker.name)
96
114
  return worker;
97
- const workspace = getWorkspace(workspaceId);
98
115
  if (workspace.agents.some((agent) => agent.id !== workerId && agent.name === trimmed && isWorkerAgent(agent))) {
99
116
  throw new ConflictError(`Worker name already exists: ${trimmed}`);
100
117
  }
@@ -114,12 +131,21 @@ export const createWorkspaceStore = (db, messageKinds) => {
114
131
  })();
115
132
  workspace.agents = workspace.agents.filter((agent) => agent.id !== workerId);
116
133
  },
117
- getAgent: (workspaceId, agentId) => getAgentRecord(workspaces, workspaceId, agentId),
118
- getWorker: (workspaceId, workerId) => getWorkerRecord(workspaces, workspaceId, workerId),
119
- getWorkerByName: (workspaceId, workerName) => getWorkerByNameRecord(workspaces, workspaceId, workerName),
134
+ getAgent(workspaceId, agentId) {
135
+ getWorkspace(workspaceId);
136
+ return getAgentRecord(workspaces, workspaceId, agentId);
137
+ },
138
+ getWorker(workspaceId, workerId) {
139
+ getWorkspace(workspaceId);
140
+ return getWorkerRecord(workspaces, workspaceId, workerId);
141
+ },
142
+ getWorkerByName(workspaceId, workerName) {
143
+ getWorkspace(workspaceId);
144
+ return getWorkerByNameRecord(workspaces, workspaceId, workerName);
145
+ },
120
146
  getWorkspaceSnapshot: getWorkspace,
121
147
  hasAgent(workspaceId, agentId) {
122
- hydrateWorkspaceFromDb(db, workspaces, messageKinds, workspaceId);
148
+ hydrateWorkspaceFromDb(db, workspaces, listOpenDispatchKinds(), workspaceId);
123
149
  return workspaces.get(workspaceId)?.agents.some((agent) => agent.id === agentId) ?? false;
124
150
  },
125
151
  listWorkers(workspaceId) {
@@ -0,0 +1 @@
1
+ export declare const WINDOWS_DRIVES_ROOT = "hive://windows-drives";
@@ -0,0 +1 @@
1
+ export const WINDOWS_DRIVES_ROOT = 'hive://windows-drives';
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Normalize a path pasted into a workspace input. Windows Explorer's
3
+ * "Copy as path" wraps the value in double quotes (e.g. `"C:\\Users\\me"`);
4
+ * shell-style paste from a terminal sometimes uses single quotes. Resolving
5
+ * the quoted form with `realpathSync` fails even though the unquoted path is
6
+ * valid.
7
+ *
8
+ * The heuristic is intentionally conservative: only a symmetric outer pair
9
+ * of identical quote characters is removed. Asymmetric or interior quotes
10
+ * survive so legitimately quote-containing paths still reach the server.
11
+ */
12
+ export declare const sanitizePastedPath: (raw: string) => string;
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Normalize a path pasted into a workspace input. Windows Explorer's
3
+ * "Copy as path" wraps the value in double quotes (e.g. `"C:\\Users\\me"`);
4
+ * shell-style paste from a terminal sometimes uses single quotes. Resolving
5
+ * the quoted form with `realpathSync` fails even though the unquoted path is
6
+ * valid.
7
+ *
8
+ * The heuristic is intentionally conservative: only a symmetric outer pair
9
+ * of identical quote characters is removed. Asymmetric or interior quotes
10
+ * survive so legitimately quote-containing paths still reach the server.
11
+ */
12
+ export const sanitizePastedPath = (raw) => {
13
+ const trimmed = raw.trim();
14
+ if (trimmed.length < 2)
15
+ return trimmed;
16
+ const first = trimmed[0];
17
+ const last = trimmed[trimmed.length - 1];
18
+ if ((first === '"' || first === "'") && first === last) {
19
+ return trimmed.slice(1, -1);
20
+ }
21
+ return trimmed;
22
+ };
@@ -0,0 +1,19 @@
1
+ import { type StreamMeta } from './remote-protocol.js';
2
+ export declare const ALLOWED_HTTP_PREFIX: "/api/";
3
+ export type BridgeRejectReason = 'path_not_whitelisted' | 'path_not_canonical' | 'path_denied' | 'bad_method' | 'malformed_meta';
4
+ export type RouteDecision = {
5
+ ok: true;
6
+ transport: 'http';
7
+ method: string;
8
+ path: string;
9
+ } | {
10
+ ok: true;
11
+ transport: 'ws';
12
+ path: string;
13
+ query?: [string, string][];
14
+ } | {
15
+ ok: false;
16
+ reason: BridgeRejectReason;
17
+ };
18
+ export declare function isCanonicalPath(path: string): boolean;
19
+ export declare function classifyOpen(meta: StreamMeta): RouteDecision;