@gajae-code/coding-agent 0.2.5 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (234) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/dist/types/async/job-manager.d.ts +91 -2
  3. package/dist/types/cli/args.d.ts +1 -1
  4. package/dist/types/commands/deep-interview.d.ts +3 -0
  5. package/dist/types/commands/harness.d.ts +37 -0
  6. package/dist/types/config/keybindings.d.ts +5 -0
  7. package/dist/types/config/settings-schema.d.ts +10 -4
  8. package/dist/types/config/settings.d.ts +2 -0
  9. package/dist/types/debug/crash-diagnostics.d.ts +45 -0
  10. package/dist/types/debug/runtime-gauges.d.ts +6 -0
  11. package/dist/types/deep-interview/render-middleware.d.ts +6 -0
  12. package/dist/types/eval/py/executor.d.ts +2 -0
  13. package/dist/types/eval/py/kernel.d.ts +2 -0
  14. package/dist/types/exec/bash-executor.d.ts +10 -0
  15. package/dist/types/extensibility/custom-tools/types.d.ts +1 -0
  16. package/dist/types/extensibility/extensions/types.d.ts +6 -0
  17. package/dist/types/extensibility/shared-events.d.ts +1 -0
  18. package/dist/types/gjc-runtime/cli-write-receipt.d.ts +24 -0
  19. package/dist/types/gjc-runtime/deep-interview-runtime.d.ts +1 -0
  20. package/dist/types/gjc-runtime/state-graph.d.ts +4 -0
  21. package/dist/types/gjc-runtime/state-migrations.d.ts +33 -0
  22. package/dist/types/gjc-runtime/state-renderer.d.ts +65 -0
  23. package/dist/types/gjc-runtime/state-runtime.d.ts +2 -0
  24. package/dist/types/gjc-runtime/state-schema.d.ts +317 -0
  25. package/dist/types/gjc-runtime/state-validation.d.ts +6 -0
  26. package/dist/types/gjc-runtime/state-writer.d.ts +147 -0
  27. package/dist/types/gjc-runtime/team-runtime.d.ts +81 -7
  28. package/dist/types/gjc-runtime/workflow-command-ref.d.ts +43 -0
  29. package/dist/types/gjc-runtime/workflow-manifest.d.ts +54 -0
  30. package/dist/types/harness-control-plane/classifier.d.ts +13 -0
  31. package/dist/types/harness-control-plane/control-endpoint.d.ts +31 -0
  32. package/dist/types/harness-control-plane/finalize.d.ts +47 -0
  33. package/dist/types/harness-control-plane/frame-mapper.d.ts +29 -0
  34. package/dist/types/harness-control-plane/operate.d.ts +35 -0
  35. package/dist/types/harness-control-plane/owner.d.ts +46 -0
  36. package/dist/types/harness-control-plane/preserve.d.ts +19 -0
  37. package/dist/types/harness-control-plane/receipts.d.ts +88 -0
  38. package/dist/types/harness-control-plane/rpc-adapter.d.ts +66 -0
  39. package/dist/types/harness-control-plane/seams.d.ts +21 -0
  40. package/dist/types/harness-control-plane/session-lease.d.ts +65 -0
  41. package/dist/types/harness-control-plane/state-machine.d.ts +19 -0
  42. package/dist/types/harness-control-plane/storage.d.ts +53 -0
  43. package/dist/types/harness-control-plane/types.d.ts +162 -0
  44. package/dist/types/hooks/skill-keywords.d.ts +2 -1
  45. package/dist/types/hooks/skill-state.d.ts +23 -29
  46. package/dist/types/internal-urls/agent-protocol.d.ts +2 -2
  47. package/dist/types/internal-urls/artifact-protocol.d.ts +2 -2
  48. package/dist/types/internal-urls/registry-helpers.d.ts +8 -7
  49. package/dist/types/internal-urls/types.d.ts +4 -0
  50. package/dist/types/lsp/index.d.ts +10 -10
  51. package/dist/types/modes/bridge/auth.d.ts +12 -0
  52. package/dist/types/modes/bridge/bridge-client-bridge.d.ts +9 -0
  53. package/dist/types/modes/bridge/bridge-mode.d.ts +44 -0
  54. package/dist/types/modes/bridge/bridge-ui-context.d.ts +88 -0
  55. package/dist/types/modes/bridge/event-stream.d.ts +8 -0
  56. package/dist/types/modes/components/custom-editor.d.ts +6 -0
  57. package/dist/types/modes/components/hook-selector.d.ts +1 -0
  58. package/dist/types/modes/components/jobs-overlay-model.d.ts +31 -0
  59. package/dist/types/modes/components/jobs-overlay.d.ts +30 -0
  60. package/dist/types/modes/components/status-line/types.d.ts +2 -0
  61. package/dist/types/modes/components/status-line.d.ts +2 -0
  62. package/dist/types/modes/controllers/input-controller.d.ts +1 -0
  63. package/dist/types/modes/controllers/selector-controller.d.ts +8 -0
  64. package/dist/types/modes/index.d.ts +1 -0
  65. package/dist/types/modes/interactive-mode.d.ts +2 -0
  66. package/dist/types/modes/jobs-observer.d.ts +57 -0
  67. package/dist/types/modes/rpc/host-tools.d.ts +1 -16
  68. package/dist/types/modes/rpc/host-uris.d.ts +1 -38
  69. package/dist/types/modes/shared/agent-wire/command-dispatch.d.ts +20 -0
  70. package/dist/types/modes/shared/agent-wire/command-validation.d.ts +2 -0
  71. package/dist/types/modes/shared/agent-wire/event-envelope.d.ts +24 -0
  72. package/dist/types/modes/shared/agent-wire/handshake.d.ts +46 -0
  73. package/dist/types/modes/shared/agent-wire/host-tool-bridge.d.ts +16 -0
  74. package/dist/types/modes/shared/agent-wire/host-uri-bridge.d.ts +17 -0
  75. package/dist/types/modes/shared/agent-wire/protocol.d.ts +44 -0
  76. package/dist/types/modes/shared/agent-wire/responses.d.ts +4 -0
  77. package/dist/types/modes/shared/agent-wire/scopes.d.ts +18 -0
  78. package/dist/types/modes/shared/agent-wire/ui-request-broker.d.ts +42 -0
  79. package/dist/types/modes/shared/agent-wire/ui-result.d.ts +27 -0
  80. package/dist/types/modes/types.d.ts +2 -0
  81. package/dist/types/sdk.d.ts +4 -0
  82. package/dist/types/session/agent-session.d.ts +19 -1
  83. package/dist/types/skill-state/active-state.d.ts +2 -0
  84. package/dist/types/skill-state/deep-interview-mutation-guard.d.ts +1 -1
  85. package/dist/types/skill-state/workflow-state-contract.d.ts +25 -2
  86. package/dist/types/skill-state/workflow-state-version.d.ts +3 -0
  87. package/dist/types/task/executor.d.ts +3 -0
  88. package/dist/types/task/id.d.ts +7 -0
  89. package/dist/types/task/index.d.ts +5 -0
  90. package/dist/types/task/receipt.d.ts +85 -0
  91. package/dist/types/task/spawn-gate.d.ts +38 -0
  92. package/dist/types/task/types.d.ts +198 -14
  93. package/dist/types/tools/cron.d.ts +6 -0
  94. package/dist/types/tools/index.d.ts +2 -0
  95. package/dist/types/tools/path-utils.d.ts +1 -0
  96. package/dist/types/tools/subagent.d.ts +26 -1
  97. package/package.json +7 -7
  98. package/scripts/build-binary.ts +7 -0
  99. package/src/async/job-manager.ts +334 -6
  100. package/src/cli/args.ts +9 -2
  101. package/src/cli/auth-broker-cli.ts +1 -0
  102. package/src/cli/config-cli.ts +10 -2
  103. package/src/cli.ts +2 -0
  104. package/src/commands/deep-interview.ts +1 -0
  105. package/src/commands/harness.ts +862 -0
  106. package/src/commands/launch.ts +2 -2
  107. package/src/commands/state.ts +2 -1
  108. package/src/commands/team.ts +54 -39
  109. package/src/config/keybindings.ts +6 -0
  110. package/src/config/settings-schema.ts +13 -3
  111. package/src/config/settings.ts +5 -0
  112. package/src/dap/client.ts +17 -3
  113. package/src/debug/crash-diagnostics.ts +223 -0
  114. package/src/debug/runtime-gauges.ts +20 -0
  115. package/src/deep-interview/render-middleware.ts +372 -0
  116. package/src/defaults/gjc/skills/deep-interview/SKILL.md +1 -1
  117. package/src/defaults/gjc/skills/ralplan/SKILL.md +31 -2
  118. package/src/defaults/gjc/skills/team/SKILL.md +47 -21
  119. package/src/defaults/gjc/skills/ultragoal/SKILL.md +106 -13
  120. package/src/eval/py/executor.ts +21 -1
  121. package/src/eval/py/kernel.ts +15 -0
  122. package/src/exec/bash-executor.ts +41 -0
  123. package/src/extensibility/custom-tools/types.ts +1 -0
  124. package/src/extensibility/extensions/types.ts +6 -0
  125. package/src/extensibility/shared-events.ts +1 -0
  126. package/src/gjc-runtime/cli-write-receipt.ts +31 -0
  127. package/src/gjc-runtime/deep-interview-runtime.ts +98 -42
  128. package/src/gjc-runtime/goal-mode-request.ts +11 -3
  129. package/src/gjc-runtime/ralplan-runtime.ts +235 -43
  130. package/src/gjc-runtime/state-graph.ts +86 -0
  131. package/src/gjc-runtime/state-migrations.ts +179 -0
  132. package/src/gjc-runtime/state-renderer.ts +345 -0
  133. package/src/gjc-runtime/state-runtime.ts +1155 -46
  134. package/src/gjc-runtime/state-schema.ts +192 -0
  135. package/src/gjc-runtime/state-validation.ts +49 -0
  136. package/src/gjc-runtime/state-writer.ts +749 -0
  137. package/src/gjc-runtime/team-runtime.ts +1255 -189
  138. package/src/gjc-runtime/ultragoal-runtime.ts +460 -43
  139. package/src/gjc-runtime/workflow-command-ref.ts +239 -0
  140. package/src/gjc-runtime/workflow-manifest.generated.json +1601 -0
  141. package/src/gjc-runtime/workflow-manifest.ts +427 -0
  142. package/src/harness-control-plane/classifier.ts +128 -0
  143. package/src/harness-control-plane/control-endpoint.ts +148 -0
  144. package/src/harness-control-plane/finalize.ts +222 -0
  145. package/src/harness-control-plane/frame-mapper.ts +286 -0
  146. package/src/harness-control-plane/operate.ts +225 -0
  147. package/src/harness-control-plane/owner.ts +600 -0
  148. package/src/harness-control-plane/preserve.ts +102 -0
  149. package/src/harness-control-plane/receipts.ts +216 -0
  150. package/src/harness-control-plane/rpc-adapter.ts +276 -0
  151. package/src/harness-control-plane/seams.ts +39 -0
  152. package/src/harness-control-plane/session-lease.ts +388 -0
  153. package/src/harness-control-plane/state-machine.ts +98 -0
  154. package/src/harness-control-plane/storage.ts +257 -0
  155. package/src/harness-control-plane/types.ts +214 -0
  156. package/src/hooks/skill-keywords.ts +4 -2
  157. package/src/hooks/skill-state.ts +197 -64
  158. package/src/internal-urls/agent-protocol.ts +68 -21
  159. package/src/internal-urls/artifact-protocol.ts +12 -17
  160. package/src/internal-urls/docs-index.generated.ts +3 -2
  161. package/src/internal-urls/registry-helpers.ts +19 -16
  162. package/src/internal-urls/types.ts +4 -0
  163. package/src/lsp/client.ts +18 -2
  164. package/src/main.ts +21 -5
  165. package/src/modes/bridge/auth.ts +41 -0
  166. package/src/modes/bridge/bridge-client-bridge.ts +47 -0
  167. package/src/modes/bridge/bridge-mode.ts +520 -0
  168. package/src/modes/bridge/bridge-ui-context.ts +200 -0
  169. package/src/modes/bridge/event-stream.ts +70 -0
  170. package/src/modes/components/assistant-message.ts +5 -1
  171. package/src/modes/components/custom-editor.ts +101 -0
  172. package/src/modes/components/hook-selector.ts +133 -20
  173. package/src/modes/components/jobs-overlay-model.ts +109 -0
  174. package/src/modes/components/jobs-overlay.ts +172 -0
  175. package/src/modes/components/status-line/presets.ts +7 -5
  176. package/src/modes/components/status-line/segments.ts +25 -0
  177. package/src/modes/components/status-line/types.ts +2 -0
  178. package/src/modes/components/status-line.ts +9 -1
  179. package/src/modes/controllers/event-controller.ts +71 -6
  180. package/src/modes/controllers/extension-ui-controller.ts +43 -1
  181. package/src/modes/controllers/input-controller.ts +105 -9
  182. package/src/modes/controllers/selector-controller.ts +31 -1
  183. package/src/modes/index.ts +1 -0
  184. package/src/modes/interactive-mode.ts +28 -0
  185. package/src/modes/jobs-observer.ts +204 -0
  186. package/src/modes/rpc/host-tools.ts +1 -186
  187. package/src/modes/rpc/host-uris.ts +1 -235
  188. package/src/modes/rpc/rpc-client.ts +25 -10
  189. package/src/modes/rpc/rpc-mode.ts +12 -381
  190. package/src/modes/shared/agent-wire/command-dispatch.ts +341 -0
  191. package/src/modes/shared/agent-wire/command-validation.ts +131 -0
  192. package/src/modes/shared/agent-wire/event-envelope.ts +108 -0
  193. package/src/modes/shared/agent-wire/handshake.ts +117 -0
  194. package/src/modes/shared/agent-wire/host-tool-bridge.ts +194 -0
  195. package/src/modes/shared/agent-wire/host-uri-bridge.ts +236 -0
  196. package/src/modes/shared/agent-wire/protocol.ts +96 -0
  197. package/src/modes/shared/agent-wire/responses.ts +17 -0
  198. package/src/modes/shared/agent-wire/scopes.ts +89 -0
  199. package/src/modes/shared/agent-wire/ui-request-broker.ts +150 -0
  200. package/src/modes/shared/agent-wire/ui-result.ts +48 -0
  201. package/src/modes/types.ts +2 -0
  202. package/src/prompts/agents/executor.md +13 -0
  203. package/src/prompts/tools/subagent.md +39 -4
  204. package/src/prompts/tools/task-summary.md +3 -9
  205. package/src/prompts/tools/task.md +5 -1
  206. package/src/sdk.ts +8 -0
  207. package/src/session/agent-session.ts +445 -71
  208. package/src/session/session-manager.ts +13 -1
  209. package/src/skill-state/active-state.ts +58 -65
  210. package/src/skill-state/deep-interview-mutation-guard.ts +114 -17
  211. package/src/skill-state/initial-phase.ts +2 -0
  212. package/src/skill-state/workflow-state-contract.ts +33 -4
  213. package/src/skill-state/workflow-state-version.ts +3 -0
  214. package/src/slash-commands/builtin-registry.ts +8 -0
  215. package/src/task/executor.ts +79 -13
  216. package/src/task/id.ts +33 -0
  217. package/src/task/index.ts +376 -74
  218. package/src/task/output-manager.ts +5 -4
  219. package/src/task/receipt.ts +297 -0
  220. package/src/task/render.ts +54 -134
  221. package/src/task/spawn-gate.ts +132 -0
  222. package/src/task/types.ts +104 -10
  223. package/src/tools/ask.ts +88 -27
  224. package/src/tools/ast-edit.ts +1 -0
  225. package/src/tools/ast-grep.ts +1 -0
  226. package/src/tools/bash.ts +1 -1
  227. package/src/tools/cron.ts +48 -0
  228. package/src/tools/find.ts +4 -1
  229. package/src/tools/index.ts +2 -0
  230. package/src/tools/path-utils.ts +3 -2
  231. package/src/tools/read.ts +1 -0
  232. package/src/tools/search.ts +1 -0
  233. package/src/tools/skill.ts +6 -1
  234. package/src/tools/subagent.ts +423 -79
package/src/main.ts CHANGED
@@ -32,7 +32,7 @@ import { getDefault, type SettingPath, Settings, settings } from "./config/setti
32
32
  import { initializeWithSettings } from "./discovery";
33
33
  import { exportFromFile } from "./export/html";
34
34
  import type { ExtensionUIContext } from "./extensibility/extensions/types";
35
- import { InteractiveMode, runAcpMode, runPrintMode, runRpcMode } from "./modes";
35
+ import { InteractiveMode, runAcpMode, runBridgeMode, runPrintMode, runRpcMode } from "./modes";
36
36
  import { initTheme, stopThemeWatcher } from "./modes/theme/theme";
37
37
  import type { SubmittedUserInput } from "./modes/types";
38
38
  import type { MCPManager } from "./runtime-mcp";
@@ -707,21 +707,35 @@ export async function runRootCommand(
707
707
  process.exit(0);
708
708
  }
709
709
 
710
- if ((parsedArgs.mode === "rpc" || parsedArgs.mode === "rpc-ui") && parsedArgs.fileArgs.length > 0) {
711
- process.stderr.write(`${chalk.red("Error: @file arguments are not supported in RPC mode")}\n`);
710
+ if (
711
+ (parsedArgs.mode === "rpc" || parsedArgs.mode === "rpc-ui" || parsedArgs.mode === "bridge") &&
712
+ parsedArgs.fileArgs.length > 0
713
+ ) {
714
+ process.stderr.write(`${chalk.red("Error: @file arguments are not supported in RPC or bridge mode")}\n`);
712
715
  process.exit(1);
713
716
  }
714
717
 
715
718
  const cwd = getProjectDir();
716
719
  const settingsInstance = deps.settings ?? (await logger.time("settings:init", Settings.init, { cwd }));
717
- if (parsedArgs.mode === "rpc" || parsedArgs.mode === "rpc-ui" || parsedArgs.mode === "acp") {
720
+ if (
721
+ parsedArgs.mode === "rpc" ||
722
+ parsedArgs.mode === "rpc-ui" ||
723
+ parsedArgs.mode === "acp" ||
724
+ parsedArgs.mode === "bridge"
725
+ ) {
718
726
  applyRpcDefaultSettingOverrides(settingsInstance);
719
727
  }
720
728
  modelRegistry.applyConfiguredModelBindings(settingsInstance);
721
729
  if (parsedArgs.noPty || parsedArgs.mode === "rpc-ui") {
722
730
  Bun.env.PI_NO_PTY = "1";
723
731
  }
724
- if (parsedArgs.noTitle || parsedArgs.mode === "rpc" || parsedArgs.mode === "rpc-ui" || parsedArgs.mode === "acp") {
732
+ if (
733
+ parsedArgs.noTitle ||
734
+ parsedArgs.mode === "rpc" ||
735
+ parsedArgs.mode === "rpc-ui" ||
736
+ parsedArgs.mode === "acp" ||
737
+ parsedArgs.mode === "bridge"
738
+ ) {
725
739
  Bun.env.PI_NO_TITLE = "1";
726
740
  }
727
741
  const { pipedInput, fileText, fileImages } = await logger.time("prepareInitialMessage", async () => {
@@ -894,6 +908,8 @@ export async function runRootCommand(
894
908
 
895
909
  if (mode === "rpc" || mode === "rpc-ui") {
896
910
  await runRpcMode(session, mode === "rpc-ui" ? setToolUIContext : undefined);
911
+ } else if (mode === "bridge") {
912
+ await runBridgeMode(session, setToolUIContext);
897
913
  } else if (isInteractive) {
898
914
  const versionCheckPromise = checkForNewVersion(VERSION).catch(() => undefined);
899
915
  const changelogMarkdown = await logger.time("main:getChangelogForDisplay", getChangelogForDisplay, parsedArgs);
@@ -0,0 +1,41 @@
1
+ import * as crypto from "node:crypto";
2
+
3
+ export interface BridgeAuthConfig {
4
+ token: string;
5
+ }
6
+
7
+ export interface BridgeBindConfig {
8
+ hostname: string;
9
+ port: number;
10
+ tlsConfigured: boolean;
11
+ }
12
+
13
+ export function extractBearerToken(authorization: string | null | undefined): string | undefined {
14
+ if (!authorization) return undefined;
15
+ const trimmed = authorization.trim();
16
+ const match = /^Bearer\s+(.+)$/i.exec(trimmed);
17
+ const token = match?.[1]?.trim();
18
+ return token ? token : undefined;
19
+ }
20
+
21
+ export function isBridgeTokenAuthorized(authorization: string | null | undefined, config: BridgeAuthConfig): boolean {
22
+ const candidate = extractBearerToken(authorization);
23
+ if (candidate === undefined) return false;
24
+ const candidateBytes = Buffer.from(candidate, "utf8");
25
+ const expectedBytes = Buffer.from(config.token, "utf8");
26
+ return candidateBytes.length === expectedBytes.length && crypto.timingSafeEqual(candidateBytes, expectedBytes);
27
+ }
28
+
29
+ export function isLoopbackHost(hostname: string): boolean {
30
+ const normalized = hostname.trim().toLowerCase();
31
+ return normalized === "localhost" || normalized === "127.0.0.1" || normalized === "::1" || normalized === "[::1]";
32
+ }
33
+
34
+ export function assertSafeBridgeBind(config: BridgeBindConfig): void {
35
+ if (!config.tlsConfigured) {
36
+ throw new Error(
37
+ `Refusing to start bridge on ${config.hostname}:${config.port} without TLS configured. ` +
38
+ "Set GJC_BRIDGE_TLS_CERT and GJC_BRIDGE_TLS_KEY.",
39
+ );
40
+ }
41
+ }
@@ -0,0 +1,47 @@
1
+ import type {
2
+ ClientBridge,
3
+ ClientBridgePermissionOption,
4
+ ClientBridgePermissionOutcome,
5
+ ClientBridgePermissionToolCall,
6
+ } from "../../session/client-bridge";
7
+ import type { UiRequestBroker, UiRequestCancelled, UiRequestResolution } from "../shared/agent-wire/ui-request-broker";
8
+
9
+ export interface BridgePermissionRequestPayload {
10
+ kind: "permission";
11
+ toolCall: ClientBridgePermissionToolCall;
12
+ options: ClientBridgePermissionOption[];
13
+ }
14
+
15
+ export type BridgePermissionBroker = UiRequestBroker<BridgePermissionRequestPayload, ClientBridgePermissionOutcome>;
16
+ function isUiRequestCancelled(
17
+ resolution: UiRequestResolution<ClientBridgePermissionOutcome>,
18
+ ): resolution is UiRequestCancelled {
19
+ return "status" in resolution && resolution.status === "cancelled";
20
+ }
21
+
22
+ function toPermissionOutcome(
23
+ resolution: UiRequestResolution<ClientBridgePermissionOutcome>,
24
+ ): ClientBridgePermissionOutcome {
25
+ if (isUiRequestCancelled(resolution)) {
26
+ return { outcome: "cancelled" };
27
+ }
28
+ return resolution;
29
+ }
30
+
31
+ export function createBridgeClientBridge(broker: BridgePermissionBroker): ClientBridge {
32
+ return {
33
+ capabilities: { requestPermission: true },
34
+ deferAgentInitiatedTurns: true,
35
+ async requestPermission(toolCall, options, signal) {
36
+ return toPermissionOutcome(
37
+ await broker.request(
38
+ { kind: "permission", toolCall, options },
39
+ {
40
+ correlationId: toolCall.toolCallId,
41
+ signal,
42
+ },
43
+ ),
44
+ );
45
+ },
46
+ };
47
+ }
@@ -0,0 +1,520 @@
1
+ import type { ExtensionUIContext } from "../../extensibility/extensions";
2
+ import type { AgentSession } from "../../session/agent-session";
3
+ import type { ClientBridgePermissionOutcome } from "../../session/client-bridge";
4
+ import type { RpcCommand, RpcResponse } from "../rpc/rpc-types";
5
+ import { dispatchRpcCommand } from "../shared/agent-wire/command-dispatch";
6
+ import { isRpcCommand } from "../shared/agent-wire/command-validation";
7
+ import { BridgeFrameSequencer, toBridgeEventFrame } from "../shared/agent-wire/event-envelope";
8
+ import type { BridgeCapability } from "../shared/agent-wire/handshake";
9
+ import {
10
+ type BridgeHandshakeRequest,
11
+ isBridgeHandshakeRequest,
12
+ negotiateBridgeHandshake,
13
+ } from "../shared/agent-wire/handshake";
14
+ import { isRpcHostToolResult, isRpcHostToolUpdate, RpcHostToolBridge } from "../shared/agent-wire/host-tool-bridge";
15
+ import { isRpcHostUriResult, RpcHostUriBridge } from "../shared/agent-wire/host-uri-bridge";
16
+ import type { BridgeFrameType } from "../shared/agent-wire/protocol";
17
+ import {
18
+ BRIDGE_COMMAND_SCOPES,
19
+ type BridgeCommandScope,
20
+ isRpcCommandAllowed,
21
+ isRpcCommandType,
22
+ scopeForRpcCommand,
23
+ } from "../shared/agent-wire/scopes";
24
+ import { UiRequestBroker } from "../shared/agent-wire/ui-request-broker";
25
+ import type { BridgeUiResult } from "../shared/agent-wire/ui-result";
26
+ import { assertSafeBridgeBind, isBridgeTokenAuthorized } from "./auth";
27
+ import { type BridgePermissionRequestPayload, createBridgeClientBridge } from "./bridge-client-bridge";
28
+ import { BridgeExtensionUIContext, type BridgeUiRequestPayload } from "./bridge-ui-context";
29
+ import { BridgeEventStream } from "./event-stream";
30
+
31
+ const DEFAULT_BRIDGE_HOST = "127.0.0.1";
32
+ const DEFAULT_BRIDGE_PORT = 4077;
33
+
34
+ const SERVER_CAPABILITIES: readonly BridgeCapability[] = [
35
+ "events",
36
+ "prompt",
37
+ "permission",
38
+ "elicitation",
39
+ "ui.declarative",
40
+ "host_tools",
41
+ "host_uri",
42
+ ];
43
+
44
+ const DEFAULT_BRIDGE_SCOPES: readonly BridgeCommandScope[] = ["prompt"];
45
+ interface BridgeEndpointMatrix {
46
+ events: boolean;
47
+ commands: boolean;
48
+ control: boolean;
49
+ uiResponses: boolean;
50
+ hostToolResults: boolean;
51
+ hostUriResults: boolean;
52
+ }
53
+
54
+ const FAIL_CLOSED_BRIDGE_ENDPOINTS: BridgeEndpointMatrix = {
55
+ events: false,
56
+ commands: false,
57
+ control: false,
58
+ uiResponses: false,
59
+ hostToolResults: false,
60
+ hostUriResults: false,
61
+ };
62
+
63
+ const MAX_IDEMPOTENCY_RECORDS = 1_000;
64
+
65
+ const SERVER_FRAME_TYPES: readonly BridgeFrameType[] = [
66
+ "ready",
67
+ "event",
68
+ "response",
69
+ "ui_request",
70
+ "permission_request",
71
+ "host_tool_call",
72
+ "host_uri_request",
73
+ "reset",
74
+ "error",
75
+ ];
76
+
77
+ interface BridgeFetchHandlerOptions {
78
+ sessionId: string;
79
+ token: string;
80
+ eventStream?: BridgeEventStream;
81
+ commandDispatcher?: (command: RpcCommand) => Promise<RpcResponse>;
82
+ commandScopes?: readonly BridgeCommandScope[];
83
+ idempotencyCache?: BridgeIdempotencyCache;
84
+ permissionBroker?: UiRequestBroker<BridgePermissionRequestPayload, ClientBridgePermissionOutcome>;
85
+ uiBroker?: UiRequestBroker<BridgeUiRequestPayload, BridgeUiResult<unknown>>;
86
+ hostToolBridge?: RpcHostToolBridge;
87
+ hostUriBridge?: RpcHostUriBridge;
88
+ endpointMatrix?: Partial<BridgeEndpointMatrix>;
89
+ }
90
+
91
+ interface BridgeIdempotencyRecord {
92
+ route: string;
93
+ ownerToken?: string;
94
+ body: string | Promise<string>;
95
+ response: unknown | Promise<unknown>;
96
+ pending?: boolean;
97
+ }
98
+
99
+ type BridgeIdempotencyCache = Map<string, BridgeIdempotencyRecord>;
100
+
101
+ function idempotencyConflict(): Response {
102
+ return jsonResponse(409, { error: "idempotency_conflict" });
103
+ }
104
+
105
+ function cachedIdempotencyResponse(
106
+ cache: BridgeIdempotencyCache | undefined,
107
+ key: string | undefined,
108
+ record: Omit<BridgeIdempotencyRecord, "response">,
109
+ ): Response | Promise<Response> | undefined {
110
+ if (!key) return undefined;
111
+ const cached = cache?.get(key);
112
+ if (!cached) return undefined;
113
+ return Promise.resolve(cached.body).then(body => {
114
+ if (body !== record.body || cached.route !== record.route) return idempotencyConflict();
115
+ if (cached.ownerToken !== record.ownerToken)
116
+ return jsonResponse(403, { status: "rejected", code: "not_controller" });
117
+ return Promise.resolve(cached.response).then(response => jsonResponse(200, response));
118
+ });
119
+ }
120
+
121
+ function rememberIdempotencyResponse(
122
+ cache: BridgeIdempotencyCache | undefined,
123
+ key: string | undefined,
124
+ record: BridgeIdempotencyRecord,
125
+ ): void {
126
+ if (!key) return;
127
+ cache?.set(key, record);
128
+ if (cache && cache.size > MAX_IDEMPOTENCY_RECORDS) {
129
+ for (const [candidateKey, candidate] of cache) {
130
+ if (!candidate.pending) {
131
+ cache.delete(candidateKey);
132
+ break;
133
+ }
134
+ }
135
+ }
136
+ }
137
+
138
+ function jsonResponse(status: number, body: unknown): Response {
139
+ return new Response(JSON.stringify(body), {
140
+ status,
141
+ headers: { "Content-Type": "application/json" },
142
+ });
143
+ }
144
+
145
+ function parseBridgeScopes(value: string | undefined): readonly BridgeCommandScope[] {
146
+ if (!value?.trim()) return DEFAULT_BRIDGE_SCOPES;
147
+ const allowed = new Set(BRIDGE_COMMAND_SCOPES);
148
+ const scopes = new Set<BridgeCommandScope>(DEFAULT_BRIDGE_SCOPES);
149
+ for (const raw of value.split(",")) {
150
+ const scope = raw.trim();
151
+ if (!scope) continue;
152
+ if (!allowed.has(scope as BridgeCommandScope)) throw new Error(`Invalid GJC_BRIDGE_SCOPES entry: ${scope}`);
153
+ scopes.add(scope as BridgeCommandScope);
154
+ }
155
+ return [...scopes];
156
+ }
157
+
158
+ function hasScope(scopes: readonly BridgeCommandScope[] | undefined, scope: BridgeCommandScope): boolean {
159
+ return new Set(scopes ?? DEFAULT_BRIDGE_SCOPES).has(scope);
160
+ }
161
+ function bridgeEndpointMatrix(options: BridgeFetchHandlerOptions): BridgeEndpointMatrix {
162
+ return { ...FAIL_CLOSED_BRIDGE_ENDPOINTS, ...options.endpointMatrix };
163
+ }
164
+
165
+ function disabledEndpointResponse(endpoint: keyof BridgeEndpointMatrix): Response {
166
+ return jsonResponse(403, { error: "endpoint_disabled", endpoint });
167
+ }
168
+
169
+ function bridgeHelpResponse(matrix: BridgeEndpointMatrix): Response {
170
+ return jsonResponse(200, {
171
+ status: "experimental_gated",
172
+ message: "Bridge mode is experimental; session-control endpoints fail closed by default.",
173
+ endpoints: matrix,
174
+ });
175
+ }
176
+
177
+ function frameTypeForDispatchOutput(obj: RpcResponse | object): BridgeFrameType {
178
+ const type = typeof obj === "object" && obj !== null && "type" in obj ? (obj as { type?: unknown }).type : undefined;
179
+ if (type === "host_tool_call" || type === "host_tool_cancel") return "host_tool_call";
180
+ if (type === "host_uri_request" || type === "host_uri_cancel") return "host_uri_request";
181
+ if (type === "extension_ui_request") return "ui_request";
182
+ if (type === "response") return "response";
183
+ return "response";
184
+ }
185
+
186
+ export function createBridgeFetchHandler(options: BridgeFetchHandlerOptions): (request: Request) => Promise<Response> {
187
+ return async request => {
188
+ const endpointMatrix = bridgeEndpointMatrix(options);
189
+ const url = new URL(request.url);
190
+ if (request.method === "GET" && url.pathname === "/healthz") {
191
+ return jsonResponse(200, { status: "ok" });
192
+ }
193
+ if (request.method === "GET" && url.pathname === "/v1/help") {
194
+ return bridgeHelpResponse(endpointMatrix);
195
+ }
196
+
197
+ if (request.method === "GET" && url.pathname === `/v1/sessions/${options.sessionId}/events`) {
198
+ if (!isBridgeTokenAuthorized(request.headers.get("Authorization"), { token: options.token })) {
199
+ return jsonResponse(401, { error: "unauthorized" });
200
+ }
201
+ if (!endpointMatrix.events) return disabledEndpointResponse("events");
202
+ const lastSeqRaw = url.searchParams.get("last_seq");
203
+ if (lastSeqRaw !== null && !/^\d+$/.test(lastSeqRaw)) return jsonResponse(400, { error: "invalid_last_seq" });
204
+ const lastSeq = lastSeqRaw === null ? 0 : Number.parseInt(lastSeqRaw, 10);
205
+ return options.eventStream?.response(lastSeq) ?? jsonResponse(503, { error: "events_unavailable" });
206
+ }
207
+
208
+ if (!isBridgeTokenAuthorized(request.headers.get("Authorization"), { token: options.token })) {
209
+ return jsonResponse(401, { error: "unauthorized" });
210
+ }
211
+
212
+ if (request.method === "POST" && url.pathname === "/v1/handshake") {
213
+ let payload: BridgeHandshakeRequest;
214
+ try {
215
+ payload = (await request.json()) as BridgeHandshakeRequest;
216
+ } catch {
217
+ return jsonResponse(400, { error: "invalid_json" });
218
+ }
219
+ if (!isBridgeHandshakeRequest(payload)) {
220
+ return jsonResponse(400, { error: "invalid_request" });
221
+ }
222
+ return jsonResponse(
223
+ 200,
224
+ negotiateBridgeHandshake(payload, {
225
+ sessionId: options.sessionId,
226
+ capabilities: endpointMatrix.events ? SERVER_CAPABILITIES : [],
227
+ scopes: endpointMatrix.commands ? (options.commandScopes ?? DEFAULT_BRIDGE_SCOPES) : [],
228
+ endpoints: {
229
+ events: endpointMatrix.events ? `/v1/sessions/${options.sessionId}/events` : "",
230
+ commands: endpointMatrix.commands ? `/v1/sessions/${options.sessionId}/commands` : "",
231
+ uiResponses: endpointMatrix.uiResponses
232
+ ? `/v1/sessions/${options.sessionId}/ui-responses/{correlation_id}`
233
+ : "",
234
+ claimControl: endpointMatrix.control ? `/v1/sessions/${options.sessionId}/control:claim` : "",
235
+ disconnectControl: endpointMatrix.control
236
+ ? `/v1/sessions/${options.sessionId}/control:disconnect`
237
+ : "",
238
+ hostToolResults: endpointMatrix.hostToolResults
239
+ ? `/v1/sessions/${options.sessionId}/host-tool-results/{correlation_id}`
240
+ : "",
241
+ hostUriResults: endpointMatrix.hostUriResults
242
+ ? `/v1/sessions/${options.sessionId}/host-uri-results/{correlation_id}`
243
+ : "",
244
+ },
245
+ frameTypes: endpointMatrix.events ? SERVER_FRAME_TYPES : [],
246
+ }),
247
+ );
248
+ }
249
+
250
+ if (request.method === "POST" && url.pathname === `/v1/sessions/${options.sessionId}/commands`) {
251
+ if (!endpointMatrix.commands) return disabledEndpointResponse("commands");
252
+ if (!options.commandDispatcher) return jsonResponse(503, { error: "commands_unavailable" });
253
+ const idempotencyKey = request.headers.get("Idempotency-Key") ?? undefined;
254
+ const existingRecord = idempotencyKey ? options.idempotencyCache?.get(idempotencyKey) : undefined;
255
+ let body = "";
256
+ let payload: unknown;
257
+ let pendingResponse: PromiseWithResolvers<unknown> | undefined;
258
+ try {
259
+ if (existingRecord) {
260
+ body = await request.text();
261
+ const cached = cachedIdempotencyResponse(options.idempotencyCache, idempotencyKey, {
262
+ route: url.pathname,
263
+ body,
264
+ });
265
+ if (cached) return await cached;
266
+ } else {
267
+ const bodyPromise = request.text();
268
+ pendingResponse = Promise.withResolvers<unknown>();
269
+ void pendingResponse.promise.catch(() => undefined);
270
+ rememberIdempotencyResponse(options.idempotencyCache, idempotencyKey, {
271
+ route: url.pathname,
272
+ body: bodyPromise,
273
+ response: pendingResponse.promise,
274
+ pending: true,
275
+ });
276
+ body = await bodyPromise;
277
+ }
278
+ payload = JSON.parse(body) as unknown;
279
+ } catch {
280
+ options.idempotencyCache?.delete(idempotencyKey ?? "");
281
+ pendingResponse?.reject(new Error("invalid_json"));
282
+ return jsonResponse(400, { error: "invalid_json" });
283
+ }
284
+ const type =
285
+ typeof payload === "object" && payload !== null && "type" in payload
286
+ ? (payload as { type?: unknown }).type
287
+ : undefined;
288
+ if (!isRpcCommandType(type)) {
289
+ options.idempotencyCache?.delete(idempotencyKey ?? "");
290
+ pendingResponse?.reject(new Error("invalid_command"));
291
+ return jsonResponse(400, { error: "invalid_command" });
292
+ }
293
+ if (!isRpcCommand(payload)) {
294
+ options.idempotencyCache?.delete(idempotencyKey ?? "");
295
+ pendingResponse?.reject(new Error("invalid_command"));
296
+ return jsonResponse(400, { error: "invalid_command" });
297
+ }
298
+ const scopes = new Set(options.commandScopes ?? DEFAULT_BRIDGE_SCOPES);
299
+ if (!isRpcCommandAllowed(type, scopes)) {
300
+ options.idempotencyCache?.delete(idempotencyKey ?? "");
301
+ pendingResponse?.reject(new Error("scope_denied"));
302
+ return jsonResponse(403, { error: "scope_denied", scope: scopeForRpcCommand(type) });
303
+ }
304
+ try {
305
+ const response = await options.commandDispatcher(payload);
306
+ pendingResponse?.resolve(response);
307
+ const cachedRecord = idempotencyKey ? options.idempotencyCache?.get(idempotencyKey) : undefined;
308
+ if (cachedRecord) cachedRecord.pending = false;
309
+ return jsonResponse(200, response);
310
+ } catch (err) {
311
+ options.idempotencyCache?.delete(idempotencyKey ?? "");
312
+ pendingResponse?.reject(err);
313
+ throw err;
314
+ }
315
+ }
316
+ if (request.method === "POST" && url.pathname === `/v1/sessions/${options.sessionId}/control:claim`) {
317
+ if (!endpointMatrix.control) return disabledEndpointResponse("control");
318
+ if (!hasScope(options.commandScopes, "control"))
319
+ return jsonResponse(403, { error: "scope_denied", scope: "control" });
320
+ if (options.permissionBroker?.ownerToken || options.uiBroker?.ownerToken)
321
+ return jsonResponse(409, { error: "controller_busy" });
322
+ const ownerToken = request.headers.get("X-GJC-Bridge-Owner-Token") ?? crypto.randomUUID();
323
+ const permissionClaim = options.permissionBroker?.claimController(ownerToken);
324
+ const uiClaim = options.uiBroker?.claimController(ownerToken);
325
+ if (permissionClaim?.status === "busy" || uiClaim?.status === "busy")
326
+ return jsonResponse(409, { error: "controller_busy" });
327
+ return jsonResponse(200, { status: "claimed", ownerToken });
328
+ }
329
+ if (request.method === "POST" && url.pathname === `/v1/sessions/${options.sessionId}/control:disconnect`) {
330
+ if (!endpointMatrix.control) return disabledEndpointResponse("control");
331
+ if (!hasScope(options.commandScopes, "control"))
332
+ return jsonResponse(403, { error: "scope_denied", scope: "control" });
333
+ const ownerToken = request.headers.get("X-GJC-Bridge-Owner-Token") ?? "";
334
+ const permissionReleased = options.permissionBroker?.disconnectController(ownerToken) ?? true;
335
+ const uiReleased = options.uiBroker?.disconnectController(ownerToken) ?? true;
336
+ return permissionReleased && uiReleased
337
+ ? jsonResponse(200, { status: "released" })
338
+ : jsonResponse(403, { status: "rejected", code: "not_controller" });
339
+ }
340
+
341
+ const uiResponsePrefix = `/v1/sessions/${options.sessionId}/ui-responses/`;
342
+ if (request.method === "POST" && url.pathname.startsWith(uiResponsePrefix)) {
343
+ if (!endpointMatrix.uiResponses) return disabledEndpointResponse("uiResponses");
344
+ if (!hasScope(options.commandScopes, "control"))
345
+ return jsonResponse(403, { error: "scope_denied", scope: "control" });
346
+ const correlationId = decodeURIComponent(url.pathname.slice(uiResponsePrefix.length));
347
+ const ownerToken = request.headers.get("X-GJC-Bridge-Owner-Token") ?? "";
348
+ const idempotencyKey = request.headers.get("Idempotency-Key") ?? undefined;
349
+ let body = "";
350
+ let payload: unknown;
351
+ try {
352
+ body = await request.text();
353
+ const cached = cachedIdempotencyResponse(options.idempotencyCache, idempotencyKey, {
354
+ route: url.pathname,
355
+ ownerToken,
356
+ body,
357
+ });
358
+ if (cached) return await cached;
359
+ payload = JSON.parse(body) as unknown;
360
+ } catch {
361
+ return jsonResponse(400, { error: "invalid_json" });
362
+ }
363
+ const permissionResult = options.permissionBroker?.respond(
364
+ correlationId,
365
+ ownerToken,
366
+ payload as ClientBridgePermissionOutcome,
367
+ );
368
+ if (permissionResult?.status === "accepted") {
369
+ rememberIdempotencyResponse(options.idempotencyCache, idempotencyKey, {
370
+ route: url.pathname,
371
+ ownerToken,
372
+ body,
373
+ response: permissionResult,
374
+ });
375
+ return jsonResponse(200, permissionResult);
376
+ }
377
+ const uiResult = options.uiBroker?.respond(correlationId, ownerToken, payload as BridgeUiResult<unknown>);
378
+ if (uiResult?.status === "accepted") {
379
+ rememberIdempotencyResponse(options.idempotencyCache, idempotencyKey, {
380
+ route: url.pathname,
381
+ ownerToken,
382
+ body,
383
+ response: uiResult,
384
+ });
385
+ return jsonResponse(200, uiResult);
386
+ }
387
+ const rejection = uiResult ?? permissionResult ?? { status: "rejected", code: "unknown_request" };
388
+ return jsonResponse(
389
+ rejection.status === "rejected" && rejection.code === "not_controller" ? 403 : 409,
390
+ rejection,
391
+ );
392
+ }
393
+ const hostToolResultPrefix = `/v1/sessions/${options.sessionId}/host-tool-results/`;
394
+ if (request.method === "POST" && url.pathname.startsWith(hostToolResultPrefix)) {
395
+ if (!endpointMatrix.hostToolResults) return disabledEndpointResponse("hostToolResults");
396
+ if (!hasScope(options.commandScopes, "host_tools"))
397
+ return jsonResponse(403, { error: "scope_denied", scope: "host_tools" });
398
+ if (!options.hostToolBridge) return jsonResponse(503, { error: "host_tools_unavailable" });
399
+ const id = decodeURIComponent(url.pathname.slice(hostToolResultPrefix.length));
400
+ let payload: unknown;
401
+ try {
402
+ payload = await request.json();
403
+ } catch {
404
+ return jsonResponse(400, { error: "invalid_json" });
405
+ }
406
+ const frame = typeof payload === "object" && payload !== null ? { ...payload, id } : { id };
407
+ let handled = false;
408
+ if (isRpcHostToolUpdate(frame)) {
409
+ handled = options.hostToolBridge.handleUpdate(frame);
410
+ } else if (isRpcHostToolResult(frame)) {
411
+ handled = options.hostToolBridge.handleResult(frame);
412
+ } else {
413
+ return jsonResponse(400, { error: "invalid_host_tool_result" });
414
+ }
415
+ return handled ? jsonResponse(200, { status: "accepted" }) : jsonResponse(404, { error: "unknown_request" });
416
+ }
417
+
418
+ const hostUriResultPrefix = `/v1/sessions/${options.sessionId}/host-uri-results/`;
419
+ if (request.method === "POST" && url.pathname.startsWith(hostUriResultPrefix)) {
420
+ if (!endpointMatrix.hostUriResults) return disabledEndpointResponse("hostUriResults");
421
+ if (!hasScope(options.commandScopes, "host_uri"))
422
+ return jsonResponse(403, { error: "scope_denied", scope: "host_uri" });
423
+ if (!options.hostUriBridge) return jsonResponse(503, { error: "host_uri_unavailable" });
424
+ const id = decodeURIComponent(url.pathname.slice(hostUriResultPrefix.length));
425
+ let payload: unknown;
426
+ try {
427
+ payload = await request.json();
428
+ } catch {
429
+ return jsonResponse(400, { error: "invalid_json" });
430
+ }
431
+ const frame = typeof payload === "object" && payload !== null ? { ...payload, id } : { id };
432
+ if (!isRpcHostUriResult(frame)) return jsonResponse(400, { error: "invalid_host_uri_result" });
433
+ return options.hostUriBridge.handleResult(frame)
434
+ ? jsonResponse(200, { status: "accepted" })
435
+ : jsonResponse(404, { error: "unknown_request" });
436
+ }
437
+ return jsonResponse(404, { error: "not_found" });
438
+ };
439
+ }
440
+
441
+ export async function runBridgeMode(
442
+ session: AgentSession,
443
+ setToolUIContext?: (uiContext: ExtensionUIContext, hasUI: boolean) => void,
444
+ ): Promise<never> {
445
+ const token = Bun.env.GJC_BRIDGE_TOKEN;
446
+ if (!token) {
447
+ throw new Error("GJC_BRIDGE_TOKEN is required for --mode bridge");
448
+ }
449
+ const hostname = Bun.env.GJC_BRIDGE_HOST ?? DEFAULT_BRIDGE_HOST;
450
+ const port = Bun.env.GJC_BRIDGE_PORT ? Number.parseInt(Bun.env.GJC_BRIDGE_PORT, 10) : DEFAULT_BRIDGE_PORT;
451
+ if (!Number.isInteger(port) || port <= 0 || port > 65_535) {
452
+ throw new Error(`Invalid GJC_BRIDGE_PORT: ${Bun.env.GJC_BRIDGE_PORT}`);
453
+ }
454
+ const commandScopes = parseBridgeScopes(Bun.env.GJC_BRIDGE_SCOPES);
455
+
456
+ const certPath = Bun.env.GJC_BRIDGE_TLS_CERT;
457
+ const keyPath = Bun.env.GJC_BRIDGE_TLS_KEY;
458
+ const tlsConfigured = Boolean(certPath && keyPath);
459
+ assertSafeBridgeBind({ hostname, port, tlsConfigured });
460
+
461
+ const tls = tlsConfigured
462
+ ? {
463
+ cert: await Bun.file(certPath!).text(),
464
+ key: await Bun.file(keyPath!).text(),
465
+ }
466
+ : undefined;
467
+
468
+ const eventStream = new BridgeEventStream();
469
+ const sequencer = new BridgeFrameSequencer(session.sessionId);
470
+ const permissionBroker = new UiRequestBroker<BridgePermissionRequestPayload, ClientBridgePermissionOutcome>({
471
+ emitRequest: (correlationId, request) => {
472
+ eventStream.publish(sequencer.next("permission_request", request, correlationId));
473
+ },
474
+ });
475
+ const uiBroker = new UiRequestBroker<BridgeUiRequestPayload, BridgeUiResult<unknown>>({
476
+ emitRequest: (correlationId, request) => {
477
+ eventStream.publish(sequencer.next("ui_request", request, correlationId));
478
+ },
479
+ });
480
+ const uiContext = new BridgeExtensionUIContext({
481
+ broker: uiBroker,
482
+ emit: payload => eventStream.publish(sequencer.next("ui_request", payload)),
483
+ });
484
+ setToolUIContext?.(uiContext, true);
485
+ session.setClientBridge(createBridgeClientBridge(permissionBroker));
486
+ session.subscribe(event => eventStream.publish(toBridgeEventFrame(event, sequencer)));
487
+ const output = (obj: RpcResponse | object) => {
488
+ eventStream.publish(sequencer.next(frameTypeForDispatchOutput(obj), obj));
489
+ };
490
+ const hostToolBridge = new RpcHostToolBridge(output);
491
+ const hostUriBridge = new RpcHostUriBridge(output);
492
+ const idempotencyCache: BridgeIdempotencyCache = new Map();
493
+
494
+ Bun.serve({
495
+ hostname,
496
+ port,
497
+ ...(tls ? { tls } : {}),
498
+ fetch: createBridgeFetchHandler({
499
+ sessionId: session.sessionId,
500
+ token,
501
+ eventStream,
502
+ idempotencyCache,
503
+ permissionBroker,
504
+ uiBroker,
505
+ hostToolBridge,
506
+ hostUriBridge,
507
+ commandScopes,
508
+ commandDispatcher: command =>
509
+ dispatchRpcCommand(command, {
510
+ session,
511
+ output,
512
+ hostToolRegistry: hostToolBridge,
513
+ hostUriRegistry: hostUriBridge,
514
+ createUiContext: () => uiContext,
515
+ }),
516
+ }),
517
+ });
518
+
519
+ return new Promise<never>(() => {});
520
+ }