@getpaseo/server 0.1.95 → 0.1.97-beta.1
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/server/{utils/executable.d.ts → executable-resolution/executable-resolution.d.ts} +2 -2
- package/dist/server/{utils/executable.js → executable-resolution/executable-resolution.js} +16 -14
- package/dist/server/executable-resolution/windows.d.ts +18 -0
- package/dist/server/executable-resolution/windows.js +62 -0
- package/dist/server/server/agent/agent-loading.js +4 -1
- package/dist/server/server/agent/agent-manager.d.ts +10 -2
- package/dist/server/server/agent/agent-manager.js +34 -46
- package/dist/server/server/agent/agent-projections.js +3 -0
- package/dist/server/server/agent/agent-prompt.js +19 -1
- package/dist/server/server/agent/agent-response-loop.js +2 -4
- package/dist/server/server/agent/agent-storage.d.ts +18 -19
- package/dist/server/server/agent/agent-storage.js +6 -23
- package/dist/server/server/agent/create-agent/create.d.ts +2 -12
- package/dist/server/server/agent/create-agent/create.js +28 -30
- package/dist/server/server/agent/create-agent-lifecycle-dispatch.d.ts +4 -2
- package/dist/server/server/agent/create-agent-lifecycle-dispatch.js +31 -22
- package/dist/server/server/agent/import-sessions.d.ts +1 -10
- package/dist/server/server/agent/import-sessions.js +1 -53
- package/dist/server/server/agent/lifecycle-command.js +5 -4
- package/dist/server/server/agent/mcp-server.d.ts +8 -5
- package/dist/server/server/agent/mcp-server.js +41 -14
- package/dist/server/server/agent/mcp-shared.d.ts +6 -3
- package/dist/server/server/agent/mcp-shared.js +3 -0
- package/dist/server/server/agent/provider-launch-config.js +1 -1
- package/dist/server/server/agent/providers/acp-agent.d.ts +5 -0
- package/dist/server/server/agent/providers/acp-agent.js +31 -26
- package/dist/server/server/agent/providers/claude/agent.js +45 -6
- package/dist/server/server/agent/providers/codex-app-server-agent.js +1 -1
- package/dist/server/server/agent/providers/copilot-acp-agent.js +1 -0
- package/dist/server/server/agent/providers/cursor-acp-agent.d.ts +0 -7
- package/dist/server/server/agent/providers/cursor-acp-agent.js +0 -78
- package/dist/server/server/agent/providers/mock-load-test-agent.d.ts +2 -0
- package/dist/server/server/agent/providers/mock-load-test-agent.js +73 -1
- package/dist/server/server/agent/providers/opencode/server-manager.js +1 -1
- package/dist/server/server/agent/structured-generation-providers.js +45 -1
- package/dist/server/server/agent-attention-policy.d.ts +12 -3
- package/dist/server/server/agent-attention-policy.js +15 -3
- package/dist/server/server/auto-archive-on-merge/archive-if-safe.d.ts +7 -6
- package/dist/server/server/auto-archive-on-merge/archive-if-safe.js +21 -16
- package/dist/server/server/bootstrap.d.ts +3 -0
- package/dist/server/server/bootstrap.js +91 -12
- package/dist/server/server/config.js +1 -0
- package/dist/server/server/daemon-config-store.js +1 -0
- package/dist/server/server/exports.d.ts +1 -1
- package/dist/server/server/exports.js +1 -1
- package/dist/server/server/loop-service.d.ts +24 -24
- package/dist/server/server/migrations/backfill-workspace-id.migration.d.ts +9 -0
- package/dist/server/server/migrations/backfill-workspace-id.migration.js +60 -0
- package/dist/server/server/paseo-worktree-service.d.ts +9 -0
- package/dist/server/server/paseo-worktree-service.js +71 -12
- package/dist/server/server/path-utils.d.ts +1 -0
- package/dist/server/server/path-utils.js +6 -1
- package/dist/server/server/persisted-config.d.ts +7 -0
- package/dist/server/server/persisted-config.js +1 -0
- package/dist/server/server/persistence-hooks.d.ts +1 -0
- package/dist/server/server/persistence-hooks.js +13 -5
- package/dist/server/server/resolve-workspace-id-for-path.d.ts +3 -0
- package/dist/server/server/resolve-workspace-id-for-path.js +41 -0
- package/dist/server/server/script-proxy.d.ts +1 -1
- package/dist/server/server/script-proxy.js +1 -1
- package/dist/server/server/service-proxy.js +1 -1
- package/dist/server/server/session.d.ts +31 -6
- package/dist/server/server/session.js +640 -196
- package/dist/server/server/websocket-server.d.ts +5 -0
- package/dist/server/server/websocket-server.js +137 -3
- package/dist/server/server/workspace-archive-service.d.ts +60 -3
- package/dist/server/server/workspace-archive-service.js +217 -4
- package/dist/server/server/workspace-directory.d.ts +20 -2
- package/dist/server/server/workspace-directory.js +148 -70
- package/dist/server/server/workspace-git-service.js +21 -21
- package/dist/server/server/workspace-reconciliation-service.d.ts +1 -1
- package/dist/server/server/workspace-reconciliation-service.js +21 -22
- package/dist/server/server/workspace-registry-bootstrap.js +23 -10
- package/dist/server/server/workspace-registry-model.d.ts +3 -3
- package/dist/server/server/workspace-registry-model.js +9 -10
- package/dist/server/server/workspace-registry.d.ts +17 -4
- package/dist/server/server/workspace-registry.js +27 -0
- package/dist/server/server/worktree/commands.d.ts +7 -5
- package/dist/server/server/worktree/commands.js +38 -18
- package/dist/server/server/worktree-bootstrap.d.ts +1 -0
- package/dist/server/server/worktree-bootstrap.js +4 -1
- package/dist/server/server/worktree-branch-name-generator.d.ts +5 -1
- package/dist/server/server/worktree-branch-name-generator.js +8 -2
- package/dist/server/server/worktree-session.d.ts +4 -5
- package/dist/server/server/worktree-session.js +9 -3
- package/dist/server/services/github-service.js +1 -1
- package/dist/server/terminal/activity/terminal-activity-tracker.d.ts +20 -0
- package/dist/server/terminal/activity/terminal-activity-tracker.js +59 -0
- package/dist/server/terminal/agent-hooks/agent-hook-installer.d.ts +62 -0
- package/dist/server/terminal/agent-hooks/agent-hook-installer.js +117 -0
- package/dist/server/terminal/agent-hooks/claude/claude-settings.d.ts +7 -0
- package/dist/server/terminal/agent-hooks/claude/claude-settings.js +88 -0
- package/dist/server/terminal/agent-hooks/claude/claude.d.ts +4 -0
- package/dist/server/terminal/agent-hooks/claude/claude.js +47 -0
- package/dist/server/terminal/agent-hooks/codex/codex-settings.d.ts +7 -0
- package/dist/server/terminal/agent-hooks/codex/codex-settings.js +99 -0
- package/dist/server/terminal/agent-hooks/codex/codex.d.ts +4 -0
- package/dist/server/terminal/agent-hooks/codex/codex.js +30 -0
- package/dist/server/terminal/agent-hooks/opencode/opencode-plugin.d.ts +4 -0
- package/dist/server/terminal/agent-hooks/opencode/opencode-plugin.js +46 -0
- package/dist/server/terminal/agent-hooks/opencode/opencode.d.ts +3 -0
- package/dist/server/terminal/agent-hooks/opencode/opencode.js +23 -0
- package/dist/server/terminal/agent-hooks/provider-registry.d.ts +24 -0
- package/dist/server/terminal/agent-hooks/provider-registry.js +36 -0
- package/dist/server/terminal/agent-hooks/terminal-agent-hook-setting.d.ts +10 -0
- package/dist/server/terminal/agent-hooks/terminal-agent-hook-setting.js +26 -0
- package/dist/server/terminal/terminal-manager-factory.d.ts +4 -1
- package/dist/server/terminal/terminal-manager-factory.js +2 -2
- package/dist/server/terminal/terminal-manager.d.ts +33 -2
- package/dist/server/terminal/terminal-manager.js +144 -18
- package/dist/server/terminal/terminal-output-coalescer.d.ts +4 -0
- package/dist/server/terminal/terminal-output-coalescer.js +18 -0
- package/dist/server/terminal/terminal-restore.d.ts +1 -0
- package/dist/server/terminal/terminal-restore.js +6 -0
- package/dist/server/terminal/terminal-session-controller.d.ts +4 -2
- package/dist/server/terminal/terminal-session-controller.js +65 -24
- package/dist/server/terminal/terminal-worker-process.js +146 -63
- package/dist/server/terminal/terminal-worker-protocol.d.ts +19 -14
- package/dist/server/terminal/terminal.d.ts +42 -0
- package/dist/server/terminal/terminal.js +235 -16
- package/dist/server/terminal/worker-terminal-manager.d.ts +1 -0
- package/dist/server/terminal/worker-terminal-manager.js +220 -36
- package/dist/server/utils/build-metadata-prompt.d.ts +1 -1
- package/dist/server/utils/github-remote.js +1 -1
- package/dist/server/utils/tree-kill.d.ts +2 -2
- package/dist/src/{utils/executable.js → executable-resolution/executable-resolution.js} +16 -14
- package/dist/src/executable-resolution/windows.js +62 -0
- package/dist/src/server/agent/provider-launch-config.js +1 -1
- package/dist/src/server/persisted-config.js +1 -0
- package/package.json +10 -5
- package/dist/server/server/agent/agent-metadata-generator.d.ts +0 -36
- package/dist/server/server/agent/agent-metadata-generator.js +0 -112
- package/dist/server/server/paseo-worktree-archive-service.d.ts +0 -41
- package/dist/server/server/paseo-worktree-archive-service.js +0 -144
|
@@ -90,13 +90,16 @@ export declare class VoiceAssistantWebSocketServer {
|
|
|
90
90
|
private serverCapabilities;
|
|
91
91
|
private readonly runtimeMetrics;
|
|
92
92
|
private runtimeMetricsInterval;
|
|
93
|
+
private eventLoopDelayMonitor;
|
|
93
94
|
private unsubscribeSpeechReadiness;
|
|
94
95
|
private unsubscribeDaemonConfigChange;
|
|
96
|
+
private unsubscribeTerminalActivity;
|
|
95
97
|
constructor(server: HTTPServer, logger: pino.Logger, serverId: string, agentManager: AgentManager, agentStorage: AgentStorage, downloadTokenStore: DownloadTokenStore, paseoHome: string, daemonConfigStore: DaemonConfigStore, mcpBaseUrl: string | null, wsConfig: WebSocketServerConfig, auth?: DaemonAuthConfig, speech?: SpeechService | null, terminalManager?: TerminalManager | null, dictation?: {
|
|
96
98
|
finalTimeoutMs?: number;
|
|
97
99
|
}, daemonVersion?: string, onLifecycleIntent?: (intent: SessionLifecycleIntent) => void, projectRegistry?: ProjectRegistry, workspaceRegistry?: WorkspaceRegistry, chatService?: FileBackedChatService, loopService?: LoopService, scheduleService?: ScheduleService, checkoutDiffManager?: CheckoutDiffManager, serviceProxy?: ServiceProxySubsystem | null, scriptRuntimeStore?: WorkspaceScriptRuntimeStore | null, onBranchChanged?: (workspaceId: string, oldBranch: string | null, newBranch: string | null) => void, getDaemonTcpPort?: () => number | null, getDaemonTcpHost?: () => string | null, resolveScriptHealth?: (hostname: string) => ScriptHealthState | null, workspaceGitService?: WorkspaceGitService, github?: GitHubService, pushNotificationSender?: PushNotificationSender, providerSnapshotManager?: ProviderSnapshotManager, daemonRuntimeConfig?: {
|
|
98
100
|
listen: string | null;
|
|
99
101
|
worktreesRoot?: string;
|
|
102
|
+
appBaseUrl?: string;
|
|
100
103
|
relay: {
|
|
101
104
|
enabled: boolean;
|
|
102
105
|
endpoint: string;
|
|
@@ -108,6 +111,7 @@ export declare class VoiceAssistantWebSocketServer {
|
|
|
108
111
|
private assignOptionalServices;
|
|
109
112
|
private createWebSocketServer;
|
|
110
113
|
private startRuntimeMetricsInterval;
|
|
114
|
+
private snapshotEventLoopDelay;
|
|
111
115
|
private verifyWsUpgrade;
|
|
112
116
|
private attachAuthenticatedSocket;
|
|
113
117
|
broadcast(message: WSOutboundMessage): void;
|
|
@@ -150,6 +154,7 @@ export declare class VoiceAssistantWebSocketServer {
|
|
|
150
154
|
private flushRuntimeMetrics;
|
|
151
155
|
private getClientActivityState;
|
|
152
156
|
private broadcastAgentAttention;
|
|
157
|
+
private broadcastTerminalAttention;
|
|
153
158
|
}
|
|
154
159
|
export {};
|
|
155
160
|
//# 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 { monitorEventLoopDelay } from "node:perf_hooks";
|
|
4
5
|
import { WSInboundMessageSchema, wrapSessionMessage, } from "./messages.js";
|
|
5
6
|
import { asUint8Array, decodeBinaryFrame } from "@getpaseo/protocol/binary-frames/index";
|
|
6
7
|
import { isHostnameAllowed } from "./hostnames.js";
|
|
@@ -8,12 +9,26 @@ import { Session } from "./session.js";
|
|
|
8
9
|
import { buildWorkspaceGitMetadataFromSnapshot } from "./workspace-git-metadata.js";
|
|
9
10
|
import { PushTokenStore } from "./push/token-store.js";
|
|
10
11
|
import { createPushNotificationSender } from "./push/notifications.js";
|
|
11
|
-
import { computeNotificationPlan } from "./agent-attention-policy.js";
|
|
12
|
+
import { computeNotificationPlan, isPushEligibleAttentionReason, } from "./agent-attention-policy.js";
|
|
12
13
|
import { buildAgentAttentionNotificationPayload, findLatestPermissionRequest, } from "@getpaseo/protocol/agent-attention-notification";
|
|
13
14
|
import { createGitHubService } from "../services/github-service.js";
|
|
14
15
|
import { extractWsBearerProtocol, extractWsBearerToken, isBearerTokenValid, } from "./auth.js";
|
|
15
16
|
import { WebSocketRuntimeMetricsWindow, } from "./websocket/runtime-metrics.js";
|
|
16
17
|
const WS_CLOSE_DAEMON_AUTH_FAILED = 4401;
|
|
18
|
+
function resolveTerminalAttentionReason(input) {
|
|
19
|
+
if (input.attentionReason === "finished")
|
|
20
|
+
return "finished";
|
|
21
|
+
if (input.attentionReason === "needs_input")
|
|
22
|
+
return "needs_input";
|
|
23
|
+
if (input.state === "attention")
|
|
24
|
+
return "needs_input";
|
|
25
|
+
if (input.previousState === "working" && input.state === "idle")
|
|
26
|
+
return "finished";
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
function terminalAttentionTitle(reason) {
|
|
30
|
+
return reason === "needs_input" ? "Terminal needs input" : "Terminal finished";
|
|
31
|
+
}
|
|
17
32
|
function createFallbackWorkspaceGitSnapshot(cwd) {
|
|
18
33
|
return {
|
|
19
34
|
cwd,
|
|
@@ -213,8 +228,10 @@ export class VoiceAssistantWebSocketServer {
|
|
|
213
228
|
this.workspaceSetupSnapshots = new Map();
|
|
214
229
|
this.runtimeMetrics = new WebSocketRuntimeMetricsWindow();
|
|
215
230
|
this.runtimeMetricsInterval = null;
|
|
231
|
+
this.eventLoopDelayMonitor = null;
|
|
216
232
|
this.unsubscribeSpeechReadiness = null;
|
|
217
233
|
this.unsubscribeDaemonConfigChange = null;
|
|
234
|
+
this.unsubscribeTerminalActivity = null;
|
|
218
235
|
this.logger = logger.child({ module: "websocket-server" });
|
|
219
236
|
this.serverId = serverId;
|
|
220
237
|
if (typeof daemonVersion !== "string" || daemonVersion.trim().length === 0) {
|
|
@@ -288,6 +305,27 @@ export class VoiceAssistantWebSocketServer {
|
|
|
288
305
|
assignOptionalServices(params) {
|
|
289
306
|
this.speech = params.speech ?? null;
|
|
290
307
|
this.terminalManager = params.terminalManager ?? null;
|
|
308
|
+
if (this.terminalManager) {
|
|
309
|
+
this.unsubscribeTerminalActivity = this.terminalManager.subscribeTerminalActivity((event) => {
|
|
310
|
+
const reason = resolveTerminalAttentionReason({
|
|
311
|
+
attentionReason: event.activity?.attentionReason,
|
|
312
|
+
previousState: event.previous?.state ?? null,
|
|
313
|
+
state: event.activity?.state ?? null,
|
|
314
|
+
});
|
|
315
|
+
if (!reason) {
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
void this.broadcastTerminalAttention({
|
|
319
|
+
terminalId: event.terminalId,
|
|
320
|
+
cwd: event.cwd,
|
|
321
|
+
...(event.workspaceId ? { workspaceId: event.workspaceId } : {}),
|
|
322
|
+
terminalName: event.name,
|
|
323
|
+
reason,
|
|
324
|
+
}).catch((err) => {
|
|
325
|
+
this.logger.warn({ err, terminalId: event.terminalId }, "Failed to broadcast terminal attention");
|
|
326
|
+
});
|
|
327
|
+
});
|
|
328
|
+
}
|
|
291
329
|
this.dictation = params.dictation ?? null;
|
|
292
330
|
this.onLifecycleIntent = params.onLifecycleIntent ?? null;
|
|
293
331
|
this.serviceProxy = params.serviceProxy ?? null;
|
|
@@ -315,12 +353,30 @@ export class VoiceAssistantWebSocketServer {
|
|
|
315
353
|
return wss;
|
|
316
354
|
}
|
|
317
355
|
startRuntimeMetricsInterval() {
|
|
356
|
+
this.eventLoopDelayMonitor = monitorEventLoopDelay({ resolution: 10 });
|
|
357
|
+
this.eventLoopDelayMonitor.enable();
|
|
318
358
|
const runtimeMetricsInterval = setInterval(() => {
|
|
319
359
|
this.flushRuntimeMetrics();
|
|
320
360
|
}, WS_RUNTIME_METRICS_FLUSH_MS);
|
|
321
361
|
this.runtimeMetricsInterval = runtimeMetricsInterval;
|
|
322
362
|
runtimeMetricsInterval.unref?.();
|
|
323
363
|
}
|
|
364
|
+
// Main-loop stall visibility: terminal frames and agent traffic share one event
|
|
365
|
+
// loop, so delay percentiles here are the ground truth for "the daemon is busy".
|
|
366
|
+
snapshotEventLoopDelay() {
|
|
367
|
+
const monitor = this.eventLoopDelayMonitor;
|
|
368
|
+
if (!monitor) {
|
|
369
|
+
return null;
|
|
370
|
+
}
|
|
371
|
+
const toMs = (nanoseconds) => Math.round(nanoseconds / 1e5) / 10;
|
|
372
|
+
const snapshot = {
|
|
373
|
+
p50Ms: toMs(monitor.percentile(50)),
|
|
374
|
+
p99Ms: toMs(monitor.percentile(99)),
|
|
375
|
+
maxMs: toMs(monitor.max),
|
|
376
|
+
};
|
|
377
|
+
monitor.reset();
|
|
378
|
+
return snapshot;
|
|
379
|
+
}
|
|
324
380
|
verifyWsUpgrade(req, allowedOrigins, hostnames, callback) {
|
|
325
381
|
const requestMetadata = extractSocketRequestMetadata(req);
|
|
326
382
|
const origin = requestMetadata.origin;
|
|
@@ -393,11 +449,15 @@ export class VoiceAssistantWebSocketServer {
|
|
|
393
449
|
this.unsubscribeSpeechReadiness = null;
|
|
394
450
|
this.unsubscribeDaemonConfigChange?.();
|
|
395
451
|
this.unsubscribeDaemonConfigChange = null;
|
|
452
|
+
this.unsubscribeTerminalActivity?.();
|
|
453
|
+
this.unsubscribeTerminalActivity = null;
|
|
396
454
|
if (this.runtimeMetricsInterval) {
|
|
397
455
|
clearInterval(this.runtimeMetricsInterval);
|
|
398
456
|
this.runtimeMetricsInterval = null;
|
|
399
457
|
}
|
|
400
458
|
this.flushRuntimeMetrics({ final: true });
|
|
459
|
+
this.eventLoopDelayMonitor?.disable();
|
|
460
|
+
this.eventLoopDelayMonitor = null;
|
|
401
461
|
const uniqueConnections = new Set([
|
|
402
462
|
...this.sessions.values(),
|
|
403
463
|
...this.externalSessionsByKey.values(),
|
|
@@ -548,6 +608,22 @@ export class VoiceAssistantWebSocketServer {
|
|
|
548
608
|
}
|
|
549
609
|
this.sendBinaryToConnection(connection, frame);
|
|
550
610
|
},
|
|
611
|
+
getTransportBufferedAmount: () => {
|
|
612
|
+
if (!connection) {
|
|
613
|
+
return null;
|
|
614
|
+
}
|
|
615
|
+
// Relay-attached sockets are a WebSocketLike that doesn't expose
|
|
616
|
+
// bufferedAmount. Return null when no socket gives a signal so the
|
|
617
|
+
// terminal fallback can't mistake "no signal" for "client keeping up";
|
|
618
|
+
// a direct ws reports its real buffered bytes (0 when drained).
|
|
619
|
+
let maxBuffered = null;
|
|
620
|
+
for (const socket of connection.sockets) {
|
|
621
|
+
if (typeof socket.bufferedAmount === "number") {
|
|
622
|
+
maxBuffered = Math.max(maxBuffered ?? 0, socket.bufferedAmount);
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
return maxBuffered;
|
|
626
|
+
},
|
|
551
627
|
onLifecycleIntent: (intent) => {
|
|
552
628
|
this.onLifecycleIntent?.(intent);
|
|
553
629
|
},
|
|
@@ -729,6 +805,8 @@ export class VoiceAssistantWebSocketServer {
|
|
|
729
805
|
rewind: true,
|
|
730
806
|
// COMPAT(checkoutRefresh): added in v0.1.86, remove gate after 2026-11-29.
|
|
731
807
|
checkoutRefresh: true,
|
|
808
|
+
// COMPAT(workspaceMultiplicity): added in v0.1.97, drop the gate when floor >= v0.1.97
|
|
809
|
+
workspaceMultiplicity: true,
|
|
732
810
|
},
|
|
733
811
|
};
|
|
734
812
|
}
|
|
@@ -1164,6 +1242,7 @@ export class VoiceAssistantWebSocketServer {
|
|
|
1164
1242
|
outboundAgentStreamAgentsTop: runtimeMetrics.outboundAgentStreamAgentsTop,
|
|
1165
1243
|
outboundBinaryFrameTypesTop: runtimeMetrics.outboundBinaryFrameTypesTop,
|
|
1166
1244
|
bufferedAmount: runtimeMetrics.bufferedAmount,
|
|
1245
|
+
eventLoopDelay: this.snapshotEventLoopDelay(),
|
|
1167
1246
|
runtime: sessionMetrics,
|
|
1168
1247
|
latency: runtimeMetrics.latency,
|
|
1169
1248
|
agents: agentSnapshot,
|
|
@@ -1175,12 +1254,14 @@ export class VoiceAssistantWebSocketServer {
|
|
|
1175
1254
|
return {
|
|
1176
1255
|
appVisible: false,
|
|
1177
1256
|
focusedAgentId: null,
|
|
1257
|
+
focusedTerminalId: null,
|
|
1178
1258
|
lastActivityAtMs: null,
|
|
1179
1259
|
};
|
|
1180
1260
|
}
|
|
1181
1261
|
return {
|
|
1182
1262
|
appVisible: activity.appVisible,
|
|
1183
1263
|
focusedAgentId: activity.focusedAgentId,
|
|
1264
|
+
focusedTerminalId: activity.focusedTerminalId,
|
|
1184
1265
|
lastActivityAtMs: activity.lastActivityAt.getTime(),
|
|
1185
1266
|
};
|
|
1186
1267
|
}
|
|
@@ -1205,8 +1286,8 @@ export class VoiceAssistantWebSocketServer {
|
|
|
1205
1286
|
});
|
|
1206
1287
|
const plan = computeNotificationPlan({
|
|
1207
1288
|
allStates,
|
|
1208
|
-
|
|
1209
|
-
|
|
1289
|
+
focusTarget: { kind: "agent", id: params.agentId },
|
|
1290
|
+
pushEligible: isPushEligibleAttentionReason(params.reason),
|
|
1210
1291
|
nowMs,
|
|
1211
1292
|
});
|
|
1212
1293
|
if (plan.shouldPush) {
|
|
@@ -1235,6 +1316,59 @@ export class VoiceAssistantWebSocketServer {
|
|
|
1235
1316
|
this.sendToClient(ws, message);
|
|
1236
1317
|
}
|
|
1237
1318
|
}
|
|
1319
|
+
async broadcastTerminalAttention(params) {
|
|
1320
|
+
const clientEntries = [];
|
|
1321
|
+
for (const [ws, connection] of this.sessions) {
|
|
1322
|
+
clientEntries.push({
|
|
1323
|
+
ws,
|
|
1324
|
+
state: this.getClientActivityState(connection.session),
|
|
1325
|
+
});
|
|
1326
|
+
}
|
|
1327
|
+
const allStates = clientEntries.map((e) => e.state);
|
|
1328
|
+
const nowMs = Date.now();
|
|
1329
|
+
const workspaceId = params.workspaceId;
|
|
1330
|
+
const plan = computeNotificationPlan({
|
|
1331
|
+
allStates,
|
|
1332
|
+
focusTarget: { kind: "terminal", id: params.terminalId },
|
|
1333
|
+
pushEligible: true,
|
|
1334
|
+
nowMs,
|
|
1335
|
+
});
|
|
1336
|
+
const title = terminalAttentionTitle(params.reason);
|
|
1337
|
+
const body = params.terminalName;
|
|
1338
|
+
if (plan.shouldPush) {
|
|
1339
|
+
void this.pushNotificationSender
|
|
1340
|
+
.send({
|
|
1341
|
+
title,
|
|
1342
|
+
body,
|
|
1343
|
+
data: {
|
|
1344
|
+
serverId: this.serverId,
|
|
1345
|
+
terminalId: params.terminalId,
|
|
1346
|
+
cwd: params.cwd,
|
|
1347
|
+
...(workspaceId ? { workspaceId } : {}),
|
|
1348
|
+
},
|
|
1349
|
+
})
|
|
1350
|
+
.catch((err) => {
|
|
1351
|
+
this.logger.warn({ err, terminalId: params.terminalId }, "Failed to send push notification");
|
|
1352
|
+
});
|
|
1353
|
+
}
|
|
1354
|
+
for (const [clientIndex, { ws }] of clientEntries.entries()) {
|
|
1355
|
+
const shouldNotify = clientIndex === plan.inAppRecipientIndex;
|
|
1356
|
+
const message = wrapSessionMessage({
|
|
1357
|
+
type: "terminal_attention_required",
|
|
1358
|
+
payload: {
|
|
1359
|
+
serverId: this.serverId,
|
|
1360
|
+
terminalId: params.terminalId,
|
|
1361
|
+
cwd: params.cwd,
|
|
1362
|
+
...(workspaceId ? { workspaceId } : {}),
|
|
1363
|
+
reason: params.reason,
|
|
1364
|
+
title,
|
|
1365
|
+
body,
|
|
1366
|
+
shouldNotify,
|
|
1367
|
+
},
|
|
1368
|
+
});
|
|
1369
|
+
this.sendToClient(ws, message);
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
1238
1372
|
}
|
|
1239
1373
|
function extractSocketRequestMetadata(request) {
|
|
1240
1374
|
if (!request || typeof request !== "object") {
|
|
@@ -1,8 +1,65 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Logger } from "pino";
|
|
2
|
+
import type { AgentManager } from "./agent/agent-manager.js";
|
|
3
|
+
import type { AgentStorage } from "./agent/agent-storage.js";
|
|
4
|
+
import type { WorkspaceGitService } from "./workspace-git-service.js";
|
|
5
|
+
import type { GitHubService } from "../services/github-service.js";
|
|
6
|
+
import type { TerminalManager } from "../terminal/terminal-manager.js";
|
|
7
|
+
import type { PersistedWorkspaceRecord, WorkspaceRegistry } from "./workspace-registry.js";
|
|
8
|
+
export interface ActiveWorkspaceRef {
|
|
9
|
+
workspaceId: string;
|
|
10
|
+
cwd: string;
|
|
11
|
+
kind?: "local_checkout" | "worktree" | "directory";
|
|
12
|
+
}
|
|
13
|
+
export interface ArchiveDependencies {
|
|
14
|
+
paseoHome?: string;
|
|
15
|
+
paseoWorktreesBaseRoot?: string;
|
|
16
|
+
github: GitHubService;
|
|
17
|
+
workspaceGitService: Pick<WorkspaceGitService, "getSnapshot">;
|
|
18
|
+
agentManager: Pick<AgentManager, "listAgents" | "archiveAgent" | "archiveSnapshot">;
|
|
19
|
+
agentStorage: Pick<AgentStorage, "list">;
|
|
20
|
+
findWorkspaceIdForCwd: (cwd: string) => Promise<string | null>;
|
|
21
|
+
listActiveWorkspaces: () => Promise<ActiveWorkspaceRef[]>;
|
|
22
|
+
archiveWorkspaceRecord: (workspaceId: string) => Promise<void>;
|
|
23
|
+
emitWorkspaceUpdatesForWorkspaceIds: (workspaceIds: Iterable<string>) => Promise<void>;
|
|
24
|
+
markWorkspaceArchiving: (workspaceIds: Iterable<string>, archivingAt: string) => void;
|
|
25
|
+
clearWorkspaceArchiving: (workspaceIds: Iterable<string>) => void;
|
|
26
|
+
killTerminalsForWorkspace: (workspaceId: string) => Promise<void>;
|
|
27
|
+
sessionLogger?: Logger;
|
|
28
|
+
}
|
|
29
|
+
export interface KillTerminalsForWorkspaceDependencies {
|
|
30
|
+
detachTerminalStream?: (terminalId: string, options: {
|
|
31
|
+
emitExit: boolean;
|
|
32
|
+
}) => void;
|
|
33
|
+
sessionLogger: Logger;
|
|
34
|
+
terminalManager: TerminalManager | null;
|
|
35
|
+
}
|
|
36
|
+
export type ArchiveScope = {
|
|
37
|
+
kind: "workspace";
|
|
38
|
+
workspaceId: string;
|
|
39
|
+
} | {
|
|
40
|
+
kind: "worktree";
|
|
41
|
+
targetPath: string;
|
|
42
|
+
};
|
|
43
|
+
export interface ArchiveResult {
|
|
44
|
+
archivedAgentIds: string[];
|
|
45
|
+
archivedWorkspaceIds: string[];
|
|
46
|
+
removedDirectory: boolean;
|
|
47
|
+
}
|
|
48
|
+
export interface ArchiveByScopeRequest {
|
|
49
|
+
scope: ArchiveScope;
|
|
50
|
+
repoRoot: string | null;
|
|
51
|
+
repoWorktreesRoot?: string;
|
|
52
|
+
paseoWorktreesBaseRoot?: string;
|
|
53
|
+
requestId: string;
|
|
54
|
+
}
|
|
55
|
+
export declare function resolveWorkspaceIdAtPath(dependencies: Pick<ArchiveDependencies, "findWorkspaceIdForCwd" | "listActiveWorkspaces">, targetPath: string): Promise<string | null>;
|
|
56
|
+
export declare function archiveByScope(dependencies: ArchiveDependencies, request: ArchiveByScopeRequest): Promise<ArchiveResult>;
|
|
57
|
+
export type ArchiveWorkspaceContentsDependencies = Pick<ArchiveDependencies, "agentManager" | "agentStorage" | "killTerminalsForWorkspace" | "sessionLogger">;
|
|
58
|
+
export declare function archiveWorkspaceContents(dependencies: ArchiveWorkspaceContentsDependencies, workspaceId: string): Promise<Set<string>>;
|
|
59
|
+
export declare function killTerminalsForWorkspace(dependencies: KillTerminalsForWorkspaceDependencies, workspaceId: string): Promise<void>;
|
|
2
60
|
export declare function archivePersistedWorkspaceRecord(input: {
|
|
3
61
|
workspaceId: string;
|
|
4
|
-
workspaceRegistry: Pick<WorkspaceRegistry, "get" | "
|
|
5
|
-
projectRegistry: Pick<ProjectRegistry, "archive">;
|
|
62
|
+
workspaceRegistry: Pick<WorkspaceRegistry, "get" | "archive">;
|
|
6
63
|
archivedAt?: string;
|
|
7
64
|
}): Promise<PersistedWorkspaceRecord | null>;
|
|
8
65
|
//# sourceMappingURL=workspace-archive-service.d.ts.map
|
|
@@ -1,3 +1,220 @@
|
|
|
1
|
+
import { resolve } from "node:path";
|
|
2
|
+
import { deletePaseoWorktree, isPaseoOwnedWorktreeCwd, resolvePaseoWorktreeRootForCwd, WorktreeTeardownError, } from "../utils/worktree.js";
|
|
3
|
+
export async function resolveWorkspaceIdAtPath(dependencies, targetPath) {
|
|
4
|
+
const targetDir = resolve(targetPath);
|
|
5
|
+
const activeWorkspaces = await dependencies.listActiveWorkspaces();
|
|
6
|
+
const exactMatches = activeWorkspaces.filter((workspace) => resolve(workspace.cwd) === targetDir);
|
|
7
|
+
const worktreeMatch = exactMatches.find((workspace) => workspace.kind === "worktree");
|
|
8
|
+
if (worktreeMatch) {
|
|
9
|
+
return worktreeMatch.workspaceId;
|
|
10
|
+
}
|
|
11
|
+
return dependencies.findWorkspaceIdForCwd(targetPath);
|
|
12
|
+
}
|
|
13
|
+
// THE single archive entry. Resolves the in-scope record set, tears each down
|
|
14
|
+
// (agents + terminals + record), then removes the backing directory iff it is
|
|
15
|
+
// Paseo-owned AND no active workspace still references it.
|
|
16
|
+
export async function archiveByScope(dependencies, request) {
|
|
17
|
+
const { targetDir, targetWorkspaceIds } = await resolveArchiveTargets(dependencies, request.scope, request.paseoWorktreesBaseRoot);
|
|
18
|
+
if (targetWorkspaceIds.length > 0) {
|
|
19
|
+
dependencies.markWorkspaceArchiving(targetWorkspaceIds, new Date().toISOString());
|
|
20
|
+
}
|
|
21
|
+
let removedDirectory = false;
|
|
22
|
+
try {
|
|
23
|
+
if (targetWorkspaceIds.length > 0) {
|
|
24
|
+
await dependencies.emitWorkspaceUpdatesForWorkspaceIds(targetWorkspaceIds);
|
|
25
|
+
}
|
|
26
|
+
const { archivedAgents, archivedWorkspaceIds } = await archiveTargetRecords(dependencies, targetWorkspaceIds, request.requestId);
|
|
27
|
+
if (request.repoRoot) {
|
|
28
|
+
try {
|
|
29
|
+
await dependencies.workspaceGitService.getSnapshot(request.repoRoot, {
|
|
30
|
+
force: true,
|
|
31
|
+
reason: "archive-worktree",
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
dependencies.sessionLogger?.warn({ err: error, cwd: request.repoRoot, requestId: request.requestId }, "Failed to force-refresh workspace git snapshot after archiving");
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (targetDir !== null) {
|
|
39
|
+
removedDirectory = await maybeRemoveDirectory(dependencies, request, targetDir, archivedWorkspaceIds);
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
archivedAgentIds: Array.from(archivedAgents),
|
|
43
|
+
archivedWorkspaceIds,
|
|
44
|
+
removedDirectory,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
finally {
|
|
48
|
+
if (targetWorkspaceIds.length > 0) {
|
|
49
|
+
dependencies.clearWorkspaceArchiving(targetWorkspaceIds);
|
|
50
|
+
await dependencies.emitWorkspaceUpdatesForWorkspaceIds(targetWorkspaceIds);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
async function resolveArchiveTargets(dependencies, scope, paseoWorktreesBaseRoot) {
|
|
55
|
+
const activeWorkspaces = await dependencies.listActiveWorkspaces();
|
|
56
|
+
if (scope.kind === "workspace") {
|
|
57
|
+
const workspaceId = scope.workspaceId;
|
|
58
|
+
const record = activeWorkspaces.find((workspace) => workspace.workspaceId === workspaceId);
|
|
59
|
+
if (!record) {
|
|
60
|
+
dependencies.sessionLogger?.warn({ workspaceId }, "Workspace not found for archive-by-scope; skipping");
|
|
61
|
+
return { targetDir: null, targetWorkspaceIds: [] };
|
|
62
|
+
}
|
|
63
|
+
return { targetDir: resolve(record.cwd), targetWorkspaceIds: [workspaceId] };
|
|
64
|
+
}
|
|
65
|
+
let targetPath = scope.targetPath;
|
|
66
|
+
const resolvedWorktree = await resolvePaseoWorktreeRootForCwd(targetPath, {
|
|
67
|
+
paseoHome: dependencies.paseoHome,
|
|
68
|
+
worktreesRoot: paseoWorktreesBaseRoot ?? dependencies.paseoWorktreesBaseRoot,
|
|
69
|
+
});
|
|
70
|
+
if (resolvedWorktree) {
|
|
71
|
+
targetPath = resolvedWorktree.worktreePath;
|
|
72
|
+
}
|
|
73
|
+
const targetDir = resolve(targetPath);
|
|
74
|
+
const targetWorkspaceIds = activeWorkspaces
|
|
75
|
+
.filter((workspace) => resolve(workspace.cwd) === targetDir)
|
|
76
|
+
.map((workspace) => workspace.workspaceId);
|
|
77
|
+
return { targetDir, targetWorkspaceIds };
|
|
78
|
+
}
|
|
79
|
+
async function archiveTargetRecords(dependencies, targetWorkspaceIds, requestId) {
|
|
80
|
+
const archivedAgents = new Set();
|
|
81
|
+
const archivedWorkspaceIds = [];
|
|
82
|
+
const results = await Promise.allSettled(targetWorkspaceIds.map(async (workspaceId) => {
|
|
83
|
+
const agents = await archiveWorkspaceContents(dependencies, workspaceId);
|
|
84
|
+
await dependencies.archiveWorkspaceRecord(workspaceId);
|
|
85
|
+
return { workspaceId, agents };
|
|
86
|
+
}));
|
|
87
|
+
for (const result of results) {
|
|
88
|
+
if (result.status === "fulfilled") {
|
|
89
|
+
archivedWorkspaceIds.push(result.value.workspaceId);
|
|
90
|
+
for (const agentId of result.value.agents) {
|
|
91
|
+
archivedAgents.add(agentId);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
dependencies.sessionLogger?.warn({ err: result.reason, requestId }, "archiveByScope workspace teardown failed; continuing");
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return { archivedAgents, archivedWorkspaceIds };
|
|
99
|
+
}
|
|
100
|
+
async function maybeRemoveDirectory(dependencies, request, targetDir, archivedWorkspaceIds) {
|
|
101
|
+
const ownership = await isPaseoOwnedWorktreeCwd(targetDir, {
|
|
102
|
+
paseoHome: dependencies.paseoHome,
|
|
103
|
+
worktreesRoot: request.paseoWorktreesBaseRoot ?? dependencies.paseoWorktreesBaseRoot,
|
|
104
|
+
});
|
|
105
|
+
if (!ownership.allowed) {
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
const remainingActive = await dependencies.listActiveWorkspaces();
|
|
109
|
+
if (!isDirectoryUnreferenced(remainingActive, targetDir, new Set(archivedWorkspaceIds))) {
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
try {
|
|
113
|
+
await deletePaseoWorktree({
|
|
114
|
+
cwd: request.repoRoot,
|
|
115
|
+
worktreePath: targetDir,
|
|
116
|
+
worktreesRoot: request.repoWorktreesRoot ?? ownership.worktreeRoot,
|
|
117
|
+
paseoHome: dependencies.paseoHome,
|
|
118
|
+
worktreesBaseRoot: request.paseoWorktreesBaseRoot ?? dependencies.paseoWorktreesBaseRoot,
|
|
119
|
+
});
|
|
120
|
+
dependencies.github.invalidate({ cwd: targetDir });
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
if (error instanceof WorktreeTeardownError) {
|
|
125
|
+
dependencies.sessionLogger?.warn({ err: error, targetPath: targetDir, requestId: request.requestId }, "Worktree disk removal failed during archive; workspace already archived");
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
throw error;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
// Tears down everything OWNED by a single workspace record: its live agents,
|
|
132
|
+
// its persisted-but-not-running agent snapshots, and its terminals. Scoped by
|
|
133
|
+
// workspaceId so a sibling workspace sharing the same directory is untouched.
|
|
134
|
+
// Returns the set of archived agent ids.
|
|
135
|
+
export async function archiveWorkspaceContents(dependencies, workspaceId) {
|
|
136
|
+
const archivedAgents = new Set();
|
|
137
|
+
const liveAgents = dependencies.agentManager
|
|
138
|
+
.listAgents()
|
|
139
|
+
.filter((agent) => agent.workspaceId === workspaceId);
|
|
140
|
+
for (const agent of liveAgents) {
|
|
141
|
+
archivedAgents.add(agent.id);
|
|
142
|
+
}
|
|
143
|
+
let storedRecords = [];
|
|
144
|
+
try {
|
|
145
|
+
storedRecords = await dependencies.agentStorage.list();
|
|
146
|
+
}
|
|
147
|
+
catch (error) {
|
|
148
|
+
dependencies.sessionLogger?.warn({ err: error, workspaceId }, "Failed to list stored agents during workspace archive; continuing");
|
|
149
|
+
}
|
|
150
|
+
const liveAgentIds = new Set(liveAgents.map((agent) => agent.id));
|
|
151
|
+
const matchingStoredRecords = storedRecords.filter((record) => record.workspaceId === workspaceId);
|
|
152
|
+
for (const record of matchingStoredRecords) {
|
|
153
|
+
archivedAgents.add(record.id);
|
|
154
|
+
}
|
|
155
|
+
const archivedAt = new Date().toISOString();
|
|
156
|
+
const archiveResults = await Promise.allSettled([
|
|
157
|
+
...liveAgents.map((agent) => dependencies.agentManager.archiveAgent(agent.id)),
|
|
158
|
+
...matchingStoredRecords
|
|
159
|
+
.filter((record) => !liveAgentIds.has(record.id) && !record.archivedAt)
|
|
160
|
+
.map((record) => dependencies.agentManager.archiveSnapshot(record.id, archivedAt)),
|
|
161
|
+
dependencies.killTerminalsForWorkspace(workspaceId),
|
|
162
|
+
]);
|
|
163
|
+
for (const result of archiveResults) {
|
|
164
|
+
if (result.status === "rejected") {
|
|
165
|
+
dependencies.sessionLogger?.warn({ err: result.reason, workspaceId }, "Workspace archive teardown step failed; continuing");
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return archivedAgents;
|
|
169
|
+
}
|
|
170
|
+
// EXACTLY one last-reference predicate in the module. True when, after archiving
|
|
171
|
+
// the in-scope records, no active workspace still points at targetDir. Derived
|
|
172
|
+
// from records each call — no stored counter.
|
|
173
|
+
function isDirectoryUnreferenced(activeWorkspaces, targetDir, archivedWorkspaceIds) {
|
|
174
|
+
const target = resolve(targetDir);
|
|
175
|
+
return !activeWorkspaces.some((workspace) => !archivedWorkspaceIds.has(workspace.workspaceId) && resolve(workspace.cwd) === target);
|
|
176
|
+
}
|
|
177
|
+
export async function killTerminalsForWorkspace(dependencies, workspaceId) {
|
|
178
|
+
const terminalManager = dependencies.terminalManager;
|
|
179
|
+
if (!terminalManager) {
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
const terminalIds = [];
|
|
183
|
+
const terminalLists = await Promise.all(terminalManager.listDirectories().map(async (terminalCwd) => {
|
|
184
|
+
try {
|
|
185
|
+
return await terminalManager.getTerminals(terminalCwd, { workspaceId });
|
|
186
|
+
}
|
|
187
|
+
catch (error) {
|
|
188
|
+
dependencies.sessionLogger.warn({ err: error, cwd: terminalCwd }, "Failed to enumerate workspace terminals during archive");
|
|
189
|
+
return [];
|
|
190
|
+
}
|
|
191
|
+
}));
|
|
192
|
+
for (const terminals of terminalLists) {
|
|
193
|
+
for (const terminal of terminals) {
|
|
194
|
+
if (terminal.workspaceId === workspaceId) {
|
|
195
|
+
terminalIds.push(terminal.id);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
if (terminalIds.length === 0) {
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
await Promise.allSettled(terminalIds.map(async (terminalId) => {
|
|
203
|
+
try {
|
|
204
|
+
dependencies.detachTerminalStream?.(terminalId, { emitExit: true });
|
|
205
|
+
await terminalManager.killTerminalAndWait(terminalId, {
|
|
206
|
+
gracefulTimeoutMs: 2000,
|
|
207
|
+
forceTimeoutMs: 1500,
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
catch (error) {
|
|
211
|
+
dependencies.sessionLogger.warn({ err: error, terminalId }, "Terminal kill escalation failed during archive; proceeding anyway");
|
|
212
|
+
}
|
|
213
|
+
}));
|
|
214
|
+
}
|
|
215
|
+
// Archiving the last workspace of a project leaves the project as a first-class
|
|
216
|
+
// empty project — it persists until explicitly removed, so we never archive the
|
|
217
|
+
// parent project here.
|
|
1
218
|
export async function archivePersistedWorkspaceRecord(input) {
|
|
2
219
|
const existingWorkspace = await input.workspaceRegistry.get(input.workspaceId);
|
|
3
220
|
if (!existingWorkspace) {
|
|
@@ -8,10 +225,6 @@ export async function archivePersistedWorkspaceRecord(input) {
|
|
|
8
225
|
}
|
|
9
226
|
const archivedAt = input.archivedAt ?? new Date().toISOString();
|
|
10
227
|
await input.workspaceRegistry.archive(input.workspaceId, archivedAt);
|
|
11
|
-
const activeSiblings = (await input.workspaceRegistry.list()).filter((workspace) => workspace.projectId === existingWorkspace.projectId && !workspace.archivedAt);
|
|
12
|
-
if (activeSiblings.length === 0) {
|
|
13
|
-
await input.projectRegistry.archive(existingWorkspace.projectId, archivedAt);
|
|
14
|
-
}
|
|
15
228
|
return existingWorkspace;
|
|
16
229
|
}
|
|
17
230
|
//# sourceMappingURL=workspace-archive-service.js.map
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type pino from "pino";
|
|
2
2
|
import type { AgentSnapshotPayload, SessionInboundMessage, SessionOutboundMessage, WorkspaceDescriptorPayload } from "./messages.js";
|
|
3
3
|
import type { PersistedProjectRecord, PersistedWorkspaceRecord } from "./workspace-registry.js";
|
|
4
|
+
import { type TerminalActivity } from "@getpaseo/protocol/terminal-activity";
|
|
4
5
|
type FetchWorkspacesRequestMessage = Extract<SessionInboundMessage, {
|
|
5
6
|
type: "fetch_workspaces_request";
|
|
6
7
|
}>;
|
|
@@ -10,6 +11,7 @@ type FetchWorkspacesResponsePayload = Extract<SessionOutboundMessage, {
|
|
|
10
11
|
}>["payload"];
|
|
11
12
|
type FetchWorkspacesResponseEntry = FetchWorkspacesResponsePayload["entries"][number];
|
|
12
13
|
type FetchWorkspacesResponsePageInfo = FetchWorkspacesResponsePayload["pageInfo"];
|
|
14
|
+
type WorkspaceProjectDescriptor = FetchWorkspacesResponsePayload["emptyProjects"][number];
|
|
13
15
|
export type WorkspaceUpdatesFilter = FetchWorkspacesRequestFilter;
|
|
14
16
|
export interface WorkspaceDirectoryDeps {
|
|
15
17
|
logger: pino.Logger;
|
|
@@ -20,6 +22,11 @@ export interface WorkspaceDirectoryDeps {
|
|
|
20
22
|
list(): Promise<PersistedWorkspaceRecord[]>;
|
|
21
23
|
};
|
|
22
24
|
listAgentPayloads(): Promise<AgentSnapshotPayload[]>;
|
|
25
|
+
listTerminalActivityContributions(): Promise<Array<{
|
|
26
|
+
cwd: string;
|
|
27
|
+
workspaceId?: string;
|
|
28
|
+
activity: TerminalActivity | null;
|
|
29
|
+
}>>;
|
|
23
30
|
isProviderVisibleToClient(provider: string): boolean;
|
|
24
31
|
buildWorkspaceDescriptor(input: {
|
|
25
32
|
workspace: PersistedWorkspaceRecord;
|
|
@@ -41,6 +48,14 @@ export declare function summarizeFetchWorkspacesEntries(entries: Iterable<FetchW
|
|
|
41
48
|
activityAt: string | null;
|
|
42
49
|
}>;
|
|
43
50
|
};
|
|
51
|
+
/**
|
|
52
|
+
* Git facts (branch, diff, dirty, PR) belong to a checkout on disk, not to a
|
|
53
|
+
* workspace identity. Every workspace whose own cwd is that checkout re-derives
|
|
54
|
+
* its git facts from the same folder. This returns the ids of those workspaces
|
|
55
|
+
* so a git change can fan out to all of them. This is git-fact display, NOT
|
|
56
|
+
* ownership: do not use it to decide which workspace owns an arbitrary path.
|
|
57
|
+
*/
|
|
58
|
+
export declare function workspaceIdsOnCheckout(workspaces: Iterable<PersistedWorkspaceRecord>, cwd: string): string[];
|
|
44
59
|
export declare class WorkspaceDirectory {
|
|
45
60
|
private readonly deps;
|
|
46
61
|
private readonly archivingByWorkspaceId;
|
|
@@ -58,9 +73,11 @@ export declare class WorkspaceDirectory {
|
|
|
58
73
|
includeGitData: boolean;
|
|
59
74
|
workspaceIds?: Iterable<string>;
|
|
60
75
|
}): Promise<Map<string, WorkspaceDescriptorPayload>>;
|
|
76
|
+
private applyAgentBucketContributions;
|
|
77
|
+
private applyTerminalContributions;
|
|
61
78
|
private resolveStatusEnteredAt;
|
|
62
|
-
private
|
|
63
|
-
|
|
79
|
+
private findNewestTimestampInBucket;
|
|
80
|
+
listEmptyProjects(): Promise<WorkspaceProjectDescriptor[]>;
|
|
64
81
|
listDescriptors(): Promise<WorkspaceDescriptorPayload[]>;
|
|
65
82
|
matchesFilter(input: {
|
|
66
83
|
workspace: WorkspaceDescriptorPayload;
|
|
@@ -68,6 +85,7 @@ export declare class WorkspaceDirectory {
|
|
|
68
85
|
}): boolean;
|
|
69
86
|
listFetchEntries(request: FetchWorkspacesRequestMessage): Promise<{
|
|
70
87
|
entries: FetchWorkspacesResponseEntry[];
|
|
88
|
+
emptyProjects: WorkspaceProjectDescriptor[];
|
|
71
89
|
pageInfo: FetchWorkspacesResponsePageInfo;
|
|
72
90
|
}>;
|
|
73
91
|
}
|