@posthog/agent 2.3.67 → 2.3.73

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 (33) hide show
  1. package/dist/adapters/claude/permissions/permission-options.js +12 -2
  2. package/dist/adapters/claude/permissions/permission-options.js.map +1 -1
  3. package/dist/adapters/claude/session/jsonl-hydration.js.map +1 -1
  4. package/dist/agent.js +239 -70
  5. package/dist/agent.js.map +1 -1
  6. package/dist/claude-cli/cli.js +4002 -2916
  7. package/dist/claude-cli/vendor/audio-capture/arm64-darwin/audio-capture.node +0 -0
  8. package/dist/claude-cli/vendor/audio-capture/arm64-linux/audio-capture.node +0 -0
  9. package/dist/claude-cli/vendor/audio-capture/arm64-win32/audio-capture.node +0 -0
  10. package/dist/claude-cli/vendor/audio-capture/x64-darwin/audio-capture.node +0 -0
  11. package/dist/claude-cli/vendor/audio-capture/x64-linux/audio-capture.node +0 -0
  12. package/dist/claude-cli/vendor/audio-capture/x64-win32/audio-capture.node +0 -0
  13. package/dist/claude-cli/vendor/tree-sitter-bash/arm64-darwin/tree-sitter-bash.node +0 -0
  14. package/dist/claude-cli/vendor/tree-sitter-bash/arm64-linux/tree-sitter-bash.node +0 -0
  15. package/dist/claude-cli/vendor/tree-sitter-bash/arm64-win32/tree-sitter-bash.node +0 -0
  16. package/dist/claude-cli/vendor/tree-sitter-bash/x64-darwin/tree-sitter-bash.node +0 -0
  17. package/dist/claude-cli/vendor/tree-sitter-bash/x64-linux/tree-sitter-bash.node +0 -0
  18. package/dist/claude-cli/vendor/tree-sitter-bash/x64-win32/tree-sitter-bash.node +0 -0
  19. package/dist/posthog-api.js +3 -3
  20. package/dist/posthog-api.js.map +1 -1
  21. package/dist/server/agent-server.js +239 -70
  22. package/dist/server/agent-server.js.map +1 -1
  23. package/dist/server/bin.cjs +239 -70
  24. package/dist/server/bin.cjs.map +1 -1
  25. package/package.json +3 -3
  26. package/src/adapters/base-acp-agent.ts +11 -2
  27. package/src/adapters/claude/UPSTREAM.md +3 -4
  28. package/src/adapters/claude/claude-agent.ts +217 -35
  29. package/src/adapters/claude/conversion/sdk-to-acp.ts +2 -25
  30. package/src/adapters/claude/permissions/permission-handlers.ts +5 -7
  31. package/src/adapters/claude/permissions/permission-options.ts +17 -2
  32. package/src/adapters/claude/session/models.ts +94 -4
  33. package/src/adapters/claude/types.ts +3 -0
package/dist/agent.js CHANGED
@@ -281,7 +281,7 @@ import { v7 as uuidv7 } from "uuid";
281
281
  // package.json
282
282
  var package_default = {
283
283
  name: "@posthog/agent",
284
- version: "2.3.67",
284
+ version: "2.3.73",
285
285
  repository: "https://github.com/PostHog/code",
286
286
  description: "TypeScript agent framework wrapping Claude Agent SDK with Git-based task execution for PostHog",
287
287
  exports: {
@@ -368,8 +368,8 @@ var package_default = {
368
368
  vitest: "^2.1.8"
369
369
  },
370
370
  dependencies: {
371
- "@agentclientprotocol/sdk": "0.15.0",
372
- "@anthropic-ai/claude-agent-sdk": "0.2.71",
371
+ "@agentclientprotocol/sdk": "0.16.1",
372
+ "@anthropic-ai/claude-agent-sdk": "0.2.76",
373
373
  "@anthropic-ai/sdk": "^0.78.0",
374
374
  "@hono/node-server": "^1.19.9",
375
375
  "@opentelemetry/api-logs": "^0.208.0",
@@ -508,12 +508,14 @@ function formatGatewayModelName(model) {
508
508
  }
509
509
 
510
510
  // src/adapters/base-acp-agent.ts
511
+ var DEFAULT_CONTEXT_WINDOW = 2e5;
511
512
  var BaseAcpAgent = class {
512
513
  session;
513
514
  sessionId;
514
515
  client;
515
516
  logger;
516
517
  fileContentCache = {};
518
+ gatewayModels = [];
517
519
  constructor(client) {
518
520
  this.client = client;
519
521
  this.logger = new Logger({ debug: true, prefix: "[BaseAcpAgent]" });
@@ -566,8 +568,8 @@ var BaseAcpAgent = class {
566
568
  throw new Error("Method not implemented.");
567
569
  }
568
570
  async getModelConfigOptions(currentModelOverride) {
569
- const gatewayModels = await fetchGatewayModels();
570
- const options = gatewayModels.filter((model) => isAnthropicModel(model)).map((model) => ({
571
+ this.gatewayModels = await fetchGatewayModels();
572
+ const options = this.gatewayModels.filter((model) => isAnthropicModel(model)).map((model) => ({
571
573
  value: model.id,
572
574
  name: formatGatewayModelName(model),
573
575
  description: `Context: ${model.context_window.toLocaleString()} tokens`
@@ -588,6 +590,10 @@ var BaseAcpAgent = class {
588
590
  }
589
591
  return { currentModelId, options };
590
592
  }
593
+ getContextWindowForModel(modelId) {
594
+ const match = this.gatewayModels.find((m) => m.id === modelId);
595
+ return match?.context_window ?? DEFAULT_CONTEXT_WINDOW;
596
+ }
591
597
  };
592
598
 
593
599
  // src/adapters/claude/conversion/acp-to-sdk.ts
@@ -1763,7 +1769,7 @@ function streamEventToAcpNotifications(message, sessionId, toolUseCache, fileCon
1763
1769
  }
1764
1770
  }
1765
1771
  async function handleSystemMessage(message, context) {
1766
- const { sessionId, client, logger } = context;
1772
+ const { session, sessionId, client, logger } = context;
1767
1773
  switch (message.subtype) {
1768
1774
  case "init":
1769
1775
  break;
@@ -1771,7 +1777,8 @@ async function handleSystemMessage(message, context) {
1771
1777
  await client.extNotification("_posthog/compact_boundary", {
1772
1778
  sessionId,
1773
1779
  trigger: message.compact_metadata.trigger,
1774
- preTokens: message.compact_metadata.pre_tokens
1780
+ preTokens: message.compact_metadata.pre_tokens,
1781
+ contextSize: session.contextSize
1775
1782
  });
1776
1783
  break;
1777
1784
  case "hook_response":
@@ -1943,21 +1950,6 @@ function filterMessageContent(content) {
1943
1950
  async function handleUserAssistantMessage(message, context) {
1944
1951
  const { session, sessionId, client, toolUseCache, fileContentCache, logger } = context;
1945
1952
  if (shouldSkipUserAssistantMessage(message)) {
1946
- const content2 = message.message.content;
1947
- if (typeof content2 === "string" && hasLocalCommandStdout(content2) && content2.includes("Context Usage")) {
1948
- const stripped = content2.replace("<local-command-stdout>", "").replace("</local-command-stdout>", "");
1949
- for (const notification of toAcpNotifications(
1950
- stripped,
1951
- "assistant",
1952
- sessionId,
1953
- toolUseCache,
1954
- fileContentCache,
1955
- client,
1956
- logger
1957
- )) {
1958
- await client.sessionUpdate(notification);
1959
- }
1960
- }
1961
1953
  logSpecialMessages(message, logger);
1962
1954
  if (isLoginRequiredMessage(message)) {
1963
1955
  return { shouldStop: true, error: RequestError.authRequired() };
@@ -2217,8 +2209,17 @@ function buildPermissionOptions(toolName, toolInput, cwd, suggestions) {
2217
2209
  }
2218
2210
  return permissionOptions("Yes, always allow");
2219
2211
  }
2212
+ var ALLOW_BYPASS2 = !IS_ROOT || !!process.env.IS_SANDBOX;
2220
2213
  function buildExitPlanModePermissionOptions() {
2221
- return [
2214
+ const options = [];
2215
+ if (ALLOW_BYPASS2) {
2216
+ options.push({
2217
+ kind: "allow_always",
2218
+ name: "Yes, bypass all permissions",
2219
+ optionId: "bypassPermissions"
2220
+ });
2221
+ }
2222
+ options.push(
2222
2223
  {
2223
2224
  kind: "allow_always",
2224
2225
  name: "Yes, and auto-accept edits",
@@ -2235,7 +2236,8 @@ function buildExitPlanModePermissionOptions() {
2235
2236
  optionId: "reject_with_feedback",
2236
2237
  _meta: { customInput: true }
2237
2238
  }
2238
- ];
2239
+ );
2240
+ return options;
2239
2241
  }
2240
2242
 
2241
2243
  // src/adapters/claude/permissions/permission-handlers.ts
@@ -2309,7 +2311,7 @@ async function requestPlanApproval(context, updatedInput) {
2309
2311
  }
2310
2312
  async function applyPlanApproval(response, context, updatedInput) {
2311
2313
  const { session } = context;
2312
- if (response.outcome?.outcome === "selected" && (response.outcome.optionId === "default" || response.outcome.optionId === "acceptEdits")) {
2314
+ if (response.outcome?.outcome === "selected" && (response.outcome.optionId === "default" || response.outcome.optionId === "acceptEdits" || response.outcome.optionId === "bypassPermissions")) {
2313
2315
  session.permissionMode = response.outcome.optionId;
2314
2316
  await session.query.setPermissionMode(response.outcome.optionId);
2315
2317
  await context.client.sessionUpdate({
@@ -2382,8 +2384,7 @@ async function handleAskUserQuestionTool(context) {
2382
2384
  context.logger.warn("[AskUserQuestion] No questions found in input");
2383
2385
  return {
2384
2386
  behavior: "deny",
2385
- message: "No questions provided",
2386
- interrupt: true
2387
+ message: "No questions provided"
2387
2388
  };
2388
2389
  }
2389
2390
  const { client, sessionId, toolUseID, toolInput } = context;
@@ -2414,16 +2415,14 @@ async function handleAskUserQuestionTool(context) {
2414
2415
  const customMessage = response._meta?.message;
2415
2416
  return {
2416
2417
  behavior: "deny",
2417
- message: typeof customMessage === "string" ? customMessage : "User cancelled the questions",
2418
- interrupt: true
2418
+ message: typeof customMessage === "string" ? customMessage : "User cancelled the questions"
2419
2419
  };
2420
2420
  }
2421
2421
  const answers = response._meta?.answers;
2422
2422
  if (!answers || Object.keys(answers).length === 0) {
2423
2423
  return {
2424
2424
  behavior: "deny",
2425
- message: "User did not provide answers",
2426
- interrupt: true
2425
+ message: "User did not provide answers"
2427
2426
  };
2428
2427
  }
2429
2428
  return {
@@ -2490,8 +2489,7 @@ async function handleDefaultPermissionFlow(context) {
2490
2489
  await emitToolDenial(context, message);
2491
2490
  return {
2492
2491
  behavior: "deny",
2493
- message,
2494
- interrupt: true
2492
+ message
2495
2493
  };
2496
2494
  }
2497
2495
  }
@@ -2605,16 +2603,6 @@ var GATEWAY_TO_SDK_MODEL = {
2605
2603
  function toSdkModelId(modelId) {
2606
2604
  return GATEWAY_TO_SDK_MODEL[modelId] ?? modelId;
2607
2605
  }
2608
- var MODELS_WITH_1M_CONTEXT = /* @__PURE__ */ new Set([
2609
- "claude-opus-4-6",
2610
- "claude-sonnet-4-6"
2611
- ]);
2612
- function supports1MContext(modelId) {
2613
- return MODELS_WITH_1M_CONTEXT.has(modelId);
2614
- }
2615
- function getDefaultContextWindow(modelId) {
2616
- return supports1MContext(modelId) ? 1e6 : 2e5;
2617
- }
2618
2606
  var MODELS_WITH_EFFORT = /* @__PURE__ */ new Set([
2619
2607
  "claude-opus-4-5",
2620
2608
  "claude-opus-4-6",
@@ -2639,6 +2627,56 @@ function getEffortOptions(modelId) {
2639
2627
  }
2640
2628
  return options;
2641
2629
  }
2630
+ var MODEL_CONTEXT_HINT_PATTERN = /\[(\d+m)\]$/i;
2631
+ function tokenizeModelPreference(model) {
2632
+ const lower = model.trim().toLowerCase();
2633
+ const contextHint = lower.match(MODEL_CONTEXT_HINT_PATTERN)?.[1]?.toLowerCase();
2634
+ const normalized = lower.replace(MODEL_CONTEXT_HINT_PATTERN, " $1 ");
2635
+ const rawTokens = normalized.split(/[^a-z0-9]+/).filter(Boolean);
2636
+ const tokens = rawTokens.map((token) => {
2637
+ if (token === "opusplan") return "opus";
2638
+ if (token === "best" || token === "default") return "";
2639
+ return token;
2640
+ }).filter((token) => token && token !== "claude").filter((token) => /[a-z]/.test(token) || token.endsWith("m"));
2641
+ return { tokens, contextHint };
2642
+ }
2643
+ function scoreModelMatch(model, tokens, contextHint) {
2644
+ const haystack = `${model.value} ${model.name ?? ""}`.toLowerCase();
2645
+ let score = 0;
2646
+ for (const token of tokens) {
2647
+ if (haystack.includes(token)) {
2648
+ score += token === contextHint ? 3 : 1;
2649
+ }
2650
+ }
2651
+ return score;
2652
+ }
2653
+ function resolveModelPreference(preference, options) {
2654
+ const trimmed = preference.trim();
2655
+ if (!trimmed) return null;
2656
+ const lower = trimmed.toLowerCase();
2657
+ const directMatch = options.find(
2658
+ (o) => o.value === trimmed || o.value.toLowerCase() === lower || o.name && o.name.toLowerCase() === lower
2659
+ );
2660
+ if (directMatch) return directMatch.value;
2661
+ const includesMatch = options.find((o) => {
2662
+ const value = o.value.toLowerCase();
2663
+ const display = (o.name ?? "").toLowerCase();
2664
+ return value.includes(lower) || display.includes(lower) || lower.includes(value);
2665
+ });
2666
+ if (includesMatch) return includesMatch.value;
2667
+ const { tokens, contextHint } = tokenizeModelPreference(trimmed);
2668
+ if (tokens.length === 0) return null;
2669
+ let bestMatch = null;
2670
+ let bestScore = 0;
2671
+ for (const model of options) {
2672
+ const score = scoreModelMatch(model, tokens, contextHint);
2673
+ if (0 < score && (!bestMatch || bestScore < score)) {
2674
+ bestMatch = model;
2675
+ bestScore = score;
2676
+ }
2677
+ }
2678
+ return bestMatch?.value ?? null;
2679
+ }
2642
2680
 
2643
2681
  // src/adapters/claude/session/options.ts
2644
2682
  import { spawn } from "child_process";
@@ -3119,6 +3157,7 @@ var SettingsManager = class {
3119
3157
  // src/adapters/claude/claude-agent.ts
3120
3158
  var SESSION_VALIDATION_TIMEOUT_MS = 1e4;
3121
3159
  var MAX_TITLE_LENGTH = 256;
3160
+ var LOCAL_ONLY_COMMANDS = /* @__PURE__ */ new Set(["/context", "/heapdump", "/extra-usage"]);
3122
3161
  function sanitizeTitle(text2) {
3123
3162
  const sanitized = text2.replace(/[\r\n]+/g, " ").replace(/\s+/g, " ").trim();
3124
3163
  if (sanitized.length <= MAX_TITLE_LENGTH) {
@@ -3155,7 +3194,8 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
3155
3194
  sessionCapabilities: {
3156
3195
  list: {},
3157
3196
  fork: {},
3158
- resume: {}
3197
+ resume: {},
3198
+ close: {}
3159
3199
  },
3160
3200
  _meta: {
3161
3201
  posthog: {
@@ -3195,6 +3235,8 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
3195
3235
  );
3196
3236
  }
3197
3237
  async unstable_resumeSession(params) {
3238
+ const existing = this.getExistingSessionState(params.sessionId);
3239
+ if (existing) return existing;
3198
3240
  const response = await this.createSession(
3199
3241
  {
3200
3242
  cwd: params.cwd,
@@ -3208,6 +3250,8 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
3208
3250
  return response;
3209
3251
  }
3210
3252
  async loadSession(params) {
3253
+ const existing = this.getExistingSessionState(params.sessionId);
3254
+ if (existing) return existing;
3211
3255
  const response = await this.createSession(
3212
3256
  {
3213
3257
  cwd: params.cwd,
@@ -3224,7 +3268,7 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
3224
3268
  configOptions: response.configOptions
3225
3269
  };
3226
3270
  }
3227
- async unstable_listSessions(params) {
3271
+ async listSessions(params) {
3228
3272
  const sdkSessions = await listSessions({ dir: params.cwd ?? void 0 });
3229
3273
  const sessions = [];
3230
3274
  for (const session of sdkSessions) {
@@ -3240,6 +3284,9 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
3240
3284
  sessions
3241
3285
  };
3242
3286
  }
3287
+ async unstable_listSessions(params) {
3288
+ return this.listSessions(params);
3289
+ }
3243
3290
  async prompt(params) {
3244
3291
  this.session.cancelled = false;
3245
3292
  this.session.interruptReason = void 0;
@@ -3250,17 +3297,37 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
3250
3297
  cachedWriteTokens: 0
3251
3298
  };
3252
3299
  const userMessage = promptToClaude(params);
3300
+ const promptUuid = randomUUID();
3301
+ userMessage.uuid = promptUuid;
3302
+ let promptReplayed = false;
3303
+ let isLocalOnlyCommand = false;
3304
+ const msgContent = userMessage.message.content;
3305
+ let firstTextPart = "";
3306
+ if (typeof msgContent === "string") {
3307
+ firstTextPart = msgContent;
3308
+ } else if (Array.isArray(msgContent)) {
3309
+ for (const block of msgContent) {
3310
+ if ("type" in block && block.type === "text" && "text" in block) {
3311
+ firstTextPart = block.text;
3312
+ break;
3313
+ }
3314
+ }
3315
+ }
3316
+ const commandMatch = firstTextPart.match(/^(\/\S+)/);
3317
+ if (commandMatch && LOCAL_ONLY_COMMANDS.has(commandMatch[1])) {
3318
+ isLocalOnlyCommand = true;
3319
+ promptReplayed = true;
3320
+ }
3253
3321
  if (this.session.promptRunning) {
3254
- const uuid = randomUUID();
3255
- userMessage.uuid = uuid;
3256
3322
  this.session.input.push(userMessage);
3257
3323
  const order = this.session.nextPendingOrder++;
3258
3324
  const cancelled = await new Promise((resolve3) => {
3259
- this.session.pendingMessages.set(uuid, { resolve: resolve3, order });
3325
+ this.session.pendingMessages.set(promptUuid, { resolve: resolve3, order });
3260
3326
  });
3261
3327
  if (cancelled) {
3262
3328
  return { stopReason: "cancelled" };
3263
3329
  }
3330
+ promptReplayed = true;
3264
3331
  } else {
3265
3332
  this.session.input.push(userMessage);
3266
3333
  }
@@ -3268,6 +3335,16 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
3268
3335
  this.session.promptRunning = true;
3269
3336
  let handedOff = false;
3270
3337
  let lastAssistantTotalUsage = null;
3338
+ if (this.session.lastContextWindowSize == null) {
3339
+ this.session.lastContextWindowSize = this.getContextWindowForModel(
3340
+ this.session.modelId ?? ""
3341
+ );
3342
+ this.logger.debug("Initial context window size from gateway", {
3343
+ modelId: this.session.modelId,
3344
+ contextWindowSize: this.session.lastContextWindowSize
3345
+ });
3346
+ }
3347
+ let lastContextWindowSize = this.session.lastContextWindowSize;
3271
3348
  const supportsTerminalOutput = this.clientCapabilities?._meta?.terminal_output === true;
3272
3349
  const context = {
3273
3350
  session: this.session,
@@ -3294,10 +3371,21 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
3294
3371
  case "system":
3295
3372
  if (message.subtype === "compact_boundary") {
3296
3373
  lastAssistantTotalUsage = 0;
3374
+ promptReplayed = true;
3375
+ }
3376
+ if (message.subtype === "local_command_output") {
3377
+ promptReplayed = true;
3297
3378
  }
3298
3379
  await handleSystemMessage(message, context);
3299
3380
  break;
3300
3381
  case "result": {
3382
+ if (!promptReplayed) {
3383
+ this.logger.debug(
3384
+ "Skipping background task result before prompt replay",
3385
+ { sessionId: params.sessionId }
3386
+ );
3387
+ break;
3388
+ }
3301
3389
  if (this.session.cancelled) {
3302
3390
  return { stopReason: "cancelled" };
3303
3391
  }
@@ -3308,8 +3396,19 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
3308
3396
  const contextWindows = Object.values(message.modelUsage).map(
3309
3397
  (m) => m.contextWindow
3310
3398
  );
3311
- const contextWindowSize = contextWindows.length > 0 ? Math.min(...contextWindows) : getDefaultContextWindow(this.session.modelId ?? "");
3312
- this.session.contextSize = contextWindowSize;
3399
+ if (contextWindows.length > 0) {
3400
+ const sdkContextWindow = Math.min(...contextWindows);
3401
+ if (sdkContextWindow > lastContextWindowSize) {
3402
+ lastContextWindowSize = sdkContextWindow;
3403
+ }
3404
+ }
3405
+ this.session.lastContextWindowSize = lastContextWindowSize;
3406
+ this.logger.debug("Context window size from result", {
3407
+ sdkReported: contextWindows,
3408
+ resolved: lastContextWindowSize,
3409
+ modelId: this.session.modelId
3410
+ });
3411
+ this.session.contextSize = lastContextWindowSize;
3313
3412
  if (lastAssistantTotalUsage !== null) {
3314
3413
  this.session.contextUsed = lastAssistantTotalUsage;
3315
3414
  }
@@ -3319,7 +3418,7 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
3319
3418
  update: {
3320
3419
  sessionUpdate: "usage_update",
3321
3420
  used: lastAssistantTotalUsage,
3322
- size: contextWindowSize,
3421
+ size: lastContextWindowSize,
3323
3422
  cost: {
3324
3423
  amount: message.total_cost_usd,
3325
3424
  currency: "USD"
@@ -3346,6 +3445,15 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
3346
3445
  };
3347
3446
  const result = handleResultMessage(message);
3348
3447
  if (result.error) throw result.error;
3448
+ if (isLocalOnlyCommand && message.subtype === "success" && message.result) {
3449
+ await this.client.sessionUpdate({
3450
+ sessionId: params.sessionId,
3451
+ update: {
3452
+ sessionUpdate: "agent_message_chunk",
3453
+ content: { type: "text", text: message.result }
3454
+ }
3455
+ });
3456
+ }
3349
3457
  return { stopReason: result.stopReason ?? "end_turn", usage };
3350
3458
  }
3351
3459
  case "stream_event":
@@ -3357,6 +3465,10 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
3357
3465
  break;
3358
3466
  }
3359
3467
  if (message.type === "user" && "uuid" in message && message.uuid) {
3468
+ if (message.uuid === promptUuid) {
3469
+ promptReplayed = true;
3470
+ break;
3471
+ }
3360
3472
  const pending = this.session.pendingMessages.get(
3361
3473
  message.uuid
3362
3474
  );
@@ -3372,7 +3484,16 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
3372
3484
  }
3373
3485
  if ("usage" in message.message && message.parent_tool_use_id === null) {
3374
3486
  const usage = message.message.usage;
3375
- lastAssistantTotalUsage = usage.input_tokens + usage.output_tokens + usage.cache_read_input_tokens + usage.cache_creation_input_tokens;
3487
+ lastAssistantTotalUsage = usage.input_tokens + usage.cache_read_input_tokens + usage.cache_creation_input_tokens;
3488
+ await this.client.sessionUpdate({
3489
+ sessionId: params.sessionId,
3490
+ update: {
3491
+ sessionUpdate: "usage_update",
3492
+ used: lastAssistantTotalUsage,
3493
+ size: lastContextWindowSize,
3494
+ cost: null
3495
+ }
3496
+ });
3376
3497
  }
3377
3498
  const result = await handleUserAssistantMessage(message, context);
3378
3499
  if (result.error) throw result.error;
@@ -3402,6 +3523,7 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
3402
3523
  this.logger.error(`Process died: ${msg}`, {
3403
3524
  sessionId: this.sessionId
3404
3525
  });
3526
+ this.session.settingsManager.dispose();
3405
3527
  this.session.input.end();
3406
3528
  throw RequestError2.internalError(
3407
3529
  void 0,
@@ -3429,9 +3551,11 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
3429
3551
  await this.session.query.interrupt();
3430
3552
  }
3431
3553
  async unstable_setSessionModel(params) {
3432
- const sdkModelId = toSdkModelId(params.modelId);
3433
- await this.session.query.setModel(sdkModelId);
3554
+ await this.session.query.setModel(toSdkModelId(params.modelId));
3434
3555
  this.session.modelId = params.modelId;
3556
+ this.session.lastContextWindowSize = this.getContextWindowForModel(
3557
+ params.modelId
3558
+ );
3435
3559
  this.rebuildEffortConfigOption(params.modelId);
3436
3560
  await this.updateConfigOption("model", params.modelId);
3437
3561
  return {};
@@ -3448,42 +3572,55 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
3448
3572
  if (!option) {
3449
3573
  throw new Error(`Unknown config option: ${params.configId}`);
3450
3574
  }
3575
+ if (typeof params.value !== "string") {
3576
+ throw new Error(
3577
+ `Invalid value type for config option ${params.configId}`
3578
+ );
3579
+ }
3451
3580
  const allValues = "options" in option && Array.isArray(option.options) ? option.options.flatMap(
3452
3581
  (o) => "options" in o && Array.isArray(o.options) ? o.options : [o]
3453
3582
  ) : [];
3454
- const validValue = allValues.find((o) => o.value === params.value);
3583
+ let validValue = allValues.find((o) => o.value === params.value);
3584
+ if (!validValue && params.configId === "model") {
3585
+ const resolved = resolveModelPreference(params.value, allValues);
3586
+ if (resolved) {
3587
+ validValue = allValues.find((o) => o.value === resolved);
3588
+ }
3589
+ }
3455
3590
  if (!validValue) {
3456
3591
  throw new Error(
3457
3592
  `Invalid value for config option ${params.configId}: ${params.value}`
3458
3593
  );
3459
3594
  }
3595
+ const resolvedValue = validValue.value;
3460
3596
  if (params.configId === "mode") {
3461
- await this.applySessionMode(params.value);
3597
+ await this.applySessionMode(resolvedValue);
3462
3598
  await this.client.sessionUpdate({
3463
3599
  sessionId: this.sessionId,
3464
3600
  update: {
3465
3601
  sessionUpdate: "current_mode_update",
3466
- currentModeId: params.value
3602
+ currentModeId: resolvedValue
3467
3603
  }
3468
3604
  });
3469
3605
  } else if (params.configId === "model") {
3470
- const sdkModelId = toSdkModelId(params.value);
3606
+ const sdkModelId = toSdkModelId(resolvedValue);
3471
3607
  await this.session.query.setModel(sdkModelId);
3472
- this.session.modelId = params.value;
3473
- this.rebuildEffortConfigOption(params.value);
3608
+ this.session.modelId = resolvedValue;
3609
+ this.session.lastContextWindowSize = this.getContextWindowForModel(resolvedValue);
3610
+ this.rebuildEffortConfigOption(resolvedValue);
3474
3611
  } else if (params.configId === "effort") {
3475
- const newEffort = params.value;
3612
+ const newEffort = resolvedValue;
3476
3613
  this.session.effort = newEffort;
3477
3614
  this.session.queryOptions.effort = newEffort;
3478
3615
  }
3479
3616
  this.session.configOptions = this.session.configOptions.map(
3480
- (o) => o.id === params.configId ? { ...o, currentValue: params.value } : o
3617
+ (o) => o.id === params.configId && typeof o.currentValue === "string" ? { ...o, currentValue: resolvedValue } : o
3481
3618
  );
3482
3619
  return { configOptions: this.session.configOptions };
3483
3620
  }
3484
3621
  async updateConfigOption(configId, value) {
3485
3622
  this.session.configOptions = this.session.configOptions.map(
3486
- (o) => o.id === configId ? { ...o, currentValue: value } : o
3623
+ (o) => o.id === configId && typeof o.currentValue === "string" ? { ...o, currentValue: value } : o
3487
3624
  );
3488
3625
  await this.client.sessionUpdate({
3489
3626
  sessionId: this.sessionId,
@@ -3550,7 +3687,10 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
3550
3687
  sessionId,
3551
3688
  isResume,
3552
3689
  forkSession,
3553
- additionalDirectories: meta?.claudeCode?.options?.additionalDirectories,
3690
+ additionalDirectories: [
3691
+ ...meta?.claudeCode?.options?.additionalDirectories ?? [],
3692
+ ...meta?.additionalRoots ?? []
3693
+ ],
3554
3694
  disableBuiltInTools: meta?.disableBuiltInTools,
3555
3695
  settingsManager,
3556
3696
  onModeChange: this.createOnModeChange(),
@@ -3627,11 +3767,10 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
3627
3767
  const modelOptions = await this.getModelConfigOptions();
3628
3768
  const resolvedModelId = settingsModel || modelOptions.currentModelId;
3629
3769
  session.modelId = resolvedModelId;
3630
- if (!isResume) {
3631
- const resolvedSdkModel = toSdkModelId(resolvedModelId);
3632
- if (resolvedSdkModel !== DEFAULT_MODEL) {
3633
- await this.session.query.setModel(resolvedSdkModel);
3634
- }
3770
+ session.lastContextWindowSize = this.getContextWindowForModel(resolvedModelId);
3771
+ const resolvedSdkModel = toSdkModelId(resolvedModelId);
3772
+ if (!isResume && resolvedSdkModel !== DEFAULT_MODEL) {
3773
+ await this.session.query.setModel(resolvedSdkModel);
3635
3774
  }
3636
3775
  const availableModes2 = getAvailableModes();
3637
3776
  const modes = {
@@ -3694,6 +3833,35 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
3694
3833
  await this.updateConfigOption("mode", newMode);
3695
3834
  };
3696
3835
  }
3836
+ getExistingSessionState(sessionId) {
3837
+ if (this.sessionId !== sessionId || !this.session) return null;
3838
+ const availableModes2 = getAvailableModes();
3839
+ const modes = {
3840
+ currentModeId: this.session.permissionMode,
3841
+ availableModes: availableModes2.map((mode) => ({
3842
+ id: mode.id,
3843
+ name: mode.name,
3844
+ description: mode.description ?? void 0
3845
+ }))
3846
+ };
3847
+ const modelOptions = this.session.configOptions.find(
3848
+ (o) => o.id === "model"
3849
+ );
3850
+ const models = {
3851
+ currentModelId: this.session.modelId ?? DEFAULT_MODEL,
3852
+ availableModels: modelOptions && "options" in modelOptions ? modelOptions.options.map((opt) => ({
3853
+ modelId: opt.value,
3854
+ name: opt.name,
3855
+ description: opt.description
3856
+ })) : []
3857
+ };
3858
+ return {
3859
+ sessionId,
3860
+ modes,
3861
+ models,
3862
+ configOptions: this.session.configOptions
3863
+ };
3864
+ }
3697
3865
  buildConfigOptions(currentModeId, modelOptions, currentEffort = "high") {
3698
3866
  const modeOptions = getAvailableModes().map((mode) => ({
3699
3867
  value: mode.id,
@@ -3749,7 +3917,8 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
3749
3917
  }
3750
3918
  return;
3751
3919
  }
3752
- const currentValue = existingEffort?.currentValue ?? "high";
3920
+ const rawCurrentValue = existingEffort?.currentValue;
3921
+ const currentValue = typeof rawCurrentValue === "string" ? rawCurrentValue : "high";
3753
3922
  const isValidValue = effortOptions.some((o) => o.value === currentValue);
3754
3923
  const resolvedValue = isValidValue ? currentValue : "high";
3755
3924
  if (resolvedValue !== currentValue && this.session.effort) {