@getpaseo/server 0.1.91-beta.2 → 0.1.92
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.
- package/dist/scripts/supervisor.js +21 -0
- package/dist/server/server/agent/agent-manager.d.ts +13 -5
- package/dist/server/server/agent/agent-manager.js +110 -74
- package/dist/server/server/agent/agent-projections.d.ts +4 -2
- package/dist/server/server/agent/agent-projections.js +8 -28
- package/dist/server/server/agent/agent-sdk-types.d.ts +30 -10
- package/dist/server/server/agent/import-sessions.d.ts +3 -2
- package/dist/server/server/agent/import-sessions.js +23 -55
- package/dist/server/server/agent/prompt-attachments.js +8 -0
- package/dist/server/server/agent/provider-registry.d.ts +0 -1
- package/dist/server/server/agent/provider-registry.js +55 -16
- package/dist/server/server/agent/provider-session-import.d.ts +10 -0
- package/dist/server/server/agent/provider-session-import.js +49 -0
- package/dist/server/server/agent/providers/acp-agent.d.ts +12 -2
- package/dist/server/server/agent/providers/acp-agent.js +78 -36
- package/dist/server/server/agent/providers/claude/agent.d.ts +3 -2
- package/dist/server/server/agent/providers/claude/agent.js +28 -24
- package/dist/server/server/agent/providers/codex-app-server-agent.d.ts +3 -2
- package/dist/server/server/agent/providers/codex-app-server-agent.js +29 -26
- package/dist/server/server/agent/providers/cursor-acp-agent.d.ts +1 -0
- package/dist/server/server/agent/providers/cursor-acp-agent.js +1 -0
- package/dist/server/server/agent/providers/generic-acp-agent.d.ts +9 -0
- package/dist/server/server/agent/providers/generic-acp-agent.js +18 -1
- package/dist/server/server/agent/providers/mock-load-test-agent.d.ts +3 -2
- package/dist/server/server/agent/providers/mock-load-test-agent.js +11 -1
- package/dist/server/server/agent/providers/mock-slow-provider.d.ts +1 -2
- package/dist/server/server/agent/providers/mock-slow-provider.js +0 -3
- package/dist/server/server/agent/providers/opencode/test-utils/test-opencode-runtime.d.ts +3 -0
- package/dist/server/server/agent/providers/opencode/test-utils/test-opencode-runtime.js +12 -0
- package/dist/server/server/agent/providers/opencode-agent.d.ts +18 -3
- package/dist/server/server/agent/providers/opencode-agent.js +135 -36
- package/dist/server/server/agent/providers/pi/agent.d.ts +25 -2
- package/dist/server/server/agent/providers/pi/agent.js +243 -14
- package/dist/server/server/agent/providers/pi/cli-runtime.js +9 -0
- package/dist/server/server/agent/providers/pi/rpc-types.d.ts +9 -0
- package/dist/server/server/agent/providers/pi/runtime.d.ts +2 -0
- package/dist/server/server/agent/providers/pi/session-descriptor.d.ts +11 -0
- package/dist/server/server/agent/providers/pi/session-descriptor.js +284 -0
- package/dist/server/server/agent/providers/pi/test-utils/fake-pi.d.ts +8 -0
- package/dist/server/server/agent/providers/pi/test-utils/fake-pi.js +22 -0
- package/dist/server/server/agent/runtime-mcp-config.d.ts +8 -0
- package/dist/server/server/agent/runtime-mcp-config.js +50 -0
- package/dist/server/server/auto-archive-on-merge/archive-if-safe.js +2 -2
- package/dist/server/server/daemon-worker.js +84 -1
- package/dist/server/server/file-upload/index.d.ts +27 -0
- package/dist/server/server/file-upload/index.js +158 -0
- package/dist/server/server/loop-service.d.ts +12 -12
- package/dist/server/server/persisted-config.d.ts +8 -0
- package/dist/server/server/persisted-config.js +1 -1
- package/dist/server/server/persistence-hooks.js +6 -4
- package/dist/server/server/session.d.ts +5 -2
- package/dist/server/server/session.js +20 -3
- package/dist/server/server/speech/providers/local/runtime.js +1 -0
- package/dist/server/server/speech/providers/local/worker-client.d.ts +14 -1
- package/dist/server/server/speech/providers/local/worker-client.js +169 -7
- package/dist/server/server/websocket-server.d.ts +2 -0
- package/dist/server/server/websocket-server.js +20 -7
- package/dist/server/server/workspace-registry.d.ts +4 -4
- package/dist/src/server/persisted-config.js +1 -1
- package/package.json +5 -5
|
@@ -8,6 +8,7 @@ import { CLIENT_CAPS } from "@getpaseo/protocol/client-capabilities";
|
|
|
8
8
|
import { serializeAgentStreamEvent, } from "./messages.js";
|
|
9
9
|
import { TerminalSessionController } from "../terminal/terminal-session-controller.js";
|
|
10
10
|
import { encodeFileTransferFrame, FileTransferOpcode, } from "@getpaseo/protocol/binary-frames/index";
|
|
11
|
+
import { FileUploadStore } from "./file-upload/index.js";
|
|
11
12
|
import { CursorError } from "./pagination/cursor.js";
|
|
12
13
|
import { SortablePager } from "./pagination/sortable-pager.js";
|
|
13
14
|
import { TTSManager } from "./agent/tts-manager.js";
|
|
@@ -327,6 +328,7 @@ export class Session {
|
|
|
327
328
|
this.onLifecycleIntent = onLifecycleIntent ?? null;
|
|
328
329
|
this.downloadTokenStore = downloadTokenStore;
|
|
329
330
|
this.pushTokenStore = pushTokenStore;
|
|
331
|
+
this.fileUploads = new FileUploadStore({ paseoHome });
|
|
330
332
|
this.paseoHome = paseoHome;
|
|
331
333
|
this.worktreesRoot = worktreesRoot;
|
|
332
334
|
this.sessionLogger = logger.child({
|
|
@@ -1322,6 +1324,9 @@ export class Session {
|
|
|
1322
1324
|
return this.handleProjectIconRequest(msg);
|
|
1323
1325
|
case "file_download_token_request":
|
|
1324
1326
|
return this.handleFileDownloadTokenRequest(msg);
|
|
1327
|
+
case "file.upload.request":
|
|
1328
|
+
this.handleFileUploadRequest(msg);
|
|
1329
|
+
return undefined;
|
|
1325
1330
|
default:
|
|
1326
1331
|
return undefined;
|
|
1327
1332
|
}
|
|
@@ -1419,8 +1424,12 @@ export class Session {
|
|
|
1419
1424
|
resetPeakInflight() {
|
|
1420
1425
|
this.peakInflightRequests = this.inflightRequests;
|
|
1421
1426
|
}
|
|
1422
|
-
handleBinaryFrame(
|
|
1423
|
-
|
|
1427
|
+
async handleBinaryFrame(binaryFrame) {
|
|
1428
|
+
if (binaryFrame.kind === "file_transfer") {
|
|
1429
|
+
await this.handleFileTransferFrame(binaryFrame.frame);
|
|
1430
|
+
return;
|
|
1431
|
+
}
|
|
1432
|
+
this.terminalController.handleBinaryFrame(binaryFrame.frame);
|
|
1424
1433
|
}
|
|
1425
1434
|
async handleRestartServerRequest(requestId, reason) {
|
|
1426
1435
|
const payload = {
|
|
@@ -2260,7 +2269,6 @@ export class Session {
|
|
|
2260
2269
|
logger: this.sessionLogger,
|
|
2261
2270
|
});
|
|
2262
2271
|
await this.registerWorkspaceForImportedAgent(snapshot.cwd);
|
|
2263
|
-
await this.forwardAgentUpdate(snapshot);
|
|
2264
2272
|
const agentPayload = await this.buildAgentPayload(snapshot);
|
|
2265
2273
|
this.emit({
|
|
2266
2274
|
type: "status",
|
|
@@ -4395,6 +4403,15 @@ export class Session {
|
|
|
4395
4403
|
});
|
|
4396
4404
|
}
|
|
4397
4405
|
}
|
|
4406
|
+
handleFileUploadRequest(request) {
|
|
4407
|
+
this.fileUploads.beginUpload(request);
|
|
4408
|
+
}
|
|
4409
|
+
async handleFileTransferFrame(frame) {
|
|
4410
|
+
const response = await this.fileUploads.receiveFrame(frame);
|
|
4411
|
+
if (response) {
|
|
4412
|
+
this.emit(response);
|
|
4413
|
+
}
|
|
4414
|
+
}
|
|
4398
4415
|
/**
|
|
4399
4416
|
* Handle project icon request for a given cwd
|
|
4400
4417
|
*/
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { EventEmitter } from "node:events";
|
|
2
|
+
import { Readable } from "node:stream";
|
|
2
3
|
import type pino from "pino";
|
|
3
4
|
import type { SpeechStreamResult, SpeechToTextProvider, StreamingTranscriptionSession, TextToSpeechProvider } from "../../speech-provider.js";
|
|
4
5
|
import type { TurnDetectionProvider, TurnDetectionSession } from "../../turn-detection-provider.js";
|
|
@@ -6,20 +7,24 @@ import type { LocalSpeechSessionKind, LocalSpeechTranscriptionResult, LocalSpeec
|
|
|
6
7
|
interface LocalSpeechWorkerProcess {
|
|
7
8
|
connected: boolean;
|
|
8
9
|
killed: boolean;
|
|
10
|
+
pid?: number;
|
|
11
|
+
stderr?: Readable | null;
|
|
9
12
|
send(message: LocalSpeechWorkerRequest, callback: (error: Error | null) => void): boolean;
|
|
10
13
|
disconnect(): void;
|
|
11
14
|
kill(): boolean;
|
|
12
15
|
on(event: "message", listener: (message: LocalSpeechWorkerToParentMessage) => void): this;
|
|
13
|
-
on(event: "
|
|
16
|
+
on(event: "close", listener: (code: number | null, signal: NodeJS.Signals | null) => void): this;
|
|
14
17
|
}
|
|
15
18
|
interface LocalSpeechWorkerClientOptions {
|
|
16
19
|
config: LocalSpeechWorkerConfig;
|
|
20
|
+
logger: pino.Logger;
|
|
17
21
|
requestTimeoutMs?: number;
|
|
18
22
|
idleTtlMs?: number;
|
|
19
23
|
forkWorker?: () => LocalSpeechWorkerProcess;
|
|
20
24
|
}
|
|
21
25
|
export declare class LocalSpeechWorkerClient {
|
|
22
26
|
private readonly config;
|
|
27
|
+
private readonly logger;
|
|
23
28
|
private readonly requestTimeoutMs;
|
|
24
29
|
private readonly idleTtlMs;
|
|
25
30
|
private readonly forkWorker;
|
|
@@ -27,8 +32,12 @@ export declare class LocalSpeechWorkerClient {
|
|
|
27
32
|
private readonly activeSessionIds;
|
|
28
33
|
private readonly sessionEmitters;
|
|
29
34
|
private worker;
|
|
35
|
+
private workerPid;
|
|
36
|
+
private stderrTail;
|
|
37
|
+
private stderrLineBuffer;
|
|
30
38
|
private inFlightRequests;
|
|
31
39
|
private idleTimer;
|
|
40
|
+
private readonly intentionalWorkerCloses;
|
|
32
41
|
constructor(options: LocalSpeechWorkerClientOptions);
|
|
33
42
|
synthesizeSpeech(text: string): Promise<SpeechStreamResult>;
|
|
34
43
|
transcribeVoice(audio: Buffer, format: string): Promise<LocalSpeechTranscriptionResult>;
|
|
@@ -45,10 +54,14 @@ export declare class LocalSpeechWorkerClient {
|
|
|
45
54
|
shutdown(): void;
|
|
46
55
|
private sendRequest;
|
|
47
56
|
private ensureWorker;
|
|
57
|
+
private handleWorkerStderr;
|
|
48
58
|
private handleWorkerMessage;
|
|
49
59
|
private handleWorkerExit;
|
|
60
|
+
private describePendingRequests;
|
|
61
|
+
private getStderrTail;
|
|
50
62
|
private rejectAllPending;
|
|
51
63
|
private emitSessionError;
|
|
64
|
+
private emitErrorIfObserved;
|
|
52
65
|
private scheduleIdleShutdownIfReady;
|
|
53
66
|
private clearIdleTimer;
|
|
54
67
|
}
|
|
@@ -8,6 +8,8 @@ import { bufferToWorkerBytes, workerBytesToBuffer } from "./worker-bytes.js";
|
|
|
8
8
|
const DEFAULT_REQUEST_TIMEOUT_MS = 30000;
|
|
9
9
|
const DEFAULT_IDLE_TTL_MS = 5 * 60 * 1000;
|
|
10
10
|
const DEFAULT_LOCAL_SAMPLE_RATE = 16000;
|
|
11
|
+
const STDERR_TAIL_MAX_CHARS = 8000;
|
|
12
|
+
const USER_ERROR_STDERR_MAX_CHARS = 1000;
|
|
11
13
|
function resolveWorkerUrl() {
|
|
12
14
|
const currentUrl = import.meta.url;
|
|
13
15
|
if (currentUrl.endsWith(".ts")) {
|
|
@@ -38,21 +40,81 @@ function forkLocalSpeechWorker() {
|
|
|
38
40
|
env,
|
|
39
41
|
execArgv: resolveWorkerExecArgv(),
|
|
40
42
|
serialization: "advanced",
|
|
41
|
-
stdio: ["ignore", "ignore", "
|
|
43
|
+
stdio: ["ignore", "ignore", "pipe", "ipc"],
|
|
42
44
|
});
|
|
43
45
|
}
|
|
44
46
|
function isResponse(message) {
|
|
45
47
|
return message.type === "response";
|
|
46
48
|
}
|
|
49
|
+
function truncateStart(value, maxChars) {
|
|
50
|
+
if (value.length <= maxChars) {
|
|
51
|
+
return value;
|
|
52
|
+
}
|
|
53
|
+
return value.slice(value.length - maxChars);
|
|
54
|
+
}
|
|
55
|
+
function summarizeWorkerRequest(message) {
|
|
56
|
+
switch (message.type) {
|
|
57
|
+
case "session.create":
|
|
58
|
+
return { kind: message.kind, sessionId: message.sessionId };
|
|
59
|
+
case "session.append":
|
|
60
|
+
return { sessionId: message.sessionId, audioBytes: message.audio.byteLength };
|
|
61
|
+
case "session.commit":
|
|
62
|
+
case "session.clear":
|
|
63
|
+
case "session.flush":
|
|
64
|
+
case "session.reset":
|
|
65
|
+
case "session.close":
|
|
66
|
+
return { sessionId: message.sessionId };
|
|
67
|
+
case "stt.transcribe":
|
|
68
|
+
return {
|
|
69
|
+
model: message.model,
|
|
70
|
+
audioBytes: message.audio.byteLength,
|
|
71
|
+
format: message.format,
|
|
72
|
+
};
|
|
73
|
+
case "tts.synthesize":
|
|
74
|
+
return { textLength: message.text.length };
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function formatExitStatus(code, signal) {
|
|
78
|
+
const parts = [];
|
|
79
|
+
if (code !== null) {
|
|
80
|
+
parts.push(`code ${code}`);
|
|
81
|
+
}
|
|
82
|
+
if (signal) {
|
|
83
|
+
parts.push(`signal ${signal}`);
|
|
84
|
+
}
|
|
85
|
+
return parts.length > 0 ? parts.join(", ") : "unknown exit status";
|
|
86
|
+
}
|
|
87
|
+
function formatPendingRequestForMessage(request) {
|
|
88
|
+
const type = typeof request.type === "string" ? request.type : "unknown request";
|
|
89
|
+
const kind = typeof request.kind === "string" ? ` (${request.kind})` : "";
|
|
90
|
+
return `${type}${kind}`;
|
|
91
|
+
}
|
|
92
|
+
function buildWorkerExitMessage(params) {
|
|
93
|
+
const pending = params.pendingRequests.length > 0
|
|
94
|
+
? ` while handling ${params.pendingRequests
|
|
95
|
+
.slice(0, 3)
|
|
96
|
+
.map(formatPendingRequestForMessage)
|
|
97
|
+
.join(", ")}`
|
|
98
|
+
: "";
|
|
99
|
+
const stderr = params.stderrTail
|
|
100
|
+
? ` Last stderr: ${truncateStart(params.stderrTail, USER_ERROR_STDERR_MAX_CHARS)}`
|
|
101
|
+
: " Check daemon.log and macOS DiagnosticReports for Paseo Voice crash details.";
|
|
102
|
+
return `Local speech worker exited (${formatExitStatus(params.code, params.signal)})${pending}.${stderr}`;
|
|
103
|
+
}
|
|
47
104
|
export class LocalSpeechWorkerClient {
|
|
48
105
|
constructor(options) {
|
|
49
106
|
this.pendingRequests = new Map();
|
|
50
107
|
this.activeSessionIds = new Set();
|
|
51
108
|
this.sessionEmitters = new Map();
|
|
52
109
|
this.worker = null;
|
|
110
|
+
this.workerPid = null;
|
|
111
|
+
this.stderrTail = "";
|
|
112
|
+
this.stderrLineBuffer = "";
|
|
53
113
|
this.inFlightRequests = 0;
|
|
54
114
|
this.idleTimer = null;
|
|
115
|
+
this.intentionalWorkerCloses = new WeakSet();
|
|
55
116
|
this.config = options.config;
|
|
117
|
+
this.logger = options.logger.child({ component: "local-speech-worker-client" });
|
|
56
118
|
this.requestTimeoutMs = options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
57
119
|
this.idleTtlMs = options.idleTtlMs ?? DEFAULT_IDLE_TTL_MS;
|
|
58
120
|
this.forkWorker = options.forkWorker ?? forkLocalSpeechWorker;
|
|
@@ -141,7 +203,9 @@ export class LocalSpeechWorkerClient {
|
|
|
141
203
|
this.sessionEmitters.clear();
|
|
142
204
|
const worker = this.worker;
|
|
143
205
|
this.worker = null;
|
|
206
|
+
this.workerPid = null;
|
|
144
207
|
if (worker && !worker.killed) {
|
|
208
|
+
this.intentionalWorkerCloses.add(worker);
|
|
145
209
|
try {
|
|
146
210
|
worker.disconnect();
|
|
147
211
|
}
|
|
@@ -160,6 +224,7 @@ export class LocalSpeechWorkerClient {
|
|
|
160
224
|
const worker = this.ensureWorker();
|
|
161
225
|
const requestId = randomUUID();
|
|
162
226
|
const message = { ...input, requestId };
|
|
227
|
+
const requestSummary = summarizeWorkerRequest(message);
|
|
163
228
|
this.inFlightRequests++;
|
|
164
229
|
this.clearIdleTimer();
|
|
165
230
|
return new Promise((resolve, reject) => {
|
|
@@ -173,6 +238,9 @@ export class LocalSpeechWorkerClient {
|
|
|
173
238
|
resolve: (value) => resolve(value),
|
|
174
239
|
reject,
|
|
175
240
|
timeout,
|
|
241
|
+
type: message.type,
|
|
242
|
+
summary: requestSummary,
|
|
243
|
+
startedAt: Date.now(),
|
|
176
244
|
});
|
|
177
245
|
worker.send(message, (error) => {
|
|
178
246
|
if (!error) {
|
|
@@ -196,10 +264,35 @@ export class LocalSpeechWorkerClient {
|
|
|
196
264
|
}
|
|
197
265
|
const worker = this.forkWorker();
|
|
198
266
|
this.worker = worker;
|
|
267
|
+
this.workerPid = worker.pid ?? null;
|
|
268
|
+
this.stderrTail = "";
|
|
269
|
+
this.stderrLineBuffer = "";
|
|
270
|
+
this.logger.info({
|
|
271
|
+
workerPid: this.workerPid,
|
|
272
|
+
modelsDir: this.config.modelsDir,
|
|
273
|
+
voiceSttModel: this.config.voiceSttModel,
|
|
274
|
+
dictationSttModel: this.config.dictationSttModel,
|
|
275
|
+
voiceTtsModel: this.config.voiceTtsModel,
|
|
276
|
+
}, "Local speech worker spawned");
|
|
277
|
+
worker.stderr?.on("data", (chunk) => this.handleWorkerStderr(chunk));
|
|
199
278
|
worker.on("message", (message) => this.handleWorkerMessage(message));
|
|
200
|
-
worker.on("
|
|
279
|
+
worker.on("close", (code, signal) => this.handleWorkerExit(worker, code, signal));
|
|
201
280
|
return worker;
|
|
202
281
|
}
|
|
282
|
+
handleWorkerStderr(chunk) {
|
|
283
|
+
const text = Buffer.isBuffer(chunk) ? chunk.toString("utf8") : chunk;
|
|
284
|
+
this.stderrTail = truncateStart(this.stderrTail + text, STDERR_TAIL_MAX_CHARS);
|
|
285
|
+
this.stderrLineBuffer += text;
|
|
286
|
+
const lines = this.stderrLineBuffer.split(/\r?\n/);
|
|
287
|
+
this.stderrLineBuffer = lines.pop() ?? "";
|
|
288
|
+
for (const line of lines) {
|
|
289
|
+
const trimmed = line.trim();
|
|
290
|
+
if (!trimmed) {
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
this.logger.warn({ workerPid: this.workerPid, stderr: trimmed }, "Local speech worker stderr");
|
|
294
|
+
}
|
|
295
|
+
}
|
|
203
296
|
handleWorkerMessage(message) {
|
|
204
297
|
if (isResponse(message)) {
|
|
205
298
|
const pending = this.pendingRequests.get(message.requestId);
|
|
@@ -240,19 +333,81 @@ export class LocalSpeechWorkerClient {
|
|
|
240
333
|
return;
|
|
241
334
|
}
|
|
242
335
|
}
|
|
243
|
-
handleWorkerExit() {
|
|
244
|
-
this.worker
|
|
336
|
+
handleWorkerExit(worker, code, signal) {
|
|
337
|
+
const wasCurrentWorker = this.worker === worker;
|
|
338
|
+
const wasIntentionalClose = this.intentionalWorkerCloses.has(worker);
|
|
339
|
+
this.intentionalWorkerCloses.delete(worker);
|
|
340
|
+
const workerPid = worker.pid ?? (wasCurrentWorker ? this.workerPid : null);
|
|
341
|
+
const pendingRequests = this.describePendingRequests();
|
|
342
|
+
const activeSessionCount = this.activeSessionIds.size;
|
|
343
|
+
const stderrTail = this.getStderrTail();
|
|
344
|
+
if (wasIntentionalClose) {
|
|
345
|
+
this.logger.info({
|
|
346
|
+
workerPid,
|
|
347
|
+
code,
|
|
348
|
+
signal,
|
|
349
|
+
pendingRequests,
|
|
350
|
+
activeSessionCount,
|
|
351
|
+
stderrTail: stderrTail || null,
|
|
352
|
+
}, "Local speech worker closed after shutdown");
|
|
353
|
+
if (wasCurrentWorker) {
|
|
354
|
+
this.worker = null;
|
|
355
|
+
this.workerPid = null;
|
|
356
|
+
}
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
if (!wasCurrentWorker) {
|
|
360
|
+
this.logger.warn({
|
|
361
|
+
workerPid,
|
|
362
|
+
code,
|
|
363
|
+
signal,
|
|
364
|
+
pendingRequests,
|
|
365
|
+
activeSessionCount,
|
|
366
|
+
stderrTail: stderrTail || null,
|
|
367
|
+
}, "Stale local speech worker closed");
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
const error = new Error(buildWorkerExitMessage({
|
|
371
|
+
code,
|
|
372
|
+
signal,
|
|
373
|
+
pendingRequests,
|
|
374
|
+
stderrTail,
|
|
375
|
+
}));
|
|
376
|
+
this.logger.error({
|
|
377
|
+
err: error,
|
|
378
|
+
workerPid,
|
|
379
|
+
code,
|
|
380
|
+
signal,
|
|
381
|
+
pendingRequests,
|
|
382
|
+
activeSessionCount,
|
|
383
|
+
stderrTail: stderrTail || null,
|
|
384
|
+
}, "Local speech worker exited");
|
|
385
|
+
if (wasCurrentWorker) {
|
|
386
|
+
this.worker = null;
|
|
387
|
+
this.workerPid = null;
|
|
388
|
+
}
|
|
245
389
|
this.clearIdleTimer();
|
|
246
|
-
this.rejectAllPending(
|
|
390
|
+
this.rejectAllPending(error);
|
|
247
391
|
for (const [sessionId, emitter] of this.sessionEmitters) {
|
|
248
392
|
if (this.activeSessionIds.has(sessionId)) {
|
|
249
|
-
|
|
393
|
+
this.emitErrorIfObserved(sessionId, emitter, error, workerPid);
|
|
250
394
|
}
|
|
251
395
|
}
|
|
252
396
|
this.activeSessionIds.clear();
|
|
253
397
|
this.sessionEmitters.clear();
|
|
254
398
|
this.inFlightRequests = 0;
|
|
255
399
|
}
|
|
400
|
+
describePendingRequests() {
|
|
401
|
+
const now = Date.now();
|
|
402
|
+
return Array.from(this.pendingRequests.entries()).map(([requestId, request]) => Object.assign({
|
|
403
|
+
requestId,
|
|
404
|
+
type: request.type,
|
|
405
|
+
ageMs: Math.max(0, now - request.startedAt),
|
|
406
|
+
}, request.summary));
|
|
407
|
+
}
|
|
408
|
+
getStderrTail() {
|
|
409
|
+
return truncateStart(this.stderrTail.trim(), STDERR_TAIL_MAX_CHARS);
|
|
410
|
+
}
|
|
256
411
|
rejectAllPending(error) {
|
|
257
412
|
for (const [requestId, pending] of this.pendingRequests) {
|
|
258
413
|
clearTimeout(pending.timeout);
|
|
@@ -265,7 +420,14 @@ export class LocalSpeechWorkerClient {
|
|
|
265
420
|
if (!emitter) {
|
|
266
421
|
return;
|
|
267
422
|
}
|
|
268
|
-
|
|
423
|
+
this.emitErrorIfObserved(sessionId, emitter, error instanceof Error ? error : new Error(String(error)));
|
|
424
|
+
}
|
|
425
|
+
emitErrorIfObserved(sessionId, emitter, error, workerPid = this.workerPid) {
|
|
426
|
+
if (emitter.listenerCount("error") > 0) {
|
|
427
|
+
emitter.emit("error", error);
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
this.logger.warn({ err: error, workerPid, sessionId }, "Local speech worker session error had no listener");
|
|
269
431
|
}
|
|
270
432
|
scheduleIdleShutdownIfReady() {
|
|
271
433
|
if (!this.worker || this.inFlightRequests > 0 || this.activeSessionIds.size > 0) {
|
|
@@ -51,6 +51,7 @@ export declare class VoiceAssistantWebSocketServer {
|
|
|
51
51
|
private readonly pendingConnections;
|
|
52
52
|
private readonly sessions;
|
|
53
53
|
private readonly externalSessionsByKey;
|
|
54
|
+
private readonly socketMessageQueues;
|
|
54
55
|
private readonly serverId;
|
|
55
56
|
private readonly daemonVersion;
|
|
56
57
|
private readonly daemonRuntimeConfig;
|
|
@@ -129,6 +130,7 @@ export declare class VoiceAssistantWebSocketServer {
|
|
|
129
130
|
private broadcastCapabilitiesUpdate;
|
|
130
131
|
private broadcastDaemonConfigChanged;
|
|
131
132
|
private bindSocketHandlers;
|
|
133
|
+
private enqueueRawMessage;
|
|
132
134
|
resolveVoiceSpeakHandler(callerAgentId: string): VoiceSpeakHandler | null;
|
|
133
135
|
resolveVoiceCallerContext(callerAgentId: string): VoiceCallerContext | null;
|
|
134
136
|
private detachSocket;
|
|
@@ -2,7 +2,7 @@ import { WebSocketServer } from "ws";
|
|
|
2
2
|
import { basename, join } from "path";
|
|
3
3
|
import { hostname as getHostname } from "node:os";
|
|
4
4
|
import { WSInboundMessageSchema, wrapSessionMessage, } from "./messages.js";
|
|
5
|
-
import { asUint8Array,
|
|
5
|
+
import { asUint8Array, decodeBinaryFrame } from "@getpaseo/protocol/binary-frames/index";
|
|
6
6
|
import { isHostnameAllowed } from "./hostnames.js";
|
|
7
7
|
import { Session } from "./session.js";
|
|
8
8
|
import { buildWorkspaceGitMetadataFromSnapshot } from "./workspace-git-metadata.js";
|
|
@@ -206,6 +206,7 @@ export class VoiceAssistantWebSocketServer {
|
|
|
206
206
|
this.pendingConnections = new Map();
|
|
207
207
|
this.sessions = new Map();
|
|
208
208
|
this.externalSessionsByKey = new Map();
|
|
209
|
+
this.socketMessageQueues = new Map();
|
|
209
210
|
this.voiceSpeakHandlers = new Map();
|
|
210
211
|
this.voiceCallerContexts = new Map();
|
|
211
212
|
this.workspaceSetupSnapshots = new Map();
|
|
@@ -756,7 +757,7 @@ export class VoiceAssistantWebSocketServer {
|
|
|
756
757
|
bindSocketHandlers(ws) {
|
|
757
758
|
ws.on("message", (...args) => {
|
|
758
759
|
const data = args[0];
|
|
759
|
-
|
|
760
|
+
this.enqueueRawMessage(ws, data);
|
|
760
761
|
});
|
|
761
762
|
ws.on("close", async (...args) => {
|
|
762
763
|
const code = args[0];
|
|
@@ -776,6 +777,18 @@ export class VoiceAssistantWebSocketServer {
|
|
|
776
777
|
await this.detachSocket(ws, { error: err });
|
|
777
778
|
});
|
|
778
779
|
}
|
|
780
|
+
enqueueRawMessage(ws, data) {
|
|
781
|
+
const previous = this.socketMessageQueues.get(ws) ?? Promise.resolve();
|
|
782
|
+
const next = previous.then(() => this.handleRawMessage(ws, data), () => this.handleRawMessage(ws, data));
|
|
783
|
+
this.socketMessageQueues.set(ws, next);
|
|
784
|
+
void next
|
|
785
|
+
.catch(() => undefined)
|
|
786
|
+
.finally(() => {
|
|
787
|
+
if (this.socketMessageQueues.get(ws) === next) {
|
|
788
|
+
this.socketMessageQueues.delete(ws);
|
|
789
|
+
}
|
|
790
|
+
});
|
|
791
|
+
}
|
|
779
792
|
resolveVoiceSpeakHandler(callerAgentId) {
|
|
780
793
|
return this.voiceSpeakHandlers.get(callerAgentId) ?? null;
|
|
781
794
|
}
|
|
@@ -897,14 +910,14 @@ export class VoiceAssistantWebSocketServer {
|
|
|
897
910
|
},
|
|
898
911
|
}));
|
|
899
912
|
}
|
|
900
|
-
maybeHandleBinaryFrame(params) {
|
|
913
|
+
async maybeHandleBinaryFrame(params) {
|
|
901
914
|
const { ws, buffer, activeConnection, log } = params;
|
|
902
915
|
const asBytes = asUint8Array(buffer);
|
|
903
916
|
if (!asBytes) {
|
|
904
917
|
return false;
|
|
905
918
|
}
|
|
906
|
-
const
|
|
907
|
-
if (!
|
|
919
|
+
const decodedFrame = decodeBinaryFrame(asBytes);
|
|
920
|
+
if (!decodedFrame) {
|
|
908
921
|
return false;
|
|
909
922
|
}
|
|
910
923
|
if (!activeConnection) {
|
|
@@ -919,7 +932,7 @@ export class VoiceAssistantWebSocketServer {
|
|
|
919
932
|
}
|
|
920
933
|
return true;
|
|
921
934
|
}
|
|
922
|
-
activeConnection.session.handleBinaryFrame(
|
|
935
|
+
await activeConnection.session.handleBinaryFrame(decodedFrame);
|
|
923
936
|
return true;
|
|
924
937
|
}
|
|
925
938
|
handlePendingConnectionMessage(params) {
|
|
@@ -950,7 +963,7 @@ export class VoiceAssistantWebSocketServer {
|
|
|
950
963
|
const log = activeConnection?.connectionLogger ?? pendingConnection?.connectionLogger ?? this.logger;
|
|
951
964
|
try {
|
|
952
965
|
const buffer = bufferFromWsData(data);
|
|
953
|
-
const binaryHandled = this.maybeHandleBinaryFrame({
|
|
966
|
+
const binaryHandled = await this.maybeHandleBinaryFrame({
|
|
954
967
|
ws,
|
|
955
968
|
buffer,
|
|
956
969
|
activeConnection,
|
|
@@ -16,8 +16,8 @@ declare const PersistedProjectRecordSchema: z.ZodObject<{
|
|
|
16
16
|
archivedAt: string | null;
|
|
17
17
|
kind: "git" | "non_git";
|
|
18
18
|
projectId: string;
|
|
19
|
-
rootPath: string;
|
|
20
19
|
displayName: string;
|
|
20
|
+
rootPath: string;
|
|
21
21
|
customName: string | null;
|
|
22
22
|
}, {
|
|
23
23
|
createdAt: string;
|
|
@@ -25,8 +25,8 @@ declare const PersistedProjectRecordSchema: z.ZodObject<{
|
|
|
25
25
|
archivedAt: string | null;
|
|
26
26
|
kind: "git" | "non_git";
|
|
27
27
|
projectId: string;
|
|
28
|
-
rootPath: string;
|
|
29
28
|
displayName: string;
|
|
29
|
+
rootPath: string;
|
|
30
30
|
customName?: string | null | undefined;
|
|
31
31
|
}>;
|
|
32
32
|
declare const PersistedWorkspaceRecordSchema: z.ZodObject<{
|
|
@@ -44,7 +44,7 @@ declare const PersistedWorkspaceRecordSchema: z.ZodObject<{
|
|
|
44
44
|
createdAt: string;
|
|
45
45
|
updatedAt: string;
|
|
46
46
|
archivedAt: string | null;
|
|
47
|
-
kind: "
|
|
47
|
+
kind: "local_checkout" | "worktree" | "directory";
|
|
48
48
|
projectId: string;
|
|
49
49
|
displayName: string;
|
|
50
50
|
}, {
|
|
@@ -53,7 +53,7 @@ declare const PersistedWorkspaceRecordSchema: z.ZodObject<{
|
|
|
53
53
|
createdAt: string;
|
|
54
54
|
updatedAt: string;
|
|
55
55
|
archivedAt: string | null;
|
|
56
|
-
kind: "
|
|
56
|
+
kind: "local_checkout" | "worktree" | "directory";
|
|
57
57
|
projectId: string;
|
|
58
58
|
displayName: string;
|
|
59
59
|
}>;
|
|
@@ -129,7 +129,7 @@ const AgentMetadataGenerationSchema = z
|
|
|
129
129
|
providers: z.array(StructuredGenerationProviderConfigSchema).optional(),
|
|
130
130
|
})
|
|
131
131
|
.strict();
|
|
132
|
-
const BUILTIN_PROVIDER_IDS = ["claude", "codex", "copilot", "opencode", "pi"];
|
|
132
|
+
const BUILTIN_PROVIDER_IDS = ["claude", "codex", "copilot", "opencode", "pi", "omp"];
|
|
133
133
|
function isLegacyProviderEntry(value) {
|
|
134
134
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
135
135
|
return false;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@getpaseo/server",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.92",
|
|
4
4
|
"description": "Paseo backend server",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist/server",
|
|
@@ -59,10 +59,10 @@
|
|
|
59
59
|
"dependencies": {
|
|
60
60
|
"@agentclientprotocol/sdk": "^0.17.1",
|
|
61
61
|
"@anthropic-ai/claude-agent-sdk": "^0.2.133",
|
|
62
|
-
"@getpaseo/client": "0.1.
|
|
63
|
-
"@getpaseo/highlight": "0.1.
|
|
64
|
-
"@getpaseo/protocol": "0.1.
|
|
65
|
-
"@getpaseo/relay": "0.1.
|
|
62
|
+
"@getpaseo/client": "0.1.92",
|
|
63
|
+
"@getpaseo/highlight": "0.1.92",
|
|
64
|
+
"@getpaseo/protocol": "0.1.92",
|
|
65
|
+
"@getpaseo/relay": "0.1.92",
|
|
66
66
|
"@isaacs/ttlcache": "^2.1.4",
|
|
67
67
|
"@modelcontextprotocol/sdk": "^1.20.1",
|
|
68
68
|
"@opencode-ai/sdk": "1.14.46",
|