@getpaseo/server 0.1.99 → 0.1.101

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 (83) hide show
  1. package/dist/server/executable-resolution/windows.js +3 -0
  2. package/dist/server/server/agent/agent-manager.d.ts +10 -0
  3. package/dist/server/server/agent/agent-manager.js +65 -27
  4. package/dist/server/server/agent/agent-sdk-types.d.ts +8 -0
  5. package/dist/server/server/agent/mcp-server.d.ts +2 -45
  6. package/dist/server/server/agent/mcp-server.js +45 -1985
  7. package/dist/server/server/agent/prompt-attachments.js +6 -2
  8. package/dist/server/server/agent/provider-registry.js +1 -0
  9. package/dist/server/server/agent/provider-snapshot-manager.d.ts +4 -0
  10. package/dist/server/server/agent/provider-snapshot-manager.js +58 -13
  11. package/dist/server/server/agent/providers/acp-agent.d.ts +39 -2
  12. package/dist/server/server/agent/providers/acp-agent.js +281 -20
  13. package/dist/server/server/agent/providers/claude/agent.js +96 -62
  14. package/dist/server/server/agent/providers/codex-app-server-agent.js +6 -57
  15. package/dist/server/server/agent/providers/copilot-acp-agent.d.ts +2 -1
  16. package/dist/server/server/agent/providers/copilot-acp-agent.js +10 -0
  17. package/dist/server/server/agent/providers/diagnostic-utils.d.ts +1 -0
  18. package/dist/server/server/agent/providers/diagnostic-utils.js +1 -1
  19. package/dist/server/server/agent/providers/generic-acp-agent.d.ts +3 -0
  20. package/dist/server/server/agent/providers/generic-acp-agent.js +41 -23
  21. package/dist/server/server/agent/providers/mock-load-test-agent.js +4 -2
  22. package/dist/server/server/agent/providers/opencode/server-manager.d.ts +14 -11
  23. package/dist/server/server/agent/providers/opencode/server-manager.js +149 -91
  24. package/dist/server/server/agent/providers/opencode/test-server-manager.d.ts +6 -5
  25. package/dist/server/server/agent/providers/opencode/test-server-manager.js +13 -3
  26. package/dist/server/server/agent/providers/opencode/test-utils/{test-opencode-runtime.d.ts → test-opencode-harness.d.ts} +11 -11
  27. package/dist/server/server/agent/providers/opencode/test-utils/{test-opencode-runtime.js → test-opencode-harness.js} +23 -10
  28. package/dist/server/server/agent/providers/opencode-agent.d.ts +9 -3
  29. package/dist/server/server/agent/providers/opencode-agent.js +26 -38
  30. package/dist/server/server/agent/providers/pi/agent.d.ts +4 -2
  31. package/dist/server/server/agent/providers/pi/agent.js +8 -3
  32. package/dist/server/server/agent/providers/pi/cli-runtime.d.ts +3 -0
  33. package/dist/server/server/agent/providers/pi/cli-runtime.js +6 -3
  34. package/dist/server/server/agent/providers/pi/rpc-types.d.ts +2 -1
  35. package/dist/server/server/agent/providers/provider-image-output.d.ts +5 -0
  36. package/dist/server/server/agent/providers/provider-image-output.js +55 -0
  37. package/dist/server/server/agent/tools/paseo-tools.d.ts +48 -0
  38. package/dist/server/server/agent/tools/paseo-tools.js +2121 -0
  39. package/dist/server/server/agent/tools/types.d.ts +36 -0
  40. package/dist/server/server/agent/tools/types.js +2 -0
  41. package/dist/server/server/bootstrap.js +71 -62
  42. package/dist/server/server/persisted-config.d.ts +5 -0
  43. package/dist/server/server/persisted-config.js +10 -2
  44. package/dist/server/server/session/agent-updates/agent-updates-service.d.ts +59 -0
  45. package/dist/server/server/session/agent-updates/agent-updates-service.js +220 -0
  46. package/dist/server/server/session/checkout/checkout-session.d.ts +13 -15
  47. package/dist/server/server/session/checkout/checkout-session.js +18 -16
  48. package/dist/server/server/session/checkout/git-metadata-generator.d.ts +53 -0
  49. package/dist/server/server/session/checkout/git-metadata-generator.js +159 -0
  50. package/dist/server/server/session/daemon/daemon-session.d.ts +14 -0
  51. package/dist/server/server/session/daemon/daemon-session.js +38 -0
  52. package/dist/server/server/session/daemon/diagnostics.d.ts +41 -0
  53. package/dist/server/server/session/daemon/diagnostics.js +421 -0
  54. package/dist/server/server/session/git-mutation/git-mutation-service.d.ts +34 -0
  55. package/dist/server/server/session/git-mutation/git-mutation-service.js +71 -0
  56. package/dist/server/server/session/workspace-git-observer/workspace-git-observer-service.d.ts +36 -0
  57. package/dist/server/server/session/workspace-git-observer/workspace-git-observer-service.js +134 -0
  58. package/dist/server/server/session/workspace-provisioning/workspace-provisioning-service.d.ts +34 -0
  59. package/dist/server/server/session/workspace-provisioning/workspace-provisioning-service.js +190 -0
  60. package/dist/server/server/session/workspace-scripts/workspace-scripts-service.d.ts +41 -0
  61. package/dist/server/server/session/workspace-scripts/workspace-scripts-service.js +100 -0
  62. package/dist/server/server/session.d.ts +7 -51
  63. package/dist/server/server/session.js +113 -938
  64. package/dist/server/server/speech/providers/openai/config.d.ts +1 -2
  65. package/dist/server/server/speech/providers/openai/config.js +13 -9
  66. package/dist/server/server/speech/providers/openai/runtime.js +2 -16
  67. package/dist/server/server/speech/providers/openai/stt.d.ts +1 -0
  68. package/dist/server/server/speech/providers/openai/stt.js +4 -2
  69. package/dist/server/server/speech/providers/openai/tts.d.ts +1 -0
  70. package/dist/server/server/speech/providers/openai/tts.js +1 -0
  71. package/dist/server/server/websocket/runtime-metrics.d.ts +20 -0
  72. package/dist/server/server/websocket-server.d.ts +1 -2
  73. package/dist/server/server/websocket-server.js +26 -21
  74. package/dist/server/server/worktree-bootstrap.d.ts +1 -1
  75. package/dist/server/server/worktree-branch-name-generator.js +3 -1
  76. package/dist/server/utils/checkout-git.js +51 -26
  77. package/dist/src/executable-resolution/windows.js +3 -0
  78. package/dist/src/server/persisted-config.js +10 -2
  79. package/package.json +5 -5
  80. package/dist/server/server/agent/providers/opencode/runtime.d.ts +0 -28
  81. package/dist/server/server/agent/providers/opencode/runtime.js +0 -5
  82. package/dist/server/server/speech/providers/openai/realtime-transcription-session.d.ts +0 -42
  83. package/dist/server/server/speech/providers/openai/realtime-transcription-session.js +0 -168
@@ -17,6 +17,7 @@ import { realClaudeRewindSdk, revertClaudeConversation, revertClaudeFiles } from
17
17
  import { normalizeProviderReplayTimestamp } from "../../provider-history-timestamps.js";
18
18
  import { claudeProjectDirSync } from "./project-dir.js";
19
19
  import { SETTING_APPLIES_NEXT_TURN_NOTICE } from "../../provider-notices.js";
20
+ import { isProviderImageMarkdown, materializeProviderImage, renderProviderImageOutputAsAssistantMarkdown, } from "../provider-image-output.js";
20
21
  import { getAgentStreamEventTurnId, } from "../../agent-sdk-types.js";
21
22
  import { importSessionFromPersistence } from "../../provider-session-import.js";
22
23
  import { checkProviderLaunchAvailable, createProviderEnv, createProviderEnvSpec, resolveProviderLaunch, } from "../../provider-launch-config.js";
@@ -370,6 +371,39 @@ function coerceToolResultContentToString(content) {
370
371
  }
371
372
  return deterministicStringify(content);
372
373
  }
374
+ function toBase64ImageOutput(block) {
375
+ const record = toObjectRecord(block);
376
+ if (!record || record.type !== "image") {
377
+ return null;
378
+ }
379
+ const source = toObjectRecord(record.source);
380
+ if (!source || source.type !== "base64" || typeof source.data !== "string") {
381
+ return null;
382
+ }
383
+ return {
384
+ data: source.data,
385
+ mimeType: typeof source.media_type === "string" ? source.media_type : null,
386
+ };
387
+ }
388
+ // Claude returns images inside tool_result content as base64 Anthropic blocks. Left in place they
389
+ // reach coerceToolResultContentToString, which JSON.stringifies the whole array — dumping base64
390
+ // into the tool output. We pull those blocks out to render them as image markdown and leave a
391
+ // "[image]" placeholder so image-only results still produce non-empty output.
392
+ function splitClaudeToolResultImages(content) {
393
+ if (!Array.isArray(content)) {
394
+ return { images: [], text: content };
395
+ }
396
+ const images = [];
397
+ const text = content.map((block) => {
398
+ const image = toBase64ImageOutput(block);
399
+ if (image) {
400
+ images.push(image);
401
+ return { type: "text", text: "[image]" };
402
+ }
403
+ return block;
404
+ });
405
+ return { images, text };
406
+ }
373
407
  function normalizeClaudeTranscriptText(value) {
374
408
  if (typeof value !== "string") {
375
409
  return null;
@@ -1252,23 +1286,6 @@ function readLegacyResultUsageTokens(usage) {
1252
1286
  const usageRecord = toObjectRecord(usage);
1253
1287
  return usageRecord ? readUsageTokenTotal(usageRecord) : undefined;
1254
1288
  }
1255
- function readCurrentContextUsage(value) {
1256
- const record = toObjectRecord(value);
1257
- if (!record) {
1258
- return undefined;
1259
- }
1260
- const totalTokens = record.totalTokens;
1261
- if (typeof totalTokens !== "number" || !Number.isFinite(totalTokens) || totalTokens < 0) {
1262
- return undefined;
1263
- }
1264
- const maxTokens = record.maxTokens;
1265
- return {
1266
- totalTokens,
1267
- ...(typeof maxTokens === "number" && Number.isFinite(maxTokens) && maxTokens > 0
1268
- ? { maxTokens }
1269
- : {}),
1270
- };
1271
- }
1272
1289
  function isClaudeSubagentToolName(name) {
1273
1290
  return name === "Task" || name === "Agent";
1274
1291
  }
@@ -1280,6 +1297,7 @@ class ClaudeContextUsageState {
1280
1297
  beginTurn() {
1281
1298
  this.streamRequestInputTokens = undefined;
1282
1299
  this.streamRequestOutputTokens = undefined;
1300
+ this.compactedContextWindowUsedTokens = undefined;
1283
1301
  }
1284
1302
  setInitialContextWindowMaxTokens(contextWindowMaxTokens) {
1285
1303
  this.contextWindowMaxTokens = contextWindowMaxTokens;
@@ -1291,11 +1309,6 @@ class ClaudeContextUsageState {
1291
1309
  }
1292
1310
  return this.contextWindowMaxTokens;
1293
1311
  }
1294
- recordCurrentContextUsage(usage) {
1295
- if (usage?.maxTokens !== undefined) {
1296
- this.contextWindowMaxTokens = usage.maxTokens;
1297
- }
1298
- }
1299
1312
  buildStreamUsageEvent(event) {
1300
1313
  const streamEvent = toObjectRecord(event);
1301
1314
  if (!streamEvent) {
@@ -1326,7 +1339,7 @@ class ClaudeContextUsageState {
1326
1339
  }
1327
1340
  return this.createUsageUpdatedEvent(usedTokens);
1328
1341
  }
1329
- buildResultUsage(message, modelUsage, currentContextUsage) {
1342
+ buildResultUsage(message, modelUsage) {
1330
1343
  try {
1331
1344
  if (!message.usage) {
1332
1345
  return undefined;
@@ -1338,7 +1351,6 @@ class ClaudeContextUsageState {
1338
1351
  totalCostUsd: message.total_cost_usd,
1339
1352
  };
1340
1353
  const modelContextWindowMaxTokens = this.recordModelUsage(modelUsage ?? message.modelUsage);
1341
- this.recordCurrentContextUsage(currentContextUsage);
1342
1354
  if (this.contextWindowMaxTokens !== undefined) {
1343
1355
  usage.contextWindowMaxTokens = this.contextWindowMaxTokens;
1344
1356
  }
@@ -1347,13 +1359,14 @@ class ClaudeContextUsageState {
1347
1359
  }
1348
1360
  const activeResultUsageTokens = readActiveUsageTokens(message.usage) ??
1349
1361
  (this.completedResultTurns === 0 ? readLegacyResultUsageTokens(message.usage) : undefined);
1350
- const usedTokens = currentContextUsage?.totalTokens ?? this.streamUsedTokens() ?? activeResultUsageTokens;
1362
+ const usedTokens = this.streamUsedTokens() ?? activeResultUsageTokens ?? this.compactedContextWindowUsedTokens;
1351
1363
  if (usedTokens !== undefined) {
1352
1364
  usage.contextWindowUsedTokens = usedTokens;
1353
1365
  }
1354
1366
  return usage;
1355
1367
  }
1356
1368
  finally {
1369
+ this.compactedContextWindowUsedTokens = undefined;
1357
1370
  this.completedResultTurns += 1;
1358
1371
  }
1359
1372
  }
@@ -1362,7 +1375,8 @@ class ClaudeContextUsageState {
1362
1375
  typeof this.streamRequestOutputTokens !== "number") {
1363
1376
  return undefined;
1364
1377
  }
1365
- return this.streamRequestInputTokens + this.streamRequestOutputTokens;
1378
+ const usedTokens = this.streamRequestInputTokens + this.streamRequestOutputTokens;
1379
+ return usedTokens > 0 ? usedTokens : undefined;
1366
1380
  }
1367
1381
  createUsageUpdatedEvent(contextWindowUsedTokens) {
1368
1382
  const usage = {
@@ -1377,6 +1391,23 @@ class ClaudeContextUsageState {
1377
1391
  usage,
1378
1392
  };
1379
1393
  }
1394
+ buildCompactionUsageEvent(postTokens) {
1395
+ this.streamRequestInputTokens = undefined;
1396
+ this.streamRequestOutputTokens = undefined;
1397
+ this.compactedContextWindowUsedTokens = postTokens;
1398
+ const usage = {};
1399
+ if (this.contextWindowMaxTokens !== undefined) {
1400
+ usage.contextWindowMaxTokens = this.contextWindowMaxTokens;
1401
+ }
1402
+ if (postTokens !== undefined) {
1403
+ usage.contextWindowUsedTokens = postTokens;
1404
+ }
1405
+ return {
1406
+ type: "usage_updated",
1407
+ provider: "claude",
1408
+ usage,
1409
+ };
1410
+ }
1380
1411
  }
1381
1412
  class ClaudeAgentSession {
1382
1413
  constructor(config, options) {
@@ -2659,7 +2690,7 @@ class ClaudeAgentSession {
2659
2690
  if (await this.handleMissingResumedConversation(message, activeQuery)) {
2660
2691
  return true;
2661
2692
  }
2662
- await this.routeSdkMessageFromPump(message, activeQuery);
2693
+ await this.routeSdkMessageFromPump(message);
2663
2694
  return false;
2664
2695
  };
2665
2696
  const drainActiveQuery = async () => {
@@ -2727,7 +2758,7 @@ class ClaudeAgentSession {
2727
2758
  message.type === "tool_progress" ||
2728
2759
  (message.type === "system" && message.subtype === "task_notification"));
2729
2760
  }
2730
- async routeSdkMessageFromPump(message, activeQuery) {
2761
+ async routeSdkMessageFromPump(message) {
2731
2762
  if (this.shouldSuppressStaleResult(message)) {
2732
2763
  return;
2733
2764
  }
@@ -2750,7 +2781,7 @@ class ClaudeAgentSession {
2750
2781
  identifiers,
2751
2782
  rawEvent: message,
2752
2783
  }, "provider.claude.parsed_event");
2753
- const events = await this.buildPumpedMessageEvents(message, activeQuery, identifiers.messageId, turnId);
2784
+ const events = await this.buildPumpedMessageEvents(message, identifiers.messageId, turnId);
2754
2785
  if (events.length === 0) {
2755
2786
  return;
2756
2787
  }
@@ -2773,14 +2804,10 @@ class ClaudeAgentSession {
2773
2804
  }
2774
2805
  this.dispatchEvents(events);
2775
2806
  }
2776
- async buildPumpedMessageEvents(message, activeQuery, messageIdHint, turnId) {
2777
- const currentContextUsage = message.type === "result" && message.subtype === "success"
2778
- ? await this.queryCurrentContextUsage(activeQuery)
2779
- : undefined;
2807
+ async buildPumpedMessageEvents(message, messageIdHint, turnId) {
2780
2808
  const messageEvents = this.translateMessageToEvents(message, {
2781
2809
  suppressAssistantText: true,
2782
2810
  suppressReasoning: true,
2783
- currentContextUsage,
2784
2811
  });
2785
2812
  const assistantTimelineEvents = this.timelineAssembler
2786
2813
  .consume({
@@ -2795,16 +2822,6 @@ class ClaudeAgentSession {
2795
2822
  }));
2796
2823
  return [...messageEvents, ...assistantTimelineEvents];
2797
2824
  }
2798
- async queryCurrentContextUsage(activeQuery) {
2799
- try {
2800
- const usage = await withTimeout(activeQuery.getContextUsage(), 3000, "timeout");
2801
- return readCurrentContextUsage(usage);
2802
- }
2803
- catch (error) {
2804
- this.logger.debug({ err: error }, "Claude context usage query failed");
2805
- return undefined;
2806
- }
2807
- }
2808
2825
  async handleMissingResumedConversation(message, activeQuery) {
2809
2826
  const staleResumeError = this.readMissingResumedConversationError(message);
2810
2827
  if (!staleResumeError) {
@@ -2895,9 +2912,7 @@ class ClaudeAgentSession {
2895
2912
  this.appendStreamEventEvents(message, events, options);
2896
2913
  break;
2897
2914
  case "result":
2898
- this.appendResultEvents(message, events, {
2899
- currentContextUsage: options?.currentContextUsage,
2900
- });
2915
+ this.appendResultEvents(message, events);
2901
2916
  break;
2902
2917
  default:
2903
2918
  break;
@@ -2963,6 +2978,7 @@ class ClaudeAgentSession {
2963
2978
  },
2964
2979
  provider: "claude",
2965
2980
  });
2981
+ events.push(this.contextUsage.buildCompactionUsageEvent(compactMetadata?.postTokens));
2966
2982
  return;
2967
2983
  }
2968
2984
  if (message.subtype === "task_notification") {
@@ -3067,8 +3083,8 @@ class ClaudeAgentSession {
3067
3083
  events.push({ type: "timeline", item, provider: "claude" });
3068
3084
  }
3069
3085
  }
3070
- appendResultEvents(message, events, options) {
3071
- const usage = this.convertUsage(message, message.modelUsage, options?.currentContextUsage);
3086
+ appendResultEvents(message, events) {
3087
+ const usage = this.convertUsage(message, message.modelUsage);
3072
3088
  if (message.subtype === "success") {
3073
3089
  // Built-in slash commands (e.g. /voice, /usage, "Unknown command: …")
3074
3090
  // run client-side in the Claude CLI with no model turn — output_tokens
@@ -3212,8 +3228,8 @@ class ClaudeAgentSession {
3212
3228
  }
3213
3229
  return null;
3214
3230
  }
3215
- convertUsage(message, modelUsage, currentContextUsage) {
3216
- return this.contextUsage.buildResultUsage(message, modelUsage, currentContextUsage);
3231
+ convertUsage(message, modelUsage) {
3232
+ return this.contextUsage.buildResultUsage(message, modelUsage);
3217
3233
  }
3218
3234
  enqueueTimeline(item) {
3219
3235
  this.pushEvent({ type: "timeline", item, provider: "claude" });
@@ -3490,15 +3506,17 @@ class ClaudeAgentSession {
3490
3506
  const callId = typeof block.tool_use_id === "string" && block.tool_use_id.length > 0
3491
3507
  ? block.tool_use_id
3492
3508
  : (entry?.id ?? null);
3493
- // Extract output from block.content (SDK always returns content in string form)
3494
- const output = this.buildToolOutput(block, entry);
3509
+ // Pull image blocks out of the result so base64 never reaches the tool output, and render each
3510
+ // one as an assistant_message markdown image after the tool_call (matching how Codex emits).
3511
+ const { images, text } = splitClaudeToolResultImages(block.content);
3512
+ const output = this.buildToolOutput(text, block, entry);
3495
3513
  if (block.is_error) {
3496
3514
  this.pushToolCall(mapClaudeFailedToolCall({
3497
3515
  name: toolName,
3498
3516
  callId,
3499
3517
  input: entry?.input ?? null,
3500
3518
  output: output ?? null,
3501
- error: block,
3519
+ error: { ...block, content: text },
3502
3520
  }), items);
3503
3521
  }
3504
3522
  else {
@@ -3509,12 +3527,20 @@ class ClaudeAgentSession {
3509
3527
  output: output ?? null,
3510
3528
  }), items);
3511
3529
  }
3530
+ for (const image of images) {
3531
+ const imageItem = renderProviderImageOutputAsAssistantMarkdown(image, {
3532
+ materialize: materializeProviderImage,
3533
+ });
3534
+ if (imageItem) {
3535
+ items.push(imageItem);
3536
+ }
3537
+ }
3512
3538
  if (typeof block.tool_use_id === "string") {
3513
3539
  this.toolUseCache.delete(block.tool_use_id);
3514
3540
  this.sidechainTracker.delete(block.tool_use_id);
3515
3541
  }
3516
3542
  }
3517
- buildToolOutput(block, entry) {
3543
+ buildToolOutput(content, block, entry) {
3518
3544
  if (block.is_error) {
3519
3545
  return undefined;
3520
3546
  }
@@ -3522,23 +3548,23 @@ class ClaudeAgentSession {
3522
3548
  const blockToolName = typeof block.tool_name === "string" ? block.tool_name : undefined;
3523
3549
  const server = entry?.server ?? blockServer ?? "tool";
3524
3550
  const tool = entry?.name ?? blockToolName ?? "tool";
3525
- const content = coerceToolResultContentToString(block.content);
3551
+ const coercedContent = coerceToolResultContentToString(content);
3526
3552
  const input = entry?.input;
3527
3553
  // Build structured result based on tool type
3528
- const structured = this.buildStructuredToolResult(server, tool, content, input);
3554
+ const structured = this.buildStructuredToolResult(server, tool, coercedContent, input);
3529
3555
  if (structured) {
3530
3556
  return structured;
3531
3557
  }
3532
3558
  // Fallback format - try to parse JSON first
3533
3559
  const result = {};
3534
- if (content.length > 0) {
3560
+ if (coercedContent.length > 0) {
3535
3561
  try {
3536
3562
  // If content is a JSON string, parse it
3537
- result.output = JSON.parse(content);
3563
+ result.output = JSON.parse(coercedContent);
3538
3564
  }
3539
3565
  catch {
3540
3566
  // If not JSON, return unchanged (no extra wrapping)
3541
- result.output = content;
3567
+ result.output = coercedContent;
3542
3568
  }
3543
3569
  }
3544
3570
  // Preserve file changes tracked during tool execution
@@ -3899,7 +3925,9 @@ function readCompactionMetadata(source) {
3899
3925
  const trigger = typeof metadata.trigger === "string" ? metadata.trigger : undefined;
3900
3926
  const preTokensRaw = metadata.preTokens ?? metadata.pre_tokens;
3901
3927
  const preTokens = typeof preTokensRaw === "number" ? preTokensRaw : undefined;
3902
- return { trigger, preTokens };
3928
+ const postTokensRaw = metadata.postTokens ?? metadata.post_tokens;
3929
+ const postTokens = typeof postTokensRaw === "number" ? postTokensRaw : undefined;
3930
+ return { trigger, preTokens, postTokens };
3903
3931
  }
3904
3932
  return null;
3905
3933
  }
@@ -3961,6 +3989,9 @@ function convertClaudeHistoryEntryPreamble(entry) {
3961
3989
  }
3962
3990
  return { proceed: { content } };
3963
3991
  }
3992
+ function isProviderImageMessage(item) {
3993
+ return item.type === "assistant_message" && isProviderImageMarkdown(item.text);
3994
+ }
3964
3995
  export function convertClaudeHistoryEntry(entry, mapBlocks) {
3965
3996
  const preamble = convertClaudeHistoryEntryPreamble(entry);
3966
3997
  if ("shortCircuit" in preamble) {
@@ -3996,7 +4027,10 @@ export function convertClaudeHistoryEntry(entry, mapBlocks) {
3996
4027
  if (hasToolBlock && normalizedBlocks) {
3997
4028
  const mapped = mapBlocks(normalizedBlocks);
3998
4029
  if (entry.type === "user") {
3999
- const toolItems = mapped.filter((item) => item.type === "tool_call");
4030
+ // tool_result handling (handleToolResult) emits image markdown as an assistant_message
4031
+ // alongside the tool_call. User-entry text blocks also map to assistant_message in this path
4032
+ // and must stay suppressed, so keep tool_calls plus only the image assistant_messages.
4033
+ const toolItems = mapped.filter((item) => item.type === "tool_call" || isProviderImageMessage(item));
4000
4034
  return timeline.length ? [...timeline, ...toolItems] : toolItems;
4001
4035
  }
4002
4036
  return mapped;
@@ -1,7 +1,6 @@
1
1
  import { getAgentStreamEventTurnId, } from "../agent-sdk-types.js";
2
2
  import { importSessionFromPersistence } from "../provider-session-import.js";
3
3
  import { randomUUID } from "node:crypto";
4
- import * as fsSync from "node:fs";
5
4
  import fs from "node:fs/promises";
6
5
  import os from "node:os";
7
6
  import path from "node:path";
@@ -18,7 +17,7 @@ import { extractCodexTerminalSessionId, nonEmptyString } from "./tool-call-mappe
18
17
  import { buildCodexFeatures, codexModelSupportsFastMode } from "./codex-feature-definitions.js";
19
18
  import { CodexAppServerClient, parseCodexThreadForkResponse, parseCodexThreadRollbackResponse, } from "./codex/app-server-transport.js";
20
19
  import { revertCodexConversation } from "./codex/rewind.js";
21
- import { renderProviderImageOutputAsAssistantMarkdown, } from "./provider-image-output.js";
20
+ import { materializeProviderImage, renderProviderImageOutputAsAssistantMarkdown, } from "./provider-image-output.js";
22
21
  import { normalizeProviderReplayTimestamp } from "../provider-history-timestamps.js";
23
22
  import { formatProviderDiagnostic, formatProviderDiagnosticError, buildBinaryDiagnosticRows, buildCommandResolutionDiagnosticRows, resolveBinaryVersion, } from "./diagnostic-utils.js";
24
23
  import { runProviderTurn } from "./provider-runner.js";
@@ -38,7 +37,6 @@ function isCodexAlreadyUnarchivedError(error, threadId) {
38
37
  const TURN_START_TIMEOUT_MS = 90 * 1000;
39
38
  const INTERRUPT_TIMEOUT_MS = 2000;
40
39
  const CODEX_PROVIDER = "codex";
41
- const CODEX_IMAGE_ATTACHMENT_DIR = "paseo-attachments";
42
40
  // Codex treats most app-server client names as the model-request originator.
43
41
  // This reserved Codex name is non-originating, so requests keep Codex's default
44
42
  // CLI identity instead of showing up as Paseo in provider usage logs.
@@ -1220,21 +1218,6 @@ function codexImageOutputFromResult(result) {
1220
1218
  mimeType: firstStringField(resultRecord, ["mimeType", "mime_type"]),
1221
1219
  };
1222
1220
  }
1223
- function writeImageAttachmentSync(mimeType, data) {
1224
- const attachmentsDir = path.join(os.tmpdir(), CODEX_IMAGE_ATTACHMENT_DIR);
1225
- fsSync.mkdirSync(attachmentsDir, { recursive: true });
1226
- const normalized = normalizeImageData(mimeType, data);
1227
- const extension = getImageExtension(normalized.mimeType);
1228
- const filename = `${randomUUID()}.${extension}`;
1229
- const filePath = path.join(attachmentsDir, filename);
1230
- fsSync.writeFileSync(filePath, Buffer.from(normalized.data, "base64"));
1231
- return filePath;
1232
- }
1233
- function materializeCodexImageOutput(image) {
1234
- return {
1235
- path: writeImageAttachmentSync(image.mimeType ?? "image/png", image.data),
1236
- };
1237
- }
1238
1221
  function mapCodexThreadImageItem(normalizedType, normalizedItem) {
1239
1222
  if (normalizedType === "imageView") {
1240
1223
  return renderProviderImageOutputAsAssistantMarkdown({
@@ -1248,7 +1231,7 @@ function mapCodexThreadImageItem(normalizedType, normalizedItem) {
1248
1231
  url: result?.url ?? null,
1249
1232
  data: result?.data ?? null,
1250
1233
  mimeType: result?.mimeType ?? null,
1251
- }, { materialize: materializeCodexImageOutput });
1234
+ }, { materialize: materializeProviderImage });
1252
1235
  }
1253
1236
  export function threadItemToTimeline(item, options) {
1254
1237
  const itemRecord = toObjectRecord(item);
@@ -1357,33 +1340,6 @@ function toSandboxPolicy(type, networkAccess) {
1357
1340
  return { type: "workspaceWrite", networkAccess: networkAccess ?? false };
1358
1341
  }
1359
1342
  }
1360
- function getImageExtension(mimeType) {
1361
- switch (mimeType) {
1362
- case "image/jpeg":
1363
- return "jpg";
1364
- case "image/png":
1365
- return "png";
1366
- case "image/webp":
1367
- return "webp";
1368
- case "image/gif":
1369
- return "gif";
1370
- case "image/bmp":
1371
- return "bmp";
1372
- case "image/tiff":
1373
- return "tiff";
1374
- default:
1375
- return "bin";
1376
- }
1377
- }
1378
- function normalizeImageData(mimeType, data) {
1379
- if (data.startsWith("data:")) {
1380
- const match = data.match(/^data:([^;]+);base64,(.*)$/);
1381
- if (match) {
1382
- return { mimeType: match[1], data: match[2] };
1383
- }
1384
- }
1385
- return { mimeType, data };
1386
- }
1387
1343
  const ThreadStartedNotificationSchema = z
1388
1344
  .object({
1389
1345
  thread: z.object({ id: z.string() }).passthrough(),
@@ -2008,16 +1964,6 @@ const CodexNotificationSchema = z.union([
2008
1964
  .object({ method: z.string(), params: z.unknown() })
2009
1965
  .transform(({ method, params }) => ({ kind: "unknown_method", method, params })),
2010
1966
  ]);
2011
- async function writeImageAttachment(mimeType, data) {
2012
- const attachmentsDir = path.join(os.tmpdir(), CODEX_IMAGE_ATTACHMENT_DIR);
2013
- await fs.mkdir(attachmentsDir, { recursive: true });
2014
- const normalized = normalizeImageData(mimeType, data);
2015
- const extension = getImageExtension(normalized.mimeType);
2016
- const filename = `${randomUUID()}.${extension}`;
2017
- const filePath = path.join(attachmentsDir, filename);
2018
- await fs.writeFile(filePath, Buffer.from(normalized.data, "base64"));
2019
- return filePath;
2020
- }
2021
1967
  async function readCodexConfiguredDefaults(client, logger) {
2022
1968
  let savedConfigDefaults = {};
2023
1969
  try {
@@ -2071,7 +2017,10 @@ export async function codexAppServerTurnInputFromPrompt(prompt, logger) {
2071
2017
  }
2072
2018
  if (block.type === "image") {
2073
2019
  try {
2074
- const filePath = await writeImageAttachment(block.mimeType, block.data);
2020
+ const filePath = materializeProviderImage({
2021
+ data: block.data,
2022
+ mimeType: block.mimeType,
2023
+ }).path;
2075
2024
  output.push({ type: "localImage", path: filePath });
2076
2025
  }
2077
2026
  catch (error) {
@@ -2,8 +2,9 @@ import type { Logger } from "pino";
2
2
  import type { SessionConfigOption } from "@agentclientprotocol/sdk";
3
3
  import type { AgentMode } from "../agent-sdk-types.js";
4
4
  import { type ProviderRuntimeSettings } from "../provider-launch-config.js";
5
- import { ACPAgentClient, type ACPBeforeModeWriteResult, type ACPProviderModeWriteResult, type ACPProviderModeWriterContext, type SessionStateResponse } from "./acp-agent.js";
5
+ import { ACPAgentClient, type ACPConfigFeatureOption, type ACPBeforeModeWriteResult, type ACPProviderModeWriteResult, type ACPProviderModeWriterContext, type SessionStateResponse } from "./acp-agent.js";
6
6
  export declare const COPILOT_ALLOW_ALL_MODE_ID = "allow-all";
7
+ export declare const COPILOT_AGENT_FEATURE_OPTION: ACPConfigFeatureOption;
7
8
  export declare const COPILOT_MODES: AgentMode[];
8
9
  interface CopilotACPAgentClientOptions {
9
10
  logger: Logger;
@@ -20,6 +20,15 @@ export const COPILOT_ALLOW_ALL_MODE_ID = "allow-all";
20
20
  const COPILOT_ALLOW_ALL_CONFIG_ID = "allow_all";
21
21
  const COPILOT_ALLOW_ALL_ON = "on";
22
22
  const COPILOT_ALLOW_ALL_OFF = "off";
23
+ export const COPILOT_AGENT_FEATURE_OPTION = {
24
+ id: "agent",
25
+ configId: "agent",
26
+ category: "_agent",
27
+ label: "Agent",
28
+ description: "Use a Copilot custom agent profile",
29
+ tooltip: "Select Copilot agent",
30
+ emptyOptionLabel: "Default",
31
+ };
23
32
  export const COPILOT_MODES = [
24
33
  {
25
34
  id: COPILOT_AGENT_MODE_ID,
@@ -47,6 +56,7 @@ export class CopilotACPAgentClient extends ACPAgentClient {
47
56
  defaultModes: COPILOT_MODES,
48
57
  sessionResponseTransformer: transformCopilotSessionResponse,
49
58
  configOptionsTransformer: transformCopilotConfigOptions,
59
+ configFeatureOptions: [COPILOT_AGENT_FEATURE_OPTION],
50
60
  modeIdTransformer: transformCopilotModeId,
51
61
  providerModeWriter: writeCopilotProviderMode,
52
62
  beforeModeWriter: beforeCopilotModeWriter,
@@ -10,6 +10,7 @@ export declare function formatDiagnosticStatus(available: boolean, error?: {
10
10
  source: string;
11
11
  cause: unknown;
12
12
  }): string;
13
+ export declare function truncateForDiagnostic(value: string): string;
13
14
  export declare function toDiagnosticErrorMessage(error: unknown): string;
14
15
  export declare function resolveBinaryVersion(binaryPath: string): Promise<string>;
15
16
  export interface BinaryDiagnosticVersionCommand {
@@ -24,7 +24,7 @@ export function formatDiagnosticStatus(available, error) {
24
24
  return formatAvailabilityStatus(available);
25
25
  }
26
26
  const DIAGNOSTIC_OUTPUT_CAP = 4096;
27
- function truncateForDiagnostic(value) {
27
+ export function truncateForDiagnostic(value) {
28
28
  const trimmed = value.trim();
29
29
  if (trimmed.length <= DIAGNOSTIC_OUTPUT_CAP) {
30
30
  return trimmed;
@@ -13,11 +13,13 @@ interface GenericACPAgentClientOptions {
13
13
  providerParams?: unknown;
14
14
  waitForInitialCommands?: boolean;
15
15
  initialCommandsWaitTimeoutMs?: number;
16
+ diagnosticPhaseTimeoutMs?: number;
16
17
  }
17
18
  export declare class GenericACPAgentClient extends ACPAgentClient {
18
19
  private readonly command;
19
20
  private readonly providerId?;
20
21
  private readonly label?;
22
+ private readonly diagnosticPhaseTimeoutMs?;
21
23
  constructor(options: GenericACPAgentClientOptions);
22
24
  protected resolveLaunchCommand(): Promise<{
23
25
  command: string;
@@ -28,6 +30,7 @@ export declare class GenericACPAgentClient extends ACPAgentClient {
28
30
  diagnostic: string;
29
31
  }>;
30
32
  private resolveConfiguredLaunch;
33
+ private getACPProbeRowsForDiagnostic;
31
34
  }
32
35
  export interface CommandInvocation {
33
36
  command: string;
@@ -1,7 +1,7 @@
1
1
  import { z } from "zod";
2
2
  import { checkProviderLaunchAvailable, resolveProviderLaunch } from "../provider-launch-config.js";
3
3
  import { ACPAgentClient, DEFAULT_ACP_CAPABILITIES } from "./acp-agent.js";
4
- import { formatProviderDiagnostic, formatProviderDiagnosticError, buildBinaryDiagnosticRows, } from "./diagnostic-utils.js";
4
+ import { buildBinaryDiagnosticRows, formatProviderDiagnostic, toDiagnosticErrorMessage, } from "./diagnostic-utils.js";
5
5
  export const GenericACPProviderParamsSchema = z
6
6
  .object({
7
7
  supportsMcpServers: z.boolean().optional(),
@@ -23,6 +23,7 @@ export class GenericACPAgentClient extends ACPAgentClient {
23
23
  this.command = options.command;
24
24
  this.providerId = options.providerId;
25
25
  this.label = options.label;
26
+ this.diagnosticPhaseTimeoutMs = options.diagnosticPhaseTimeoutMs;
26
27
  }
27
28
  async resolveLaunchCommand() {
28
29
  return {
@@ -37,34 +38,36 @@ export class GenericACPAgentClient extends ACPAgentClient {
37
38
  }
38
39
  async getDiagnostic() {
39
40
  const providerName = formatProviderName(this.label, this.providerId);
41
+ const entries = [
42
+ { label: "Provider ID", value: this.providerId ?? "unknown" },
43
+ { label: "Configured command", value: this.command.join(" ") },
44
+ ];
45
+ const versionProbe = buildVersionProbeCommand(this.command);
40
46
  try {
41
47
  const launch = await this.resolveConfiguredLaunch();
42
48
  const availability = await checkProviderLaunchAvailable(launch);
43
- const versionProbe = buildVersionProbeCommand(this.command);
44
- return {
45
- diagnostic: formatProviderDiagnostic(providerName, [
46
- { label: "Provider ID", value: this.providerId ?? "unknown" },
47
- { label: "Configured command", value: this.command.join(" ") },
48
- ...(await buildBinaryDiagnosticRows(launch, availability, {
49
- binaryLabel: "Launcher binary",
50
- versionCommand: {
51
- command: versionProbe.command,
52
- args: versionProbe.args,
53
- env: this.runtimeSettings?.env,
54
- },
55
- })),
56
- {
57
- label: "Version command",
58
- value: formatCommand(versionProbe.command, versionProbe.args),
59
- },
60
- ]),
61
- };
49
+ entries.push(...(await buildBinaryDiagnosticRows(launch, availability, {
50
+ binaryLabel: "Launcher binary",
51
+ versionCommand: {
52
+ command: versionProbe.command,
53
+ args: versionProbe.args,
54
+ env: this.runtimeSettings?.env,
55
+ },
56
+ })));
62
57
  }
63
58
  catch (error) {
64
- return {
65
- diagnostic: formatProviderDiagnosticError(providerName, error),
66
- };
59
+ entries.push({
60
+ label: "Launcher binary",
61
+ value: `error: ${toDiagnosticErrorMessage(error)}`,
62
+ });
67
63
  }
64
+ entries.push({
65
+ label: "Version command",
66
+ value: formatCommand(versionProbe.command, versionProbe.args),
67
+ }, ...(await this.getACPProbeRowsForDiagnostic()));
68
+ return {
69
+ diagnostic: formatProviderDiagnostic(providerName, entries),
70
+ };
68
71
  }
69
72
  async resolveConfiguredLaunch() {
70
73
  return resolveProviderLaunch({
@@ -72,6 +75,21 @@ export class GenericACPAgentClient extends ACPAgentClient {
72
75
  defaultBinary: this.command[0],
73
76
  });
74
77
  }
78
+ async getACPProbeRowsForDiagnostic() {
79
+ try {
80
+ return await this.buildACPProbeDiagnosticRows({
81
+ phaseTimeoutMs: this.diagnosticPhaseTimeoutMs,
82
+ });
83
+ }
84
+ catch (error) {
85
+ return [
86
+ {
87
+ label: "ACP probe",
88
+ value: `error: ${toDiagnosticErrorMessage(error)}`,
89
+ },
90
+ ];
91
+ }
92
+ }
75
93
  }
76
94
  function buildGenericACPCapabilities(options) {
77
95
  const params = parseGenericACPProviderParams(options.providerParams);
@@ -167,7 +167,7 @@ function parseAgentStreamStressPrompt(prompt) {
167
167
  }
168
168
  function parseStructuredBranchNamePrompt(prompt) {
169
169
  const text = promptToText(prompt);
170
- const hasBranchNamePrompt = text.includes("Generate a git branch name for a coding agent") &&
170
+ const hasBranchNamePrompt = text.includes("Generate a title and a git branch name for a coding agent") &&
171
171
  (text.includes("Return JSON only with fields 'title' and 'branch'.") ||
172
172
  text.includes('"title"') ||
173
173
  text.includes('"branch"'));
@@ -177,7 +177,9 @@ function parseStructuredBranchNamePrompt(prompt) {
177
177
  text.includes('"branch"'))) {
178
178
  return null;
179
179
  }
180
- const seed = text.split("User context:\n").at(-1)?.trim() ?? "";
180
+ const seed = text.match(/<user-prompt>\n([\s\S]*?)\n<\/user-prompt>/)?.[1]?.trim() ??
181
+ text.match(/<attachments>\n([\s\S]*?)\n<\/attachments>/)?.[1]?.trim() ??
182
+ "";
181
183
  const firstLine = seed
182
184
  .split("\n")
183
185
  .find((line) => line.trim().length > 0)