@getpaseo/server 0.1.2 → 0.1.3

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 (112) hide show
  1. package/dist/server/client/daemon-client.d.ts +11 -5
  2. package/dist/server/client/daemon-client.d.ts.map +1 -1
  3. package/dist/server/client/daemon-client.js +54 -46
  4. package/dist/server/client/daemon-client.js.map +1 -1
  5. package/dist/server/server/agent/agent-manager.d.ts +7 -0
  6. package/dist/server/server/agent/agent-manager.d.ts.map +1 -1
  7. package/dist/server/server/agent/agent-manager.js +41 -1
  8. package/dist/server/server/agent/agent-manager.js.map +1 -1
  9. package/dist/server/server/agent/providers/claude/tool-call-detail-parser.d.ts.map +1 -1
  10. package/dist/server/server/agent/providers/claude/tool-call-detail-parser.js +62 -12
  11. package/dist/server/server/agent/providers/claude/tool-call-detail-parser.js.map +1 -1
  12. package/dist/server/server/agent/providers/claude/tool-call-mapper.d.ts +4 -4
  13. package/dist/server/server/agent/providers/claude/tool-call-mapper.d.ts.map +1 -1
  14. package/dist/server/server/agent/providers/claude/tool-call-mapper.js +99 -50
  15. package/dist/server/server/agent/providers/claude/tool-call-mapper.js.map +1 -1
  16. package/dist/server/server/agent/providers/claude-agent.d.ts.map +1 -1
  17. package/dist/server/server/agent/providers/claude-agent.js +17 -10
  18. package/dist/server/server/agent/providers/claude-agent.js.map +1 -1
  19. package/dist/server/server/agent/providers/codex/tool-call-detail-parser.d.ts +0 -1
  20. package/dist/server/server/agent/providers/codex/tool-call-detail-parser.d.ts.map +1 -1
  21. package/dist/server/server/agent/providers/codex/tool-call-detail-parser.js +67 -30
  22. package/dist/server/server/agent/providers/codex/tool-call-detail-parser.js.map +1 -1
  23. package/dist/server/server/agent/providers/codex/tool-call-mapper.d.ts +1 -1
  24. package/dist/server/server/agent/providers/codex/tool-call-mapper.d.ts.map +1 -1
  25. package/dist/server/server/agent/providers/codex/tool-call-mapper.js +293 -266
  26. package/dist/server/server/agent/providers/codex/tool-call-mapper.js.map +1 -1
  27. package/dist/server/server/agent/providers/codex-app-server-agent.d.ts +1 -1
  28. package/dist/server/server/agent/providers/codex-app-server-agent.d.ts.map +1 -1
  29. package/dist/server/server/agent/providers/codex-app-server-agent.js +24 -14
  30. package/dist/server/server/agent/providers/codex-app-server-agent.js.map +1 -1
  31. package/dist/server/server/agent/providers/codex-rollout-timeline.d.ts.map +1 -1
  32. package/dist/server/server/agent/providers/codex-rollout-timeline.js +5 -4
  33. package/dist/server/server/agent/providers/codex-rollout-timeline.js.map +1 -1
  34. package/dist/server/server/agent/providers/opencode/tool-call-detail-parser.d.ts.map +1 -1
  35. package/dist/server/server/agent/providers/opencode/tool-call-detail-parser.js +8 -2
  36. package/dist/server/server/agent/providers/opencode/tool-call-detail-parser.js.map +1 -1
  37. package/dist/server/server/agent/providers/opencode/tool-call-mapper.d.ts +1 -1
  38. package/dist/server/server/agent/providers/opencode/tool-call-mapper.d.ts.map +1 -1
  39. package/dist/server/server/agent/providers/opencode/tool-call-mapper.js +121 -45
  40. package/dist/server/server/agent/providers/opencode/tool-call-mapper.js.map +1 -1
  41. package/dist/server/server/agent/providers/opencode-agent.d.ts.map +1 -1
  42. package/dist/server/server/agent/providers/opencode-agent.js +87 -35
  43. package/dist/server/server/agent/providers/opencode-agent.js.map +1 -1
  44. package/dist/server/server/agent/providers/tool-call-detail-primitives.d.ts +2 -2
  45. package/dist/server/server/agent/providers/tool-call-detail-primitives.d.ts.map +1 -1
  46. package/dist/server/server/agent/providers/tool-call-detail-primitives.js +23 -6
  47. package/dist/server/server/agent/providers/tool-call-detail-primitives.js.map +1 -1
  48. package/dist/server/server/agent/providers/tool-call-mapper-utils.d.ts +0 -1
  49. package/dist/server/server/agent/providers/tool-call-mapper-utils.d.ts.map +1 -1
  50. package/dist/server/server/agent/providers/tool-call-mapper-utils.js +0 -10
  51. package/dist/server/server/agent/providers/tool-call-mapper-utils.js.map +1 -1
  52. package/dist/server/server/agent/tts-manager.d.ts.map +1 -1
  53. package/dist/server/server/agent/tts-manager.js +0 -14
  54. package/dist/server/server/agent/tts-manager.js.map +1 -1
  55. package/dist/server/server/bootstrap.d.ts.map +1 -1
  56. package/dist/server/server/bootstrap.js +0 -2
  57. package/dist/server/server/bootstrap.js.map +1 -1
  58. package/dist/server/server/dictation/dictation-stream-manager.d.ts.map +1 -1
  59. package/dist/server/server/dictation/dictation-stream-manager.js +2 -13
  60. package/dist/server/server/dictation/dictation-stream-manager.js.map +1 -1
  61. package/dist/server/server/exports.d.ts +1 -0
  62. package/dist/server/server/exports.d.ts.map +1 -1
  63. package/dist/server/server/exports.js +1 -0
  64. package/dist/server/server/exports.js.map +1 -1
  65. package/dist/server/server/persisted-config.d.ts +32 -18
  66. package/dist/server/server/persisted-config.d.ts.map +1 -1
  67. package/dist/server/server/persisted-config.js +2 -0
  68. package/dist/server/server/persisted-config.js.map +1 -1
  69. package/dist/server/server/relay-transport.d.ts.map +1 -1
  70. package/dist/server/server/relay-transport.js +78 -11
  71. package/dist/server/server/relay-transport.js.map +1 -1
  72. package/dist/server/server/session.d.ts +2 -8
  73. package/dist/server/server/session.d.ts.map +1 -1
  74. package/dist/server/server/session.js +120 -249
  75. package/dist/server/server/session.js.map +1 -1
  76. package/dist/server/server/speech/providers/local/config.d.ts.map +1 -1
  77. package/dist/server/server/speech/providers/local/config.js +11 -8
  78. package/dist/server/server/speech/providers/local/config.js.map +1 -1
  79. package/dist/server/server/speech/providers/local/runtime.d.ts.map +1 -1
  80. package/dist/server/server/speech/providers/local/runtime.js +9 -6
  81. package/dist/server/server/speech/providers/local/runtime.js.map +1 -1
  82. package/dist/server/server/speech/providers/local/sherpa/model-downloader.d.ts.map +1 -1
  83. package/dist/server/server/speech/providers/local/sherpa/model-downloader.js +134 -52
  84. package/dist/server/server/speech/providers/local/sherpa/model-downloader.js.map +1 -1
  85. package/dist/server/server/speech/providers/openai/config.d.ts.map +1 -1
  86. package/dist/server/server/speech/providers/openai/config.js +10 -5
  87. package/dist/server/server/speech/providers/openai/config.js.map +1 -1
  88. package/dist/server/server/speech/providers/openai/runtime.d.ts.map +1 -1
  89. package/dist/server/server/speech/providers/openai/runtime.js +17 -6
  90. package/dist/server/server/speech/providers/openai/runtime.js.map +1 -1
  91. package/dist/server/server/speech/speech-config-resolver.d.ts.map +1 -1
  92. package/dist/server/server/speech/speech-config-resolver.js +25 -4
  93. package/dist/server/server/speech/speech-config-resolver.js.map +1 -1
  94. package/dist/server/server/speech/speech-runtime.d.ts.map +1 -1
  95. package/dist/server/server/speech/speech-runtime.js +44 -15
  96. package/dist/server/server/speech/speech-runtime.js.map +1 -1
  97. package/dist/server/server/speech/speech-types.d.ts +3 -0
  98. package/dist/server/server/speech/speech-types.d.ts.map +1 -1
  99. package/dist/server/server/speech/speech-types.js +1 -0
  100. package/dist/server/server/speech/speech-types.js.map +1 -1
  101. package/dist/server/server/websocket-server.d.ts.map +1 -1
  102. package/dist/server/server/websocket-server.js +2 -40
  103. package/dist/server/server/websocket-server.js.map +1 -1
  104. package/dist/server/shared/messages.d.ts +680 -1328
  105. package/dist/server/shared/messages.d.ts.map +1 -1
  106. package/dist/server/shared/messages.js +41 -32
  107. package/dist/server/shared/messages.js.map +1 -1
  108. package/dist/server/utils/checkout-git.d.ts +5 -1
  109. package/dist/server/utils/checkout-git.d.ts.map +1 -1
  110. package/dist/server/utils/checkout-git.js +222 -75
  111. package/dist/server/utils/checkout-git.js.map +1 -1
  112. package/package.json +2 -2
@@ -6,7 +6,6 @@ import { promisify } from "util";
6
6
  import { join, resolve, sep } from "path";
7
7
  import { z } from "zod";
8
8
  import { serializeAgentStreamEvent, } from "./messages.js";
9
- import { parseAndHighlightDiff } from "./utils/diff-highlighter.js";
10
9
  import { TTSManager } from "./agent/tts-manager.js";
11
10
  import { STTManager } from "./agent/stt-manager.js";
12
11
  import { maybePersistTtsDebugAudio } from "./agent/tts-debug.js";
@@ -261,7 +260,7 @@ export class Session {
261
260
  // Initialize agent MCP client asynchronously
262
261
  void this.initializeAgentMcp();
263
262
  this.subscribeToAgentEvents();
264
- this.sessionLogger.info("Session created");
263
+ this.sessionLogger.trace("Session created");
265
264
  }
266
265
  /**
267
266
  * Get the client's current activity state
@@ -331,11 +330,11 @@ export class Session {
331
330
  /**
332
331
  * Start streaming an agent run and forward results via the websocket broadcast
333
332
  */
334
- startAgentStream(agentId, prompt) {
333
+ startAgentStream(agentId, prompt, runOptions) {
335
334
  this.sessionLogger.info({ agentId }, `Starting agent stream for ${agentId}`);
336
335
  let iterator;
337
336
  try {
338
- iterator = this.agentManager.streamAgent(agentId, prompt);
337
+ iterator = this.agentManager.streamAgent(agentId, prompt, runOptions);
339
338
  }
340
339
  catch (error) {
341
340
  this.handleAgentRunError(agentId, error, "Failed to start agent run");
@@ -379,7 +378,7 @@ export class Session {
379
378
  });
380
379
  this.agentTools = (await this.agentMcpClient.tools());
381
380
  const agentToolCount = Object.keys(this.agentTools ?? {}).length;
382
- this.sessionLogger.info({ agentToolCount }, `Agent MCP initialized with ${agentToolCount} tools`);
381
+ this.sessionLogger.trace({ agentToolCount }, `Agent MCP initialized with ${agentToolCount} tools`);
383
382
  }
384
383
  catch (error) {
385
384
  this.sessionLogger.error({ err: error }, "Failed to initialize Agent MCP");
@@ -534,10 +533,12 @@ export class Session {
534
533
  let snapshot;
535
534
  if (handle) {
536
535
  snapshot = await this.agentManager.resumeAgentFromPersistence(handle, buildConfigOverrides(record), agentId, extractTimestamps(record));
536
+ this.sessionLogger.info({ agentId, provider: record.provider }, "Agent resumed from persistence");
537
537
  }
538
538
  else {
539
539
  const config = buildSessionConfig(record);
540
540
  snapshot = await this.agentManager.createAgent(config, agentId, { labels: record.labels });
541
+ this.sessionLogger.info({ agentId, provider: record.provider }, "Agent created from stored config");
541
542
  }
542
543
  await this.agentManager.hydrateTimelineFromProvider(agentId);
543
544
  return this.agentManager.getAgent(agentId) ?? snapshot;
@@ -685,6 +686,9 @@ export class Session {
685
686
  case "archive_agent_request":
686
687
  await this.handleArchiveAgentRequest(msg.agentId, msg.requestId);
687
688
  break;
689
+ case "update_agent_request":
690
+ await this.handleUpdateAgentRequest(msg.agentId, msg.name, msg.labels, msg.requestId);
691
+ break;
688
692
  case "set_voice_mode":
689
693
  await this.handleSetVoiceMode(msg.enabled, msg.agentId, msg.requestId);
690
694
  break;
@@ -741,9 +745,6 @@ export class Session {
741
745
  case "agent_permission_response":
742
746
  await this.handleAgentPermissionResponse(msg.agentId, msg.requestId, msg.response);
743
747
  break;
744
- case "git_diff_request":
745
- await this.handleGitDiffRequest(msg.agentId, msg.requestId);
746
- break;
747
748
  case "checkout_status_request":
748
749
  await this.handleCheckoutStatusRequest(msg);
749
750
  break;
@@ -780,9 +781,6 @@ export class Session {
780
781
  case "paseo_worktree_archive_request":
781
782
  await this.handlePaseoWorktreeArchiveRequest(msg);
782
783
  break;
783
- case "highlighted_diff_request":
784
- await this.handleHighlightedDiffRequest(msg.agentId, msg.requestId);
785
- break;
786
784
  case "file_explorer_request":
787
785
  await this.handleFileExplorerRequest(msg);
788
786
  break;
@@ -795,6 +793,9 @@ export class Session {
795
793
  case "list_provider_models_request":
796
794
  await this.handleListProviderModelsRequest(msg);
797
795
  break;
796
+ case "list_available_providers_request":
797
+ await this.handleListAvailableProvidersRequest(msg);
798
+ break;
798
799
  case "speech_models_list_request":
799
800
  await this.handleSpeechModelsListRequest(msg);
800
801
  break;
@@ -965,6 +966,72 @@ export class Session {
965
966
  },
966
967
  });
967
968
  }
969
+ async handleUpdateAgentRequest(agentId, name, labels, requestId) {
970
+ this.sessionLogger.info({ agentId, requestId, hasName: typeof name === "string", labelCount: labels ? Object.keys(labels).length : 0 }, "session: update_agent_request");
971
+ const normalizedName = name?.trim();
972
+ const normalizedLabels = labels && Object.keys(labels).length > 0 ? labels : undefined;
973
+ if (!normalizedName && !normalizedLabels) {
974
+ this.emit({
975
+ type: "update_agent_response",
976
+ payload: {
977
+ requestId,
978
+ agentId,
979
+ accepted: false,
980
+ error: "Nothing to update (provide name and/or labels)",
981
+ },
982
+ });
983
+ return;
984
+ }
985
+ try {
986
+ const liveAgent = this.agentManager.getAgent(agentId);
987
+ if (liveAgent) {
988
+ if (normalizedName) {
989
+ await this.agentManager.setTitle(agentId, normalizedName);
990
+ }
991
+ if (normalizedLabels) {
992
+ await this.agentManager.setLabels(agentId, normalizedLabels);
993
+ }
994
+ }
995
+ else {
996
+ const existing = await this.agentStorage.get(agentId);
997
+ if (!existing) {
998
+ throw new Error(`Agent not found: ${agentId}`);
999
+ }
1000
+ await this.agentStorage.upsert({
1001
+ ...existing,
1002
+ ...(normalizedName ? { title: normalizedName } : {}),
1003
+ ...(normalizedLabels
1004
+ ? { labels: { ...existing.labels, ...normalizedLabels } }
1005
+ : {}),
1006
+ });
1007
+ }
1008
+ this.emit({
1009
+ type: "update_agent_response",
1010
+ payload: { requestId, agentId, accepted: true, error: null },
1011
+ });
1012
+ }
1013
+ catch (error) {
1014
+ this.sessionLogger.error({ err: error, agentId, requestId }, "session: update_agent_request error");
1015
+ this.emit({
1016
+ type: "activity_log",
1017
+ payload: {
1018
+ id: uuidv4(),
1019
+ timestamp: new Date(),
1020
+ type: "error",
1021
+ content: `Failed to update agent: ${error.message}`,
1022
+ },
1023
+ });
1024
+ this.emit({
1025
+ type: "update_agent_response",
1026
+ payload: {
1027
+ requestId,
1028
+ agentId,
1029
+ accepted: false,
1030
+ error: error?.message ? String(error.message) : "Failed to update agent",
1031
+ },
1032
+ });
1033
+ }
1034
+ }
968
1035
  /**
969
1036
  * Handle voice mode toggle
970
1037
  */
@@ -1318,7 +1385,7 @@ export class Session {
1318
1385
  /**
1319
1386
  * Handle text message to agent (with optional image attachments)
1320
1387
  */
1321
- async handleSendAgentMessage(agentId, text, messageId, images) {
1388
+ async handleSendAgentMessage(agentId, text, messageId, images, runOptions) {
1322
1389
  this.sessionLogger.info({ agentId, textPreview: text.substring(0, 50), imageCount: images?.length ?? 0 }, `Sending text to agent ${agentId}${images && images.length > 0 ? ` with ${images.length} image attachment(s)` : ''}`);
1323
1390
  try {
1324
1391
  await this.ensureAgentLoaded(agentId);
@@ -1341,13 +1408,12 @@ export class Session {
1341
1408
  catch (error) {
1342
1409
  this.sessionLogger.error({ err: error, agentId }, `Failed to record user message for agent ${agentId}`);
1343
1410
  }
1344
- this.startAgentStream(agentId, prompt);
1411
+ this.startAgentStream(agentId, prompt, runOptions);
1345
1412
  }
1346
1413
  /**
1347
1414
  * Handle on-demand agent initialization request from client
1348
1415
  */
1349
1416
  async handleInitializeAgentRequest(agentId, requestId) {
1350
- this.sessionLogger.info({ agentId }, `Initializing agent ${agentId} on demand`);
1351
1417
  try {
1352
1418
  const snapshot = await this.ensureAgentLoaded(agentId);
1353
1419
  await this.forwardAgentUpdate(snapshot);
@@ -1362,7 +1428,6 @@ export class Session {
1362
1428
  requestId,
1363
1429
  },
1364
1430
  });
1365
- this.sessionLogger.info({ agentId, timelineSize, status: snapshot.lifecycle }, `Agent ${agentId} initialized with ${timelineSize} timeline item(s); status=${snapshot.lifecycle}`);
1366
1431
  }
1367
1432
  catch (error) {
1368
1433
  this.sessionLogger.error({ err: error, agentId }, `Failed to initialize agent ${agentId}`);
@@ -1380,7 +1445,7 @@ export class Session {
1380
1445
  * Handle create agent request
1381
1446
  */
1382
1447
  async handleCreateAgentRequest(msg) {
1383
- const { config, worktreeName, requestId, initialPrompt, git, images, labels } = msg;
1448
+ const { config, worktreeName, requestId, initialPrompt, outputSchema, git, images, labels } = msg;
1384
1449
  this.sessionLogger.info({ cwd: config.cwd, provider: config.provider, worktreeName }, `Creating agent in ${config.cwd} (${config.provider})${worktreeName ? ` with worktree ${worktreeName}` : ""}`);
1385
1450
  try {
1386
1451
  const { sessionConfig, worktreeConfig } = await this.buildAgentSessionConfig(config, git, worktreeName, labels);
@@ -1398,7 +1463,7 @@ export class Session {
1398
1463
  logger: this.sessionLogger,
1399
1464
  });
1400
1465
  try {
1401
- await this.handleSendAgentMessage(snapshot.id, trimmedPrompt, uuidv4(), images);
1466
+ await this.handleSendAgentMessage(snapshot.id, trimmedPrompt, uuidv4(), images, outputSchema ? { outputSchema } : undefined);
1402
1467
  }
1403
1468
  catch (promptError) {
1404
1469
  this.sessionLogger.error({ err: promptError, agentId: snapshot.id }, `Failed to run initial prompt for agent ${snapshot.id}`);
@@ -1752,6 +1817,33 @@ export class Session {
1752
1817
  });
1753
1818
  }
1754
1819
  }
1820
+ async handleListAvailableProvidersRequest(msg) {
1821
+ const fetchedAt = new Date().toISOString();
1822
+ try {
1823
+ const providers = await this.agentManager.listProviderAvailability();
1824
+ this.emit({
1825
+ type: "list_available_providers_response",
1826
+ payload: {
1827
+ providers,
1828
+ error: null,
1829
+ fetchedAt,
1830
+ requestId: msg.requestId,
1831
+ },
1832
+ });
1833
+ }
1834
+ catch (error) {
1835
+ this.sessionLogger.error({ err: error }, "Failed to list provider availability");
1836
+ this.emit({
1837
+ type: "list_available_providers_response",
1838
+ payload: {
1839
+ providers: [],
1840
+ error: error?.message ?? String(error),
1841
+ fetchedAt,
1842
+ requestId: msg.requestId,
1843
+ },
1844
+ });
1845
+ }
1846
+ }
1755
1847
  async handleSpeechModelsListRequest(msg) {
1756
1848
  const modelsDir = this.localSpeechModelsDir;
1757
1849
  const models = await Promise.all(listLocalSpeechModels().map(async (model) => {
@@ -2377,52 +2469,6 @@ export class Session {
2377
2469
  throw error;
2378
2470
  }
2379
2471
  }
2380
- /**
2381
- * Handle git diff request for an agent
2382
- */
2383
- async handleGitDiffRequest(agentId, requestId) {
2384
- this.sessionLogger.debug({ agentId }, `Handling git diff request for agent ${agentId}`);
2385
- try {
2386
- const agents = this.agentManager.listAgents();
2387
- const agent = agents.find((a) => a.id === agentId);
2388
- if (!agent) {
2389
- this.emit({
2390
- type: "git_diff_response",
2391
- payload: {
2392
- agentId,
2393
- diff: "",
2394
- error: `Agent not found: ${agentId}`,
2395
- requestId,
2396
- },
2397
- });
2398
- return;
2399
- }
2400
- const diffResult = await getCheckoutDiff(agent.cwd, { mode: "uncommitted" }, { paseoHome: this.paseoHome });
2401
- const combinedDiff = diffResult.diff;
2402
- this.emit({
2403
- type: "git_diff_response",
2404
- payload: {
2405
- agentId,
2406
- diff: combinedDiff,
2407
- error: null,
2408
- requestId,
2409
- },
2410
- });
2411
- this.sessionLogger.debug({ agentId, diffBytes: combinedDiff.length }, `Git diff for agent ${agentId} completed (${combinedDiff.length} bytes)`);
2412
- }
2413
- catch (error) {
2414
- this.sessionLogger.error({ err: error, agentId }, `Failed to get git diff for agent ${agentId}`);
2415
- this.emit({
2416
- type: "git_diff_response",
2417
- payload: {
2418
- agentId,
2419
- diff: "",
2420
- error: error.message,
2421
- requestId,
2422
- },
2423
- });
2424
- }
2425
- }
2426
2472
  async handleCheckoutStatusRequest(msg) {
2427
2473
  const { cwd, requestId } = msg;
2428
2474
  try {
@@ -3051,12 +3097,13 @@ export class Session {
3051
3097
  async handleCheckoutPrStatusRequest(msg) {
3052
3098
  const { cwd, requestId } = msg;
3053
3099
  try {
3054
- const status = await getPullRequestStatus(cwd);
3100
+ const prStatus = await getPullRequestStatus(cwd);
3055
3101
  this.emit({
3056
3102
  type: "checkout_pr_status_response",
3057
3103
  payload: {
3058
3104
  cwd,
3059
- status,
3105
+ status: prStatus.status,
3106
+ githubFeaturesEnabled: prStatus.githubFeaturesEnabled,
3060
3107
  error: null,
3061
3108
  requestId,
3062
3109
  },
@@ -3068,6 +3115,7 @@ export class Session {
3068
3115
  payload: {
3069
3116
  cwd,
3070
3117
  status: null,
3118
+ githubFeaturesEnabled: true,
3071
3119
  error: this.toCheckoutError(error),
3072
3120
  requestId,
3073
3121
  },
@@ -3223,185 +3271,6 @@ export class Session {
3223
3271
  });
3224
3272
  }
3225
3273
  }
3226
- /**
3227
- * Handle highlighted diff request - returns parsed and syntax-highlighted diff
3228
- */
3229
- async handleHighlightedDiffRequest(agentId, requestId) {
3230
- this.sessionLogger.debug({ agentId }, `Handling highlighted diff request for agent ${agentId}`);
3231
- // Maximum lines changed before we skip showing the diff content
3232
- const MAX_DIFF_LINES = 5000;
3233
- try {
3234
- const agents = this.agentManager.listAgents();
3235
- const agent = agents.find((a) => a.id === agentId);
3236
- if (!agent) {
3237
- this.emit({
3238
- type: "highlighted_diff_response",
3239
- payload: {
3240
- agentId,
3241
- files: [],
3242
- error: `Agent not found: ${agentId}`,
3243
- requestId,
3244
- },
3245
- });
3246
- return;
3247
- }
3248
- // Step 1: Get the list of changed files with their stats (numstat gives additions/deletions per file)
3249
- const { stdout: numstatOutput } = await execAsync("git diff --numstat HEAD", { cwd: agent.cwd });
3250
- // Get file statuses (A=added, D=deleted, M=modified) to detect deleted files
3251
- const { stdout: nameStatusOutput } = await execAsync("git diff --name-status HEAD", { cwd: agent.cwd });
3252
- const deletedFiles = new Set();
3253
- const addedFiles = new Set();
3254
- for (const line of nameStatusOutput.trim().split("\n").filter(Boolean)) {
3255
- const [status, ...pathParts] = line.split("\t");
3256
- const path = pathParts.join("\t");
3257
- if (status === "D") {
3258
- deletedFiles.add(path);
3259
- }
3260
- else if (status === "A") {
3261
- addedFiles.add(path);
3262
- }
3263
- }
3264
- const fileStats = [];
3265
- for (const line of numstatOutput.trim().split("\n").filter(Boolean)) {
3266
- const parts = line.split("\t");
3267
- if (parts.length >= 3) {
3268
- const [addStr, delStr, ...pathParts] = parts;
3269
- const path = pathParts.join("\t"); // Handle paths with tabs
3270
- const isBinary = addStr === "-" && delStr === "-";
3271
- fileStats.push({
3272
- path,
3273
- additions: isBinary ? 0 : parseInt(addStr, 10),
3274
- deletions: isBinary ? 0 : parseInt(delStr, 10),
3275
- isBinary,
3276
- isTracked: true,
3277
- isDeleted: deletedFiles.has(path),
3278
- isNew: addedFiles.has(path),
3279
- });
3280
- }
3281
- }
3282
- // Step 2: Get untracked files
3283
- try {
3284
- const { stdout: untrackedFiles } = await execAsync("git ls-files --others --exclude-standard", { cwd: agent.cwd });
3285
- for (const filePath of untrackedFiles.trim().split("\n").filter(Boolean)) {
3286
- // Use git's numstat with --no-index to detect binary files (cross-platform)
3287
- // Binary files show as "-\t-\tfilepath", text files show line counts
3288
- try {
3289
- const { stdout: numstatLine } = await execAsync(`git diff --numstat --no-index /dev/null "${filePath}" || true`, { cwd: agent.cwd });
3290
- const parts = numstatLine.trim().split("\t");
3291
- const isBinary = parts[0] === "-" && parts[1] === "-";
3292
- const additions = isBinary ? 0 : (parseInt(parts[0], 10) || 0);
3293
- fileStats.push({
3294
- path: filePath,
3295
- additions,
3296
- deletions: 0,
3297
- isBinary,
3298
- isTracked: false,
3299
- isDeleted: false,
3300
- isNew: true,
3301
- });
3302
- }
3303
- catch {
3304
- // If we can't determine, assume text and try to get it
3305
- fileStats.push({
3306
- path: filePath,
3307
- additions: 0,
3308
- deletions: 0,
3309
- isBinary: false,
3310
- isTracked: false,
3311
- isDeleted: false,
3312
- isNew: true,
3313
- });
3314
- }
3315
- }
3316
- }
3317
- catch {
3318
- // Ignore errors getting untracked files
3319
- }
3320
- // Step 3: Fetch diffs per-file, respecting limits
3321
- const allFiles = [];
3322
- for (const stats of fileStats) {
3323
- const totalLines = stats.additions + stats.deletions;
3324
- // Handle binary files
3325
- if (stats.isBinary) {
3326
- allFiles.push({
3327
- path: stats.path,
3328
- isNew: stats.isNew,
3329
- isDeleted: stats.isDeleted,
3330
- additions: 0,
3331
- deletions: 0,
3332
- hunks: [],
3333
- status: "binary",
3334
- });
3335
- continue;
3336
- }
3337
- // Handle files that are too large
3338
- if (totalLines > MAX_DIFF_LINES) {
3339
- allFiles.push({
3340
- path: stats.path,
3341
- isNew: stats.isNew,
3342
- isDeleted: stats.isDeleted,
3343
- additions: stats.additions,
3344
- deletions: stats.deletions,
3345
- hunks: [],
3346
- status: "too_large",
3347
- });
3348
- continue;
3349
- }
3350
- // Fetch the actual diff for this file
3351
- try {
3352
- let fileDiff;
3353
- if (stats.isTracked) {
3354
- const { stdout } = await execAsync(`git diff HEAD -- "${stats.path}"`, { cwd: agent.cwd });
3355
- fileDiff = stdout;
3356
- }
3357
- else {
3358
- const { stdout } = await execAsync(`git diff --no-index /dev/null "${stats.path}" || true`, { cwd: agent.cwd });
3359
- fileDiff = stdout;
3360
- }
3361
- if (fileDiff) {
3362
- const parsedFiles = await parseAndHighlightDiff(fileDiff, agent.cwd);
3363
- for (const file of parsedFiles) {
3364
- allFiles.push({ ...file, status: "ok" });
3365
- }
3366
- }
3367
- }
3368
- catch {
3369
- // If diff fails for this file, add it with empty hunks
3370
- allFiles.push({
3371
- path: stats.path,
3372
- isNew: stats.isNew,
3373
- isDeleted: stats.isDeleted,
3374
- additions: stats.additions,
3375
- deletions: stats.deletions,
3376
- hunks: [],
3377
- status: "ok",
3378
- });
3379
- }
3380
- }
3381
- this.emit({
3382
- type: "highlighted_diff_response",
3383
- payload: {
3384
- agentId,
3385
- files: allFiles,
3386
- error: null,
3387
- requestId,
3388
- },
3389
- });
3390
- this.sessionLogger.debug({ agentId, fileCount: allFiles.length }, `Highlighted diff for agent ${agentId} completed (${allFiles.length} files)`);
3391
- }
3392
- catch (error) {
3393
- this.sessionLogger.error({ err: error, agentId }, `Failed to get highlighted diff for agent ${agentId}`);
3394
- this.emit({
3395
- type: "highlighted_diff_response",
3396
- payload: {
3397
- agentId,
3398
- files: [],
3399
- error: error.message,
3400
- requestId,
3401
- },
3402
- });
3403
- }
3404
- }
3405
3274
  /**
3406
3275
  * Handle read-only file explorer requests scoped to an agent's cwd
3407
3276
  */
@@ -3825,7 +3694,7 @@ export class Session {
3825
3694
  if (!resolved.ok) {
3826
3695
  this.emit({
3827
3696
  type: "wait_for_finish_response",
3828
- payload: { requestId, status: "error", final: null, error: resolved.error },
3697
+ payload: { requestId, status: "error", final: null, error: resolved.error, lastMessage: null },
3829
3698
  });
3830
3699
  return;
3831
3700
  }
@@ -3841,6 +3710,7 @@ export class Session {
3841
3710
  status: "error",
3842
3711
  final: null,
3843
3712
  error: `Agent not found: ${agentId}`,
3713
+ lastMessage: null,
3844
3714
  },
3845
3715
  });
3846
3716
  return;
@@ -3853,7 +3723,7 @@ export class Session {
3853
3723
  : "idle";
3854
3724
  this.emit({
3855
3725
  type: "wait_for_finish_response",
3856
- payload: { requestId, status, final, error: null },
3726
+ payload: { requestId, status, final, error: null, lastMessage: null },
3857
3727
  });
3858
3728
  return;
3859
3729
  }
@@ -3877,7 +3747,7 @@ export class Session {
3877
3747
  : "idle";
3878
3748
  this.emit({
3879
3749
  type: "wait_for_finish_response",
3880
- payload: { requestId, status, final, error: null },
3750
+ payload: { requestId, status, final, error: null, lastMessage: result.lastMessage },
3881
3751
  });
3882
3752
  }
3883
3753
  catch (error) {
@@ -3894,6 +3764,7 @@ export class Session {
3894
3764
  status: "error",
3895
3765
  final,
3896
3766
  error: message,
3767
+ lastMessage: null,
3897
3768
  },
3898
3769
  });
3899
3770
  return;
@@ -3904,7 +3775,7 @@ export class Session {
3904
3775
  }
3905
3776
  this.emit({
3906
3777
  type: "wait_for_finish_response",
3907
- payload: { requestId, status: "timeout", final, error: null },
3778
+ payload: { requestId, status: "timeout", final, error: null, lastMessage: null },
3908
3779
  });
3909
3780
  }
3910
3781
  finally {
@@ -4390,7 +4261,7 @@ export class Session {
4390
4261
  * Clean up session resources
4391
4262
  */
4392
4263
  async cleanup() {
4393
- this.sessionLogger.info("Cleaning up");
4264
+ this.sessionLogger.trace("Cleaning up");
4394
4265
  if (this.unsubscribeAgentEvents) {
4395
4266
  this.unsubscribeAgentEvents();
4396
4267
  this.unsubscribeAgentEvents = null;