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.
- 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,211 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thread/turn JSON-RPC handlers — conversation CRUD backed by the
|
|
3
|
+
* {@link ThreadStore}, with `turn/send` driving the {@link AgentManager}.
|
|
4
|
+
* Streaming output reaches the phone via `stream/*` notifications.
|
|
5
|
+
*
|
|
6
|
+
* Source: architecture/02a-system-architecture.md §5.8.8.
|
|
7
|
+
*/
|
|
8
|
+
import { RpcError } from '@uxnan/shared';
|
|
9
|
+
import { optionalBoolean, optionalNumber, optionalString, requireString } from './params.js';
|
|
10
|
+
export function registerThreadHandlers(router) {
|
|
11
|
+
router.register('thread/list', (p, ctx) => ctx.threadStore.listThreads(optionalString(p, 'projectId')));
|
|
12
|
+
router.register('thread/read', (p, ctx) => ctx.threadStore.getThread(requireString(p, 'threadId')));
|
|
13
|
+
router.register('thread/start', (p, ctx) => {
|
|
14
|
+
const projectId = requireString(p, 'projectId');
|
|
15
|
+
// The phone provides the cwd (e.g. a folder-browser directory, which
|
|
16
|
+
// `project/resolve` SYNTHESIZES into a project that is NOT in
|
|
17
|
+
// workspaceRoots). Use that cwd directly; only resolve the project by id to
|
|
18
|
+
// get a cwd fallback when none is given — otherwise a browsed folder failed
|
|
19
|
+
// `byId` with "unknown project", and the thread was never created.
|
|
20
|
+
const cwd = optionalString(p, 'cwd') ?? ctx.projects.byId(projectId).cwd;
|
|
21
|
+
// Per-project pin: when the phone omits agent/model, fall back to the
|
|
22
|
+
// project's configured agent (then the bridge's global default). The pinned
|
|
23
|
+
// model only applies when the resolved agent IS the pinned one, so we never
|
|
24
|
+
// force one agent's model onto a thread the phone steered to another agent.
|
|
25
|
+
const pin = ctx.projects.agentConfigFor(cwd);
|
|
26
|
+
const explicitAgent = optionalString(p, 'agentId');
|
|
27
|
+
const agentId = explicitAgent ?? pin?.agentId ?? ctx.agentManager.defaultAgent;
|
|
28
|
+
const explicitModel = optionalString(p, 'model');
|
|
29
|
+
const model = explicitModel ?? (pin && agentId === pin.agentId ? pin.model : undefined);
|
|
30
|
+
return ctx.threadStore.startThread({
|
|
31
|
+
projectId,
|
|
32
|
+
...(optionalString(p, 'title') !== undefined ? { title: optionalString(p, 'title') } : {}),
|
|
33
|
+
agentId,
|
|
34
|
+
...(model !== undefined ? { model } : {}),
|
|
35
|
+
cwd,
|
|
36
|
+
}, ctx.now());
|
|
37
|
+
});
|
|
38
|
+
router.register('thread/resume', (p, ctx) => ctx.threadStore.resumeThread(requireString(p, 'threadId'), ctx.now()));
|
|
39
|
+
router.register('thread/fork', (p, ctx) => ctx.threadStore.forkThread(requireString(p, 'threadId'), ctx.now()));
|
|
40
|
+
router.register('thread/setModel', async (p, ctx) => {
|
|
41
|
+
await ctx.threadStore.setModel(requireString(p, 'threadId'), requireString(p, 'model'), ctx.now());
|
|
42
|
+
return null;
|
|
43
|
+
});
|
|
44
|
+
router.register('thread/rename', (p, ctx) => ctx.threadStore.renameThread(requireString(p, 'threadId'), requireString(p, 'title'), ctx.now()));
|
|
45
|
+
router.register('thread/setAccessMode', (p, ctx) => ctx.threadStore.setAccessMode(requireString(p, 'threadId'), parseAccessMode(requireString(p, 'mode')), ctx.now()));
|
|
46
|
+
router.register('thread/archive', (p, ctx) => ctx.threadStore.archiveThread(requireString(p, 'threadId'), ctx.now()));
|
|
47
|
+
router.register('thread/unarchive', (p, ctx) => ctx.threadStore.unarchiveThread(requireString(p, 'threadId'), ctx.now()));
|
|
48
|
+
router.register('thread/delete', async (p, ctx) => {
|
|
49
|
+
await ctx.threadStore.deleteThread(requireString(p, 'threadId'));
|
|
50
|
+
return null;
|
|
51
|
+
});
|
|
52
|
+
router.register('turn/list', async (p, ctx) => {
|
|
53
|
+
const threadId = requireString(p, 'threadId');
|
|
54
|
+
const cursor = optionalString(p, 'cursor');
|
|
55
|
+
const limit = optionalNumber(p, 'limit');
|
|
56
|
+
const fromEnd = optionalBoolean(p, 'fromEnd') ?? false;
|
|
57
|
+
const stored = await ctx.threadStore.listTurns(threadId, cursor, limit, fromEnd);
|
|
58
|
+
// Fallback (§5.8.8): when the store has nothing for this thread, read the
|
|
59
|
+
// agent's own on-disk session log so the phone can still show history (e.g.
|
|
60
|
+
// after the bridge missed the turns, or threads.json was lost).
|
|
61
|
+
if (stored.turns.length > 0 || stored.nextCursor)
|
|
62
|
+
return stored;
|
|
63
|
+
const source = await ctx.threadStore.getHistorySource(threadId);
|
|
64
|
+
const turns = await ctx.sessionHistory.readTurns(source, threadId);
|
|
65
|
+
if (!turns || turns.length === 0)
|
|
66
|
+
return stored;
|
|
67
|
+
return paginateTurns(turns, cursor, limit, fromEnd);
|
|
68
|
+
});
|
|
69
|
+
router.register('turn/read', (p, ctx) => ctx.threadStore.getTurn(requireString(p, 'turnId')));
|
|
70
|
+
router.register('turn/send', async (p, ctx) => {
|
|
71
|
+
const threadId = requireString(p, 'threadId');
|
|
72
|
+
// A `turn/send` may instead be a control-only reply to a pending approval:
|
|
73
|
+
// no new turn is created — the decision is routed to the agent adapter.
|
|
74
|
+
const approval = optionalApprovalResponse(p);
|
|
75
|
+
if (approval) {
|
|
76
|
+
return ctx.agentManager.respondApproval(threadId, approval.approvalId, approval.decision);
|
|
77
|
+
}
|
|
78
|
+
// `text` is OPTIONAL: an image-only message (empty text + attachments) is
|
|
79
|
+
// valid, so we no longer hard-require a non-empty string. Reject only when
|
|
80
|
+
// there is neither text nor an attachment to act on.
|
|
81
|
+
const text = optionalString(p, 'text') ?? '';
|
|
82
|
+
const attachments = optionalAttachments(p);
|
|
83
|
+
if (text.length === 0 && attachments.length === 0) {
|
|
84
|
+
throw RpcError.invalidParams('turn/send requires non-empty text or attachments');
|
|
85
|
+
}
|
|
86
|
+
const runtime = await ctx.threadStore.getThreadRuntime(threadId);
|
|
87
|
+
// A turn runs with the thread's agent/model/cwd; explicit params override.
|
|
88
|
+
const service = optionalString(p, 'service') ?? runtime.model;
|
|
89
|
+
const options = {
|
|
90
|
+
...(runtime.agentId !== undefined ? { agentId: runtime.agentId } : {}),
|
|
91
|
+
...(service !== undefined ? { service } : {}),
|
|
92
|
+
...optionalEffort(p),
|
|
93
|
+
...optionalRunOptions(p),
|
|
94
|
+
...(attachments.length > 0 ? { attachments } : {}),
|
|
95
|
+
...(runtime.cwd !== undefined ? { cwd: runtime.cwd } : {}),
|
|
96
|
+
// Apply the thread's persisted access mode to this turn (adapters map it
|
|
97
|
+
// to their permission flag; absent → the adapter's configured posture).
|
|
98
|
+
...(runtime.accessMode !== undefined ? { accessMode: runtime.accessMode } : {}),
|
|
99
|
+
};
|
|
100
|
+
return ctx.agentManager.sendTurn(threadId, text, options);
|
|
101
|
+
});
|
|
102
|
+
router.register('turn/cancel', async (p, ctx) => {
|
|
103
|
+
await ctx.agentManager.cancelTurn(requireString(p, 'threadId'), requireString(p, 'turnId'));
|
|
104
|
+
return null;
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Page a full turn list (from the on-disk history fallback) the same way the
|
|
109
|
+
* store does: numeric cursor offset + limit, with a `nextCursor` when more remain.
|
|
110
|
+
*/
|
|
111
|
+
const ACCESS_MODES = ['requestApproval', 'approveForMe', 'fullAccess'];
|
|
112
|
+
/** Validates a wire `mode` string against the {@link AccessMode} union. */
|
|
113
|
+
function parseAccessMode(mode) {
|
|
114
|
+
if (ACCESS_MODES.includes(mode))
|
|
115
|
+
return mode;
|
|
116
|
+
throw RpcError.invalidParams(`mode must be one of ${ACCESS_MODES.join(' | ')}`);
|
|
117
|
+
}
|
|
118
|
+
function paginateTurns(turns, cursor, limit, fromEnd = false) {
|
|
119
|
+
const total = turns.length;
|
|
120
|
+
const size = limit && limit > 0 ? limit : 20;
|
|
121
|
+
const start = fromEnd ? Math.max(0, total - size) : cursor ? Number.parseInt(cursor, 10) || 0 : 0;
|
|
122
|
+
const result = { turns: turns.slice(start, start + size), total };
|
|
123
|
+
if (start + size < total)
|
|
124
|
+
result.nextCursor = String(start + size);
|
|
125
|
+
return result;
|
|
126
|
+
}
|
|
127
|
+
function optionalEffort(params) {
|
|
128
|
+
const value = optionalString(params, 'effort');
|
|
129
|
+
return value === undefined ? {} : { effort: value };
|
|
130
|
+
}
|
|
131
|
+
const APPROVAL_DECISIONS = new Set(['approve', 'reject', 'approveSession']);
|
|
132
|
+
/**
|
|
133
|
+
* Extracts the `approvalResponse` from `turn/send` params, or `undefined` when
|
|
134
|
+
* absent. Validates the shape (`approvalId` non-empty + a known `decision`) and
|
|
135
|
+
* throws `invalidParams` on a malformed one — an approval reply is control data,
|
|
136
|
+
* so a garbled one should surface rather than silently start a turn.
|
|
137
|
+
*/
|
|
138
|
+
function optionalApprovalResponse(params) {
|
|
139
|
+
if (!params || typeof params !== 'object')
|
|
140
|
+
return undefined;
|
|
141
|
+
const raw = params['approvalResponse'];
|
|
142
|
+
if (raw === undefined || raw === null)
|
|
143
|
+
return undefined;
|
|
144
|
+
if (typeof raw !== 'object') {
|
|
145
|
+
throw RpcError.invalidParams('approvalResponse must be an object');
|
|
146
|
+
}
|
|
147
|
+
const obj = raw;
|
|
148
|
+
const approvalId = obj['approvalId'];
|
|
149
|
+
const decision = obj['decision'];
|
|
150
|
+
if (typeof approvalId !== 'string' || approvalId.length === 0) {
|
|
151
|
+
throw RpcError.invalidParams('approvalResponse.approvalId must be a non-empty string');
|
|
152
|
+
}
|
|
153
|
+
if (typeof decision !== 'string' || !APPROVAL_DECISIONS.has(decision)) {
|
|
154
|
+
throw RpcError.invalidParams('approvalResponse.decision must be approve | reject | approveSession');
|
|
155
|
+
}
|
|
156
|
+
return { approvalId, decision: decision };
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Extracts the inline `attachments` from `turn/send` params, keeping only
|
|
160
|
+
* well-formed image entries (a `mimeType` plus at least one of
|
|
161
|
+
* `base64Data`/`path`). Tolerant — malformed entries are dropped, never thrown,
|
|
162
|
+
* so an older/garbled client degrades to a text turn instead of an error.
|
|
163
|
+
*/
|
|
164
|
+
function optionalAttachments(params) {
|
|
165
|
+
if (!params || typeof params !== 'object')
|
|
166
|
+
return [];
|
|
167
|
+
const raw = params['attachments'];
|
|
168
|
+
if (!Array.isArray(raw))
|
|
169
|
+
return [];
|
|
170
|
+
const out = [];
|
|
171
|
+
for (const item of raw) {
|
|
172
|
+
if (!item || typeof item !== 'object')
|
|
173
|
+
continue;
|
|
174
|
+
const obj = item;
|
|
175
|
+
const mimeType = typeof obj['mimeType'] === 'string' ? obj['mimeType'] : 'application/octet-stream';
|
|
176
|
+
const base64Data = typeof obj['base64Data'] === 'string' ? obj['base64Data'] : undefined;
|
|
177
|
+
const path = typeof obj['path'] === 'string' ? obj['path'] : undefined;
|
|
178
|
+
if (base64Data === undefined && path === undefined)
|
|
179
|
+
continue;
|
|
180
|
+
const att = { type: 'image', mimeType };
|
|
181
|
+
if (base64Data !== undefined)
|
|
182
|
+
att.base64Data = base64Data;
|
|
183
|
+
if (path !== undefined)
|
|
184
|
+
att.path = path;
|
|
185
|
+
if (typeof obj['width'] === 'number')
|
|
186
|
+
att.width = obj['width'];
|
|
187
|
+
if (typeof obj['height'] === 'number')
|
|
188
|
+
att.height = obj['height'];
|
|
189
|
+
out.push(att);
|
|
190
|
+
}
|
|
191
|
+
return out;
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Extracts the per-model run-option values (`{ options: { key: value } }`) from
|
|
195
|
+
* `turn/send` params, keeping only string/boolean values (tolerant — unknown
|
|
196
|
+
* shapes are dropped, never thrown).
|
|
197
|
+
*/
|
|
198
|
+
function optionalRunOptions(params) {
|
|
199
|
+
if (!params || typeof params !== 'object')
|
|
200
|
+
return {};
|
|
201
|
+
const raw = params['options'];
|
|
202
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw))
|
|
203
|
+
return {};
|
|
204
|
+
const out = {};
|
|
205
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
206
|
+
if (typeof value === 'string' || typeof value === 'boolean')
|
|
207
|
+
out[key] = value;
|
|
208
|
+
}
|
|
209
|
+
return Object.keys(out).length > 0 ? { options: out } : {};
|
|
210
|
+
}
|
|
211
|
+
//# sourceMappingURL=thread-context-handler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"thread-context-handler.js","sourceRoot":"","sources":["../../../src/handlers/thread-context-handler.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workspace JSON-RPC handlers — file reads, listing and patch application,
|
|
3
|
+
* confined to the project root with sensitive files excluded.
|
|
4
|
+
*
|
|
5
|
+
* Checkpoints (capture/diff/apply) are backed by {@link CheckpointService};
|
|
6
|
+
* `capture` prunes old checkpoints per the configured retention.
|
|
7
|
+
*
|
|
8
|
+
* Source: architecture/02a-system-architecture.md §5.8.7 / §5.8.9.
|
|
9
|
+
*/
|
|
10
|
+
import { JsonRpcErrorCode, RpcError, } from '@uxnan/shared';
|
|
11
|
+
import { access, stat } from 'node:fs/promises';
|
|
12
|
+
import { join } from 'node:path';
|
|
13
|
+
import { WorkspaceService } from '../workspace/workspace-service.js';
|
|
14
|
+
import { CheckpointService } from '../workspace/checkpoint-service.js';
|
|
15
|
+
import { GitCommandError } from '../git/git-runner.js';
|
|
16
|
+
import { asObject, optionalString, requireArray, requireString } from './params.js';
|
|
17
|
+
/** Map a git failure inside a checkpoint op to -32003 (RpcErrors pass through). */
|
|
18
|
+
function checkpointOp(fn) {
|
|
19
|
+
return fn().catch((err) => {
|
|
20
|
+
if (err instanceof GitCommandError) {
|
|
21
|
+
throw new RpcError(JsonRpcErrorCode.GitOperationFailed, err.message, { stderr: err.stderr });
|
|
22
|
+
}
|
|
23
|
+
throw err;
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
/** A checkpoint service wired with the daemon's configured retention bounds. */
|
|
27
|
+
function checkpoints(ctx) {
|
|
28
|
+
return new CheckpointService(ctx.state, {
|
|
29
|
+
maxPerProject: ctx.config.checkpointMaxPerProject,
|
|
30
|
+
ttlDays: ctx.config.checkpointTtlDays,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
export function registerWorkspaceHandlers(router) {
|
|
34
|
+
const ws = new WorkspaceService();
|
|
35
|
+
router.register('workspace/readFile', (p) => ws.readFile(requireString(p, 'cwd'), requireString(p, 'path')));
|
|
36
|
+
router.register('workspace/readImage', (p) => ws.readImage(requireString(p, 'cwd'), requireString(p, 'path')));
|
|
37
|
+
router.register('workspace/list', (p) => ws.list(requireString(p, 'cwd')));
|
|
38
|
+
router.register('workspace/browseDirs', (p, ctx) => {
|
|
39
|
+
const params = p ?? {};
|
|
40
|
+
return ctx.browse.browse(optionalString(params, 'rootId'), optionalString(params, 'path'));
|
|
41
|
+
});
|
|
42
|
+
router.register('workspace/applyPatch', (p) => ws.applyPatch(requireString(p, 'cwd'), parseChanges(p)));
|
|
43
|
+
// Probe whether a thread's cwd still exists (folders/worktrees can be removed
|
|
44
|
+
// outside the app), so the phone can mark the thread unavailable. Never throws.
|
|
45
|
+
router.register('workspace/exists', (p) => workspaceExists(requireString(p, 'cwd')));
|
|
46
|
+
router.register('workspace/checkpoint', (p, ctx) => {
|
|
47
|
+
const cwd = requireString(p, 'cwd');
|
|
48
|
+
const options = {
|
|
49
|
+
now: ctx.now(),
|
|
50
|
+
...optionalField(p, 'label'),
|
|
51
|
+
...optionalThreadId(p),
|
|
52
|
+
};
|
|
53
|
+
return checkpointOp(() => checkpoints(ctx).capture(cwd, options));
|
|
54
|
+
});
|
|
55
|
+
router.register('workspace/diffCheckpoint', (p, ctx) => checkpointOp(() => checkpoints(ctx).diff(requireString(p, 'id'))));
|
|
56
|
+
router.register('workspace/applyCheckpoint', (p, ctx) => checkpointOp(() => checkpoints(ctx).apply(requireString(p, 'id'))));
|
|
57
|
+
}
|
|
58
|
+
/** Whether [cwd] is an existing directory, and (if so) whether it's a git repo/worktree. */
|
|
59
|
+
async function workspaceExists(cwd) {
|
|
60
|
+
try {
|
|
61
|
+
const info = await stat(cwd);
|
|
62
|
+
if (!info.isDirectory())
|
|
63
|
+
return { exists: false };
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return { exists: false };
|
|
67
|
+
}
|
|
68
|
+
// A regular repo has a `.git` directory; a worktree has a `.git` FILE — both
|
|
69
|
+
// are accessible, so this is true for either.
|
|
70
|
+
try {
|
|
71
|
+
await access(join(cwd, '.git'));
|
|
72
|
+
return { exists: true, isGitRepo: true };
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
return { exists: true, isGitRepo: false };
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
function optionalField(params, key) {
|
|
79
|
+
const value = optionalString(params, key);
|
|
80
|
+
return value === undefined ? {} : { label: value };
|
|
81
|
+
}
|
|
82
|
+
function optionalThreadId(params) {
|
|
83
|
+
const value = optionalString(params, 'threadId');
|
|
84
|
+
return value === undefined ? {} : { threadId: value };
|
|
85
|
+
}
|
|
86
|
+
function parseChanges(params) {
|
|
87
|
+
return requireArray(params, 'changes').map((raw, index) => {
|
|
88
|
+
const item = asObject(raw);
|
|
89
|
+
const op = item['op'];
|
|
90
|
+
if (op !== 'add' && op !== 'modify' && op !== 'delete') {
|
|
91
|
+
throw RpcError.invalidParams(`changes[${index}].op must be add|modify|delete`);
|
|
92
|
+
}
|
|
93
|
+
const path = requireString(item, 'path');
|
|
94
|
+
const content = optionalString(item, 'content');
|
|
95
|
+
const change = { op, path };
|
|
96
|
+
if (content !== undefined)
|
|
97
|
+
change.content = content;
|
|
98
|
+
return change;
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=workspace-handler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workspace-handler.js","sourceRoot":"","sources":["../../../src/handlers/workspace-handler.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Writes the hook script to [scriptPath] (creating its directory) and returns
|
|
3
|
+
* the path. Idempotent — overwrites each call so an updated bridge ships a fresh
|
|
4
|
+
* hook. Best-effort caller should handle write failures.
|
|
5
|
+
*/
|
|
6
|
+
export declare function writeClaudeApprovalHook(scriptPath: string): Promise<string>;
|
|
7
|
+
export declare const CLAUDE_APPROVAL_HOOK_SCRIPT: string;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Code PreToolUse approval hook.
|
|
3
|
+
*
|
|
4
|
+
* Headless `claude -p` has no interactive-approval channel (validated against
|
|
5
|
+
* claude 2.1.177), but a **PreToolUse hook** injected via `--settings` DOES gate
|
|
6
|
+
* every tool in print mode. This module ships that hook as a standalone CommonJS
|
|
7
|
+
* script: the Claude adapter writes it to `~/.uxnan/hooks/` and references its
|
|
8
|
+
* path in the `--settings` it passes per turn.
|
|
9
|
+
*
|
|
10
|
+
* The hook reads the PreToolUse payload (`{ tool_name, tool_input, ... }`) on
|
|
11
|
+
* stdin, POSTs it to the bridge's `POST /agent-hook/approval` endpoint (URL +
|
|
12
|
+
* token + threadId from env the adapter injects), and the bridge HOLDS the
|
|
13
|
+
* response until the phone answers (`turn/send { approvalResponse }`). The hook
|
|
14
|
+
* then prints the permission decision Claude consumes:
|
|
15
|
+
* { hookSpecificOutput: { hookEventName, permissionDecision: 'allow'|'deny', … } }
|
|
16
|
+
*
|
|
17
|
+
* Fail-safe: any error (bad URL, unreachable bridge, malformed response) → deny.
|
|
18
|
+
* The script is dependency-free (only `node:http`/`node:url`).
|
|
19
|
+
*/
|
|
20
|
+
import { mkdir, writeFile } from 'node:fs/promises';
|
|
21
|
+
import { dirname } from 'node:path';
|
|
22
|
+
/**
|
|
23
|
+
* Writes the hook script to [scriptPath] (creating its directory) and returns
|
|
24
|
+
* the path. Idempotent — overwrites each call so an updated bridge ships a fresh
|
|
25
|
+
* hook. Best-effort caller should handle write failures.
|
|
26
|
+
*/
|
|
27
|
+
export async function writeClaudeApprovalHook(scriptPath) {
|
|
28
|
+
await mkdir(dirname(scriptPath), { recursive: true });
|
|
29
|
+
await writeFile(scriptPath, CLAUDE_APPROVAL_HOOK_SCRIPT, 'utf-8');
|
|
30
|
+
return scriptPath;
|
|
31
|
+
}
|
|
32
|
+
export const CLAUDE_APPROVAL_HOOK_SCRIPT = String.raw `'use strict';
|
|
33
|
+
const http = require('node:http');
|
|
34
|
+
let data = '';
|
|
35
|
+
process.stdin.on('data', (c) => (data += c));
|
|
36
|
+
process.stdin.on('end', () => {
|
|
37
|
+
let payload = {};
|
|
38
|
+
try { payload = JSON.parse(data || '{}'); } catch (e) {}
|
|
39
|
+
const url = process.env.UXNAN_HOOK_URL;
|
|
40
|
+
const token = process.env.UXNAN_HOOK_TOKEN || '';
|
|
41
|
+
const threadId = process.env.UXNAN_HOOK_THREAD_ID || '';
|
|
42
|
+
const finish = (decision, reason) => {
|
|
43
|
+
process.stdout.write(JSON.stringify({
|
|
44
|
+
hookSpecificOutput: {
|
|
45
|
+
hookEventName: 'PreToolUse',
|
|
46
|
+
permissionDecision: decision,
|
|
47
|
+
permissionDecisionReason: reason,
|
|
48
|
+
},
|
|
49
|
+
}));
|
|
50
|
+
process.exit(0);
|
|
51
|
+
};
|
|
52
|
+
// Not in approval mode (hook present but no endpoint): don't block the agent.
|
|
53
|
+
if (!url) return finish('allow', 'no bridge approval endpoint');
|
|
54
|
+
let u;
|
|
55
|
+
try { u = new URL(url); } catch (e) { return finish('deny', 'bad bridge hook url'); }
|
|
56
|
+
const body = JSON.stringify({
|
|
57
|
+
threadId,
|
|
58
|
+
toolName: payload.tool_name,
|
|
59
|
+
input: payload.tool_input,
|
|
60
|
+
toolUseId: payload.tool_use_id,
|
|
61
|
+
});
|
|
62
|
+
const req = http.request(
|
|
63
|
+
{
|
|
64
|
+
hostname: u.hostname,
|
|
65
|
+
port: u.port,
|
|
66
|
+
path: u.pathname,
|
|
67
|
+
method: 'POST',
|
|
68
|
+
headers: {
|
|
69
|
+
'content-type': 'application/json',
|
|
70
|
+
'content-length': Buffer.byteLength(body),
|
|
71
|
+
'x-uxnan-hook-token': token,
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
(res) => {
|
|
75
|
+
let out = '';
|
|
76
|
+
res.on('data', (c) => (out += c));
|
|
77
|
+
res.on('end', () => {
|
|
78
|
+
try {
|
|
79
|
+
const j = JSON.parse(out);
|
|
80
|
+
if (j && j.decision === 'allow') return finish('allow', 'approved on your phone');
|
|
81
|
+
return finish('deny', (j && j.reason) || 'rejected on your phone');
|
|
82
|
+
} catch (e) {
|
|
83
|
+
return finish('deny', 'bridge approval error');
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
},
|
|
87
|
+
);
|
|
88
|
+
req.on('error', () => finish('deny', 'bridge unreachable'));
|
|
89
|
+
// No client timeout: the bridge holds the response until the user answers or
|
|
90
|
+
// its own timeout fires (returning deny).
|
|
91
|
+
req.write(body);
|
|
92
|
+
req.end();
|
|
93
|
+
});
|
|
94
|
+
`;
|
|
95
|
+
//# sourceMappingURL=claude-approval-hook.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claude-approval-hook.js","sourceRoot":"","sources":["../../../src/hooks/claude-approval-hook.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAAC,UAAkB;IAC9D,MAAM,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACtD,MAAM,SAAS,CAAC,UAAU,EAAE,2BAA2B,EAAE,OAAO,CAAC,CAAC;IAClE,OAAO,UAAU,CAAC;AACpB,CAAC;AAED,MAAM,CAAC,MAAM,2BAA2B,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8DpD,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Writes the Gemini hook script to [scriptPath] (creating its directory) and
|
|
3
|
+
* returns the path. Idempotent — overwrites each call so an updated bridge
|
|
4
|
+
* ships a fresh script. Best-effort caller should handle write failures.
|
|
5
|
+
*/
|
|
6
|
+
export declare function writeGeminiApprovalHook(scriptPath: string): Promise<string>;
|
|
7
|
+
export declare const GEMINI_APPROVAL_HOOK_SCRIPT: string;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gemini CLI `BeforeTool` approval hook.
|
|
3
|
+
*
|
|
4
|
+
* Gemini CLI uses the same hook contract as Claude Code (the CLI ships a
|
|
5
|
+
* `gemini hooks migrate` command that imports Claude hook settings). This
|
|
6
|
+
* module ships a hook script that the bridge writes to
|
|
7
|
+
* `~/.uxnan/hooks/gemini-approval-hook.cjs`, then the adapter injects a
|
|
8
|
+
* matching `BeforeTool` entry in `<cwd>/.gemini/settings.json` so EVERY tool
|
|
9
|
+
* Gemini wants to run is round-tripped to the bridge's local HTTP endpoint.
|
|
10
|
+
*
|
|
11
|
+
* The hook reads the BeforeTool payload (`{ tool_name, tool_input, ... }`) on
|
|
12
|
+
* stdin, POSTs it to `POST /agent-hook/approval` on the bridge (URL + token +
|
|
13
|
+
* threadId from env the adapter injects per turn), and prints the decision
|
|
14
|
+
* Gemini consumes:
|
|
15
|
+
* { "decision": "deny", "reason": "..." } — tool is denied
|
|
16
|
+
* exit 2 — same as deny, stderr is the reason
|
|
17
|
+
* exit 0 + (no output) — tool is allowed
|
|
18
|
+
*
|
|
19
|
+
* Fail-safe: any error (bad URL, unreachable bridge, malformed response) → deny
|
|
20
|
+
* with a clear reason. The script is dependency-free (only `node:http`/`node:url`).
|
|
21
|
+
*
|
|
22
|
+
* Source: bridge/CHANGELOG.md (2026-06 entry) + `architecture/02a` §6.2.
|
|
23
|
+
*/
|
|
24
|
+
import { mkdir, writeFile } from 'node:fs/promises';
|
|
25
|
+
import { dirname } from 'node:path';
|
|
26
|
+
/**
|
|
27
|
+
* Writes the Gemini hook script to [scriptPath] (creating its directory) and
|
|
28
|
+
* returns the path. Idempotent — overwrites each call so an updated bridge
|
|
29
|
+
* ships a fresh script. Best-effort caller should handle write failures.
|
|
30
|
+
*/
|
|
31
|
+
export async function writeGeminiApprovalHook(scriptPath) {
|
|
32
|
+
await mkdir(dirname(scriptPath), { recursive: true });
|
|
33
|
+
await writeFile(scriptPath, GEMINI_APPROVAL_HOOK_SCRIPT, 'utf-8');
|
|
34
|
+
return scriptPath;
|
|
35
|
+
}
|
|
36
|
+
export const GEMINI_APPROVAL_HOOK_SCRIPT = String.raw `'use strict';
|
|
37
|
+
// Gemini CLI BeforeTool approval hook — the bridge writes this script under
|
|
38
|
+
// ~/.uxnan/hooks/ and references it from <cwd>/.gemini/settings.json when
|
|
39
|
+
// the user enables interactive approvals (agents.gemini-cli.interactiveApprovals).
|
|
40
|
+
// Fail-safe: any error → deny.
|
|
41
|
+
const http = require('node:http');
|
|
42
|
+
let data = '';
|
|
43
|
+
process.stdin.setEncoding('utf-8');
|
|
44
|
+
process.stdin.on('data', (c) => (data += c));
|
|
45
|
+
process.stdin.on('end', () => {
|
|
46
|
+
let payload = {};
|
|
47
|
+
try { payload = JSON.parse(data || '{}'); } catch (e) { /* default {} */ }
|
|
48
|
+
const url = process.env.UXNAN_HOOK_URL;
|
|
49
|
+
const token = process.env.UXNAN_HOOK_TOKEN || '';
|
|
50
|
+
const threadId = process.env.UXNAN_HOOK_THREAD_ID || '';
|
|
51
|
+
const toolName = typeof payload.tool_name === 'string' ? payload.tool_name : 'tool';
|
|
52
|
+
const toolInput = (payload.tool_input && typeof payload.tool_input === 'object')
|
|
53
|
+
? payload.tool_input
|
|
54
|
+
: {};
|
|
55
|
+
const toolCallId = typeof payload.tool_call_id === 'string' ? payload.tool_call_id : '';
|
|
56
|
+
|
|
57
|
+
// Allow the tool when the bridge has no approval endpoint wired (defensive —
|
|
58
|
+
// the bridge only injects the hook when the endpoint exists, but a misconfig
|
|
59
|
+
// shouldn't silently deny every tool).
|
|
60
|
+
const finish = (decision, reason) => {
|
|
61
|
+
if (decision === 'deny') {
|
|
62
|
+
// Gemini's documented shape: exit 0 with { decision, reason }.
|
|
63
|
+
process.stdout.write(JSON.stringify({ decision: 'deny', reason }));
|
|
64
|
+
} else if (decision === 'block') {
|
|
65
|
+
// Exit 2 = system block; stderr is the reason the agent sees as an error.
|
|
66
|
+
process.stderr.write(reason || 'blocked by bridge');
|
|
67
|
+
process.exit(2);
|
|
68
|
+
}
|
|
69
|
+
process.exit(0);
|
|
70
|
+
};
|
|
71
|
+
if (!url) return finish('allow', 'no bridge approval endpoint');
|
|
72
|
+
let u;
|
|
73
|
+
try { u = new URL(url); } catch (e) { return finish('deny', 'bad bridge hook url'); }
|
|
74
|
+
const body = JSON.stringify({
|
|
75
|
+
threadId,
|
|
76
|
+
toolName,
|
|
77
|
+
input: toolInput,
|
|
78
|
+
toolCallId,
|
|
79
|
+
});
|
|
80
|
+
const req = http.request(
|
|
81
|
+
{
|
|
82
|
+
hostname: u.hostname,
|
|
83
|
+
port: u.port,
|
|
84
|
+
path: u.pathname,
|
|
85
|
+
method: 'POST',
|
|
86
|
+
headers: {
|
|
87
|
+
'content-type': 'application/json',
|
|
88
|
+
'content-length': Buffer.byteLength(body),
|
|
89
|
+
'x-uxnan-hook-token': token,
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
(res) => {
|
|
93
|
+
let out = '';
|
|
94
|
+
res.on('data', (c) => (out += c));
|
|
95
|
+
res.on('end', () => {
|
|
96
|
+
try {
|
|
97
|
+
const j = JSON.parse(out);
|
|
98
|
+
if (j && j.decision === 'allow') return finish('allow', '');
|
|
99
|
+
return finish('deny', (j && j.reason) || 'rejected on your phone');
|
|
100
|
+
} catch (e) {
|
|
101
|
+
return finish('deny', 'bridge approval error');
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
},
|
|
105
|
+
);
|
|
106
|
+
req.on('error', () => finish('deny', 'bridge unreachable'));
|
|
107
|
+
// No client timeout: the bridge holds the response until the user answers
|
|
108
|
+
// (or its own 5-min timeout fires, returning deny).
|
|
109
|
+
req.write(body);
|
|
110
|
+
req.end();
|
|
111
|
+
});
|
|
112
|
+
`;
|
|
113
|
+
//# sourceMappingURL=gemini-approval-hook.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gemini-approval-hook.js","sourceRoot":"","sources":["../../../src/hooks/gemini-approval-hook.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAAC,UAAkB;IAC9D,MAAM,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACtD,MAAM,SAAS,CAAC,UAAU,EAAE,2BAA2B,EAAE,OAAO,CAAC,CAAC;IAClE,OAAO,UAAU,CAAC;AACpB,CAAC;AAED,MAAM,CAAC,MAAM,2BAA2B,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4EpD,CAAC"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* uxnan-bridge — public API.
|
|
3
|
+
*
|
|
4
|
+
* `startBridge()` boots the daemon core (state, identity, JSON-RPC router).
|
|
5
|
+
* The live transport and agent runtimes are added in later increments.
|
|
6
|
+
*/
|
|
7
|
+
export { startBridge, type Bridge, type StartBridgeOptions } from './bridge.js';
|
|
8
|
+
export { BRIDGE_VERSION } from './version.js';
|
|
9
|
+
export { HandlerRouter, type RpcHandler } from './handler-router.js';
|
|
10
|
+
export type { BridgeContext } from './bridge-context.js';
|
|
11
|
+
export { DaemonState, DAEMON_FILES } from './daemon-state.js';
|
|
12
|
+
export { DEFAULT_DAEMON_CONFIG, resolveDaemonConfig, type DaemonConfig, type AgentSettings, type AgentModelSpec, } from './daemon-config.js';
|
|
13
|
+
export { ProjectRegistry, projectIdFor } from './projects/project-registry.js';
|
|
14
|
+
export { PushService, type PushServiceOptions, type RegisterPushParams, type TurnEndInfo as PushTurnEndInfo, } from './push/push-service.js';
|
|
15
|
+
export { createBridgePushSender, defaultServiceAccountPath, type PushSender, type PushPayload, } from './push/push-sender.js';
|
|
16
|
+
export { SessionHistoryReader, type HistorySource, type SessionHistoryOptions, } from './conversation/session-history.js';
|
|
17
|
+
export { PairingCodeService, type PairingCodeServiceOptions, } from './pairing/pairing-code-service.js';
|
|
18
|
+
export { MdnsAdvertiser, parseQuestions, buildMessage, encodeName, type MdnsAdvertiserOptions, type UdpSocketLike, } from './transport/mdns-advertiser.js';
|
|
19
|
+
export { SecureDeviceState, type PublicIdentity } from './secure-device-state.js';
|
|
20
|
+
export { InMemorySecretStore, type SecretStore } from './secret-store.js';
|
|
21
|
+
export { KeyringSecretStore, createDefaultSecretStore, loadNativeKeyringBackend, type KeyringBackend, } from './keyring-secret-store.js';
|
|
22
|
+
export { SessionState } from './session-state.js';
|
|
23
|
+
export { LockFile, isProcessAlive, type LockInfo } from './lock-file.js';
|
|
24
|
+
export { buildServicePlan, buildWindowsStartupPlan, installService, uninstallService, currentServiceEnv, isServicePlatformSupported, type ServiceEnv, type ServicePlan, type ServiceCommand, type ServicePlatform, } from './service-installer.js';
|
|
25
|
+
export { buildBridgeStatus, type BridgeStatusInput } from './bridge-status.js';
|
|
26
|
+
export { getAuthStatus, type AccountStatusDeps } from './account-status.js';
|
|
27
|
+
export { generatePairingPayload, renderPairingQr, type GeneratePairingOptions } from './qr.js';
|
|
28
|
+
export { createLogger, createFileLogger, redactSecrets, logFileFor, type Logger, type LogLevel, type FileLoggerOptions, } from './logger.js';
|
|
29
|
+
export { BaseAgentAdapter } from './adapters/base-adapter.js';
|
|
30
|
+
export { CodexAdapter, codexUsageTokens, parseCodexConfigModels, parseCodexModelList, parseCodexModelWindows, parseCodexReasoning, type CodexAdapterOptions, type CodexEvent, type SpawnedAppServer, type CodexPermissionMode, } from './adapters/codex-adapter.js';
|
|
31
|
+
export { resolveCodexBinary, type ResolvedCodex } from './adapters/resolve-codex.js';
|
|
32
|
+
export { PiAdapter, parsePiLine, parsePiModelList, parsePiUsageTokens, parsePiContextWindow, type PiAdapterOptions, type PiEvent, type PiPermissionMode, } from './adapters/pi-adapter.js';
|
|
33
|
+
export { resolvePiBinary, type ResolvedPi } from './adapters/resolve-pi.js';
|
|
34
|
+
export { GeminiAdapter, parseGeminiLine, type GeminiAdapterOptions, type GeminiEvent, type GeminiPermissionMode, } from './adapters/gemini-adapter.js';
|
|
35
|
+
export { resolveGeminiBinary, type ResolvedGemini } from './adapters/resolve-gemini.js';
|
|
36
|
+
export { OpenCodeAdapter, parseOpenCodeLine, parseModelList, parseOpenCodeModelWindows, openCodeUsageTokens, type OpenCodeAdapterOptions, type OpenCodeEvent, } from './adapters/opencode-adapter.js';
|
|
37
|
+
export { resolveOpenCodeBinary, type ResolvedOpenCode } from './adapters/resolve-opencode.js';
|
|
38
|
+
export { defaultSpawn, type SpawnFn, type SpawnedProcess } from './adapters/spawn.js';
|
|
39
|
+
export { ClaudeCodeAdapter, claudeContextWindow, claudeUsageTokens, parseClaudeLine, type ClaudeCodeAdapterOptions, type ClaudeEvent, type ClaudeModelSpec, type ClaudePermissionMode, } from './adapters/claude-adapter.js';
|
|
40
|
+
export { resolveClaudeBinary, type ResolvedClaude } from './adapters/resolve-claude.js';
|
|
41
|
+
export { EchoAgentAdapter } from './adapters/echo-agent-adapter.js';
|
|
42
|
+
export { ProcessAgentAdapter, type ProcessAdapterOptions, } from './adapters/process-agent-adapter.js';
|
|
43
|
+
export { ThreadStore, type StartTurnResult, type StartThreadInput, type ThreadRuntime, } from './conversation/thread-store.js';
|
|
44
|
+
export { AgentManager, type AgentManagerOptions, type AgentMeta, type SendTurnOptions as AgentManagerSendTurnOptions, } from './agents/agent-manager.js';
|
|
45
|
+
export { type MessageIO, MessageQueue, queueFor, createInMemoryIoPair, } from './transport/message-io.js';
|
|
46
|
+
export { generateEphemeralKeyPair, deriveSessionKey, randomHex, verifyEd25519, aesGcmEncrypt, aesGcmDecrypt, type EphemeralKeyPair, type AesGcmParts, } from './transport/crypto.js';
|
|
47
|
+
export { BridgeSecureChannel, ReplayError } from './transport/secure-channel.js';
|
|
48
|
+
export { performServerHandshake, HandshakeError, type ServerHandshakeResult, type ServerHandshakeOptions, } from './transport/server-handshake.js';
|
|
49
|
+
export { handleSecureConnection, type SecureConnectionOptions, } from './transport/session-handler.js';
|
|
50
|
+
export { FileTrustStore, type TrustStore } from './transport/trust-store.js';
|
|
51
|
+
export { connectRelayAsMac, type ConnectRelayOptions, type RelayConnection, } from './transport/relay-client.js';
|
|
52
|
+
export { startLanServer, type LanServerOptions, type LanServerHandle, } from './transport/lan-server.js';
|
|
53
|
+
export { wsToMessageIO, rawDataToBuffer } from './transport/ws-adapter.js';
|
|
54
|
+
export { localHostPorts, type InterfaceMap } from './transport/local-hosts.js';
|
|
55
|
+
export { OutboundLog, type OutboundLogEntry } from './transport/outbound-log.js';
|
|
56
|
+
export { SessionRegistry, type SessionSink } from './transport/session-registry.js';
|
|
57
|
+
export { GitService } from './git/git-service.js';
|
|
58
|
+
export { runGit, GitCommandError, sanitizePaths, type RunGitResult } from './git/git-runner.js';
|
|
59
|
+
export { WorkspaceService } from './workspace/workspace-service.js';
|
|
60
|
+
export { BrowseService, browseRootIdFor } from './workspace/browse-service.js';
|
|
61
|
+
export { CheckpointService, type CaptureOptions } from './workspace/checkpoint-service.js';
|
|
62
|
+
export { resolveWithinRoot, isSensitiveName } from './workspace/path-guard.js';
|