uxnan-bridge 0.0.1-alpha.20260621
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.
Potentially problematic release.
This version of uxnan-bridge might be problematic. Click here for more details.
- package/README.md +150 -0
- package/dist/src/account-status.d.ts +13 -0
- package/dist/src/account-status.js +78 -0
- package/dist/src/account-status.js.map +1 -0
- package/dist/src/adapters/base-adapter.d.ts +18 -0
- package/dist/src/adapters/base-adapter.js +15 -0
- package/dist/src/adapters/base-adapter.js.map +1 -0
- package/dist/src/adapters/claude-adapter.d.ts +102 -0
- package/dist/src/adapters/claude-adapter.js +486 -0
- package/dist/src/adapters/claude-adapter.js.map +1 -0
- package/dist/src/adapters/claude-tools.d.ts +25 -0
- package/dist/src/adapters/claude-tools.js +146 -0
- package/dist/src/adapters/claude-tools.js.map +1 -0
- package/dist/src/adapters/codex-adapter.d.ts +116 -0
- package/dist/src/adapters/codex-adapter.js +912 -0
- package/dist/src/adapters/codex-adapter.js.map +1 -0
- package/dist/src/adapters/codex-app-server.d.ts +74 -0
- package/dist/src/adapters/codex-app-server.js +225 -0
- package/dist/src/adapters/codex-app-server.js.map +1 -0
- package/dist/src/adapters/codex-approval.d.ts +88 -0
- package/dist/src/adapters/codex-approval.js +160 -0
- package/dist/src/adapters/codex-approval.js.map +1 -0
- package/dist/src/adapters/codex-tools.d.ts +18 -0
- package/dist/src/adapters/codex-tools.js +106 -0
- package/dist/src/adapters/codex-tools.js.map +1 -0
- package/dist/src/adapters/content-blocks.d.ts +68 -0
- package/dist/src/adapters/content-blocks.js +205 -0
- package/dist/src/adapters/content-blocks.js.map +1 -0
- package/dist/src/adapters/echo-agent-adapter.d.ts +23 -0
- package/dist/src/adapters/echo-agent-adapter.js +72 -0
- package/dist/src/adapters/echo-agent-adapter.js.map +1 -0
- package/dist/src/adapters/gemini-adapter.d.ts +87 -0
- package/dist/src/adapters/gemini-adapter.js +594 -0
- package/dist/src/adapters/gemini-adapter.js.map +1 -0
- package/dist/src/adapters/gemini-tools.d.ts +4 -0
- package/dist/src/adapters/gemini-tools.js +48 -0
- package/dist/src/adapters/gemini-tools.js.map +1 -0
- package/dist/src/adapters/opencode-adapter.d.ts +74 -0
- package/dist/src/adapters/opencode-adapter.js +418 -0
- package/dist/src/adapters/opencode-adapter.js.map +1 -0
- package/dist/src/adapters/opencode-tools.d.ts +2 -0
- package/dist/src/adapters/opencode-tools.js +41 -0
- package/dist/src/adapters/opencode-tools.js.map +1 -0
- package/dist/src/adapters/pi-adapter.d.ts +92 -0
- package/dist/src/adapters/pi-adapter.js +467 -0
- package/dist/src/adapters/pi-adapter.js.map +1 -0
- package/dist/src/adapters/pi-tools.d.ts +10 -0
- package/dist/src/adapters/pi-tools.js +72 -0
- package/dist/src/adapters/pi-tools.js.map +1 -0
- package/dist/src/adapters/process-agent-adapter.d.ts +24 -0
- package/dist/src/adapters/process-agent-adapter.js +111 -0
- package/dist/src/adapters/process-agent-adapter.js.map +1 -0
- package/dist/src/adapters/resolve-claude.d.ts +13 -0
- package/dist/src/adapters/resolve-claude.js +57 -0
- package/dist/src/adapters/resolve-claude.js.map +1 -0
- package/dist/src/adapters/resolve-codex.d.ts +13 -0
- package/dist/src/adapters/resolve-codex.js +48 -0
- package/dist/src/adapters/resolve-codex.js.map +1 -0
- package/dist/src/adapters/resolve-gemini.d.ts +13 -0
- package/dist/src/adapters/resolve-gemini.js +47 -0
- package/dist/src/adapters/resolve-gemini.js.map +1 -0
- package/dist/src/adapters/resolve-opencode.d.ts +11 -0
- package/dist/src/adapters/resolve-opencode.js +49 -0
- package/dist/src/adapters/resolve-opencode.js.map +1 -0
- package/dist/src/adapters/resolve-pi.d.ts +13 -0
- package/dist/src/adapters/resolve-pi.js +46 -0
- package/dist/src/adapters/resolve-pi.js.map +1 -0
- package/dist/src/adapters/run-options.d.ts +22 -0
- package/dist/src/adapters/run-options.js +48 -0
- package/dist/src/adapters/run-options.js.map +1 -0
- package/dist/src/adapters/spawn.d.ts +20 -0
- package/dist/src/adapters/spawn.js +16 -0
- package/dist/src/adapters/spawn.js.map +1 -0
- package/dist/src/agents/agent-manager.d.ts +98 -0
- package/dist/src/agents/agent-manager.js +433 -0
- package/dist/src/agents/agent-manager.js.map +1 -0
- package/dist/src/agents/attachments.d.ts +28 -0
- package/dist/src/agents/attachments.js +121 -0
- package/dist/src/agents/attachments.js.map +1 -0
- package/dist/src/bridge-context.d.ts +45 -0
- package/dist/src/bridge-context.js +2 -0
- package/dist/src/bridge-context.js.map +1 -0
- package/dist/src/bridge-status.d.ts +12 -0
- package/dist/src/bridge-status.js +17 -0
- package/dist/src/bridge-status.js.map +1 -0
- package/dist/src/bridge.d.ts +37 -0
- package/dist/src/bridge.js +446 -0
- package/dist/src/bridge.js.map +1 -0
- package/dist/src/cli.d.ts +2 -0
- package/dist/src/cli.js +194 -0
- package/dist/src/cli.js.map +1 -0
- package/dist/src/conversation/session-history.d.ts +27 -0
- package/dist/src/conversation/session-history.js +1082 -0
- package/dist/src/conversation/session-history.js.map +1 -0
- package/dist/src/conversation/thread-store.d.ts +74 -0
- package/dist/src/conversation/thread-store.js +366 -0
- package/dist/src/conversation/thread-store.js.map +1 -0
- package/dist/src/daemon-config.d.ts +123 -0
- package/dist/src/daemon-config.js +64 -0
- package/dist/src/daemon-config.js.map +1 -0
- package/dist/src/daemon-state.d.ts +27 -0
- package/dist/src/daemon-state.js +76 -0
- package/dist/src/daemon-state.js.map +1 -0
- package/dist/src/git/git-runner.d.ts +24 -0
- package/dist/src/git/git-runner.js +63 -0
- package/dist/src/git/git-runner.js.map +1 -0
- package/dist/src/git/git-service.d.ts +76 -0
- package/dist/src/git/git-service.js +435 -0
- package/dist/src/git/git-service.js.map +1 -0
- package/dist/src/handler-router.d.ts +34 -0
- package/dist/src/handler-router.js +67 -0
- package/dist/src/handler-router.js.map +1 -0
- package/dist/src/handlers/account-handler.d.ts +4 -0
- package/dist/src/handlers/account-handler.js +27 -0
- package/dist/src/handlers/account-handler.js.map +1 -0
- package/dist/src/handlers/agent-handler.d.ts +2 -0
- package/dist/src/handlers/agent-handler.js +8 -0
- package/dist/src/handlers/agent-handler.js.map +1 -0
- package/dist/src/handlers/bridge-control-handler.d.ts +2 -0
- package/dist/src/handlers/bridge-control-handler.js +64 -0
- package/dist/src/handlers/bridge-control-handler.js.map +1 -0
- package/dist/src/handlers/desktop-handler.d.ts +12 -0
- package/dist/src/handlers/desktop-handler.js +5 -0
- package/dist/src/handlers/desktop-handler.js.map +1 -0
- package/dist/src/handlers/git-handler.d.ts +2 -0
- package/dist/src/handlers/git-handler.js +82 -0
- package/dist/src/handlers/git-handler.js.map +1 -0
- package/dist/src/handlers/index.d.ts +8 -0
- package/dist/src/handlers/index.js +22 -0
- package/dist/src/handlers/index.js.map +1 -0
- package/dist/src/handlers/not-implemented.d.ts +10 -0
- package/dist/src/handlers/not-implemented.js +21 -0
- package/dist/src/handlers/not-implemented.js.map +1 -0
- package/dist/src/handlers/notifications-handler.d.ts +2 -0
- package/dist/src/handlers/notifications-handler.js +62 -0
- package/dist/src/handlers/notifications-handler.js.map +1 -0
- package/dist/src/handlers/params.d.ts +11 -0
- package/dist/src/handlers/params.js +72 -0
- package/dist/src/handlers/params.js.map +1 -0
- package/dist/src/handlers/project-handler.d.ts +2 -0
- package/dist/src/handlers/project-handler.js +6 -0
- package/dist/src/handlers/project-handler.js.map +1 -0
- package/dist/src/handlers/thread-context-handler.d.ts +2 -0
- package/dist/src/handlers/thread-context-handler.js +211 -0
- package/dist/src/handlers/thread-context-handler.js.map +1 -0
- package/dist/src/handlers/workspace-handler.d.ts +2 -0
- package/dist/src/handlers/workspace-handler.js +101 -0
- package/dist/src/handlers/workspace-handler.js.map +1 -0
- package/dist/src/hooks/claude-approval-hook.d.ts +7 -0
- package/dist/src/hooks/claude-approval-hook.js +95 -0
- package/dist/src/hooks/claude-approval-hook.js.map +1 -0
- package/dist/src/hooks/gemini-approval-hook.d.ts +7 -0
- package/dist/src/hooks/gemini-approval-hook.js +113 -0
- package/dist/src/hooks/gemini-approval-hook.js.map +1 -0
- package/dist/src/index.d.ts +62 -0
- package/dist/src/index.js +65 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/keyring-secret-store.d.ts +36 -0
- package/dist/src/keyring-secret-store.js +70 -0
- package/dist/src/keyring-secret-store.js.map +1 -0
- package/dist/src/lock-file.d.ts +18 -0
- package/dist/src/lock-file.js +60 -0
- package/dist/src/lock-file.js.map +1 -0
- package/dist/src/logger.d.ts +28 -0
- package/dist/src/logger.js +99 -0
- package/dist/src/logger.js.map +1 -0
- package/dist/src/pairing/pairing-code-service.d.ts +45 -0
- package/dist/src/pairing/pairing-code-service.js +183 -0
- package/dist/src/pairing/pairing-code-service.js.map +1 -0
- package/dist/src/projects/project-registry.d.ts +14 -0
- package/dist/src/projects/project-registry.js +60 -0
- package/dist/src/projects/project-registry.js.map +1 -0
- package/dist/src/push/push-sender.d.ts +21 -0
- package/dist/src/push/push-sender.js +96 -0
- package/dist/src/push/push-sender.js.map +1 -0
- package/dist/src/push/push-service.d.ts +122 -0
- package/dist/src/push/push-service.js +260 -0
- package/dist/src/push/push-service.js.map +1 -0
- package/dist/src/qr.d.ts +17 -0
- package/dist/src/qr.js +31 -0
- package/dist/src/qr.js.map +1 -0
- package/dist/src/secret-store.d.ts +23 -0
- package/dist/src/secret-store.js +27 -0
- package/dist/src/secret-store.js.map +1 -0
- package/dist/src/secure-device-state.d.ts +16 -0
- package/dist/src/secure-device-state.js +63 -0
- package/dist/src/secure-device-state.js.map +1 -0
- package/dist/src/service-installer.d.ts +57 -0
- package/dist/src/service-installer.js +254 -0
- package/dist/src/service-installer.js.map +1 -0
- package/dist/src/session-state.d.ts +14 -0
- package/dist/src/session-state.js +19 -0
- package/dist/src/session-state.js.map +1 -0
- package/dist/src/transport/crypto.d.ts +43 -0
- package/dist/src/transport/crypto.js +78 -0
- package/dist/src/transport/crypto.js.map +1 -0
- package/dist/src/transport/lan-server.d.ts +33 -0
- package/dist/src/transport/lan-server.js +105 -0
- package/dist/src/transport/lan-server.js.map +1 -0
- package/dist/src/transport/local-hosts.d.ts +17 -0
- package/dist/src/transport/local-hosts.js +30 -0
- package/dist/src/transport/local-hosts.js.map +1 -0
- package/dist/src/transport/mdns-advertiser.d.ts +83 -0
- package/dist/src/transport/mdns-advertiser.js +282 -0
- package/dist/src/transport/mdns-advertiser.js.map +1 -0
- package/dist/src/transport/message-io.d.ts +33 -0
- package/dist/src/transport/message-io.js +87 -0
- package/dist/src/transport/message-io.js.map +1 -0
- package/dist/src/transport/outbound-log.d.ts +24 -0
- package/dist/src/transport/outbound-log.js +78 -0
- package/dist/src/transport/outbound-log.js.map +1 -0
- package/dist/src/transport/relay-client.d.ts +19 -0
- package/dist/src/transport/relay-client.js +27 -0
- package/dist/src/transport/relay-client.js.map +1 -0
- package/dist/src/transport/secure-channel.d.ts +33 -0
- package/dist/src/transport/secure-channel.js +81 -0
- package/dist/src/transport/secure-channel.js.map +1 -0
- package/dist/src/transport/server-handshake.d.ts +49 -0
- package/dist/src/transport/server-handshake.js +137 -0
- package/dist/src/transport/server-handshake.js.map +1 -0
- package/dist/src/transport/session-handler.d.ts +19 -0
- package/dist/src/transport/session-handler.js +134 -0
- package/dist/src/transport/session-handler.js.map +1 -0
- package/dist/src/transport/session-registry.d.ts +58 -0
- package/dist/src/transport/session-registry.js +91 -0
- package/dist/src/transport/session-registry.js.map +1 -0
- package/dist/src/transport/trust-store.d.ts +23 -0
- package/dist/src/transport/trust-store.js +33 -0
- package/dist/src/transport/trust-store.js.map +1 -0
- package/dist/src/transport/ws-adapter.d.ts +7 -0
- package/dist/src/transport/ws-adapter.js +16 -0
- package/dist/src/transport/ws-adapter.js.map +1 -0
- package/dist/src/version.d.ts +1 -0
- package/dist/src/version.js +13 -0
- package/dist/src/version.js.map +1 -0
- package/dist/src/workspace/browse-service.d.ts +10 -0
- package/dist/src/workspace/browse-service.js +97 -0
- package/dist/src/workspace/browse-service.js.map +1 -0
- package/dist/src/workspace/checkpoint-service.d.ts +21 -0
- package/dist/src/workspace/checkpoint-service.js +219 -0
- package/dist/src/workspace/checkpoint-service.js.map +1 -0
- package/dist/src/workspace/path-guard.d.ts +7 -0
- package/dist/src/workspace/path-guard.js +51 -0
- package/dist/src/workspace/path-guard.js.map +1 -0
- package/dist/src/workspace/workspace-service.d.ts +8 -0
- package/dist/src/workspace/workspace-service.js +111 -0
- package/dist/src/workspace/workspace-service.js.map +1 -0
- package/package.json +46 -0
- package/scripts/extract-gemini-hook.mjs +16 -0
- package/scripts/fake-approval-bridge.mjs +23 -0
- package/scripts/install-service-linux.sh +38 -0
- package/scripts/install-service-macos.sh +38 -0
- package/scripts/install-service-windows.ps1 +26 -0
- package/scripts/test-gemini-hook-e2e.mjs +168 -0
- package/scripts/write-gemini-settings.mjs +31 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Drives a single mobile connection: runs the E2EE handshake, then decrypts
|
|
3
|
+
* inbound envelopes, dispatches the JSON-RPC request through the router, and
|
|
4
|
+
* returns the encrypted response.
|
|
5
|
+
*
|
|
6
|
+
* Transport-agnostic: works over any {@link MessageIO} (relay client or LAN
|
|
7
|
+
* server connection).
|
|
8
|
+
*/
|
|
9
|
+
import { validateE2EEnvelope } from '@uxnan/shared';
|
|
10
|
+
import { queueFor } from './message-io.js';
|
|
11
|
+
import { performServerHandshake } from './server-handshake.js';
|
|
12
|
+
/**
|
|
13
|
+
* Handle a connection end-to-end. Resolves when the connection closes. Never
|
|
14
|
+
* throws — handshake/transport failures are logged and the channel is closed.
|
|
15
|
+
*/
|
|
16
|
+
export async function handleSecureConnection(options) {
|
|
17
|
+
const { io, ctx, router, deviceState, trustStore, displayName } = options;
|
|
18
|
+
const queue = queueFor(io);
|
|
19
|
+
const send = (message) => io.send(Buffer.from(JSON.stringify(message), 'utf-8'));
|
|
20
|
+
let phoneDeviceId;
|
|
21
|
+
let sessionId;
|
|
22
|
+
let sink;
|
|
23
|
+
try {
|
|
24
|
+
const handshakeOptions = {
|
|
25
|
+
queue,
|
|
26
|
+
send,
|
|
27
|
+
deviceState,
|
|
28
|
+
trustStore,
|
|
29
|
+
displayName,
|
|
30
|
+
now: ctx.now,
|
|
31
|
+
// Build the channel over this phone's persistent outbound log so its seq
|
|
32
|
+
// continues across reconnects and outbound messages are retained for
|
|
33
|
+
// seq-based catch-up.
|
|
34
|
+
outboundLogFor: (id) => ctx.sessionRegistry.logFor(id),
|
|
35
|
+
...(options.expectedSessionId !== undefined
|
|
36
|
+
? { expectedSessionId: options.expectedSessionId }
|
|
37
|
+
: {}),
|
|
38
|
+
};
|
|
39
|
+
const result = await performServerHandshake(handshakeOptions);
|
|
40
|
+
phoneDeviceId = result.phoneDeviceId;
|
|
41
|
+
sessionId = result.sessionId;
|
|
42
|
+
// Mark this as the active session so a `notifications/register` from the phone
|
|
43
|
+
// (and later turn-end pushes) target the right relay session.
|
|
44
|
+
ctx.pushService.setActiveSession(result.sessionId);
|
|
45
|
+
// Catch-up (architecture/02a §5.9.2): replay every retained outbound message
|
|
46
|
+
// the phone hasn't applied yet (seq > resumeState.lastAppliedBridgeOutboundSeq),
|
|
47
|
+
// re-encrypted under the new session key, BEFORE registering the live sink so
|
|
48
|
+
// the replayed backlog precedes any new traffic and ordering is preserved.
|
|
49
|
+
const outboundLog = ctx.sessionRegistry.logFor(result.phoneDeviceId);
|
|
50
|
+
for (const entry of outboundLog.entriesAfter(result.lastAppliedBridgeOutboundSeq)) {
|
|
51
|
+
io.send(Buffer.from(JSON.stringify(result.channel.encryptReplay(entry.seq, entry.plaintext)), 'utf-8'));
|
|
52
|
+
}
|
|
53
|
+
// Register the encrypted sink synchronously (before any further await) so the
|
|
54
|
+
// bridge can push notifications immediately. Each live send is encrypted by
|
|
55
|
+
// the channel, which records it in the outbound log for future catch-up.
|
|
56
|
+
// Kept in a variable so the teardown below only fires when this connection
|
|
57
|
+
// is still the current one (a reconnecting phone may have superseded it).
|
|
58
|
+
sink = {
|
|
59
|
+
send: (message) => io.send(Buffer.from(JSON.stringify(result.channel.encrypt(toBytes(message))), 'utf-8')),
|
|
60
|
+
};
|
|
61
|
+
ctx.sessionRegistry.register(result.phoneDeviceId, sink);
|
|
62
|
+
const trusted = await trustStore.get(result.phoneDeviceId);
|
|
63
|
+
ctx.sessions.add({
|
|
64
|
+
deviceId: result.phoneDeviceId,
|
|
65
|
+
displayName: trusted?.displayName ?? result.phoneDeviceId,
|
|
66
|
+
connectedAt: ctx.now(),
|
|
67
|
+
lastSeen: ctx.now(),
|
|
68
|
+
});
|
|
69
|
+
ctx.logger.info(`phone session established (${result.mode}): ${result.phoneDeviceId}`);
|
|
70
|
+
for (;;) {
|
|
71
|
+
const raw = await queue.next();
|
|
72
|
+
const parsed = tryParse(raw);
|
|
73
|
+
const validation = validateE2EEnvelope(parsed);
|
|
74
|
+
if (!validation.valid)
|
|
75
|
+
continue;
|
|
76
|
+
let plaintext;
|
|
77
|
+
try {
|
|
78
|
+
plaintext = result.channel.decrypt(validation.data);
|
|
79
|
+
}
|
|
80
|
+
catch (err) {
|
|
81
|
+
ctx.logger.warn(`envelope rejected: ${errorMessage(err)}`);
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
const request = tryParse(plaintext);
|
|
85
|
+
// Tag the request with this connection's session identity so per-phone
|
|
86
|
+
// handlers (notifications/*) target the right session when several phones
|
|
87
|
+
// are connected concurrently.
|
|
88
|
+
const response = await router.dispatchRaw(request, {
|
|
89
|
+
sessionId: result.sessionId,
|
|
90
|
+
deviceId: result.phoneDeviceId,
|
|
91
|
+
});
|
|
92
|
+
send(result.channel.encrypt(Buffer.from(JSON.stringify(response), 'utf-8')));
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
catch (err) {
|
|
96
|
+
// queue closed (normal disconnect) or handshake failure.
|
|
97
|
+
if (phoneDeviceId === undefined) {
|
|
98
|
+
ctx.logger.warn(`handshake failed: ${errorMessage(err)}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
finally {
|
|
102
|
+
if (phoneDeviceId !== undefined) {
|
|
103
|
+
// Only tear down the shared session state if THIS connection still owns
|
|
104
|
+
// it. A reconnecting phone (LAN/direct) may have opened a newer connection
|
|
105
|
+
// that already replaced our sink + active session while our old socket was
|
|
106
|
+
// still half-open; when our stale connection finally closes, clobbering
|
|
107
|
+
// the newer one's sink/session/active-id would silently kill its push
|
|
108
|
+
// delivery. `unregister` returns false when we were superseded.
|
|
109
|
+
const stillCurrent = sink === undefined || ctx.sessionRegistry.unregister(phoneDeviceId, sink);
|
|
110
|
+
if (stillCurrent) {
|
|
111
|
+
ctx.sessions.remove(phoneDeviceId);
|
|
112
|
+
if (sessionId !== undefined)
|
|
113
|
+
ctx.pushService.clearActiveSession(sessionId);
|
|
114
|
+
}
|
|
115
|
+
ctx.logger.info(`phone session closed: ${phoneDeviceId}`);
|
|
116
|
+
}
|
|
117
|
+
io.close();
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
function toBytes(message) {
|
|
121
|
+
return Buffer.from(JSON.stringify(message), 'utf-8');
|
|
122
|
+
}
|
|
123
|
+
function tryParse(bytes) {
|
|
124
|
+
try {
|
|
125
|
+
return JSON.parse(bytes.toString('utf-8'));
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
function errorMessage(err) {
|
|
132
|
+
return err instanceof Error ? err.message : String(err);
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=session-handler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-handler.js","sourceRoot":"","sources":["../../../src/transport/session-handler.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tracks the live encrypted sink for each connected phone so the bridge can push
|
|
3
|
+
* JSON-RPC notifications (e.g. streamed agent events) to it, and owns each
|
|
4
|
+
* phone's persistent {@link OutboundLog} (seq counter + catch-up window).
|
|
5
|
+
*
|
|
6
|
+
* The log outlives any single connection: it is created on first use and kept
|
|
7
|
+
* across reconnects (so a returning phone can be caught up via seq-based
|
|
8
|
+
* replay), and is dropped only when the device is untrusted ({@link forget}).
|
|
9
|
+
* Messages sent to a device with no active sink are recorded in its log (and
|
|
10
|
+
* thus replayed on reconnect) instead of being lost.
|
|
11
|
+
*/
|
|
12
|
+
import { OutboundLog } from './outbound-log.js';
|
|
13
|
+
/** Encrypts a JSON-RPC message and writes it to the active connection. */
|
|
14
|
+
export interface SessionSink {
|
|
15
|
+
send(message: unknown): void;
|
|
16
|
+
}
|
|
17
|
+
export declare class SessionRegistry {
|
|
18
|
+
#private;
|
|
19
|
+
/**
|
|
20
|
+
* The per-device outbound log, created on first use. The session handler
|
|
21
|
+
* builds the secure channel over this so its `seq` continues across
|
|
22
|
+
* reconnects and every outbound message is retained for catch-up.
|
|
23
|
+
*/
|
|
24
|
+
logFor(deviceId: string): OutboundLog;
|
|
25
|
+
/** Register the active sink for a device. */
|
|
26
|
+
register(deviceId: string, sink: SessionSink): void;
|
|
27
|
+
/**
|
|
28
|
+
* Drop the live sink (e.g. on disconnect) but KEEP the outbound log so the
|
|
29
|
+
* phone can be caught up when it reconnects.
|
|
30
|
+
*
|
|
31
|
+
* Only removes [sink] if it is still the device's CURRENT sink. A returning
|
|
32
|
+
* phone (LAN/direct) can open a new connection — whose handshake calls
|
|
33
|
+
* {@link register} and overwrites the sink — while the old connection's socket
|
|
34
|
+
* is still half-open; when that stale connection finally tears down, it must
|
|
35
|
+
* not delete the newer connection's live sink. Returns `true` when this sink
|
|
36
|
+
* was the current one (i.e. the caller still owned the session), `false` when
|
|
37
|
+
* a newer connection had already superseded it.
|
|
38
|
+
*/
|
|
39
|
+
unregister(deviceId: string, sink: SessionSink): boolean;
|
|
40
|
+
/**
|
|
41
|
+
* Forget a device entirely — drop its sink AND its outbound log. Used when the
|
|
42
|
+
* device is untrusted/unpaired: there is nothing to catch up anymore.
|
|
43
|
+
*/
|
|
44
|
+
forget(deviceId: string): void;
|
|
45
|
+
/**
|
|
46
|
+
* Send a message to every known device: delivered live where a sink is
|
|
47
|
+
* connected, recorded in the outbound log otherwise (so it replays on
|
|
48
|
+
* reconnect). Each device keeps its own seq.
|
|
49
|
+
*/
|
|
50
|
+
broadcast(message: unknown): void;
|
|
51
|
+
isActive(deviceId: string): boolean;
|
|
52
|
+
/**
|
|
53
|
+
* Deliver a message to a device. Returns `true` if sent live (the sink
|
|
54
|
+
* encrypts it, which records it in the log), `false` if the device is offline
|
|
55
|
+
* and the message was recorded in the log for later replay.
|
|
56
|
+
*/
|
|
57
|
+
notify(deviceId: string, message: unknown): boolean;
|
|
58
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tracks the live encrypted sink for each connected phone so the bridge can push
|
|
3
|
+
* JSON-RPC notifications (e.g. streamed agent events) to it, and owns each
|
|
4
|
+
* phone's persistent {@link OutboundLog} (seq counter + catch-up window).
|
|
5
|
+
*
|
|
6
|
+
* The log outlives any single connection: it is created on first use and kept
|
|
7
|
+
* across reconnects (so a returning phone can be caught up via seq-based
|
|
8
|
+
* replay), and is dropped only when the device is untrusted ({@link forget}).
|
|
9
|
+
* Messages sent to a device with no active sink are recorded in its log (and
|
|
10
|
+
* thus replayed on reconnect) instead of being lost.
|
|
11
|
+
*/
|
|
12
|
+
import { OutboundLog } from './outbound-log.js';
|
|
13
|
+
export class SessionRegistry {
|
|
14
|
+
#sinks = new Map();
|
|
15
|
+
#logs = new Map();
|
|
16
|
+
/**
|
|
17
|
+
* The per-device outbound log, created on first use. The session handler
|
|
18
|
+
* builds the secure channel over this so its `seq` continues across
|
|
19
|
+
* reconnects and every outbound message is retained for catch-up.
|
|
20
|
+
*/
|
|
21
|
+
logFor(deviceId) {
|
|
22
|
+
let log = this.#logs.get(deviceId);
|
|
23
|
+
if (!log) {
|
|
24
|
+
log = new OutboundLog();
|
|
25
|
+
this.#logs.set(deviceId, log);
|
|
26
|
+
}
|
|
27
|
+
return log;
|
|
28
|
+
}
|
|
29
|
+
/** Register the active sink for a device. */
|
|
30
|
+
register(deviceId, sink) {
|
|
31
|
+
this.#sinks.set(deviceId, sink);
|
|
32
|
+
// Ensure a log exists so subsequent broadcasts/notifies are retained even if
|
|
33
|
+
// the handshake path didn't pre-create one.
|
|
34
|
+
this.logFor(deviceId);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Drop the live sink (e.g. on disconnect) but KEEP the outbound log so the
|
|
38
|
+
* phone can be caught up when it reconnects.
|
|
39
|
+
*
|
|
40
|
+
* Only removes [sink] if it is still the device's CURRENT sink. A returning
|
|
41
|
+
* phone (LAN/direct) can open a new connection — whose handshake calls
|
|
42
|
+
* {@link register} and overwrites the sink — while the old connection's socket
|
|
43
|
+
* is still half-open; when that stale connection finally tears down, it must
|
|
44
|
+
* not delete the newer connection's live sink. Returns `true` when this sink
|
|
45
|
+
* was the current one (i.e. the caller still owned the session), `false` when
|
|
46
|
+
* a newer connection had already superseded it.
|
|
47
|
+
*/
|
|
48
|
+
unregister(deviceId, sink) {
|
|
49
|
+
if (this.#sinks.get(deviceId) !== sink)
|
|
50
|
+
return false;
|
|
51
|
+
this.#sinks.delete(deviceId);
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Forget a device entirely — drop its sink AND its outbound log. Used when the
|
|
56
|
+
* device is untrusted/unpaired: there is nothing to catch up anymore.
|
|
57
|
+
*/
|
|
58
|
+
forget(deviceId) {
|
|
59
|
+
this.#sinks.delete(deviceId);
|
|
60
|
+
this.#logs.delete(deviceId);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Send a message to every known device: delivered live where a sink is
|
|
64
|
+
* connected, recorded in the outbound log otherwise (so it replays on
|
|
65
|
+
* reconnect). Each device keeps its own seq.
|
|
66
|
+
*/
|
|
67
|
+
broadcast(message) {
|
|
68
|
+
for (const deviceId of this.#logs.keys()) {
|
|
69
|
+
this.notify(deviceId, message);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
isActive(deviceId) {
|
|
73
|
+
return this.#sinks.has(deviceId);
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Deliver a message to a device. Returns `true` if sent live (the sink
|
|
77
|
+
* encrypts it, which records it in the log), `false` if the device is offline
|
|
78
|
+
* and the message was recorded in the log for later replay.
|
|
79
|
+
*/
|
|
80
|
+
notify(deviceId, message) {
|
|
81
|
+
const sink = this.#sinks.get(deviceId);
|
|
82
|
+
if (sink) {
|
|
83
|
+
sink.send(message);
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
// Offline: record the plaintext so a reconnecting phone replays it (seq > N).
|
|
87
|
+
this.logFor(deviceId).record(Buffer.from(JSON.stringify(message), 'utf-8'));
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=session-registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-registry.js","sourceRoot":"","sources":["../../../src/transport/session-registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAOhD,MAAM,OAAO,eAAe;IACjB,MAAM,GAAG,IAAI,GAAG,EAAuB,CAAC;IACxC,KAAK,GAAG,IAAI,GAAG,EAAuB,CAAC;IAEhD;;;;OAIG;IACH,MAAM,CAAC,QAAgB;QACrB,IAAI,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QACnC,IAAI,CAAC,GAAG,EAAE,CAAC;YACT,GAAG,GAAG,IAAI,WAAW,EAAE,CAAC;YACxB,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;QAChC,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC;IAED,6CAA6C;IAC7C,QAAQ,CAAC,QAAgB,EAAE,IAAiB;QAC1C,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;QAChC,6EAA6E;QAC7E,4CAA4C;QAC5C,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IACxB,CAAC;IAED;;;;;;;;;;;OAWG;IACH,UAAU,CAAC,QAAgB,EAAE,IAAiB;QAC5C,IAAI,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,KAAK,IAAI;YAAE,OAAO,KAAK,CAAC;QACrD,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAC7B,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;;OAGG;IACH,MAAM,CAAC,QAAgB;QACrB,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAC7B,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IAC9B,CAAC;IAED;;;;OAIG;IACH,SAAS,CAAC,OAAgB;QACxB,KAAK,MAAM,QAAQ,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE,CAAC;YACzC,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QACjC,CAAC;IACH,CAAC;IAED,QAAQ,CAAC,QAAgB;QACvB,OAAO,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IACnC,CAAC;IAED;;;;OAIG;IACH,MAAM,CAAC,QAAgB,EAAE,OAAgB;QACvC,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QACvC,IAAI,IAAI,EAAE,CAAC;YACT,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YACnB,OAAO,IAAI,CAAC;QACd,CAAC;QACD,8EAA8E;QAC9E,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;QAC5E,OAAO,KAAK,CAAC;IACf,CAAC;CACF"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Persistence of trusted phones (`~/.uxnan/trusted-phones.json`).
|
|
3
|
+
*
|
|
4
|
+
* Stores only non-secret data: the phone's Ed25519 identity public key and
|
|
5
|
+
* metadata. Used to authenticate trusted reconnects and to back the
|
|
6
|
+
* `bridge/trustedDevices` handler.
|
|
7
|
+
*/
|
|
8
|
+
import type { TrustedDevice } from '@uxnan/shared';
|
|
9
|
+
import { type DaemonState } from '../daemon-state.js';
|
|
10
|
+
export interface TrustStore {
|
|
11
|
+
list(): Promise<TrustedDevice[]>;
|
|
12
|
+
get(deviceId: string): Promise<TrustedDevice | null>;
|
|
13
|
+
upsert(device: TrustedDevice): Promise<void>;
|
|
14
|
+
remove(deviceId: string): Promise<boolean>;
|
|
15
|
+
}
|
|
16
|
+
export declare class FileTrustStore implements TrustStore {
|
|
17
|
+
#private;
|
|
18
|
+
constructor(state: DaemonState);
|
|
19
|
+
list(): Promise<TrustedDevice[]>;
|
|
20
|
+
get(deviceId: string): Promise<TrustedDevice | null>;
|
|
21
|
+
upsert(device: TrustedDevice): Promise<void>;
|
|
22
|
+
remove(deviceId: string): Promise<boolean>;
|
|
23
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { DAEMON_FILES } from '../daemon-state.js';
|
|
2
|
+
export class FileTrustStore {
|
|
3
|
+
#state;
|
|
4
|
+
constructor(state) {
|
|
5
|
+
this.#state = state;
|
|
6
|
+
}
|
|
7
|
+
async list() {
|
|
8
|
+
return (await this.#state.readJson(DAEMON_FILES.trustedPhones)) ?? [];
|
|
9
|
+
}
|
|
10
|
+
async get(deviceId) {
|
|
11
|
+
return (await this.list()).find((d) => d.deviceId === deviceId) ?? null;
|
|
12
|
+
}
|
|
13
|
+
async upsert(device) {
|
|
14
|
+
const all = await this.list();
|
|
15
|
+
const index = all.findIndex((d) => d.deviceId === device.deviceId);
|
|
16
|
+
if (index >= 0) {
|
|
17
|
+
all[index] = device;
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
all.push(device);
|
|
21
|
+
}
|
|
22
|
+
await this.#state.writeJson(DAEMON_FILES.trustedPhones, all);
|
|
23
|
+
}
|
|
24
|
+
async remove(deviceId) {
|
|
25
|
+
const all = await this.list();
|
|
26
|
+
const next = all.filter((d) => d.deviceId !== deviceId);
|
|
27
|
+
if (next.length === all.length)
|
|
28
|
+
return false;
|
|
29
|
+
await this.#state.writeJson(DAEMON_FILES.trustedPhones, next);
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=trust-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"trust-store.js","sourceRoot":"","sources":["../../../src/transport/trust-store.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,YAAY,EAAoB,MAAM,oBAAoB,CAAC;AASpE,MAAM,OAAO,cAAc;IAChB,MAAM,CAAc;IAE7B,YAAY,KAAkB;QAC5B,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC;IACtB,CAAC;IAED,KAAK,CAAC,IAAI;QACR,OAAO,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAkB,YAAY,CAAC,aAAa,CAAC,CAAC,IAAI,EAAE,CAAC;IACzF,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,QAAgB;QACxB,OAAO,CAAC,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,QAAQ,CAAC,IAAI,IAAI,CAAC;IAC1E,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,MAAqB;QAChC,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;QAC9B,MAAM,KAAK,GAAG,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,MAAM,CAAC,QAAQ,CAAC,CAAC;QACnE,IAAI,KAAK,IAAI,CAAC,EAAE,CAAC;YACf,GAAG,CAAC,KAAK,CAAC,GAAG,MAAM,CAAC;QACtB,CAAC;aAAM,CAAC;YACN,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACnB,CAAC;QACD,MAAM,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,YAAY,CAAC,aAAa,EAAE,GAAG,CAAC,CAAC;IAC/D,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,QAAgB;QAC3B,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;QAC9B,MAAM,IAAI,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC;QACxD,IAAI,IAAI,CAAC,MAAM,KAAK,GAAG,CAAC,MAAM;YAAE,OAAO,KAAK,CAAC;QAC7C,MAAM,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,YAAY,CAAC,aAAa,EAAE,IAAI,CAAC,CAAC;QAC9D,OAAO,IAAI,CAAC;IACd,CAAC;CACF"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adapts a `ws` WebSocket to the {@link MessageIO} interface.
|
|
3
|
+
*/
|
|
4
|
+
import type { RawData, WebSocket } from 'ws';
|
|
5
|
+
import type { MessageIO } from './message-io.js';
|
|
6
|
+
export declare function rawDataToBuffer(data: RawData): Buffer;
|
|
7
|
+
export declare function wsToMessageIO(ws: WebSocket): MessageIO;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export function rawDataToBuffer(data) {
|
|
2
|
+
if (Buffer.isBuffer(data))
|
|
3
|
+
return data;
|
|
4
|
+
if (Array.isArray(data))
|
|
5
|
+
return Buffer.concat(data);
|
|
6
|
+
return Buffer.from(data);
|
|
7
|
+
}
|
|
8
|
+
export function wsToMessageIO(ws) {
|
|
9
|
+
return {
|
|
10
|
+
send: (bytes) => ws.send(bytes, { binary: true }),
|
|
11
|
+
onMessage: (listener) => ws.on('message', (data) => listener(rawDataToBuffer(data))),
|
|
12
|
+
onClose: (listener) => ws.on('close', () => listener()),
|
|
13
|
+
close: () => ws.close(),
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=ws-adapter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ws-adapter.js","sourceRoot":"","sources":["../../../src/transport/ws-adapter.ts"],"names":[],"mappings":"AAMA,MAAM,UAAU,eAAe,CAAC,IAAa;IAC3C,IAAI,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IACvC,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC;QAAE,OAAO,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IACpD,OAAO,MAAM,CAAC,IAAI,CAAC,IAAmB,CAAC,CAAC;AAC1C,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,EAAa;IACzC,OAAO;QACL,IAAI,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,EAAE,CAAC,IAAI,CAAC,KAAK,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;QACjD,SAAS,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,SAAS,EAAE,CAAC,IAAa,EAAE,EAAE,CAAC,QAAQ,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC;QAC7F,OAAO,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC;QACvD,KAAK,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,KAAK,EAAE;KACxB,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const BRIDGE_VERSION: string;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bridge package version, read from package.json at runtime so the CLI's
|
|
3
|
+
* reported version can never drift from the published package. Works both
|
|
4
|
+
* from the compiled tree (dist/src/version.js → ../../package.json) and from
|
|
5
|
+
* a published npm install (node_modules/uxnan-bridge/package.json).
|
|
6
|
+
*/
|
|
7
|
+
import { readFileSync } from 'node:fs';
|
|
8
|
+
import { dirname, join } from 'node:path';
|
|
9
|
+
import { fileURLToPath } from 'node:url';
|
|
10
|
+
const pkgPath = join(dirname(fileURLToPath(import.meta.url)), '../../package.json');
|
|
11
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
|
|
12
|
+
export const BRIDGE_VERSION = pkg.version;
|
|
13
|
+
//# sourceMappingURL=version.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"version.js","sourceRoot":"","sources":["../../src/version.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,oBAAoB,CAAC,CAAC;AACpF,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,OAAO,EAAE,MAAM,CAAC,CAAwB,CAAC;AAE7E,MAAM,CAAC,MAAM,cAAc,GAAW,GAAG,CAAC,OAAO,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type BrowseResult, type BrowseRoot } from '@uxnan/shared';
|
|
2
|
+
/** Stable id derived from the absolute path, so it survives restarts. */
|
|
3
|
+
export declare function browseRootIdFor(cwd: string): string;
|
|
4
|
+
export declare class BrowseService {
|
|
5
|
+
#private;
|
|
6
|
+
constructor(roots: string[], fallback?: string);
|
|
7
|
+
listRoots(): BrowseRoot[];
|
|
8
|
+
/** Browse `path` (relative to `rootId`, default the first root and its top). */
|
|
9
|
+
browse(rootId?: string, path?: string): Promise<BrowseResult>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plug-and-play directory browsing: lets the phone navigate sub-directories under
|
|
3
|
+
* a configured base root (e.g. the user's `Documents`), mark which ones are git
|
|
4
|
+
* repos, and pick ANY directory as a thread's cwd — without per-project
|
|
5
|
+
* pre-configuration and without ever escaping above the root.
|
|
6
|
+
*
|
|
7
|
+
* The confinement here applies to the phone-facing browse/workspace API (it
|
|
8
|
+
* reuses {@link resolveWithinRoot}, which rejects `..`/absolute escapes). Note it
|
|
9
|
+
* does NOT sandbox the agent process itself: once a directory is chosen as a
|
|
10
|
+
* thread cwd, the agent CLI runs there and can act on that subtree (writes are
|
|
11
|
+
* bounded by each agent's sandbox posture). See bridge/FOR-HUMAN.md.
|
|
12
|
+
*
|
|
13
|
+
* Source: architecture/02a-system-architecture.md §5.8.5 (project resolution),
|
|
14
|
+
* extended for plug-and-play browsing.
|
|
15
|
+
*/
|
|
16
|
+
import { existsSync } from 'node:fs';
|
|
17
|
+
import { readdir } from 'node:fs/promises';
|
|
18
|
+
import { createHash } from 'node:crypto';
|
|
19
|
+
import { basename, join, relative, resolve, sep } from 'node:path';
|
|
20
|
+
import { JsonRpcErrorCode, RpcError } from '@uxnan/shared';
|
|
21
|
+
import { isSensitiveName, resolveWithinRoot } from './path-guard.js';
|
|
22
|
+
/** Stable id derived from the absolute path, so it survives restarts. */
|
|
23
|
+
export function browseRootIdFor(cwd) {
|
|
24
|
+
return `root_${createHash('sha1').update(resolve(cwd)).digest('hex').slice(0, 12)}`;
|
|
25
|
+
}
|
|
26
|
+
export class BrowseService {
|
|
27
|
+
#roots;
|
|
28
|
+
// Defaults the root to the bridge's launch directory (`process.cwd()`) so a
|
|
29
|
+
// zero-config install is plug-and-play: start the bridge inside the folder you
|
|
30
|
+
// want the phone to reach, and that folder (plus its sub-directories) is the
|
|
31
|
+
// root — no `browseRoots`/`workspaceRoots` to configure. Matches ProjectRegistry.
|
|
32
|
+
constructor(roots, fallback = process.cwd()) {
|
|
33
|
+
const resolved = [...new Set(roots.map((r) => resolve(r)).filter((r) => r.length > 0))];
|
|
34
|
+
this.#roots = resolved.length > 0 ? resolved : [resolve(fallback)];
|
|
35
|
+
}
|
|
36
|
+
listRoots() {
|
|
37
|
+
return this.#roots.map((cwd) => ({
|
|
38
|
+
id: browseRootIdFor(cwd),
|
|
39
|
+
name: basename(cwd) || cwd,
|
|
40
|
+
cwd,
|
|
41
|
+
}));
|
|
42
|
+
}
|
|
43
|
+
/** Browse `path` (relative to `rootId`, default the first root and its top). */
|
|
44
|
+
async browse(rootId, path) {
|
|
45
|
+
const root = this.#resolveRoot(rootId);
|
|
46
|
+
const rel = (path ?? '').replace(/^[/\\]+/, '');
|
|
47
|
+
const abs = resolveWithinRoot(root, rel === '' ? '.' : rel);
|
|
48
|
+
let dirents;
|
|
49
|
+
try {
|
|
50
|
+
dirents = await readdir(abs, { withFileTypes: true });
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
throw new RpcError(JsonRpcErrorCode.WorkspaceAccessDenied, 'directory not accessible');
|
|
54
|
+
}
|
|
55
|
+
const dirs = dirents
|
|
56
|
+
.filter((d) => d.isDirectory() && d.name !== '.git' && !isSensitiveName(d.name))
|
|
57
|
+
.map((d) => {
|
|
58
|
+
const childAbs = join(abs, d.name);
|
|
59
|
+
return { name: d.name, path: toRel(root, childAbs), isGitRepo: isGitRepo(childAbs) };
|
|
60
|
+
})
|
|
61
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
62
|
+
const currentPath = toRel(root, abs);
|
|
63
|
+
return {
|
|
64
|
+
roots: this.listRoots(),
|
|
65
|
+
rootId: browseRootIdFor(root),
|
|
66
|
+
path: currentPath,
|
|
67
|
+
parent: parentOf(currentPath),
|
|
68
|
+
cwd: abs,
|
|
69
|
+
isGitRepo: isGitRepo(abs),
|
|
70
|
+
dirs,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
#resolveRoot(rootId) {
|
|
74
|
+
if (!rootId)
|
|
75
|
+
return this.#roots[0];
|
|
76
|
+
const match = this.#roots.find((r) => browseRootIdFor(r) === rootId);
|
|
77
|
+
if (!match) {
|
|
78
|
+
throw new RpcError(JsonRpcErrorCode.ResourceNotFound, `unknown browse root: ${rootId}`);
|
|
79
|
+
}
|
|
80
|
+
return match;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
function isGitRepo(dir) {
|
|
84
|
+
return existsSync(join(dir, '.git'));
|
|
85
|
+
}
|
|
86
|
+
/** Path of `abs` relative to `root`, POSIX separators (`''` when `abs` IS root). */
|
|
87
|
+
function toRel(root, abs) {
|
|
88
|
+
return relative(resolve(root), abs).split(sep).join('/');
|
|
89
|
+
}
|
|
90
|
+
/** Parent of a root-relative POSIX path, or null at the root. */
|
|
91
|
+
function parentOf(relPosix) {
|
|
92
|
+
if (relPosix === '')
|
|
93
|
+
return null;
|
|
94
|
+
const idx = relPosix.lastIndexOf('/');
|
|
95
|
+
return idx === -1 ? '' : relPosix.slice(0, idx);
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=browse-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browse-service.js","sourceRoot":"","sources":["../../../src/workspace/browse-service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Checkpoint, CheckpointDiff } from '@uxnan/shared';
|
|
2
|
+
import { type DaemonState } from '../daemon-state.js';
|
|
3
|
+
export interface CaptureOptions {
|
|
4
|
+
threadId?: string;
|
|
5
|
+
label?: string;
|
|
6
|
+
now: number;
|
|
7
|
+
}
|
|
8
|
+
/** Bounds on how many checkpoints are retained (per project / by age). */
|
|
9
|
+
export interface CheckpointRetention {
|
|
10
|
+
/** Keep at most N newest checkpoints per project `cwd` (0 = unlimited). */
|
|
11
|
+
maxPerProject: number;
|
|
12
|
+
/** Delete checkpoints older than N days (0 = no TTL). */
|
|
13
|
+
ttlDays: number;
|
|
14
|
+
}
|
|
15
|
+
export declare class CheckpointService {
|
|
16
|
+
#private;
|
|
17
|
+
constructor(state: DaemonState, retention?: Partial<CheckpointRetention>);
|
|
18
|
+
capture(cwd: string, options: CaptureOptions): Promise<Checkpoint>;
|
|
19
|
+
diff(id: string): Promise<CheckpointDiff>;
|
|
20
|
+
apply(id: string): Promise<void>;
|
|
21
|
+
}
|