@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.
- package/CHANGELOG.md +60 -0
- package/README.en.md +73 -11
- package/README.md +41 -8
- package/dist/src/cli/hive-remote.d.ts +46 -0
- package/dist/src/cli/hive-remote.js +257 -0
- package/dist/src/cli/hive-update.js +7 -2
- package/dist/src/cli/hive.d.ts +6 -0
- package/dist/src/cli/hive.js +64 -0
- package/dist/src/cli/team.d.ts +22 -0
- package/dist/src/cli/team.js +255 -5
- package/dist/src/server/agent-command-resolver.js +10 -3
- package/dist/src/server/agent-exit-classification.d.ts +6 -0
- package/dist/src/server/agent-exit-classification.js +6 -0
- package/dist/src/server/agent-manager-support.d.ts +2 -1
- package/dist/src/server/agent-manager-support.js +59 -15
- package/dist/src/server/agent-manager.d.ts +3 -0
- package/dist/src/server/agent-manager.js +22 -7
- package/dist/src/server/agent-run-bootstrap.d.ts +14 -0
- package/dist/src/server/agent-run-bootstrap.js +11 -4
- package/dist/src/server/agent-run-exit-handler.js +14 -8
- package/dist/src/server/agent-run-starter.d.ts +3 -1
- package/dist/src/server/agent-run-starter.js +22 -5
- package/dist/src/server/agent-run-sync.js +13 -5
- package/dist/src/server/agent-runtime-types.d.ts +1 -0
- package/dist/src/server/agent-runtime.d.ts +2 -1
- package/dist/src/server/agent-runtime.js +9 -2
- package/dist/src/server/agent-startup-instructions.d.ts +2 -1
- package/dist/src/server/agent-startup-instructions.js +8 -4
- package/dist/src/server/agent-stdin-dispatcher.d.ts +4 -2
- package/dist/src/server/agent-stdin-dispatcher.js +35 -3
- package/dist/src/server/command-preset-defaults.d.ts +6 -1
- package/dist/src/server/command-preset-defaults.js +56 -0
- package/dist/src/server/fs-browse.d.ts +2 -0
- package/dist/src/server/fs-browse.js +165 -31
- package/dist/src/server/fs-pick-folder.js +6 -69
- package/dist/src/server/fs-sandbox.d.ts +5 -3
- package/dist/src/server/fs-sandbox.js +5 -3
- package/dist/src/server/hive-team-guidance.js +18 -6
- package/dist/src/server/machine-name.d.ts +2 -0
- package/dist/src/server/machine-name.js +13 -0
- package/dist/src/server/open-target-commands.d.ts +1 -0
- package/dist/src/server/open-target-commands.js +4 -1
- package/dist/src/server/orchestrator-autostart.js +1 -1
- package/dist/src/server/platform-path.d.ts +1 -0
- package/dist/src/server/platform-path.js +14 -1
- package/dist/src/server/post-start-input-writer.js +50 -13
- package/dist/src/server/preset-launch-support.js +1 -0
- package/dist/src/server/recovery-summary.d.ts +2 -1
- package/dist/src/server/recovery-summary.js +2 -1
- package/dist/src/server/remote-audit-store.d.ts +51 -0
- package/dist/src/server/remote-audit-store.js +108 -0
- package/dist/src/server/remote-config-keys.d.ts +17 -0
- package/dist/src/server/remote-config-keys.js +27 -0
- package/dist/src/server/remote-control-constants.d.ts +30 -0
- package/dist/src/server/remote-control-constants.js +29 -0
- package/dist/src/server/remote-device-session.d.ts +40 -0
- package/dist/src/server/remote-device-session.js +22 -0
- package/dist/src/server/remote-device-store.d.ts +36 -0
- package/dist/src/server/remote-device-store.js +67 -0
- package/dist/src/server/remote-frame-bridge.d.ts +102 -0
- package/dist/src/server/remote-frame-bridge.js +791 -0
- package/dist/src/server/remote-gateway-client.d.ts +14 -0
- package/dist/src/server/remote-gateway-client.js +36 -0
- package/dist/src/server/remote-loopback-auth.d.ts +6 -0
- package/dist/src/server/remote-loopback-auth.js +112 -0
- package/dist/src/server/remote-pairing-tunnel.d.ts +59 -0
- package/dist/src/server/remote-pairing-tunnel.js +146 -0
- package/dist/src/server/remote-pairing.d.ts +58 -0
- package/dist/src/server/remote-pairing.js +237 -0
- package/dist/src/server/remote-tunnel.d.ts +113 -0
- package/dist/src/server/remote-tunnel.js +514 -0
- package/dist/src/server/restart-policy-support.d.ts +4 -1
- package/dist/src/server/restart-policy-support.js +3 -1
- package/dist/src/server/restart-policy.d.ts +1 -1
- package/dist/src/server/restart-policy.js +19 -3
- package/dist/src/server/route-types.d.ts +1 -1
- package/dist/src/server/routes-dispatches.js +1 -1
- package/dist/src/server/routes-fs.js +3 -3
- package/dist/src/server/routes-marketplace.js +2 -2
- package/dist/src/server/routes-open-workspace.js +1 -1
- package/dist/src/server/routes-remote.d.ts +2 -0
- package/dist/src/server/routes-remote.js +166 -0
- package/dist/src/server/routes-runtime.js +6 -6
- package/dist/src/server/routes-settings.js +16 -16
- package/dist/src/server/routes-tasks.js +2 -2
- package/dist/src/server/routes-team-memory.d.ts +2 -0
- package/dist/src/server/routes-team-memory.js +154 -0
- package/dist/src/server/routes-team-recall.d.ts +2 -0
- package/dist/src/server/routes-team-recall.js +119 -0
- package/dist/src/server/routes-team.js +31 -9
- package/dist/src/server/routes-ui.js +11 -1
- package/dist/src/server/routes-workflow-schedules.js +3 -3
- package/dist/src/server/routes-workflows.js +5 -5
- package/dist/src/server/routes-workspace-memory-dreams.d.ts +2 -0
- package/dist/src/server/routes-workspace-memory-dreams.js +105 -0
- package/dist/src/server/routes-workspace-memory.d.ts +2 -0
- package/dist/src/server/routes-workspace-memory.js +215 -0
- package/dist/src/server/routes-workspaces.js +9 -9
- package/dist/src/server/routes.js +10 -0
- package/dist/src/server/runtime-database.d.ts +1 -0
- package/dist/src/server/runtime-database.js +27 -2
- package/dist/src/server/runtime-restart-policy.d.ts +3 -1
- package/dist/src/server/runtime-restart-policy.js +2 -1
- package/dist/src/server/runtime-store-contract.d.ts +37 -0
- package/dist/src/server/runtime-store-dream.d.ts +23 -0
- package/dist/src/server/runtime-store-dream.js +16 -0
- package/dist/src/server/runtime-store-helpers.d.ts +20 -0
- package/dist/src/server/runtime-store-helpers.js +81 -7
- package/dist/src/server/runtime-store-memory.d.ts +33 -0
- package/dist/src/server/runtime-store-memory.js +37 -0
- package/dist/src/server/runtime-store-remote.d.ts +5 -0
- package/dist/src/server/runtime-store-remote.js +45 -0
- package/dist/src/server/runtime-store-workflows.js +2 -0
- package/dist/src/server/runtime-store.js +14 -3
- package/dist/src/server/session-capture-claude.d.ts +1 -1
- package/dist/src/server/session-capture-claude.js +7 -4
- package/dist/src/server/session-capture-codex.js +4 -5
- package/dist/src/server/session-capture-gemini.js +4 -5
- package/dist/src/server/session-capture-opencode.d.ts +4 -4
- package/dist/src/server/session-capture-opencode.js +20 -12
- package/dist/src/server/session-capture-qwen.d.ts +5 -0
- package/dist/src/server/session-capture-qwen.js +104 -0
- package/dist/src/server/session-capture.d.ts +17 -0
- package/dist/src/server/session-capture.js +16 -0
- package/dist/src/server/sqlite-schema-v23.d.ts +2 -0
- package/dist/src/server/sqlite-schema-v23.js +43 -0
- package/dist/src/server/sqlite-schema-v24.d.ts +2 -0
- package/dist/src/server/sqlite-schema-v24.js +34 -0
- package/dist/src/server/sqlite-schema-v25.d.ts +2 -0
- package/dist/src/server/sqlite-schema-v25.js +127 -0
- package/dist/src/server/sqlite-schema-v26.d.ts +2 -0
- package/dist/src/server/sqlite-schema-v26.js +56 -0
- package/dist/src/server/sqlite-schema-v27.d.ts +6 -0
- package/dist/src/server/sqlite-schema-v27.js +92 -0
- package/dist/src/server/sqlite-schema-v28.d.ts +2 -0
- package/dist/src/server/sqlite-schema-v28.js +19 -0
- package/dist/src/server/sqlite-schema-v29.d.ts +2 -0
- package/dist/src/server/sqlite-schema-v29.js +27 -0
- package/dist/src/server/sqlite-schema-v30.d.ts +2 -0
- package/dist/src/server/sqlite-schema-v30.js +27 -0
- package/dist/src/server/sqlite-schema-v31.d.ts +2 -0
- package/dist/src/server/sqlite-schema-v31.js +30 -0
- package/dist/src/server/sqlite-schema.d.ts +1 -1
- package/dist/src/server/sqlite-schema.js +49 -1
- package/dist/src/server/startup-command-parser.js +5 -1
- package/dist/src/server/tasks-file-watcher.d.ts +2 -0
- package/dist/src/server/tasks-file-watcher.js +15 -6
- package/dist/src/server/tasks-file.js +30 -5
- package/dist/src/server/tasks-websocket-server.js +4 -0
- package/dist/src/server/team-authz.d.ts +1 -1
- package/dist/src/server/team-authz.js +13 -1
- package/dist/src/server/team-list-enrichment.js +3 -1
- package/dist/src/server/team-memory-digest.d.ts +52 -0
- package/dist/src/server/team-memory-digest.js +200 -0
- package/dist/src/server/team-memory-dream-applier.d.ts +5 -0
- package/dist/src/server/team-memory-dream-applier.js +234 -0
- package/dist/src/server/team-memory-dream-http-serializers.d.ts +13 -0
- package/dist/src/server/team-memory-dream-http-serializers.js +12 -0
- package/dist/src/server/team-memory-dream-ops.d.ts +40 -0
- package/dist/src/server/team-memory-dream-ops.js +153 -0
- package/dist/src/server/team-memory-dream-reverter.d.ts +22 -0
- package/dist/src/server/team-memory-dream-reverter.js +221 -0
- package/dist/src/server/team-memory-dream-run-store.d.ts +23 -0
- package/dist/src/server/team-memory-dream-run-store.js +211 -0
- package/dist/src/server/team-memory-dream-runner.d.ts +37 -0
- package/dist/src/server/team-memory-dream-runner.js +178 -0
- package/dist/src/server/team-memory-dream-scheduler.d.ts +32 -0
- package/dist/src/server/team-memory-dream-scheduler.js +115 -0
- package/dist/src/server/team-memory-dream-store.d.ts +19 -0
- package/dist/src/server/team-memory-dream-store.js +16 -0
- package/dist/src/server/team-memory-dream-types.d.ts +104 -0
- package/dist/src/server/team-memory-dream-types.js +23 -0
- package/dist/src/server/team-memory-export.d.ts +22 -0
- package/dist/src/server/team-memory-export.js +220 -0
- package/dist/src/server/team-memory-feature.d.ts +12 -0
- package/dist/src/server/team-memory-feature.js +12 -0
- package/dist/src/server/team-memory-http-serializers.d.ts +102 -0
- package/dist/src/server/team-memory-http-serializers.js +46 -0
- package/dist/src/server/team-memory-injection.d.ts +31 -0
- package/dist/src/server/team-memory-injection.js +49 -0
- package/dist/src/server/team-memory-store.d.ts +116 -0
- package/dist/src/server/team-memory-store.js +513 -0
- package/dist/src/server/team-operations.d.ts +5 -1
- package/dist/src/server/team-operations.js +46 -16
- package/dist/src/server/team-recall-store.d.ts +38 -0
- package/dist/src/server/team-recall-store.js +205 -0
- package/dist/src/server/terminal-input-profile.d.ts +1 -1
- package/dist/src/server/terminal-input-profile.js +18 -0
- package/dist/src/server/terminal-ws-server.js +6 -0
- package/dist/src/server/ui-auth-helpers.d.ts +1 -1
- package/dist/src/server/ui-auth-helpers.js +7 -1
- package/dist/src/server/ui-auth.d.ts +3 -0
- package/dist/src/server/ui-auth.js +21 -1
- package/dist/src/server/workflow-cli-policy.d.ts +2 -3
- package/dist/src/server/workflow-cli-policy.js +3 -3
- package/dist/src/server/workflow-runner.d.ts +1 -0
- package/dist/src/server/workflow-runner.js +9 -4
- package/dist/src/server/workspace-path-validation.js +6 -2
- package/dist/src/server/workspace-store.d.ts +1 -1
- package/dist/src/server/workspace-store.js +35 -9
- package/dist/src/shared/fs-browse.d.ts +1 -0
- package/dist/src/shared/fs-browse.js +1 -0
- package/dist/src/shared/path-input.d.ts +12 -0
- package/dist/src/shared/path-input.js +22 -0
- package/dist/src/shared/remote-bridge-routing.d.ts +19 -0
- package/dist/src/shared/remote-bridge-routing.js +141 -0
- package/dist/src/shared/remote-crypto.d.ts +138 -0
- package/dist/src/shared/remote-crypto.js +427 -0
- package/dist/src/shared/remote-pairing-code.d.ts +7 -0
- package/dist/src/shared/remote-pairing-code.js +47 -0
- package/dist/src/shared/remote-protocol.d.ts +160 -0
- package/dist/src/shared/remote-protocol.js +526 -0
- package/dist/src/shared/team-memory.d.ts +11 -0
- package/dist/src/shared/team-memory.js +10 -0
- package/dist/src/shared/team-recall.d.ts +1 -0
- package/dist/src/shared/team-recall.js +1 -0
- package/dist/src/shared/types.d.ts +4 -5
- package/package.json +12 -5
- package/scripts/postinstall-native-artifacts.mjs +113 -0
- package/web/dist/assets/AddWorkerDialog-CbV75qUX.js +2 -0
- package/web/dist/assets/AddWorkspaceFlow-CwV-7wPx.js +1 -0
- package/web/dist/assets/FirstRunWizard-a6PWIK3x.js +1 -0
- package/web/dist/assets/MarketplaceDrawer-Dd8WIA8T.js +67 -0
- package/web/dist/assets/TaskGraphDrawer-Bk5WFIk_.js +1 -0
- package/web/dist/assets/{WhatsNewDialog-CHkZeINH.js → WhatsNewDialog-C2VZaip0.js} +1 -1
- package/web/dist/assets/WorkerModal-DucW-9YT.js +1 -0
- package/web/dist/assets/WorkflowsDrawer-Bjf4olbR.js +1 -0
- package/web/dist/assets/WorkspaceMemoryDrawer-DglCy_5f.js +1 -0
- package/web/dist/assets/WorkspaceTaskDrawer-BIWwISvA.js +1 -0
- package/web/dist/assets/index-BAiLYajK.css +1 -0
- package/web/dist/assets/index-BV2k9Dts.js +73 -0
- package/web/dist/assets/search-Bk2HQvO7.js +1 -0
- package/web/dist/assets/square-terminal-D93m9hfY.js +1 -0
- package/web/dist/cli-icons/agy.png +0 -0
- package/web/dist/cli-icons/cursor.ico +0 -0
- package/web/dist/cli-icons/grok.ico +0 -0
- package/web/dist/cli-icons/qwen.png +0 -0
- package/web/dist/index.html +8 -3
- package/web/dist/sw.js +1 -1
- package/scripts/fix-runtime-artifacts.mjs +0 -33
- package/web/dist/assets/AddWorkerDialog-BRUxpa3f.js +0 -2
- package/web/dist/assets/AddWorkspaceDialog-D56x5JCb.js +0 -1
- package/web/dist/assets/FirstRunWizard-BFVaMIsE.js +0 -1
- package/web/dist/assets/MarketplaceDrawer-DeEZ35dN.js +0 -76
- package/web/dist/assets/WorkerModal-BBCuMLIa.js +0 -1
- package/web/dist/assets/WorkspaceTaskDrawer-CpZHAcj1.js +0 -1
- package/web/dist/assets/WorkspaceTerminalPanels-7If2mDyp.js +0 -1
- package/web/dist/assets/WorkspaceTerminalPanels-DDGTF8rc.css +0 -1
- package/web/dist/assets/index-5zh61jMg.css +0 -1
- package/web/dist/assets/index-CxNL0O-C.js +0 -73
- 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
|
-
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
export const CANONICAL_WORKFLOW_CLIS =
|
|
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
|
|
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 = [
|
|
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' }, {
|
|
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'
|
|
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,
|
|
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,
|
|
16
|
+
export const createWorkspaceStore = (db, listOpenDispatchKinds) => {
|
|
17
17
|
const workspaces = new Map();
|
|
18
|
-
seedWorkspacesFromDb(db, workspaces,
|
|
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,
|
|
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
|
|
118
|
-
|
|
119
|
-
|
|
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,
|
|
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;
|