@getpaseo/server 0.1.101 → 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 (132) hide show
  1. package/dist/scripts/supervisor.js +26 -8
  2. package/dist/server/server/agent/activity-curator.d.ts +17 -0
  3. package/dist/server/server/agent/activity-curator.js +101 -24
  4. package/dist/server/server/agent/agent-manager.js +5 -1
  5. package/dist/server/server/agent/agent-sdk-types.d.ts +7 -2
  6. package/dist/server/server/agent/provider-snapshot-manager.d.ts +8 -1
  7. package/dist/server/server/agent/provider-snapshot-manager.js +78 -33
  8. package/dist/server/server/agent/providers/acp-agent.d.ts +7 -0
  9. package/dist/server/server/agent/providers/acp-agent.js +8 -1
  10. package/dist/server/server/agent/providers/claude/agent.js +51 -14
  11. package/dist/server/server/agent/providers/claude/query.d.ts +3 -0
  12. package/dist/server/server/agent/providers/claude/query.js +4 -2
  13. package/dist/server/server/agent/providers/mock-load-test-agent.js +8 -0
  14. package/dist/server/server/agent/providers/opencode/paths.d.ts +2 -0
  15. package/dist/server/server/agent/providers/opencode/paths.js +7 -0
  16. package/dist/server/server/agent/providers/opencode/server-manager.d.ts +2 -0
  17. package/dist/server/server/agent/providers/opencode/server-manager.js +34 -5
  18. package/dist/server/server/agent/providers/opencode-agent.d.ts +4 -0
  19. package/dist/server/server/agent/providers/opencode-agent.js +14 -2
  20. package/dist/server/server/agent/providers/pi/agent.d.ts +3 -0
  21. package/dist/server/server/agent/providers/pi/agent.js +9 -3
  22. package/dist/server/server/agent/providers/provider-image-output.js +11 -6
  23. package/dist/server/server/agent/tools/paseo-tools.d.ts +1 -1
  24. package/dist/server/server/agent/tools/paseo-tools.js +0 -2
  25. package/dist/server/server/bootstrap.d.ts +7 -1
  26. package/dist/server/server/bootstrap.js +18 -0
  27. package/dist/server/server/config.d.ts +2 -0
  28. package/dist/server/server/config.js +57 -1
  29. package/dist/server/server/daemon-worker.js +19 -7
  30. package/dist/server/server/lifecycle-reasons.d.ts +4 -0
  31. package/dist/server/server/lifecycle-reasons.js +6 -0
  32. package/dist/server/server/persisted-config.d.ts +7 -0
  33. package/dist/server/server/persisted-config.js +8 -0
  34. package/dist/server/server/process-diagnostics.d.ts +17 -0
  35. package/dist/server/server/process-diagnostics.js +22 -0
  36. package/dist/server/server/relay-transport.js +1 -0
  37. package/dist/server/server/resolve-worktree-creation-intent.js +3 -1
  38. package/dist/server/server/session/daemon/daemon-self-update-session-controller.d.ts +32 -0
  39. package/dist/server/server/session/daemon/daemon-self-update-session-controller.js +88 -0
  40. package/dist/server/server/session/daemon/daemon-self-updater.d.ts +32 -0
  41. package/dist/server/server/session/daemon/daemon-self-updater.js +56 -0
  42. package/dist/server/server/session/daemon/daemon-session.d.ts +12 -0
  43. package/dist/server/server/session/daemon/daemon-session.js +12 -0
  44. package/dist/server/server/session/daemon/diagnostics.js +10 -0
  45. package/dist/server/server/session/daemon/install-origin.d.ts +7 -0
  46. package/dist/server/server/session/daemon/install-origin.js +64 -0
  47. package/dist/server/server/session/daemon/npm-global-cli.d.ts +29 -0
  48. package/dist/server/server/session/daemon/npm-global-cli.js +98 -0
  49. package/dist/server/server/session/provider/provider-catalog-session.js +8 -4
  50. package/dist/server/server/session.d.ts +5 -3
  51. package/dist/server/server/session.js +74 -32
  52. package/dist/server/server/web-ui.d.ts +10 -0
  53. package/dist/server/server/web-ui.js +205 -0
  54. package/dist/server/server/websocket/runtime-metrics.d.ts +3 -0
  55. package/dist/server/server/websocket-server.d.ts +3 -0
  56. package/dist/server/server/websocket-server.js +190 -32
  57. package/dist/server/services/quota-fetcher/manifest.js +5 -0
  58. package/dist/server/services/quota-fetcher/providers/minimax.d.ts +29 -0
  59. package/dist/server/services/quota-fetcher/providers/minimax.js +227 -0
  60. package/dist/server/terminal/agent-hooks/agent-hook-installer.js +2 -2
  61. package/dist/server/utils/checkout-git.js +156 -3
  62. package/dist/server/utils/directory-suggestions.js +1 -4
  63. package/dist/server/utils/path.d.ts +2 -0
  64. package/dist/server/utils/path.js +13 -0
  65. package/dist/server/utils/worktree.d.ts +1 -0
  66. package/dist/server/utils/worktree.js +92 -11
  67. package/dist/server/web-ui/_expo/static/css/xterm-3bb1704bf6cb0876640973dc0244b4cb.css +1 -0
  68. package/dist/server/web-ui/_expo/static/css/xterm-3bb1704bf6cb0876640973dc0244b4cb.css.br +0 -0
  69. package/dist/server/web-ui/_expo/static/css/xterm-3bb1704bf6cb0876640973dc0244b4cb.css.gz +0 -0
  70. package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-bridge-b01555c9b42665a03988c0a0032ef528.js +1 -0
  71. package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-bridge-b01555c9b42665a03988c0a0032ef528.js.br +0 -0
  72. package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-bridge-b01555c9b42665a03988c0a0032ef528.js.gz +0 -0
  73. package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-store-648388eca5c510b496e1eddf523f70ff.js +1 -0
  74. package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-store-648388eca5c510b496e1eddf523f70ff.js.br +0 -0
  75. package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-store-648388eca5c510b496e1eddf523f70ff.js.gz +0 -0
  76. package/dist/server/web-ui/_expo/static/js/web/index-0ebbea2cd337f0c0680fdb3f8d4d5af3.js +16157 -0
  77. package/dist/server/web-ui/_expo/static/js/web/index-0ebbea2cd337f0c0680fdb3f8d4d5af3.js.br +0 -0
  78. package/dist/server/web-ui/_expo/static/js/web/index-0ebbea2cd337f0c0680fdb3f8d4d5af3.js.gz +0 -0
  79. package/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-c64fa2416284927857a39087fd8d1332.js +1 -0
  80. package/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-c64fa2416284927857a39087fd8d1332.js.br +0 -0
  81. package/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-c64fa2416284927857a39087fd8d1332.js.gz +0 -0
  82. package/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-a9784226715772edf87ef36c596599c2.js +3 -0
  83. package/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-a9784226715772edf87ef36c596599c2.js.br +0 -0
  84. package/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-a9784226715772edf87ef36c596599c2.js.gz +0 -0
  85. package/dist/server/web-ui/apple-touch-icon.png +0 -0
  86. package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/back-icon-mask.0a328cd9c1afd0afe8e3b1ec5165b1b4.png +0 -0
  87. package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/back-icon.35ba0eaec5a4f5ed12ca16fabeae451d.png +0 -0
  88. package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/clear-icon.c94f6478e7ae0cdd9f15de1fcb9e5e55.png +0 -0
  89. package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/clear-icon.c94f6478e7ae0cdd9f15de1fcb9e5e55@2x.png +0 -0
  90. package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/clear-icon.c94f6478e7ae0cdd9f15de1fcb9e5e55@3x.png +0 -0
  91. package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/clear-icon.c94f6478e7ae0cdd9f15de1fcb9e5e55@4x.png +0 -0
  92. package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/close-icon.808e1b1b9b53114ec2838071a7e6daa7.png +0 -0
  93. package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/close-icon.808e1b1b9b53114ec2838071a7e6daa7@2x.png +0 -0
  94. package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/close-icon.808e1b1b9b53114ec2838071a7e6daa7@3x.png +0 -0
  95. package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/close-icon.808e1b1b9b53114ec2838071a7e6daa7@4x.png +0 -0
  96. package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/search-icon.286d67d3f74808a60a78d3ebf1a5fb57.png +0 -0
  97. package/dist/server/web-ui/assets/__node_modules/expo-router/assets/arrow_down.017bc6ba3fc25503e5eb5e53826d48a8.png +0 -0
  98. package/dist/server/web-ui/assets/__node_modules/expo-router/assets/error.d1ea1496f9057eb392d5bbf3732a61b7.png +0 -0
  99. package/dist/server/web-ui/assets/__node_modules/expo-router/assets/file.19eeb73b9593a38f8e9f418337fc7d10.png +0 -0
  100. package/dist/server/web-ui/assets/__node_modules/expo-router/assets/forward.d8b800c443b8972542883e0b9de2bdc6.png +0 -0
  101. package/dist/server/web-ui/assets/__node_modules/expo-router/assets/pkg.ab19f4cbc543357183a20571f68380a3.png +0 -0
  102. package/dist/server/web-ui/assets/__node_modules/expo-router/assets/sitemap.412dd9275b6b48ad28f5e3d81bb1f626.png +0 -0
  103. package/dist/server/web-ui/assets/__node_modules/expo-router/assets/unmatched.20e71bdf79e3a97bf55fd9e164041578.png +0 -0
  104. package/dist/server/web-ui/assets/assets/images/editor-apps/antigravity.6e91a685c33435e0b466a56db86cf141.png +0 -0
  105. package/dist/server/web-ui/assets/assets/images/editor-apps/cursor.c31d6bce4fe9aadc3fe59962f4c4fcf3.png +0 -0
  106. package/dist/server/web-ui/assets/assets/images/editor-apps/file-explorer.3e15e8f72c825c85ce336bcb0cdef776.png +0 -0
  107. package/dist/server/web-ui/assets/assets/images/editor-apps/finder.7f68fc2c475621a672e1be09309d5567.png +0 -0
  108. package/dist/server/web-ui/assets/assets/images/editor-apps/vscode.832bdb4c685d930f1c864c793703600b.png +0 -0
  109. package/dist/server/web-ui/assets/assets/images/editor-apps/webstorm.aa5dc2cd8c20cc0a155c4c5c5ab3c5f5.png +0 -0
  110. package/dist/server/web-ui/assets/assets/images/editor-apps/zed.f3a670b7f9aa226da4fe53fb86f1abbd.png +0 -0
  111. package/dist/server/web-ui/assets/assets/images/favicon-dark-attention.882b3a27dcb2073e9e31b334f9ed9728.png +0 -0
  112. package/dist/server/web-ui/assets/assets/images/favicon-dark-running.8112342ff0d39e047a7f8d4fad9402f3.png +0 -0
  113. package/dist/server/web-ui/assets/assets/images/favicon-dark.8005ed36ac07a5a7c60de25780897bd4.png +0 -0
  114. package/dist/server/web-ui/assets/assets/images/favicon-light-attention.882b3a27dcb2073e9e31b334f9ed9728.png +0 -0
  115. package/dist/server/web-ui/assets/assets/images/favicon-light-running.8112342ff0d39e047a7f8d4fad9402f3.png +0 -0
  116. package/dist/server/web-ui/assets/assets/images/favicon-light.8005ed36ac07a5a7c60de25780897bd4.png +0 -0
  117. package/dist/server/web-ui/assets/assets/images/notification-icon.3bf81d33ddbf380606bdd248ba83e158.png +0 -0
  118. package/dist/server/web-ui/favicon.ico +0 -0
  119. package/dist/server/web-ui/index.html +90 -0
  120. package/dist/server/web-ui/index.html.br +0 -0
  121. package/dist/server/web-ui/index.html.gz +0 -0
  122. package/dist/server/web-ui/manifest.json +27 -0
  123. package/dist/server/web-ui/manifest.json.br +0 -0
  124. package/dist/server/web-ui/manifest.json.gz +0 -0
  125. package/dist/server/web-ui/metadata.json +1 -0
  126. package/dist/server/web-ui/metadata.json.br +1 -0
  127. package/dist/server/web-ui/metadata.json.gz +0 -0
  128. package/dist/server/web-ui/pwa-icon-192.png +0 -0
  129. package/dist/server/web-ui/pwa-icon-512.png +0 -0
  130. package/dist/server/web-ui/robots.txt +2 -0
  131. package/dist/src/server/persisted-config.js +8 -0
  132. package/package.json +7 -7
@@ -22,6 +22,7 @@ import { getAgentStreamEventTurnId, } from "../../agent-sdk-types.js";
22
22
  import { importSessionFromPersistence } from "../../provider-session-import.js";
23
23
  import { checkProviderLaunchAvailable, createProviderEnv, createProviderEnvSpec, resolveProviderLaunch, } from "../../provider-launch-config.js";
24
24
  import { withTimeout } from "../../../../utils/promise-timeout.js";
25
+ import { terminateWithTreeKill } from "../../../../utils/tree-kill.js";
25
26
  import { execCommand } from "../../../../utils/spawn.js";
26
27
  import { composeSystemPromptParts } from "../../system-prompt.js";
27
28
  const fsPromises = promises;
@@ -1289,6 +1290,13 @@ function readLegacyResultUsageTokens(usage) {
1289
1290
  function isClaudeSubagentToolName(name) {
1290
1291
  return name === "Task" || name === "Agent";
1291
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
+ }
1292
1300
  class ClaudeContextUsageState {
1293
1301
  constructor(initialContextWindowMaxTokens) {
1294
1302
  this.completedResultTurns = 0;
@@ -1414,6 +1422,7 @@ class ClaudeAgentSession {
1414
1422
  this.provider = "claude";
1415
1423
  this.capabilities = CLAUDE_CAPABILITIES;
1416
1424
  this.query = null;
1425
+ this.childProcess = null;
1417
1426
  this.input = null;
1418
1427
  this.planResumeMode = null;
1419
1428
  this.availableModes = DEFAULT_MODES;
@@ -1887,6 +1896,19 @@ class ClaudeAgentSession {
1887
1896
  await this.awaitWithTimeout(this.query?.return?.(), "close query return");
1888
1897
  this.query = null;
1889
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
+ }
1890
1912
  if (this.persistSession === false && this.claudeSessionId) {
1891
1913
  // Claude Code currently ignores --no-session-persistence outside --print mode
1892
1914
  // (see `claude --help`), so the SDK's persistSession=false is silently dropped
@@ -2225,6 +2247,18 @@ class ClaudeAgentSession {
2225
2247
  catch {
2226
2248
  /* ignore */
2227
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
+ }
2228
2262
  }
2229
2263
  // Preserve claudeSessionId across query recreation so buildOptions() passes
2230
2264
  // resume: sessionId and the new query continues the existing conversation.
@@ -2237,6 +2271,9 @@ class ClaudeAgentSession {
2237
2271
  runtimeSettings: this.runtimeSettings,
2238
2272
  launchEnv: this.launchEnv,
2239
2273
  queryFactory: this.queryFactory,
2274
+ onChildProcess: (child) => {
2275
+ this.childProcess = child;
2276
+ },
2240
2277
  });
2241
2278
  const fastMode = this.resolveFastModeSetting();
2242
2279
  if (fastMode !== null) {
@@ -2809,17 +2846,19 @@ class ClaudeAgentSession {
2809
2846
  suppressAssistantText: true,
2810
2847
  suppressReasoning: true,
2811
2848
  });
2812
- const assistantTimelineEvents = this.timelineAssembler
2813
- .consume({
2814
- message,
2815
- runId: turnId,
2816
- messageIdHint,
2817
- })
2818
- .map((item) => ({
2819
- type: "timeline",
2820
- item,
2821
- provider: "claude",
2822
- }));
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
+ }));
2823
2862
  return [...messageEvents, ...assistantTimelineEvents];
2824
2863
  }
2825
2864
  async handleMissingResumedConversation(message, activeQuery) {
@@ -2867,9 +2906,7 @@ class ClaudeAgentSession {
2867
2906
  }
2868
2907
  }
2869
2908
  translateMessageToEvents(message, options) {
2870
- const parentToolUseId = "parent_tool_use_id" in message
2871
- ? message.parent_tool_use_id
2872
- : null;
2909
+ const parentToolUseId = readClaudeParentToolUseId(message);
2873
2910
  if (parentToolUseId) {
2874
2911
  return this.sidechainTracker.handleMessage(message, parentToolUseId);
2875
2912
  }
@@ -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
@@ -439,12 +439,14 @@ export class MockLoadTestAgentSession {
439
439
  }
440
440
  const profile = resolveModelProfile(this.modelId);
441
441
  const turnId = randomUUID();
442
+ const assistantMessageId = randomUUID();
442
443
  let resolve;
443
444
  const completed = new Promise((promiseResolve) => {
444
445
  resolve = promiseResolve;
445
446
  });
446
447
  const turn = {
447
448
  turnId,
449
+ assistantMessageId,
448
450
  prompt,
449
451
  startedAt: Date.now(),
450
452
  cycle: 0,
@@ -655,6 +657,7 @@ export class MockLoadTestAgentSession {
655
657
  this.emitTimeline(turn.turnId, {
656
658
  type: "assistant_message",
657
659
  text: finalText,
660
+ messageId: turn.assistantMessageId,
658
661
  });
659
662
  this.activeTurn = null;
660
663
  this.emit({
@@ -669,6 +672,7 @@ export class MockLoadTestAgentSession {
669
672
  {
670
673
  type: "assistant_message",
671
674
  text: finalText,
675
+ messageId: turn.assistantMessageId,
672
676
  },
673
677
  ],
674
678
  canceled: false,
@@ -768,6 +772,7 @@ export class MockLoadTestAgentSession {
768
772
  ? {
769
773
  type: "assistant_message",
770
774
  text: `stress-update-${index}`,
775
+ messageId: turn.assistantMessageId,
771
776
  }
772
777
  : {
773
778
  type: "todo",
@@ -834,6 +839,7 @@ export class MockLoadTestAgentSession {
834
839
  this.emitTimeline(turn.turnId, {
835
840
  type: "assistant_message",
836
841
  text: `data:image/png;base64,${payload}`,
842
+ messageId: turn.assistantMessageId,
837
843
  });
838
844
  }
839
845
  this.activeTurn = null;
@@ -892,6 +898,7 @@ export class MockLoadTestAgentSession {
892
898
  this.emitTimeline(turn.turnId, {
893
899
  type: "assistant_message",
894
900
  text: event.text,
901
+ messageId: turn.assistantMessageId,
895
902
  });
896
903
  return;
897
904
  }
@@ -933,6 +940,7 @@ export class MockLoadTestAgentSession {
933
940
  this.emitTimeline(turn.turnId, {
934
941
  type: "assistant_message",
935
942
  text: "\n\n_(end of synthetic stream)_\n",
943
+ messageId: turn.assistantMessageId,
936
944
  });
937
945
  this.finishTurnWithText(turn, "Synthetic load test complete");
938
946
  }
@@ -0,0 +1,2 @@
1
+ export declare function resolveOpenCodeHomeDir(env?: NodeJS.ProcessEnv): string;
2
+ //# sourceMappingURL=paths.d.ts.map
@@ -0,0 +1,7 @@
1
+ import path from "node:path";
2
+ import { resolvePaseoHome } from "../../../paseo-home.js";
3
+ const OPENCODE_HOME_DIRNAME = "opencode-home";
4
+ export function resolveOpenCodeHomeDir(env = process.env) {
5
+ return path.join(resolvePaseoHome(env), OPENCODE_HOME_DIRNAME);
6
+ }
7
+ //# sourceMappingURL=paths.js.map
@@ -46,6 +46,7 @@ export interface OpenCodeServerManagerOptions {
46
46
  terminateProcess?: ProcessTerminator;
47
47
  portAllocator?: OpenCodePortAllocator;
48
48
  resolveCommandPrefix?: OpenCodeCommandPrefixResolver;
49
+ resolveHomeDir?: () => string;
49
50
  spawnServerProcess?: OpenCodeServerProcessSpawner;
50
51
  }
51
52
  export declare class OpenCodeServerManager implements OpenCodeServerManagerLike {
@@ -62,6 +63,7 @@ export declare class OpenCodeServerManager implements OpenCodeServerManagerLike
62
63
  private readonly terminateProcess;
63
64
  private readonly portAllocator;
64
65
  private readonly resolveCommandPrefix;
66
+ private readonly resolveHomeDir;
65
67
  private readonly spawnServerProcess;
66
68
  constructor(options: OpenCodeServerManagerOptions);
67
69
  static getInstance(logger: Logger, runtimeSettings?: ProviderRuntimeSettings, options?: Omit<OpenCodeServerManagerOptions, "logger" | "runtimeSettings">): OpenCodeServerManager;
@@ -1,9 +1,12 @@
1
+ import { mkdirSync } from "node:fs";
2
+ import { stat } from "node:fs/promises";
1
3
  import net from "node:net";
2
- import os from "node:os";
4
+ import path from "node:path";
3
5
  import { findExecutable } from "../../../../executable-resolution/executable-resolution.js";
4
6
  import { spawnProcess } from "../../../../utils/spawn.js";
5
7
  import { terminateWithTreeKill } from "../../../../utils/tree-kill.js";
6
8
  import { createProviderEnvSpec, resolveProviderCommandPrefix, } from "../../provider-launch-config.js";
9
+ import { resolveOpenCodeHomeDir } from "./paths.js";
7
10
  const OPENCODE_SERVER_GRACEFUL_SHUTDOWN_TIMEOUT_MS = 5000;
8
11
  const OPENCODE_SERVER_FORCE_SHUTDOWN_TIMEOUT_MS = 1000;
9
12
  export class OpenCodeServerManager {
@@ -21,6 +24,7 @@ export class OpenCodeServerManager {
21
24
  this.resolveCommandPrefix =
22
25
  options.resolveCommandPrefix ??
23
26
  (() => resolveProviderCommandPrefix(this.runtimeSettings?.command, resolveOpenCodeBinary));
27
+ this.resolveHomeDir = options.resolveHomeDir ?? resolveOpenCodeHomeDir;
24
28
  this.spawnServerProcess = options.spawnServerProcess ?? spawnProcess;
25
29
  }
26
30
  static getInstance(logger, runtimeSettings, options = {}) {
@@ -164,7 +168,11 @@ export class OpenCodeServerManager {
164
168
  const url = `http://127.0.0.1:${port}`;
165
169
  const launchPrefix = await this.resolveCommandPrefix();
166
170
  const serverArgs = [...launchPrefix.args, "serve", "--port", String(port)];
167
- const serverCwd = os.homedir();
171
+ // Use a neutral OpenCode home as the server cwd. Launching from the user's
172
+ // home directory causes OpenCode to treat it as the default workspace and
173
+ // index the entire home tree.
174
+ const serverCwd = this.resolveHomeDir();
175
+ mkdirSync(serverCwd, { recursive: true });
168
176
  const serverProcess = this.spawnServerProcess(launchPrefix.command, serverArgs, {
169
177
  cwd: serverCwd,
170
178
  detached: process.platform !== "win32",
@@ -371,10 +379,31 @@ OpenCodeServerManager.instance = null;
371
379
  OpenCodeServerManager.exitHandlerRegistered = false;
372
380
  async function resolveOpenCodeBinary() {
373
381
  const found = await findExecutable("opencode");
374
- if (found) {
375
- return found;
382
+ if (!found) {
383
+ throw new Error("OpenCode binary not found. Install OpenCode (https://github.com/opencode-ai/opencode) and ensure it is available in your shell PATH.");
384
+ }
385
+ if (process.platform === "win32" && path.extname(found).toLowerCase() === ".cmd") {
386
+ // Global npm: <prefix>/opencode.cmd → <prefix>/node_modules/opencode-ai/bin/opencode.exe
387
+ const globalCandidate = path.join(path.dirname(found), "node_modules", "opencode-ai", "bin", "opencode.exe");
388
+ if (await pathExists(globalCandidate))
389
+ return globalCandidate;
390
+ // Local/pnpm: <project>/node_modules/.bin/opencode.cmd → <project>/node_modules/opencode-ai/bin/opencode.exe
391
+ const localCandidate = path.join(path.dirname(found), "..", "opencode-ai", "bin", "opencode.exe");
392
+ if (await pathExists(localCandidate))
393
+ return localCandidate;
394
+ console.warn("[opencode-server] Found opencode.cmd but could not resolve the real opencode.exe. " +
395
+ "The process may not be properly terminated on exit. Path: %s", found);
396
+ }
397
+ return found;
398
+ }
399
+ async function pathExists(filePath) {
400
+ try {
401
+ await stat(filePath);
402
+ return true;
403
+ }
404
+ catch {
405
+ return false;
376
406
  }
377
- throw new Error("OpenCode binary not found. Install OpenCode (https://github.com/opencode-ai/opencode) and ensure it is available in your shell PATH.");
378
407
  }
379
408
  function findAvailablePort() {
380
409
  return new Promise((resolve, reject) => {
@@ -4,6 +4,7 @@ import { type AgentCapabilityFlags, type AgentClient, type AgentCreateSessionOpt
4
4
  import { isDefaultAgentCreateConfigUnattended } from "../create-agent-mode.js";
5
5
  import { type ProviderRuntimeSettings } from "../provider-launch-config.js";
6
6
  import { type OpenCodeServerManagerLike } from "./opencode/server-manager.js";
7
+ import { resolveOpenCodeHomeDir } from "./opencode/paths.js";
7
8
  import type { ManagedProcessRegistry } from "../../managed-processes/managed-processes.js";
8
9
  declare function resolveOpenCodeCreateConfig(input: ResolveAgentCreateConfigInput): ResolveAgentCreateConfigResult;
9
10
  declare function isOpenCodeCreateConfigUnattended(input: Parameters<typeof isDefaultAgentCreateConfigUnattended>[0]): boolean;
@@ -100,11 +101,13 @@ export declare const __openCodeInternals: {
100
101
  resolveOpenCodeSelectedModelContextWindow: typeof resolveOpenCodeSelectedModelContextWindow;
101
102
  isSelectableOpenCodeAgent: typeof isSelectableOpenCodeAgent;
102
103
  mapOpenCodeAgentToMode: typeof mapOpenCodeAgentToMode;
104
+ resolveOpenCodeHomeDir: typeof resolveOpenCodeHomeDir;
103
105
  readonly OpenCodeAgentSession: typeof OpenCodeAgentSession;
104
106
  };
105
107
  interface OpenCodeAgentClientDeps {
106
108
  serverManager?: OpenCodeServerManagerLike;
107
109
  createClient?: OpenCodeClientFactory;
110
+ resolveHomeDir?: () => string;
108
111
  managedProcesses?: ManagedProcessRegistry;
109
112
  }
110
113
  type OpenCodeClientFactory = (options: {
@@ -118,6 +121,7 @@ export declare class OpenCodeAgentClient implements AgentClient {
118
121
  readonly isCreateConfigUnattended: typeof isOpenCodeCreateConfigUnattended;
119
122
  private readonly serverManager;
120
123
  private readonly createOpenCodeClient;
124
+ private readonly resolveHomeDir;
121
125
  private readonly logger;
122
126
  private readonly runtimeSettings?;
123
127
  private readonly modelContextWindows;
@@ -1,4 +1,5 @@
1
1
  import { createOpencodeClient, } from "@opencode-ai/sdk/v2/client";
2
+ import fs from "node:fs/promises";
2
3
  import { createPathEquivalenceMatcher } from "../../../utils/path.js";
3
4
  import pLimit from "p-limit";
4
5
  import { z } from "zod";
@@ -11,6 +12,7 @@ import { execCommand } from "../../../utils/spawn.js";
11
12
  import { buildToolCallDisplayModel } from "@getpaseo/protocol/tool-call-display";
12
13
  import { mapOpencodeToolCall } from "./opencode/tool-call-mapper.js";
13
14
  import { OpenCodeServerManager, } from "./opencode/server-manager.js";
15
+ import { resolveOpenCodeHomeDir } from "./opencode/paths.js";
14
16
  import { formatProviderDiagnostic, formatProviderDiagnosticError, buildBinaryDiagnosticRows, buildCommandResolutionDiagnosticRows, toDiagnosticErrorMessage, } from "./diagnostic-utils.js";
15
17
  import { runProviderTurn } from "./provider-runner.js";
16
18
  import { renderPromptAttachmentAsText } from "../prompt-attachments.js";
@@ -847,6 +849,7 @@ export const __openCodeInternals = {
847
849
  resolveOpenCodeSelectedModelContextWindow,
848
850
  isSelectableOpenCodeAgent,
849
851
  mapOpenCodeAgentToMode,
852
+ resolveOpenCodeHomeDir,
850
853
  get OpenCodeAgentSession() {
851
854
  return OpenCodeAgentSession;
852
855
  },
@@ -867,8 +870,10 @@ export class OpenCodeAgentClient {
867
870
  deps.serverManager ??
868
871
  OpenCodeServerManager.getInstance(this.logger, runtimeSettings, {
869
872
  managedProcesses: deps.managedProcesses,
873
+ resolveHomeDir: deps.resolveHomeDir,
870
874
  });
871
875
  this.createOpenCodeClient = deps.createClient ?? createSdkOpenCodeClient;
876
+ this.resolveHomeDir = deps.resolveHomeDir ?? resolveOpenCodeHomeDir;
872
877
  }
873
878
  async createSession(config, launchContext, options) {
874
879
  const openCodeConfig = this.assertConfig(config);
@@ -930,9 +935,16 @@ export class OpenCodeAgentClient {
930
935
  ? await this.serverManager.acquireNew()
931
936
  : await this.serverManager.acquireCurrent();
932
937
  const { url } = acquisition.server;
933
- const directory = options.cwd;
934
- const client = this.createOpenCodeClient({ baseUrl: url, directory });
938
+ const isGlobalCatalog = options.scope === "global";
935
939
  try {
940
+ // OpenCode treats the catalog directory as a workspace. The global catalog
941
+ // is not a project, so use the neutral OpenCode home instead of user home.
942
+ const directory = isGlobalCatalog ? this.resolveHomeDir() : options.cwd;
943
+ if (isGlobalCatalog) {
944
+ await fs.mkdir(directory, { recursive: true });
945
+ this.logger.debug({ directory }, "opencode catalog refresh: using opencode-home for global provider catalog");
946
+ }
947
+ const client = this.createOpenCodeClient({ baseUrl: url, directory });
936
948
  const [models, modes] = await Promise.all([
937
949
  this.fetchModelsFromClient(client, directory),
938
950
  this.fetchModesFromClient(client, directory),
@@ -6,6 +6,7 @@ import type { PiRuntime, PiRuntimeSession } from "./runtime.js";
6
6
  import type { PiCommandsRpcType, PiSessionState } from "./rpc-types.js";
7
7
  export declare const PiProviderParamsSchema: z.ZodObject<{
8
8
  sessionDir: z.ZodOptional<z.ZodString>;
9
+ extensionTimeoutMs: z.ZodDefault<z.ZodNumber>;
9
10
  }, z.core.$strict>;
10
11
  interface PiRpcAgentClientOptions {
11
12
  logger: Logger;
@@ -23,6 +24,7 @@ interface PiRpcAgentSessionOptions {
23
24
  initialState: PiSessionState;
24
25
  capabilities: AgentCapabilityFlags;
25
26
  cleanup?: () => void;
27
+ extensionTimeoutMs?: number;
26
28
  }
27
29
  export declare function transformPiModels(models: AgentModelDefinition[]): AgentModelDefinition[];
28
30
  export declare class PiRpcAgentSession implements AgentSession {
@@ -50,6 +52,7 @@ export declare class PiRpcAgentSession implements AgentSession {
50
52
  private readonly runtimeSession;
51
53
  private readonly config;
52
54
  private readonly cleanup?;
55
+ private readonly extensionTimeoutMs;
53
56
  get id(): string | null;
54
57
  run(prompt: AgentPromptInput, options?: AgentRunOptions): Promise<AgentRunResult>;
55
58
  startTurn(prompt: AgentPromptInput, _options?: AgentRunOptions): Promise<StartTurnResult>;
@@ -22,7 +22,7 @@ const PASEO_PI_TREE_EXTENSION_COMMAND = "paseo_tree";
22
22
  const PASEO_PI_CAPTURE_EXTENSION_COMMAND = "paseo_capture_entries";
23
23
  const PASEO_PI_ENTRY_CAPTURE_MARKER = "PASEO_ENTRY_CAPTURE";
24
24
  const PASEO_PI_COMMAND_RESULT_MARKER = "PASEO_COMMAND_RESULT";
25
- const PASEO_PI_EXTENSION_RESULT_TIMEOUT_MS = 10000;
25
+ const DEFAULT_PI_EXTENSION_RESULT_TIMEOUT_MS = 30000;
26
26
  const QUESTION_RESPONSE_HEADER = "Response";
27
27
  const QUESTION_COMMENT_HEADER = "Comment";
28
28
  const PI_ASK_USER_FREEFORM_SENTINEL = "✏️ Type custom response...";
@@ -30,6 +30,7 @@ const COMBINED_ASK_USER_METADATA = "ask_user_select_optional_comment";
30
30
  export const PiProviderParamsSchema = z
31
31
  .object({
32
32
  sessionDir: z.string().min(1).optional(),
33
+ extensionTimeoutMs: z.number().int().positive().default(DEFAULT_PI_EXTENSION_RESULT_TIMEOUT_MS),
33
34
  })
34
35
  .strict();
35
36
  const PI_HANDLED_BUILTIN_SLASH_COMMANDS = [
@@ -715,6 +716,7 @@ export class PiRpcAgentSession {
715
716
  normalizePiThinkingOption(options.config.thinkingOptionId) ??
716
717
  this.state.thinkingLevel ??
717
718
  null;
719
+ this.extensionTimeoutMs = options.extensionTimeoutMs ?? DEFAULT_PI_EXTENSION_RESULT_TIMEOUT_MS;
718
720
  this.runtimeSession.onEvent((event) => {
719
721
  this.handleRuntimeEvent(event);
720
722
  });
@@ -1065,7 +1067,7 @@ export class PiRpcAgentSession {
1065
1067
  const timer = setTimeout(() => {
1066
1068
  this.pendingExtensionResults.delete(requestId);
1067
1069
  reject(new Error(`Pi extension result timed out for request ${requestId}`));
1068
- }, PASEO_PI_EXTENSION_RESULT_TIMEOUT_MS);
1070
+ }, this.extensionTimeoutMs);
1069
1071
  this.pendingExtensionResults.set(requestId, { resolve, reject, timer });
1070
1072
  });
1071
1073
  }
@@ -1489,6 +1491,7 @@ export class PiRpcAgentClient {
1489
1491
  initialState: await runtimeSession.getState(),
1490
1492
  capabilities: withPiMcpCapability(mcpConfig !== null),
1491
1493
  cleanup: combineCleanup([mcpConfig?.cleanup, paseoExtension.cleanup]),
1494
+ extensionTimeoutMs: this.providerParams.extensionTimeoutMs,
1492
1495
  });
1493
1496
  }
1494
1497
  catch (error) {
@@ -1531,6 +1534,7 @@ export class PiRpcAgentClient {
1531
1534
  initialState: await runtimeSession.getState(),
1532
1535
  capabilities: withPiMcpCapability(mcpConfig !== null),
1533
1536
  cleanup: combineCleanup([mcpConfig?.cleanup, paseoExtension.cleanup]),
1537
+ extensionTimeoutMs: this.providerParams.extensionTimeoutMs,
1534
1538
  });
1535
1539
  }
1536
1540
  catch (error) {
@@ -1541,7 +1545,9 @@ export class PiRpcAgentClient {
1541
1545
  }
1542
1546
  }
1543
1547
  async fetchCatalog(options) {
1544
- const runtimeSession = await this.runtime.startSession({ cwd: options.cwd });
1548
+ const runtimeSession = await this.runtime.startSession({
1549
+ cwd: options.scope === "global" ? homedir() : options.cwd,
1550
+ });
1545
1551
  try {
1546
1552
  const models = transformPiModels((await runtimeSession.getAvailableModels(PI_CATALOG_REQUEST_TIMEOUT_MS)).map(mapPiModel));
1547
1553
  return { models, modes: [] };
@@ -44,11 +44,10 @@ export function materializeProviderImage(image) {
44
44
  fsSync.writeFileSync(filePath, bytes);
45
45
  return { path: filePath };
46
46
  }
47
- // Recognizes the markdown renderProviderImageOutputAsAssistantMarkdown emits for a materialized
48
- // provider image: its source is a content-hashed file in the attachments dir. Matching the full
49
- // <hash>.<ext> shape (not just a leading "![") keeps user-authored text from being mistaken for a
50
- // provider image when it reaches the history-replay filter. The separator class allows one-or-more
51
- // because on Windows the path uses "\\" and escapeMarkdownImageSource doubles each backslash.
47
+ // Recognizes markdown rendered for a materialized provider image: its source is a content-hashed
48
+ // file in the attachments dir. Matching the full <hash>.<ext> shape (not just a leading "![")
49
+ // keeps user-authored text from being mistaken for a provider image during history replay. The
50
+ // separator still accepts old doubled-backslash Windows history; new Windows output uses file URIs.
52
51
  const PROVIDER_IMAGE_MARKDOWN = new RegExp(`^!\\[[^\\]]*\\]\\([^)]*${PROVIDER_IMAGE_ATTACHMENT_DIR}[/\\\\]+[0-9a-f]{64}\\.[a-z0-9]+\\)`);
53
52
  export function isProviderImageMarkdown(text) {
54
53
  return PROVIDER_IMAGE_MARKDOWN.test(text);
@@ -63,8 +62,14 @@ function isDataImageSource(source) {
63
62
  function escapeMarkdownImageAlt(value) {
64
63
  return value.replace(/\\/g, "\\\\").replace(/\]/g, "\\]");
65
64
  }
65
+ function markdownImageSource(value) {
66
+ if (/^[A-Za-z]:[\\/]/.test(value)) {
67
+ return `file:///${value.replace(/\\/g, "/")}`;
68
+ }
69
+ return value;
70
+ }
66
71
  function escapeMarkdownImageSource(value) {
67
- return value.replace(/\\/g, "\\\\").replace(/\)/g, "\\)");
72
+ return markdownImageSource(value).replace(/\\/g, "\\\\").replace(/\)/g, "\\)");
68
73
  }
69
74
  export function renderProviderImageOutputAsAssistantMarkdown(image, options = {}) {
70
75
  const source = nonEmptyString(image.path) ?? nonEmptyString(image.url);
@@ -6,7 +6,7 @@ import type { VoiceCallerContext, VoiceSpeakHandler } from "../../voice-types.js
6
6
  import type { TerminalManager } from "../../../terminal/terminal-manager.js";
7
7
  import type { CreatePaseoWorktreeWorkflowFn } from "../../worktree-session.js";
8
8
  import type { ScheduleService } from "../../schedule/service.js";
9
- import { type ProviderSnapshotManager } from "../provider-snapshot-manager.js";
9
+ import type { ProviderSnapshotManager } from "../provider-snapshot-manager.js";
10
10
  import type { GitHubService } from "../../../services/github-service.js";
11
11
  import type { WorkspaceGitService } from "../../workspace-git-service.js";
12
12
  import type { PaseoToolCatalog } from "./types.js";
@@ -11,7 +11,6 @@ import { WaitForAgentTracker } from "../wait-for-agent-tracker.js";
11
11
  import { createAgentCommand } from "../create-agent/create.js";
12
12
  import { expandUserPath, isSameOrDescendantPath, resolvePathFromBase } from "../../path-utils.js";
13
13
  import { ScheduleRunSchema, ScheduleSummarySchema, StoredScheduleSchema, } from "@getpaseo/protocol/schedule/types";
14
- import { resolveSnapshotCwd } from "../provider-snapshot-manager.js";
15
14
  import { AgentModelSchema, AgentProviderEnum, AgentStatusEnum, ProviderModeSchema, ProviderSummarySchema, parseDurationString, resolveRequiredProviderModel, sanitizePermissionRequest, serializeSnapshotWithMetadata, toScheduleSummary, waitForAgentWithTimeout, } from "../mcp-shared.js";
16
15
  import { sendPromptToAgent, setupFinishNotification } from "../agent-prompt.js";
17
16
  import { respondToAgentPermission } from "../permission-response.js";
@@ -1760,7 +1759,6 @@ export function createPaseoToolCatalog(options) {
1760
1759
  },
1761
1760
  }, async ({ provider }) => {
1762
1761
  const models = await providerSnapshotManager.listModels({
1763
- cwd: resolveSnapshotCwd(),
1764
1762
  provider,
1765
1763
  wait: true,
1766
1764
  });
@@ -44,11 +44,12 @@ export type DaemonLifecycleIntent = {
44
44
  type: "shutdown";
45
45
  clientId: string;
46
46
  requestId: string;
47
+ reason: string;
47
48
  } | {
48
49
  type: "restart";
49
50
  clientId: string;
50
51
  requestId: string;
51
- reason?: string;
52
+ reason: string;
52
53
  };
53
54
  export interface PaseoDaemonConfig {
54
55
  listen: string;
@@ -57,6 +58,7 @@ export interface PaseoDaemonConfig {
57
58
  corsAllowedOrigins: string[];
58
59
  allowedHosts?: HostnamesConfig;
59
60
  hostnames?: HostnamesConfig;
61
+ trustedProxies?: true | string[];
60
62
  mcpEnabled?: boolean;
61
63
  mcpInjectIntoAgents?: boolean;
62
64
  autoArchiveAfterMerge?: boolean;
@@ -77,6 +79,10 @@ export interface PaseoDaemonConfig {
77
79
  publicBaseUrl: string | null;
78
80
  standaloneListen: string | null;
79
81
  };
82
+ webUi?: {
83
+ enabled: boolean;
84
+ distDir: string | null;
85
+ };
80
86
  appBaseUrl?: string;
81
87
  auth?: DaemonAuthConfig;
82
88
  openai?: PaseoOpenAIConfig;
@@ -113,6 +113,7 @@ import { createManagedProcessRegistry, createSystemManagedProcessTable, } from "
113
113
  import { terminateWithTreeKill } from "../utils/tree-kill.js";
114
114
  import { isHostnameAllowed } from "./hostnames.js";
115
115
  import { createRequireBearerMiddleware, isAgentMcpRequestAuthorized, } from "./auth.js";
116
+ import { createWebUiMiddleware } from "./web-ui.js";
116
117
  const MAX_MCP_DEBUG_BATCH_ITEMS = 10;
117
118
  const REDACTED_LOG_VALUE = "[redacted]";
118
119
  const DOWNLOAD_OPEN_FLAGS = process.platform === "win32" ? constants.O_RDONLY : constants.O_RDONLY | constants.O_NOFOLLOW;
@@ -230,6 +231,17 @@ async function reconcileManagedProcessLedger(managedProcesses, logger) {
230
231
  logger.info(reapResult, "Managed helper process ledger reconciled");
231
232
  }
232
233
  }
234
+ function mountWebUi(app, config, logger) {
235
+ app.use(createWebUiMiddleware({
236
+ enabled: config.webUi?.enabled ?? false,
237
+ distDir: config.webUi?.distDir ?? null,
238
+ label: getHostname(),
239
+ logger,
240
+ }));
241
+ }
242
+ function resolveExpressTrustProxySetting(config) {
243
+ return config.trustedProxies ?? ["loopback"];
244
+ }
233
245
  export async function createPaseoDaemon(config, rootLogger) {
234
246
  const logger = rootLogger.child({ module: "bootstrap" });
235
247
  const bootstrapStart = performance.now();
@@ -279,6 +291,7 @@ export async function createPaseoDaemon(config, rootLogger) {
279
291
  const agentMcpAuthToken = randomUUID();
280
292
  const listenTarget = parseListenString(config.listen);
281
293
  const app = express();
294
+ app.set("trust proxy", resolveExpressTrustProxySetting(config));
282
295
  let boundListenTarget = null;
283
296
  let workspaceRegistry = null;
284
297
  const terminalManager = createConfiguredTerminalManager({
@@ -363,6 +376,11 @@ export async function createPaseoDaemon(config, rootLogger) {
363
376
  });
364
377
  // Local, harmless, and token-gated; deliberately skips daemon auth.
365
378
  app.post("/api/terminal-activity", express.json(), createTerminalActivityRouteHandler(terminalManager));
379
+ // Serve the bundled browser web UI when enabled. Mounted after service-proxy
380
+ // classification and host/CORS handling, but before daemon bearer auth, so
381
+ // static app files load without the daemon password while API/WebSocket calls
382
+ // remain protected.
383
+ mountWebUi(app, config, logger);
366
384
  app.use(createRequireBearerMiddleware(config.auth, (context) => {
367
385
  logger.warn(context, "Rejected HTTP request with invalid daemon password");
368
386
  }));
@@ -1,11 +1,13 @@
1
1
  import type { PaseoDaemonConfig } from "./bootstrap.js";
2
2
  import { type HostnamesConfig } from "./hostnames.js";
3
+ export declare function resolveBundledWebUiDistDir(moduleUrl?: string | URL): string;
3
4
  export type CliConfigOverrides = Partial<{
4
5
  listen: string;
5
6
  relayEnabled: boolean;
6
7
  relayUseTls: boolean;
7
8
  mcpEnabled: boolean;
8
9
  mcpInjectIntoAgents: boolean;
10
+ webUiEnabled: boolean;
9
11
  hostnames: HostnamesConfig;
10
12
  }>;
11
13
  export declare function loadConfig(paseoHome: string, options?: {