@getpaseo/server 0.1.100 → 0.1.102-beta.2

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 (172) hide show
  1. package/dist/scripts/supervisor.js +26 -8
  2. package/dist/server/executable-resolution/windows.js +3 -0
  3. package/dist/server/server/agent/activity-curator.d.ts +17 -0
  4. package/dist/server/server/agent/activity-curator.js +101 -24
  5. package/dist/server/server/agent/agent-manager.d.ts +10 -0
  6. package/dist/server/server/agent/agent-manager.js +69 -27
  7. package/dist/server/server/agent/agent-sdk-types.d.ts +15 -2
  8. package/dist/server/server/agent/mcp-server.d.ts +2 -45
  9. package/dist/server/server/agent/mcp-server.js +45 -1985
  10. package/dist/server/server/agent/prompt-attachments.js +6 -2
  11. package/dist/server/server/agent/provider-snapshot-manager.d.ts +12 -1
  12. package/dist/server/server/agent/provider-snapshot-manager.js +132 -42
  13. package/dist/server/server/agent/providers/acp-agent.d.ts +27 -1
  14. package/dist/server/server/agent/providers/acp-agent.js +178 -27
  15. package/dist/server/server/agent/providers/claude/agent.js +111 -24
  16. package/dist/server/server/agent/providers/claude/query.d.ts +3 -0
  17. package/dist/server/server/agent/providers/claude/query.js +4 -2
  18. package/dist/server/server/agent/providers/codex-app-server-agent.js +6 -57
  19. package/dist/server/server/agent/providers/diagnostic-utils.d.ts +1 -0
  20. package/dist/server/server/agent/providers/diagnostic-utils.js +1 -1
  21. package/dist/server/server/agent/providers/generic-acp-agent.d.ts +3 -0
  22. package/dist/server/server/agent/providers/generic-acp-agent.js +41 -23
  23. package/dist/server/server/agent/providers/mock-load-test-agent.js +12 -2
  24. package/dist/server/server/agent/providers/opencode/paths.d.ts +2 -0
  25. package/dist/server/server/agent/providers/opencode/paths.js +7 -0
  26. package/dist/server/server/agent/providers/opencode/server-manager.d.ts +2 -0
  27. package/dist/server/server/agent/providers/opencode/server-manager.js +34 -5
  28. package/dist/server/server/agent/providers/opencode-agent.d.ts +4 -0
  29. package/dist/server/server/agent/providers/opencode-agent.js +14 -2
  30. package/dist/server/server/agent/providers/pi/agent.d.ts +5 -1
  31. package/dist/server/server/agent/providers/pi/agent.js +12 -3
  32. package/dist/server/server/agent/providers/provider-image-output.d.ts +5 -0
  33. package/dist/server/server/agent/providers/provider-image-output.js +61 -1
  34. package/dist/server/server/agent/tools/paseo-tools.d.ts +48 -0
  35. package/dist/server/server/agent/tools/paseo-tools.js +2119 -0
  36. package/dist/server/server/agent/tools/types.d.ts +36 -0
  37. package/dist/server/server/agent/tools/types.js +2 -0
  38. package/dist/server/server/bootstrap.d.ts +7 -1
  39. package/dist/server/server/bootstrap.js +89 -62
  40. package/dist/server/server/config.d.ts +2 -0
  41. package/dist/server/server/config.js +57 -1
  42. package/dist/server/server/daemon-worker.js +19 -7
  43. package/dist/server/server/lifecycle-reasons.d.ts +4 -0
  44. package/dist/server/server/lifecycle-reasons.js +6 -0
  45. package/dist/server/server/persisted-config.d.ts +12 -0
  46. package/dist/server/server/persisted-config.js +18 -2
  47. package/dist/server/server/process-diagnostics.d.ts +17 -0
  48. package/dist/server/server/process-diagnostics.js +22 -0
  49. package/dist/server/server/relay-transport.js +1 -0
  50. package/dist/server/server/resolve-worktree-creation-intent.js +3 -1
  51. package/dist/server/server/session/agent-updates/agent-updates-service.d.ts +59 -0
  52. package/dist/server/server/session/agent-updates/agent-updates-service.js +220 -0
  53. package/dist/server/server/session/checkout/checkout-session.d.ts +13 -15
  54. package/dist/server/server/session/checkout/checkout-session.js +18 -16
  55. package/dist/server/server/session/checkout/git-metadata-generator.d.ts +53 -0
  56. package/dist/server/server/session/checkout/git-metadata-generator.js +159 -0
  57. package/dist/server/server/session/daemon/daemon-self-update-session-controller.d.ts +32 -0
  58. package/dist/server/server/session/daemon/daemon-self-update-session-controller.js +88 -0
  59. package/dist/server/server/session/daemon/daemon-self-updater.d.ts +32 -0
  60. package/dist/server/server/session/daemon/daemon-self-updater.js +56 -0
  61. package/dist/server/server/session/daemon/daemon-session.d.ts +26 -0
  62. package/dist/server/server/session/daemon/daemon-session.js +50 -0
  63. package/dist/server/server/session/daemon/diagnostics.d.ts +41 -0
  64. package/dist/server/server/session/daemon/diagnostics.js +431 -0
  65. package/dist/server/server/session/daemon/install-origin.d.ts +7 -0
  66. package/dist/server/server/session/daemon/install-origin.js +64 -0
  67. package/dist/server/server/session/daemon/npm-global-cli.d.ts +29 -0
  68. package/dist/server/server/session/daemon/npm-global-cli.js +98 -0
  69. package/dist/server/server/session/git-mutation/git-mutation-service.d.ts +34 -0
  70. package/dist/server/server/session/git-mutation/git-mutation-service.js +71 -0
  71. package/dist/server/server/session/provider/provider-catalog-session.js +8 -4
  72. package/dist/server/server/session/workspace-git-observer/workspace-git-observer-service.d.ts +36 -0
  73. package/dist/server/server/session/workspace-git-observer/workspace-git-observer-service.js +134 -0
  74. package/dist/server/server/session/workspace-provisioning/workspace-provisioning-service.d.ts +34 -0
  75. package/dist/server/server/session/workspace-provisioning/workspace-provisioning-service.js +190 -0
  76. package/dist/server/server/session/workspace-scripts/workspace-scripts-service.d.ts +41 -0
  77. package/dist/server/server/session/workspace-scripts/workspace-scripts-service.js +100 -0
  78. package/dist/server/server/session.d.ts +12 -54
  79. package/dist/server/server/session.js +187 -970
  80. package/dist/server/server/speech/providers/openai/config.d.ts +1 -2
  81. package/dist/server/server/speech/providers/openai/config.js +13 -9
  82. package/dist/server/server/speech/providers/openai/runtime.js +2 -16
  83. package/dist/server/server/speech/providers/openai/stt.d.ts +1 -0
  84. package/dist/server/server/speech/providers/openai/stt.js +4 -2
  85. package/dist/server/server/speech/providers/openai/tts.d.ts +1 -0
  86. package/dist/server/server/speech/providers/openai/tts.js +1 -0
  87. package/dist/server/server/web-ui.d.ts +10 -0
  88. package/dist/server/server/web-ui.js +205 -0
  89. package/dist/server/server/websocket/runtime-metrics.d.ts +23 -0
  90. package/dist/server/server/websocket-server.d.ts +4 -2
  91. package/dist/server/server/websocket-server.js +215 -52
  92. package/dist/server/server/worktree-bootstrap.d.ts +1 -1
  93. package/dist/server/server/worktree-branch-name-generator.js +3 -1
  94. package/dist/server/services/quota-fetcher/manifest.js +5 -0
  95. package/dist/server/services/quota-fetcher/providers/minimax.d.ts +29 -0
  96. package/dist/server/services/quota-fetcher/providers/minimax.js +227 -0
  97. package/dist/server/terminal/agent-hooks/agent-hook-installer.js +2 -2
  98. package/dist/server/utils/checkout-git.js +203 -25
  99. package/dist/server/utils/directory-suggestions.js +1 -4
  100. package/dist/server/utils/path.d.ts +2 -0
  101. package/dist/server/utils/path.js +13 -0
  102. package/dist/server/utils/worktree.d.ts +1 -0
  103. package/dist/server/utils/worktree.js +92 -11
  104. package/dist/server/web-ui/_expo/static/css/xterm-3bb1704bf6cb0876640973dc0244b4cb.css +1 -0
  105. package/dist/server/web-ui/_expo/static/css/xterm-3bb1704bf6cb0876640973dc0244b4cb.css.br +0 -0
  106. package/dist/server/web-ui/_expo/static/css/xterm-3bb1704bf6cb0876640973dc0244b4cb.css.gz +0 -0
  107. package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-bridge-b01555c9b42665a03988c0a0032ef528.js +1 -0
  108. package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-bridge-b01555c9b42665a03988c0a0032ef528.js.br +0 -0
  109. package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-bridge-b01555c9b42665a03988c0a0032ef528.js.gz +0 -0
  110. package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-store-648388eca5c510b496e1eddf523f70ff.js +1 -0
  111. package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-store-648388eca5c510b496e1eddf523f70ff.js.br +0 -0
  112. package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-store-648388eca5c510b496e1eddf523f70ff.js.gz +0 -0
  113. package/dist/server/web-ui/_expo/static/js/web/index-0ebbea2cd337f0c0680fdb3f8d4d5af3.js +16157 -0
  114. package/dist/server/web-ui/_expo/static/js/web/index-0ebbea2cd337f0c0680fdb3f8d4d5af3.js.br +0 -0
  115. package/dist/server/web-ui/_expo/static/js/web/index-0ebbea2cd337f0c0680fdb3f8d4d5af3.js.gz +0 -0
  116. package/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-c64fa2416284927857a39087fd8d1332.js +1 -0
  117. package/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-c64fa2416284927857a39087fd8d1332.js.br +0 -0
  118. package/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-c64fa2416284927857a39087fd8d1332.js.gz +0 -0
  119. package/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-a9784226715772edf87ef36c596599c2.js +3 -0
  120. package/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-a9784226715772edf87ef36c596599c2.js.br +0 -0
  121. package/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-a9784226715772edf87ef36c596599c2.js.gz +0 -0
  122. package/dist/server/web-ui/apple-touch-icon.png +0 -0
  123. package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/back-icon-mask.0a328cd9c1afd0afe8e3b1ec5165b1b4.png +0 -0
  124. package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/back-icon.35ba0eaec5a4f5ed12ca16fabeae451d.png +0 -0
  125. package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/clear-icon.c94f6478e7ae0cdd9f15de1fcb9e5e55.png +0 -0
  126. package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/clear-icon.c94f6478e7ae0cdd9f15de1fcb9e5e55@2x.png +0 -0
  127. package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/clear-icon.c94f6478e7ae0cdd9f15de1fcb9e5e55@3x.png +0 -0
  128. package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/clear-icon.c94f6478e7ae0cdd9f15de1fcb9e5e55@4x.png +0 -0
  129. package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/close-icon.808e1b1b9b53114ec2838071a7e6daa7.png +0 -0
  130. package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/close-icon.808e1b1b9b53114ec2838071a7e6daa7@2x.png +0 -0
  131. package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/close-icon.808e1b1b9b53114ec2838071a7e6daa7@3x.png +0 -0
  132. package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/close-icon.808e1b1b9b53114ec2838071a7e6daa7@4x.png +0 -0
  133. package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/search-icon.286d67d3f74808a60a78d3ebf1a5fb57.png +0 -0
  134. package/dist/server/web-ui/assets/__node_modules/expo-router/assets/arrow_down.017bc6ba3fc25503e5eb5e53826d48a8.png +0 -0
  135. package/dist/server/web-ui/assets/__node_modules/expo-router/assets/error.d1ea1496f9057eb392d5bbf3732a61b7.png +0 -0
  136. package/dist/server/web-ui/assets/__node_modules/expo-router/assets/file.19eeb73b9593a38f8e9f418337fc7d10.png +0 -0
  137. package/dist/server/web-ui/assets/__node_modules/expo-router/assets/forward.d8b800c443b8972542883e0b9de2bdc6.png +0 -0
  138. package/dist/server/web-ui/assets/__node_modules/expo-router/assets/pkg.ab19f4cbc543357183a20571f68380a3.png +0 -0
  139. package/dist/server/web-ui/assets/__node_modules/expo-router/assets/sitemap.412dd9275b6b48ad28f5e3d81bb1f626.png +0 -0
  140. package/dist/server/web-ui/assets/__node_modules/expo-router/assets/unmatched.20e71bdf79e3a97bf55fd9e164041578.png +0 -0
  141. package/dist/server/web-ui/assets/assets/images/editor-apps/antigravity.6e91a685c33435e0b466a56db86cf141.png +0 -0
  142. package/dist/server/web-ui/assets/assets/images/editor-apps/cursor.c31d6bce4fe9aadc3fe59962f4c4fcf3.png +0 -0
  143. package/dist/server/web-ui/assets/assets/images/editor-apps/file-explorer.3e15e8f72c825c85ce336bcb0cdef776.png +0 -0
  144. package/dist/server/web-ui/assets/assets/images/editor-apps/finder.7f68fc2c475621a672e1be09309d5567.png +0 -0
  145. package/dist/server/web-ui/assets/assets/images/editor-apps/vscode.832bdb4c685d930f1c864c793703600b.png +0 -0
  146. package/dist/server/web-ui/assets/assets/images/editor-apps/webstorm.aa5dc2cd8c20cc0a155c4c5c5ab3c5f5.png +0 -0
  147. package/dist/server/web-ui/assets/assets/images/editor-apps/zed.f3a670b7f9aa226da4fe53fb86f1abbd.png +0 -0
  148. package/dist/server/web-ui/assets/assets/images/favicon-dark-attention.882b3a27dcb2073e9e31b334f9ed9728.png +0 -0
  149. package/dist/server/web-ui/assets/assets/images/favicon-dark-running.8112342ff0d39e047a7f8d4fad9402f3.png +0 -0
  150. package/dist/server/web-ui/assets/assets/images/favicon-dark.8005ed36ac07a5a7c60de25780897bd4.png +0 -0
  151. package/dist/server/web-ui/assets/assets/images/favicon-light-attention.882b3a27dcb2073e9e31b334f9ed9728.png +0 -0
  152. package/dist/server/web-ui/assets/assets/images/favicon-light-running.8112342ff0d39e047a7f8d4fad9402f3.png +0 -0
  153. package/dist/server/web-ui/assets/assets/images/favicon-light.8005ed36ac07a5a7c60de25780897bd4.png +0 -0
  154. package/dist/server/web-ui/assets/assets/images/notification-icon.3bf81d33ddbf380606bdd248ba83e158.png +0 -0
  155. package/dist/server/web-ui/favicon.ico +0 -0
  156. package/dist/server/web-ui/index.html +90 -0
  157. package/dist/server/web-ui/index.html.br +0 -0
  158. package/dist/server/web-ui/index.html.gz +0 -0
  159. package/dist/server/web-ui/manifest.json +27 -0
  160. package/dist/server/web-ui/manifest.json.br +0 -0
  161. package/dist/server/web-ui/manifest.json.gz +0 -0
  162. package/dist/server/web-ui/metadata.json +1 -0
  163. package/dist/server/web-ui/metadata.json.br +1 -0
  164. package/dist/server/web-ui/metadata.json.gz +0 -0
  165. package/dist/server/web-ui/pwa-icon-192.png +0 -0
  166. package/dist/server/web-ui/pwa-icon-512.png +0 -0
  167. package/dist/server/web-ui/robots.txt +2 -0
  168. package/dist/src/executable-resolution/windows.js +3 -0
  169. package/dist/src/server/persisted-config.js +18 -2
  170. package/package.json +7 -7
  171. package/dist/server/server/speech/providers/openai/realtime-transcription-session.d.ts +0 -42
  172. package/dist/server/server/speech/providers/openai/realtime-transcription-session.js +0 -168
@@ -1,5 +1,6 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import fs from "node:fs/promises";
3
+ import { homedir } from "node:os";
3
4
  import path from "node:path";
4
5
  import { Readable, Writable } from "node:stream";
5
6
  import { terminateWithTreeKill } from "../../../utils/tree-kill.js";
@@ -10,6 +11,8 @@ import { checkProviderLaunchAvailable, createProviderEnvSpec, resolveProviderLau
10
11
  import { renderPromptAttachmentAsText } from "../prompt-attachments.js";
11
12
  import { appendOrReplaceGrowingAssistantMessage, runProviderTurn } from "./provider-runner.js";
12
13
  import { platformShell, spawnProcess } from "../../../utils/spawn.js";
14
+ import { toDiagnosticErrorMessage, truncateForDiagnostic, } from "./diagnostic-utils.js";
15
+ import { withTimeout } from "../../../utils/promise-timeout.js";
13
16
  function assertChildWithPipes(child) {
14
17
  if (!child.stdin || !child.stdout || !child.stderr) {
15
18
  throw new Error("Child process did not expose stdio pipes");
@@ -70,6 +73,19 @@ function resolveTerminalCommand(command, args) {
70
73
  const shell = platformShell();
71
74
  return { command: shell.command, args: [...shell.flag, command] };
72
75
  }
76
+ function formatDurationMs(startedAt) {
77
+ return `${Math.max(0, Date.now() - startedAt)}ms`;
78
+ }
79
+ function pushACPStderrRow(rows, stderrChunks) {
80
+ const stderr = stderrChunks.join("").trim();
81
+ if (!stderr) {
82
+ return;
83
+ }
84
+ rows.push({
85
+ label: "ACP stderr",
86
+ value: truncateForDiagnostic(stderr),
87
+ });
88
+ }
73
89
  export const DEFAULT_ACP_CAPABILITIES = {
74
90
  supportsStreaming: true,
75
91
  supportsSessionPersistence: true,
@@ -96,6 +112,8 @@ const ACP_CLIENT_CAPABILITIES = {
96
112
  // sign-in URL in the browser) when probing an ACP agent for models/modes.
97
113
  // NO_BROWSER is honored by Gemini CLI; other ACP agents ignore it.
98
114
  const PROBE_ENV = { NO_BROWSER: "true" };
115
+ const ACP_CATALOG_TIMEOUT_MS = 60000;
116
+ const ACP_DIAGNOSTIC_PHASE_TIMEOUT_MS = 20000;
99
117
  function summarizeMalformedACPStdoutError(error) {
100
118
  return {
101
119
  type: error instanceof Error ? error.name : typeof error,
@@ -368,23 +386,36 @@ export class ACPAgentClient {
368
386
  return session;
369
387
  }
370
388
  async fetchCatalog(options) {
371
- const { cwd } = options;
372
- const probe = await this.spawnProcess(PROBE_ENV);
389
+ const cwd = options.scope === "global" ? homedir() : options.cwd;
390
+ const timeoutMs = options.timeoutMs ?? ACP_CATALOG_TIMEOUT_MS;
391
+ let probe = null;
373
392
  try {
374
- const response = await this.runACPRequest(() => probe.connection.newSession({
375
- cwd,
376
- mcpServers: [],
377
- }));
378
- const transformed = this.transformSessionResponse(response);
379
- const models = deriveModelDefinitionsFromACP(this.provider, transformed.models, transformed.configOptions);
380
- const modeInfo = deriveModesFromACP(this.defaultModes, transformed.modes, transformed.configOptions);
381
- return {
382
- models: this.modelTransformer ? this.modelTransformer(models) : models,
383
- modes: modeInfo.modes,
384
- };
393
+ const catalogProbe = (async () => {
394
+ const initializedProbe = await this.spawnProcess(PROBE_ENV, {
395
+ initializeTimeoutMs: timeoutMs,
396
+ onSpawned: (spawned) => {
397
+ probe = spawned;
398
+ },
399
+ });
400
+ probe = initializedProbe;
401
+ const response = await this.runACPRequest(() => initializedProbe.connection.newSession({
402
+ cwd,
403
+ mcpServers: [],
404
+ }));
405
+ const transformed = this.transformSessionResponse(response);
406
+ const models = deriveModelDefinitionsFromACP(this.provider, transformed.models, transformed.configOptions);
407
+ const modeInfo = deriveModesFromACP(this.defaultModes, transformed.modes, transformed.configOptions);
408
+ return {
409
+ models: this.modelTransformer ? this.modelTransformer(models) : models,
410
+ modes: modeInfo.modes,
411
+ };
412
+ })();
413
+ return await withTimeout(catalogProbe, timeoutMs, `ACP catalog probe timed out after ${timeoutMs}ms`);
385
414
  }
386
415
  finally {
387
- await this.closeProbe(probe);
416
+ if (probe) {
417
+ await this.closeProbe(probe);
418
+ }
388
419
  }
389
420
  }
390
421
  async listFeatures(config) {
@@ -461,6 +492,28 @@ export class ACPAgentClient {
461
492
  }
462
493
  }
463
494
  async spawnProcess(launchEnv, options) {
495
+ const transport = await this.spawnTransport(launchEnv);
496
+ const probe = {
497
+ child: transport.child,
498
+ connection: transport.connection,
499
+ stderrChunks: transport.stderrChunks,
500
+ };
501
+ options?.onSpawned?.(probe);
502
+ try {
503
+ const initialize = await this.initializeTransport(transport, options?.initializeTimeoutMs);
504
+ const initializedProbe = {
505
+ ...probe,
506
+ initialize,
507
+ };
508
+ probe.initialize = initialize;
509
+ return initializedProbe;
510
+ }
511
+ catch (error) {
512
+ await terminateChildProcess(transport.child, 2000, this.terminateProcess);
513
+ throw error;
514
+ }
515
+ }
516
+ async spawnTransport(launchEnv) {
464
517
  const { command, args } = await this.resolveLaunchCommand();
465
518
  const child = spawnProcess(command, args, {
466
519
  cwd: process.cwd(),
@@ -481,38 +534,46 @@ export class ACPAgentClient {
481
534
  reject(new Error(stderr ? `${String(error)}\n${stderr}` : String(error)));
482
535
  });
483
536
  });
537
+ const spawnReadyPromise = new Promise((resolve) => {
538
+ child.once("spawn", () => {
539
+ resolve();
540
+ });
541
+ });
484
542
  const stream = createLoggedNdJsonStream(Writable.toWeb(child.stdin), Readable.toWeb(child.stdout), { logger: this.logger, provider: this.provider });
485
543
  const connection = new ClientSideConnection(() => this.buildProbeClient(), stream);
544
+ return {
545
+ child,
546
+ connection,
547
+ stderrChunks,
548
+ spawnReady: spawnReadyPromise,
549
+ spawnError: spawnErrorPromise,
550
+ };
551
+ }
552
+ async initializeTransport(transport, initializeTimeoutMs) {
486
553
  let timeout = null;
487
- const initializeTimeoutPromise = options?.initializeTimeoutMs
554
+ const initializeTimeoutPromise = initializeTimeoutMs
488
555
  ? new Promise((_, reject) => {
489
556
  timeout = setTimeout(() => {
490
- reject(new Error(`ACP initialize timed out after ${options.initializeTimeoutMs}ms`));
491
- }, options.initializeTimeoutMs);
557
+ reject(new Error(`ACP initialize timed out after ${initializeTimeoutMs}ms`));
558
+ }, initializeTimeoutMs);
492
559
  })
493
560
  : null;
494
- let initialize;
495
561
  try {
496
- initialize = await this.runACPRequest(() => Promise.race([
497
- connection.initialize({
562
+ return await this.runACPRequest(() => Promise.race([
563
+ transport.connection.initialize({
498
564
  protocolVersion: PROTOCOL_VERSION,
499
565
  clientCapabilities: ACP_CLIENT_CAPABILITIES,
500
566
  clientInfo: { name: "Paseo", version: "dev" },
501
567
  }),
502
- spawnErrorPromise,
568
+ transport.spawnError,
503
569
  ...(initializeTimeoutPromise ? [initializeTimeoutPromise] : []),
504
570
  ]));
505
571
  }
506
- catch (error) {
507
- await terminateChildProcess(child, 2000, this.terminateProcess);
508
- throw error;
509
- }
510
572
  finally {
511
573
  if (timeout) {
512
574
  clearTimeout(timeout);
513
575
  }
514
576
  }
515
- return { child, connection, initialize };
516
577
  }
517
578
  buildProbeClient() {
518
579
  return {
@@ -536,7 +597,7 @@ export class ACPAgentClient {
536
597
  }
537
598
  async closeProbe(probe) {
538
599
  try {
539
- if (probe.initialize.agentCapabilities?.sessionCapabilities?.close) {
600
+ if (probe.initialize?.agentCapabilities?.sessionCapabilities?.close) {
540
601
  // No active session to close here; ignore capability.
541
602
  }
542
603
  }
@@ -552,6 +613,89 @@ export class ACPAgentClient {
552
613
  throw toACPRequestError(error);
553
614
  }
554
615
  }
616
+ async buildACPProbeDiagnosticRows(options = {}) {
617
+ const rows = [];
618
+ const phaseTimeoutMs = options.phaseTimeoutMs ?? ACP_DIAGNOSTIC_PHASE_TIMEOUT_MS;
619
+ const cwd = options.cwd ?? homedir();
620
+ let transport = null;
621
+ try {
622
+ const spawnStartedAt = Date.now();
623
+ try {
624
+ transport = await this.spawnTransport(PROBE_ENV);
625
+ await withTimeout(Promise.race([transport.spawnReady, transport.spawnError]), phaseTimeoutMs, `ACP spawn timed out after ${phaseTimeoutMs}ms`);
626
+ rows.push({
627
+ label: "ACP spawn",
628
+ value: `ok (${formatDurationMs(spawnStartedAt)})`,
629
+ });
630
+ }
631
+ catch (error) {
632
+ rows.push({
633
+ label: "ACP spawn",
634
+ value: `error: ${toDiagnosticErrorMessage(error)}`,
635
+ });
636
+ return rows;
637
+ }
638
+ const activeTransport = transport;
639
+ const initializeStartedAt = Date.now();
640
+ try {
641
+ await this.initializeTransport(activeTransport, phaseTimeoutMs);
642
+ rows.push({
643
+ label: "ACP initialize",
644
+ value: `ok (${formatDurationMs(initializeStartedAt)})`,
645
+ });
646
+ }
647
+ catch (error) {
648
+ rows.push({
649
+ label: "ACP initialize",
650
+ value: `error: ${toDiagnosticErrorMessage(error)}`,
651
+ });
652
+ pushACPStderrRow(rows, activeTransport.stderrChunks);
653
+ return rows;
654
+ }
655
+ const sessionStartedAt = Date.now();
656
+ try {
657
+ const response = await withTimeout(this.runACPRequest(() => activeTransport.connection.newSession({
658
+ cwd,
659
+ mcpServers: [],
660
+ })), phaseTimeoutMs, `ACP session/new timed out after ${phaseTimeoutMs}ms`);
661
+ const transformed = this.transformSessionResponse(response);
662
+ const models = deriveModelDefinitionsFromACP(this.provider, transformed.models, transformed.configOptions);
663
+ const modeInfo = deriveModesFromACP(this.defaultModes, transformed.modes, transformed.configOptions);
664
+ rows.push({
665
+ label: "ACP session/new",
666
+ value: `ok (${formatDurationMs(sessionStartedAt)}; models=${models.length}; modes=${modeInfo.modes.length})`,
667
+ });
668
+ }
669
+ catch (error) {
670
+ rows.push({
671
+ label: "ACP session/new",
672
+ value: `error: ${toDiagnosticErrorMessage(error)}`,
673
+ });
674
+ pushACPStderrRow(rows, activeTransport.stderrChunks);
675
+ return rows;
676
+ }
677
+ pushACPStderrRow(rows, activeTransport.stderrChunks);
678
+ return rows;
679
+ }
680
+ finally {
681
+ if (transport) {
682
+ const cleanupStartedAt = Date.now();
683
+ try {
684
+ await terminateChildProcess(transport.child, 2000, this.terminateProcess);
685
+ rows.push({
686
+ label: "ACP cleanup",
687
+ value: `ok (${formatDurationMs(cleanupStartedAt)})`,
688
+ });
689
+ }
690
+ catch (error) {
691
+ rows.push({
692
+ label: "ACP cleanup",
693
+ value: `error: ${toDiagnosticErrorMessage(error)}`,
694
+ });
695
+ }
696
+ }
697
+ }
698
+ }
555
699
  async resolveLaunchCommand() {
556
700
  const prefix = await resolveProviderLaunch({
557
701
  commandConfig: this.runtimeSettings?.command,
@@ -658,6 +802,13 @@ export class ACPAgentSession {
658
802
  this.applySessionState(response);
659
803
  await this.applyConfiguredOverrides();
660
804
  }
805
+ /**
806
+ * IMPORTANT: Some ACP providers (e.g., Devin CLI) require all three params
807
+ * (sessionId, cwd, mcpServers) to be present in session/load or
808
+ * unstable_resumeSession — even when mcpServers is an empty array — and
809
+ * return "Invalid params" if any are omitted. Never drop cwd or mcpServers
810
+ * from these calls regardless of capabilities.
811
+ */
661
812
  async initializeResumedSession() {
662
813
  const handle = this.initialHandle;
663
814
  if (!handle) {
@@ -17,10 +17,12 @@ 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";
23
24
  import { withTimeout } from "../../../../utils/promise-timeout.js";
25
+ import { terminateWithTreeKill } from "../../../../utils/tree-kill.js";
24
26
  import { execCommand } from "../../../../utils/spawn.js";
25
27
  import { composeSystemPromptParts } from "../../system-prompt.js";
26
28
  const fsPromises = promises;
@@ -370,6 +372,39 @@ function coerceToolResultContentToString(content) {
370
372
  }
371
373
  return deterministicStringify(content);
372
374
  }
375
+ function toBase64ImageOutput(block) {
376
+ const record = toObjectRecord(block);
377
+ if (!record || record.type !== "image") {
378
+ return null;
379
+ }
380
+ const source = toObjectRecord(record.source);
381
+ if (!source || source.type !== "base64" || typeof source.data !== "string") {
382
+ return null;
383
+ }
384
+ return {
385
+ data: source.data,
386
+ mimeType: typeof source.media_type === "string" ? source.media_type : null,
387
+ };
388
+ }
389
+ // Claude returns images inside tool_result content as base64 Anthropic blocks. Left in place they
390
+ // reach coerceToolResultContentToString, which JSON.stringifies the whole array — dumping base64
391
+ // into the tool output. We pull those blocks out to render them as image markdown and leave a
392
+ // "[image]" placeholder so image-only results still produce non-empty output.
393
+ function splitClaudeToolResultImages(content) {
394
+ if (!Array.isArray(content)) {
395
+ return { images: [], text: content };
396
+ }
397
+ const images = [];
398
+ const text = content.map((block) => {
399
+ const image = toBase64ImageOutput(block);
400
+ if (image) {
401
+ images.push(image);
402
+ return { type: "text", text: "[image]" };
403
+ }
404
+ return block;
405
+ });
406
+ return { images, text };
407
+ }
373
408
  function normalizeClaudeTranscriptText(value) {
374
409
  if (typeof value !== "string") {
375
410
  return null;
@@ -1255,6 +1290,13 @@ function readLegacyResultUsageTokens(usage) {
1255
1290
  function isClaudeSubagentToolName(name) {
1256
1291
  return name === "Task" || name === "Agent";
1257
1292
  }
1293
+ function readClaudeParentToolUseId(message) {
1294
+ if (!("parent_tool_use_id" in message)) {
1295
+ return null;
1296
+ }
1297
+ const parentToolUseId = message.parent_tool_use_id;
1298
+ return typeof parentToolUseId === "string" && parentToolUseId.length > 0 ? parentToolUseId : null;
1299
+ }
1258
1300
  class ClaudeContextUsageState {
1259
1301
  constructor(initialContextWindowMaxTokens) {
1260
1302
  this.completedResultTurns = 0;
@@ -1380,6 +1422,7 @@ class ClaudeAgentSession {
1380
1422
  this.provider = "claude";
1381
1423
  this.capabilities = CLAUDE_CAPABILITIES;
1382
1424
  this.query = null;
1425
+ this.childProcess = null;
1383
1426
  this.input = null;
1384
1427
  this.planResumeMode = null;
1385
1428
  this.availableModes = DEFAULT_MODES;
@@ -1853,6 +1896,19 @@ class ClaudeAgentSession {
1853
1896
  await this.awaitWithTimeout(this.query?.return?.(), "close query return");
1854
1897
  this.query = null;
1855
1898
  this.input = null;
1899
+ // Terminate the entire process tree (claude + MCP children) to prevent
1900
+ // orphan accumulation. The SDK's internal cleanup may only kill the
1901
+ // direct child process.
1902
+ if (this.childProcess) {
1903
+ const result = await terminateWithTreeKill(this.childProcess, {
1904
+ gracefulTimeoutMs: 2000,
1905
+ forceTimeoutMs: 2000,
1906
+ });
1907
+ if (result === "kill-timeout") {
1908
+ this.logger.warn({ pid: this.childProcess.pid, agentId: this.agentId }, "Claude process tree did not report exit after SIGKILL");
1909
+ }
1910
+ this.childProcess = null;
1911
+ }
1856
1912
  if (this.persistSession === false && this.claudeSessionId) {
1857
1913
  // Claude Code currently ignores --no-session-persistence outside --print mode
1858
1914
  // (see `claude --help`), so the SDK's persistSession=false is silently dropped
@@ -2191,6 +2247,18 @@ class ClaudeAgentSession {
2191
2247
  catch {
2192
2248
  /* ignore */
2193
2249
  }
2250
+ // Tree-kill the old process tree now that the SDK has cleaned up.
2251
+ // If we skip this, MCP children of the previous claude process can
2252
+ // survive as orphans when the session spawns a replacement query.
2253
+ if (this.childProcess) {
2254
+ await terminateWithTreeKill(this.childProcess, {
2255
+ gracefulTimeoutMs: 2000,
2256
+ forceTimeoutMs: 2000,
2257
+ }).catch(() => {
2258
+ /* process may already be dead */
2259
+ });
2260
+ this.childProcess = null;
2261
+ }
2194
2262
  }
2195
2263
  // Preserve claudeSessionId across query recreation so buildOptions() passes
2196
2264
  // resume: sessionId and the new query continues the existing conversation.
@@ -2203,6 +2271,9 @@ class ClaudeAgentSession {
2203
2271
  runtimeSettings: this.runtimeSettings,
2204
2272
  launchEnv: this.launchEnv,
2205
2273
  queryFactory: this.queryFactory,
2274
+ onChildProcess: (child) => {
2275
+ this.childProcess = child;
2276
+ },
2206
2277
  });
2207
2278
  const fastMode = this.resolveFastModeSetting();
2208
2279
  if (fastMode !== null) {
@@ -2775,17 +2846,19 @@ class ClaudeAgentSession {
2775
2846
  suppressAssistantText: true,
2776
2847
  suppressReasoning: true,
2777
2848
  });
2778
- const assistantTimelineEvents = this.timelineAssembler
2779
- .consume({
2780
- message,
2781
- runId: turnId,
2782
- messageIdHint,
2783
- })
2784
- .map((item) => ({
2785
- type: "timeline",
2786
- item,
2787
- provider: "claude",
2788
- }));
2849
+ const assistantTimelineEvents = readClaudeParentToolUseId(message)
2850
+ ? []
2851
+ : this.timelineAssembler
2852
+ .consume({
2853
+ message,
2854
+ runId: turnId,
2855
+ messageIdHint,
2856
+ })
2857
+ .map((item) => ({
2858
+ type: "timeline",
2859
+ item,
2860
+ provider: "claude",
2861
+ }));
2789
2862
  return [...messageEvents, ...assistantTimelineEvents];
2790
2863
  }
2791
2864
  async handleMissingResumedConversation(message, activeQuery) {
@@ -2833,9 +2906,7 @@ class ClaudeAgentSession {
2833
2906
  }
2834
2907
  }
2835
2908
  translateMessageToEvents(message, options) {
2836
- const parentToolUseId = "parent_tool_use_id" in message
2837
- ? message.parent_tool_use_id
2838
- : null;
2909
+ const parentToolUseId = readClaudeParentToolUseId(message);
2839
2910
  if (parentToolUseId) {
2840
2911
  return this.sidechainTracker.handleMessage(message, parentToolUseId);
2841
2912
  }
@@ -3472,15 +3543,17 @@ class ClaudeAgentSession {
3472
3543
  const callId = typeof block.tool_use_id === "string" && block.tool_use_id.length > 0
3473
3544
  ? block.tool_use_id
3474
3545
  : (entry?.id ?? null);
3475
- // Extract output from block.content (SDK always returns content in string form)
3476
- const output = this.buildToolOutput(block, entry);
3546
+ // Pull image blocks out of the result so base64 never reaches the tool output, and render each
3547
+ // one as an assistant_message markdown image after the tool_call (matching how Codex emits).
3548
+ const { images, text } = splitClaudeToolResultImages(block.content);
3549
+ const output = this.buildToolOutput(text, block, entry);
3477
3550
  if (block.is_error) {
3478
3551
  this.pushToolCall(mapClaudeFailedToolCall({
3479
3552
  name: toolName,
3480
3553
  callId,
3481
3554
  input: entry?.input ?? null,
3482
3555
  output: output ?? null,
3483
- error: block,
3556
+ error: { ...block, content: text },
3484
3557
  }), items);
3485
3558
  }
3486
3559
  else {
@@ -3491,12 +3564,20 @@ class ClaudeAgentSession {
3491
3564
  output: output ?? null,
3492
3565
  }), items);
3493
3566
  }
3567
+ for (const image of images) {
3568
+ const imageItem = renderProviderImageOutputAsAssistantMarkdown(image, {
3569
+ materialize: materializeProviderImage,
3570
+ });
3571
+ if (imageItem) {
3572
+ items.push(imageItem);
3573
+ }
3574
+ }
3494
3575
  if (typeof block.tool_use_id === "string") {
3495
3576
  this.toolUseCache.delete(block.tool_use_id);
3496
3577
  this.sidechainTracker.delete(block.tool_use_id);
3497
3578
  }
3498
3579
  }
3499
- buildToolOutput(block, entry) {
3580
+ buildToolOutput(content, block, entry) {
3500
3581
  if (block.is_error) {
3501
3582
  return undefined;
3502
3583
  }
@@ -3504,23 +3585,23 @@ class ClaudeAgentSession {
3504
3585
  const blockToolName = typeof block.tool_name === "string" ? block.tool_name : undefined;
3505
3586
  const server = entry?.server ?? blockServer ?? "tool";
3506
3587
  const tool = entry?.name ?? blockToolName ?? "tool";
3507
- const content = coerceToolResultContentToString(block.content);
3588
+ const coercedContent = coerceToolResultContentToString(content);
3508
3589
  const input = entry?.input;
3509
3590
  // Build structured result based on tool type
3510
- const structured = this.buildStructuredToolResult(server, tool, content, input);
3591
+ const structured = this.buildStructuredToolResult(server, tool, coercedContent, input);
3511
3592
  if (structured) {
3512
3593
  return structured;
3513
3594
  }
3514
3595
  // Fallback format - try to parse JSON first
3515
3596
  const result = {};
3516
- if (content.length > 0) {
3597
+ if (coercedContent.length > 0) {
3517
3598
  try {
3518
3599
  // If content is a JSON string, parse it
3519
- result.output = JSON.parse(content);
3600
+ result.output = JSON.parse(coercedContent);
3520
3601
  }
3521
3602
  catch {
3522
3603
  // If not JSON, return unchanged (no extra wrapping)
3523
- result.output = content;
3604
+ result.output = coercedContent;
3524
3605
  }
3525
3606
  }
3526
3607
  // Preserve file changes tracked during tool execution
@@ -3945,6 +4026,9 @@ function convertClaudeHistoryEntryPreamble(entry) {
3945
4026
  }
3946
4027
  return { proceed: { content } };
3947
4028
  }
4029
+ function isProviderImageMessage(item) {
4030
+ return item.type === "assistant_message" && isProviderImageMarkdown(item.text);
4031
+ }
3948
4032
  export function convertClaudeHistoryEntry(entry, mapBlocks) {
3949
4033
  const preamble = convertClaudeHistoryEntryPreamble(entry);
3950
4034
  if ("shortCircuit" in preamble) {
@@ -3980,7 +4064,10 @@ export function convertClaudeHistoryEntry(entry, mapBlocks) {
3980
4064
  if (hasToolBlock && normalizedBlocks) {
3981
4065
  const mapped = mapBlocks(normalizedBlocks);
3982
4066
  if (entry.type === "user") {
3983
- const toolItems = mapped.filter((item) => item.type === "tool_call");
4067
+ // tool_result handling (handleToolResult) emits image markdown as an assistant_message
4068
+ // alongside the tool_call. User-entry text blocks also map to assistant_message in this path
4069
+ // and must stay suppressed, so keep tool_calls plus only the image assistant_messages.
4070
+ const toolItems = mapped.filter((item) => item.type === "tool_call" || isProviderImageMessage(item));
3984
4071
  return timeline.length ? [...timeline, ...toolItems] : toolItems;
3985
4072
  }
3986
4073
  return mapped;
@@ -1,3 +1,4 @@
1
+ import { type ChildProcess } from "node:child_process";
1
2
  import { query, type Options, type Query } from "@anthropic-ai/claude-agent-sdk";
2
3
  import { type ProviderRuntimeSettings } from "../../provider-launch-config.js";
3
4
  export type ClaudeOptions = Options;
@@ -9,6 +10,8 @@ export interface ClaudeQueryContext {
9
10
  runtimeSettings?: ProviderRuntimeSettings;
10
11
  launchEnv?: Record<string, string>;
11
12
  queryFactory?: ClaudeQueryFactory;
13
+ /** Called with the spawned child process so the caller can tree-kill it on close. */
14
+ onChildProcess?: (child: ChildProcess) => void;
12
15
  }
13
16
  export declare function claudeQuery(input: ClaudeQueryInput, context?: ClaudeQueryContext): Query;
14
17
  //# sourceMappingURL=query.d.ts.map
@@ -24,7 +24,8 @@ function resolveClaudeSpawnCommand(spawnOptions, runtimeSettings) {
24
24
  args: [...commandConfig.argv.slice(1), ...spawnOptions.args],
25
25
  };
26
26
  }
27
- function applyRuntimeSettingsToClaudeOptions(options, runtimeSettings, launchEnv) {
27
+ function applyRuntimeSettingsToClaudeOptions(options, context) {
28
+ const { runtimeSettings, launchEnv, onChildProcess } = context;
28
29
  return {
29
30
  ...options,
30
31
  spawnClaudeCodeProcess: (spawnOptions) => {
@@ -62,6 +63,7 @@ function applyRuntimeSettingsToClaudeOptions(options, runtimeSettings, launchEnv
62
63
  // The command is always a resolved binary path, so shell routing is unnecessary.
63
64
  shell: false,
64
65
  });
66
+ onChildProcess?.(child);
65
67
  if (typeof options.stderr === "function") {
66
68
  child.stderr?.on("data", (chunk) => {
67
69
  options.stderr?.(chunk.toString());
@@ -78,7 +80,7 @@ export function claudeQuery(input, context = {}) {
78
80
  const launchQuery = context.queryFactory ?? query;
79
81
  return launchQuery({
80
82
  ...input,
81
- options: applyRuntimeSettingsToClaudeOptions(input.options, context.runtimeSettings, context.launchEnv),
83
+ options: applyRuntimeSettingsToClaudeOptions(input.options, context),
82
84
  });
83
85
  }
84
86
  //# sourceMappingURL=query.js.map