@ai-hero/sandcastle 0.6.5 → 0.7.0

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 (229) hide show
  1. package/README.md +137 -62
  2. package/dist/{MountConfig.d.ts → MountConfig-CmXclHA5.d.ts} +3 -2
  3. package/dist/{SandboxProvider.d.ts → SandboxProvider-EkSMuBp8.d.ts} +25 -39
  4. package/dist/chunk-52CIJF45.js +25569 -0
  5. package/dist/chunk-52CIJF45.js.map +1 -0
  6. package/dist/chunk-5VM5QZ26.js +26988 -0
  7. package/dist/chunk-5VM5QZ26.js.map +1 -0
  8. package/dist/chunk-72UVAC7B.js +99 -0
  9. package/dist/chunk-72UVAC7B.js.map +1 -0
  10. package/dist/chunk-BIWNFKGV.js +22 -0
  11. package/dist/chunk-BIWNFKGV.js.map +1 -0
  12. package/dist/chunk-NGBM7T3E.js +76 -0
  13. package/dist/chunk-NGBM7T3E.js.map +1 -0
  14. package/dist/chunk-NSFQW6ML.js +362 -0
  15. package/dist/chunk-NSFQW6ML.js.map +1 -0
  16. package/dist/index.d.ts +920 -22
  17. package/dist/index.js +3212 -9
  18. package/dist/index.js.map +1 -1
  19. package/dist/main.d.ts +0 -2
  20. package/dist/main.js +19349 -13
  21. package/dist/main.js.map +1 -1
  22. package/dist/mountUtils-CCA-bbpK.d.ts +25 -0
  23. package/dist/sandboxes/daytona.d.ts +8 -5
  24. package/dist/sandboxes/daytona.js +118 -124
  25. package/dist/sandboxes/daytona.js.map +1 -1
  26. package/dist/sandboxes/docker.d.ts +10 -8
  27. package/dist/sandboxes/docker.js +8 -255
  28. package/dist/sandboxes/docker.js.map +1 -1
  29. package/dist/sandboxes/no-sandbox.d.ts +7 -4
  30. package/dist/sandboxes/no-sandbox.js +6 -114
  31. package/dist/sandboxes/no-sandbox.js.map +1 -1
  32. package/dist/sandboxes/podman.d.ts +10 -8
  33. package/dist/sandboxes/podman.js +287 -297
  34. package/dist/sandboxes/podman.js.map +1 -1
  35. package/dist/sandboxes/vercel.d.ts +7 -4
  36. package/dist/sandboxes/vercel.js +144 -165
  37. package/dist/sandboxes/vercel.js.map +1 -1
  38. package/dist/templates/sequential-reviewer/implement-prompt.md +2 -2
  39. package/dist/templates/simple-loop/prompt.md +2 -2
  40. package/package.json +15 -14
  41. package/dist/AgentProvider.d.ts +0 -134
  42. package/dist/AgentProvider.d.ts.map +0 -1
  43. package/dist/AgentProvider.js +0 -647
  44. package/dist/AgentProvider.js.map +0 -1
  45. package/dist/AgentStreamEmitter.d.ts +0 -36
  46. package/dist/AgentStreamEmitter.d.ts.map +0 -1
  47. package/dist/AgentStreamEmitter.js +0 -21
  48. package/dist/AgentStreamEmitter.js.map +0 -1
  49. package/dist/CopyToWorktree.d.ts +0 -15
  50. package/dist/CopyToWorktree.d.ts.map +0 -1
  51. package/dist/CopyToWorktree.js +0 -60
  52. package/dist/CopyToWorktree.js.map +0 -1
  53. package/dist/Display.d.ts +0 -58
  54. package/dist/Display.d.ts.map +0 -1
  55. package/dist/Display.js +0 -142
  56. package/dist/Display.js.map +0 -1
  57. package/dist/DockerLifecycle.d.ts +0 -54
  58. package/dist/DockerLifecycle.d.ts.map +0 -1
  59. package/dist/DockerLifecycle.js +0 -123
  60. package/dist/DockerLifecycle.js.map +0 -1
  61. package/dist/EnvResolver.d.ts +0 -11
  62. package/dist/EnvResolver.d.ts.map +0 -1
  63. package/dist/EnvResolver.js +0 -63
  64. package/dist/EnvResolver.js.map +0 -1
  65. package/dist/ErrorHandler.d.ts +0 -15
  66. package/dist/ErrorHandler.d.ts.map +0 -1
  67. package/dist/ErrorHandler.js +0 -85
  68. package/dist/ErrorHandler.js.map +0 -1
  69. package/dist/InitService.d.ts +0 -92
  70. package/dist/InitService.d.ts.map +0 -1
  71. package/dist/InitService.js +0 -836
  72. package/dist/InitService.js.map +0 -1
  73. package/dist/MountConfig.d.ts.map +0 -1
  74. package/dist/MountConfig.js +0 -7
  75. package/dist/MountConfig.js.map +0 -1
  76. package/dist/Orchestrator.d.ts +0 -56
  77. package/dist/Orchestrator.d.ts.map +0 -1
  78. package/dist/Orchestrator.js +0 -293
  79. package/dist/Orchestrator.js.map +0 -1
  80. package/dist/Output.d.ts +0 -107
  81. package/dist/Output.d.ts.map +0 -1
  82. package/dist/Output.js +0 -95
  83. package/dist/Output.js.map +0 -1
  84. package/dist/PodmanLifecycle.d.ts +0 -17
  85. package/dist/PodmanLifecycle.d.ts.map +0 -1
  86. package/dist/PodmanLifecycle.js +0 -45
  87. package/dist/PodmanLifecycle.js.map +0 -1
  88. package/dist/PromptArgumentSubstitution.d.ts +0 -32
  89. package/dist/PromptArgumentSubstitution.d.ts.map +0 -1
  90. package/dist/PromptArgumentSubstitution.js +0 -104
  91. package/dist/PromptArgumentSubstitution.js.map +0 -1
  92. package/dist/PromptPreprocessor.d.ts +0 -15
  93. package/dist/PromptPreprocessor.d.ts.map +0 -1
  94. package/dist/PromptPreprocessor.js +0 -55
  95. package/dist/PromptPreprocessor.js.map +0 -1
  96. package/dist/PromptResolver.d.ts +0 -21
  97. package/dist/PromptResolver.d.ts.map +0 -1
  98. package/dist/PromptResolver.js +0 -27
  99. package/dist/PromptResolver.js.map +0 -1
  100. package/dist/RecoveryMessage.d.ts +0 -15
  101. package/dist/RecoveryMessage.d.ts.map +0 -1
  102. package/dist/RecoveryMessage.js +0 -81
  103. package/dist/RecoveryMessage.js.map +0 -1
  104. package/dist/SandboxFactory.d.ts +0 -90
  105. package/dist/SandboxFactory.d.ts.map +0 -1
  106. package/dist/SandboxFactory.js +0 -324
  107. package/dist/SandboxFactory.js.map +0 -1
  108. package/dist/SandboxLifecycle.d.ts +0 -65
  109. package/dist/SandboxLifecycle.d.ts.map +0 -1
  110. package/dist/SandboxLifecycle.js +0 -296
  111. package/dist/SandboxLifecycle.js.map +0 -1
  112. package/dist/SandboxProvider.d.ts.map +0 -1
  113. package/dist/SandboxProvider.js +0 -28
  114. package/dist/SandboxProvider.js.map +0 -1
  115. package/dist/SessionStore.d.ts +0 -110
  116. package/dist/SessionStore.d.ts.map +0 -1
  117. package/dist/SessionStore.js +0 -330
  118. package/dist/SessionStore.js.map +0 -1
  119. package/dist/TextDeltaBuffer.d.ts +0 -24
  120. package/dist/TextDeltaBuffer.d.ts.map +0 -1
  121. package/dist/TextDeltaBuffer.js +0 -68
  122. package/dist/TextDeltaBuffer.js.map +0 -1
  123. package/dist/WorktreeManager.d.ts +0 -79
  124. package/dist/WorktreeManager.d.ts.map +0 -1
  125. package/dist/WorktreeManager.js +0 -283
  126. package/dist/WorktreeManager.js.map +0 -1
  127. package/dist/boundedTail.d.ts +0 -48
  128. package/dist/boundedTail.d.ts.map +0 -1
  129. package/dist/boundedTail.js +0 -64
  130. package/dist/boundedTail.js.map +0 -1
  131. package/dist/cli.d.ts +0 -30
  132. package/dist/cli.d.ts.map +0 -1
  133. package/dist/cli.js +0 -340
  134. package/dist/cli.js.map +0 -1
  135. package/dist/createSandbox.d.ts +0 -154
  136. package/dist/createSandbox.d.ts.map +0 -1
  137. package/dist/createSandbox.js +0 -476
  138. package/dist/createSandbox.js.map +0 -1
  139. package/dist/createWorktree.d.ts +0 -154
  140. package/dist/createWorktree.d.ts.map +0 -1
  141. package/dist/createWorktree.js +0 -391
  142. package/dist/createWorktree.js.map +0 -1
  143. package/dist/errors.d.ts +0 -227
  144. package/dist/errors.d.ts.map +0 -1
  145. package/dist/errors.js +0 -81
  146. package/dist/errors.js.map +0 -1
  147. package/dist/extractStructuredOutput.d.ts +0 -23
  148. package/dist/extractStructuredOutput.d.ts.map +0 -1
  149. package/dist/extractStructuredOutput.js +0 -102
  150. package/dist/extractStructuredOutput.js.map +0 -1
  151. package/dist/index.d.ts.map +0 -1
  152. package/dist/interactive.d.ts +0 -74
  153. package/dist/interactive.d.ts.map +0 -1
  154. package/dist/interactive.js +0 -279
  155. package/dist/interactive.js.map +0 -1
  156. package/dist/main.d.ts.map +0 -1
  157. package/dist/mergeProviderEnv.d.ts +0 -13
  158. package/dist/mergeProviderEnv.d.ts.map +0 -1
  159. package/dist/mergeProviderEnv.js +0 -23
  160. package/dist/mergeProviderEnv.js.map +0 -1
  161. package/dist/mountUtils.d.ts +0 -146
  162. package/dist/mountUtils.d.ts.map +0 -1
  163. package/dist/mountUtils.js +0 -301
  164. package/dist/mountUtils.js.map +0 -1
  165. package/dist/raceAbortSignal.d.ts +0 -18
  166. package/dist/raceAbortSignal.d.ts.map +0 -1
  167. package/dist/raceAbortSignal.js +0 -32
  168. package/dist/raceAbortSignal.js.map +0 -1
  169. package/dist/resolveCwd.d.ts +0 -24
  170. package/dist/resolveCwd.d.ts.map +0 -1
  171. package/dist/resolveCwd.js +0 -32
  172. package/dist/resolveCwd.js.map +0 -1
  173. package/dist/resumePrecheck.d.ts +0 -26
  174. package/dist/resumePrecheck.d.ts.map +0 -1
  175. package/dist/resumePrecheck.js +0 -40
  176. package/dist/resumePrecheck.js.map +0 -1
  177. package/dist/run.d.ts +0 -216
  178. package/dist/run.d.ts.map +0 -1
  179. package/dist/run.js +0 -313
  180. package/dist/run.js.map +0 -1
  181. package/dist/sandboxExec.d.ts +0 -12
  182. package/dist/sandboxExec.d.ts.map +0 -1
  183. package/dist/sandboxExec.js +0 -26
  184. package/dist/sandboxExec.js.map +0 -1
  185. package/dist/sandboxes/daytona.d.ts.map +0 -1
  186. package/dist/sandboxes/docker.d.ts.map +0 -1
  187. package/dist/sandboxes/no-sandbox.d.ts.map +0 -1
  188. package/dist/sandboxes/podman.d.ts.map +0 -1
  189. package/dist/sandboxes/test-bind-mount.d.ts +0 -17
  190. package/dist/sandboxes/test-bind-mount.d.ts.map +0 -1
  191. package/dist/sandboxes/test-bind-mount.js +0 -92
  192. package/dist/sandboxes/test-bind-mount.js.map +0 -1
  193. package/dist/sandboxes/test-isolated.d.ts +0 -17
  194. package/dist/sandboxes/test-isolated.d.ts.map +0 -1
  195. package/dist/sandboxes/test-isolated.js +0 -98
  196. package/dist/sandboxes/test-isolated.js.map +0 -1
  197. package/dist/sandboxes/vercel.d.ts.map +0 -1
  198. package/dist/shutdownRegistry.d.ts +0 -30
  199. package/dist/shutdownRegistry.d.ts.map +0 -1
  200. package/dist/shutdownRegistry.js +0 -73
  201. package/dist/shutdownRegistry.js.map +0 -1
  202. package/dist/startSandbox.d.ts +0 -50
  203. package/dist/startSandbox.d.ts.map +0 -1
  204. package/dist/startSandbox.js +0 -117
  205. package/dist/startSandbox.js.map +0 -1
  206. package/dist/syncIn.d.ts +0 -24
  207. package/dist/syncIn.d.ts.map +0 -1
  208. package/dist/syncIn.js +0 -107
  209. package/dist/syncIn.js.map +0 -1
  210. package/dist/syncOut.d.ts +0 -27
  211. package/dist/syncOut.d.ts.map +0 -1
  212. package/dist/syncOut.js +0 -271
  213. package/dist/syncOut.js.map +0 -1
  214. package/dist/templates.d.ts +0 -2
  215. package/dist/templates.d.ts.map +0 -1
  216. package/dist/templates.js +0 -26
  217. package/dist/templates.js.map +0 -1
  218. package/dist/terminalCleanup.d.ts +0 -30
  219. package/dist/terminalCleanup.d.ts.map +0 -1
  220. package/dist/terminalCleanup.js +0 -37
  221. package/dist/terminalCleanup.js.map +0 -1
  222. package/dist/testSandbox.d.ts +0 -8
  223. package/dist/testSandbox.d.ts.map +0 -1
  224. package/dist/testSandbox.js +0 -109
  225. package/dist/testSandbox.js.map +0 -1
  226. package/dist/testSetup.d.ts +0 -2
  227. package/dist/testSetup.d.ts.map +0 -1
  228. package/dist/testSetup.js +0 -29
  229. package/dist/testSetup.js.map +0 -1
@@ -1,647 +0,0 @@
1
- import { codexHostSessionStore, codexSandboxSessionStore, findClaudeSessionOnHost, findCodexSessionOnHost, hostSessionStore, sandboxSessionStore, transferClaudeSession, transferCodexSession, } from "./SessionStore.js";
2
- const shellEscape = (s) => "'" + s.replace(/'/g, "'\\''") + "'";
3
- /** Maps allowlisted tool names to the input field containing the display arg */
4
- const TOOL_ARG_FIELDS = {
5
- Bash: "command",
6
- WebSearch: "query",
7
- WebFetch: "url",
8
- Agent: "description",
9
- };
10
- /**
11
- * Extract an error message from a parsed JSON error event.
12
- * Handles { error: "string" }, { error: { message: "string" } },
13
- * { error: { data: { message: "string" } } }, and { message: "string" }.
14
- */
15
- const extractErrorMessage = (obj) => {
16
- const err = obj.error;
17
- if (typeof err === "string")
18
- return err;
19
- if (typeof err === "object" && err !== null) {
20
- if (typeof err.message === "string")
21
- return err.message;
22
- if (typeof err.data?.message === "string")
23
- return err.data.message;
24
- }
25
- if (typeof obj.message === "string")
26
- return obj.message;
27
- return undefined;
28
- };
29
- const parseStreamJsonLine = (line) => {
30
- if (!line.startsWith("{"))
31
- return [];
32
- try {
33
- const obj = JSON.parse(line);
34
- if (obj.type === "assistant" && Array.isArray(obj.message?.content)) {
35
- const events = [];
36
- const texts = [];
37
- for (const block of obj.message.content) {
38
- if (block.type === "text" && typeof block.text === "string") {
39
- texts.push(block.text);
40
- }
41
- else if (block.type === "tool_use" &&
42
- typeof block.name === "string" &&
43
- block.input !== undefined) {
44
- const argField = TOOL_ARG_FIELDS[block.name];
45
- if (argField === undefined)
46
- continue; // not allowlisted
47
- const argValue = block.input[argField];
48
- if (typeof argValue !== "string")
49
- continue; // missing/wrong arg field
50
- if (texts.length > 0) {
51
- events.push({ type: "text", text: texts.join("") });
52
- texts.length = 0;
53
- }
54
- events.push({
55
- type: "tool_call",
56
- name: block.name,
57
- args: argValue,
58
- });
59
- }
60
- }
61
- if (texts.length > 0) {
62
- events.push({ type: "text", text: texts.join("") });
63
- }
64
- return events;
65
- }
66
- if (obj.type === "result" && typeof obj.result === "string") {
67
- return [{ type: "result", result: obj.result }];
68
- }
69
- if (obj.type === "system" &&
70
- obj.subtype === "init" &&
71
- typeof obj.session_id === "string") {
72
- return [{ type: "session_id", sessionId: obj.session_id }];
73
- }
74
- }
75
- catch {
76
- // Not valid JSON — skip
77
- }
78
- return [];
79
- };
80
- /**
81
- * Cursor Agent CLI print mode passes the prompt as a positional argv argument; stdin is not
82
- * documented for delivering the prompt. Linux enforces a per-argument limit (~128 KiB, ARG_MAX
83
- * stack). Stay slightly under so users get a clear error instead of spawn E2BIG.
84
- */
85
- const CURSOR_PRINT_PROMPT_MAX_BYTES = 120 * 1024;
86
- function assertCursorPrintPromptFitsArgv(prompt) {
87
- const n = Buffer.byteLength(prompt, "utf8");
88
- if (n > CURSOR_PRINT_PROMPT_MAX_BYTES) {
89
- throw new Error(`Cursor print-mode prompt is ${n} bytes (max ${CURSOR_PRINT_PROMPT_MAX_BYTES} bytes). The Cursor CLI accepts the prompt only as a command-line argument; shorten the prompt or split the work. Other Sandcastle providers use stdin for large prompts.`);
90
- }
91
- }
92
- /** Cursor stream-json emits top-level `tool_call` events (see Cursor CLI output-format docs). */
93
- const parseCursorToolCallStarted = (obj) => {
94
- if (obj.type !== "tool_call" || obj.subtype !== "started")
95
- return [];
96
- const toolCall = obj.tool_call;
97
- if (!toolCall || typeof toolCall !== "object")
98
- return [];
99
- const tc = toolCall;
100
- const readToolCall = tc.readToolCall;
101
- if (readToolCall?.args && typeof readToolCall.args.path === "string") {
102
- return [{ type: "tool_call", name: "Read", args: readToolCall.args.path }];
103
- }
104
- const writeToolCall = tc.writeToolCall;
105
- if (writeToolCall?.args && typeof writeToolCall.args.path === "string") {
106
- return [
107
- { type: "tool_call", name: "Write", args: writeToolCall.args.path },
108
- ];
109
- }
110
- const fn = tc.function;
111
- if (fn && typeof fn.name === "string") {
112
- const rawArgs = typeof fn.arguments === "string" ? fn.arguments : "";
113
- if (rawArgs) {
114
- try {
115
- const parsedArgs = JSON.parse(rawArgs);
116
- if (typeof parsedArgs.command === "string") {
117
- return [
118
- { type: "tool_call", name: "Bash", args: parsedArgs.command },
119
- ];
120
- }
121
- }
122
- catch {
123
- // Use raw arguments string for display.
124
- }
125
- return [{ type: "tool_call", name: fn.name, args: rawArgs }];
126
- }
127
- return [{ type: "tool_call", name: fn.name, args: "" }];
128
- }
129
- return [];
130
- };
131
- const parseCursorStreamLine = (line) => {
132
- if (!line.startsWith("{"))
133
- return [];
134
- let obj;
135
- try {
136
- obj = JSON.parse(line);
137
- }
138
- catch {
139
- // Not valid JSON — skip
140
- return [];
141
- }
142
- if (obj.type === "tool_call") {
143
- return parseCursorToolCallStarted(obj);
144
- }
145
- return parseStreamJsonLine(line);
146
- };
147
- export const DEFAULT_MODEL = "claude-opus-4-7";
148
- // ---------------------------------------------------------------------------
149
- // Pi agent provider
150
- // ---------------------------------------------------------------------------
151
- const parsePiStreamLine = (line) => {
152
- if (!line.startsWith("{"))
153
- return [];
154
- try {
155
- const obj = JSON.parse(line);
156
- if (obj.type === "message_update" && obj.assistantMessageEvent) {
157
- const evt = obj.assistantMessageEvent;
158
- if (evt.type === "text_delta" && typeof evt.delta === "string") {
159
- return [{ type: "text", text: evt.delta }];
160
- }
161
- return [];
162
- }
163
- if (obj.type === "tool_execution_start") {
164
- const toolName = obj.toolName;
165
- if (typeof toolName !== "string")
166
- return [];
167
- const argField = TOOL_ARG_FIELDS[toolName];
168
- if (argField === undefined)
169
- return [];
170
- const args = obj.args;
171
- if (!args)
172
- return [];
173
- const argValue = args[argField];
174
- if (typeof argValue !== "string")
175
- return [];
176
- return [{ type: "tool_call", name: toolName, args: argValue }];
177
- }
178
- // Pi emits agent_error / error events on stdout (not stderr) for auth
179
- // failures, rate limits, and API errors. Capture them as result events so
180
- // the Orchestrator's stderr-empty fallback can surface them to the user.
181
- if (obj.type === "agent_error" || obj.type === "error") {
182
- const msg = extractErrorMessage(obj);
183
- return msg ? [{ type: "result", result: msg }] : [];
184
- }
185
- if (obj.type === "agent_end" && Array.isArray(obj.messages)) {
186
- const messages = obj.messages;
187
- for (let i = messages.length - 1; i >= 0; i--) {
188
- const msg = messages[i];
189
- if (msg?.role === "assistant") {
190
- const texts = [];
191
- for (const block of msg.content) {
192
- if (block.type === "text" && typeof block.text === "string") {
193
- texts.push(block.text);
194
- }
195
- }
196
- if (texts.length > 0) {
197
- return [{ type: "result", result: texts.join("") }];
198
- }
199
- break;
200
- }
201
- }
202
- return [];
203
- }
204
- }
205
- catch {
206
- // Not valid JSON — skip
207
- }
208
- return [];
209
- };
210
- export const pi = (model, options) => ({
211
- name: "pi",
212
- env: options?.env ?? {},
213
- captureSessions: false,
214
- buildPrintCommand({ prompt }) {
215
- return {
216
- command: `pi -p --mode json --no-session --model ${shellEscape(model)}`,
217
- stdin: prompt,
218
- };
219
- },
220
- buildInteractiveArgs({ prompt }) {
221
- const args = ["pi", "--model", model];
222
- if (prompt)
223
- args.push(prompt);
224
- return args;
225
- },
226
- parseStreamLine(line) {
227
- return parsePiStreamLine(line);
228
- },
229
- });
230
- // ---------------------------------------------------------------------------
231
- // Codex agent provider
232
- // ---------------------------------------------------------------------------
233
- /**
234
- * Map a Codex `turn.completed` usage object to the Claude-shaped IterationUsage.
235
- *
236
- * OpenAI/Codex usage is `{ input_tokens, cached_input_tokens, output_tokens }`,
237
- * where `input_tokens` is the *total* prompt tokens and `cached_input_tokens` is
238
- * a subset already included in that total. There is no cache-creation concept.
239
- * To avoid double-counting cached tokens in the context-window display (which
240
- * sums input + cacheCreation + cacheRead), the cached portion maps to
241
- * `cacheReadInputTokens` and the remainder to `inputTokens`.
242
- */
243
- const parseCodexUsage = (usage) => {
244
- if (typeof usage !== "object" || usage === null)
245
- return undefined;
246
- const u = usage;
247
- if (typeof u.input_tokens !== "number" ||
248
- typeof u.cached_input_tokens !== "number" ||
249
- typeof u.output_tokens !== "number") {
250
- return undefined;
251
- }
252
- return {
253
- inputTokens: u.input_tokens - u.cached_input_tokens,
254
- cacheCreationInputTokens: 0,
255
- cacheReadInputTokens: u.cached_input_tokens,
256
- outputTokens: u.output_tokens,
257
- };
258
- };
259
- const parseCodexStreamLine = (line) => {
260
- if (!line.startsWith("{"))
261
- return [];
262
- try {
263
- const obj = JSON.parse(line);
264
- if (obj.type === "thread.started" && typeof obj.thread_id === "string") {
265
- return [{ type: "session_id", sessionId: obj.thread_id }];
266
- }
267
- // item.completed with agent_message → text + result
268
- if (obj.type === "item.completed" &&
269
- obj.item?.type === "agent_message" &&
270
- typeof obj.item.text === "string") {
271
- const text = obj.item.text;
272
- return [
273
- { type: "text", text },
274
- { type: "result", result: text },
275
- ];
276
- }
277
- // item.started with command_execution → tool call
278
- if (obj.type === "item.started" &&
279
- obj.item?.type === "command_execution" &&
280
- typeof obj.item.command === "string") {
281
- return [{ type: "tool_call", name: "Bash", args: obj.item.command }];
282
- }
283
- // Codex emits error events on stdout (not stderr) for auth failures,
284
- // rate limits, and API errors. Capture them as result events so the
285
- // Orchestrator's stderr-empty fallback can surface them to the user.
286
- if (obj.type === "error") {
287
- const msg = extractErrorMessage(obj);
288
- return msg ? [{ type: "result", result: msg }] : [];
289
- }
290
- // turn.completed carries token usage for the turn.
291
- if (obj.type === "turn.completed") {
292
- const usage = parseCodexUsage(obj.usage);
293
- return usage ? [{ type: "usage", usage }] : [];
294
- }
295
- }
296
- catch {
297
- // Not valid JSON — skip
298
- }
299
- return [];
300
- };
301
- export const codex = (model, options) => ({
302
- name: "codex",
303
- env: options?.env ?? {},
304
- captureSessions: options?.captureSessions ?? true,
305
- sessionStorage: {
306
- hostStore: (cwd) => codexHostSessionStore(cwd, options?.sessionStorage?.hostSessionsDir),
307
- sandboxStore: (cwd, handle) => codexSandboxSessionStore(cwd, handle, options?.sessionStorage?.sandboxSessionsDir),
308
- // Both stores above are LocatableSessionStore by construction; the
309
- // AgentSessionStorage seam types them as the narrower SessionStore.
310
- transfer: (from, to, id) => transferCodexSession(from, to, id),
311
- findByIdOnHost: (id) => findCodexSessionOnHost(id, options?.sessionStorage?.hostSessionsDir),
312
- },
313
- buildPrintCommand({ prompt, resumeSession, }) {
314
- const effortFlag = options?.effort
315
- ? ` -c ${shellEscape(`model_reasoning_effort="${options.effort}"`)}`
316
- : "";
317
- const base = resumeSession
318
- ? `codex exec resume ${shellEscape(resumeSession)}`
319
- : "codex exec";
320
- const stdinArg = resumeSession ? " -" : "";
321
- return {
322
- command: `${base} --json --dangerously-bypass-approvals-and-sandbox -m ${shellEscape(model)}${effortFlag}${stdinArg}`,
323
- stdin: prompt,
324
- };
325
- },
326
- buildInteractiveArgs({ prompt }) {
327
- const args = ["codex", "--model", model];
328
- if (prompt)
329
- args.push(prompt);
330
- return args;
331
- },
332
- parseStreamLine(line) {
333
- return parseCodexStreamLine(line);
334
- },
335
- });
336
- export const cursor = (model, options) => ({
337
- name: "cursor",
338
- env: options?.env ?? {},
339
- captureSessions: false,
340
- // Cursor has no filesystem-backed session storage (captureSessions: false, no
341
- // sessionStorage), so it is non-resumable per ADR 0012/0016. resumeSession is
342
- // ignored here — like pi and opencode — rather than wired to --resume.
343
- buildPrintCommand({ prompt, dangerouslySkipPermissions, }) {
344
- assertCursorPrintPromptFitsArgv(prompt);
345
- const forceFlag = dangerouslySkipPermissions ? " --force" : "";
346
- return {
347
- command: `agent --print --output-format stream-json --model ${shellEscape(model)} ${forceFlag} ${shellEscape(prompt)}`,
348
- };
349
- },
350
- buildInteractiveArgs({ prompt, dangerouslySkipPermissions, }) {
351
- const args = ["agent", "--model", model];
352
- if (dangerouslySkipPermissions)
353
- args.push("--force");
354
- if (prompt)
355
- args.push(prompt);
356
- return args;
357
- },
358
- parseStreamLine(line) {
359
- return parseCursorStreamLine(line);
360
- },
361
- });
362
- // ---------------------------------------------------------------------------
363
- // OpenCode agent provider
364
- // ---------------------------------------------------------------------------
365
- /** Maps OpenCode tool names to the input field containing the friendly display
366
- * arg. Tools not listed here are still surfaced, falling back to a JSON dump of
367
- * the whole input. The tool name is surfaced as-is (OpenCode's lowercase names). */
368
- const OPENCODE_TOOL_ARG_FIELDS = {
369
- bash: "command",
370
- webfetch: "url",
371
- task: "description",
372
- };
373
- const parseOpenCodeStreamLine = (line) => {
374
- if (!line.startsWith("{"))
375
- return [];
376
- try {
377
- const obj = JSON.parse(line);
378
- const part = obj.part;
379
- // step_start carries the session ID for the run.
380
- if (obj.type === "step_start" && typeof obj.sessionID === "string") {
381
- return [{ type: "session_id", sessionId: obj.sessionID }];
382
- }
383
- // text event → assistant text. Emit both text (for streaming display) and
384
- // result (final message; the last result wins in the Orchestrator).
385
- if (obj.type === "text" &&
386
- part?.type === "text" &&
387
- typeof part.text === "string") {
388
- return [
389
- { type: "text", text: part.text },
390
- { type: "result", result: part.text },
391
- ];
392
- }
393
- // tool_use event → tool call. Tool name is in part.tool, args in
394
- // part.state.input. Gate on the completed status so intermediate
395
- // pending/running states don't surface duplicate tool calls.
396
- if (obj.type === "tool_use" && part?.type === "tool") {
397
- if (typeof part.tool !== "string")
398
- return [];
399
- const state = part.state;
400
- if (state?.status !== "completed")
401
- return [];
402
- const input = state.input;
403
- if (!input)
404
- return [];
405
- const argField = OPENCODE_TOOL_ARG_FIELDS[part.tool];
406
- const argValue = argField !== undefined ? input[argField] : undefined;
407
- const args = typeof argValue === "string" ? argValue : JSON.stringify(input);
408
- return [{ type: "tool_call", name: part.tool, args }];
409
- }
410
- // OpenCode emits error events on stdout (not stderr) for auth failures,
411
- // rate limits, and API errors. Capture them as result events so the
412
- // Orchestrator's stderr-empty fallback can surface them to the user.
413
- if (obj.type === "error") {
414
- const msg = extractErrorMessage(obj);
415
- return msg ? [{ type: "result", result: msg }] : [];
416
- }
417
- // step_finish, tool output, etc. → skip
418
- }
419
- catch {
420
- // Not valid JSON — skip
421
- }
422
- return [];
423
- };
424
- export const opencode = (model, options) => ({
425
- name: "opencode",
426
- env: options?.env ?? {},
427
- captureSessions: false,
428
- buildPrintCommand({ prompt, dangerouslySkipPermissions, }) {
429
- const variantFlag = options?.variant
430
- ? ` --variant ${shellEscape(options.variant)}`
431
- : "";
432
- const agentFlag = options?.agent
433
- ? ` --agent ${shellEscape(options.agent)}`
434
- : "";
435
- const permissionsFlag = dangerouslySkipPermissions
436
- ? " --dangerously-skip-permissions"
437
- : "";
438
- return {
439
- command: `opencode run --format json --model ${shellEscape(model)}${variantFlag}${agentFlag}${permissionsFlag} ${shellEscape(prompt)}`,
440
- };
441
- },
442
- buildInteractiveArgs({ prompt }) {
443
- const args = ["opencode", "--model", model];
444
- if (options?.agent)
445
- args.push("--agent", options.agent);
446
- if (prompt)
447
- args.push("-p", prompt);
448
- return args;
449
- },
450
- parseStreamLine(line) {
451
- return parseOpenCodeStreamLine(line);
452
- },
453
- });
454
- // ---------------------------------------------------------------------------
455
- // GitHub Copilot CLI agent provider
456
- // ---------------------------------------------------------------------------
457
- /**
458
- * Copilot CLI print mode passes the prompt as the `-p` argv argument. (The CLI
459
- * can also read a prompt piped on stdin — `echo "..." | copilot` — but we use
460
- * the `-p` argv form here for parity with the tested print-command path.) Linux
461
- * enforces a per-argument limit (~128 KiB, ARG_MAX stack). Stay slightly under
462
- * so users get a clear error instead of spawn E2BIG. Mirrors the Cursor guard.
463
- */
464
- const COPILOT_PRINT_PROMPT_MAX_BYTES = 120 * 1024;
465
- function assertCopilotPrintPromptFitsArgv(prompt) {
466
- const n = Buffer.byteLength(prompt, "utf8");
467
- if (n > COPILOT_PRINT_PROMPT_MAX_BYTES) {
468
- throw new Error(`Copilot print-mode prompt is ${n} bytes (max ${COPILOT_PRINT_PROMPT_MAX_BYTES} bytes). This provider passes the prompt as a command-line argument; shorten the prompt or split the work. Other Sandcastle providers use stdin for large prompts.`);
469
- }
470
- }
471
- /**
472
- * Parse one line of `copilot --output-format json` JSONL output.
473
- *
474
- * Schema (observed via `copilot -p ... --output-format json --model ...`):
475
- *
476
- * - `assistant.message_delta` — `{ data: { messageId, deltaContent } }`
477
- * Streaming chunks of assistant text. Mapped to `text` events.
478
- *
479
- * - `assistant.message` — `{ data: { messageId, content, toolRequests, ... } }`
480
- * The complete assistant message. We surface its `content` as a `result`
481
- * event so the Orchestrator's "last result wins" buffer ends up holding
482
- * the final assistant text. (Tool calls in `toolRequests` are surfaced
483
- * separately via `tool.execution_start` events.)
484
- *
485
- * - `tool.execution_start` — `{ data: { toolCallId, toolName, arguments } }`
486
- * Mapped to `tool_call` events for allowlisted tools. Copilot uses lowercase
487
- * `bash`; we normalise to the existing `Bash` allowlist entry.
488
- *
489
- * - `result` — `{ sessionId, exitCode, usage }`
490
- * Terminal event. We surface `sessionId` as a `session_id` event.
491
- *
492
- * - `error` / `agent_error` — defensive: surface as a `result` event the same
493
- * way Pi/Codex do, so the Orchestrator's stderr-empty fallback can show it.
494
- */
495
- const parseCopilotStreamLine = (line) => {
496
- if (!line.startsWith("{"))
497
- return [];
498
- try {
499
- const obj = JSON.parse(line);
500
- // Streaming text deltas
501
- if (obj.type === "assistant.message_delta" &&
502
- typeof obj.data?.deltaContent === "string") {
503
- return [{ type: "text", text: obj.data.deltaContent }];
504
- }
505
- // Tool execution start → tool_call (allowlisted tools only)
506
- if (obj.type === "tool.execution_start") {
507
- const rawName = obj.data?.toolName;
508
- if (typeof rawName !== "string")
509
- return [];
510
- // Copilot CLI uses lowercase "bash"; normalise to the shared allowlist.
511
- const toolName = rawName === "bash" ? "Bash" : rawName;
512
- const argField = TOOL_ARG_FIELDS[toolName];
513
- if (argField === undefined)
514
- return [];
515
- const args = obj.data?.arguments;
516
- if (!args)
517
- return [];
518
- const argValue = args[argField];
519
- if (typeof argValue !== "string")
520
- return [];
521
- return [{ type: "tool_call", name: toolName, args: argValue }];
522
- }
523
- // Final assistant message → result. Each assistant turn emits one of
524
- // these with the complete text; the Orchestrator's resultText is
525
- // last-write-wins, so the final turn ends up surfaced to callers.
526
- if (obj.type === "assistant.message" &&
527
- typeof obj.data?.content === "string" &&
528
- obj.data.content.length > 0) {
529
- return [{ type: "result", result: obj.data.content }];
530
- }
531
- // Terminal result event carries the session id
532
- if (obj.type === "result" && typeof obj.sessionId === "string") {
533
- return [{ type: "session_id", sessionId: obj.sessionId }];
534
- }
535
- // Defensive: surface error events as result events (matches Pi/Codex)
536
- if (obj.type === "error" || obj.type === "agent_error") {
537
- const msg = extractErrorMessage(obj);
538
- return msg ? [{ type: "result", result: msg }] : [];
539
- }
540
- }
541
- catch {
542
- // Not valid JSON — skip
543
- }
544
- return [];
545
- };
546
- export const copilot = (model, options) => ({
547
- name: "copilot",
548
- env: options?.env ?? {},
549
- captureSessions: false,
550
- // Copilot CLI does expose `--resume <id>`, but its session state is indexed by
551
- // a SQLite database alongside the JSONL files in ~/.copilot/session-state/, so
552
- // transferring a single session file between host and sandbox is not enough to
553
- // make resume work (see ADR 0016). Until the round-trip is verified end-to-end,
554
- // copilot is non-resumable: captureSessions is false, there is no sessionStorage,
555
- // and resumeSession is ignored here — like cursor, pi, and opencode.
556
- buildPrintCommand({ prompt, dangerouslySkipPermissions, }) {
557
- assertCopilotPrintPromptFitsArgv(prompt);
558
- const allowAll = dangerouslySkipPermissions ? " --allow-all-tools" : "";
559
- const effortFlag = options?.effort ? ` --effort ${options.effort}` : "";
560
- return {
561
- command: `copilot -p ${shellEscape(prompt)} --output-format json --model ${shellEscape(model)}${allowAll}${effortFlag}`,
562
- };
563
- },
564
- buildInteractiveArgs({ prompt }) {
565
- const args = ["copilot", "--model", model];
566
- // Seed the interactive session with `-i`/`--interactive`, NOT `-p`. The
567
- // `-p`/`--prompt` flag runs the prompt programmatically and exits after
568
- // completion; since interactive() attaches these args to the real TTY,
569
- // `-p` would print-and-exit instead of launching the TUI. `-i` starts an
570
- // interactive session and auto-executes the prompt without exiting.
571
- if (prompt)
572
- args.push("-i", prompt);
573
- return args;
574
- },
575
- parseStreamLine(line) {
576
- return parseCopilotStreamLine(line);
577
- },
578
- });
579
- export const claudeCode = (model, options) => ({
580
- name: "claude-code",
581
- env: options?.env ?? {},
582
- captureSessions: options?.captureSessions ?? true,
583
- sessionStorage: {
584
- hostStore: (cwd) => hostSessionStore(cwd, options?.sessionStorage?.hostProjectsDir),
585
- sandboxStore: (cwd, handle) => sandboxSessionStore(cwd, handle, options?.sessionStorage?.sandboxProjectsDir ??
586
- "/home/agent/.claude/projects"),
587
- transfer: transferClaudeSession,
588
- findByIdOnHost: (id) => findClaudeSessionOnHost(id, options?.sessionStorage?.hostProjectsDir),
589
- },
590
- buildPrintCommand({ prompt, dangerouslySkipPermissions, resumeSession, }) {
591
- const skipPerms = dangerouslySkipPermissions
592
- ? " --dangerously-skip-permissions"
593
- : "";
594
- const effortFlag = options?.effort ? ` --effort ${options.effort}` : "";
595
- const resumeFlag = resumeSession
596
- ? ` --resume ${shellEscape(resumeSession)}`
597
- : "";
598
- return {
599
- command: `claude --print --verbose${skipPerms} --output-format stream-json --model ${shellEscape(model)}${effortFlag}${resumeFlag} -p -`,
600
- stdin: prompt,
601
- };
602
- },
603
- buildInteractiveArgs({ prompt, dangerouslySkipPermissions, }) {
604
- const args = ["claude"];
605
- if (dangerouslySkipPermissions)
606
- args.push("--dangerously-skip-permissions");
607
- args.push("--model", model);
608
- if (options?.effort)
609
- args.push("--effort", options.effort);
610
- if (prompt)
611
- args.push(prompt);
612
- return args;
613
- },
614
- parseStreamLine(line) {
615
- return parseStreamJsonLine(line);
616
- },
617
- parseSessionUsage(content) {
618
- const lines = content.split("\n");
619
- for (let i = lines.length - 1; i >= 0; i--) {
620
- const line = lines[i];
621
- if (!line.startsWith("{"))
622
- continue;
623
- try {
624
- const obj = JSON.parse(line);
625
- if (obj.type === "assistant" && obj.message?.usage) {
626
- const u = obj.message.usage;
627
- if (typeof u.input_tokens === "number" &&
628
- typeof u.cache_creation_input_tokens === "number" &&
629
- typeof u.cache_read_input_tokens === "number" &&
630
- typeof u.output_tokens === "number") {
631
- return {
632
- inputTokens: u.input_tokens,
633
- cacheCreationInputTokens: u.cache_creation_input_tokens,
634
- cacheReadInputTokens: u.cache_read_input_tokens,
635
- outputTokens: u.output_tokens,
636
- };
637
- }
638
- }
639
- }
640
- catch {
641
- // Not valid JSON — skip
642
- }
643
- }
644
- return undefined;
645
- },
646
- });
647
- //# sourceMappingURL=AgentProvider.js.map