@getpaseo/server 0.1.101 → 0.1.102-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. package/dist/scripts/supervisor.js +26 -8
  2. package/dist/server/server/agent/activity-curator.d.ts +17 -0
  3. package/dist/server/server/agent/activity-curator.js +101 -24
  4. package/dist/server/server/agent/agent-manager.js +5 -1
  5. package/dist/server/server/agent/agent-sdk-types.d.ts +7 -2
  6. package/dist/server/server/agent/provider-snapshot-manager.d.ts +8 -1
  7. package/dist/server/server/agent/provider-snapshot-manager.js +78 -33
  8. package/dist/server/server/agent/providers/acp-agent.d.ts +7 -0
  9. package/dist/server/server/agent/providers/acp-agent.js +8 -1
  10. package/dist/server/server/agent/providers/claude/agent.js +51 -14
  11. package/dist/server/server/agent/providers/claude/query.d.ts +3 -0
  12. package/dist/server/server/agent/providers/claude/query.js +4 -2
  13. package/dist/server/server/agent/providers/mock-load-test-agent.js +8 -0
  14. package/dist/server/server/agent/providers/opencode/paths.d.ts +2 -0
  15. package/dist/server/server/agent/providers/opencode/paths.js +7 -0
  16. package/dist/server/server/agent/providers/opencode/server-manager.d.ts +2 -0
  17. package/dist/server/server/agent/providers/opencode/server-manager.js +34 -5
  18. package/dist/server/server/agent/providers/opencode-agent.d.ts +4 -0
  19. package/dist/server/server/agent/providers/opencode-agent.js +14 -2
  20. package/dist/server/server/agent/providers/pi/agent.d.ts +3 -0
  21. package/dist/server/server/agent/providers/pi/agent.js +9 -3
  22. package/dist/server/server/agent/providers/provider-image-output.js +11 -6
  23. package/dist/server/server/agent/tools/paseo-tools.d.ts +1 -1
  24. package/dist/server/server/agent/tools/paseo-tools.js +0 -2
  25. package/dist/server/server/bootstrap.d.ts +7 -1
  26. package/dist/server/server/bootstrap.js +18 -0
  27. package/dist/server/server/config.d.ts +2 -0
  28. package/dist/server/server/config.js +57 -1
  29. package/dist/server/server/daemon-worker.js +19 -7
  30. package/dist/server/server/lifecycle-reasons.d.ts +4 -0
  31. package/dist/server/server/lifecycle-reasons.js +6 -0
  32. package/dist/server/server/persisted-config.d.ts +7 -0
  33. package/dist/server/server/persisted-config.js +8 -0
  34. package/dist/server/server/process-diagnostics.d.ts +17 -0
  35. package/dist/server/server/process-diagnostics.js +22 -0
  36. package/dist/server/server/relay-transport.js +1 -0
  37. package/dist/server/server/resolve-worktree-creation-intent.js +3 -1
  38. package/dist/server/server/session/daemon/daemon-self-update-session-controller.d.ts +32 -0
  39. package/dist/server/server/session/daemon/daemon-self-update-session-controller.js +88 -0
  40. package/dist/server/server/session/daemon/daemon-self-updater.d.ts +32 -0
  41. package/dist/server/server/session/daemon/daemon-self-updater.js +56 -0
  42. package/dist/server/server/session/daemon/daemon-session.d.ts +12 -0
  43. package/dist/server/server/session/daemon/daemon-session.js +12 -0
  44. package/dist/server/server/session/daemon/diagnostics.js +10 -0
  45. package/dist/server/server/session/daemon/install-origin.d.ts +7 -0
  46. package/dist/server/server/session/daemon/install-origin.js +64 -0
  47. package/dist/server/server/session/daemon/npm-global-cli.d.ts +29 -0
  48. package/dist/server/server/session/daemon/npm-global-cli.js +98 -0
  49. package/dist/server/server/session/provider/provider-catalog-session.js +8 -4
  50. package/dist/server/server/session.d.ts +5 -3
  51. package/dist/server/server/session.js +74 -32
  52. package/dist/server/server/web-ui.d.ts +10 -0
  53. package/dist/server/server/web-ui.js +205 -0
  54. package/dist/server/server/websocket/runtime-metrics.d.ts +3 -0
  55. package/dist/server/server/websocket-server.d.ts +3 -0
  56. package/dist/server/server/websocket-server.js +190 -32
  57. package/dist/server/services/quota-fetcher/manifest.js +5 -0
  58. package/dist/server/services/quota-fetcher/providers/minimax.d.ts +29 -0
  59. package/dist/server/services/quota-fetcher/providers/minimax.js +227 -0
  60. package/dist/server/terminal/agent-hooks/agent-hook-installer.js +2 -2
  61. package/dist/server/utils/checkout-git.js +156 -3
  62. package/dist/server/utils/directory-suggestions.js +1 -4
  63. package/dist/server/utils/path.d.ts +2 -0
  64. package/dist/server/utils/path.js +13 -0
  65. package/dist/server/utils/worktree.d.ts +1 -0
  66. package/dist/server/utils/worktree.js +92 -11
  67. package/dist/server/web-ui/_expo/static/css/xterm-3bb1704bf6cb0876640973dc0244b4cb.css +1 -0
  68. package/dist/server/web-ui/_expo/static/css/xterm-3bb1704bf6cb0876640973dc0244b4cb.css.br +0 -0
  69. package/dist/server/web-ui/_expo/static/css/xterm-3bb1704bf6cb0876640973dc0244b4cb.css.gz +0 -0
  70. package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-bridge-b01555c9b42665a03988c0a0032ef528.js +1 -0
  71. package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-bridge-b01555c9b42665a03988c0a0032ef528.js.br +0 -0
  72. package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-bridge-b01555c9b42665a03988c0a0032ef528.js.gz +0 -0
  73. package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-store-648388eca5c510b496e1eddf523f70ff.js +1 -0
  74. package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-store-648388eca5c510b496e1eddf523f70ff.js.br +0 -0
  75. package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-store-648388eca5c510b496e1eddf523f70ff.js.gz +0 -0
  76. package/dist/server/web-ui/_expo/static/js/web/index-0ebbea2cd337f0c0680fdb3f8d4d5af3.js +16157 -0
  77. package/dist/server/web-ui/_expo/static/js/web/index-0ebbea2cd337f0c0680fdb3f8d4d5af3.js.br +0 -0
  78. package/dist/server/web-ui/_expo/static/js/web/index-0ebbea2cd337f0c0680fdb3f8d4d5af3.js.gz +0 -0
  79. package/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-c64fa2416284927857a39087fd8d1332.js +1 -0
  80. package/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-c64fa2416284927857a39087fd8d1332.js.br +0 -0
  81. package/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-c64fa2416284927857a39087fd8d1332.js.gz +0 -0
  82. package/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-a9784226715772edf87ef36c596599c2.js +3 -0
  83. package/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-a9784226715772edf87ef36c596599c2.js.br +0 -0
  84. package/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-a9784226715772edf87ef36c596599c2.js.gz +0 -0
  85. package/dist/server/web-ui/apple-touch-icon.png +0 -0
  86. package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/back-icon-mask.0a328cd9c1afd0afe8e3b1ec5165b1b4.png +0 -0
  87. package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/back-icon.35ba0eaec5a4f5ed12ca16fabeae451d.png +0 -0
  88. package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/clear-icon.c94f6478e7ae0cdd9f15de1fcb9e5e55.png +0 -0
  89. package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/clear-icon.c94f6478e7ae0cdd9f15de1fcb9e5e55@2x.png +0 -0
  90. package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/clear-icon.c94f6478e7ae0cdd9f15de1fcb9e5e55@3x.png +0 -0
  91. package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/clear-icon.c94f6478e7ae0cdd9f15de1fcb9e5e55@4x.png +0 -0
  92. package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/close-icon.808e1b1b9b53114ec2838071a7e6daa7.png +0 -0
  93. package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/close-icon.808e1b1b9b53114ec2838071a7e6daa7@2x.png +0 -0
  94. package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/close-icon.808e1b1b9b53114ec2838071a7e6daa7@3x.png +0 -0
  95. package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/close-icon.808e1b1b9b53114ec2838071a7e6daa7@4x.png +0 -0
  96. package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/search-icon.286d67d3f74808a60a78d3ebf1a5fb57.png +0 -0
  97. package/dist/server/web-ui/assets/__node_modules/expo-router/assets/arrow_down.017bc6ba3fc25503e5eb5e53826d48a8.png +0 -0
  98. package/dist/server/web-ui/assets/__node_modules/expo-router/assets/error.d1ea1496f9057eb392d5bbf3732a61b7.png +0 -0
  99. package/dist/server/web-ui/assets/__node_modules/expo-router/assets/file.19eeb73b9593a38f8e9f418337fc7d10.png +0 -0
  100. package/dist/server/web-ui/assets/__node_modules/expo-router/assets/forward.d8b800c443b8972542883e0b9de2bdc6.png +0 -0
  101. package/dist/server/web-ui/assets/__node_modules/expo-router/assets/pkg.ab19f4cbc543357183a20571f68380a3.png +0 -0
  102. package/dist/server/web-ui/assets/__node_modules/expo-router/assets/sitemap.412dd9275b6b48ad28f5e3d81bb1f626.png +0 -0
  103. package/dist/server/web-ui/assets/__node_modules/expo-router/assets/unmatched.20e71bdf79e3a97bf55fd9e164041578.png +0 -0
  104. package/dist/server/web-ui/assets/assets/images/editor-apps/antigravity.6e91a685c33435e0b466a56db86cf141.png +0 -0
  105. package/dist/server/web-ui/assets/assets/images/editor-apps/cursor.c31d6bce4fe9aadc3fe59962f4c4fcf3.png +0 -0
  106. package/dist/server/web-ui/assets/assets/images/editor-apps/file-explorer.3e15e8f72c825c85ce336bcb0cdef776.png +0 -0
  107. package/dist/server/web-ui/assets/assets/images/editor-apps/finder.7f68fc2c475621a672e1be09309d5567.png +0 -0
  108. package/dist/server/web-ui/assets/assets/images/editor-apps/vscode.832bdb4c685d930f1c864c793703600b.png +0 -0
  109. package/dist/server/web-ui/assets/assets/images/editor-apps/webstorm.aa5dc2cd8c20cc0a155c4c5c5ab3c5f5.png +0 -0
  110. package/dist/server/web-ui/assets/assets/images/editor-apps/zed.f3a670b7f9aa226da4fe53fb86f1abbd.png +0 -0
  111. package/dist/server/web-ui/assets/assets/images/favicon-dark-attention.882b3a27dcb2073e9e31b334f9ed9728.png +0 -0
  112. package/dist/server/web-ui/assets/assets/images/favicon-dark-running.8112342ff0d39e047a7f8d4fad9402f3.png +0 -0
  113. package/dist/server/web-ui/assets/assets/images/favicon-dark.8005ed36ac07a5a7c60de25780897bd4.png +0 -0
  114. package/dist/server/web-ui/assets/assets/images/favicon-light-attention.882b3a27dcb2073e9e31b334f9ed9728.png +0 -0
  115. package/dist/server/web-ui/assets/assets/images/favicon-light-running.8112342ff0d39e047a7f8d4fad9402f3.png +0 -0
  116. package/dist/server/web-ui/assets/assets/images/favicon-light.8005ed36ac07a5a7c60de25780897bd4.png +0 -0
  117. package/dist/server/web-ui/assets/assets/images/notification-icon.3bf81d33ddbf380606bdd248ba83e158.png +0 -0
  118. package/dist/server/web-ui/favicon.ico +0 -0
  119. package/dist/server/web-ui/index.html +90 -0
  120. package/dist/server/web-ui/index.html.br +0 -0
  121. package/dist/server/web-ui/index.html.gz +0 -0
  122. package/dist/server/web-ui/manifest.json +27 -0
  123. package/dist/server/web-ui/manifest.json.br +0 -0
  124. package/dist/server/web-ui/manifest.json.gz +0 -0
  125. package/dist/server/web-ui/metadata.json +1 -0
  126. package/dist/server/web-ui/metadata.json.br +1 -0
  127. package/dist/server/web-ui/metadata.json.gz +0 -0
  128. package/dist/server/web-ui/pwa-icon-192.png +0 -0
  129. package/dist/server/web-ui/pwa-icon-512.png +0 -0
  130. package/dist/server/web-ui/robots.txt +2 -0
  131. package/dist/src/server/persisted-config.js +8 -0
  132. package/package.json +7 -7
@@ -0,0 +1,205 @@
1
+ import { createReadStream, readFileSync, statSync } from "node:fs";
2
+ import path from "node:path";
3
+ const EXCLUDED_PATH_PREFIXES = ["/api/", "/mcp/", "/public/"];
4
+ const EXCLUDED_PATHS = new Set(["/api", "/mcp", "/public"]);
5
+ function isExcludedPath(requestPath) {
6
+ for (const prefix of EXCLUDED_PATH_PREFIXES) {
7
+ if (requestPath.startsWith(prefix)) {
8
+ return true;
9
+ }
10
+ }
11
+ return EXCLUDED_PATHS.has(requestPath);
12
+ }
13
+ const CONTENT_TYPES = {
14
+ ".html": "text/html; charset=utf-8",
15
+ ".js": "application/javascript; charset=utf-8",
16
+ ".mjs": "application/javascript; charset=utf-8",
17
+ ".css": "text/css; charset=utf-8",
18
+ ".json": "application/json; charset=utf-8",
19
+ ".png": "image/png",
20
+ ".jpg": "image/jpeg",
21
+ ".jpeg": "image/jpeg",
22
+ ".gif": "image/gif",
23
+ ".svg": "image/svg+xml",
24
+ ".ico": "image/x-icon",
25
+ ".woff": "font/woff",
26
+ ".woff2": "font/woff2",
27
+ ".ttf": "font/ttf",
28
+ ".otf": "font/otf",
29
+ ".eot": "application/vnd.ms-fontobject",
30
+ ".map": "application/json",
31
+ };
32
+ function getContentType(filePath) {
33
+ const ext = path.extname(filePath).toLowerCase();
34
+ return CONTENT_TYPES[ext] ?? "application/octet-stream";
35
+ }
36
+ function selectEncoding(acceptEncoding) {
37
+ if (!acceptEncoding) {
38
+ return null;
39
+ }
40
+ const normalized = acceptEncoding.toLowerCase();
41
+ if (normalized.includes("br")) {
42
+ return "br";
43
+ }
44
+ if (normalized.includes("gzip")) {
45
+ return "gzip";
46
+ }
47
+ return null;
48
+ }
49
+ function isHashedAsset(filePath) {
50
+ const base = path.basename(filePath);
51
+ // Match content hashes like index-abc123def456.js or main.abc123def456.css.
52
+ return /[-.][0-9a-f]{16,}[-.]/i.test(base);
53
+ }
54
+ function isInsideDir(targetPath, dirPath) {
55
+ const resolvedDir = path.resolve(dirPath);
56
+ const resolvedTarget = path.resolve(targetPath);
57
+ return resolvedTarget === resolvedDir || resolvedTarget.startsWith(resolvedDir + path.sep);
58
+ }
59
+ function resolveTargetFile(distDir, requestPath) {
60
+ const safePath = path.normalize(requestPath).replace(/^(\.\.[/\\])+/, "");
61
+ let filePath = path.join(distDir, safePath);
62
+ const stat = safeStat(filePath);
63
+ if (stat?.isDirectory()) {
64
+ filePath = path.join(filePath, "index.html");
65
+ }
66
+ const finalStat = safeStat(filePath);
67
+ if (!finalStat?.isFile()) {
68
+ filePath = path.join(distDir, "index.html");
69
+ const fallbackStat = safeStat(filePath);
70
+ if (!fallbackStat?.isFile()) {
71
+ return null;
72
+ }
73
+ }
74
+ if (!isInsideDir(filePath, distDir)) {
75
+ return null;
76
+ }
77
+ const resolvedFile = path.resolve(filePath);
78
+ const isIndexHtml = path.basename(resolvedFile).toLowerCase() === "index.html";
79
+ return { resolvedFile, isIndexHtml };
80
+ }
81
+ function safeStat(filePath) {
82
+ try {
83
+ return statSync(filePath);
84
+ }
85
+ catch {
86
+ return null;
87
+ }
88
+ }
89
+ function resolveContentEncoding(resolvedFile, acceptEncoding) {
90
+ const encoding = selectEncoding(acceptEncoding);
91
+ if (!encoding) {
92
+ return { finalFile: resolvedFile, contentEncoding: null };
93
+ }
94
+ const compressedFile = `${resolvedFile}.${encoding === "br" ? "br" : "gz"}`;
95
+ const compressedStat = safeStat(compressedFile);
96
+ if (compressedStat?.isFile()) {
97
+ return { finalFile: compressedFile, contentEncoding: encoding };
98
+ }
99
+ return { finalFile: resolvedFile, contentEncoding: null };
100
+ }
101
+ function setResponseCacheHeaders(res, isIndexHtml, resolvedFile) {
102
+ if (isIndexHtml) {
103
+ res.setHeader("Cache-Control", "no-store, no-cache, must-revalidate, proxy-revalidate");
104
+ res.setHeader("Pragma", "no-cache");
105
+ res.setHeader("Expires", "0");
106
+ }
107
+ else if (isHashedAsset(resolvedFile)) {
108
+ res.setHeader("Cache-Control", "public, max-age=31536000, immutable");
109
+ }
110
+ else {
111
+ res.setHeader("Cache-Control", "no-cache");
112
+ }
113
+ }
114
+ export function createWebUiMiddleware(options) {
115
+ const { enabled, distDir, label, logger } = options;
116
+ const childLogger = logger.child({ module: "web-ui" });
117
+ if (!enabled || !distDir) {
118
+ childLogger.info({ enabled, hasDistDir: !!distDir }, "Daemon web UI disabled or missing dist directory");
119
+ }
120
+ else {
121
+ childLogger.info({ distDir }, "Daemon web UI mounted");
122
+ }
123
+ return (req, res, next) => {
124
+ if (req.method !== "GET" && req.method !== "HEAD") {
125
+ next();
126
+ return;
127
+ }
128
+ if (isExcludedPath(req.path)) {
129
+ next();
130
+ return;
131
+ }
132
+ if (!enabled || !distDir) {
133
+ res.status(404).end();
134
+ return;
135
+ }
136
+ serveWebUiFile({ distDir, requestPath: req.path, label, req, res });
137
+ };
138
+ }
139
+ function serveWebUiFile(options) {
140
+ const { distDir, requestPath, label, req, res } = options;
141
+ const target = resolveTargetFile(distDir, requestPath);
142
+ if (!target) {
143
+ res.status(404).end();
144
+ return;
145
+ }
146
+ const { resolvedFile, isIndexHtml } = target;
147
+ const acceptEncoding = isIndexHtml ? undefined : req.headers["accept-encoding"];
148
+ const { finalFile, contentEncoding } = resolveContentEncoding(resolvedFile, acceptEncoding);
149
+ res.setHeader("Content-Type", getContentType(resolvedFile));
150
+ if (contentEncoding) {
151
+ res.setHeader("Content-Encoding", contentEncoding);
152
+ res.setHeader("Vary", "Accept-Encoding");
153
+ }
154
+ setResponseCacheHeaders(res, isIndexHtml, resolvedFile);
155
+ if (req.method === "HEAD") {
156
+ res.status(200).end();
157
+ return;
158
+ }
159
+ if (isIndexHtml) {
160
+ sendIndexHtml(res, finalFile, req, label);
161
+ return;
162
+ }
163
+ const stream = createReadStream(finalFile);
164
+ stream.on("error", () => {
165
+ if (!res.headersSent) {
166
+ res.status(500).end();
167
+ }
168
+ else {
169
+ res.end();
170
+ }
171
+ });
172
+ stream.pipe(res);
173
+ }
174
+ function sendIndexHtml(res, filePath, req, label) {
175
+ try {
176
+ const html = readFileSync(filePath, "utf-8");
177
+ const injected = injectConnectionHint(html, req, label);
178
+ res.status(200).send(injected);
179
+ }
180
+ catch {
181
+ res.status(500).end();
182
+ }
183
+ }
184
+ function serializeInlineScriptJson(value) {
185
+ return JSON.stringify(value)
186
+ .replace(/</g, "\\u003C")
187
+ .replace(/>/g, "\\u003E")
188
+ .replace(/&/g, "\\u0026");
189
+ }
190
+ function injectConnectionHint(html, req, label) {
191
+ const host = typeof req.headers.host === "string" ? req.headers.host : "";
192
+ const useTls = req.protocol === "https";
193
+ const hint = {
194
+ listen: host,
195
+ useTls,
196
+ label,
197
+ };
198
+ const script = `<script>window.__PASEO_INITIAL_DAEMON_CONNECTION__=${serializeInlineScriptJson(hint)}</script>`;
199
+ const headClose = /<\/head>/i;
200
+ if (headClose.test(html)) {
201
+ return html.replace(headClose, `${script}</head>`);
202
+ }
203
+ return script + html;
204
+ }
205
+ //# sourceMappingURL=web-ui.js.map
@@ -1,4 +1,5 @@
1
1
  import type { WSOutboundMessage } from "../messages.js";
2
+ import type { ProcessMemoryDiagnostics } from "../process-diagnostics.js";
2
3
  export interface WebSocketRuntimeCounters {
3
4
  connectedAwaitingHello: number;
4
5
  helloResumed: number;
@@ -56,6 +57,8 @@ export interface WebSocketRuntimeDiagnosticSnapshot<TRuntime = unknown, TAgents
56
57
  p99Ms: number;
57
58
  maxMs: number;
58
59
  } | null;
60
+ uptimeSeconds: number;
61
+ memory: ProcessMemoryDiagnostics;
59
62
  runtime: TRuntime;
60
63
  agents: TAgents;
61
64
  }
@@ -26,6 +26,7 @@ import { type DaemonAuthConfig } from "./auth.js";
26
26
  export interface ExternalSocketMetadata {
27
27
  transport: "relay";
28
28
  externalSessionKey?: string;
29
+ relayConnectionId?: string;
29
30
  }
30
31
  interface WebSocketServerConfig {
31
32
  allowedOrigins: Set<string>;
@@ -50,6 +51,7 @@ export declare class VoiceAssistantWebSocketServer {
50
51
  private readonly wss;
51
52
  private readonly pendingConnections;
52
53
  private readonly sessions;
54
+ private readonly socketIdentities;
53
55
  private readonly externalSessionsByKey;
54
56
  private readonly serverId;
55
57
  private readonly daemonVersion;
@@ -156,5 +158,6 @@ export declare class VoiceAssistantWebSocketServer {
156
158
  private broadcastAgentAttention;
157
159
  private broadcastTerminalAttention;
158
160
  }
161
+ export declare function isWebSocketSameOrigin(origin: string | undefined, requestHost: string | null): boolean;
159
162
  export {};
160
163
  //# sourceMappingURL=websocket-server.d.ts.map
@@ -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