@posthog/agent 1.30.0 → 2.0.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 (144) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +221 -219
  3. package/dist/adapters/claude/conversion/tool-use-to-acp.d.ts +21 -0
  4. package/dist/adapters/claude/conversion/tool-use-to-acp.js +547 -0
  5. package/dist/adapters/claude/conversion/tool-use-to-acp.js.map +1 -0
  6. package/dist/adapters/claude/permissions/permission-options.d.ts +13 -0
  7. package/dist/adapters/claude/permissions/permission-options.js +117 -0
  8. package/dist/adapters/claude/permissions/permission-options.js.map +1 -0
  9. package/dist/adapters/claude/questions/utils.d.ts +132 -0
  10. package/dist/adapters/claude/questions/utils.js +63 -0
  11. package/dist/adapters/claude/questions/utils.js.map +1 -0
  12. package/dist/adapters/claude/tools.d.ts +18 -0
  13. package/dist/adapters/claude/tools.js +95 -0
  14. package/dist/adapters/claude/tools.js.map +1 -0
  15. package/dist/agent-DBQY1BfC.d.ts +123 -0
  16. package/dist/agent.d.ts +5 -0
  17. package/dist/agent.js +3656 -0
  18. package/dist/agent.js.map +1 -0
  19. package/dist/claude-cli/cli.js +3695 -2746
  20. package/dist/claude-cli/vendor/ripgrep/COPYING +3 -0
  21. package/dist/claude-cli/vendor/ripgrep/arm64-darwin/rg +0 -0
  22. package/dist/claude-cli/vendor/ripgrep/arm64-darwin/ripgrep.node +0 -0
  23. package/dist/claude-cli/vendor/ripgrep/arm64-linux/rg +0 -0
  24. package/dist/claude-cli/vendor/ripgrep/arm64-linux/ripgrep.node +0 -0
  25. package/dist/claude-cli/vendor/ripgrep/x64-darwin/rg +0 -0
  26. package/dist/claude-cli/vendor/ripgrep/x64-darwin/ripgrep.node +0 -0
  27. package/dist/claude-cli/vendor/ripgrep/x64-linux/rg +0 -0
  28. package/dist/claude-cli/vendor/ripgrep/x64-linux/ripgrep.node +0 -0
  29. package/dist/claude-cli/vendor/ripgrep/x64-win32/rg.exe +0 -0
  30. package/dist/claude-cli/vendor/ripgrep/x64-win32/ripgrep.node +0 -0
  31. package/dist/gateway-models.d.ts +24 -0
  32. package/dist/gateway-models.js +93 -0
  33. package/dist/gateway-models.js.map +1 -0
  34. package/dist/index.d.ts +172 -1203
  35. package/dist/index.js +3704 -6826
  36. package/dist/index.js.map +1 -1
  37. package/dist/logger-DDBiMOOD.d.ts +24 -0
  38. package/dist/posthog-api.d.ts +40 -0
  39. package/dist/posthog-api.js +175 -0
  40. package/dist/posthog-api.js.map +1 -0
  41. package/dist/server/agent-server.d.ts +41 -0
  42. package/dist/server/agent-server.js +4451 -0
  43. package/dist/server/agent-server.js.map +1 -0
  44. package/dist/server/bin.d.ts +1 -0
  45. package/dist/server/bin.js +4507 -0
  46. package/dist/server/bin.js.map +1 -0
  47. package/dist/types.d.ts +129 -0
  48. package/dist/types.js +1 -0
  49. package/dist/types.js.map +1 -0
  50. package/package.json +66 -14
  51. package/src/acp-extensions.ts +93 -61
  52. package/src/adapters/acp-connection.ts +494 -0
  53. package/src/adapters/base-acp-agent.ts +150 -0
  54. package/src/adapters/claude/claude-agent.ts +596 -0
  55. package/src/adapters/claude/conversion/acp-to-sdk.ts +102 -0
  56. package/src/adapters/claude/conversion/sdk-to-acp.ts +571 -0
  57. package/src/adapters/claude/conversion/tool-use-to-acp.ts +618 -0
  58. package/src/adapters/claude/hooks.ts +64 -0
  59. package/src/adapters/claude/mcp/tool-metadata.ts +102 -0
  60. package/src/adapters/claude/permissions/permission-handlers.ts +433 -0
  61. package/src/adapters/claude/permissions/permission-options.ts +103 -0
  62. package/src/adapters/claude/plan/utils.ts +56 -0
  63. package/src/adapters/claude/questions/utils.ts +92 -0
  64. package/src/adapters/claude/session/commands.ts +38 -0
  65. package/src/adapters/claude/session/mcp-config.ts +37 -0
  66. package/src/adapters/claude/session/models.ts +12 -0
  67. package/src/adapters/claude/session/options.ts +236 -0
  68. package/src/adapters/claude/tool-meta.ts +143 -0
  69. package/src/adapters/claude/tools.ts +53 -611
  70. package/src/adapters/claude/types.ts +61 -0
  71. package/src/adapters/codex/spawn.ts +130 -0
  72. package/src/agent.ts +97 -734
  73. package/src/execution-mode.ts +43 -0
  74. package/src/gateway-models.ts +135 -0
  75. package/src/index.ts +79 -0
  76. package/src/otel-log-writer.test.ts +105 -0
  77. package/src/otel-log-writer.ts +94 -0
  78. package/src/posthog-api.ts +75 -235
  79. package/src/resume.ts +115 -0
  80. package/src/sagas/apply-snapshot-saga.test.ts +690 -0
  81. package/src/sagas/apply-snapshot-saga.ts +88 -0
  82. package/src/sagas/capture-tree-saga.test.ts +892 -0
  83. package/src/sagas/capture-tree-saga.ts +141 -0
  84. package/src/sagas/resume-saga.test.ts +558 -0
  85. package/src/sagas/resume-saga.ts +332 -0
  86. package/src/sagas/test-fixtures.ts +250 -0
  87. package/src/server/agent-server.test.ts +220 -0
  88. package/src/server/agent-server.ts +748 -0
  89. package/src/server/bin.ts +88 -0
  90. package/src/server/jwt.ts +65 -0
  91. package/src/server/schemas.ts +47 -0
  92. package/src/server/types.ts +13 -0
  93. package/src/server/utils/retry.test.ts +122 -0
  94. package/src/server/utils/retry.ts +61 -0
  95. package/src/server/utils/sse-parser.test.ts +93 -0
  96. package/src/server/utils/sse-parser.ts +46 -0
  97. package/src/session-log-writer.test.ts +140 -0
  98. package/src/session-log-writer.ts +137 -0
  99. package/src/test/assertions.ts +114 -0
  100. package/src/test/controllers/sse-controller.ts +107 -0
  101. package/src/test/fixtures/api.ts +111 -0
  102. package/src/test/fixtures/config.ts +33 -0
  103. package/src/test/fixtures/notifications.ts +92 -0
  104. package/src/test/mocks/claude-sdk.ts +251 -0
  105. package/src/test/mocks/msw-handlers.ts +48 -0
  106. package/src/test/setup.ts +114 -0
  107. package/src/test/wait.ts +41 -0
  108. package/src/tree-tracker.ts +173 -0
  109. package/src/types.ts +51 -154
  110. package/src/utils/acp-content.ts +58 -0
  111. package/src/utils/async-mutex.test.ts +104 -0
  112. package/src/utils/async-mutex.ts +31 -0
  113. package/src/utils/common.ts +15 -0
  114. package/src/utils/gateway.ts +9 -6
  115. package/src/utils/logger.ts +0 -30
  116. package/src/utils/streams.ts +220 -0
  117. package/CLAUDE.md +0 -331
  118. package/dist/templates/plan-template.md +0 -41
  119. package/src/adapters/claude/claude.ts +0 -1543
  120. package/src/adapters/claude/mcp-server.ts +0 -810
  121. package/src/adapters/claude/utils.ts +0 -267
  122. package/src/agents/execution.ts +0 -37
  123. package/src/agents/planning.ts +0 -60
  124. package/src/agents/research.ts +0 -160
  125. package/src/file-manager.ts +0 -306
  126. package/src/git-manager.ts +0 -577
  127. package/src/prompt-builder.ts +0 -499
  128. package/src/schemas.ts +0 -241
  129. package/src/session-store.ts +0 -259
  130. package/src/task-manager.ts +0 -163
  131. package/src/template-manager.ts +0 -236
  132. package/src/templates/plan-template.md +0 -41
  133. package/src/todo-manager.ts +0 -180
  134. package/src/tools/registry.ts +0 -129
  135. package/src/tools/types.ts +0 -127
  136. package/src/utils/tapped-stream.ts +0 -60
  137. package/src/workflow/config.ts +0 -53
  138. package/src/workflow/steps/build.ts +0 -135
  139. package/src/workflow/steps/finalize.ts +0 -241
  140. package/src/workflow/steps/plan.ts +0 -167
  141. package/src/workflow/steps/research.ts +0 -223
  142. package/src/workflow/types.ts +0 -62
  143. package/src/workflow/utils.ts +0 -53
  144. package/src/worktree-manager.ts +0 -928
package/dist/agent.js ADDED
@@ -0,0 +1,3656 @@
1
+ // src/adapters/acp-connection.ts
2
+ import { AgentSideConnection, ndJsonStream } from "@agentclientprotocol/sdk";
3
+
4
+ // src/acp-extensions.ts
5
+ var POSTHOG_NOTIFICATIONS = {
6
+ /** Git branch was created for a task */
7
+ BRANCH_CREATED: "_posthog/branch_created",
8
+ /** Task run has started execution */
9
+ RUN_STARTED: "_posthog/run_started",
10
+ /** Task has completed (success or failure) */
11
+ TASK_COMPLETE: "_posthog/task_complete",
12
+ /** Error occurred during task execution */
13
+ ERROR: "_posthog/error",
14
+ /** Console/log output from the agent */
15
+ CONSOLE: "_posthog/console",
16
+ /** Maps taskRunId to agent's sessionId and adapter type (for resumption) */
17
+ SDK_SESSION: "_posthog/sdk_session",
18
+ /** Tree state snapshot captured (git tree hash + file archive) */
19
+ TREE_SNAPSHOT: "_posthog/tree_snapshot",
20
+ /** Agent mode changed (interactive/background) */
21
+ MODE_CHANGE: "_posthog/mode_change",
22
+ /** Request to resume a session from previous state */
23
+ SESSION_RESUME: "_posthog/session/resume",
24
+ /** User message sent from client to agent */
25
+ USER_MESSAGE: "_posthog/user_message",
26
+ /** Request to cancel current operation */
27
+ CANCEL: "_posthog/cancel",
28
+ /** Request to close the session */
29
+ CLOSE: "_posthog/close",
30
+ /** Agent status update (thinking, working, etc.) */
31
+ STATUS: "_posthog/status",
32
+ /** Task-level notification (progress, milestones) */
33
+ TASK_NOTIFICATION: "_posthog/task_notification",
34
+ /** Marks a boundary for log compaction */
35
+ COMPACT_BOUNDARY: "_posthog/compact_boundary"
36
+ };
37
+
38
+ // src/utils/logger.ts
39
+ var Logger = class _Logger {
40
+ debugEnabled;
41
+ prefix;
42
+ scope;
43
+ onLog;
44
+ constructor(config = {}) {
45
+ this.debugEnabled = config.debug ?? false;
46
+ this.prefix = config.prefix ?? "[PostHog Agent]";
47
+ this.scope = config.scope ?? "agent";
48
+ this.onLog = config.onLog;
49
+ }
50
+ formatMessage(level, message, data) {
51
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
52
+ const base = `${timestamp} ${this.prefix} [${level}] ${message}`;
53
+ if (data !== void 0) {
54
+ return `${base} ${JSON.stringify(data, null, 2)}`;
55
+ }
56
+ return base;
57
+ }
58
+ emitLog(level, message, data) {
59
+ if (this.onLog) {
60
+ this.onLog(level, this.scope, message, data);
61
+ return;
62
+ }
63
+ const shouldLog = this.debugEnabled || level === "error";
64
+ if (shouldLog) {
65
+ console[level](this.formatMessage(level.toLowerCase(), message, data));
66
+ }
67
+ }
68
+ error(message, error) {
69
+ const data = error instanceof Error ? { message: error.message, stack: error.stack } : error;
70
+ this.emitLog("error", message, data);
71
+ }
72
+ warn(message, data) {
73
+ this.emitLog("warn", message, data);
74
+ }
75
+ info(message, data) {
76
+ this.emitLog("info", message, data);
77
+ }
78
+ debug(message, data) {
79
+ this.emitLog("debug", message, data);
80
+ }
81
+ child(childPrefix) {
82
+ return new _Logger({
83
+ debug: this.debugEnabled,
84
+ prefix: `${this.prefix} [${childPrefix}]`,
85
+ scope: `${this.scope}:${childPrefix}`,
86
+ onLog: this.onLog
87
+ });
88
+ }
89
+ };
90
+
91
+ // src/utils/streams.ts
92
+ import { ReadableStream, WritableStream as WritableStream2 } from "stream/web";
93
+ var Pushable = class {
94
+ queue = [];
95
+ resolvers = [];
96
+ done = false;
97
+ push(item) {
98
+ const resolve3 = this.resolvers.shift();
99
+ if (resolve3) {
100
+ resolve3({ value: item, done: false });
101
+ } else {
102
+ this.queue.push(item);
103
+ }
104
+ }
105
+ end() {
106
+ this.done = true;
107
+ for (const resolve3 of this.resolvers) {
108
+ resolve3({ value: void 0, done: true });
109
+ }
110
+ this.resolvers = [];
111
+ }
112
+ [Symbol.asyncIterator]() {
113
+ return {
114
+ next: () => {
115
+ if (this.queue.length > 0) {
116
+ const value = this.queue.shift();
117
+ return Promise.resolve({ value, done: false });
118
+ }
119
+ if (this.done) {
120
+ return Promise.resolve({
121
+ value: void 0,
122
+ done: true
123
+ });
124
+ }
125
+ return new Promise((resolve3) => {
126
+ this.resolvers.push(resolve3);
127
+ });
128
+ }
129
+ };
130
+ }
131
+ };
132
+ function pushableToReadableStream(pushable) {
133
+ const iterator = pushable[Symbol.asyncIterator]();
134
+ return new ReadableStream({
135
+ async pull(controller) {
136
+ const { value, done } = await iterator.next();
137
+ if (done) {
138
+ controller.close();
139
+ } else {
140
+ controller.enqueue(value);
141
+ }
142
+ }
143
+ });
144
+ }
145
+ function createBidirectionalStreams() {
146
+ const clientToAgentPushable = new Pushable();
147
+ const agentToClientPushable = new Pushable();
148
+ const clientToAgentReadable = pushableToReadableStream(clientToAgentPushable);
149
+ const agentToClientReadable = pushableToReadableStream(agentToClientPushable);
150
+ const clientToAgentWritable = new WritableStream2({
151
+ write(chunk) {
152
+ clientToAgentPushable.push(chunk);
153
+ },
154
+ close() {
155
+ clientToAgentPushable.end();
156
+ }
157
+ });
158
+ const agentToClientWritable = new WritableStream2({
159
+ write(chunk) {
160
+ agentToClientPushable.push(chunk);
161
+ },
162
+ close() {
163
+ agentToClientPushable.end();
164
+ }
165
+ });
166
+ return {
167
+ client: {
168
+ readable: agentToClientReadable,
169
+ writable: clientToAgentWritable
170
+ },
171
+ agent: {
172
+ readable: clientToAgentReadable,
173
+ writable: agentToClientWritable
174
+ }
175
+ };
176
+ }
177
+ function createTappedWritableStream(underlying, options) {
178
+ const { onMessage, logger } = options;
179
+ const decoder = new TextDecoder();
180
+ let buffer = "";
181
+ let _messageCount = 0;
182
+ return new WritableStream2({
183
+ async write(chunk) {
184
+ buffer += decoder.decode(chunk, { stream: true });
185
+ const lines = buffer.split("\n");
186
+ buffer = lines.pop() ?? "";
187
+ for (const line of lines) {
188
+ if (!line.trim()) continue;
189
+ _messageCount++;
190
+ onMessage(line);
191
+ }
192
+ try {
193
+ const writer = underlying.getWriter();
194
+ await writer.write(chunk);
195
+ writer.releaseLock();
196
+ } catch (err) {
197
+ logger?.error("ACP write error", err);
198
+ }
199
+ },
200
+ async close() {
201
+ try {
202
+ const writer = underlying.getWriter();
203
+ await writer.close();
204
+ writer.releaseLock();
205
+ } catch {
206
+ }
207
+ },
208
+ async abort(reason) {
209
+ logger?.warn("Tapped stream aborted", { reason });
210
+ try {
211
+ const writer = underlying.getWriter();
212
+ await writer.abort(reason);
213
+ writer.releaseLock();
214
+ } catch {
215
+ }
216
+ }
217
+ });
218
+ }
219
+ function nodeReadableToWebReadable(nodeStream) {
220
+ return new ReadableStream({
221
+ start(controller) {
222
+ nodeStream.on("data", (chunk) => {
223
+ controller.enqueue(new Uint8Array(chunk));
224
+ });
225
+ nodeStream.on("end", () => {
226
+ controller.close();
227
+ });
228
+ nodeStream.on("error", (err) => {
229
+ controller.error(err);
230
+ });
231
+ },
232
+ cancel() {
233
+ nodeStream.destroy();
234
+ }
235
+ });
236
+ }
237
+ function nodeWritableToWebWritable(nodeStream) {
238
+ return new WritableStream2({
239
+ write(chunk) {
240
+ return new Promise((resolve3, reject) => {
241
+ const ok = nodeStream.write(Buffer.from(chunk), (err) => {
242
+ if (err) reject(err);
243
+ });
244
+ if (ok) {
245
+ resolve3();
246
+ } else {
247
+ nodeStream.once("drain", resolve3);
248
+ }
249
+ });
250
+ },
251
+ close() {
252
+ return new Promise((resolve3) => {
253
+ nodeStream.end(resolve3);
254
+ });
255
+ },
256
+ abort(reason) {
257
+ nodeStream.destroy(
258
+ reason instanceof Error ? reason : new Error(String(reason))
259
+ );
260
+ }
261
+ });
262
+ }
263
+
264
+ // src/adapters/claude/claude-agent.ts
265
+ import * as fs2 from "fs";
266
+ import * as os3 from "os";
267
+ import * as path3 from "path";
268
+ import {
269
+ RequestError as RequestError2
270
+ } from "@agentclientprotocol/sdk";
271
+ import {
272
+ query
273
+ } from "@anthropic-ai/claude-agent-sdk";
274
+ import { v7 as uuidv7 } from "uuid";
275
+
276
+ // package.json
277
+ var package_default = {
278
+ name: "@posthog/agent",
279
+ version: "2.0.1",
280
+ repository: "https://github.com/PostHog/twig",
281
+ description: "TypeScript agent framework wrapping Claude Agent SDK with Git-based task execution for PostHog",
282
+ exports: {
283
+ ".": {
284
+ types: "./dist/index.d.ts",
285
+ import: "./dist/index.js"
286
+ },
287
+ "./agent": {
288
+ types: "./dist/agent.d.ts",
289
+ import: "./dist/agent.js"
290
+ },
291
+ "./gateway-models": {
292
+ types: "./dist/gateway-models.d.ts",
293
+ import: "./dist/gateway-models.js"
294
+ },
295
+ "./posthog-api": {
296
+ types: "./dist/posthog-api.d.ts",
297
+ import: "./dist/posthog-api.js"
298
+ },
299
+ "./types": {
300
+ types: "./dist/types.d.ts",
301
+ import: "./dist/types.js"
302
+ },
303
+ "./adapters/claude/questions/utils": {
304
+ types: "./dist/adapters/claude/questions/utils.d.ts",
305
+ import: "./dist/adapters/claude/questions/utils.js"
306
+ },
307
+ "./adapters/claude/permissions/permission-options": {
308
+ types: "./dist/adapters/claude/permissions/permission-options.d.ts",
309
+ import: "./dist/adapters/claude/permissions/permission-options.js"
310
+ },
311
+ "./adapters/claude/tools": {
312
+ types: "./dist/adapters/claude/tools.d.ts",
313
+ import: "./dist/adapters/claude/tools.js"
314
+ },
315
+ "./adapters/claude/conversion/tool-use-to-acp": {
316
+ types: "./dist/adapters/claude/conversion/tool-use-to-acp.d.ts",
317
+ import: "./dist/adapters/claude/conversion/tool-use-to-acp.js"
318
+ },
319
+ "./server": {
320
+ types: "./dist/server/agent-server.d.ts",
321
+ import: "./dist/server/agent-server.js"
322
+ }
323
+ },
324
+ bin: {
325
+ "agent-server": "./dist/server/bin.js"
326
+ },
327
+ type: "module",
328
+ keywords: [
329
+ "posthog",
330
+ "claude",
331
+ "agent",
332
+ "ai",
333
+ "git",
334
+ "typescript"
335
+ ],
336
+ author: "PostHog",
337
+ license: "SEE LICENSE IN LICENSE",
338
+ scripts: {
339
+ build: "tsup",
340
+ dev: "tsup --watch",
341
+ test: "vitest run",
342
+ "test:watch": "vitest",
343
+ typecheck: "pnpm exec tsc --noEmit",
344
+ prepublishOnly: "pnpm run build"
345
+ },
346
+ engines: {
347
+ node: ">=20.0.0"
348
+ },
349
+ devDependencies: {
350
+ "@changesets/cli": "^2.27.8",
351
+ "@types/bun": "latest",
352
+ "@types/tar": "^6.1.13",
353
+ minimatch: "^10.0.3",
354
+ msw: "^2.12.7",
355
+ tsup: "^8.5.1",
356
+ tsx: "^4.20.6",
357
+ typescript: "^5.5.0",
358
+ vitest: "^2.1.8"
359
+ },
360
+ dependencies: {
361
+ "@opentelemetry/api-logs": "^0.208.0",
362
+ "@opentelemetry/exporter-logs-otlp-http": "^0.208.0",
363
+ "@opentelemetry/resources": "^2.0.0",
364
+ "@opentelemetry/sdk-logs": "^0.208.0",
365
+ "@opentelemetry/semantic-conventions": "^1.28.0",
366
+ "@agentclientprotocol/sdk": "^0.14.0",
367
+ "@anthropic-ai/claude-agent-sdk": "0.2.12",
368
+ "@anthropic-ai/sdk": "^0.71.0",
369
+ "@hono/node-server": "^1.19.9",
370
+ "@modelcontextprotocol/sdk": "^1.25.3",
371
+ "@posthog/shared": "workspace:*",
372
+ "@twig/git": "workspace:*",
373
+ "@types/jsonwebtoken": "^9.0.10",
374
+ commander: "^14.0.2",
375
+ diff: "^8.0.2",
376
+ dotenv: "^17.2.3",
377
+ hono: "^4.11.7",
378
+ jsonwebtoken: "^9.0.2",
379
+ tar: "^7.5.0",
380
+ uuid: "13.0.0",
381
+ "yoga-wasm-web": "^0.3.3",
382
+ zod: "^3.24.1"
383
+ },
384
+ files: [
385
+ "dist/**/*",
386
+ "src/**/*",
387
+ "README.md",
388
+ "CLAUDE.md"
389
+ ],
390
+ publishConfig: {
391
+ access: "public"
392
+ }
393
+ };
394
+
395
+ // src/utils/common.ts
396
+ var IS_ROOT = typeof process !== "undefined" && (process.geteuid?.() ?? process.getuid?.()) === 0;
397
+ function unreachable(value, logger) {
398
+ let valueAsString;
399
+ try {
400
+ valueAsString = JSON.stringify(value);
401
+ } catch {
402
+ valueAsString = value;
403
+ }
404
+ logger.error(`Unexpected case: ${valueAsString}`);
405
+ }
406
+
407
+ // src/gateway-models.ts
408
+ var DEFAULT_GATEWAY_MODEL = "claude-opus-4-6";
409
+ var BLOCKED_MODELS = /* @__PURE__ */ new Set(["gpt-5-mini", "openai/gpt-5-mini"]);
410
+ async function fetchGatewayModels(options) {
411
+ const gatewayUrl = options?.gatewayUrl ?? process.env.ANTHROPIC_BASE_URL;
412
+ if (!gatewayUrl) {
413
+ return [];
414
+ }
415
+ const modelsUrl = `${gatewayUrl}/v1/models`;
416
+ try {
417
+ const response = await fetch(modelsUrl);
418
+ if (!response.ok) {
419
+ return [];
420
+ }
421
+ const data = await response.json();
422
+ const models = data.data ?? [];
423
+ return models.filter((m) => !BLOCKED_MODELS.has(m.id));
424
+ } catch {
425
+ return [];
426
+ }
427
+ }
428
+ function isAnthropicModel(model) {
429
+ if (model.owned_by) {
430
+ return model.owned_by === "anthropic";
431
+ }
432
+ return model.id.startsWith("claude-") || model.id.startsWith("anthropic/");
433
+ }
434
+ async function fetchArrayModels(options) {
435
+ const gatewayUrl = options?.gatewayUrl ?? process.env.ANTHROPIC_BASE_URL;
436
+ if (!gatewayUrl) {
437
+ return [];
438
+ }
439
+ try {
440
+ const base = new URL(gatewayUrl);
441
+ base.pathname = "/array/v1/models";
442
+ base.search = "";
443
+ base.hash = "";
444
+ const response = await fetch(base.toString());
445
+ if (!response.ok) {
446
+ return [];
447
+ }
448
+ const data = await response.json();
449
+ const models = Array.isArray(data) ? data : data.data ?? data.models ?? [];
450
+ const results = [];
451
+ for (const model of models) {
452
+ const id = model?.id ? String(model.id) : "";
453
+ if (!id) continue;
454
+ results.push({ id, owned_by: model?.owned_by });
455
+ }
456
+ return results;
457
+ } catch {
458
+ return [];
459
+ }
460
+ }
461
+ var PROVIDER_PREFIXES = ["anthropic/", "openai/", "google-vertex/"];
462
+ function formatGatewayModelName(model) {
463
+ let cleanId = model.id;
464
+ for (const prefix of PROVIDER_PREFIXES) {
465
+ if (cleanId.startsWith(prefix)) {
466
+ cleanId = cleanId.slice(prefix.length);
467
+ break;
468
+ }
469
+ }
470
+ cleanId = cleanId.replace(/(\d)-(\d)/g, "$1.$2");
471
+ const words = cleanId.split(/[-_]/).map((word) => {
472
+ if (word.match(/^[0-9.]+$/)) return word;
473
+ return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
474
+ });
475
+ return words.join(" ");
476
+ }
477
+
478
+ // src/adapters/base-acp-agent.ts
479
+ var BaseAcpAgent = class {
480
+ session;
481
+ sessionId;
482
+ client;
483
+ logger;
484
+ fileContentCache = {};
485
+ constructor(client) {
486
+ this.client = client;
487
+ this.logger = new Logger({ debug: true, prefix: "[BaseAcpAgent]" });
488
+ }
489
+ async cancel(params) {
490
+ if (this.sessionId !== params.sessionId) {
491
+ throw new Error("Session not found");
492
+ }
493
+ this.session.cancelled = true;
494
+ const meta = params._meta;
495
+ if (meta?.interruptReason) {
496
+ this.session.interruptReason = meta.interruptReason;
497
+ }
498
+ await this.interruptSession();
499
+ }
500
+ async closeSession() {
501
+ try {
502
+ this.session.abortController.abort();
503
+ await this.cancel({ sessionId: this.sessionId });
504
+ this.logger.info("Closed session", { sessionId: this.sessionId });
505
+ } catch (err) {
506
+ this.logger.warn("Failed to close session", {
507
+ sessionId: this.sessionId,
508
+ error: err
509
+ });
510
+ }
511
+ }
512
+ hasSession(sessionId) {
513
+ return this.sessionId === sessionId;
514
+ }
515
+ appendNotification(sessionId, notification) {
516
+ if (this.sessionId === sessionId) {
517
+ this.session.notificationHistory.push(notification);
518
+ }
519
+ }
520
+ async readTextFile(params) {
521
+ const response = await this.client.readTextFile(params);
522
+ if (!params.limit && !params.line) {
523
+ this.fileContentCache[params.path] = response.content;
524
+ }
525
+ return response;
526
+ }
527
+ async writeTextFile(params) {
528
+ const response = await this.client.writeTextFile(params);
529
+ this.fileContentCache[params.path] = params.content;
530
+ return response;
531
+ }
532
+ async authenticate(_params) {
533
+ throw new Error("Method not implemented.");
534
+ }
535
+ async getModelConfigOptions(currentModelOverride) {
536
+ const gatewayModels = await fetchGatewayModels();
537
+ const options = gatewayModels.filter((model) => isAnthropicModel(model)).map((model) => ({
538
+ value: model.id,
539
+ name: formatGatewayModelName(model),
540
+ description: `Context: ${model.context_window.toLocaleString()} tokens`
541
+ }));
542
+ const isAnthropicModelId = (modelId) => modelId.startsWith("claude-") || modelId.startsWith("anthropic/");
543
+ let currentModelId = currentModelOverride ?? DEFAULT_GATEWAY_MODEL;
544
+ if (!options.some((opt) => opt.value === currentModelId)) {
545
+ if (!isAnthropicModelId(currentModelId)) {
546
+ currentModelId = DEFAULT_GATEWAY_MODEL;
547
+ }
548
+ }
549
+ if (!options.some((opt) => opt.value === currentModelId)) {
550
+ options.unshift({
551
+ value: currentModelId,
552
+ name: currentModelId,
553
+ description: "Custom model"
554
+ });
555
+ }
556
+ return { currentModelId, options };
557
+ }
558
+ };
559
+
560
+ // src/adapters/claude/conversion/acp-to-sdk.ts
561
+ function sdkText(value) {
562
+ return { type: "text", text: value };
563
+ }
564
+ function formatUriAsLink(uri) {
565
+ try {
566
+ if (uri.startsWith("file://")) {
567
+ const filePath = uri.slice(7);
568
+ const name = filePath.split("/").pop() || filePath;
569
+ return `[@${name}](${uri})`;
570
+ }
571
+ if (uri.startsWith("zed://")) {
572
+ const parts = uri.split("/");
573
+ const name = parts[parts.length - 1] || uri;
574
+ return `[@${name}](${uri})`;
575
+ }
576
+ return uri;
577
+ } catch {
578
+ return uri;
579
+ }
580
+ }
581
+ function transformMcpCommand(text2) {
582
+ const mcpMatch = text2.match(/^\/mcp:([^:\s]+):(\S+)(\s+.*)?$/);
583
+ if (mcpMatch) {
584
+ const [, server, command, args] = mcpMatch;
585
+ return `/${server}:${command} (MCP)${args || ""}`;
586
+ }
587
+ return text2;
588
+ }
589
+ function processPromptChunk(chunk, content, context) {
590
+ switch (chunk.type) {
591
+ case "text":
592
+ content.push(sdkText(transformMcpCommand(chunk.text)));
593
+ break;
594
+ case "resource_link":
595
+ content.push(sdkText(formatUriAsLink(chunk.uri)));
596
+ break;
597
+ case "resource":
598
+ if ("text" in chunk.resource) {
599
+ content.push(sdkText(formatUriAsLink(chunk.resource.uri)));
600
+ context.push(
601
+ sdkText(
602
+ `
603
+ <context ref="${chunk.resource.uri}">
604
+ ${chunk.resource.text}
605
+ </context>`
606
+ )
607
+ );
608
+ }
609
+ break;
610
+ case "image":
611
+ if (chunk.data) {
612
+ content.push({
613
+ type: "image",
614
+ source: {
615
+ type: "base64",
616
+ data: chunk.data,
617
+ media_type: chunk.mimeType
618
+ }
619
+ });
620
+ } else if (chunk.uri?.startsWith("http")) {
621
+ content.push({
622
+ type: "image",
623
+ source: { type: "url", url: chunk.uri }
624
+ });
625
+ }
626
+ break;
627
+ default:
628
+ break;
629
+ }
630
+ }
631
+ function promptToClaude(prompt) {
632
+ const content = [];
633
+ const context = [];
634
+ for (const chunk of prompt.prompt) {
635
+ processPromptChunk(chunk, content, context);
636
+ }
637
+ content.push(...context);
638
+ return {
639
+ type: "user",
640
+ message: { role: "user", content },
641
+ session_id: prompt.sessionId,
642
+ parent_tool_use_id: null
643
+ };
644
+ }
645
+
646
+ // src/adapters/claude/conversion/sdk-to-acp.ts
647
+ import { RequestError } from "@agentclientprotocol/sdk";
648
+
649
+ // src/utils/acp-content.ts
650
+ function text(value) {
651
+ return { type: "text", text: value };
652
+ }
653
+ function image(data, mimeType, uri) {
654
+ return { type: "image", data, mimeType, uri };
655
+ }
656
+ function resourceLink(uri, name, options) {
657
+ return {
658
+ type: "resource_link",
659
+ uri,
660
+ name,
661
+ ...options
662
+ };
663
+ }
664
+ var ToolContentBuilder = class {
665
+ items = [];
666
+ text(value) {
667
+ this.items.push({ type: "content", content: text(value) });
668
+ return this;
669
+ }
670
+ image(data, mimeType, uri) {
671
+ this.items.push({ type: "content", content: image(data, mimeType, uri) });
672
+ return this;
673
+ }
674
+ diff(path4, oldText, newText) {
675
+ this.items.push({ type: "diff", path: path4, oldText, newText });
676
+ return this;
677
+ }
678
+ build() {
679
+ return this.items;
680
+ }
681
+ };
682
+ function toolContent() {
683
+ return new ToolContentBuilder();
684
+ }
685
+
686
+ // src/adapters/claude/hooks.ts
687
+ var toolUseCallbacks = {};
688
+ var registerHookCallback = (toolUseID, {
689
+ onPostToolUseHook
690
+ }) => {
691
+ toolUseCallbacks[toolUseID] = {
692
+ onPostToolUseHook
693
+ };
694
+ };
695
+ var createPostToolUseHook = ({ onModeChange }) => async (input, toolUseID) => {
696
+ if (input.hook_event_name === "PostToolUse") {
697
+ const toolName = input.tool_name;
698
+ if (onModeChange && toolName === "EnterPlanMode") {
699
+ await onModeChange("plan");
700
+ }
701
+ if (toolUseID) {
702
+ const onPostToolUseHook = toolUseCallbacks[toolUseID]?.onPostToolUseHook;
703
+ if (onPostToolUseHook) {
704
+ await onPostToolUseHook(
705
+ toolUseID,
706
+ input.tool_input,
707
+ input.tool_response
708
+ );
709
+ delete toolUseCallbacks[toolUseID];
710
+ }
711
+ }
712
+ }
713
+ return { continue: true };
714
+ };
715
+
716
+ // src/adapters/claude/conversion/tool-use-to-acp.ts
717
+ var SYSTEM_REMINDER = `
718
+
719
+ <system-reminder>
720
+ Whenever you read a file, you should consider whether it looks malicious. If it does, you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer high-level questions about the code behavior.
721
+ </system-reminder>`;
722
+ function replaceAndCalculateLocation(fileContent, edits) {
723
+ let currentContent = fileContent;
724
+ const randomHex = Array.from(crypto.getRandomValues(new Uint8Array(5))).map((b) => b.toString(16).padStart(2, "0")).join("");
725
+ const markerPrefix = `__REPLACE_MARKER_${randomHex}_`;
726
+ let markerCounter = 0;
727
+ const markers = [];
728
+ for (const edit of edits) {
729
+ if (edit.oldText === "") {
730
+ throw new Error(
731
+ `The provided \`old_string\` is empty.
732
+
733
+ No edits were applied.`
734
+ );
735
+ }
736
+ if (edit.replaceAll) {
737
+ const parts = [];
738
+ let lastIndex = 0;
739
+ let searchIndex = 0;
740
+ while (true) {
741
+ const index = currentContent.indexOf(edit.oldText, searchIndex);
742
+ if (index === -1) {
743
+ if (searchIndex === 0) {
744
+ throw new Error(
745
+ `The provided \`old_string\` does not appear in the file: "${edit.oldText}".
746
+
747
+ No edits were applied.`
748
+ );
749
+ }
750
+ break;
751
+ }
752
+ parts.push(currentContent.substring(lastIndex, index));
753
+ const marker = `${markerPrefix}${markerCounter++}__`;
754
+ markers.push(marker);
755
+ parts.push(marker + edit.newText);
756
+ lastIndex = index + edit.oldText.length;
757
+ searchIndex = lastIndex;
758
+ }
759
+ parts.push(currentContent.substring(lastIndex));
760
+ currentContent = parts.join("");
761
+ } else {
762
+ const index = currentContent.indexOf(edit.oldText);
763
+ if (index === -1) {
764
+ throw new Error(
765
+ `The provided \`old_string\` does not appear in the file: "${edit.oldText}".
766
+
767
+ No edits were applied.`
768
+ );
769
+ } else {
770
+ const marker = `${markerPrefix}${markerCounter++}__`;
771
+ markers.push(marker);
772
+ currentContent = currentContent.substring(0, index) + marker + edit.newText + currentContent.substring(index + edit.oldText.length);
773
+ }
774
+ }
775
+ }
776
+ const lineNumbers = [];
777
+ for (const marker of markers) {
778
+ const index = currentContent.indexOf(marker);
779
+ if (index !== -1) {
780
+ const lineNumber = Math.max(
781
+ 0,
782
+ currentContent.substring(0, index).split(/\r\n|\r|\n/).length - 1
783
+ );
784
+ lineNumbers.push(lineNumber);
785
+ }
786
+ }
787
+ let finalContent = currentContent;
788
+ for (const marker of markers) {
789
+ finalContent = finalContent.replace(marker, "");
790
+ }
791
+ const uniqueLineNumbers = [...new Set(lineNumbers)].sort();
792
+ return { newContent: finalContent, lineNumbers: uniqueLineNumbers };
793
+ }
794
+ function toolInfoFromToolUse(toolUse, cachedFileContent, logger = new Logger({ debug: false, prefix: "[ClaudeTools]" })) {
795
+ const name = toolUse.name;
796
+ const input = toolUse.input;
797
+ switch (name) {
798
+ case "Task":
799
+ return {
800
+ title: input?.description ? String(input.description) : "Task",
801
+ kind: "think",
802
+ content: input?.prompt ? toolContent().text(String(input.prompt)).build() : []
803
+ };
804
+ case "NotebookRead":
805
+ return {
806
+ title: input?.notebook_path ? `Read Notebook ${String(input.notebook_path)}` : "Read Notebook",
807
+ kind: "read",
808
+ content: [],
809
+ locations: input?.notebook_path ? [{ path: String(input.notebook_path) }] : []
810
+ };
811
+ case "NotebookEdit":
812
+ return {
813
+ title: input?.notebook_path ? `Edit Notebook ${String(input.notebook_path)}` : "Edit Notebook",
814
+ kind: "edit",
815
+ content: input?.new_source ? toolContent().text(String(input.new_source)).build() : [],
816
+ locations: input?.notebook_path ? [{ path: String(input.notebook_path) }] : []
817
+ };
818
+ case "Bash":
819
+ return {
820
+ title: input?.description ? String(input.description) : "Execute command",
821
+ kind: "execute",
822
+ content: input?.command ? toolContent().text(String(input.command)).build() : []
823
+ };
824
+ case "BashOutput":
825
+ return {
826
+ title: "Tail Logs",
827
+ kind: "execute",
828
+ content: []
829
+ };
830
+ case "KillShell":
831
+ return {
832
+ title: "Kill Process",
833
+ kind: "execute",
834
+ content: []
835
+ };
836
+ case "Read": {
837
+ let limit = "";
838
+ const inputLimit = input?.limit;
839
+ const inputOffset = input?.offset ?? 0;
840
+ if (inputLimit) {
841
+ limit = ` (${inputOffset + 1} - ${inputOffset + inputLimit})`;
842
+ } else if (inputOffset) {
843
+ limit = ` (from line ${inputOffset + 1})`;
844
+ }
845
+ return {
846
+ title: `Read ${input?.file_path ? String(input.file_path) : "File"}${limit}`,
847
+ kind: "read",
848
+ locations: input?.file_path ? [
849
+ {
850
+ path: String(input.file_path),
851
+ line: inputOffset
852
+ }
853
+ ] : [],
854
+ content: []
855
+ };
856
+ }
857
+ case "LS":
858
+ return {
859
+ title: `List the ${input?.path ? `\`${String(input.path)}\`` : "current"} directory's contents`,
860
+ kind: "search",
861
+ content: [],
862
+ locations: []
863
+ };
864
+ case "Edit": {
865
+ const path4 = input?.file_path ? String(input.file_path) : void 0;
866
+ let oldText = input?.old_string ? String(input.old_string) : null;
867
+ let newText = input?.new_string ? String(input.new_string) : "";
868
+ let affectedLines = [];
869
+ if (path4 && oldText) {
870
+ try {
871
+ const oldContent = cachedFileContent[path4] || "";
872
+ const newContent = replaceAndCalculateLocation(oldContent, [
873
+ {
874
+ oldText,
875
+ newText,
876
+ replaceAll: false
877
+ }
878
+ ]);
879
+ oldText = oldContent;
880
+ newText = newContent.newContent;
881
+ affectedLines = newContent.lineNumbers;
882
+ } catch (e) {
883
+ logger.error("Failed to edit file", e);
884
+ }
885
+ }
886
+ return {
887
+ title: path4 ? `Edit \`${path4}\`` : "Edit",
888
+ kind: "edit",
889
+ content: input && path4 ? [
890
+ {
891
+ type: "diff",
892
+ path: path4,
893
+ oldText,
894
+ newText
895
+ }
896
+ ] : [],
897
+ locations: path4 ? affectedLines.length > 0 ? affectedLines.map((line) => ({ line, path: path4 })) : [{ path: path4 }] : []
898
+ };
899
+ }
900
+ case "Write": {
901
+ let contentResult = [];
902
+ const filePath = input?.file_path ? String(input.file_path) : void 0;
903
+ const contentStr = input?.content ? String(input.content) : void 0;
904
+ if (filePath) {
905
+ contentResult = toolContent().diff(filePath, null, contentStr ?? "").build();
906
+ } else if (contentStr) {
907
+ contentResult = toolContent().text(contentStr).build();
908
+ }
909
+ return {
910
+ title: filePath ? `Write ${filePath}` : "Write",
911
+ kind: "edit",
912
+ content: contentResult,
913
+ locations: filePath ? [{ path: filePath }] : []
914
+ };
915
+ }
916
+ case "Glob": {
917
+ let label = "Find";
918
+ const pathStr = input?.path ? String(input.path) : void 0;
919
+ if (pathStr) {
920
+ label += ` "${pathStr}"`;
921
+ }
922
+ if (input?.pattern) {
923
+ label += ` "${String(input.pattern)}"`;
924
+ }
925
+ return {
926
+ title: label,
927
+ kind: "search",
928
+ content: [],
929
+ locations: pathStr ? [{ path: pathStr }] : []
930
+ };
931
+ }
932
+ case "Grep": {
933
+ let label = "grep";
934
+ if (input?.["-i"]) {
935
+ label += " -i";
936
+ }
937
+ if (input?.["-n"]) {
938
+ label += " -n";
939
+ }
940
+ if (input?.["-A"] !== void 0) {
941
+ label += ` -A ${input["-A"]}`;
942
+ }
943
+ if (input?.["-B"] !== void 0) {
944
+ label += ` -B ${input["-B"]}`;
945
+ }
946
+ if (input?.["-C"] !== void 0) {
947
+ label += ` -C ${input["-C"]}`;
948
+ }
949
+ if (input?.output_mode) {
950
+ switch (input.output_mode) {
951
+ case "FilesWithMatches":
952
+ label += " -l";
953
+ break;
954
+ case "Count":
955
+ label += " -c";
956
+ break;
957
+ default:
958
+ break;
959
+ }
960
+ }
961
+ if (input?.head_limit !== void 0) {
962
+ label += ` | head -${input.head_limit}`;
963
+ }
964
+ if (input?.glob) {
965
+ label += ` --include="${String(input.glob)}"`;
966
+ }
967
+ if (input?.type) {
968
+ label += ` --type=${String(input.type)}`;
969
+ }
970
+ if (input?.multiline) {
971
+ label += " -P";
972
+ }
973
+ label += ` "${input?.pattern ? String(input.pattern) : ""}"`;
974
+ if (input?.path) {
975
+ label += ` ${String(input.path)}`;
976
+ }
977
+ return {
978
+ title: label,
979
+ kind: "search",
980
+ content: []
981
+ };
982
+ }
983
+ case "WebFetch":
984
+ return {
985
+ title: "Fetch",
986
+ kind: "fetch",
987
+ content: input?.url ? [
988
+ {
989
+ type: "content",
990
+ content: resourceLink(String(input.url), String(input.url), {
991
+ description: input?.prompt ? String(input.prompt) : void 0
992
+ })
993
+ }
994
+ ] : []
995
+ };
996
+ case "WebSearch": {
997
+ let label = `"${input?.query ? String(input.query) : ""}"`;
998
+ const allowedDomains = input?.allowed_domains;
999
+ const blockedDomains = input?.blocked_domains;
1000
+ if (allowedDomains && allowedDomains.length > 0) {
1001
+ label += ` (allowed: ${allowedDomains.join(", ")})`;
1002
+ }
1003
+ if (blockedDomains && blockedDomains.length > 0) {
1004
+ label += ` (blocked: ${blockedDomains.join(", ")})`;
1005
+ }
1006
+ return {
1007
+ title: label,
1008
+ kind: "fetch",
1009
+ content: []
1010
+ };
1011
+ }
1012
+ case "TodoWrite":
1013
+ return {
1014
+ title: Array.isArray(input?.todos) ? `Update TODOs: ${input.todos.map((todo) => todo.content).join(", ")}` : "Update TODOs",
1015
+ kind: "think",
1016
+ content: []
1017
+ };
1018
+ case "ExitPlanMode":
1019
+ return {
1020
+ title: "Ready to code?",
1021
+ kind: "switch_mode",
1022
+ content: input?.plan ? toolContent().text(String(input.plan)).build() : []
1023
+ };
1024
+ case "AskUserQuestion": {
1025
+ const questions = input?.questions;
1026
+ return {
1027
+ title: questions?.[0]?.question || "Question",
1028
+ kind: "other",
1029
+ content: questions ? toolContent().text(JSON.stringify(questions, null, 2)).build() : []
1030
+ };
1031
+ }
1032
+ case "Other": {
1033
+ let output;
1034
+ try {
1035
+ output = JSON.stringify(input, null, 2);
1036
+ } catch {
1037
+ output = typeof input === "string" ? input : "{}";
1038
+ }
1039
+ return {
1040
+ title: name || "Unknown Tool",
1041
+ kind: "other",
1042
+ content: toolContent().text(`\`\`\`json
1043
+ ${output}\`\`\``).build()
1044
+ };
1045
+ }
1046
+ default:
1047
+ return {
1048
+ title: name || "Unknown Tool",
1049
+ kind: "other",
1050
+ content: []
1051
+ };
1052
+ }
1053
+ }
1054
+ function toolUpdateFromToolResult(toolResult, toolUse) {
1055
+ switch (toolUse?.name) {
1056
+ case "Read":
1057
+ if (Array.isArray(toolResult.content) && toolResult.content.length > 0) {
1058
+ return {
1059
+ content: toolResult.content.map((item) => {
1060
+ const itemObj = item;
1061
+ if (itemObj.type === "text") {
1062
+ return {
1063
+ type: "content",
1064
+ content: text(
1065
+ markdownEscape(
1066
+ (itemObj.text ?? "").replace(SYSTEM_REMINDER, "")
1067
+ )
1068
+ )
1069
+ };
1070
+ }
1071
+ return {
1072
+ type: "content",
1073
+ content: item
1074
+ };
1075
+ })
1076
+ };
1077
+ } else if (typeof toolResult.content === "string" && toolResult.content.length > 0) {
1078
+ return {
1079
+ content: toolContent().text(
1080
+ markdownEscape(toolResult.content.replace(SYSTEM_REMINDER, ""))
1081
+ ).build()
1082
+ };
1083
+ }
1084
+ return {};
1085
+ case "Bash": {
1086
+ return toAcpContentUpdate(
1087
+ toolResult.content,
1088
+ "is_error" in toolResult ? toolResult.is_error : false
1089
+ );
1090
+ }
1091
+ case "Edit":
1092
+ case "Write": {
1093
+ if ("is_error" in toolResult && toolResult.is_error && toolResult.content && toolResult.content.length > 0) {
1094
+ return toAcpContentUpdate(toolResult.content, true);
1095
+ }
1096
+ return {};
1097
+ }
1098
+ case "ExitPlanMode": {
1099
+ return { title: "Exited Plan Mode" };
1100
+ }
1101
+ case "AskUserQuestion": {
1102
+ const content = toolResult.content;
1103
+ if (Array.isArray(content) && content.length > 0) {
1104
+ const firstItem = content[0];
1105
+ if (typeof firstItem === "object" && firstItem !== null && "text" in firstItem) {
1106
+ return {
1107
+ title: "Answer received",
1108
+ content: toolContent().text(String(firstItem.text)).build()
1109
+ };
1110
+ }
1111
+ }
1112
+ return { title: "Question answered" };
1113
+ }
1114
+ default: {
1115
+ return toAcpContentUpdate(
1116
+ toolResult.content,
1117
+ "is_error" in toolResult ? toolResult.is_error : false
1118
+ );
1119
+ }
1120
+ }
1121
+ }
1122
+ function toAcpContentUpdate(content, isError = false) {
1123
+ if (Array.isArray(content) && content.length > 0) {
1124
+ return {
1125
+ content: content.map((item) => {
1126
+ const itemObj = item;
1127
+ if (isError && itemObj.type === "text") {
1128
+ return {
1129
+ type: "content",
1130
+ content: text(`\`\`\`
1131
+ ${itemObj.text ?? ""}
1132
+ \`\`\``)
1133
+ };
1134
+ }
1135
+ return {
1136
+ type: "content",
1137
+ content: item
1138
+ };
1139
+ })
1140
+ };
1141
+ } else if (typeof content === "string" && content.length > 0) {
1142
+ return {
1143
+ content: toolContent().text(isError ? `\`\`\`
1144
+ ${content}
1145
+ \`\`\`` : content).build()
1146
+ };
1147
+ }
1148
+ return {};
1149
+ }
1150
+ function planEntries(input) {
1151
+ return input.todos.map((input2) => ({
1152
+ content: input2.content,
1153
+ status: input2.status,
1154
+ priority: "medium"
1155
+ }));
1156
+ }
1157
+ function markdownEscape(text2) {
1158
+ let escapedText = "```";
1159
+ for (const [m] of text2.matchAll(/^```+/gm)) {
1160
+ while (m.length >= escapedText.length) {
1161
+ escapedText += "`";
1162
+ }
1163
+ }
1164
+ return `${escapedText}
1165
+ ${text2}${text2.endsWith("\n") ? "" : "\n"}${escapedText}`;
1166
+ }
1167
+
1168
+ // src/adapters/claude/conversion/sdk-to-acp.ts
1169
+ function messageUpdateType(role) {
1170
+ return role === "assistant" ? "agent_message_chunk" : "user_message_chunk";
1171
+ }
1172
+ function toolMeta(toolName, toolResponse) {
1173
+ return toolResponse ? { claudeCode: { toolName, toolResponse } } : { claudeCode: { toolName } };
1174
+ }
1175
+ function handleTextChunk(chunk, role) {
1176
+ return {
1177
+ sessionUpdate: messageUpdateType(role),
1178
+ content: text(chunk.text)
1179
+ };
1180
+ }
1181
+ function handleImageChunk(chunk, role) {
1182
+ return {
1183
+ sessionUpdate: messageUpdateType(role),
1184
+ content: image(
1185
+ chunk.source.type === "base64" ? chunk.source.data ?? "" : "",
1186
+ chunk.source.type === "base64" ? chunk.source.media_type ?? "" : "",
1187
+ chunk.source.type === "url" ? chunk.source.url : void 0
1188
+ )
1189
+ };
1190
+ }
1191
+ function handleThinkingChunk(chunk) {
1192
+ return {
1193
+ sessionUpdate: "agent_thought_chunk",
1194
+ content: text(chunk.thinking)
1195
+ };
1196
+ }
1197
+ function handleToolUseChunk(chunk, ctx) {
1198
+ ctx.toolUseCache[chunk.id] = chunk;
1199
+ if (chunk.name === "TodoWrite") {
1200
+ const input = chunk.input;
1201
+ if (Array.isArray(input.todos)) {
1202
+ return {
1203
+ sessionUpdate: "plan",
1204
+ entries: planEntries(chunk.input)
1205
+ };
1206
+ }
1207
+ return null;
1208
+ }
1209
+ registerHookCallback(chunk.id, {
1210
+ onPostToolUseHook: async (toolUseId, _toolInput, toolResponse) => {
1211
+ const toolUse = ctx.toolUseCache[toolUseId];
1212
+ if (toolUse) {
1213
+ await ctx.client.sessionUpdate({
1214
+ sessionId: ctx.sessionId,
1215
+ update: {
1216
+ _meta: toolMeta(toolUse.name, toolResponse),
1217
+ toolCallId: toolUseId,
1218
+ sessionUpdate: "tool_call_update"
1219
+ }
1220
+ });
1221
+ } else {
1222
+ ctx.logger.error(
1223
+ `Got a tool response for tool use that wasn't tracked: ${toolUseId}`
1224
+ );
1225
+ }
1226
+ }
1227
+ });
1228
+ let rawInput;
1229
+ try {
1230
+ rawInput = JSON.parse(JSON.stringify(chunk.input));
1231
+ } catch {
1232
+ }
1233
+ return {
1234
+ _meta: toolMeta(chunk.name),
1235
+ toolCallId: chunk.id,
1236
+ sessionUpdate: "tool_call",
1237
+ rawInput,
1238
+ status: "pending",
1239
+ ...toolInfoFromToolUse(chunk, ctx.fileContentCache, ctx.logger)
1240
+ };
1241
+ }
1242
+ function handleToolResultChunk(chunk, ctx) {
1243
+ const toolUse = ctx.toolUseCache[chunk.tool_use_id];
1244
+ if (!toolUse) {
1245
+ ctx.logger.error(
1246
+ `Got a tool result for tool use that wasn't tracked: ${chunk.tool_use_id}`
1247
+ );
1248
+ return null;
1249
+ }
1250
+ if (toolUse.name === "TodoWrite") {
1251
+ return null;
1252
+ }
1253
+ return {
1254
+ _meta: toolMeta(toolUse.name),
1255
+ toolCallId: chunk.tool_use_id,
1256
+ sessionUpdate: "tool_call_update",
1257
+ status: chunk.is_error ? "failed" : "completed",
1258
+ ...toolUpdateFromToolResult(
1259
+ chunk,
1260
+ toolUse
1261
+ )
1262
+ };
1263
+ }
1264
+ function processContentChunk(chunk, role, ctx) {
1265
+ switch (chunk.type) {
1266
+ case "text":
1267
+ case "text_delta":
1268
+ return handleTextChunk(chunk, role);
1269
+ case "image":
1270
+ return handleImageChunk(chunk, role);
1271
+ case "thinking":
1272
+ case "thinking_delta":
1273
+ return handleThinkingChunk(chunk);
1274
+ case "tool_use":
1275
+ case "server_tool_use":
1276
+ case "mcp_tool_use":
1277
+ return handleToolUseChunk(chunk, ctx);
1278
+ case "tool_result":
1279
+ case "tool_search_tool_result":
1280
+ case "web_fetch_tool_result":
1281
+ case "web_search_tool_result":
1282
+ case "code_execution_tool_result":
1283
+ case "bash_code_execution_tool_result":
1284
+ case "text_editor_code_execution_tool_result":
1285
+ case "mcp_tool_result":
1286
+ return handleToolResultChunk(
1287
+ chunk,
1288
+ ctx
1289
+ );
1290
+ case "document":
1291
+ case "search_result":
1292
+ case "redacted_thinking":
1293
+ case "input_json_delta":
1294
+ case "citations_delta":
1295
+ case "signature_delta":
1296
+ case "container_upload":
1297
+ return null;
1298
+ default:
1299
+ unreachable(chunk, ctx.logger);
1300
+ return null;
1301
+ }
1302
+ }
1303
+ function toAcpNotifications(content, role, sessionId, toolUseCache, fileContentCache, client, logger) {
1304
+ if (typeof content === "string") {
1305
+ return [
1306
+ {
1307
+ sessionId,
1308
+ update: {
1309
+ sessionUpdate: messageUpdateType(role),
1310
+ content: text(content)
1311
+ }
1312
+ }
1313
+ ];
1314
+ }
1315
+ const ctx = {
1316
+ sessionId,
1317
+ toolUseCache,
1318
+ fileContentCache,
1319
+ client,
1320
+ logger
1321
+ };
1322
+ const output = [];
1323
+ for (const chunk of content) {
1324
+ const update = processContentChunk(chunk, role, ctx);
1325
+ if (update) {
1326
+ output.push({ sessionId, update });
1327
+ }
1328
+ }
1329
+ return output;
1330
+ }
1331
+ function streamEventToAcpNotifications(message, sessionId, toolUseCache, fileContentCache, client, logger) {
1332
+ const event = message.event;
1333
+ switch (event.type) {
1334
+ case "content_block_start":
1335
+ return toAcpNotifications(
1336
+ [event.content_block],
1337
+ "assistant",
1338
+ sessionId,
1339
+ toolUseCache,
1340
+ fileContentCache,
1341
+ client,
1342
+ logger
1343
+ );
1344
+ case "content_block_delta":
1345
+ return toAcpNotifications(
1346
+ [event.delta],
1347
+ "assistant",
1348
+ sessionId,
1349
+ toolUseCache,
1350
+ fileContentCache,
1351
+ client,
1352
+ logger
1353
+ );
1354
+ case "message_start":
1355
+ case "message_delta":
1356
+ case "message_stop":
1357
+ case "content_block_stop":
1358
+ return [];
1359
+ default:
1360
+ unreachable(event, logger);
1361
+ return [];
1362
+ }
1363
+ }
1364
+ async function handleSystemMessage(message, context) {
1365
+ const { session, sessionId, client, logger } = context;
1366
+ switch (message.subtype) {
1367
+ case "init":
1368
+ if (message.session_id && session && !session.sessionId) {
1369
+ session.sessionId = message.session_id;
1370
+ if (session.taskRunId) {
1371
+ await client.extNotification("_posthog/sdk_session", {
1372
+ taskRunId: session.taskRunId,
1373
+ sessionId: message.session_id,
1374
+ adapter: "claude"
1375
+ });
1376
+ }
1377
+ }
1378
+ break;
1379
+ case "compact_boundary":
1380
+ await client.extNotification("_posthog/compact_boundary", {
1381
+ sessionId,
1382
+ trigger: message.compact_metadata.trigger,
1383
+ preTokens: message.compact_metadata.pre_tokens
1384
+ });
1385
+ break;
1386
+ case "hook_response":
1387
+ logger.info("Hook response received", {
1388
+ hookName: message.hook_name,
1389
+ hookEvent: message.hook_event
1390
+ });
1391
+ break;
1392
+ case "status":
1393
+ if (message.status === "compacting") {
1394
+ logger.info("Session compacting started", { sessionId });
1395
+ await client.extNotification("_posthog/status", {
1396
+ sessionId,
1397
+ status: "compacting"
1398
+ });
1399
+ }
1400
+ break;
1401
+ case "task_notification": {
1402
+ logger.info("Task notification received", {
1403
+ sessionId,
1404
+ taskId: message.task_id,
1405
+ status: message.status,
1406
+ summary: message.summary
1407
+ });
1408
+ await client.extNotification("_posthog/task_notification", {
1409
+ sessionId,
1410
+ taskId: message.task_id,
1411
+ status: message.status,
1412
+ summary: message.summary,
1413
+ outputFile: message.output_file
1414
+ });
1415
+ break;
1416
+ }
1417
+ default:
1418
+ break;
1419
+ }
1420
+ }
1421
+ function handleResultMessage(message, context) {
1422
+ const { session } = context;
1423
+ if (session.cancelled) {
1424
+ return {
1425
+ shouldStop: true,
1426
+ stopReason: "cancelled"
1427
+ };
1428
+ }
1429
+ switch (message.subtype) {
1430
+ case "success": {
1431
+ if (message.result.includes("Please run /login")) {
1432
+ return {
1433
+ shouldStop: true,
1434
+ error: RequestError.authRequired()
1435
+ };
1436
+ }
1437
+ if (message.is_error) {
1438
+ return {
1439
+ shouldStop: true,
1440
+ error: RequestError.internalError(void 0, message.result)
1441
+ };
1442
+ }
1443
+ return { shouldStop: true, stopReason: "end_turn" };
1444
+ }
1445
+ case "error_during_execution":
1446
+ if (message.is_error) {
1447
+ return {
1448
+ shouldStop: true,
1449
+ error: RequestError.internalError(
1450
+ void 0,
1451
+ message.errors.join(", ") || message.subtype
1452
+ )
1453
+ };
1454
+ }
1455
+ return { shouldStop: true, stopReason: "end_turn" };
1456
+ case "error_max_budget_usd":
1457
+ case "error_max_turns":
1458
+ case "error_max_structured_output_retries":
1459
+ if (message.is_error) {
1460
+ return {
1461
+ shouldStop: true,
1462
+ error: RequestError.internalError(
1463
+ void 0,
1464
+ message.errors.join(", ") || message.subtype
1465
+ )
1466
+ };
1467
+ }
1468
+ return { shouldStop: true, stopReason: "max_turn_requests" };
1469
+ default:
1470
+ return { shouldStop: false };
1471
+ }
1472
+ }
1473
+ async function handleStreamEvent(message, context) {
1474
+ const { sessionId, client, toolUseCache, fileContentCache, logger } = context;
1475
+ for (const notification of streamEventToAcpNotifications(
1476
+ message,
1477
+ sessionId,
1478
+ toolUseCache,
1479
+ fileContentCache,
1480
+ client,
1481
+ logger
1482
+ )) {
1483
+ await client.sessionUpdate(notification);
1484
+ context.session.notificationHistory.push(notification);
1485
+ }
1486
+ }
1487
+ function hasLocalCommandStdout(content) {
1488
+ return typeof content === "string" && content.includes("<local-command-stdout>");
1489
+ }
1490
+ function hasLocalCommandStderr(content) {
1491
+ return typeof content === "string" && content.includes("<local-command-stderr>");
1492
+ }
1493
+ function isSimpleUserMessage(message) {
1494
+ return message.type === "user" && (typeof message.message.content === "string" || Array.isArray(message.message.content) && message.message.content.length === 1 && message.message.content[0].type === "text");
1495
+ }
1496
+ function isLoginRequiredMessage(message) {
1497
+ return message.type === "assistant" && message.message.model === "<synthetic>" && Array.isArray(message.message.content) && message.message.content.length === 1 && message.message.content[0].type === "text" && message.message.content[0].text?.includes("Please run /login") === true;
1498
+ }
1499
+ function shouldSkipUserAssistantMessage(message) {
1500
+ return hasLocalCommandStdout(message.message.content) || hasLocalCommandStderr(message.message.content) || isSimpleUserMessage(message) || isLoginRequiredMessage(message);
1501
+ }
1502
+ function logSpecialMessages(message, logger) {
1503
+ const content = message.message.content;
1504
+ if (hasLocalCommandStdout(content) && typeof content === "string") {
1505
+ logger.info(content);
1506
+ }
1507
+ if (hasLocalCommandStderr(content) && typeof content === "string") {
1508
+ logger.error(content);
1509
+ }
1510
+ }
1511
+ function filterMessageContent(content) {
1512
+ if (!Array.isArray(content)) {
1513
+ return content;
1514
+ }
1515
+ return content.filter(
1516
+ (block) => block.type !== "text" && block.type !== "thinking"
1517
+ );
1518
+ }
1519
+ async function handleUserAssistantMessage(message, context) {
1520
+ const { session, sessionId, client, toolUseCache, fileContentCache, logger } = context;
1521
+ if (session.cancelled) {
1522
+ return {};
1523
+ }
1524
+ if (shouldSkipUserAssistantMessage(message)) {
1525
+ logSpecialMessages(message, logger);
1526
+ if (isLoginRequiredMessage(message)) {
1527
+ return { shouldStop: true, error: RequestError.authRequired() };
1528
+ }
1529
+ return {};
1530
+ }
1531
+ const content = message.message.content;
1532
+ const contentToProcess = filterMessageContent(content);
1533
+ for (const notification of toAcpNotifications(
1534
+ contentToProcess,
1535
+ message.message.role,
1536
+ sessionId,
1537
+ toolUseCache,
1538
+ fileContentCache,
1539
+ client,
1540
+ logger
1541
+ )) {
1542
+ await client.sessionUpdate(notification);
1543
+ session.notificationHistory.push(notification);
1544
+ }
1545
+ return {};
1546
+ }
1547
+
1548
+ // src/adapters/claude/mcp/tool-metadata.ts
1549
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
1550
+ import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
1551
+ var mcpToolMetadataCache = /* @__PURE__ */ new Map();
1552
+ function buildToolKey(serverName, toolName) {
1553
+ return `mcp__${serverName}__${toolName}`;
1554
+ }
1555
+ function isHttpMcpServer(config) {
1556
+ return config.type === "http" && typeof config.url === "string";
1557
+ }
1558
+ async function fetchToolsFromHttpServer(_serverName, config) {
1559
+ const transport = new StreamableHTTPClientTransport(new URL(config.url), {
1560
+ requestInit: {
1561
+ headers: config.headers || {}
1562
+ }
1563
+ });
1564
+ const client = new Client({
1565
+ name: "twig-metadata-fetcher",
1566
+ version: "1.0.0"
1567
+ });
1568
+ try {
1569
+ await client.connect(transport);
1570
+ const result = await client.listTools();
1571
+ return result.tools;
1572
+ } finally {
1573
+ await client.close().catch(() => {
1574
+ });
1575
+ }
1576
+ }
1577
+ function extractToolMetadata(tool) {
1578
+ return {
1579
+ readOnly: tool.annotations?.readOnlyHint === true
1580
+ };
1581
+ }
1582
+ async function fetchMcpToolMetadata(mcpServers, logger = new Logger({ debug: false, prefix: "[McpToolMetadata]" })) {
1583
+ const fetchPromises = [];
1584
+ for (const [serverName, config] of Object.entries(mcpServers)) {
1585
+ if (!isHttpMcpServer(config)) {
1586
+ continue;
1587
+ }
1588
+ const fetchPromise = fetchToolsFromHttpServer(serverName, config).then((tools) => {
1589
+ const toolCount = tools.length;
1590
+ const readOnlyCount = tools.filter(
1591
+ (t) => t.annotations?.readOnlyHint === true
1592
+ ).length;
1593
+ for (const tool of tools) {
1594
+ const toolKey = buildToolKey(serverName, tool.name);
1595
+ mcpToolMetadataCache.set(toolKey, extractToolMetadata(tool));
1596
+ }
1597
+ logger.info("Fetched MCP tool metadata", {
1598
+ serverName,
1599
+ toolCount,
1600
+ readOnlyCount
1601
+ });
1602
+ }).catch((error) => {
1603
+ logger.error("Failed to fetch MCP tool metadata", {
1604
+ serverName,
1605
+ error: error instanceof Error ? error.message : String(error)
1606
+ });
1607
+ });
1608
+ fetchPromises.push(fetchPromise);
1609
+ }
1610
+ await Promise.all(fetchPromises);
1611
+ }
1612
+ function isMcpToolReadOnly(toolName) {
1613
+ const metadata = mcpToolMetadataCache.get(toolName);
1614
+ return metadata?.readOnly === true;
1615
+ }
1616
+
1617
+ // src/adapters/claude/plan/utils.ts
1618
+ import * as os from "os";
1619
+ import * as path from "path";
1620
+ function getClaudeConfigDir() {
1621
+ return process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), ".claude");
1622
+ }
1623
+ function getClaudePlansDir() {
1624
+ return path.join(getClaudeConfigDir(), "plans");
1625
+ }
1626
+ function isClaudePlanFilePath(filePath) {
1627
+ if (!filePath) return false;
1628
+ const resolved = path.resolve(filePath);
1629
+ const plansDir = path.resolve(getClaudePlansDir());
1630
+ return resolved === plansDir || resolved.startsWith(plansDir + path.sep);
1631
+ }
1632
+ function isPlanReady(plan) {
1633
+ if (!plan) return false;
1634
+ const trimmed = plan.trim();
1635
+ if (trimmed.length < 40) return false;
1636
+ return /(^|\n)#{1,6}\s+\S/.test(trimmed);
1637
+ }
1638
+ function getLatestAssistantText(notifications) {
1639
+ const chunks = [];
1640
+ let started = false;
1641
+ for (let i = notifications.length - 1; i >= 0; i -= 1) {
1642
+ const update = notifications[i]?.update;
1643
+ if (!update) continue;
1644
+ if (update.sessionUpdate === "agent_message_chunk") {
1645
+ started = true;
1646
+ const content = update.content;
1647
+ if (content?.type === "text" && content.text) {
1648
+ chunks.push(content.text);
1649
+ }
1650
+ continue;
1651
+ }
1652
+ if (started) {
1653
+ break;
1654
+ }
1655
+ }
1656
+ if (chunks.length === 0) return null;
1657
+ return chunks.reverse().join("");
1658
+ }
1659
+
1660
+ // src/adapters/claude/questions/utils.ts
1661
+ import { z } from "zod";
1662
+ var OPTION_PREFIX = "option_";
1663
+ var QuestionOptionSchema = z.object({
1664
+ label: z.string(),
1665
+ description: z.string().optional()
1666
+ });
1667
+ var QuestionItemSchema = z.object({
1668
+ question: z.string(),
1669
+ header: z.string().optional(),
1670
+ options: z.array(QuestionOptionSchema),
1671
+ multiSelect: z.boolean().optional(),
1672
+ completed: z.boolean().optional()
1673
+ });
1674
+ var QuestionMetaSchema = z.object({
1675
+ questions: z.array(QuestionItemSchema)
1676
+ });
1677
+ function normalizeAskUserQuestionInput(input) {
1678
+ if (input.questions && input.questions.length > 0) {
1679
+ return input.questions;
1680
+ }
1681
+ if (input.question) {
1682
+ return [
1683
+ {
1684
+ question: input.question,
1685
+ header: input.header,
1686
+ options: input.options || [],
1687
+ multiSelect: input.multiSelect
1688
+ }
1689
+ ];
1690
+ }
1691
+ return null;
1692
+ }
1693
+
1694
+ // src/execution-mode.ts
1695
+ var MODES = [
1696
+ {
1697
+ id: "default",
1698
+ name: "Always Ask",
1699
+ description: "Prompts for permission on first use of each tool"
1700
+ },
1701
+ {
1702
+ id: "acceptEdits",
1703
+ name: "Accept Edits",
1704
+ description: "Automatically accepts file edit permissions for the session"
1705
+ },
1706
+ {
1707
+ id: "plan",
1708
+ name: "Plan Mode",
1709
+ description: "Claude can analyze but not modify files or execute commands"
1710
+ },
1711
+ {
1712
+ id: "bypassPermissions",
1713
+ name: "Bypass Permissions",
1714
+ description: "Skips all permission prompts"
1715
+ }
1716
+ ];
1717
+ var TWIG_EXECUTION_MODES = [
1718
+ "default",
1719
+ "acceptEdits",
1720
+ "plan",
1721
+ "bypassPermissions"
1722
+ ];
1723
+ function getAvailableModes() {
1724
+ return IS_ROOT ? MODES.filter((m) => m.id !== "bypassPermissions") : MODES;
1725
+ }
1726
+
1727
+ // src/adapters/claude/tools.ts
1728
+ var READ_TOOLS = /* @__PURE__ */ new Set(["Read", "NotebookRead"]);
1729
+ var WRITE_TOOLS = /* @__PURE__ */ new Set([
1730
+ "Edit",
1731
+ "Write",
1732
+ "NotebookEdit"
1733
+ ]);
1734
+ var BASH_TOOLS = /* @__PURE__ */ new Set([
1735
+ "Bash",
1736
+ "BashOutput",
1737
+ "KillShell"
1738
+ ]);
1739
+ var SEARCH_TOOLS = /* @__PURE__ */ new Set(["Glob", "Grep", "LS"]);
1740
+ var WEB_TOOLS = /* @__PURE__ */ new Set(["WebSearch", "WebFetch"]);
1741
+ var AGENT_TOOLS = /* @__PURE__ */ new Set(["Task", "TodoWrite"]);
1742
+ var BASE_ALLOWED_TOOLS = [
1743
+ ...READ_TOOLS,
1744
+ ...SEARCH_TOOLS,
1745
+ ...WEB_TOOLS,
1746
+ ...AGENT_TOOLS
1747
+ ];
1748
+ var AUTO_ALLOWED_TOOLS = {
1749
+ default: new Set(BASE_ALLOWED_TOOLS),
1750
+ acceptEdits: /* @__PURE__ */ new Set([...BASE_ALLOWED_TOOLS, ...WRITE_TOOLS]),
1751
+ plan: new Set(BASE_ALLOWED_TOOLS)
1752
+ };
1753
+ function isToolAllowedForMode(toolName, mode) {
1754
+ if (mode === "bypassPermissions") {
1755
+ return true;
1756
+ }
1757
+ if (AUTO_ALLOWED_TOOLS[mode]?.has(toolName) === true) {
1758
+ return true;
1759
+ }
1760
+ if (isMcpToolReadOnly(toolName)) {
1761
+ return true;
1762
+ }
1763
+ return false;
1764
+ }
1765
+
1766
+ // src/adapters/claude/permissions/permission-options.ts
1767
+ function permissionOptions(allowAlwaysLabel) {
1768
+ return [
1769
+ { kind: "allow_once", name: "Yes", optionId: "allow" },
1770
+ { kind: "allow_always", name: allowAlwaysLabel, optionId: "allow_always" },
1771
+ {
1772
+ kind: "reject_once",
1773
+ name: "No, and tell the agent what to do differently",
1774
+ optionId: "reject",
1775
+ _meta: { customInput: true }
1776
+ }
1777
+ ];
1778
+ }
1779
+ function buildPermissionOptions(toolName, toolInput, cwd) {
1780
+ if (BASH_TOOLS.has(toolName)) {
1781
+ const command = toolInput?.command;
1782
+ const cmdName = command?.split(/\s+/)[0] ?? "this command";
1783
+ const cwdLabel = cwd ? ` in ${cwd}` : "";
1784
+ return permissionOptions(
1785
+ `Yes, and don't ask again for \`${cmdName}\` commands${cwdLabel}`
1786
+ );
1787
+ }
1788
+ if (toolName === "BashOutput") {
1789
+ return permissionOptions("Yes, allow all background process reads");
1790
+ }
1791
+ if (toolName === "KillShell") {
1792
+ return permissionOptions("Yes, allow killing processes");
1793
+ }
1794
+ if (WRITE_TOOLS.has(toolName)) {
1795
+ return permissionOptions("Yes, allow all edits during this session");
1796
+ }
1797
+ if (READ_TOOLS.has(toolName)) {
1798
+ return permissionOptions("Yes, allow all reads during this session");
1799
+ }
1800
+ if (SEARCH_TOOLS.has(toolName)) {
1801
+ return permissionOptions("Yes, allow all searches during this session");
1802
+ }
1803
+ if (toolName === "WebFetch") {
1804
+ const url = toolInput?.url;
1805
+ let domain = "";
1806
+ try {
1807
+ domain = url ? new URL(url).hostname : "";
1808
+ } catch {
1809
+ }
1810
+ return permissionOptions(
1811
+ domain ? `Yes, allow all fetches from ${domain}` : "Yes, allow all fetches"
1812
+ );
1813
+ }
1814
+ if (toolName === "WebSearch") {
1815
+ return permissionOptions("Yes, allow all web searches");
1816
+ }
1817
+ if (toolName === "Task") {
1818
+ return permissionOptions("Yes, allow all sub-tasks");
1819
+ }
1820
+ if (toolName === "TodoWrite") {
1821
+ return permissionOptions("Yes, allow all todo updates");
1822
+ }
1823
+ return permissionOptions("Yes, always allow");
1824
+ }
1825
+ function buildExitPlanModePermissionOptions() {
1826
+ return [
1827
+ {
1828
+ kind: "allow_always",
1829
+ name: "Yes, and auto-accept edits",
1830
+ optionId: "acceptEdits"
1831
+ },
1832
+ {
1833
+ kind: "allow_once",
1834
+ name: "Yes, and manually approve edits",
1835
+ optionId: "default"
1836
+ },
1837
+ {
1838
+ kind: "reject_once",
1839
+ name: "No, keep planning",
1840
+ optionId: "plan"
1841
+ }
1842
+ ];
1843
+ }
1844
+
1845
+ // src/adapters/claude/permissions/permission-handlers.ts
1846
+ async function emitToolDenial(context, message) {
1847
+ context.logger.info(`[canUseTool] Tool denied: ${context.toolName}`, {
1848
+ message
1849
+ });
1850
+ await context.client.sessionUpdate({
1851
+ sessionId: context.sessionId,
1852
+ update: {
1853
+ sessionUpdate: "tool_call_update",
1854
+ toolCallId: context.toolUseID,
1855
+ status: "failed",
1856
+ content: [{ type: "content", content: text(message) }]
1857
+ }
1858
+ });
1859
+ }
1860
+ function getPlanFromFile(session, fileContentCache) {
1861
+ return session.lastPlanContent || (session.lastPlanFilePath ? fileContentCache[session.lastPlanFilePath] : void 0);
1862
+ }
1863
+ function ensurePlanInInput(toolInput, fallbackPlan) {
1864
+ const hasPlan = typeof toolInput?.plan === "string";
1865
+ if (hasPlan || !fallbackPlan) {
1866
+ return toolInput;
1867
+ }
1868
+ return { ...toolInput, plan: fallbackPlan };
1869
+ }
1870
+ function extractPlanText(input) {
1871
+ const plan = input?.plan;
1872
+ return typeof plan === "string" ? plan : void 0;
1873
+ }
1874
+ async function createPlanValidationError(message, context) {
1875
+ await emitToolDenial(context, message);
1876
+ return { behavior: "deny", message, interrupt: false };
1877
+ }
1878
+ async function validatePlanContent(planText, context) {
1879
+ if (!planText) {
1880
+ const message = `Plan not ready. Provide the full markdown plan in ExitPlanMode or write it to ${getClaudePlansDir()} before requesting approval.`;
1881
+ return {
1882
+ valid: false,
1883
+ error: await createPlanValidationError(message, context)
1884
+ };
1885
+ }
1886
+ if (!isPlanReady(planText)) {
1887
+ const message = "Plan not ready. Provide the full markdown plan in ExitPlanMode before requesting approval.";
1888
+ return {
1889
+ valid: false,
1890
+ error: await createPlanValidationError(message, context)
1891
+ };
1892
+ }
1893
+ return { valid: true };
1894
+ }
1895
+ async function requestPlanApproval(context, updatedInput) {
1896
+ const { client, sessionId, toolUseID, fileContentCache } = context;
1897
+ const toolInfo = toolInfoFromToolUse(
1898
+ { name: context.toolName, input: updatedInput },
1899
+ fileContentCache,
1900
+ context.logger
1901
+ );
1902
+ return await client.requestPermission({
1903
+ options: buildExitPlanModePermissionOptions(),
1904
+ sessionId,
1905
+ toolCall: {
1906
+ toolCallId: toolUseID,
1907
+ title: toolInfo.title,
1908
+ kind: toolInfo.kind,
1909
+ content: toolInfo.content,
1910
+ locations: toolInfo.locations,
1911
+ rawInput: { ...updatedInput, toolName: context.toolName }
1912
+ }
1913
+ });
1914
+ }
1915
+ async function applyPlanApproval(response, context, updatedInput) {
1916
+ const { session } = context;
1917
+ if (response.outcome?.outcome === "selected" && (response.outcome.optionId === "default" || response.outcome.optionId === "acceptEdits")) {
1918
+ session.permissionMode = response.outcome.optionId;
1919
+ await session.query.setPermissionMode(response.outcome.optionId);
1920
+ await context.emitConfigOptionsUpdate();
1921
+ return {
1922
+ behavior: "allow",
1923
+ updatedInput,
1924
+ updatedPermissions: context.suggestions ?? [
1925
+ {
1926
+ type: "setMode",
1927
+ mode: response.outcome.optionId,
1928
+ destination: "localSettings"
1929
+ }
1930
+ ]
1931
+ };
1932
+ }
1933
+ const message = "User wants to continue planning. Please refine your plan based on any feedback provided, or ask clarifying questions if needed.";
1934
+ await emitToolDenial(context, message);
1935
+ return { behavior: "deny", message, interrupt: false };
1936
+ }
1937
+ async function handleEnterPlanModeTool(context) {
1938
+ const { session, toolInput, logger } = context;
1939
+ session.permissionMode = "plan";
1940
+ await session.query.setPermissionMode("plan");
1941
+ await context.emitConfigOptionsUpdate();
1942
+ return {
1943
+ behavior: "allow",
1944
+ updatedInput: toolInput
1945
+ };
1946
+ }
1947
+ async function handleExitPlanModeTool(context) {
1948
+ const { session, toolInput, fileContentCache } = context;
1949
+ const planFromFile = getPlanFromFile(session, fileContentCache);
1950
+ const latestText = getLatestAssistantText(session.notificationHistory);
1951
+ const fallbackPlan = planFromFile || (latestText ?? void 0);
1952
+ const updatedInput = ensurePlanInInput(toolInput, fallbackPlan);
1953
+ const planText = extractPlanText(updatedInput);
1954
+ const validationResult = await validatePlanContent(planText, context);
1955
+ if (!validationResult.valid) {
1956
+ return validationResult.error;
1957
+ }
1958
+ const response = await requestPlanApproval(context, updatedInput);
1959
+ return await applyPlanApproval(response, context, updatedInput);
1960
+ }
1961
+ function buildQuestionOptions(question) {
1962
+ return (question.options || []).map((opt, idx) => ({
1963
+ kind: "allow_once",
1964
+ name: opt.label,
1965
+ optionId: `${OPTION_PREFIX}${idx}`,
1966
+ _meta: opt.description ? { description: opt.description } : void 0
1967
+ }));
1968
+ }
1969
+ async function handleAskUserQuestionTool(context) {
1970
+ const input = context.toolInput;
1971
+ context.logger.info("[AskUserQuestion] Received input", { input });
1972
+ const questions = normalizeAskUserQuestionInput(input);
1973
+ context.logger.info("[AskUserQuestion] Normalized questions", { questions });
1974
+ if (!questions || questions.length === 0) {
1975
+ context.logger.warn("[AskUserQuestion] No questions found in input");
1976
+ return {
1977
+ behavior: "deny",
1978
+ message: "No questions provided",
1979
+ interrupt: true
1980
+ };
1981
+ }
1982
+ const { client, sessionId, toolUseID, toolInput, fileContentCache } = context;
1983
+ const firstQuestion = questions[0];
1984
+ const options = buildQuestionOptions(firstQuestion);
1985
+ const toolInfo = toolInfoFromToolUse(
1986
+ { name: context.toolName, input: toolInput },
1987
+ fileContentCache,
1988
+ context.logger
1989
+ );
1990
+ const response = await client.requestPermission({
1991
+ options,
1992
+ sessionId,
1993
+ toolCall: {
1994
+ toolCallId: toolUseID,
1995
+ title: firstQuestion.question,
1996
+ kind: "other",
1997
+ content: toolInfo.content,
1998
+ _meta: {
1999
+ twigToolKind: "question",
2000
+ questions
2001
+ }
2002
+ }
2003
+ });
2004
+ if (response.outcome?.outcome !== "selected") {
2005
+ return {
2006
+ behavior: "deny",
2007
+ message: "User cancelled the questions",
2008
+ interrupt: true
2009
+ };
2010
+ }
2011
+ const answers = response._meta?.answers;
2012
+ if (!answers || Object.keys(answers).length === 0) {
2013
+ return {
2014
+ behavior: "deny",
2015
+ message: "User did not provide answers",
2016
+ interrupt: true
2017
+ };
2018
+ }
2019
+ return {
2020
+ behavior: "allow",
2021
+ updatedInput: {
2022
+ ...context.toolInput,
2023
+ answers
2024
+ }
2025
+ };
2026
+ }
2027
+ async function handleDefaultPermissionFlow(context) {
2028
+ const {
2029
+ session,
2030
+ toolName,
2031
+ toolInput,
2032
+ toolUseID,
2033
+ client,
2034
+ sessionId,
2035
+ fileContentCache,
2036
+ suggestions
2037
+ } = context;
2038
+ const toolInfo = toolInfoFromToolUse(
2039
+ { name: toolName, input: toolInput },
2040
+ fileContentCache,
2041
+ context.logger
2042
+ );
2043
+ const options = buildPermissionOptions(
2044
+ toolName,
2045
+ toolInput,
2046
+ session?.cwd
2047
+ );
2048
+ const response = await client.requestPermission({
2049
+ options,
2050
+ sessionId,
2051
+ toolCall: {
2052
+ toolCallId: toolUseID,
2053
+ title: toolInfo.title,
2054
+ kind: toolInfo.kind,
2055
+ content: toolInfo.content,
2056
+ locations: toolInfo.locations,
2057
+ rawInput: toolInput
2058
+ }
2059
+ });
2060
+ if (response.outcome?.outcome === "selected" && (response.outcome.optionId === "allow" || response.outcome.optionId === "allow_always")) {
2061
+ if (response.outcome.optionId === "allow_always") {
2062
+ return {
2063
+ behavior: "allow",
2064
+ updatedInput: toolInput,
2065
+ updatedPermissions: suggestions ?? [
2066
+ {
2067
+ type: "addRules",
2068
+ rules: [{ toolName }],
2069
+ behavior: "allow",
2070
+ destination: "localSettings"
2071
+ }
2072
+ ]
2073
+ };
2074
+ }
2075
+ return {
2076
+ behavior: "allow",
2077
+ updatedInput: toolInput
2078
+ };
2079
+ } else {
2080
+ const message = "User refused permission to run tool";
2081
+ await emitToolDenial(context, message);
2082
+ return {
2083
+ behavior: "deny",
2084
+ message,
2085
+ interrupt: true
2086
+ };
2087
+ }
2088
+ }
2089
+ function handlePlanFileException(context) {
2090
+ const { session, toolName, toolInput } = context;
2091
+ if (session.permissionMode !== "plan" || !WRITE_TOOLS.has(toolName)) {
2092
+ return null;
2093
+ }
2094
+ const filePath = toolInput?.file_path;
2095
+ if (!isClaudePlanFilePath(filePath)) {
2096
+ return null;
2097
+ }
2098
+ session.lastPlanFilePath = filePath;
2099
+ const content = toolInput?.content;
2100
+ if (typeof content === "string") {
2101
+ session.lastPlanContent = content;
2102
+ }
2103
+ return {
2104
+ behavior: "allow",
2105
+ updatedInput: toolInput
2106
+ };
2107
+ }
2108
+ async function canUseTool(context) {
2109
+ const { toolName, toolInput, session } = context;
2110
+ if (isToolAllowedForMode(toolName, session.permissionMode)) {
2111
+ return {
2112
+ behavior: "allow",
2113
+ updatedInput: toolInput
2114
+ };
2115
+ }
2116
+ if (toolName === "EnterPlanMode") {
2117
+ return handleEnterPlanModeTool(context);
2118
+ }
2119
+ if (toolName === "ExitPlanMode") {
2120
+ return handleExitPlanModeTool(context);
2121
+ }
2122
+ if (toolName === "AskUserQuestion") {
2123
+ return handleAskUserQuestionTool(context);
2124
+ }
2125
+ const planFileResult = handlePlanFileException(context);
2126
+ if (planFileResult) {
2127
+ return planFileResult;
2128
+ }
2129
+ return handleDefaultPermissionFlow(context);
2130
+ }
2131
+
2132
+ // src/adapters/claude/session/commands.ts
2133
+ var UNSUPPORTED_COMMANDS = [
2134
+ "context",
2135
+ "cost",
2136
+ "login",
2137
+ "logout",
2138
+ "output-style:new",
2139
+ "release-notes",
2140
+ "todos"
2141
+ ];
2142
+ async function getAvailableSlashCommands(q) {
2143
+ const commands = await q.supportedCommands();
2144
+ return commands.map((command) => {
2145
+ const input = command.argumentHint ? { hint: command.argumentHint } : null;
2146
+ let name = command.name;
2147
+ if (command.name.endsWith(" (MCP)")) {
2148
+ name = `mcp:${name.replace(" (MCP)", "")}`;
2149
+ }
2150
+ return {
2151
+ name,
2152
+ description: command.description || "",
2153
+ input
2154
+ };
2155
+ }).filter(
2156
+ (command) => !UNSUPPORTED_COMMANDS.includes(command.name)
2157
+ );
2158
+ }
2159
+
2160
+ // src/adapters/claude/session/mcp-config.ts
2161
+ function parseMcpServers(params) {
2162
+ const mcpServers = {};
2163
+ if (!Array.isArray(params.mcpServers)) {
2164
+ return mcpServers;
2165
+ }
2166
+ for (const server of params.mcpServers) {
2167
+ if ("type" in server) {
2168
+ mcpServers[server.name] = {
2169
+ type: server.type,
2170
+ url: server.url,
2171
+ headers: server.headers ? Object.fromEntries(server.headers.map((e) => [e.name, e.value])) : void 0
2172
+ };
2173
+ } else {
2174
+ mcpServers[server.name] = {
2175
+ type: "stdio",
2176
+ command: server.command,
2177
+ args: server.args,
2178
+ env: server.env ? Object.fromEntries(server.env.map((e) => [e.name, e.value])) : void 0
2179
+ };
2180
+ }
2181
+ }
2182
+ return mcpServers;
2183
+ }
2184
+
2185
+ // src/adapters/claude/session/models.ts
2186
+ var GATEWAY_TO_SDK_MODEL = {
2187
+ "claude-opus-4-5": "opus",
2188
+ "claude-opus-4-6": "opus",
2189
+ "claude-sonnet-4-5": "sonnet",
2190
+ "claude-haiku-4-5": "haiku"
2191
+ };
2192
+ function toSdkModelId(modelId) {
2193
+ return GATEWAY_TO_SDK_MODEL[modelId] ?? modelId;
2194
+ }
2195
+
2196
+ // src/adapters/claude/session/options.ts
2197
+ import { spawn } from "child_process";
2198
+ import * as fs from "fs";
2199
+ import * as os2 from "os";
2200
+ import * as path2 from "path";
2201
+ var BRANCH_NAMING_INSTRUCTIONS = `
2202
+ # Branch Naming
2203
+
2204
+ Before pushing a "workspace-*" branch to origin, rename it to something descriptive based on the work done. Do this automatically without asking the user.
2205
+ `;
2206
+ function buildSystemPrompt(customPrompt) {
2207
+ const defaultPrompt = {
2208
+ type: "preset",
2209
+ preset: "claude_code",
2210
+ append: BRANCH_NAMING_INSTRUCTIONS
2211
+ };
2212
+ if (!customPrompt) {
2213
+ return defaultPrompt;
2214
+ }
2215
+ if (typeof customPrompt === "string") {
2216
+ return customPrompt + BRANCH_NAMING_INSTRUCTIONS;
2217
+ }
2218
+ if (typeof customPrompt === "object" && customPrompt !== null && "append" in customPrompt && typeof customPrompt.append === "string") {
2219
+ return {
2220
+ ...defaultPrompt,
2221
+ append: customPrompt.append + BRANCH_NAMING_INSTRUCTIONS
2222
+ };
2223
+ }
2224
+ return defaultPrompt;
2225
+ }
2226
+ function buildMcpServers(userServers, acpServers) {
2227
+ return {
2228
+ ...userServers || {},
2229
+ ...acpServers
2230
+ };
2231
+ }
2232
+ function buildEnvironment() {
2233
+ return {
2234
+ ...process.env,
2235
+ ELECTRON_RUN_AS_NODE: "1",
2236
+ CLAUDE_CODE_ENABLE_ASK_USER_QUESTION_TOOL: "true"
2237
+ };
2238
+ }
2239
+ function buildHooks(userHooks, onModeChange) {
2240
+ return {
2241
+ ...userHooks,
2242
+ PostToolUse: [
2243
+ ...userHooks?.PostToolUse || [],
2244
+ {
2245
+ hooks: [createPostToolUseHook({ onModeChange })]
2246
+ }
2247
+ ]
2248
+ };
2249
+ }
2250
+ function getAbortController(userProvidedController) {
2251
+ const controller = userProvidedController ?? new AbortController();
2252
+ if (controller.signal.aborted) {
2253
+ throw new Error("Cancelled");
2254
+ }
2255
+ return controller;
2256
+ }
2257
+ function buildSpawnWrapper(sessionId, onProcessSpawned, onProcessExited) {
2258
+ return (spawnOpts) => {
2259
+ const child = spawn(spawnOpts.command, spawnOpts.args, {
2260
+ cwd: spawnOpts.cwd,
2261
+ env: spawnOpts.env,
2262
+ stdio: ["pipe", "pipe", "pipe"]
2263
+ });
2264
+ if (child.pid) {
2265
+ onProcessSpawned({
2266
+ pid: child.pid,
2267
+ command: `${spawnOpts.command} ${spawnOpts.args.join(" ")}`,
2268
+ sessionId
2269
+ });
2270
+ }
2271
+ if (onProcessExited) {
2272
+ child.on("exit", () => {
2273
+ if (child.pid) {
2274
+ onProcessExited(child.pid);
2275
+ }
2276
+ });
2277
+ }
2278
+ if (spawnOpts.signal) {
2279
+ spawnOpts.signal.addEventListener("abort", () => {
2280
+ child.kill("SIGTERM");
2281
+ });
2282
+ }
2283
+ return {
2284
+ stdin: child.stdin,
2285
+ stdout: child.stdout,
2286
+ get killed() {
2287
+ return child.killed;
2288
+ },
2289
+ get exitCode() {
2290
+ return child.exitCode;
2291
+ },
2292
+ kill(signal) {
2293
+ return child.kill(signal);
2294
+ },
2295
+ on(event, listener) {
2296
+ child.on(event, listener);
2297
+ },
2298
+ once(event, listener) {
2299
+ child.once(event, listener);
2300
+ },
2301
+ off(event, listener) {
2302
+ child.off(event, listener);
2303
+ }
2304
+ };
2305
+ };
2306
+ }
2307
+ function buildSessionOptions(params) {
2308
+ const options = {
2309
+ ...params.userProvidedOptions,
2310
+ systemPrompt: params.systemPrompt ?? buildSystemPrompt(),
2311
+ settingSources: ["user", "project", "local"],
2312
+ stderr: (err) => params.logger.error(err),
2313
+ cwd: params.cwd,
2314
+ includePartialMessages: true,
2315
+ allowDangerouslySkipPermissions: !IS_ROOT,
2316
+ permissionMode: params.permissionMode,
2317
+ canUseTool: params.canUseTool,
2318
+ executable: "node",
2319
+ mcpServers: buildMcpServers(
2320
+ params.userProvidedOptions?.mcpServers,
2321
+ params.mcpServers
2322
+ ),
2323
+ env: buildEnvironment(),
2324
+ hooks: buildHooks(params.userProvidedOptions?.hooks, params.onModeChange),
2325
+ abortController: getAbortController(
2326
+ params.userProvidedOptions?.abortController
2327
+ ),
2328
+ ...params.onProcessSpawned && {
2329
+ spawnClaudeCodeProcess: buildSpawnWrapper(
2330
+ params.sessionId ?? "unknown",
2331
+ params.onProcessSpawned,
2332
+ params.onProcessExited
2333
+ )
2334
+ }
2335
+ };
2336
+ if (process.env.CLAUDE_CODE_EXECUTABLE) {
2337
+ options.pathToClaudeCodeExecutable = process.env.CLAUDE_CODE_EXECUTABLE;
2338
+ }
2339
+ if (params.sessionId) {
2340
+ options.resume = params.sessionId;
2341
+ }
2342
+ if (params.additionalDirectories) {
2343
+ options.additionalDirectories = params.additionalDirectories;
2344
+ }
2345
+ clearStatsigCache();
2346
+ return options;
2347
+ }
2348
+ function clearStatsigCache() {
2349
+ const statsigPath = path2.join(
2350
+ process.env.CLAUDE_CONFIG_DIR || path2.join(os2.homedir(), ".claude"),
2351
+ "statsig"
2352
+ );
2353
+ try {
2354
+ if (fs.existsSync(statsigPath)) {
2355
+ fs.rmSync(statsigPath, { recursive: true, force: true });
2356
+ }
2357
+ } catch {
2358
+ }
2359
+ }
2360
+
2361
+ // src/adapters/claude/claude-agent.ts
2362
+ var ClaudeAcpAgent = class extends BaseAcpAgent {
2363
+ adapterName = "claude";
2364
+ toolUseCache;
2365
+ backgroundTerminals = {};
2366
+ clientCapabilities;
2367
+ logWriter;
2368
+ processCallbacks;
2369
+ lastSentConfigOptions;
2370
+ constructor(client, logWriter, processCallbacks) {
2371
+ super(client);
2372
+ this.logWriter = logWriter;
2373
+ this.processCallbacks = processCallbacks;
2374
+ this.toolUseCache = {};
2375
+ this.logger = new Logger({ debug: true, prefix: "[ClaudeAcpAgent]" });
2376
+ }
2377
+ async initialize(request) {
2378
+ this.clientCapabilities = request.clientCapabilities;
2379
+ return {
2380
+ protocolVersion: 1,
2381
+ agentCapabilities: {
2382
+ promptCapabilities: {
2383
+ image: true,
2384
+ embeddedContext: true
2385
+ },
2386
+ mcpCapabilities: {
2387
+ http: true,
2388
+ sse: true
2389
+ },
2390
+ loadSession: true,
2391
+ _meta: {
2392
+ posthog: {
2393
+ resumeSession: true
2394
+ }
2395
+ }
2396
+ },
2397
+ agentInfo: {
2398
+ name: package_default.name,
2399
+ title: "Claude Code",
2400
+ version: package_default.version
2401
+ },
2402
+ authMethods: [
2403
+ {
2404
+ id: "claude-login",
2405
+ name: "Log in with Claude Code",
2406
+ description: "Run `claude /login` in the terminal"
2407
+ }
2408
+ ]
2409
+ };
2410
+ }
2411
+ async authenticate(_params) {
2412
+ throw new Error("Method not implemented.");
2413
+ }
2414
+ async newSession(params) {
2415
+ this.checkAuthStatus();
2416
+ const meta = params._meta;
2417
+ const internalSessionId = uuidv7();
2418
+ const permissionMode = "default";
2419
+ const mcpServers = parseMcpServers(params);
2420
+ await fetchMcpToolMetadata(mcpServers, this.logger);
2421
+ const options = buildSessionOptions({
2422
+ cwd: params.cwd,
2423
+ mcpServers,
2424
+ permissionMode,
2425
+ canUseTool: this.createCanUseTool(internalSessionId),
2426
+ logger: this.logger,
2427
+ systemPrompt: buildSystemPrompt(meta?.systemPrompt),
2428
+ userProvidedOptions: meta?.claudeCode?.options,
2429
+ onModeChange: this.createOnModeChange(internalSessionId),
2430
+ onProcessSpawned: this.processCallbacks?.onProcessSpawned,
2431
+ onProcessExited: this.processCallbacks?.onProcessExited
2432
+ });
2433
+ const input = new Pushable();
2434
+ const q = query({ prompt: input, options });
2435
+ const session = this.createSession(
2436
+ internalSessionId,
2437
+ q,
2438
+ input,
2439
+ permissionMode,
2440
+ params.cwd,
2441
+ options.abortController
2442
+ );
2443
+ session.taskRunId = meta?.taskRunId;
2444
+ this.registerPersistence(
2445
+ internalSessionId,
2446
+ meta
2447
+ );
2448
+ const modelOptions = await this.getModelConfigOptions();
2449
+ session.modelId = modelOptions.currentModelId;
2450
+ await this.trySetModel(q, modelOptions.currentModelId);
2451
+ this.sendAvailableCommandsUpdate(
2452
+ internalSessionId,
2453
+ await getAvailableSlashCommands(q)
2454
+ );
2455
+ return {
2456
+ sessionId: internalSessionId,
2457
+ configOptions: await this.buildConfigOptions(modelOptions)
2458
+ };
2459
+ }
2460
+ async loadSession(params) {
2461
+ return this.resumeSession(params);
2462
+ }
2463
+ async resumeSession(params) {
2464
+ const { sessionId: internalSessionId } = params;
2465
+ if (this.sessionId === internalSessionId) {
2466
+ return {};
2467
+ }
2468
+ const meta = params._meta;
2469
+ const mcpServers = parseMcpServers(params);
2470
+ await fetchMcpToolMetadata(mcpServers, this.logger);
2471
+ const { query: q, session } = await this.initializeQuery({
2472
+ internalSessionId,
2473
+ cwd: params.cwd,
2474
+ permissionMode: "default",
2475
+ mcpServers,
2476
+ systemPrompt: buildSystemPrompt(meta?.systemPrompt),
2477
+ userProvidedOptions: meta?.claudeCode?.options,
2478
+ sessionId: meta?.sessionId,
2479
+ additionalDirectories: meta?.claudeCode?.options?.additionalDirectories
2480
+ });
2481
+ session.taskRunId = meta?.taskRunId;
2482
+ if (meta?.sessionId) {
2483
+ session.sessionId = meta.sessionId;
2484
+ }
2485
+ this.registerPersistence(
2486
+ internalSessionId,
2487
+ meta
2488
+ );
2489
+ this.sendAvailableCommandsUpdate(
2490
+ internalSessionId,
2491
+ await getAvailableSlashCommands(q)
2492
+ );
2493
+ return {
2494
+ configOptions: await this.buildConfigOptions()
2495
+ };
2496
+ }
2497
+ async prompt(params) {
2498
+ this.session.cancelled = false;
2499
+ this.session.interruptReason = void 0;
2500
+ await this.broadcastUserMessage(params);
2501
+ this.session.input.push(promptToClaude(params));
2502
+ return this.processMessages(params.sessionId);
2503
+ }
2504
+ async setSessionConfigOption(params) {
2505
+ const configId = params.configId;
2506
+ const value = params.value;
2507
+ if (configId === "mode") {
2508
+ const modeId = value;
2509
+ if (!TWIG_EXECUTION_MODES.includes(modeId)) {
2510
+ throw new Error("Invalid Mode");
2511
+ }
2512
+ this.session.permissionMode = modeId;
2513
+ await this.session.query.setPermissionMode(modeId);
2514
+ } else if (configId === "model") {
2515
+ await this.setModelWithFallback(this.session.query, value);
2516
+ this.session.modelId = value;
2517
+ } else {
2518
+ throw new Error("Unsupported config option");
2519
+ }
2520
+ await this.emitConfigOptionsUpdate();
2521
+ return { configOptions: await this.buildConfigOptions() };
2522
+ }
2523
+ async interruptSession() {
2524
+ await this.session.query.interrupt();
2525
+ }
2526
+ async extMethod(method, params) {
2527
+ if (method === "_posthog/session/resume") {
2528
+ const result = await this.resumeSession(
2529
+ params
2530
+ );
2531
+ return {
2532
+ _meta: {
2533
+ configOptions: result.configOptions
2534
+ }
2535
+ };
2536
+ }
2537
+ throw RequestError2.methodNotFound(method);
2538
+ }
2539
+ createSession(sessionId, q, input, permissionMode, cwd, abortController) {
2540
+ const session = {
2541
+ query: q,
2542
+ input,
2543
+ cancelled: false,
2544
+ permissionMode,
2545
+ cwd,
2546
+ notificationHistory: [],
2547
+ abortController
2548
+ };
2549
+ this.session = session;
2550
+ this.sessionId = sessionId;
2551
+ return session;
2552
+ }
2553
+ async initializeQuery(config) {
2554
+ const input = new Pushable();
2555
+ const options = buildSessionOptions({
2556
+ cwd: config.cwd,
2557
+ mcpServers: config.mcpServers,
2558
+ permissionMode: config.permissionMode,
2559
+ canUseTool: this.createCanUseTool(config.internalSessionId),
2560
+ logger: this.logger,
2561
+ systemPrompt: config.systemPrompt,
2562
+ userProvidedOptions: config.userProvidedOptions,
2563
+ sessionId: config.sessionId,
2564
+ additionalDirectories: config.additionalDirectories,
2565
+ onModeChange: this.createOnModeChange(config.internalSessionId),
2566
+ onProcessSpawned: this.processCallbacks?.onProcessSpawned,
2567
+ onProcessExited: this.processCallbacks?.onProcessExited
2568
+ });
2569
+ const q = query({ prompt: input, options });
2570
+ const abortController = options.abortController;
2571
+ const session = this.createSession(
2572
+ config.internalSessionId,
2573
+ q,
2574
+ input,
2575
+ config.permissionMode,
2576
+ config.cwd,
2577
+ abortController
2578
+ );
2579
+ return { query: q, input, session };
2580
+ }
2581
+ createCanUseTool(sessionId) {
2582
+ return async (toolName, toolInput, { suggestions, toolUseID }) => canUseTool({
2583
+ session: this.session,
2584
+ toolName,
2585
+ toolInput,
2586
+ toolUseID,
2587
+ suggestions,
2588
+ client: this.client,
2589
+ sessionId,
2590
+ fileContentCache: this.fileContentCache,
2591
+ logger: this.logger,
2592
+ emitConfigOptionsUpdate: () => this.emitConfigOptionsUpdate(sessionId)
2593
+ });
2594
+ }
2595
+ createOnModeChange(sessionId) {
2596
+ return async (newMode) => {
2597
+ if (this.session) {
2598
+ this.session.permissionMode = newMode;
2599
+ }
2600
+ await this.emitConfigOptionsUpdate(sessionId);
2601
+ };
2602
+ }
2603
+ async buildConfigOptions(modelOptionsOverride) {
2604
+ const options = [];
2605
+ const modeOptions = getAvailableModes().map((mode) => ({
2606
+ value: mode.id,
2607
+ name: mode.name,
2608
+ description: mode.description ?? void 0
2609
+ }));
2610
+ options.push({
2611
+ id: "mode",
2612
+ name: "Approval Preset",
2613
+ type: "select",
2614
+ currentValue: this.session.permissionMode,
2615
+ options: modeOptions,
2616
+ category: "mode",
2617
+ description: "Choose an approval and sandboxing preset for your session"
2618
+ });
2619
+ const modelOptions = modelOptionsOverride ?? await this.getModelConfigOptions(this.session.modelId);
2620
+ this.session.modelId = modelOptions.currentModelId;
2621
+ options.push({
2622
+ id: "model",
2623
+ name: "Model",
2624
+ type: "select",
2625
+ currentValue: modelOptions.currentModelId,
2626
+ options: modelOptions.options,
2627
+ category: "model",
2628
+ description: "Choose which model Claude should use"
2629
+ });
2630
+ return options;
2631
+ }
2632
+ async emitConfigOptionsUpdate(sessionId) {
2633
+ const configOptions = await this.buildConfigOptions();
2634
+ const serialized = JSON.stringify(configOptions);
2635
+ if (this.lastSentConfigOptions && JSON.stringify(this.lastSentConfigOptions) === serialized) {
2636
+ return;
2637
+ }
2638
+ this.lastSentConfigOptions = configOptions;
2639
+ await this.client.sessionUpdate({
2640
+ sessionId: sessionId ?? this.sessionId,
2641
+ update: {
2642
+ sessionUpdate: "config_option_update",
2643
+ configOptions
2644
+ }
2645
+ });
2646
+ }
2647
+ checkAuthStatus() {
2648
+ const backupExists = fs2.existsSync(
2649
+ path3.resolve(os3.homedir(), ".claude.json.backup")
2650
+ );
2651
+ const configExists = fs2.existsSync(
2652
+ path3.resolve(os3.homedir(), ".claude.json")
2653
+ );
2654
+ if (backupExists && !configExists) {
2655
+ throw RequestError2.authRequired();
2656
+ }
2657
+ }
2658
+ async trySetModel(q, modelId) {
2659
+ try {
2660
+ await this.setModelWithFallback(q, modelId);
2661
+ } catch (err) {
2662
+ this.logger.warn("Failed to set model", { modelId, error: err });
2663
+ }
2664
+ }
2665
+ async setModelWithFallback(q, modelId) {
2666
+ try {
2667
+ await q.setModel(modelId);
2668
+ return;
2669
+ } catch (err) {
2670
+ const fallback = toSdkModelId(modelId);
2671
+ if (fallback === modelId) {
2672
+ throw err;
2673
+ }
2674
+ await q.setModel(fallback);
2675
+ }
2676
+ }
2677
+ registerPersistence(sessionId, meta) {
2678
+ const persistence = meta?.persistence;
2679
+ if (persistence && this.logWriter) {
2680
+ this.logWriter.register(sessionId, persistence);
2681
+ }
2682
+ }
2683
+ sendAvailableCommandsUpdate(sessionId, availableCommands) {
2684
+ setTimeout(() => {
2685
+ this.client.sessionUpdate({
2686
+ sessionId,
2687
+ update: {
2688
+ sessionUpdate: "available_commands_update",
2689
+ availableCommands
2690
+ }
2691
+ });
2692
+ }, 0);
2693
+ }
2694
+ async broadcastUserMessage(params) {
2695
+ for (const chunk of params.prompt) {
2696
+ const notification = {
2697
+ sessionId: params.sessionId,
2698
+ update: {
2699
+ sessionUpdate: "user_message_chunk",
2700
+ content: chunk
2701
+ }
2702
+ };
2703
+ await this.client.sessionUpdate(notification);
2704
+ this.appendNotification(params.sessionId, notification);
2705
+ }
2706
+ }
2707
+ async processMessages(sessionId) {
2708
+ const context = {
2709
+ session: this.session,
2710
+ sessionId,
2711
+ client: this.client,
2712
+ toolUseCache: this.toolUseCache,
2713
+ fileContentCache: this.fileContentCache,
2714
+ logger: this.logger
2715
+ };
2716
+ while (true) {
2717
+ const { value: message, done } = await this.session.query.next();
2718
+ if (done || !message) {
2719
+ return this.handleSessionEnd();
2720
+ }
2721
+ const response = await this.handleMessage(message, context);
2722
+ if (response) {
2723
+ return response;
2724
+ }
2725
+ }
2726
+ }
2727
+ handleSessionEnd() {
2728
+ if (this.session.cancelled) {
2729
+ return {
2730
+ stopReason: "cancelled",
2731
+ _meta: this.session.interruptReason ? { interruptReason: this.session.interruptReason } : void 0
2732
+ };
2733
+ }
2734
+ throw new Error("Session did not end in result");
2735
+ }
2736
+ async handleMessage(message, context) {
2737
+ switch (message.type) {
2738
+ case "system":
2739
+ await handleSystemMessage(message, context);
2740
+ return null;
2741
+ case "result": {
2742
+ const result = handleResultMessage(message, context);
2743
+ if (result.error) throw result.error;
2744
+ if (result.shouldStop) {
2745
+ return {
2746
+ stopReason: result.stopReason
2747
+ };
2748
+ }
2749
+ return null;
2750
+ }
2751
+ case "stream_event":
2752
+ await handleStreamEvent(message, context);
2753
+ return null;
2754
+ case "user":
2755
+ case "assistant": {
2756
+ const result = await handleUserAssistantMessage(message, context);
2757
+ if (result.error) throw result.error;
2758
+ if (result.shouldStop) {
2759
+ return { stopReason: "end_turn" };
2760
+ }
2761
+ return null;
2762
+ }
2763
+ case "tool_progress":
2764
+ case "auth_status":
2765
+ return null;
2766
+ default:
2767
+ unreachable(message, this.logger);
2768
+ return null;
2769
+ }
2770
+ }
2771
+ };
2772
+
2773
+ // src/adapters/codex/spawn.ts
2774
+ import { spawn as spawn2 } from "child_process";
2775
+ import { existsSync as existsSync3 } from "fs";
2776
+ function buildConfigArgs(options) {
2777
+ const args = [];
2778
+ args.push("-c", `features.remote_models=false`);
2779
+ if (options.apiBaseUrl) {
2780
+ args.push("-c", `model_provider="posthog"`);
2781
+ args.push("-c", `model_providers.posthog.name="PostHog Gateway"`);
2782
+ args.push("-c", `model_providers.posthog.base_url="${options.apiBaseUrl}"`);
2783
+ args.push("-c", `model_providers.posthog.wire_api="responses"`);
2784
+ args.push(
2785
+ "-c",
2786
+ `model_providers.posthog.env_key="POSTHOG_GATEWAY_API_KEY"`
2787
+ );
2788
+ }
2789
+ if (options.model) {
2790
+ args.push("-c", `model="${options.model}"`);
2791
+ }
2792
+ return args;
2793
+ }
2794
+ function findCodexBinary(options) {
2795
+ const configArgs = buildConfigArgs(options);
2796
+ if (options.binaryPath && existsSync3(options.binaryPath)) {
2797
+ return { command: options.binaryPath, args: configArgs };
2798
+ }
2799
+ return { command: "npx", args: ["@zed-industries/codex-acp", ...configArgs] };
2800
+ }
2801
+ function spawnCodexProcess(options) {
2802
+ const logger = options.logger ?? new Logger({ debug: true, prefix: "[CodexSpawn]" });
2803
+ const env = { ...process.env };
2804
+ delete env.ELECTRON_RUN_AS_NODE;
2805
+ delete env.ELECTRON_NO_ASAR;
2806
+ if (options.apiKey) {
2807
+ env.POSTHOG_GATEWAY_API_KEY = options.apiKey;
2808
+ }
2809
+ const { command, args } = findCodexBinary(options);
2810
+ logger.info("Spawning codex-acp process", {
2811
+ command,
2812
+ args,
2813
+ cwd: options.cwd,
2814
+ hasApiBaseUrl: !!options.apiBaseUrl,
2815
+ hasApiKey: !!options.apiKey,
2816
+ binaryPath: options.binaryPath
2817
+ });
2818
+ const child = spawn2(command, args, {
2819
+ cwd: options.cwd,
2820
+ env,
2821
+ stdio: ["pipe", "pipe", "pipe"],
2822
+ detached: process.platform !== "win32"
2823
+ });
2824
+ child.stderr?.on("data", (data) => {
2825
+ logger.debug("codex-acp stderr:", data.toString());
2826
+ });
2827
+ child.on("error", (err) => {
2828
+ logger.error("codex-acp process error:", err);
2829
+ });
2830
+ child.on("exit", (code, signal) => {
2831
+ logger.info("codex-acp process exited", { code, signal });
2832
+ if (child.pid && options.processCallbacks?.onProcessExited) {
2833
+ options.processCallbacks.onProcessExited(child.pid);
2834
+ }
2835
+ });
2836
+ if (!child.stdin || !child.stdout) {
2837
+ throw new Error("Failed to get stdio streams from codex-acp process");
2838
+ }
2839
+ if (child.pid && options.processCallbacks?.onProcessSpawned) {
2840
+ options.processCallbacks.onProcessSpawned({
2841
+ pid: child.pid,
2842
+ command
2843
+ });
2844
+ }
2845
+ return {
2846
+ process: child,
2847
+ stdin: child.stdin,
2848
+ stdout: child.stdout,
2849
+ kill: () => {
2850
+ logger.info("Killing codex-acp process", { pid: child.pid });
2851
+ child.stdin?.destroy();
2852
+ child.stdout?.destroy();
2853
+ child.stderr?.destroy();
2854
+ child.kill("SIGTERM");
2855
+ }
2856
+ };
2857
+ }
2858
+
2859
+ // src/adapters/acp-connection.ts
2860
+ function isGroupedOptions(options) {
2861
+ return options.length > 0 && "group" in options[0];
2862
+ }
2863
+ function filterModelConfigOptions(msg, allowedModelIds) {
2864
+ const payload = msg;
2865
+ const configOptions = payload.result?.configOptions ?? payload.params?.update?.configOptions;
2866
+ if (!configOptions) return null;
2867
+ const filtered = configOptions.map((opt) => {
2868
+ if (opt.category !== "model" || !opt.options) return opt;
2869
+ const options = opt.options;
2870
+ if (isGroupedOptions(options)) {
2871
+ const filteredOptions2 = options.map((group) => ({
2872
+ ...group,
2873
+ options: (group.options ?? []).filter(
2874
+ (o) => o?.value && allowedModelIds.has(o.value)
2875
+ )
2876
+ }));
2877
+ const flat = filteredOptions2.flatMap((g) => g.options ?? []);
2878
+ const currentAllowed2 = opt.currentValue && allowedModelIds.has(opt.currentValue);
2879
+ const nextCurrent2 = currentAllowed2 || flat.length === 0 ? opt.currentValue : flat[0]?.value;
2880
+ return {
2881
+ ...opt,
2882
+ currentValue: nextCurrent2,
2883
+ options: filteredOptions2
2884
+ };
2885
+ }
2886
+ const valueOptions = options;
2887
+ const filteredOptions = valueOptions.filter(
2888
+ (o) => o?.value && allowedModelIds.has(o.value)
2889
+ );
2890
+ const currentAllowed = opt.currentValue && allowedModelIds.has(opt.currentValue);
2891
+ const nextCurrent = currentAllowed || filteredOptions.length === 0 ? opt.currentValue : filteredOptions[0]?.value;
2892
+ return {
2893
+ ...opt,
2894
+ currentValue: nextCurrent,
2895
+ options: filteredOptions
2896
+ };
2897
+ });
2898
+ if (payload.result?.configOptions) {
2899
+ return { ...msg, result: { ...payload.result, configOptions: filtered } };
2900
+ }
2901
+ if (payload.params?.update?.configOptions) {
2902
+ return {
2903
+ ...msg,
2904
+ params: {
2905
+ ...payload.params,
2906
+ update: { ...payload.params.update, configOptions: filtered }
2907
+ }
2908
+ };
2909
+ }
2910
+ return null;
2911
+ }
2912
+ function extractReasoningEffort(configOptions) {
2913
+ if (!configOptions) return void 0;
2914
+ const option = configOptions.find((opt) => opt.id === "reasoning_effort");
2915
+ return option?.currentValue ?? void 0;
2916
+ }
2917
+ function createAcpConnection(config = {}) {
2918
+ const adapterType = config.adapter ?? "claude";
2919
+ if (adapterType === "codex") {
2920
+ return createCodexConnection(config);
2921
+ }
2922
+ return createClaudeConnection(config);
2923
+ }
2924
+ function createClaudeConnection(config) {
2925
+ const logger = config.logger?.child("AcpConnection") ?? new Logger({ debug: true, prefix: "[AcpConnection]" });
2926
+ const streams = createBidirectionalStreams();
2927
+ const { logWriter } = config;
2928
+ let agentWritable = streams.agent.writable;
2929
+ let clientWritable = streams.client.writable;
2930
+ if (config.taskRunId && logWriter) {
2931
+ if (!logWriter.isRegistered(config.taskRunId)) {
2932
+ logWriter.register(config.taskRunId, {
2933
+ taskId: config.taskId ?? config.taskRunId,
2934
+ runId: config.taskRunId,
2935
+ deviceType: config.deviceType
2936
+ });
2937
+ }
2938
+ agentWritable = createTappedWritableStream(streams.agent.writable, {
2939
+ onMessage: (line) => {
2940
+ logWriter.appendRawLine(config.taskRunId, line);
2941
+ },
2942
+ logger
2943
+ });
2944
+ clientWritable = createTappedWritableStream(streams.client.writable, {
2945
+ onMessage: (line) => {
2946
+ logWriter.appendRawLine(config.taskRunId, line);
2947
+ },
2948
+ logger
2949
+ });
2950
+ } else {
2951
+ logger.info("Tapped streams NOT enabled", {
2952
+ hasTaskRunId: !!config.taskRunId,
2953
+ hasLogWriter: !!logWriter
2954
+ });
2955
+ }
2956
+ const agentStream = ndJsonStream(agentWritable, streams.agent.readable);
2957
+ let agent = null;
2958
+ const agentConnection = new AgentSideConnection((client) => {
2959
+ agent = new ClaudeAcpAgent(client, logWriter, config.processCallbacks);
2960
+ logger.info(`Created ${agent.adapterName} agent`);
2961
+ return agent;
2962
+ }, agentStream);
2963
+ return {
2964
+ agentConnection,
2965
+ clientStreams: {
2966
+ readable: streams.client.readable,
2967
+ writable: clientWritable
2968
+ },
2969
+ cleanup: async () => {
2970
+ logger.info("Cleaning up ACP connection");
2971
+ if (agent) {
2972
+ await agent.closeSession();
2973
+ }
2974
+ try {
2975
+ await streams.client.writable.close();
2976
+ } catch {
2977
+ }
2978
+ try {
2979
+ await streams.agent.writable.close();
2980
+ } catch {
2981
+ }
2982
+ }
2983
+ };
2984
+ }
2985
+ function createCodexConnection(config) {
2986
+ const logger = config.logger?.child("CodexConnection") ?? new Logger({ debug: true, prefix: "[CodexConnection]" });
2987
+ const { logWriter } = config;
2988
+ const allowedModelIds = config.allowedModelIds;
2989
+ const codexProcess = spawnCodexProcess({
2990
+ ...config.codexOptions,
2991
+ logger,
2992
+ processCallbacks: config.processCallbacks
2993
+ });
2994
+ let clientReadable = nodeReadableToWebReadable(codexProcess.stdout);
2995
+ let clientWritable = nodeWritableToWebWritable(codexProcess.stdin);
2996
+ let isLoadingSession = false;
2997
+ let loadRequestId = null;
2998
+ let newSessionRequestId = null;
2999
+ let sdkSessionEmitted = false;
3000
+ const reasoningEffortBySessionId = /* @__PURE__ */ new Map();
3001
+ let injectedConfigId = 0;
3002
+ const decoder = new TextDecoder();
3003
+ const encoder = new TextEncoder();
3004
+ let readBuffer = "";
3005
+ const taskRunId = config.taskRunId;
3006
+ const filteringReadable = clientReadable.pipeThrough(
3007
+ new TransformStream({
3008
+ transform(chunk, controller) {
3009
+ readBuffer += decoder.decode(chunk, { stream: true });
3010
+ const lines = readBuffer.split("\n");
3011
+ readBuffer = lines.pop() ?? "";
3012
+ const outputLines = [];
3013
+ for (const line of lines) {
3014
+ const trimmed = line.trim();
3015
+ if (!trimmed) {
3016
+ outputLines.push(line);
3017
+ continue;
3018
+ }
3019
+ let shouldFilter = false;
3020
+ try {
3021
+ const msg = JSON.parse(trimmed);
3022
+ const sessionId = msg?.params?.sessionId ?? msg?.result?.sessionId ?? null;
3023
+ const configOptions = msg?.result?.configOptions ?? msg?.params?.update?.configOptions;
3024
+ if (sessionId && configOptions) {
3025
+ const effort = extractReasoningEffort(configOptions);
3026
+ if (effort) {
3027
+ reasoningEffortBySessionId.set(sessionId, effort);
3028
+ }
3029
+ }
3030
+ if (!sdkSessionEmitted && newSessionRequestId !== null && msg.id === newSessionRequestId && "result" in msg) {
3031
+ const sessionId2 = msg.result?.sessionId;
3032
+ if (sessionId2 && taskRunId) {
3033
+ const sdkSessionNotification = {
3034
+ jsonrpc: "2.0",
3035
+ method: POSTHOG_NOTIFICATIONS.SDK_SESSION,
3036
+ params: {
3037
+ taskRunId,
3038
+ sessionId: sessionId2,
3039
+ adapter: "codex"
3040
+ }
3041
+ };
3042
+ outputLines.push(JSON.stringify(sdkSessionNotification));
3043
+ sdkSessionEmitted = true;
3044
+ }
3045
+ newSessionRequestId = null;
3046
+ }
3047
+ if (isLoadingSession) {
3048
+ if (msg.id === loadRequestId && "result" in msg) {
3049
+ logger.debug("session/load complete, resuming stream");
3050
+ isLoadingSession = false;
3051
+ loadRequestId = null;
3052
+ } else if (msg.method === "session/update") {
3053
+ shouldFilter = true;
3054
+ }
3055
+ }
3056
+ if (!shouldFilter && allowedModelIds && allowedModelIds.size > 0) {
3057
+ const updated = filterModelConfigOptions(msg, allowedModelIds);
3058
+ if (updated) {
3059
+ outputLines.push(JSON.stringify(updated));
3060
+ continue;
3061
+ }
3062
+ }
3063
+ } catch {
3064
+ }
3065
+ if (!shouldFilter) {
3066
+ outputLines.push(line);
3067
+ const isChunkNoise = trimmed.includes('"sessionUpdate":"agent_message_chunk"') || trimmed.includes('"sessionUpdate":"agent_thought_chunk"');
3068
+ if (!isChunkNoise) {
3069
+ logger.debug("codex-acp stdout:", trimmed);
3070
+ }
3071
+ }
3072
+ }
3073
+ if (outputLines.length > 0) {
3074
+ const output = `${outputLines.join("\n")}
3075
+ `;
3076
+ controller.enqueue(encoder.encode(output));
3077
+ }
3078
+ },
3079
+ flush(controller) {
3080
+ if (readBuffer.trim()) {
3081
+ controller.enqueue(encoder.encode(readBuffer));
3082
+ }
3083
+ }
3084
+ })
3085
+ );
3086
+ clientReadable = filteringReadable;
3087
+ const originalWritable = clientWritable;
3088
+ clientWritable = new WritableStream({
3089
+ write(chunk) {
3090
+ const text2 = decoder.decode(chunk, { stream: true });
3091
+ const trimmed = text2.trim();
3092
+ logger.debug("codex-acp stdin:", trimmed);
3093
+ try {
3094
+ const msg = JSON.parse(trimmed);
3095
+ if (msg.method === "session/set_config_option" && msg.params?.configId === "reasoning_effort" && msg.params?.sessionId && msg.params?.value) {
3096
+ reasoningEffortBySessionId.set(
3097
+ msg.params.sessionId,
3098
+ msg.params.value
3099
+ );
3100
+ }
3101
+ if (msg.method === "session/prompt" && msg.params?.sessionId) {
3102
+ const effort = reasoningEffortBySessionId.get(msg.params.sessionId);
3103
+ if (effort) {
3104
+ const injection = {
3105
+ jsonrpc: "2.0",
3106
+ id: `reasoning_effort_${Date.now()}_${injectedConfigId++}`,
3107
+ method: "session/set_config_option",
3108
+ params: {
3109
+ sessionId: msg.params.sessionId,
3110
+ configId: "reasoning_effort",
3111
+ value: effort
3112
+ }
3113
+ };
3114
+ const injectionLine = `${JSON.stringify(injection)}
3115
+ `;
3116
+ const writer2 = originalWritable.getWriter();
3117
+ return writer2.write(encoder.encode(injectionLine)).then(() => writer2.releaseLock()).then(() => {
3118
+ const nextWriter = originalWritable.getWriter();
3119
+ return nextWriter.write(chunk).finally(() => nextWriter.releaseLock());
3120
+ });
3121
+ }
3122
+ }
3123
+ if (msg.method === "session/new" && msg.id) {
3124
+ logger.debug("session/new detected, tracking request ID");
3125
+ newSessionRequestId = msg.id;
3126
+ } else if (msg.method === "session/load" && msg.id) {
3127
+ logger.debug("session/load detected, pausing stream updates");
3128
+ isLoadingSession = true;
3129
+ loadRequestId = msg.id;
3130
+ }
3131
+ } catch {
3132
+ }
3133
+ const writer = originalWritable.getWriter();
3134
+ return writer.write(chunk).finally(() => writer.releaseLock());
3135
+ },
3136
+ close() {
3137
+ const writer = originalWritable.getWriter();
3138
+ return writer.close().finally(() => writer.releaseLock());
3139
+ }
3140
+ });
3141
+ const shouldTapLogs = config.taskRunId && logWriter;
3142
+ if (shouldTapLogs) {
3143
+ const taskRunId2 = config.taskRunId;
3144
+ if (!logWriter.isRegistered(taskRunId2)) {
3145
+ logWriter.register(taskRunId2, {
3146
+ taskId: config.taskId ?? taskRunId2,
3147
+ runId: taskRunId2
3148
+ });
3149
+ }
3150
+ clientWritable = createTappedWritableStream(clientWritable, {
3151
+ onMessage: (line) => {
3152
+ logWriter.appendRawLine(taskRunId2, line);
3153
+ },
3154
+ logger
3155
+ });
3156
+ const originalReadable = clientReadable;
3157
+ const logDecoder = new TextDecoder();
3158
+ let logBuffer = "";
3159
+ clientReadable = originalReadable.pipeThrough(
3160
+ new TransformStream({
3161
+ transform(chunk, controller) {
3162
+ logBuffer += logDecoder.decode(chunk, { stream: true });
3163
+ const lines = logBuffer.split("\n");
3164
+ logBuffer = lines.pop() ?? "";
3165
+ for (const line of lines) {
3166
+ if (line.trim()) {
3167
+ logWriter.appendRawLine(taskRunId2, line);
3168
+ }
3169
+ }
3170
+ controller.enqueue(chunk);
3171
+ },
3172
+ flush() {
3173
+ if (logBuffer.trim()) {
3174
+ logWriter.appendRawLine(taskRunId2, logBuffer);
3175
+ }
3176
+ }
3177
+ })
3178
+ );
3179
+ } else {
3180
+ logger.info("Tapped streams NOT enabled for Codex", {
3181
+ hasTaskRunId: !!config.taskRunId,
3182
+ hasLogWriter: !!logWriter
3183
+ });
3184
+ }
3185
+ return {
3186
+ agentConnection: void 0,
3187
+ clientStreams: {
3188
+ readable: clientReadable,
3189
+ writable: clientWritable
3190
+ },
3191
+ cleanup: async () => {
3192
+ logger.info("Cleaning up Codex connection");
3193
+ codexProcess.kill();
3194
+ try {
3195
+ await clientWritable.close();
3196
+ } catch {
3197
+ }
3198
+ }
3199
+ };
3200
+ }
3201
+
3202
+ // src/utils/gateway.ts
3203
+ function getLlmGatewayUrl(posthogHost) {
3204
+ const url = new URL(posthogHost);
3205
+ const hostname = url.hostname;
3206
+ if (hostname === "localhost" || hostname === "127.0.0.1") {
3207
+ return `${url.protocol}//localhost:3308/twig`;
3208
+ }
3209
+ if (hostname === "host.docker.internal") {
3210
+ return `${url.protocol}//host.docker.internal:3308/twig`;
3211
+ }
3212
+ const region = hostname.match(/^(us|eu)\.posthog\.com$/)?.[1] ?? "us";
3213
+ return `https://gateway.${region}.posthog.com/twig`;
3214
+ }
3215
+
3216
+ // src/posthog-api.ts
3217
+ var PostHogAPIClient = class {
3218
+ config;
3219
+ constructor(config) {
3220
+ this.config = config;
3221
+ }
3222
+ get baseUrl() {
3223
+ const host = this.config.apiUrl.endsWith("/") ? this.config.apiUrl.slice(0, -1) : this.config.apiUrl;
3224
+ return host;
3225
+ }
3226
+ get headers() {
3227
+ return {
3228
+ Authorization: `Bearer ${this.config.getApiKey()}`,
3229
+ "Content-Type": "application/json"
3230
+ };
3231
+ }
3232
+ async apiRequest(endpoint, options = {}) {
3233
+ const url = `${this.baseUrl}${endpoint}`;
3234
+ const response = await fetch(url, {
3235
+ ...options,
3236
+ headers: {
3237
+ ...this.headers,
3238
+ ...options.headers
3239
+ }
3240
+ });
3241
+ if (!response.ok) {
3242
+ let errorMessage;
3243
+ try {
3244
+ const errorResponse = await response.json();
3245
+ errorMessage = `Failed request: [${response.status}] ${JSON.stringify(errorResponse)}`;
3246
+ } catch {
3247
+ errorMessage = `Failed request: [${response.status}] ${response.statusText}`;
3248
+ }
3249
+ throw new Error(errorMessage);
3250
+ }
3251
+ return response.json();
3252
+ }
3253
+ getTeamId() {
3254
+ return this.config.projectId;
3255
+ }
3256
+ getApiKey() {
3257
+ return this.config.getApiKey();
3258
+ }
3259
+ getLlmGatewayUrl() {
3260
+ return getLlmGatewayUrl(this.baseUrl);
3261
+ }
3262
+ async getTask(taskId) {
3263
+ const teamId = this.getTeamId();
3264
+ return this.apiRequest(`/api/projects/${teamId}/tasks/${taskId}/`);
3265
+ }
3266
+ async getTaskRun(taskId, runId) {
3267
+ const teamId = this.getTeamId();
3268
+ return this.apiRequest(
3269
+ `/api/projects/${teamId}/tasks/${taskId}/runs/${runId}/`
3270
+ );
3271
+ }
3272
+ async updateTaskRun(taskId, runId, payload) {
3273
+ const teamId = this.getTeamId();
3274
+ return this.apiRequest(
3275
+ `/api/projects/${teamId}/tasks/${taskId}/runs/${runId}/`,
3276
+ {
3277
+ method: "PATCH",
3278
+ body: JSON.stringify(payload)
3279
+ }
3280
+ );
3281
+ }
3282
+ async appendTaskRunLog(taskId, runId, entries) {
3283
+ const teamId = this.getTeamId();
3284
+ return this.apiRequest(
3285
+ `/api/projects/${teamId}/tasks/${taskId}/runs/${runId}/append_log/`,
3286
+ {
3287
+ method: "POST",
3288
+ body: JSON.stringify({ entries })
3289
+ }
3290
+ );
3291
+ }
3292
+ async uploadTaskArtifacts(taskId, runId, artifacts) {
3293
+ if (!artifacts.length) {
3294
+ return [];
3295
+ }
3296
+ const teamId = this.getTeamId();
3297
+ const response = await this.apiRequest(
3298
+ `/api/projects/${teamId}/tasks/${taskId}/runs/${runId}/artifacts/`,
3299
+ {
3300
+ method: "POST",
3301
+ body: JSON.stringify({ artifacts })
3302
+ }
3303
+ );
3304
+ return response.artifacts ?? [];
3305
+ }
3306
+ async getArtifactPresignedUrl(taskId, runId, storagePath) {
3307
+ const teamId = this.getTeamId();
3308
+ try {
3309
+ const response = await this.apiRequest(
3310
+ `/api/projects/${teamId}/tasks/${taskId}/runs/${runId}/artifacts/presign/`,
3311
+ {
3312
+ method: "POST",
3313
+ body: JSON.stringify({ storage_path: storagePath })
3314
+ }
3315
+ );
3316
+ return response.url;
3317
+ } catch {
3318
+ return null;
3319
+ }
3320
+ }
3321
+ /**
3322
+ * Download artifact content by storage path
3323
+ * Gets a presigned URL and fetches the content
3324
+ */
3325
+ async downloadArtifact(taskId, runId, storagePath) {
3326
+ const url = await this.getArtifactPresignedUrl(taskId, runId, storagePath);
3327
+ if (!url) {
3328
+ return null;
3329
+ }
3330
+ try {
3331
+ const response = await fetch(url);
3332
+ if (!response.ok) {
3333
+ throw new Error(`Failed to download artifact: ${response.status}`);
3334
+ }
3335
+ return response.arrayBuffer();
3336
+ } catch {
3337
+ return null;
3338
+ }
3339
+ }
3340
+ /**
3341
+ * Fetch logs for a task run via the logs API endpoint
3342
+ * @param taskRun - The task run to fetch logs for
3343
+ * @returns Array of stored entries, or empty array if no logs available
3344
+ */
3345
+ async fetchTaskRunLogs(taskRun) {
3346
+ const teamId = this.getTeamId();
3347
+ try {
3348
+ const response = await fetch(
3349
+ `${this.baseUrl}/api/projects/${teamId}/tasks/${taskRun.task}/runs/${taskRun.id}/logs`,
3350
+ { headers: this.headers }
3351
+ );
3352
+ if (!response.ok) {
3353
+ if (response.status === 404) {
3354
+ return [];
3355
+ }
3356
+ throw new Error(
3357
+ `Failed to fetch logs: ${response.status} ${response.statusText}`
3358
+ );
3359
+ }
3360
+ const content = await response.text();
3361
+ if (!content.trim()) {
3362
+ return [];
3363
+ }
3364
+ return content.trim().split("\n").map((line) => JSON.parse(line));
3365
+ } catch (error) {
3366
+ throw new Error(
3367
+ `Failed to fetch task run logs: ${error instanceof Error ? error.message : String(error)}`
3368
+ );
3369
+ }
3370
+ }
3371
+ };
3372
+
3373
+ // src/otel-log-writer.ts
3374
+ import { SeverityNumber } from "@opentelemetry/api-logs";
3375
+ import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-http";
3376
+ import { resourceFromAttributes } from "@opentelemetry/resources";
3377
+ import {
3378
+ BatchLogRecordProcessor,
3379
+ LoggerProvider
3380
+ } from "@opentelemetry/sdk-logs";
3381
+ import { ATTR_SERVICE_NAME } from "@opentelemetry/semantic-conventions";
3382
+ var OtelLogWriter = class {
3383
+ loggerProvider;
3384
+ logger;
3385
+ constructor(config, sessionContext, _debugLogger) {
3386
+ const logsPath = config.logsPath ?? "/i/v1/agent-logs";
3387
+ const exporter = new OTLPLogExporter({
3388
+ url: `${config.posthogHost}${logsPath}`,
3389
+ headers: { Authorization: `Bearer ${config.apiKey}` }
3390
+ });
3391
+ const processor = new BatchLogRecordProcessor(exporter, {
3392
+ scheduledDelayMillis: config.flushIntervalMs ?? 500
3393
+ });
3394
+ this.loggerProvider = new LoggerProvider({
3395
+ resource: resourceFromAttributes({
3396
+ [ATTR_SERVICE_NAME]: "twig-agent",
3397
+ run_id: sessionContext.runId,
3398
+ task_id: sessionContext.taskId,
3399
+ device_type: sessionContext.deviceType ?? "local"
3400
+ }),
3401
+ processors: [processor]
3402
+ });
3403
+ this.logger = this.loggerProvider.getLogger("agent-session");
3404
+ }
3405
+ /**
3406
+ * Emit an agent event to PostHog Logs via OTEL.
3407
+ */
3408
+ emit(entry) {
3409
+ const { notification } = entry;
3410
+ const eventType = notification.notification.method;
3411
+ this.logger.emit({
3412
+ severityNumber: SeverityNumber.INFO,
3413
+ severityText: "INFO",
3414
+ body: JSON.stringify(notification),
3415
+ attributes: {
3416
+ event_type: eventType
3417
+ }
3418
+ });
3419
+ }
3420
+ async flush() {
3421
+ await this.loggerProvider.forceFlush();
3422
+ }
3423
+ async shutdown() {
3424
+ await this.loggerProvider.shutdown();
3425
+ }
3426
+ };
3427
+
3428
+ // src/session-log-writer.ts
3429
+ var SessionLogWriter = class {
3430
+ posthogAPI;
3431
+ otelConfig;
3432
+ pendingEntries = /* @__PURE__ */ new Map();
3433
+ flushTimeouts = /* @__PURE__ */ new Map();
3434
+ sessions = /* @__PURE__ */ new Map();
3435
+ logger;
3436
+ constructor(options = {}) {
3437
+ this.posthogAPI = options.posthogAPI;
3438
+ this.otelConfig = options.otelConfig;
3439
+ this.logger = options.logger ?? new Logger({ debug: false, prefix: "[SessionLogWriter]" });
3440
+ }
3441
+ async flushAll() {
3442
+ const flushPromises = [];
3443
+ for (const sessionId of this.sessions.keys()) {
3444
+ flushPromises.push(this.flush(sessionId));
3445
+ }
3446
+ await Promise.all(flushPromises);
3447
+ }
3448
+ register(sessionId, context) {
3449
+ if (this.sessions.has(sessionId)) {
3450
+ return;
3451
+ }
3452
+ let otelWriter;
3453
+ if (this.otelConfig) {
3454
+ otelWriter = new OtelLogWriter(
3455
+ this.otelConfig,
3456
+ context,
3457
+ this.logger.child(`OtelWriter:${sessionId}`)
3458
+ );
3459
+ }
3460
+ this.sessions.set(sessionId, { context, otelWriter });
3461
+ }
3462
+ isRegistered(sessionId) {
3463
+ return this.sessions.has(sessionId);
3464
+ }
3465
+ appendRawLine(sessionId, line) {
3466
+ const session = this.sessions.get(sessionId);
3467
+ if (!session) {
3468
+ return;
3469
+ }
3470
+ try {
3471
+ const message = JSON.parse(line);
3472
+ const entry = {
3473
+ type: "notification",
3474
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
3475
+ notification: message
3476
+ };
3477
+ if (session.otelWriter) {
3478
+ session.otelWriter.emit({ notification: entry });
3479
+ }
3480
+ if (this.posthogAPI) {
3481
+ const pending = this.pendingEntries.get(sessionId) ?? [];
3482
+ pending.push(entry);
3483
+ this.pendingEntries.set(sessionId, pending);
3484
+ this.scheduleFlush(sessionId);
3485
+ }
3486
+ } catch {
3487
+ this.logger.warn("Failed to parse raw line for persistence", {
3488
+ sessionId,
3489
+ lineLength: line.length
3490
+ });
3491
+ }
3492
+ }
3493
+ async flush(sessionId) {
3494
+ const session = this.sessions.get(sessionId);
3495
+ if (!session) return;
3496
+ if (session.otelWriter) {
3497
+ await session.otelWriter.flush();
3498
+ }
3499
+ const pending = this.pendingEntries.get(sessionId);
3500
+ if (!this.posthogAPI || !pending?.length) return;
3501
+ this.pendingEntries.delete(sessionId);
3502
+ const timeout = this.flushTimeouts.get(sessionId);
3503
+ if (timeout) {
3504
+ clearTimeout(timeout);
3505
+ this.flushTimeouts.delete(sessionId);
3506
+ }
3507
+ try {
3508
+ await this.posthogAPI.appendTaskRunLog(
3509
+ session.context.taskId,
3510
+ session.context.runId,
3511
+ pending
3512
+ );
3513
+ } catch (error) {
3514
+ this.logger.error("Failed to persist session logs:", error);
3515
+ }
3516
+ }
3517
+ scheduleFlush(sessionId) {
3518
+ const existing = this.flushTimeouts.get(sessionId);
3519
+ if (existing) clearTimeout(existing);
3520
+ const timeout = setTimeout(() => this.flush(sessionId), 500);
3521
+ this.flushTimeouts.set(sessionId, timeout);
3522
+ }
3523
+ };
3524
+
3525
+ // src/agent.ts
3526
+ var Agent = class {
3527
+ posthogAPI;
3528
+ logger;
3529
+ acpConnection;
3530
+ taskRunId;
3531
+ sessionLogWriter;
3532
+ debug;
3533
+ constructor(config) {
3534
+ this.debug = config.debug || false;
3535
+ this.logger = new Logger({
3536
+ debug: this.debug,
3537
+ prefix: "[PostHog Agent]",
3538
+ onLog: config.onLog
3539
+ });
3540
+ if (config.posthog) {
3541
+ this.posthogAPI = new PostHogAPIClient(config.posthog);
3542
+ }
3543
+ if (config.otelTransport) {
3544
+ this.sessionLogWriter = new SessionLogWriter({
3545
+ otelConfig: {
3546
+ posthogHost: config.otelTransport.host,
3547
+ apiKey: config.otelTransport.apiKey,
3548
+ logsPath: config.otelTransport.logsPath
3549
+ },
3550
+ logger: this.logger.child("SessionLogWriter")
3551
+ });
3552
+ } else if (config.posthog) {
3553
+ this.sessionLogWriter = new SessionLogWriter({
3554
+ posthogAPI: this.posthogAPI,
3555
+ logger: this.logger.child("SessionLogWriter")
3556
+ });
3557
+ }
3558
+ }
3559
+ _configureLlmGateway(_adapter) {
3560
+ if (!this.posthogAPI) {
3561
+ return null;
3562
+ }
3563
+ try {
3564
+ const gatewayUrl = this.posthogAPI.getLlmGatewayUrl();
3565
+ const apiKey = this.posthogAPI.getApiKey();
3566
+ process.env.OPENAI_BASE_URL = `${gatewayUrl}/v1`;
3567
+ process.env.OPENAI_API_KEY = apiKey;
3568
+ process.env.ANTHROPIC_BASE_URL = gatewayUrl;
3569
+ process.env.ANTHROPIC_AUTH_TOKEN = apiKey;
3570
+ return { gatewayUrl, apiKey };
3571
+ } catch (error) {
3572
+ this.logger.error("Failed to configure LLM gateway", error);
3573
+ throw error;
3574
+ }
3575
+ }
3576
+ async run(taskId, taskRunId, options = {}) {
3577
+ const gatewayConfig = this._configureLlmGateway(options.adapter);
3578
+ this.taskRunId = taskRunId;
3579
+ let allowedModelIds;
3580
+ let sanitizedModel = options.model && !BLOCKED_MODELS.has(options.model) ? options.model : void 0;
3581
+ if (options.adapter === "codex" && gatewayConfig) {
3582
+ const models = await fetchArrayModels({
3583
+ gatewayUrl: gatewayConfig.gatewayUrl
3584
+ });
3585
+ const codexModelIds = models.filter((model) => {
3586
+ if (BLOCKED_MODELS.has(model.id)) return false;
3587
+ if (model.owned_by) {
3588
+ return model.owned_by === "openai";
3589
+ }
3590
+ return model.id.startsWith("gpt-") || model.id.startsWith("openai/");
3591
+ }).map((model) => model.id);
3592
+ if (codexModelIds.length > 0) {
3593
+ allowedModelIds = new Set(codexModelIds);
3594
+ }
3595
+ if (!sanitizedModel || !allowedModelIds?.has(sanitizedModel)) {
3596
+ sanitizedModel = codexModelIds[0];
3597
+ }
3598
+ }
3599
+ if (!sanitizedModel) {
3600
+ sanitizedModel = DEFAULT_GATEWAY_MODEL;
3601
+ }
3602
+ this.acpConnection = createAcpConnection({
3603
+ adapter: options.adapter,
3604
+ logWriter: this.sessionLogWriter,
3605
+ taskRunId,
3606
+ taskId,
3607
+ deviceType: "local",
3608
+ logger: this.logger,
3609
+ processCallbacks: options.processCallbacks,
3610
+ allowedModelIds,
3611
+ codexOptions: options.adapter === "codex" && gatewayConfig ? {
3612
+ cwd: options.repositoryPath,
3613
+ apiBaseUrl: `${gatewayConfig.gatewayUrl}/v1`,
3614
+ apiKey: gatewayConfig.apiKey,
3615
+ binaryPath: options.codexBinaryPath,
3616
+ model: sanitizedModel
3617
+ } : void 0
3618
+ });
3619
+ return this.acpConnection;
3620
+ }
3621
+ async attachPullRequestToTask(taskId, prUrl, branchName) {
3622
+ this.logger.info("Attaching PR to task run", { taskId, prUrl, branchName });
3623
+ if (!this.posthogAPI || !this.taskRunId) {
3624
+ const error = new Error(
3625
+ "PostHog API not configured or no active run. Cannot attach PR to task."
3626
+ );
3627
+ this.logger.error("PostHog API not configured", error);
3628
+ throw error;
3629
+ }
3630
+ const updates = {
3631
+ output: { pr_url: prUrl }
3632
+ };
3633
+ if (branchName) {
3634
+ updates.branch = branchName;
3635
+ }
3636
+ await this.posthogAPI.updateTaskRun(taskId, this.taskRunId, updates);
3637
+ this.logger.debug("PR attached to task run", {
3638
+ taskId,
3639
+ taskRunId: this.taskRunId,
3640
+ prUrl
3641
+ });
3642
+ }
3643
+ async flushAllLogs() {
3644
+ await this.sessionLogWriter?.flushAll();
3645
+ }
3646
+ async cleanup() {
3647
+ if (this.sessionLogWriter && this.taskRunId) {
3648
+ await this.sessionLogWriter.flush(this.taskRunId);
3649
+ }
3650
+ await this.acpConnection?.cleanup();
3651
+ }
3652
+ };
3653
+ export {
3654
+ Agent
3655
+ };
3656
+ //# sourceMappingURL=agent.js.map