@tt-a1i/hive 1.6.0 → 2.0.1
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 +65 -0
- package/README.en.md +74 -11
- package/README.md +42 -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 +20 -1
- package/dist/src/server/agent-run-bootstrap.js +16 -6
- 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 +37 -6
- 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 +68 -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 +3 -1
- 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 +23 -0
- package/dist/src/server/session-capture.js +48 -0
- package/dist/src/server/sqlite-schema-v22.d.ts +2 -0
- package/dist/src/server/sqlite-schema-v22.js +27 -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 +54 -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 +8 -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 +5 -6
- package/package.json +12 -5
- package/scripts/postinstall-native-artifacts.mjs +113 -0
- package/web/dist/assets/AddWorkerDialog-C86CwNgQ.js +2 -0
- package/web/dist/assets/AddWorkspaceFlow-Bm2Jz34D.js +1 -0
- package/web/dist/assets/FirstRunWizard-XzBoEpA5.js +1 -0
- package/web/dist/assets/MarketplaceDrawer-BFfGT8hH.js +67 -0
- package/web/dist/assets/TaskGraphDrawer-_uVH_0C1.js +1 -0
- package/web/dist/assets/{WhatsNewDialog-CSGzk-2U.js → WhatsNewDialog-DkJHmkMs.js} +1 -1
- package/web/dist/assets/WorkerModal-BtMJEOG9.js +1 -0
- package/web/dist/assets/WorkflowsDrawer-CiIdHS6_.js +1 -0
- package/web/dist/assets/WorkspaceMemoryDrawer-C6sNocl_.js +1 -0
- package/web/dist/assets/WorkspaceTaskDrawer-CyhhEB1Z.js +1 -0
- package/web/dist/assets/index-BAiLYajK.css +1 -0
- package/web/dist/assets/index-K-GG8UwR.js +73 -0
- package/web/dist/assets/search-BtRkkEmS.js +1 -0
- package/web/dist/assets/square-terminal-lEeQUWb3.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/hermes.png +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-CGbaxu0T.js +0 -2
- package/web/dist/assets/AddWorkspaceDialog-CNgExu6b.js +0 -1
- package/web/dist/assets/FirstRunWizard-DxGApUNc.js +0 -1
- package/web/dist/assets/MarketplaceDrawer-Bk6cpukn.js +0 -76
- package/web/dist/assets/WorkerModal-i2F3n3nZ.js +0 -1
- package/web/dist/assets/WorkspaceTaskDrawer-C_Ta_K13.js +0 -1
- package/web/dist/assets/WorkspaceTerminalPanels-DDGTF8rc.css +0 -1
- package/web/dist/assets/WorkspaceTerminalPanels-VdDxtrQF.js +0 -1
- package/web/dist/assets/index-5zh61jMg.css +0 -1
- package/web/dist/assets/index-CAgGM6nb.js +0 -75
- package/web/dist/assets/path-join-7MR1s7b1.js +0 -1
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { Direction } from '../shared/remote-crypto.js';
|
|
2
|
+
export interface DeviceSession {
|
|
3
|
+
deviceId: string;
|
|
4
|
+
/**
|
|
5
|
+
* 32-byte directional ROOT keys from M1 deriveDaemonSession. M6.1: these are ROOTS — the bridge
|
|
6
|
+
* derives the per-connection AEAD keys from them via deriveConnectionKeys + the bilateral connection
|
|
7
|
+
* salts, and seals/opens ONLY under those connKeys. These bytes are NEVER passed to sealNext/openNext
|
|
8
|
+
* directly; that is what makes resetting seq to 0 per connection safe (the AEAD key is fresh per
|
|
9
|
+
* connection even though the root is persisted/reused).
|
|
10
|
+
*/
|
|
11
|
+
keys: {
|
|
12
|
+
d2p: Uint8Array;
|
|
13
|
+
p2d: Uint8Array;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export interface DeviceSessionProvider {
|
|
17
|
+
/**
|
|
18
|
+
* null => unknown / revoked / no established session. The tunnel resets the stream and audits
|
|
19
|
+
* 'no_session'; it never bridges a frame for a device it has no key for.
|
|
20
|
+
*/
|
|
21
|
+
get(deviceId: string): DeviceSession | null;
|
|
22
|
+
/**
|
|
23
|
+
* Every established session. Used ONLY to resolve which device a CHANNEL_STREAM_ID Hello came
|
|
24
|
+
* from: the relay does not tag the source device, so the daemon trial-opens the Hello against
|
|
25
|
+
* each candidate's p2d key until one authenticates (AEAD makes a wrong key fail cleanly). After
|
|
26
|
+
* the Hello opens, the daemon binds (deviceId, streamId)->device and never trial-opens that
|
|
27
|
+
* stream again. deviceId is therefore only ever the result of a successful open (invariant 5).
|
|
28
|
+
*/
|
|
29
|
+
candidates(): DeviceSession[];
|
|
30
|
+
}
|
|
31
|
+
export declare const DAEMON_OPEN_DIRECTION: Direction;
|
|
32
|
+
export declare const DAEMON_SEAL_DIRECTION: Direction;
|
|
33
|
+
export declare class InMemoryDeviceSessionProvider implements DeviceSessionProvider {
|
|
34
|
+
private readonly sessions;
|
|
35
|
+
set(session: DeviceSession): void;
|
|
36
|
+
/** Models a revoke: the key is gone, so the next frame for this device fails 'no_session'. */
|
|
37
|
+
remove(deviceId: string): void;
|
|
38
|
+
get(deviceId: string): DeviceSession | null;
|
|
39
|
+
candidates(): DeviceSession[];
|
|
40
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// Daemon directions — the mirror of the device's. Exported so the tunnel, the bridge and the tests
|
|
2
|
+
// all agree on ONE source of truth for which key opens/seals which way.
|
|
3
|
+
// - the daemon OPENS inbound phone→daemon frames with p2d
|
|
4
|
+
// - the daemon SEALS outbound daemon→phone frames with d2p
|
|
5
|
+
export const DAEMON_OPEN_DIRECTION = 'p2d';
|
|
6
|
+
export const DAEMON_SEAL_DIRECTION = 'd2p';
|
|
7
|
+
export class InMemoryDeviceSessionProvider {
|
|
8
|
+
sessions = new Map();
|
|
9
|
+
set(session) {
|
|
10
|
+
this.sessions.set(session.deviceId, session);
|
|
11
|
+
}
|
|
12
|
+
/** Models a revoke: the key is gone, so the next frame for this device fails 'no_session'. */
|
|
13
|
+
remove(deviceId) {
|
|
14
|
+
this.sessions.delete(deviceId);
|
|
15
|
+
}
|
|
16
|
+
get(deviceId) {
|
|
17
|
+
return this.sessions.get(deviceId) ?? null;
|
|
18
|
+
}
|
|
19
|
+
candidates() {
|
|
20
|
+
return [...this.sessions.values()];
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { Database } from 'better-sqlite3';
|
|
2
|
+
import type { DeviceSession, DeviceSessionProvider } from './remote-device-session.js';
|
|
3
|
+
export interface RemoteDeviceRecord {
|
|
4
|
+
id: string;
|
|
5
|
+
name: string;
|
|
6
|
+
createdAt: number;
|
|
7
|
+
lastActive: number | null;
|
|
8
|
+
revokedAt: number | null;
|
|
9
|
+
}
|
|
10
|
+
export interface PersistDeviceInput {
|
|
11
|
+
id: string;
|
|
12
|
+
name: string;
|
|
13
|
+
/** The M3 DeviceSession.keys — a stored secret. */
|
|
14
|
+
keys: {
|
|
15
|
+
d2p: Uint8Array;
|
|
16
|
+
p2d: Uint8Array;
|
|
17
|
+
};
|
|
18
|
+
devicePublicKey: Uint8Array;
|
|
19
|
+
}
|
|
20
|
+
export interface RemoteDeviceStore {
|
|
21
|
+
/** TRUST-ROOT write. ONLY remote-pairing.confirmPairing calls this, ONLY on desktop confirm. */
|
|
22
|
+
insert(input: PersistDeviceInput, now?: number): RemoteDeviceRecord;
|
|
23
|
+
/** Provider read path (returns key material). null if absent OR revoked. INTERNAL. */
|
|
24
|
+
getLiveSession(deviceId: string): DeviceSession | null;
|
|
25
|
+
/** Active (non-revoked) sessions only — backs DeviceSessionProvider.candidates(). */
|
|
26
|
+
liveSessions(): DeviceSession[];
|
|
27
|
+
/** Device-management read path — metadata ONLY, no keys. Newest first. */
|
|
28
|
+
list(includeRevoked?: boolean): RemoteDeviceRecord[];
|
|
29
|
+
get(deviceId: string): RemoteDeviceRecord | null;
|
|
30
|
+
/** Local half of the revocation closed loop. Idempotent; false if unknown / already revoked. */
|
|
31
|
+
revoke(deviceId: string, now?: number): boolean;
|
|
32
|
+
/** Best-effort last_active bump. Never resurrects a revoked row. */
|
|
33
|
+
touchActive(deviceId: string, now?: number): void;
|
|
34
|
+
}
|
|
35
|
+
export declare const createRemoteDeviceStore: (db: Database) => RemoteDeviceStore;
|
|
36
|
+
export declare const createPersistentDeviceSessionProvider: (store: RemoteDeviceStore) => DeviceSessionProvider;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { fromBase64Url, toBase64Url } from '../shared/remote-crypto.js';
|
|
2
|
+
const toRecord = (row) => ({
|
|
3
|
+
id: row.id,
|
|
4
|
+
name: row.name,
|
|
5
|
+
createdAt: row.created_at,
|
|
6
|
+
lastActive: row.last_active,
|
|
7
|
+
revokedAt: row.revoked_at,
|
|
8
|
+
});
|
|
9
|
+
const toSession = (row) => ({
|
|
10
|
+
deviceId: row.id,
|
|
11
|
+
keys: { d2p: fromBase64Url(row.key_d2p), p2d: fromBase64Url(row.key_p2d) },
|
|
12
|
+
});
|
|
13
|
+
export const createRemoteDeviceStore = (db) => {
|
|
14
|
+
const insertStmt = db.prepare(`INSERT INTO remote_devices
|
|
15
|
+
(id, name, key_d2p, key_p2d, device_pubkey, created_at, last_active, revoked_at)
|
|
16
|
+
VALUES (?, ?, ?, ?, ?, ?, NULL, NULL)`);
|
|
17
|
+
// Note: the SELECT projections for the metadata path deliberately OMIT key_d2p/key_p2d/device_pubkey
|
|
18
|
+
// so key material can never escape through list()/get() (invariant 7).
|
|
19
|
+
const getMetaStmt = db.prepare(`SELECT id, name, created_at, last_active, revoked_at FROM remote_devices WHERE id = ?`);
|
|
20
|
+
const listStmt = db.prepare(`SELECT id, name, created_at, last_active, revoked_at
|
|
21
|
+
FROM remote_devices
|
|
22
|
+
ORDER BY created_at DESC, id DESC`);
|
|
23
|
+
const listActiveStmt = db.prepare(`SELECT id, name, created_at, last_active, revoked_at
|
|
24
|
+
FROM remote_devices
|
|
25
|
+
WHERE revoked_at IS NULL
|
|
26
|
+
ORDER BY created_at DESC, id DESC`);
|
|
27
|
+
const liveSessionStmt = db.prepare(`SELECT id, key_d2p, key_p2d FROM remote_devices WHERE id = ? AND revoked_at IS NULL`);
|
|
28
|
+
const liveSessionsStmt = db.prepare(`SELECT id, key_d2p, key_p2d FROM remote_devices WHERE revoked_at IS NULL`);
|
|
29
|
+
// revoke only flips a row that is not already revoked, so a second call changes 0 rows -> false,
|
|
30
|
+
// and the original revoked_at timestamp is never overwritten.
|
|
31
|
+
const revokeStmt = db.prepare(`UPDATE remote_devices SET revoked_at = ? WHERE id = ? AND revoked_at IS NULL`);
|
|
32
|
+
const touchStmt = db.prepare(`UPDATE remote_devices SET last_active = ? WHERE id = ? AND revoked_at IS NULL`);
|
|
33
|
+
return {
|
|
34
|
+
insert(input, now = Date.now()) {
|
|
35
|
+
insertStmt.run(input.id, input.name, toBase64Url(input.keys.d2p), toBase64Url(input.keys.p2d), toBase64Url(input.devicePublicKey), now);
|
|
36
|
+
return { id: input.id, name: input.name, createdAt: now, lastActive: null, revokedAt: null };
|
|
37
|
+
},
|
|
38
|
+
getLiveSession(deviceId) {
|
|
39
|
+
const row = liveSessionStmt.get(deviceId);
|
|
40
|
+
return row ? toSession(row) : null;
|
|
41
|
+
},
|
|
42
|
+
liveSessions() {
|
|
43
|
+
return liveSessionsStmt.all().map(toSession);
|
|
44
|
+
},
|
|
45
|
+
list(includeRevoked = false) {
|
|
46
|
+
const rows = (includeRevoked ? listStmt : listActiveStmt).all();
|
|
47
|
+
return rows.map(toRecord);
|
|
48
|
+
},
|
|
49
|
+
get(deviceId) {
|
|
50
|
+
const row = getMetaStmt.get(deviceId);
|
|
51
|
+
return row ? toRecord(row) : null;
|
|
52
|
+
},
|
|
53
|
+
revoke(deviceId, now = Date.now()) {
|
|
54
|
+
return revokeStmt.run(now, deviceId).changes > 0;
|
|
55
|
+
},
|
|
56
|
+
touchActive(deviceId, now = Date.now()) {
|
|
57
|
+
touchStmt.run(now, deviceId);
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
};
|
|
61
|
+
// Persistent DeviceSessionProvider — no cache. get()/candidates() read the store live, so a revoke()
|
|
62
|
+
// write makes the next inbound frame fail in the M3 bridge (resolveAndOpen -> get null / candidate
|
|
63
|
+
// gone -> drop + audit 'no_session'). Zero bridge change for the persistence half (invariant 5).
|
|
64
|
+
export const createPersistentDeviceSessionProvider = (store) => ({
|
|
65
|
+
get: (deviceId) => store.getLiveSession(deviceId),
|
|
66
|
+
candidates: () => store.liveSessions(),
|
|
67
|
+
});
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { type Direction } from '../shared/remote-crypto.js';
|
|
2
|
+
import { type HttpResponseHead } from '../shared/remote-protocol.js';
|
|
3
|
+
import type { RemoteAuditStore } from './remote-audit-store.js';
|
|
4
|
+
import { type DeviceSessionProvider } from './remote-device-session.js';
|
|
5
|
+
/** A loopback HTTP request in flight. The bridge feeds it body chunks then end()s it. */
|
|
6
|
+
export interface LoopbackHttpRequest {
|
|
7
|
+
onData(chunk: Uint8Array): void;
|
|
8
|
+
onEnd(): void;
|
|
9
|
+
abort(): void;
|
|
10
|
+
}
|
|
11
|
+
export interface LoopbackHttpHandlers {
|
|
12
|
+
onHead(head: HttpResponseHead): void;
|
|
13
|
+
onBody(chunk: Uint8Array): void;
|
|
14
|
+
onEnd(): void;
|
|
15
|
+
onError(err: Error): void;
|
|
16
|
+
}
|
|
17
|
+
/** A loopback WS connection in flight. */
|
|
18
|
+
export interface LoopbackWsConnection {
|
|
19
|
+
onData(data: Uint8Array, isText: boolean): void;
|
|
20
|
+
onClose(): void;
|
|
21
|
+
abort(): void;
|
|
22
|
+
}
|
|
23
|
+
export interface LoopbackWsHandlers {
|
|
24
|
+
onOpen(): void;
|
|
25
|
+
onMessage(data: Uint8Array, isText: boolean): void;
|
|
26
|
+
onClose(): void;
|
|
27
|
+
onError(err: Error): void;
|
|
28
|
+
}
|
|
29
|
+
export interface LoopbackTransports {
|
|
30
|
+
openHttp(args: {
|
|
31
|
+
port: number;
|
|
32
|
+
method: string;
|
|
33
|
+
path: string;
|
|
34
|
+
headers: Record<string, string>;
|
|
35
|
+
}, handlers: LoopbackHttpHandlers): LoopbackHttpRequest;
|
|
36
|
+
openWs(args: {
|
|
37
|
+
port: number;
|
|
38
|
+
path: string;
|
|
39
|
+
headers: Record<string, string>;
|
|
40
|
+
}, handlers: LoopbackWsHandlers): LoopbackWsConnection;
|
|
41
|
+
}
|
|
42
|
+
export interface FrameBridgeContext {
|
|
43
|
+
loopbackPort: number;
|
|
44
|
+
loopbackSecret: string;
|
|
45
|
+
deviceSessions: DeviceSessionProvider;
|
|
46
|
+
audit: RemoteAuditStore;
|
|
47
|
+
/**
|
|
48
|
+
* The daemon's own id (config.getDaemonId()). M6.1: it is bound into the per-connection HKDF info,
|
|
49
|
+
* so it MUST be the SAME value the phone put in its HandshakeIds.daemonId — a mismatch diverges the
|
|
50
|
+
* info strings and EVERY Hello fails to open (total outage). Sourced at socket-open time in
|
|
51
|
+
* remote-tunnel.ts (guarded non-null; the tunnel can't be online without it).
|
|
52
|
+
*/
|
|
53
|
+
daemonId: string;
|
|
54
|
+
/**
|
|
55
|
+
* Injected loopback transports. Production omits this and gets the real node:http / ws clients;
|
|
56
|
+
* unit tests pass a stub so a single bridged request is observable without real I/O. Carried on
|
|
57
|
+
* the context (not a separate arg) so the tunnel's `createBridge: (ctx) => createFrameBridge(ctx)`
|
|
58
|
+
* wiring is untouched and a test can simply add the field.
|
|
59
|
+
*/
|
|
60
|
+
loopbackTransports?: LoopbackTransports;
|
|
61
|
+
/**
|
|
62
|
+
* Seam: the per-connection daemon salt source. Defaults to the crypto export; a test injects a
|
|
63
|
+
* deterministic-but-distinct generator so it can recompute the connKey + nonce. NOT a mock — the
|
|
64
|
+
* real HKDF still runs over whatever bytes this returns.
|
|
65
|
+
*/
|
|
66
|
+
generateConnSalt?: () => Uint8Array;
|
|
67
|
+
/**
|
|
68
|
+
* Observation hook (NOT a mock): fires for every daemon->phone seal with the REAL AEAD key + REAL
|
|
69
|
+
* 12-byte header. Lets the no-(key,nonce)-reuse + no-downgrade invariants be mutation-tested by a
|
|
70
|
+
* recorder; the production sealNext still runs unchanged.
|
|
71
|
+
*/
|
|
72
|
+
onSeal?: (rec: {
|
|
73
|
+
key: Uint8Array;
|
|
74
|
+
direction: Direction;
|
|
75
|
+
headerBytes: Uint8Array;
|
|
76
|
+
}) => void;
|
|
77
|
+
}
|
|
78
|
+
export interface FrameBridge {
|
|
79
|
+
attachSocket(send: (frame: Uint8Array) => void): void;
|
|
80
|
+
onInbound(frame: ArrayBuffer | Uint8Array): void;
|
|
81
|
+
/**
|
|
82
|
+
* Tear down every in-flight stream. `keepSink` distinguishes the two callers:
|
|
83
|
+
* - socket teardown (onSocketDown / revokeAndStop / close): the outbound socket is gone, so we
|
|
84
|
+
* also null the sink (keepSink omitted/false) and audit a session_close.
|
|
85
|
+
* - gateway 'peer-offline' control: the daemon socket STAYS OPEN (the gateway just told us the
|
|
86
|
+
* phone dropped its streams). We reset the in-flight streams but MUST keep `send` live so the
|
|
87
|
+
* phone can re-establish streams on the same socket after 'peer-online'. Audited as a
|
|
88
|
+
* stream-reset, not a session_close.
|
|
89
|
+
*/
|
|
90
|
+
resetAllStreams(reason: string, opts?: {
|
|
91
|
+
keepSink?: boolean;
|
|
92
|
+
}): void;
|
|
93
|
+
/**
|
|
94
|
+
* Close ONE device's in-flight streams (M4 revoke closed loop). Unlike resetAllStreams this leaves
|
|
95
|
+
* other devices + the outbound sink untouched, so revoking device A never tears down device B. The
|
|
96
|
+
* per-device opener/sealer is dropped too, so a re-pair of the same id starts from clean crypto
|
|
97
|
+
* state. The persistent provider's revoke (the security-load-bearing half) already makes NEW frames
|
|
98
|
+
* fail with no_session; this is the best-effort liveness half that kills an ALREADY-open stream now.
|
|
99
|
+
*/
|
|
100
|
+
closeDevice(deviceId: string, reason: string): void;
|
|
101
|
+
}
|
|
102
|
+
export declare const createFrameBridge: (ctx: FrameBridgeContext) => FrameBridge;
|