@getpaseo/server 0.1.91-beta.1 → 0.1.91
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 +1 -1
- package/dist/server/server/agent/agent-manager.js +23 -48
- package/dist/server/server/agent/agent-sdk-types.d.ts +15 -0
- 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 +22 -4
- package/dist/server/server/agent/provider-snapshot-manager.js +19 -1
- package/dist/server/server/agent/providers/claude/agent.d.ts +5 -2
- package/dist/server/server/agent/providers/claude/agent.js +6 -2
- package/dist/server/server/agent/providers/claude/models.d.ts +1 -1
- package/dist/server/server/agent/providers/claude/models.js +6 -6
- package/dist/server/server/agent/providers/codex-app-server-agent.js +9 -5
- package/dist/server/server/agent/providers/opencode/test-utils/test-opencode-runtime.d.ts +1 -0
- package/dist/server/server/agent/providers/opencode/test-utils/test-opencode-runtime.js +4 -0
- package/dist/server/server/agent/providers/opencode-agent.d.ts +16 -2
- package/dist/server/server/agent/providers/opencode-agent.js +75 -4
- package/dist/server/server/agent/providers/pi/agent.d.ts +23 -1
- package/dist/server/server/agent/providers/pi/agent.js +219 -13
- 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 +12 -0
- package/dist/server/server/agent/providers/pi/session-descriptor.js +304 -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/auth.js +16 -1
- 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 +11 -0
- package/dist/server/server/persisted-config.js +2 -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 -2
- 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/server/utils/directory-suggestions.js +10 -5
- package/dist/server/utils/worktree.d.ts +4 -0
- package/dist/server/utils/worktree.js +19 -2
- package/dist/src/server/persisted-config.js +2 -1
- package/package.json +5 -5
|
@@ -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
|
}>;
|
|
@@ -12,8 +12,11 @@ const NO_SEGMENT_INDEX = Number.MAX_SAFE_INTEGER;
|
|
|
12
12
|
const NO_MATCH_OFFSET = Number.MAX_SAFE_INTEGER;
|
|
13
13
|
const NO_FUZZY_SCORE = Number.MAX_SAFE_INTEGER;
|
|
14
14
|
const NO_WORKSPACE_MATCH_TIER = 5;
|
|
15
|
-
const
|
|
15
|
+
const IGNORED_SUGGESTION_DIRECTORY_NAMES = new Set([
|
|
16
16
|
"node_modules",
|
|
17
|
+
"venv",
|
|
18
|
+
"env",
|
|
19
|
+
"virtualenv",
|
|
17
20
|
"dist",
|
|
18
21
|
"build",
|
|
19
22
|
"target",
|
|
@@ -589,7 +592,9 @@ async function listChildDirectories(input) {
|
|
|
589
592
|
return cached.entries;
|
|
590
593
|
}
|
|
591
594
|
const dirents = await readdir(input.directory, { withFileTypes: true }).catch(() => []);
|
|
592
|
-
const candidates = dirents.filter((dirent) => !isHiddenDirectoryName(dirent.name) &&
|
|
595
|
+
const candidates = dirents.filter((dirent) => !isHiddenDirectoryName(dirent.name) &&
|
|
596
|
+
!isIgnoredSuggestionDirectoryName(dirent.name) &&
|
|
597
|
+
(dirent.isDirectory() || dirent.isSymbolicLink()));
|
|
593
598
|
const resolved = await Promise.all(candidates.map(async (dirent) => {
|
|
594
599
|
const candidatePath = path.join(input.directory, dirent.name);
|
|
595
600
|
const absolutePath = await resolveDirectoryCandidate({
|
|
@@ -613,7 +618,7 @@ async function listWorkspaceChildEntries(input) {
|
|
|
613
618
|
return cached.entries;
|
|
614
619
|
}
|
|
615
620
|
const dirents = await readdir(input.directory, { withFileTypes: true }).catch(() => []);
|
|
616
|
-
const candidates = dirents.filter((dirent) => !isHiddenDirectoryName(dirent.name) && !
|
|
621
|
+
const candidates = dirents.filter((dirent) => !isHiddenDirectoryName(dirent.name) && !isIgnoredSuggestionDirectoryName(dirent.name));
|
|
617
622
|
const resolved = await Promise.all(candidates.map(async (dirent) => {
|
|
618
623
|
const candidatePath = path.join(input.directory, dirent.name);
|
|
619
624
|
const entry = await resolveWorkspaceCandidate({
|
|
@@ -682,8 +687,8 @@ async function resolveWorkspaceCandidate(input) {
|
|
|
682
687
|
function isHiddenDirectoryName(name) {
|
|
683
688
|
return name.startsWith(".");
|
|
684
689
|
}
|
|
685
|
-
function
|
|
686
|
-
return
|
|
690
|
+
function isIgnoredSuggestionDirectoryName(name) {
|
|
691
|
+
return IGNORED_SUGGESTION_DIRECTORY_NAMES.has(name);
|
|
687
692
|
}
|
|
688
693
|
function setDirectoryListCache(cacheKey, entry) {
|
|
689
694
|
directoryListCache.set(cacheKey, entry);
|
|
@@ -128,6 +128,10 @@ export declare class UnknownBranchError extends Error {
|
|
|
128
128
|
cwd: string;
|
|
129
129
|
});
|
|
130
130
|
}
|
|
131
|
+
export declare class InvalidGitBranchNameError extends Error {
|
|
132
|
+
readonly branchName: string;
|
|
133
|
+
constructor(branchName: string);
|
|
134
|
+
}
|
|
131
135
|
export type ReadPaseoConfigResult = {
|
|
132
136
|
ok: true;
|
|
133
137
|
config: PaseoConfig | null;
|
|
@@ -55,6 +55,13 @@ export class UnknownBranchError extends Error {
|
|
|
55
55
|
this.cwd = params.cwd;
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
|
+
export class InvalidGitBranchNameError extends Error {
|
|
59
|
+
constructor(branchName) {
|
|
60
|
+
super(`Invalid branch name: Git rejected ref name '${branchName}'`);
|
|
61
|
+
this.name = "InvalidGitBranchNameError";
|
|
62
|
+
this.branchName = branchName;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
58
65
|
export function readPaseoConfig(repoRoot) {
|
|
59
66
|
try {
|
|
60
67
|
const json = readPaseoConfigJson(repoRoot);
|
|
@@ -898,7 +905,7 @@ async function resolveWorktreeSourcePlan({ cwd, source, desiredSlug, }) {
|
|
|
898
905
|
};
|
|
899
906
|
}
|
|
900
907
|
case "checkout-branch": {
|
|
901
|
-
|
|
908
|
+
await validateExistingWorktreeBranchName(cwd, source.branchName);
|
|
902
909
|
if (!(await localBranchExists(cwd, source.branchName))) {
|
|
903
910
|
try {
|
|
904
911
|
await runGitCommand(["fetch", "origin", `${source.branchName}:${source.branchName}`], {
|
|
@@ -921,7 +928,7 @@ async function resolveWorktreeSourcePlan({ cwd, source, desiredSlug, }) {
|
|
|
921
928
|
}
|
|
922
929
|
case "checkout-github-pr": {
|
|
923
930
|
const localBranchCandidate = source.localBranchName ?? source.headRef;
|
|
924
|
-
|
|
931
|
+
await validateExistingWorktreeBranchName(cwd, localBranchCandidate);
|
|
925
932
|
const localBranchName = await resolveUniqueLocalBranchName(cwd, localBranchCandidate);
|
|
926
933
|
const normalizedBaseRefName = normalizeRequiredBaseBranch(source.baseRefName);
|
|
927
934
|
await runGitCommand([
|
|
@@ -966,6 +973,16 @@ function validateWorktreeBranchName(branchName) {
|
|
|
966
973
|
throw new Error(`Invalid branch name: ${validation.error}`);
|
|
967
974
|
}
|
|
968
975
|
}
|
|
976
|
+
async function validateExistingWorktreeBranchName(cwd, branchName) {
|
|
977
|
+
const result = await runGitCommand(["check-ref-format", "--branch", branchName], {
|
|
978
|
+
cwd,
|
|
979
|
+
timeout: 30000,
|
|
980
|
+
acceptExitCodes: [0, 1, 128],
|
|
981
|
+
});
|
|
982
|
+
if (result.exitCode !== 0) {
|
|
983
|
+
throw new InvalidGitBranchNameError(branchName);
|
|
984
|
+
}
|
|
985
|
+
}
|
|
969
986
|
function normalizeRequiredBaseBranch(baseBranch) {
|
|
970
987
|
const normalizedBaseBranch = normalizeBaseRefName(baseBranch);
|
|
971
988
|
if (!normalizedBaseBranch) {
|
|
@@ -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;
|
|
@@ -169,6 +169,7 @@ function normalizeAgentProviders(value) {
|
|
|
169
169
|
}
|
|
170
170
|
export const PersistedConfigSchema = z
|
|
171
171
|
.object({
|
|
172
|
+
$schema: z.string().optional(),
|
|
172
173
|
// v1 schema marker
|
|
173
174
|
version: z.literal(1).optional(),
|
|
174
175
|
// v1 config layout
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@getpaseo/server",
|
|
3
|
-
"version": "0.1.91
|
|
3
|
+
"version": "0.1.91",
|
|
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.91
|
|
63
|
-
"@getpaseo/highlight": "0.1.91
|
|
64
|
-
"@getpaseo/protocol": "0.1.91
|
|
65
|
-
"@getpaseo/relay": "0.1.91
|
|
62
|
+
"@getpaseo/client": "0.1.91",
|
|
63
|
+
"@getpaseo/highlight": "0.1.91",
|
|
64
|
+
"@getpaseo/protocol": "0.1.91",
|
|
65
|
+
"@getpaseo/relay": "0.1.91",
|
|
66
66
|
"@isaacs/ttlcache": "^2.1.4",
|
|
67
67
|
"@modelcontextprotocol/sdk": "^1.20.1",
|
|
68
68
|
"@opencode-ai/sdk": "1.14.46",
|