@getpaseo/server 0.1.17 → 0.1.20

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 (80) hide show
  1. package/dist/server/client/daemon-client.d.ts +5 -1
  2. package/dist/server/client/daemon-client.d.ts.map +1 -1
  3. package/dist/server/client/daemon-client.js +22 -0
  4. package/dist/server/client/daemon-client.js.map +1 -1
  5. package/dist/server/server/agent/agent-management-mcp.d.ts.map +1 -1
  6. package/dist/server/server/agent/agent-management-mcp.js +11 -28
  7. package/dist/server/server/agent/agent-management-mcp.js.map +1 -1
  8. package/dist/server/server/agent/agent-manager.d.ts +2 -0
  9. package/dist/server/server/agent/agent-manager.d.ts.map +1 -1
  10. package/dist/server/server/agent/agent-manager.js +50 -6
  11. package/dist/server/server/agent/agent-manager.js.map +1 -1
  12. package/dist/server/server/agent/mcp-server.d.ts.map +1 -1
  13. package/dist/server/server/agent/mcp-server.js +11 -34
  14. package/dist/server/server/agent/mcp-server.js.map +1 -1
  15. package/dist/server/server/agent/providers/claude-agent.d.ts.map +1 -1
  16. package/dist/server/server/agent/providers/claude-agent.js +169 -40
  17. package/dist/server/server/agent/providers/claude-agent.js.map +1 -1
  18. package/dist/server/server/bootstrap.d.ts +13 -0
  19. package/dist/server/server/bootstrap.d.ts.map +1 -1
  20. package/dist/server/server/bootstrap.js +46 -32
  21. package/dist/server/server/bootstrap.js.map +1 -1
  22. package/dist/server/server/persisted-config.d.ts +22 -22
  23. package/dist/server/server/session.d.ts +10 -6
  24. package/dist/server/server/session.d.ts.map +1 -1
  25. package/dist/server/server/session.js +191 -223
  26. package/dist/server/server/session.js.map +1 -1
  27. package/dist/server/server/speech/speech-types.d.ts +2 -2
  28. package/dist/server/server/websocket-server.d.ts +4 -1
  29. package/dist/server/server/websocket-server.d.ts.map +1 -1
  30. package/dist/server/server/websocket-server.js +27 -1
  31. package/dist/server/server/websocket-server.js.map +1 -1
  32. package/dist/server/server/workspace-registry-bootstrap.d.ts +11 -0
  33. package/dist/server/server/workspace-registry-bootstrap.d.ts.map +1 -0
  34. package/dist/server/server/workspace-registry-bootstrap.js +98 -0
  35. package/dist/server/server/workspace-registry-bootstrap.js.map +1 -0
  36. package/dist/server/server/workspace-registry-model.d.ts +26 -0
  37. package/dist/server/server/workspace-registry-model.d.ts.map +1 -0
  38. package/dist/server/server/workspace-registry-model.js +150 -0
  39. package/dist/server/server/workspace-registry-model.js.map +1 -0
  40. package/dist/server/server/workspace-registry.d.ts +128 -0
  41. package/dist/server/server/workspace-registry.d.ts.map +1 -0
  42. package/dist/server/server/workspace-registry.js +141 -0
  43. package/dist/server/server/workspace-registry.js.map +1 -0
  44. package/dist/server/shared/messages.d.ts +1510 -0
  45. package/dist/server/shared/messages.d.ts.map +1 -1
  46. package/dist/server/shared/messages.js +39 -0
  47. package/dist/server/shared/messages.js.map +1 -1
  48. package/dist/server/utils/checkout-git.d.ts +5 -0
  49. package/dist/server/utils/checkout-git.d.ts.map +1 -1
  50. package/dist/server/utils/checkout-git.js +64 -4
  51. package/dist/server/utils/checkout-git.js.map +1 -1
  52. package/dist/server/utils/project-icon.d.ts +1 -1
  53. package/dist/server/utils/project-icon.d.ts.map +1 -1
  54. package/dist/server/utils/project-icon.js +9 -2
  55. package/dist/server/utils/project-icon.js.map +1 -1
  56. package/dist/src/server/agent/agent-manager.js +50 -6
  57. package/dist/src/server/agent/agent-manager.js.map +1 -1
  58. package/dist/src/server/agent/mcp-server.js +11 -34
  59. package/dist/src/server/agent/mcp-server.js.map +1 -1
  60. package/dist/src/server/agent/providers/claude-agent.js +169 -40
  61. package/dist/src/server/agent/providers/claude-agent.js.map +1 -1
  62. package/dist/src/server/bootstrap.js +46 -32
  63. package/dist/src/server/bootstrap.js.map +1 -1
  64. package/dist/src/server/session.js +191 -223
  65. package/dist/src/server/session.js.map +1 -1
  66. package/dist/src/server/websocket-server.js +27 -1
  67. package/dist/src/server/websocket-server.js.map +1 -1
  68. package/dist/src/server/workspace-registry-bootstrap.js +98 -0
  69. package/dist/src/server/workspace-registry-bootstrap.js.map +1 -0
  70. package/dist/src/server/workspace-registry-model.js +150 -0
  71. package/dist/src/server/workspace-registry-model.js.map +1 -0
  72. package/dist/src/server/workspace-registry.js +141 -0
  73. package/dist/src/server/workspace-registry.js.map +1 -0
  74. package/dist/src/shared/messages.js +39 -0
  75. package/dist/src/shared/messages.js.map +1 -1
  76. package/dist/src/utils/checkout-git.js +64 -4
  77. package/dist/src/utils/checkout-git.js.map +1 -1
  78. package/dist/src/utils/project-icon.js +9 -2
  79. package/dist/src/utils/project-icon.js.map +1 -1
  80. package/package.json +4 -3
@@ -11,6 +11,24 @@ import { buildClaudeModelFamilyAliases, buildClaudeSelectableModelIds, listClaud
11
11
  import { buildToolCallDisplayModel } from "../../../shared/tool-call-display.js";
12
12
  import { applyProviderEnv, isProviderCommandAvailable, } from "../provider-launch-config.js";
13
13
  import { getOrchestratorModeInstructions } from "../orchestrator-instructions.js";
14
+ /*
15
+ * Routing invariant:
16
+ * While a foreground Claude turn is active, identifier-less assistant/stream/result
17
+ * events must stay attached to that foreground run unless we have explicit evidence
18
+ * that a distinct autonomous run has started.
19
+ *
20
+ * We previously allowed task_notification metadata to reserve autonomous ownership,
21
+ * then consumed that reservation on the next unbound chunk. In practice Claude often
22
+ * emits task_notification records and foreground tool-use stream chunks interleaved
23
+ * within the same turn. That let a foreground turn's terminal result get misrouted
24
+ * into an autonomous side run, which stranded the agent in "running" because the
25
+ * foreground stream never received turn_completed.
26
+ *
27
+ * The rule below is intentionally conservative: foreground turns get first claim on
28
+ * same-turn traffic, and autonomous wake reservations are only consumed once no
29
+ * foreground turn is active. This keeps task_notification advisory instead of letting
30
+ * it steal ownership from the active user turn.
31
+ */
14
32
  const fsPromises = promises;
15
33
  const CLAUDE_SETTING_SOURCES = [
16
34
  "user",
@@ -157,15 +175,18 @@ const REWIND_COMMAND = {
157
175
  };
158
176
  const INTERRUPT_TOOL_USE_PLACEHOLDER = "[Request interrupted by user for tool use]";
159
177
  const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
160
- function resolveClaudeBinary() {
178
+ function whichClaude() {
161
179
  try {
162
- const claudePath = execSync("which claude", { encoding: "utf8" }).trim();
163
- if (claudePath) {
164
- return claudePath;
165
- }
180
+ return execSync("which claude", { encoding: "utf8" }).trim() || null;
166
181
  }
167
182
  catch {
168
- // fall through
183
+ return null;
184
+ }
185
+ }
186
+ function resolveClaudeBinary() {
187
+ const claudePath = whichClaude();
188
+ if (claudePath) {
189
+ return claudePath;
169
190
  }
170
191
  throw new Error("Claude CLI not found. Install claude or configure agents.providers.claude.command.mode='replace'.");
171
192
  }
@@ -942,11 +963,18 @@ export class ClaudeAgentClient {
942
963
  this.defaults = options.defaults;
943
964
  this.logger = options.logger.child({ module: "agent", provider: "claude" });
944
965
  this.runtimeSettings = options.runtimeSettings;
945
- try {
946
- this.claudePath = execSync("which claude", { encoding: "utf8" }).trim() || null;
966
+ this.claudePath = whichClaude();
967
+ if (this.claudePath) {
968
+ try {
969
+ const version = execSync(`${this.claudePath} --version`, { encoding: "utf8" }).trim();
970
+ this.logger.trace({ claudePath: this.claudePath, version }, "Resolved Claude binary");
971
+ }
972
+ catch {
973
+ this.logger.trace({ claudePath: this.claudePath }, "Resolved Claude binary (version unknown)");
974
+ }
947
975
  }
948
- catch {
949
- this.claudePath = null;
976
+ else {
977
+ this.logger.trace("Claude binary not found in PATH; SDK will use bundled binary");
950
978
  }
951
979
  }
952
980
  async createSession(config) {
@@ -2008,8 +2036,10 @@ class ClaudeAgentSession {
2008
2036
  }
2009
2037
  routeMessage(normalized) {
2010
2038
  if (normalized.metadataOnly) {
2011
- if (normalized.message.type === "user" &&
2012
- isTaskNotificationUserContent(normalized.message.message?.content)) {
2039
+ if ((normalized.message.type === "user" &&
2040
+ isTaskNotificationUserContent(normalized.message.message?.content)) ||
2041
+ (normalized.message.type === "system" &&
2042
+ normalized.message.subtype === "task_notification")) {
2013
2043
  this.reserveAutonomousWake("task_notification");
2014
2044
  }
2015
2045
  this.notePreReplayMetadata(normalized.message);
@@ -2022,12 +2052,6 @@ class ClaudeAgentSession {
2022
2052
  if (byIdentifiers.run) {
2023
2053
  return byIdentifiers;
2024
2054
  }
2025
- if (this.turnState === "autonomous") {
2026
- const activeAutonomousRun = this.runTracker.getLatestActiveRun("autonomous");
2027
- if (activeAutonomousRun) {
2028
- return { run: activeAutonomousRun, reason: "unbound_autonomous" };
2029
- }
2030
- }
2031
2055
  const foregroundRun = this.activeForegroundTurn
2032
2056
  ? this.runTracker.getRun(this.activeForegroundTurn.runId)
2033
2057
  : null;
@@ -2059,26 +2083,48 @@ class ClaudeAgentSession {
2059
2083
  })) {
2060
2084
  return { run: foregroundRun, reason: "foreground" };
2061
2085
  }
2086
+ if (this.pendingAutonomousWakeReservations > 0 &&
2087
+ !normalized.identifiers.taskId &&
2088
+ !normalized.identifiers.parentMessageId &&
2089
+ !normalized.identifiers.messageId) {
2090
+ const reservedAutonomousRun = this.claimOrCreateAutonomousRun("reservation_unbound");
2091
+ return {
2092
+ run: reservedAutonomousRun,
2093
+ reason: "reserved_autonomous",
2094
+ };
2095
+ }
2062
2096
  if (!hasIdentifiers) {
2063
- if (this.pendingAutonomousWakeReservations > 0) {
2064
- const reservedAutonomousRun = this.claimOrCreateAutonomousRun("reservation_unbound");
2065
- return {
2066
- run: reservedAutonomousRun,
2067
- reason: "unbound_autonomous",
2068
- };
2069
- }
2070
2097
  const activeAutonomousRun = this.runTracker.getLatestActiveRun("autonomous");
2071
2098
  if (activeAutonomousRun) {
2072
2099
  return { run: activeAutonomousRun, reason: "unbound_autonomous" };
2073
2100
  }
2101
+ if (!foregroundRun &&
2102
+ (normalized.message.type === "assistant" ||
2103
+ normalized.message.type === "stream_event" ||
2104
+ normalized.message.type === "result" ||
2105
+ normalized.message.type === "tool_progress")) {
2106
+ const autonomousRun = this.claimOrCreateAutonomousRun("unbound_implicit");
2107
+ return { run: autonomousRun, reason: "unbound_autonomous" };
2108
+ }
2074
2109
  }
2075
2110
  if (this.pendingAutonomousWakeReservations > 0) {
2076
2111
  const reservedAutonomousRun = this.claimOrCreateAutonomousRun("reservation_fallback");
2077
- return { run: reservedAutonomousRun, reason: "fallback" };
2112
+ return { run: reservedAutonomousRun, reason: "reserved_autonomous" };
2078
2113
  }
2079
- const autonomousRun = this.createRun("autonomous", null);
2080
- this.emitRunEvent(autonomousRun, { type: "turn_started", provider: "claude" });
2081
- return { run: autonomousRun, reason: "fallback" };
2114
+ this.logger.debug({
2115
+ messageType: normalized.message.type,
2116
+ hasIdentifiers,
2117
+ taskId: normalized.identifiers.taskId,
2118
+ parentMessageId: normalized.identifiers.parentMessageId,
2119
+ messageId: normalized.identifiers.messageId,
2120
+ turnState: this.turnState,
2121
+ pendingAutonomousWakeReservations: this.pendingAutonomousWakeReservations,
2122
+ }, "Ignoring unmatched Claude SDK message without explicit run start signal");
2123
+ return {
2124
+ run: null,
2125
+ reason: "ignored_unmatched",
2126
+ dispatchWithoutRun: false,
2127
+ };
2082
2128
  }
2083
2129
  shouldPreferForegroundRun(input) {
2084
2130
  const { run, message } = input;
@@ -2135,7 +2181,12 @@ class ClaudeAgentSession {
2135
2181
  if (!foregroundRun || foregroundRun.promptReplaySeen) {
2136
2182
  return;
2137
2183
  }
2138
- if (message.type === "system" && message.subtype === "init") {
2184
+ // Most system metadata (init/hook callbacks/etc.) can precede the first prompt
2185
+ // replay for a legitimate foreground run. Treating all of it as churn strands
2186
+ // one-shot helper runs. task_notification is the exception: it represents
2187
+ // background agent activity and should suppress pre-replay foreground routing.
2188
+ if (message.type === "system" &&
2189
+ message.subtype !== "task_notification") {
2139
2190
  return;
2140
2191
  }
2141
2192
  this.preReplayMetadataSeen = true;
@@ -2177,7 +2228,7 @@ class ClaudeAgentSession {
2177
2228
  return;
2178
2229
  }
2179
2230
  const pump = this.runQueryPump().catch((error) => {
2180
- this.logger.warn({ err: error }, "Claude query pump exited unexpectedly");
2231
+ this.logger.trace({ err: error }, "Claude query pump exited unexpectedly");
2181
2232
  });
2182
2233
  this.queryPumpPromise = pump;
2183
2234
  pump.finally(() => {
@@ -2197,17 +2248,22 @@ class ClaudeAgentSession {
2197
2248
  q = await this.ensureQuery();
2198
2249
  }
2199
2250
  catch (error) {
2200
- this.logger.warn({ err: error }, "Failed to initialize Claude query pump");
2251
+ this.logger.trace({ err: error }, "Failed to initialize Claude query pump");
2201
2252
  await this.waitForLiveHistoryPoll();
2202
2253
  continue;
2203
2254
  }
2204
2255
  let next;
2205
2256
  try {
2206
2257
  next = await q.next();
2207
- this.logger.info({ claudeSessionId: this.claudeSessionId, next }, "Claude query pump raw next()");
2258
+ this.logger.trace({ claudeSessionId: this.claudeSessionId, next }, "Claude query pump raw next()");
2208
2259
  }
2209
2260
  catch (error) {
2210
- this.logger.warn({ err: error }, "Claude query pump next() failed");
2261
+ if (this.query !== q) {
2262
+ this.logger.trace({ err: error, staleQuery: true }, "Ignoring Claude query pump next() failure from replaced query");
2263
+ await this.awaitWithTimeout(q.return?.(), "query pump return after stale failure");
2264
+ continue;
2265
+ }
2266
+ this.logger.trace({ err: error }, "Claude query pump next() failed");
2211
2267
  for (const run of this.runTracker.listActiveRuns()) {
2212
2268
  this.failRun(run, error instanceof Error ? error.message : "Claude stream failed");
2213
2269
  }
@@ -2221,6 +2277,15 @@ class ClaudeAgentSession {
2221
2277
  continue;
2222
2278
  }
2223
2279
  if (next.done) {
2280
+ if (this.query !== q) {
2281
+ this.logger.trace({
2282
+ claudeSessionId: this.claudeSessionId,
2283
+ activeRunCount: this.runTracker.listActiveRuns().length,
2284
+ staleQuery: true,
2285
+ }, "Ignoring replaced Claude query pump completion");
2286
+ await this.awaitWithTimeout(q.return?.(), "query pump return on stale done");
2287
+ continue;
2288
+ }
2224
2289
  this.logger.trace({
2225
2290
  claudeSessionId: this.claudeSessionId,
2226
2291
  activeRunCount: this.runTracker.listActiveRuns().length,
@@ -2244,11 +2309,23 @@ class ClaudeAgentSession {
2244
2309
  if (!sdkMessage) {
2245
2310
  continue;
2246
2311
  }
2312
+ if (this.query !== q) {
2313
+ this.logger.trace({
2314
+ claudeSessionId: this.claudeSessionId,
2315
+ messageType: sdkMessage.type,
2316
+ staleQuery: true,
2317
+ }, "Ignoring Claude SDK message from replaced query");
2318
+ await this.awaitWithTimeout(q.return?.(), "query pump return on stale message");
2319
+ continue;
2320
+ }
2321
+ if (await this.handleMissingResumedConversation(sdkMessage, q)) {
2322
+ continue;
2323
+ }
2247
2324
  try {
2248
2325
  this.routeSdkMessageFromPump(sdkMessage);
2249
2326
  }
2250
2327
  catch (error) {
2251
- this.logger.warn({ err: error }, "Failed to route Claude SDK message from query pump");
2328
+ this.logger.trace({ err: error }, "Failed to route Claude SDK message from query pump");
2252
2329
  }
2253
2330
  }
2254
2331
  }
@@ -2303,6 +2380,9 @@ class ClaudeAgentSession {
2303
2380
  return;
2304
2381
  }
2305
2382
  if (!route.run) {
2383
+ if (route.dispatchWithoutRun === false) {
2384
+ return;
2385
+ }
2306
2386
  this.dispatchMetadataEvents(events);
2307
2387
  return;
2308
2388
  }
@@ -2310,6 +2390,33 @@ class ClaudeAgentSession {
2310
2390
  this.emitRunEvent(route.run, event);
2311
2391
  }
2312
2392
  }
2393
+ async handleMissingResumedConversation(message, query) {
2394
+ const staleResumeError = this.readMissingResumedConversationError(message);
2395
+ if (!staleResumeError) {
2396
+ return false;
2397
+ }
2398
+ this.logger.warn({
2399
+ claudeSessionId: this.claudeSessionId,
2400
+ error: staleResumeError,
2401
+ }, "Claude resumed session no longer exists; invalidating persisted session");
2402
+ for (const run of this.runTracker.listActiveRuns()) {
2403
+ this.failRun(run, staleResumeError);
2404
+ }
2405
+ this.transitionTurnStateFromActiveRuns("missing resumed conversation");
2406
+ this.input?.end();
2407
+ await this.awaitWithTimeout(query.return?.(), "query pump return on missing resumed conversation");
2408
+ if (this.query === query) {
2409
+ this.query = null;
2410
+ this.input = null;
2411
+ }
2412
+ this.claudeSessionId = null;
2413
+ this.persistence = null;
2414
+ this.persistedHistory = [];
2415
+ this.historyPending = false;
2416
+ this.cachedRuntimeInfo = null;
2417
+ this.queryRestartNeeded = false;
2418
+ return true;
2419
+ }
2313
2420
  shouldSuppressReplayResultTerminal(input) {
2314
2421
  const { run, message } = input;
2315
2422
  if (!run || run.owner !== "foreground" || message.type !== "result") {
@@ -2428,22 +2535,22 @@ class ClaudeAgentSession {
2428
2535
  async interruptActiveTurn() {
2429
2536
  const queryToInterrupt = this.query;
2430
2537
  if (!queryToInterrupt || typeof queryToInterrupt.interrupt !== "function") {
2431
- this.logger.debug("interruptActiveTurn: no query to interrupt");
2538
+ this.logger.trace("interruptActiveTurn: no query to interrupt");
2432
2539
  return;
2433
2540
  }
2434
2541
  try {
2435
- this.logger.debug("interruptActiveTurn: calling query.interrupt()...");
2542
+ this.logger.trace("interruptActiveTurn: calling query.interrupt()...");
2436
2543
  const t0 = Date.now();
2437
2544
  await queryToInterrupt.interrupt();
2438
- this.logger.debug({ durationMs: Date.now() - t0 }, "interruptActiveTurn: query.interrupt() returned");
2545
+ this.logger.trace({ durationMs: Date.now() - t0 }, "interruptActiveTurn: query.interrupt() returned");
2439
2546
  // After interrupt(), the query iterator is done (returns done: true).
2440
2547
  // Clear it so ensureQuery() creates a fresh query for the next turn.
2441
2548
  // Also end the input stream and call return() to clean up the SDK process.
2442
2549
  this.input?.end();
2443
- this.logger.debug("interruptActiveTurn: calling query.return()...");
2550
+ this.logger.trace("interruptActiveTurn: calling query.return()...");
2444
2551
  const t1 = Date.now();
2445
2552
  await queryToInterrupt.return?.();
2446
- this.logger.debug({ durationMs: Date.now() - t1 }, "interruptActiveTurn: query.return() returned");
2553
+ this.logger.trace({ durationMs: Date.now() - t1 }, "interruptActiveTurn: query.return() returned");
2447
2554
  this.query = null;
2448
2555
  this.input = null;
2449
2556
  this.queryRestartNeeded = false;
@@ -2902,6 +3009,28 @@ class ClaudeAgentSession {
2902
3009
  }
2903
3010
  return threadStartedSessionId;
2904
3011
  }
3012
+ readMissingResumedConversationError(message) {
3013
+ if (message.type !== "result" || message.subtype !== "error_during_execution") {
3014
+ return null;
3015
+ }
3016
+ if (!this.claudeSessionId) {
3017
+ return null;
3018
+ }
3019
+ const errors = "errors" in message && Array.isArray(message.errors) ? message.errors : [];
3020
+ for (const entry of errors) {
3021
+ if (typeof entry !== "string") {
3022
+ continue;
3023
+ }
3024
+ const match = entry.match(/^No conversation found with session ID:\s*(.+)$/);
3025
+ if (!match) {
3026
+ continue;
3027
+ }
3028
+ if (match[1]?.trim() === this.claudeSessionId) {
3029
+ return entry.trim();
3030
+ }
3031
+ }
3032
+ return null;
3033
+ }
2905
3034
  convertUsage(message) {
2906
3035
  if (!message.usage) {
2907
3036
  return undefined;