@getpaseo/server 0.1.101 → 0.1.102

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 (141) hide show
  1. package/.env.example +8 -0
  2. package/dist/scripts/supervisor.js +26 -8
  3. package/dist/server/server/agent/activity-curator.d.ts +17 -0
  4. package/dist/server/server/agent/activity-curator.js +101 -24
  5. package/dist/server/server/agent/agent-manager.js +5 -1
  6. package/dist/server/server/agent/agent-sdk-types.d.ts +7 -2
  7. package/dist/server/server/agent/provider-registry.js +12 -11
  8. package/dist/server/server/agent/provider-snapshot-manager.d.ts +8 -1
  9. package/dist/server/server/agent/provider-snapshot-manager.js +78 -33
  10. package/dist/server/server/agent/providers/acp-agent.d.ts +13 -0
  11. package/dist/server/server/agent/providers/acp-agent.js +37 -1
  12. package/dist/server/server/agent/providers/claude/agent.js +51 -14
  13. package/dist/server/server/agent/providers/claude/query.d.ts +3 -0
  14. package/dist/server/server/agent/providers/claude/query.js +4 -2
  15. package/dist/server/server/agent/providers/generic-acp-agent.d.ts +2 -1
  16. package/dist/server/server/agent/providers/generic-acp-agent.js +2 -1
  17. package/dist/server/server/agent/providers/kiro-acp-agent.d.ts +17 -0
  18. package/dist/server/server/agent/providers/kiro-acp-agent.js +78 -0
  19. package/dist/server/server/agent/providers/mock-load-test-agent.js +8 -0
  20. package/dist/server/server/agent/providers/opencode/paths.d.ts +2 -0
  21. package/dist/server/server/agent/providers/opencode/paths.js +7 -0
  22. package/dist/server/server/agent/providers/opencode/server-manager.d.ts +2 -0
  23. package/dist/server/server/agent/providers/opencode/server-manager.js +34 -5
  24. package/dist/server/server/agent/providers/opencode-agent.d.ts +4 -0
  25. package/dist/server/server/agent/providers/opencode-agent.js +14 -2
  26. package/dist/server/server/agent/providers/pi/agent.d.ts +3 -0
  27. package/dist/server/server/agent/providers/pi/agent.js +9 -3
  28. package/dist/server/server/agent/providers/provider-image-output.js +11 -6
  29. package/dist/server/server/agent/tools/paseo-tools.d.ts +1 -1
  30. package/dist/server/server/agent/tools/paseo-tools.js +0 -2
  31. package/dist/server/server/bootstrap.d.ts +7 -1
  32. package/dist/server/server/bootstrap.js +18 -0
  33. package/dist/server/server/config.d.ts +2 -0
  34. package/dist/server/server/config.js +57 -1
  35. package/dist/server/server/daemon-worker.js +19 -7
  36. package/dist/server/server/lifecycle-reasons.d.ts +4 -0
  37. package/dist/server/server/lifecycle-reasons.js +6 -0
  38. package/dist/server/server/persisted-config.d.ts +13 -2
  39. package/dist/server/server/persisted-config.js +30 -11
  40. package/dist/server/server/process-diagnostics.d.ts +17 -0
  41. package/dist/server/server/process-diagnostics.js +22 -0
  42. package/dist/server/server/relay-transport.js +1 -0
  43. package/dist/server/server/resolve-worktree-creation-intent.js +3 -1
  44. package/dist/server/server/session/daemon/daemon-self-update-session-controller.d.ts +32 -0
  45. package/dist/server/server/session/daemon/daemon-self-update-session-controller.js +88 -0
  46. package/dist/server/server/session/daemon/daemon-self-updater.d.ts +32 -0
  47. package/dist/server/server/session/daemon/daemon-self-updater.js +56 -0
  48. package/dist/server/server/session/daemon/daemon-session.d.ts +12 -0
  49. package/dist/server/server/session/daemon/daemon-session.js +12 -0
  50. package/dist/server/server/session/daemon/diagnostics.js +10 -0
  51. package/dist/server/server/session/daemon/install-origin.d.ts +7 -0
  52. package/dist/server/server/session/daemon/install-origin.js +64 -0
  53. package/dist/server/server/session/daemon/npm-global-cli.d.ts +29 -0
  54. package/dist/server/server/session/daemon/npm-global-cli.js +98 -0
  55. package/dist/server/server/session/provider/provider-catalog-session.js +8 -4
  56. package/dist/server/server/session.d.ts +5 -3
  57. package/dist/server/server/session.js +74 -32
  58. package/dist/server/server/speech/providers/openai/config.d.ts +0 -2
  59. package/dist/server/server/speech/providers/openai/config.js +71 -34
  60. package/dist/server/server/speech/providers/openai/runtime.js +4 -4
  61. package/dist/server/server/web-ui.d.ts +10 -0
  62. package/dist/server/server/web-ui.js +205 -0
  63. package/dist/server/server/websocket/runtime-metrics.d.ts +3 -0
  64. package/dist/server/server/websocket-server.d.ts +3 -0
  65. package/dist/server/server/websocket-server.js +190 -32
  66. package/dist/server/services/quota-fetcher/manifest.js +5 -0
  67. package/dist/server/services/quota-fetcher/providers/minimax.d.ts +29 -0
  68. package/dist/server/services/quota-fetcher/providers/minimax.js +227 -0
  69. package/dist/server/terminal/agent-hooks/agent-hook-installer.js +2 -2
  70. package/dist/server/utils/checkout-git.js +156 -3
  71. package/dist/server/utils/directory-suggestions.js +1 -4
  72. package/dist/server/utils/path.d.ts +2 -0
  73. package/dist/server/utils/path.js +13 -0
  74. package/dist/server/utils/worktree.d.ts +1 -0
  75. package/dist/server/utils/worktree.js +92 -11
  76. package/dist/server/web-ui/_expo/static/css/xterm-3bb1704bf6cb0876640973dc0244b4cb.css +1 -0
  77. package/dist/server/web-ui/_expo/static/css/xterm-3bb1704bf6cb0876640973dc0244b4cb.css.br +0 -0
  78. package/dist/server/web-ui/_expo/static/css/xterm-3bb1704bf6cb0876640973dc0244b4cb.css.gz +0 -0
  79. package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-bridge-5fa9347afb28c7d61bbf73684f3bdafa.js +1 -0
  80. package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-bridge-5fa9347afb28c7d61bbf73684f3bdafa.js.br +0 -0
  81. package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-bridge-5fa9347afb28c7d61bbf73684f3bdafa.js.gz +0 -0
  82. package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-store-35159ed50cf218b741c5dab5a6f4a889.js +1 -0
  83. package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-store-35159ed50cf218b741c5dab5a6f4a889.js.br +0 -0
  84. package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-store-35159ed50cf218b741c5dab5a6f4a889.js.gz +0 -0
  85. package/dist/server/web-ui/_expo/static/js/web/index-cf63ba3f4de4164d2d9c3738e1b791b2.js +16157 -0
  86. package/dist/server/web-ui/_expo/static/js/web/index-cf63ba3f4de4164d2d9c3738e1b791b2.js.br +0 -0
  87. package/dist/server/web-ui/_expo/static/js/web/index-cf63ba3f4de4164d2d9c3738e1b791b2.js.gz +0 -0
  88. package/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-6a6cfdd9f06d4fd2b05adc1a8771c58c.js +1 -0
  89. package/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-6a6cfdd9f06d4fd2b05adc1a8771c58c.js.br +0 -0
  90. package/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-6a6cfdd9f06d4fd2b05adc1a8771c58c.js.gz +0 -0
  91. package/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-b31888f9a7de4f0c52d6a58fcddbcb20.js +3 -0
  92. package/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-b31888f9a7de4f0c52d6a58fcddbcb20.js.br +0 -0
  93. package/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-b31888f9a7de4f0c52d6a58fcddbcb20.js.gz +0 -0
  94. package/dist/server/web-ui/apple-touch-icon.png +0 -0
  95. package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/back-icon-mask.0a328cd9c1afd0afe8e3b1ec5165b1b4.png +0 -0
  96. package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/back-icon.35ba0eaec5a4f5ed12ca16fabeae451d.png +0 -0
  97. package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/clear-icon.c94f6478e7ae0cdd9f15de1fcb9e5e55.png +0 -0
  98. package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/clear-icon.c94f6478e7ae0cdd9f15de1fcb9e5e55@2x.png +0 -0
  99. package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/clear-icon.c94f6478e7ae0cdd9f15de1fcb9e5e55@3x.png +0 -0
  100. package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/clear-icon.c94f6478e7ae0cdd9f15de1fcb9e5e55@4x.png +0 -0
  101. package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/close-icon.808e1b1b9b53114ec2838071a7e6daa7.png +0 -0
  102. package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/close-icon.808e1b1b9b53114ec2838071a7e6daa7@2x.png +0 -0
  103. package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/close-icon.808e1b1b9b53114ec2838071a7e6daa7@3x.png +0 -0
  104. package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/close-icon.808e1b1b9b53114ec2838071a7e6daa7@4x.png +0 -0
  105. package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/search-icon.286d67d3f74808a60a78d3ebf1a5fb57.png +0 -0
  106. package/dist/server/web-ui/assets/__node_modules/expo-router/assets/arrow_down.017bc6ba3fc25503e5eb5e53826d48a8.png +0 -0
  107. package/dist/server/web-ui/assets/__node_modules/expo-router/assets/error.d1ea1496f9057eb392d5bbf3732a61b7.png +0 -0
  108. package/dist/server/web-ui/assets/__node_modules/expo-router/assets/file.19eeb73b9593a38f8e9f418337fc7d10.png +0 -0
  109. package/dist/server/web-ui/assets/__node_modules/expo-router/assets/forward.d8b800c443b8972542883e0b9de2bdc6.png +0 -0
  110. package/dist/server/web-ui/assets/__node_modules/expo-router/assets/pkg.ab19f4cbc543357183a20571f68380a3.png +0 -0
  111. package/dist/server/web-ui/assets/__node_modules/expo-router/assets/sitemap.412dd9275b6b48ad28f5e3d81bb1f626.png +0 -0
  112. package/dist/server/web-ui/assets/__node_modules/expo-router/assets/unmatched.20e71bdf79e3a97bf55fd9e164041578.png +0 -0
  113. package/dist/server/web-ui/assets/assets/images/editor-apps/antigravity.6e91a685c33435e0b466a56db86cf141.png +0 -0
  114. package/dist/server/web-ui/assets/assets/images/editor-apps/cursor.c31d6bce4fe9aadc3fe59962f4c4fcf3.png +0 -0
  115. package/dist/server/web-ui/assets/assets/images/editor-apps/file-explorer.3e15e8f72c825c85ce336bcb0cdef776.png +0 -0
  116. package/dist/server/web-ui/assets/assets/images/editor-apps/finder.7f68fc2c475621a672e1be09309d5567.png +0 -0
  117. package/dist/server/web-ui/assets/assets/images/editor-apps/vscode.832bdb4c685d930f1c864c793703600b.png +0 -0
  118. package/dist/server/web-ui/assets/assets/images/editor-apps/webstorm.aa5dc2cd8c20cc0a155c4c5c5ab3c5f5.png +0 -0
  119. package/dist/server/web-ui/assets/assets/images/editor-apps/zed.f3a670b7f9aa226da4fe53fb86f1abbd.png +0 -0
  120. package/dist/server/web-ui/assets/assets/images/favicon-dark-attention.882b3a27dcb2073e9e31b334f9ed9728.png +0 -0
  121. package/dist/server/web-ui/assets/assets/images/favicon-dark-running.8112342ff0d39e047a7f8d4fad9402f3.png +0 -0
  122. package/dist/server/web-ui/assets/assets/images/favicon-dark.8005ed36ac07a5a7c60de25780897bd4.png +0 -0
  123. package/dist/server/web-ui/assets/assets/images/favicon-light-attention.882b3a27dcb2073e9e31b334f9ed9728.png +0 -0
  124. package/dist/server/web-ui/assets/assets/images/favicon-light-running.8112342ff0d39e047a7f8d4fad9402f3.png +0 -0
  125. package/dist/server/web-ui/assets/assets/images/favicon-light.8005ed36ac07a5a7c60de25780897bd4.png +0 -0
  126. package/dist/server/web-ui/assets/assets/images/notification-icon.3bf81d33ddbf380606bdd248ba83e158.png +0 -0
  127. package/dist/server/web-ui/favicon.ico +0 -0
  128. package/dist/server/web-ui/index.html +90 -0
  129. package/dist/server/web-ui/index.html.br +0 -0
  130. package/dist/server/web-ui/index.html.gz +0 -0
  131. package/dist/server/web-ui/manifest.json +27 -0
  132. package/dist/server/web-ui/manifest.json.br +0 -0
  133. package/dist/server/web-ui/manifest.json.gz +0 -0
  134. package/dist/server/web-ui/metadata.json +1 -0
  135. package/dist/server/web-ui/metadata.json.br +1 -0
  136. package/dist/server/web-ui/metadata.json.gz +0 -0
  137. package/dist/server/web-ui/pwa-icon-192.png +0 -0
  138. package/dist/server/web-ui/pwa-icon-512.png +0 -0
  139. package/dist/server/web-ui/robots.txt +2 -0
  140. package/dist/src/server/persisted-config.js +30 -11
  141. package/package.json +7 -7
@@ -1,6 +1,7 @@
1
1
  import { WebSocketServer } from "ws";
2
2
  import { basename, join } from "path";
3
3
  import { hostname as getHostname } from "node:os";
4
+ import { randomUUID } from "node:crypto";
4
5
  import { monitorEventLoopDelay } from "node:perf_hooks";
5
6
  import { WSInboundMessageSchema, wrapSessionMessage, } from "./messages.js";
6
7
  import { asUint8Array, decodeBinaryFrame } from "@getpaseo/protocol/binary-frames/index";
@@ -15,6 +16,8 @@ import { createGitHubService } from "../services/github-service.js";
15
16
  import { extractWsBearerProtocol, extractWsBearerToken, isBearerTokenValid, } from "./auth.js";
16
17
  import { WebSocketRuntimeMetricsWindow, } from "./websocket/runtime-metrics.js";
17
18
  import { ProviderUsageService } from "../services/quota-fetcher/service.js";
19
+ import { getProcessMemoryDiagnostics, getProcessUptimeSeconds } from "./process-diagnostics.js";
20
+ import { CLIENT_SHUTDOWN_RPC_REASON, normalizeClientRestartRpcReason, } from "./lifecycle-reasons.js";
18
21
  const WS_CLOSE_DAEMON_AUTH_FAILED = 4401;
19
22
  function resolveTerminalAttentionReason(input) {
20
23
  if (input.attentionReason === "finished")
@@ -222,6 +225,7 @@ export class VoiceAssistantWebSocketServer {
222
225
  constructor(server, logger, serverId, agentManager, agentStorage, downloadTokenStore, paseoHome, daemonConfigStore, mcpBaseUrl, wsConfig, auth, speech, terminalManager, dictation, daemonVersion, onLifecycleIntent, projectRegistry, workspaceRegistry, chatService, loopService, scheduleService, checkoutDiffManager, serviceProxy, scriptRuntimeStore, onBranchChanged, getDaemonTcpPort, getDaemonTcpHost, resolveScriptHealth, workspaceGitService, github, pushNotificationSender, providerSnapshotManager, daemonRuntimeConfig, serviceProxyPublicBaseUrl) {
223
226
  this.pendingConnections = new Map();
224
227
  this.sessions = new Map();
228
+ this.socketIdentities = new Map();
225
229
  this.externalSessionsByKey = new Map();
226
230
  this.voiceSpeakHandlers = new Map();
227
231
  this.voiceCallerContexts = new Map();
@@ -391,9 +395,7 @@ export class VoiceAssistantWebSocketServer {
391
395
  callback(false, 403, "Host not allowed");
392
396
  return;
393
397
  }
394
- const sameOrigin = !!origin &&
395
- !!requestHost &&
396
- (origin === `http://${requestHost}` || origin === `https://${requestHost}`);
398
+ const sameOrigin = isWebSocketSameOrigin(origin, requestHost);
397
399
  if (!origin || allowedOrigins.has("*") || allowedOrigins.has(origin) || sameOrigin) {
398
400
  callback(true);
399
401
  }
@@ -508,6 +510,7 @@ export class VoiceAssistantWebSocketServer {
508
510
  this.workspaceGitService.dispose();
509
511
  this.pendingConnections.clear();
510
512
  this.sessions.clear();
513
+ this.socketIdentities.clear();
511
514
  this.externalSessionsByKey.clear();
512
515
  this.wss.close();
513
516
  }
@@ -550,25 +553,13 @@ export class VoiceAssistantWebSocketServer {
550
553
  }
551
554
  async attachSocket(ws, request, metadata) {
552
555
  const requestMetadata = extractSocketRequestMetadata(request);
553
- const connectionLoggerFields = {
554
- transport: metadata?.transport === "relay" ? "relay" : "direct",
555
- };
556
- if (requestMetadata.host) {
557
- connectionLoggerFields.host = requestMetadata.host;
558
- }
559
- if (requestMetadata.origin) {
560
- connectionLoggerFields.origin = requestMetadata.origin;
561
- }
562
- if (requestMetadata.userAgent) {
563
- connectionLoggerFields.userAgent = requestMetadata.userAgent;
564
- }
565
- if (requestMetadata.remoteAddress) {
566
- connectionLoggerFields.remoteAddress = requestMetadata.remoteAddress;
567
- }
568
- const connectionLogger = this.logger.child(connectionLoggerFields);
556
+ const identity = createWebSocketConnectionIdentity(requestMetadata, metadata);
557
+ this.socketIdentities.set(ws, identity);
558
+ const connectionLogger = this.logger.child(toConnectionLogFields(identity));
569
559
  const pending = {
570
560
  connectionLogger,
571
561
  helloTimeout: null,
562
+ identity,
572
563
  };
573
564
  const timeout = setTimeout(() => {
574
565
  if (this.pendingConnections.get(ws) !== pending) {
@@ -576,7 +567,7 @@ export class VoiceAssistantWebSocketServer {
576
567
  }
577
568
  pending.helloTimeout = null;
578
569
  this.pendingConnections.delete(ws);
579
- pending.connectionLogger.warn({ timeoutMs: HELLO_TIMEOUT_MS }, "Closing connection due to missing hello");
570
+ pending.connectionLogger.warn({ ...toConnectionLogFields(identity), timeoutMs: HELLO_TIMEOUT_MS }, "Closing connection due to missing hello");
580
571
  try {
581
572
  ws.close(WS_CLOSE_HELLO_TIMEOUT, "Hello timeout");
582
573
  }
@@ -589,7 +580,8 @@ export class VoiceAssistantWebSocketServer {
589
580
  this.pendingConnections.set(ws, pending);
590
581
  this.incrementRuntimeCounter("connectedAwaitingHello");
591
582
  this.bindSocketHandlers(ws);
592
- pending.connectionLogger.trace({
583
+ pending.connectionLogger.info({
584
+ ...toConnectionLogFields(identity),
593
585
  totalPendingConnections: this.pendingConnections.size,
594
586
  }, "Client connected; awaiting hello");
595
587
  }
@@ -744,6 +736,10 @@ export class VoiceAssistantWebSocketServer {
744
736
  return;
745
737
  }
746
738
  this.clearPendingConnection(ws);
739
+ pending.identity.clientId = clientId;
740
+ if (message.appVersion) {
741
+ pending.identity.appVersion = message.appVersion;
742
+ }
747
743
  const existing = this.externalSessionsByKey.get(clientId);
748
744
  if (existing) {
749
745
  this.incrementRuntimeCounter("helloResumed");
@@ -764,9 +760,10 @@ export class VoiceAssistantWebSocketServer {
764
760
  }
765
761
  existing.sockets.add(ws);
766
762
  this.sessions.set(ws, existing);
763
+ pending.identity.sessionId = existing.session.getSessionId();
767
764
  this.sendToClient(ws, this.createServerInfoMessage());
768
- existing.connectionLogger.trace({
769
- clientId,
765
+ pending.connectionLogger.info({
766
+ ...toConnectionLogFields(pending.identity),
770
767
  resumed: true,
771
768
  totalSessions: this.sessions.size,
772
769
  }, "Client connected via hello");
@@ -783,9 +780,10 @@ export class VoiceAssistantWebSocketServer {
783
780
  });
784
781
  this.sessions.set(ws, connection);
785
782
  this.externalSessionsByKey.set(clientId, connection);
783
+ pending.identity.sessionId = connection.session.getSessionId();
786
784
  this.sendToClient(ws, this.createServerInfoMessage());
787
- connection.connectionLogger.trace({
788
- clientId,
785
+ connection.connectionLogger.info({
786
+ ...toConnectionLogFields(pending.identity),
789
787
  resumed: false,
790
788
  totalSessions: this.sessions.size,
791
789
  }, "Client connected via hello");
@@ -825,6 +823,10 @@ export class VoiceAssistantWebSocketServer {
825
823
  agentDetach: true,
826
824
  // COMPAT(daemonDiagnostics): added in v0.1.100, remove gate after 2026-12-25 once daemon floor >= v0.1.100.
827
825
  daemonDiagnostics: true,
826
+ // COMPAT(daemonSelfUpdate): added in v0.1.93, remove gate after 2026-12-13.
827
+ daemonSelfUpdate: true,
828
+ // COMPAT(agentForkContext): added in v0.1.102, remove gate after 2026-12-28.
829
+ agentForkContext: true,
828
830
  },
829
831
  };
830
832
  }
@@ -882,21 +884,34 @@ export class VoiceAssistantWebSocketServer {
882
884
  return this.voiceCallerContexts.get(callerAgentId) ?? null;
883
885
  }
884
886
  async detachSocket(ws, details) {
887
+ const identity = this.socketIdentities.get(ws);
888
+ const identityFields = identity ? toConnectionLogFields(identity) : {};
885
889
  const pending = this.clearPendingConnection(ws);
886
890
  if (pending) {
887
891
  this.incrementRuntimeCounter("pendingDisconnected");
888
- pending.connectionLogger.trace({
892
+ pending.connectionLogger.info({
893
+ ...identityFields,
889
894
  code: details.code,
890
895
  reason: stringifyCloseReason(details.reason),
891
896
  }, "Pending client disconnected");
897
+ this.socketIdentities.delete(ws);
892
898
  return;
893
899
  }
894
900
  const connection = this.sessions.get(ws);
895
901
  if (!connection) {
902
+ if (identity) {
903
+ this.logger.info({
904
+ ...identityFields,
905
+ code: details.code,
906
+ reason: stringifyCloseReason(details.reason),
907
+ }, "Client socket closed without active session");
908
+ this.socketIdentities.delete(ws);
909
+ }
896
910
  return;
897
911
  }
898
912
  this.sessions.delete(ws);
899
913
  connection.sockets.delete(ws);
914
+ this.socketIdentities.delete(ws);
900
915
  if (connection.sockets.size === 0) {
901
916
  this.incrementRuntimeCounter("sessionDisconnectedWaitingReconnect");
902
917
  if (connection.externalDisconnectCleanupTimeout) {
@@ -910,8 +925,8 @@ export class VoiceAssistantWebSocketServer {
910
925
  void this.cleanupConnection(connection, "Client disconnected (grace timeout)");
911
926
  }, EXTERNAL_SESSION_DISCONNECT_GRACE_MS);
912
927
  connection.externalDisconnectCleanupTimeout = timeout;
913
- connection.connectionLogger.trace({
914
- clientId: connection.clientId,
928
+ connection.connectionLogger.info({
929
+ ...identityFields,
915
930
  code: details.code,
916
931
  reason: stringifyCloseReason(details.reason),
917
932
  reconnectGraceMs: EXTERNAL_SESSION_DISCONNECT_GRACE_MS,
@@ -920,8 +935,8 @@ export class VoiceAssistantWebSocketServer {
920
935
  }
921
936
  if (connection.sockets.size > 0) {
922
937
  this.incrementRuntimeCounter("sessionSocketDisconnectedAttached");
923
- connection.connectionLogger.trace({
924
- clientId: connection.clientId,
938
+ connection.connectionLogger.info({
939
+ ...identityFields,
925
940
  remainingSockets: connection.sockets.size,
926
941
  code: details.code,
927
942
  reason: stringifyCloseReason(details.reason),
@@ -938,6 +953,7 @@ export class VoiceAssistantWebSocketServer {
938
953
  }
939
954
  for (const socket of connection.sockets) {
940
955
  this.sessions.delete(socket);
956
+ this.socketIdentities.delete(socket);
941
957
  }
942
958
  connection.sockets.clear();
943
959
  const existing = this.externalSessionsByKey.get(connection.clientId);
@@ -1112,7 +1128,7 @@ export class VoiceAssistantWebSocketServer {
1112
1128
  return;
1113
1129
  }
1114
1130
  if (message.type === "session") {
1115
- void this.dispatchSessionMessage(activeConnection, message).catch((error) => {
1131
+ void this.dispatchSessionMessage(ws, activeConnection, message).catch((error) => {
1116
1132
  this.handleRawMessageError({ ws, data, error, log: activeConnection.connectionLogger });
1117
1133
  });
1118
1134
  }
@@ -1121,8 +1137,16 @@ export class VoiceAssistantWebSocketServer {
1121
1137
  this.handleRawMessageError({ ws, data, error, log });
1122
1138
  }
1123
1139
  }
1124
- async dispatchSessionMessage(activeConnection, message) {
1140
+ async dispatchSessionMessage(ws, activeConnection, message) {
1125
1141
  this.recordInboundSessionRequestType(message.message.type);
1142
+ const controlRpc = getControlRpcLogInfo(message.message);
1143
+ if (controlRpc) {
1144
+ const identity = this.socketIdentities.get(ws);
1145
+ activeConnection.connectionLogger.warn({
1146
+ ...(identity ? toConnectionLogFields(identity) : { clientId: activeConnection.clientId }),
1147
+ ...controlRpc,
1148
+ }, "ws_control_rpc_received");
1149
+ }
1126
1150
  const startMs = performance.now();
1127
1151
  await activeConnection.session.handleMessage(message.message);
1128
1152
  const durationMs = performance.now() - startMs;
@@ -1258,6 +1282,8 @@ export class VoiceAssistantWebSocketServer {
1258
1282
  outboundBinaryFrameTypesTop: runtimeMetrics.outboundBinaryFrameTypesTop,
1259
1283
  bufferedAmount: runtimeMetrics.bufferedAmount,
1260
1284
  eventLoopDelay: this.snapshotEventLoopDelay(),
1285
+ uptimeSeconds: getProcessUptimeSeconds(),
1286
+ memory: getProcessMemoryDiagnostics(),
1261
1287
  runtime: sessionMetrics,
1262
1288
  latency: runtimeMetrics.latency,
1263
1289
  agents: agentSnapshot,
@@ -1390,6 +1416,31 @@ export class VoiceAssistantWebSocketServer {
1390
1416
  }
1391
1417
  }
1392
1418
  }
1419
+ function createWebSocketConnectionIdentity(requestMetadata, metadata) {
1420
+ return {
1421
+ connectionId: `conn_${randomUUID().replaceAll("-", "")}`,
1422
+ transport: metadata?.transport === "relay" ? "relay" : "direct",
1423
+ ...(requestMetadata.host ? { host: requestMetadata.host } : {}),
1424
+ ...(requestMetadata.origin ? { origin: requestMetadata.origin } : {}),
1425
+ ...(requestMetadata.userAgent ? { userAgent: requestMetadata.userAgent } : {}),
1426
+ ...(requestMetadata.remoteAddress ? { remoteAddress: requestMetadata.remoteAddress } : {}),
1427
+ ...(metadata?.relayConnectionId ? { relayConnectionId: metadata.relayConnectionId } : {}),
1428
+ };
1429
+ }
1430
+ function toConnectionLogFields(identity) {
1431
+ return {
1432
+ connectionId: identity.connectionId,
1433
+ transport: identity.transport,
1434
+ ...(identity.host ? { host: identity.host } : {}),
1435
+ ...(identity.origin ? { origin: identity.origin } : {}),
1436
+ ...(identity.userAgent ? { userAgent: identity.userAgent } : {}),
1437
+ ...(identity.remoteAddress ? { remoteAddress: identity.remoteAddress } : {}),
1438
+ ...(identity.relayConnectionId ? { relayConnectionId: identity.relayConnectionId } : {}),
1439
+ ...(identity.clientId ? { clientId: identity.clientId } : {}),
1440
+ ...(identity.sessionId ? { sessionId: identity.sessionId } : {}),
1441
+ ...(identity.appVersion ? { appVersion: identity.appVersion } : {}),
1442
+ };
1443
+ }
1393
1444
  function extractSocketRequestMetadata(request) {
1394
1445
  if (!request || typeof request !== "object") {
1395
1446
  return {};
@@ -1406,6 +1457,88 @@ function extractSocketRequestMetadata(request) {
1406
1457
  ...(remoteAddress ? { remoteAddress } : {}),
1407
1458
  };
1408
1459
  }
1460
+ function stripIpv6Brackets(hostname) {
1461
+ return hostname.startsWith("[") && hostname.endsWith("]") ? hostname.slice(1, -1) : hostname;
1462
+ }
1463
+ function parseHostAuthority(host) {
1464
+ const trimmed = host.trim();
1465
+ if (!trimmed) {
1466
+ return null;
1467
+ }
1468
+ if (trimmed.startsWith("[")) {
1469
+ const end = trimmed.indexOf("]");
1470
+ if (end === -1) {
1471
+ return null;
1472
+ }
1473
+ const hostname = stripIpv6Brackets(trimmed.slice(0, end + 1)).toLowerCase();
1474
+ const rest = trimmed.slice(end + 1);
1475
+ if (!rest) {
1476
+ return { hostname, port: null };
1477
+ }
1478
+ if (!rest.startsWith(":")) {
1479
+ return null;
1480
+ }
1481
+ const port = rest.slice(1);
1482
+ return port ? { hostname, port } : null;
1483
+ }
1484
+ const firstColon = trimmed.indexOf(":");
1485
+ if (firstColon === -1) {
1486
+ return { hostname: trimmed.toLowerCase(), port: null };
1487
+ }
1488
+ if (trimmed.indexOf(":", firstColon + 1) !== -1) {
1489
+ return { hostname: trimmed.toLowerCase(), port: null };
1490
+ }
1491
+ const hostname = trimmed.slice(0, firstColon).toLowerCase();
1492
+ const port = trimmed.slice(firstColon + 1);
1493
+ return hostname && port ? { hostname, port } : null;
1494
+ }
1495
+ function defaultPortForOriginProtocol(protocol) {
1496
+ if (protocol === "http:") {
1497
+ return "80";
1498
+ }
1499
+ if (protocol === "https:") {
1500
+ return "443";
1501
+ }
1502
+ return null;
1503
+ }
1504
+ function isLoopbackAlias(hostname) {
1505
+ const normalized = stripIpv6Brackets(hostname).toLowerCase();
1506
+ if (normalized === "localhost" || normalized.endsWith(".localhost")) {
1507
+ return true;
1508
+ }
1509
+ if (normalized === "::1" || normalized === "0:0:0:0:0:0:0:1") {
1510
+ return true;
1511
+ }
1512
+ return /^127(?:\.\d{1,3}){3}$/.test(normalized);
1513
+ }
1514
+ export function isWebSocketSameOrigin(origin, requestHost) {
1515
+ if (!origin || !requestHost) {
1516
+ return false;
1517
+ }
1518
+ if (origin === `http://${requestHost}` || origin === `https://${requestHost}`) {
1519
+ return true;
1520
+ }
1521
+ let originUrl;
1522
+ try {
1523
+ originUrl = new URL(origin);
1524
+ }
1525
+ catch {
1526
+ return false;
1527
+ }
1528
+ const originPort = originUrl.port || defaultPortForOriginProtocol(originUrl.protocol);
1529
+ if (!originPort) {
1530
+ return false;
1531
+ }
1532
+ const requestAuthority = parseHostAuthority(requestHost);
1533
+ if (!requestAuthority) {
1534
+ return false;
1535
+ }
1536
+ const requestPort = requestAuthority.port || defaultPortForOriginProtocol(originUrl.protocol);
1537
+ if (originPort !== requestPort) {
1538
+ return false;
1539
+ }
1540
+ return isLoopbackAlias(originUrl.hostname) && isLoopbackAlias(requestAuthority.hostname);
1541
+ }
1409
1542
  function selectWebSocketProtocol(protocols, password) {
1410
1543
  if (!password) {
1411
1544
  return protocols.values().next().value ?? false;
@@ -1432,6 +1565,31 @@ function stringifyCloseReason(reason) {
1432
1565
  const text = String(reason);
1433
1566
  return text.length > 0 ? text : null;
1434
1567
  }
1568
+ function getControlRpcLogInfo(message) {
1569
+ if (message.type === "shutdown_server_request") {
1570
+ return {
1571
+ requestType: message.type,
1572
+ requestId: message.requestId,
1573
+ reason: CLIENT_SHUTDOWN_RPC_REASON,
1574
+ };
1575
+ }
1576
+ if (message.type === "restart_server_request") {
1577
+ const reason = normalizeClientRestartRpcReason(message.reason);
1578
+ return {
1579
+ requestType: message.type,
1580
+ requestId: message.requestId,
1581
+ reason,
1582
+ };
1583
+ }
1584
+ if (message.type === "daemon.update.request") {
1585
+ return {
1586
+ requestType: message.type,
1587
+ requestId: message.requestId,
1588
+ reason: "daemon_update",
1589
+ };
1590
+ }
1591
+ return null;
1592
+ }
1435
1593
  function extractRequestInfoFromUnknownWsInbound(payload) {
1436
1594
  if (!payload || typeof payload !== "object") {
1437
1595
  return null;
@@ -4,6 +4,7 @@ import { CopilotQuotaProvider } from "./providers/copilot.js";
4
4
  import { CursorQuotaProvider } from "./providers/cursor.js";
5
5
  import { GrokQuotaProvider } from "./providers/grok.js";
6
6
  import { KimiQuotaProvider } from "./providers/kimi.js";
7
+ import { MiniMaxQuotaProvider } from "./providers/minimax.js";
7
8
  import { ZaiQuotaProvider } from "./providers/zai.js";
8
9
  export const PROVIDER_USAGE_FETCHERS = [
9
10
  {
@@ -40,6 +41,10 @@ export const PROVIDER_USAGE_FETCHERS = [
40
41
  providerId: "kimi",
41
42
  create: (options) => new KimiQuotaProvider({ logger: options.logger, fetch: options.fetch }),
42
43
  },
44
+ {
45
+ providerId: "minimax",
46
+ create: (options) => new MiniMaxQuotaProvider({ logger: options.logger, fetch: options.fetch }),
47
+ },
43
48
  ];
44
49
  export function createProviderUsageFetchers(options) {
45
50
  return PROVIDER_USAGE_FETCHERS.map((entry) => entry.create(options));
@@ -0,0 +1,29 @@
1
+ import type { Logger } from "pino";
2
+ import type { ProviderUsage } from "../../../server/messages.js";
3
+ import type { ProviderApiFetch, ProviderUsageFetcher } from "../provider.js";
4
+ interface MiniMaxQuotaProviderOptions {
5
+ logger: Logger;
6
+ fetch?: ProviderApiFetch;
7
+ configPath?: string;
8
+ credentialsPath?: string;
9
+ env?: NodeJS.ProcessEnv;
10
+ now?: () => number;
11
+ }
12
+ export declare class MiniMaxQuotaProvider implements ProviderUsageFetcher {
13
+ readonly providerId = "minimax";
14
+ readonly displayName = "MiniMax";
15
+ private readonly logger;
16
+ private readonly fetchApi;
17
+ private readonly configPath;
18
+ private readonly credentialsPath;
19
+ private readonly env;
20
+ private readonly now;
21
+ constructor(options: MiniMaxQuotaProviderOptions);
22
+ fetchUsage(): Promise<ProviderUsage>;
23
+ private resolveAuth;
24
+ private isExpired;
25
+ private readCredentials;
26
+ private readConfig;
27
+ }
28
+ export {};
29
+ //# sourceMappingURL=minimax.d.ts.map
@@ -0,0 +1,227 @@
1
+ import { existsSync, promises as fs } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { z } from "zod";
5
+ import { ApiNumberSchema, ApiOptionalStringSchema, fetchProviderApi, unavailableUsage, windowFromUsedPct, } from "../usage.js";
6
+ const MINIMAX_GLOBAL_BASE_URL = "https://api.minimax.io";
7
+ const MINIMAX_CN_BASE_URL = "https://api.minimaxi.com";
8
+ const MINIMAX_CREDENTIALS_PATH = join(homedir(), ".mmx", "credentials.json");
9
+ const MINIMAX_CONFIG_PATH = join(homedir(), ".mmx", "config.json");
10
+ const MiniMaxModelRemainSchema = z.object({
11
+ model_name: ApiOptionalStringSchema,
12
+ start_time: ApiNumberSchema.optional(),
13
+ end_time: ApiNumberSchema.optional(),
14
+ remains_time: ApiNumberSchema.optional(),
15
+ current_interval_total_count: ApiNumberSchema.optional(),
16
+ current_interval_usage_count: ApiNumberSchema.optional(),
17
+ current_interval_remaining_percent: ApiNumberSchema.optional(),
18
+ current_weekly_total_count: ApiNumberSchema.optional(),
19
+ current_weekly_usage_count: ApiNumberSchema.optional(),
20
+ current_weekly_remaining_percent: ApiNumberSchema.optional(),
21
+ current_interval_status: ApiNumberSchema.optional(),
22
+ current_weekly_status: ApiNumberSchema.optional(),
23
+ weekly_start_time: ApiNumberSchema.optional(),
24
+ weekly_end_time: ApiNumberSchema.optional(),
25
+ weekly_remains_time: ApiNumberSchema.optional(),
26
+ weekly_boost_permille: ApiNumberSchema.optional(),
27
+ });
28
+ const MiniMaxQuotaResponseSchema = z.object({
29
+ model_remains: z.array(MiniMaxModelRemainSchema).optional(),
30
+ });
31
+ const MiniMaxCredentialsSchema = z.object({
32
+ access_token: z.string().optional(),
33
+ refresh_token: z.string().optional(),
34
+ expires_at: ApiOptionalStringSchema,
35
+ resource_url: ApiOptionalStringSchema,
36
+ });
37
+ const MiniMaxConfigSchema = z.object({
38
+ api_key: z.string().optional(),
39
+ region: z.string().optional(),
40
+ base_url: ApiOptionalStringSchema,
41
+ oauth: MiniMaxCredentialsSchema.optional(),
42
+ });
43
+ function resolveBaseUrl(input) {
44
+ const explicit = input.baseUrl;
45
+ if (explicit && explicit.startsWith("http"))
46
+ return explicit;
47
+ if (input.region === "cn")
48
+ return MINIMAX_CN_BASE_URL;
49
+ return MINIMAX_GLOBAL_BASE_URL;
50
+ }
51
+ function computeUsedPct(remaining, total) {
52
+ if (typeof remaining !== "number" || typeof total !== "number")
53
+ return null;
54
+ if (!Number.isFinite(total) || total <= 0)
55
+ return null;
56
+ if (!Number.isFinite(remaining))
57
+ return null;
58
+ const used = total - remaining;
59
+ return Math.max(0, Math.min(100, (used / total) * 100));
60
+ }
61
+ function epochMsToIso(value) {
62
+ if (typeof value !== "number" || !Number.isFinite(value) || value <= 0)
63
+ return null;
64
+ return new Date(value).toISOString();
65
+ }
66
+ function toneForStatus(status) {
67
+ if (status === 2)
68
+ return "danger";
69
+ if (status === 3)
70
+ return "default";
71
+ return "ok";
72
+ }
73
+ function toIntervalWindow(modelName, model) {
74
+ const total = model.current_interval_total_count ?? null;
75
+ const used = model.current_interval_usage_count ?? null;
76
+ const remainingPercent = model.current_interval_remaining_percent ?? null;
77
+ const usedPct = typeof remainingPercent === "number" && Number.isFinite(remainingPercent)
78
+ ? Math.max(0, Math.min(100, 100 - remainingPercent))
79
+ : computeUsedPct(typeof total === "number" && typeof used === "number" ? total - used : null, total);
80
+ if (usedPct === null)
81
+ return null;
82
+ return windowFromUsedPct({
83
+ id: `interval_${modelName}`,
84
+ label: `${modelName} · Interval`,
85
+ utilizationPct: usedPct,
86
+ resetsAt: epochMsToIso(model.end_time),
87
+ tone: toneForStatus(model.current_interval_status),
88
+ });
89
+ }
90
+ function toWeeklyWindow(modelName, model) {
91
+ const total = model.current_weekly_total_count ?? null;
92
+ const used = model.current_weekly_usage_count ?? null;
93
+ const remainingPercent = model.current_weekly_remaining_percent ?? null;
94
+ let usedPct = null;
95
+ if (typeof remainingPercent === "number" && Number.isFinite(remainingPercent)) {
96
+ usedPct = Math.max(0, Math.min(100, 100 - remainingPercent));
97
+ }
98
+ else if (typeof total === "number" && typeof used === "number") {
99
+ usedPct = computeUsedPct(total - used, total);
100
+ }
101
+ if (usedPct === null)
102
+ return null;
103
+ return windowFromUsedPct({
104
+ id: `weekly_${modelName}`,
105
+ label: `${modelName} · Weekly`,
106
+ utilizationPct: usedPct,
107
+ resetsAt: epochMsToIso(model.weekly_end_time),
108
+ tone: toneForStatus(model.current_weekly_status),
109
+ });
110
+ }
111
+ export class MiniMaxQuotaProvider {
112
+ constructor(options) {
113
+ this.providerId = "minimax";
114
+ this.displayName = "MiniMax";
115
+ this.logger = options.logger;
116
+ this.fetchApi = options.fetch ?? fetch;
117
+ this.configPath = options.configPath ?? MINIMAX_CONFIG_PATH;
118
+ this.credentialsPath = options.credentialsPath ?? MINIMAX_CREDENTIALS_PATH;
119
+ this.env = options.env ?? process.env;
120
+ this.now = options.now ?? Date.now;
121
+ }
122
+ async fetchUsage() {
123
+ const auth = await this.resolveAuth();
124
+ if (!auth)
125
+ return unavailableUsage(this);
126
+ const res = await fetchProviderApi(this.fetchApi, `${auth.baseUrl}/v1/token_plan/remains`, {
127
+ headers: {
128
+ Authorization: `Bearer ${auth.token}`,
129
+ Accept: "application/json",
130
+ },
131
+ });
132
+ if (!res.ok) {
133
+ this.logger.debug({ status: res.status }, "MiniMax usage fetch failed");
134
+ return unavailableUsage(this);
135
+ }
136
+ const resp = MiniMaxQuotaResponseSchema.parse(await res.json());
137
+ const models = resp.model_remains ?? [];
138
+ const windows = [];
139
+ for (const model of models) {
140
+ const name = model.model_name ?? "token-plan";
141
+ const intervalWindow = toIntervalWindow(name, model);
142
+ if (intervalWindow)
143
+ windows.push(intervalWindow);
144
+ const weeklyWindow = toWeeklyWindow(name, model);
145
+ if (weeklyWindow)
146
+ windows.push(weeklyWindow);
147
+ }
148
+ return {
149
+ providerId: this.providerId,
150
+ displayName: this.displayName,
151
+ status: windows.length > 0 ? "available" : "unavailable",
152
+ planLabel: null,
153
+ windows,
154
+ balances: [],
155
+ details: [],
156
+ error: null,
157
+ };
158
+ }
159
+ async resolveAuth() {
160
+ const envToken = this.env["MINIMAX_API_KEY"];
161
+ if (envToken) {
162
+ const envBase = this.env["MINIMAX_BASE_URL"];
163
+ return {
164
+ token: envToken,
165
+ baseUrl: resolveBaseUrl({ baseUrl: envBase }),
166
+ };
167
+ }
168
+ const credentials = await this.readCredentials();
169
+ if (credentials?.access_token && !this.isExpired(credentials.expires_at)) {
170
+ return {
171
+ token: credentials.access_token,
172
+ baseUrl: resolveBaseUrl({ baseUrl: credentials.resource_url }),
173
+ };
174
+ }
175
+ const config = await this.readConfig();
176
+ if (config?.api_key) {
177
+ return {
178
+ token: config.api_key,
179
+ baseUrl: resolveBaseUrl({
180
+ baseUrl: config.base_url,
181
+ region: config.region,
182
+ }),
183
+ };
184
+ }
185
+ if (config?.oauth?.access_token && !this.isExpired(config.oauth.expires_at)) {
186
+ return {
187
+ token: config.oauth.access_token,
188
+ baseUrl: resolveBaseUrl({
189
+ baseUrl: config.oauth.resource_url ?? config.base_url,
190
+ region: config.region,
191
+ }),
192
+ };
193
+ }
194
+ return null;
195
+ }
196
+ isExpired(expiresAt) {
197
+ if (!expiresAt)
198
+ return false;
199
+ const parsed = Date.parse(expiresAt);
200
+ if (!Number.isFinite(parsed))
201
+ return false;
202
+ return parsed <= this.now();
203
+ }
204
+ async readCredentials() {
205
+ if (!existsSync(this.credentialsPath))
206
+ return null;
207
+ try {
208
+ const raw = JSON.parse(await fs.readFile(this.credentialsPath, "utf8"));
209
+ return MiniMaxCredentialsSchema.parse(raw);
210
+ }
211
+ catch {
212
+ return null;
213
+ }
214
+ }
215
+ async readConfig() {
216
+ if (!existsSync(this.configPath))
217
+ return null;
218
+ try {
219
+ const raw = JSON.parse(await fs.readFile(this.configPath, "utf8"));
220
+ return MiniMaxConfigSchema.parse(raw);
221
+ }
222
+ catch {
223
+ return null;
224
+ }
225
+ }
226
+ }
227
+ //# sourceMappingURL=minimax.js.map
@@ -42,11 +42,11 @@ function resolveAgentHookInstallPath(install, options) {
42
42
  }
43
43
  export function buildAgentHookShellCommand(provider, event) {
44
44
  const hookCommand = `"\${PASEO_HOOK_CLI:-paseo}" hooks ${shellToken(provider.id)} ${shellToken(event.event)}`;
45
- return `[ -n "$PASEO_TERMINAL_ID" ] && ${hookCommand}`;
45
+ return `if [ -n "$PASEO_TERMINAL_ID" ]; then ${hookCommand}; fi`;
46
46
  }
47
47
  export function buildAgentHookWindowsCommand(provider, event) {
48
48
  const hookArgs = `hooks ${windowsToken(provider.id)} ${windowsToken(event.event)}`;
49
- return `if defined PASEO_TERMINAL_ID (if defined PASEO_HOOK_CLI ("%PASEO_HOOK_CLI%" ${hookArgs}) else (paseo ${hookArgs}))`;
49
+ return `if defined PASEO_TERMINAL_ID (if defined PASEO_HOOK_CLI ("%PASEO_HOOK_CLI%" ${hookArgs}) else (paseo ${hookArgs})) else (exit /b 0)`;
50
50
  }
51
51
  function installAgentHookPluginFile(install, options) {
52
52
  const configPath = resolveAgentHookInstallPath(install, options);