@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
@@ -1,186 +1 @@
1
- import type { AgentTool, AgentToolResult, AgentToolUpdateCallback } from "@gajae-code/agent-core";
2
- import type { Static, TSchema } from "@gajae-code/ai";
3
- import { Snowflake } from "@gajae-code/utils";
4
- import { applyToolProxy } from "../../extensibility/tool-proxy";
5
- import type { Theme } from "../../modes/theme/theme";
6
- import type {
7
- RpcHostToolCallRequest,
8
- RpcHostToolCancelRequest,
9
- RpcHostToolDefinition,
10
- RpcHostToolResult,
11
- RpcHostToolUpdate,
12
- } from "./rpc-types";
13
-
14
- type RpcHostToolOutput = (frame: RpcHostToolCallRequest | RpcHostToolCancelRequest) => void;
15
-
16
- type PendingHostToolCall = {
17
- resolve: (result: AgentToolResult<unknown>) => void;
18
- reject: (error: Error) => void;
19
- onUpdate?: AgentToolUpdateCallback<unknown>;
20
- };
21
-
22
- function isAgentToolResult(value: unknown): value is AgentToolResult<unknown> {
23
- if (!value || typeof value !== "object") return false;
24
- const content = (value as { content?: unknown }).content;
25
- return Array.isArray(content);
26
- }
27
-
28
- export function isRpcHostToolResult(value: unknown): value is RpcHostToolResult {
29
- if (!value || typeof value !== "object") return false;
30
- const frame = value as { type?: unknown; id?: unknown; result?: unknown };
31
- return frame.type === "host_tool_result" && typeof frame.id === "string" && isAgentToolResult(frame.result);
32
- }
33
-
34
- export function isRpcHostToolUpdate(value: unknown): value is RpcHostToolUpdate {
35
- if (!value || typeof value !== "object") return false;
36
- const frame = value as { type?: unknown; id?: unknown; partialResult?: unknown };
37
- return frame.type === "host_tool_update" && typeof frame.id === "string" && isAgentToolResult(frame.partialResult);
38
- }
39
-
40
- class RpcHostToolAdapter<TParams extends TSchema = TSchema, TTheme extends Theme = Theme>
41
- implements AgentTool<TParams, unknown, TTheme>
42
- {
43
- declare name: string;
44
- declare label: string;
45
- declare description: string;
46
- declare parameters: TParams;
47
- readonly strict = true;
48
- concurrency: "shared" | "exclusive" = "shared";
49
- #bridge: RpcHostToolBridge;
50
- #definition: RpcHostToolDefinition;
51
-
52
- constructor(definition: RpcHostToolDefinition, bridge: RpcHostToolBridge) {
53
- this.#definition = definition;
54
- this.#bridge = bridge;
55
- applyToolProxy(definition, this);
56
- }
57
-
58
- execute(
59
- toolCallId: string,
60
- params: Static<TParams>,
61
- signal?: AbortSignal,
62
- onUpdate?: AgentToolUpdateCallback<unknown>,
63
- ): Promise<AgentToolResult<unknown>> {
64
- return this.#bridge.requestExecution(
65
- this.#definition,
66
- toolCallId,
67
- params as Record<string, unknown>,
68
- signal,
69
- onUpdate,
70
- );
71
- }
72
- }
73
-
74
- export class RpcHostToolBridge {
75
- #output: RpcHostToolOutput;
76
- #definitions = new Map<string, RpcHostToolDefinition>();
77
- #pendingCalls = new Map<string, PendingHostToolCall>();
78
-
79
- constructor(output: RpcHostToolOutput) {
80
- this.#output = output;
81
- }
82
-
83
- getToolNames(): string[] {
84
- return Array.from(this.#definitions.keys());
85
- }
86
-
87
- setTools(tools: RpcHostToolDefinition[]): AgentTool[] {
88
- this.#definitions = new Map(tools.map(tool => [tool.name, tool]));
89
- return tools.map(tool => new RpcHostToolAdapter(tool, this));
90
- }
91
-
92
- handleResult(frame: RpcHostToolResult): boolean {
93
- const pending = this.#pendingCalls.get(frame.id);
94
- if (!pending) return false;
95
- this.#pendingCalls.delete(frame.id);
96
- if (frame.isError) {
97
- const text = frame.result.content
98
- .filter(
99
- (item): item is { type: "text"; text: string } => item.type === "text" && typeof item.text === "string",
100
- )
101
- .map(item => item.text)
102
- .join("\n")
103
- .trim();
104
- pending.reject(new Error(text || "Host tool execution failed"));
105
- return true;
106
- }
107
- pending.resolve(frame.result);
108
- return true;
109
- }
110
-
111
- handleUpdate(frame: RpcHostToolUpdate): boolean {
112
- const pending = this.#pendingCalls.get(frame.id);
113
- if (!pending) return false;
114
- pending.onUpdate?.(frame.partialResult);
115
- return true;
116
- }
117
-
118
- requestExecution(
119
- definition: RpcHostToolDefinition,
120
- toolCallId: string,
121
- args: Record<string, unknown>,
122
- signal?: AbortSignal,
123
- onUpdate?: AgentToolUpdateCallback<unknown>,
124
- ): Promise<AgentToolResult<unknown>> {
125
- if (signal?.aborted) {
126
- return Promise.reject(new Error(`Host tool "${definition.name}" was aborted`));
127
- }
128
-
129
- const id = Snowflake.next() as string;
130
- const { promise, resolve, reject } = Promise.withResolvers<AgentToolResult<unknown>>();
131
- let settled = false;
132
-
133
- const cleanup = () => {
134
- signal?.removeEventListener("abort", onAbort);
135
- this.#pendingCalls.delete(id);
136
- };
137
-
138
- const onAbort = () => {
139
- if (settled) return;
140
- settled = true;
141
- cleanup();
142
- this.#output({
143
- type: "host_tool_cancel",
144
- id: Snowflake.next() as string,
145
- targetId: id,
146
- });
147
- reject(new Error(`Host tool "${definition.name}" was aborted`));
148
- };
149
-
150
- signal?.addEventListener("abort", onAbort, { once: true });
151
- this.#pendingCalls.set(id, {
152
- resolve: result => {
153
- if (settled) return;
154
- settled = true;
155
- cleanup();
156
- resolve(result);
157
- },
158
- reject: error => {
159
- if (settled) return;
160
- settled = true;
161
- cleanup();
162
- reject(error);
163
- },
164
- onUpdate,
165
- });
166
-
167
- this.#output({
168
- type: "host_tool_call",
169
- id,
170
- toolCallId,
171
- toolName: definition.name,
172
- arguments: args,
173
- });
174
-
175
- return promise;
176
- }
177
-
178
- rejectAllPending(message: string): void {
179
- const error = new Error(message);
180
- const pendingCalls = Array.from(this.#pendingCalls.values());
181
- this.#pendingCalls.clear();
182
- for (const pending of pendingCalls) {
183
- pending.reject(error);
184
- }
185
- }
186
- }
1
+ export * from "../shared/agent-wire/host-tool-bridge";
@@ -1,235 +1 @@
1
- import { Snowflake } from "@gajae-code/utils";
2
- import { InternalUrlRouter } from "../../internal-urls";
3
- import type {
4
- InternalResource,
5
- InternalUrl,
6
- ProtocolHandler,
7
- ResolveContext,
8
- WriteContext,
9
- } from "../../internal-urls/types";
10
- import type {
11
- RpcHostUriCancelRequest,
12
- RpcHostUriRequest,
13
- RpcHostUriResult,
14
- RpcHostUriSchemeDefinition,
15
- } from "./rpc-types";
16
-
17
- type RpcHostUriOutput = (frame: RpcHostUriRequest | RpcHostUriCancelRequest) => void;
18
-
19
- type PendingUriRequest = {
20
- operation: "read" | "write";
21
- url: string;
22
- resolve: (frame: RpcHostUriResult) => void;
23
- reject: (error: Error) => void;
24
- };
25
-
26
- /** Type guard for inbound `host_uri_result` frames coming from the host. */
27
- export function isRpcHostUriResult(value: unknown): value is RpcHostUriResult {
28
- if (!value || typeof value !== "object") return false;
29
- const frame = value as { type?: unknown; id?: unknown };
30
- return frame.type === "host_uri_result" && typeof frame.id === "string";
31
- }
32
-
33
- /**
34
- * One handler instance per host-registered scheme. Delegates reads and (when
35
- * the scheme was registered as writable) writes to the bridge, which serializes
36
- * them over the RPC transport.
37
- */
38
- class RpcHostUriProtocolHandler implements ProtocolHandler {
39
- readonly scheme: string;
40
- readonly immutable: boolean;
41
- readonly write?: (url: InternalUrl, content: string, context?: WriteContext) => Promise<void>;
42
- readonly #bridge: RpcHostUriBridge;
43
-
44
- constructor(definition: RpcHostUriSchemeDefinition, bridge: RpcHostUriBridge) {
45
- this.scheme = definition.scheme;
46
- this.immutable = definition.immutable === true;
47
- this.#bridge = bridge;
48
- if (definition.writable === true) {
49
- this.write = (url, content, context) => this.#bridge.requestWrite(this.scheme, url, content, context);
50
- }
51
- }
52
-
53
- resolve(url: InternalUrl, context?: ResolveContext): Promise<InternalResource> {
54
- return this.#bridge.requestRead(this.scheme, url, context);
55
- }
56
- }
57
-
58
- /**
59
- * Bidirectional bridge that lets the RPC host own a set of URI schemes.
60
- *
61
- * The host registers schemes via `set_host_uri_schemes`; the bridge installs
62
- * a `RpcHostUriProtocolHandler` per scheme into the process-global
63
- * {@link InternalUrlRouter}. Reads land on the read tool through the existing
64
- * router; writes are intercepted by the write tool and dispatched through
65
- * `requestWrite`.
66
- */
67
- export class RpcHostUriBridge {
68
- #output: RpcHostUriOutput;
69
- #router: InternalUrlRouter;
70
- #definitions = new Map<string, RpcHostUriSchemeDefinition>();
71
- #pending = new Map<string, PendingUriRequest>();
72
-
73
- constructor(output: RpcHostUriOutput, router: InternalUrlRouter = InternalUrlRouter.instance()) {
74
- this.#output = output;
75
- this.#router = router;
76
- }
77
-
78
- getSchemes(): string[] {
79
- return Array.from(this.#definitions.keys());
80
- }
81
-
82
- /**
83
- * Replace the registered set of host URI schemes. Previously registered
84
- * schemes that no longer appear in the new set are unregistered from the
85
- * router; surviving and new schemes get fresh handler instances.
86
- */
87
- setSchemes(schemes: RpcHostUriSchemeDefinition[]): string[] {
88
- const normalized = new Map<string, RpcHostUriSchemeDefinition>();
89
- for (const raw of schemes) {
90
- const scheme = typeof raw?.scheme === "string" ? raw.scheme.trim().toLowerCase() : "";
91
- if (!scheme) {
92
- throw new Error("Host URI scheme must be a non-empty string");
93
- }
94
- if (!/^[a-z][a-z0-9+.-]*$/.test(scheme)) {
95
- throw new Error(`Host URI scheme contains invalid characters: ${raw.scheme}`);
96
- }
97
- normalized.set(scheme, {
98
- scheme,
99
- description: typeof raw.description === "string" ? raw.description : undefined,
100
- writable: raw.writable === true,
101
- immutable: raw.immutable === true,
102
- });
103
- }
104
-
105
- for (const previous of this.#definitions.keys()) {
106
- if (!normalized.has(previous)) {
107
- this.#router.unregister(previous);
108
- }
109
- }
110
- for (const definition of normalized.values()) {
111
- this.#router.register(new RpcHostUriProtocolHandler(definition, this));
112
- }
113
- this.#definitions = normalized;
114
- return Array.from(normalized.keys());
115
- }
116
-
117
- /**
118
- * Unregister every host scheme from the router and reject any in-flight
119
- * requests. Called on RPC shutdown to keep the global router clean for
120
- * subsequent sessions in the same process (used by tests).
121
- */
122
- clear(message: string = "Host URI bridge shut down"): void {
123
- for (const scheme of this.#definitions.keys()) {
124
- this.#router.unregister(scheme);
125
- }
126
- this.#definitions.clear();
127
- this.rejectAllPending(message);
128
- }
129
-
130
- /** Resolve a pending request by id; called by `rpc-mode` on inbound results. */
131
- handleResult(frame: RpcHostUriResult): boolean {
132
- const pending = this.#pending.get(frame.id);
133
- if (!pending) return false;
134
- this.#pending.delete(frame.id);
135
- pending.resolve(frame);
136
- return true;
137
- }
138
-
139
- rejectAllPending(message: string): void {
140
- const error = new Error(message);
141
- const pending = Array.from(this.#pending.values());
142
- this.#pending.clear();
143
- for (const entry of pending) {
144
- entry.reject(error);
145
- }
146
- }
147
-
148
- async requestRead(scheme: string, url: InternalUrl, context?: ResolveContext): Promise<InternalResource> {
149
- const result = await this.#dispatch("read", url.href, undefined, context?.signal);
150
- if (result.isError) {
151
- throw new Error(result.error || result.content || `Host URI read failed for ${url.href}`);
152
- }
153
- const content = result.content ?? "";
154
- const contentType = result.contentType ?? "text/plain";
155
- const definition = this.#definitions.get(scheme);
156
- return {
157
- url: url.href,
158
- content,
159
- contentType,
160
- size: Buffer.byteLength(content, "utf-8"),
161
- notes: result.notes && result.notes.length > 0 ? [...result.notes] : undefined,
162
- immutable: result.immutable ?? definition?.immutable === true,
163
- };
164
- }
165
-
166
- async requestWrite(_scheme: string, url: InternalUrl, content: string, context?: WriteContext): Promise<void> {
167
- const result = await this.#dispatch("write", url.href, content, context?.signal);
168
- if (result.isError) {
169
- throw new Error(result.error || result.content || `Host URI write failed for ${url.href}`);
170
- }
171
- }
172
-
173
- #dispatch(
174
- operation: "read" | "write",
175
- url: string,
176
- content: string | undefined,
177
- signal: AbortSignal | undefined,
178
- ): Promise<RpcHostUriResult> {
179
- if (signal?.aborted) {
180
- return Promise.reject(new Error(`Host URI ${operation} for ${url} was aborted`));
181
- }
182
-
183
- const id = Snowflake.next() as string;
184
- const { promise, resolve, reject } = Promise.withResolvers<RpcHostUriResult>();
185
- let settled = false;
186
-
187
- const cleanup = () => {
188
- signal?.removeEventListener("abort", onAbort);
189
- this.#pending.delete(id);
190
- };
191
-
192
- const onAbort = () => {
193
- if (settled) return;
194
- settled = true;
195
- cleanup();
196
- this.#output({
197
- type: "host_uri_cancel",
198
- id: Snowflake.next() as string,
199
- targetId: id,
200
- });
201
- reject(new Error(`Host URI ${operation} for ${url} was aborted`));
202
- };
203
-
204
- signal?.addEventListener("abort", onAbort, { once: true });
205
- this.#pending.set(id, {
206
- operation,
207
- url,
208
- resolve: frame => {
209
- if (settled) return;
210
- settled = true;
211
- cleanup();
212
- resolve(frame);
213
- },
214
- reject: err => {
215
- if (settled) return;
216
- settled = true;
217
- cleanup();
218
- reject(err);
219
- },
220
- });
221
-
222
- const frame: RpcHostUriRequest = {
223
- type: "host_uri_request",
224
- id,
225
- operation,
226
- url,
227
- };
228
- if (operation === "write") {
229
- frame.content = content ?? "";
230
- }
231
- this.#output(frame);
232
-
233
- return promise;
234
- }
235
- }
1
+ export * from "../shared/agent-wire/host-uri-bridge";
@@ -186,7 +186,12 @@ export class RpcClient {
186
186
  cwd: this.options.cwd,
187
187
  env: { ...Bun.env, ...this.options.env },
188
188
  stdin: "pipe",
189
+ stderr: "full",
189
190
  });
191
+ const startupStderrPromise = this.#process.stderr
192
+ ? new Response(this.#process.stderr).text().catch(() => "")
193
+ : Promise.resolve("");
194
+ const getStartupStderr = async () => this.#process?.peekStderr() || (await startupStderrPromise);
190
195
 
191
196
  // Wait for the "ready" signal or process exit
192
197
  const { promise: readyPromise, resolve: readyResolve, reject: readyReject } = Promise.withResolvers<void>();
@@ -203,10 +208,20 @@ export class RpcClient {
203
208
  }
204
209
  this.#handleLine(line);
205
210
  }
206
- // Stream ended without ready signal — process exited
211
+ // Stream ended without ready signal — process exited. Wait for the
212
+ // managed process wrapper so stderr is fully drained before reporting.
207
213
  if (!readySettled) {
208
- readySettled = true;
209
- readyReject(new Error(`Agent process exited before ready. Stderr: ${this.#process?.peekStderr() ?? ""}`));
214
+ const proc = this.#process;
215
+ const exitCode = proc ? await proc.exited.catch(() => proc.exitCode ?? -1) : undefined;
216
+ if (!readySettled) {
217
+ const stderr = await getStartupStderr();
218
+ readySettled = true;
219
+ readyReject(
220
+ new Error(
221
+ `Agent process exited${exitCode === undefined ? "" : ` with code ${exitCode}`} before ready. Stderr: ${stderr}`,
222
+ ),
223
+ );
224
+ }
210
225
  }
211
226
  })().catch((err: Error) => {
212
227
  if (!readySettled) {
@@ -216,12 +231,12 @@ export class RpcClient {
216
231
  });
217
232
 
218
233
  // Also race against process exit (in case stdout closes before we read it)
219
- void this.#process.exited.then((exitCode: number) => {
234
+ void this.#process.exited.then(async (exitCode: number) => {
220
235
  if (!readySettled) {
236
+ const stderr = await getStartupStderr();
237
+ if (readySettled) return;
221
238
  readySettled = true;
222
- readyReject(
223
- new Error(`Agent process exited with code ${exitCode}. Stderr: ${this.#process?.peekStderr() ?? ""}`),
224
- );
239
+ readyReject(new Error(`Agent process exited with code ${exitCode}. Stderr: ${stderr}`));
225
240
  }
226
241
  });
227
242
 
@@ -229,9 +244,9 @@ export class RpcClient {
229
244
  const readyTimeout = this.#startTimeout(30000, () => {
230
245
  if (readySettled) return;
231
246
  readySettled = true;
232
- readyReject(
233
- new Error(`Timeout waiting for agent to become ready. Stderr: ${this.#process?.peekStderr() ?? ""}`),
234
- );
247
+ void getStartupStderr().then(stderr => {
248
+ readyReject(new Error(`Timeout waiting for agent to become ready. Stderr: ${stderr}`));
249
+ });
235
250
  });
236
251
 
237
252
  try {