@getpaseo/server 0.1.96 → 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
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createWorkerTerminalManager } from "./worker-terminal-manager.js";
|
|
2
|
-
export function createConfiguredTerminalManager() {
|
|
3
|
-
return createWorkerTerminalManager();
|
|
2
|
+
export function createConfiguredTerminalManager(options = {}) {
|
|
3
|
+
return createWorkerTerminalManager(options);
|
|
4
4
|
}
|
|
5
5
|
//# sourceMappingURL=terminal-manager-factory.js.map
|
|
@@ -1,34 +1,60 @@
|
|
|
1
1
|
import { type TerminalSession, type TerminalStateSnapshot, type TerminalStateSnapshotOptions } from "./terminal.js";
|
|
2
2
|
import { type CaptureTerminalLinesResult } from "./terminal-capture.js";
|
|
3
|
+
import type { TerminalActivity, TerminalActivityState } from "@getpaseo/protocol/terminal-activity";
|
|
3
4
|
export interface TerminalListItem {
|
|
4
5
|
id: string;
|
|
5
6
|
name: string;
|
|
6
7
|
cwd: string;
|
|
8
|
+
workspaceId: string;
|
|
7
9
|
title?: string;
|
|
10
|
+
activity: TerminalActivity | null;
|
|
8
11
|
}
|
|
9
12
|
export interface TerminalsChangedEvent {
|
|
10
13
|
cwd: string;
|
|
11
14
|
terminals: TerminalListItem[];
|
|
12
15
|
}
|
|
13
16
|
export type TerminalsChangedListener = (input: TerminalsChangedEvent) => void;
|
|
17
|
+
export interface TerminalActivityTransitionEvent {
|
|
18
|
+
terminalId: string;
|
|
19
|
+
name: string;
|
|
20
|
+
cwd: string;
|
|
21
|
+
workspaceId: string;
|
|
22
|
+
activity: TerminalActivity | null;
|
|
23
|
+
previous: TerminalActivity | null;
|
|
24
|
+
}
|
|
25
|
+
export type TerminalActivityListener = (event: TerminalActivityTransitionEvent) => void;
|
|
26
|
+
export interface TerminalWorkspaceContributionChangedEvent {
|
|
27
|
+
terminalId: string;
|
|
28
|
+
cwd: string;
|
|
29
|
+
workspaceId: string;
|
|
30
|
+
}
|
|
31
|
+
export type TerminalWorkspaceContributionChangedListener = (event: TerminalWorkspaceContributionChangedEvent) => void;
|
|
14
32
|
export interface TerminalManager {
|
|
15
|
-
getTerminals(cwd: string
|
|
33
|
+
getTerminals(cwd: string, options?: {
|
|
34
|
+
workspaceId?: string;
|
|
35
|
+
}): Promise<TerminalSession[]>;
|
|
16
36
|
createTerminal(options: {
|
|
17
37
|
id?: string;
|
|
18
38
|
cwd: string;
|
|
39
|
+
workspaceId: string;
|
|
19
40
|
name?: string;
|
|
20
41
|
title?: string;
|
|
21
42
|
env?: Record<string, string>;
|
|
22
43
|
command?: string;
|
|
23
44
|
args?: string[];
|
|
45
|
+
activityToken?: string;
|
|
46
|
+
activityUrl?: string | null;
|
|
24
47
|
}): Promise<TerminalSession>;
|
|
25
48
|
registerCwdEnv(options: {
|
|
26
49
|
cwd: string;
|
|
27
50
|
env: Record<string, string>;
|
|
28
51
|
}): void;
|
|
52
|
+
validateTerminalActivityToken(terminalId: string, token: string): "valid" | "unknown" | "invalid";
|
|
29
53
|
getTerminal(id: string): TerminalSession | undefined;
|
|
30
54
|
getTerminalState(id: string, options?: TerminalStateSnapshotOptions): Promise<TerminalStateSnapshot | null>;
|
|
31
55
|
setTerminalTitle(id: string, title: string): boolean;
|
|
56
|
+
setTerminalActivity(id: string, state: TerminalActivityState): Promise<boolean>;
|
|
57
|
+
clearTerminalAttention(id: string): Promise<boolean>;
|
|
32
58
|
killTerminal(id: string): void;
|
|
33
59
|
killTerminalAndWait(id: string, options?: {
|
|
34
60
|
gracefulTimeoutMs?: number;
|
|
@@ -42,6 +68,11 @@ export interface TerminalManager {
|
|
|
42
68
|
listDirectories(): string[];
|
|
43
69
|
killAll(): void;
|
|
44
70
|
subscribeTerminalsChanged(listener: TerminalsChangedListener): () => void;
|
|
71
|
+
subscribeTerminalActivity(listener: TerminalActivityListener): () => void;
|
|
72
|
+
subscribeTerminalWorkspaceContributionChanged(listener: TerminalWorkspaceContributionChangedListener): () => void;
|
|
73
|
+
}
|
|
74
|
+
export interface TerminalManagerOptions {
|
|
75
|
+
getTerminalActivityUrl?: () => string | null;
|
|
45
76
|
}
|
|
46
|
-
export declare function createTerminalManager(): TerminalManager;
|
|
77
|
+
export declare function createTerminalManager(managerOptions?: TerminalManagerOptions): TerminalManager;
|
|
47
78
|
//# sourceMappingURL=terminal-manager.d.ts.map
|
|
@@ -1,19 +1,23 @@
|
|
|
1
1
|
import { createTerminal, } from "./terminal.js";
|
|
2
2
|
import { captureTerminalLines } from "./terminal-capture.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
|
|
3
|
+
import { randomBytes, randomUUID } from "node:crypto";
|
|
4
|
+
import { resolve, sep } from "node:path";
|
|
5
|
+
import { assertAbsolutePath, isSameOrDescendantPath } from "../server/path-utils.js";
|
|
6
|
+
import { deriveTerminalActivityStatusBucket } from "@getpaseo/protocol/terminal-activity";
|
|
7
|
+
function createActivityToken() {
|
|
8
|
+
return randomBytes(32).toString("base64url");
|
|
9
|
+
}
|
|
10
|
+
export function createTerminalManager(managerOptions = {}) {
|
|
6
11
|
const terminalsByCwd = new Map();
|
|
7
12
|
const terminalsById = new Map();
|
|
8
13
|
const terminalExitUnsubscribeById = new Map();
|
|
9
14
|
const terminalTitleUnsubscribeById = new Map();
|
|
15
|
+
const terminalActivityUnsubscribeById = new Map();
|
|
16
|
+
const terminalActivityTokenById = new Map();
|
|
10
17
|
const terminalsChangedListeners = new Set();
|
|
18
|
+
const terminalActivityListeners = new Set();
|
|
19
|
+
const terminalWorkspaceContributionChangedListeners = new Set();
|
|
11
20
|
const defaultEnvByRootCwd = new Map();
|
|
12
|
-
function assertAbsolutePath(cwd) {
|
|
13
|
-
if (!posix.isAbsolute(cwd) && !win32.isAbsolute(cwd)) {
|
|
14
|
-
throw new Error("cwd must be absolute path");
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
21
|
function removeSessionById(id, options) {
|
|
18
22
|
const session = terminalsById.get(id);
|
|
19
23
|
if (!session) {
|
|
@@ -29,7 +33,13 @@ export function createTerminalManager() {
|
|
|
29
33
|
unsubscribeTitle();
|
|
30
34
|
terminalTitleUnsubscribeById.delete(id);
|
|
31
35
|
}
|
|
36
|
+
const unsubscribeActivity = terminalActivityUnsubscribeById.get(id);
|
|
37
|
+
if (unsubscribeActivity) {
|
|
38
|
+
unsubscribeActivity();
|
|
39
|
+
terminalActivityUnsubscribeById.delete(id);
|
|
40
|
+
}
|
|
32
41
|
terminalsById.delete(id);
|
|
42
|
+
terminalActivityTokenById.delete(id);
|
|
33
43
|
const terminals = terminalsByCwd.get(session.cwd);
|
|
34
44
|
if (terminals) {
|
|
35
45
|
const index = terminals.findIndex((terminal) => terminal.id === id);
|
|
@@ -43,6 +53,15 @@ export function createTerminalManager() {
|
|
|
43
53
|
if (options.kill) {
|
|
44
54
|
session.kill();
|
|
45
55
|
}
|
|
56
|
+
const previousActivity = session.getActivity();
|
|
57
|
+
const previousBucket = deriveTerminalActivityStatusBucket(previousActivity);
|
|
58
|
+
if (previousBucket !== null) {
|
|
59
|
+
emitTerminalWorkspaceContributionChanged({
|
|
60
|
+
terminalId: session.id,
|
|
61
|
+
cwd: session.cwd,
|
|
62
|
+
workspaceId: session.workspaceId,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
46
65
|
emitTerminalsChanged({ cwd: session.cwd });
|
|
47
66
|
}
|
|
48
67
|
function resolveDefaultEnvForCwd(cwd) {
|
|
@@ -67,8 +86,22 @@ export function createTerminalManager() {
|
|
|
67
86
|
const unsubscribeTitle = session.onTitleChange(() => {
|
|
68
87
|
emitTerminalsChanged({ cwd: session.cwd });
|
|
69
88
|
});
|
|
89
|
+
const unsubscribeActivity = session.onActivityChange((transition) => {
|
|
90
|
+
emitTerminalActivityTransition({ session, transition });
|
|
91
|
+
emitTerminalsChanged({ cwd: session.cwd });
|
|
92
|
+
const previousBucket = deriveTerminalActivityStatusBucket(transition.previous);
|
|
93
|
+
const nextBucket = deriveTerminalActivityStatusBucket(transition.activity);
|
|
94
|
+
if (previousBucket !== nextBucket) {
|
|
95
|
+
emitTerminalWorkspaceContributionChanged({
|
|
96
|
+
terminalId: session.id,
|
|
97
|
+
cwd: session.cwd,
|
|
98
|
+
workspaceId: session.workspaceId,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
});
|
|
70
102
|
terminalExitUnsubscribeById.set(session.id, unsubscribeExit);
|
|
71
103
|
terminalTitleUnsubscribeById.set(session.id, unsubscribeTitle);
|
|
104
|
+
terminalActivityUnsubscribeById.set(session.id, unsubscribeActivity);
|
|
72
105
|
return session;
|
|
73
106
|
}
|
|
74
107
|
function toTerminalListItem(input) {
|
|
@@ -76,7 +109,9 @@ export function createTerminalManager() {
|
|
|
76
109
|
id: input.session.id,
|
|
77
110
|
name: input.session.name,
|
|
78
111
|
cwd: input.session.cwd,
|
|
112
|
+
workspaceId: input.session.workspaceId,
|
|
79
113
|
title: input.session.getTitle(),
|
|
114
|
+
activity: input.session.getActivity(),
|
|
80
115
|
};
|
|
81
116
|
}
|
|
82
117
|
function emitTerminalsChanged(input) {
|
|
@@ -97,8 +132,39 @@ export function createTerminalManager() {
|
|
|
97
132
|
}
|
|
98
133
|
}
|
|
99
134
|
}
|
|
135
|
+
function emitTerminalActivityTransition(input) {
|
|
136
|
+
if (terminalActivityListeners.size === 0) {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
const event = {
|
|
140
|
+
terminalId: input.session.id,
|
|
141
|
+
name: input.session.name,
|
|
142
|
+
cwd: input.session.cwd,
|
|
143
|
+
workspaceId: input.session.workspaceId,
|
|
144
|
+
activity: input.transition.activity,
|
|
145
|
+
previous: input.transition.previous,
|
|
146
|
+
};
|
|
147
|
+
for (const listener of terminalActivityListeners) {
|
|
148
|
+
try {
|
|
149
|
+
listener(event);
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
// no-op
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
function emitTerminalWorkspaceContributionChanged(event) {
|
|
157
|
+
for (const listener of terminalWorkspaceContributionChangedListeners) {
|
|
158
|
+
try {
|
|
159
|
+
listener(event);
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
// no-op
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
100
166
|
return {
|
|
101
|
-
async getTerminals(cwd) {
|
|
167
|
+
async getTerminals(cwd, options) {
|
|
102
168
|
assertAbsolutePath(cwd);
|
|
103
169
|
// Terminals are bucketed by exact cwd, but an agent can open a terminal in
|
|
104
170
|
// a subdirectory of the workspace. A query for the workspace root must
|
|
@@ -109,6 +175,12 @@ export function createTerminalManager() {
|
|
|
109
175
|
sessions.push(...bucketSessions);
|
|
110
176
|
}
|
|
111
177
|
}
|
|
178
|
+
// When the query carries a workspaceId, two workspaces sharing a cwd must
|
|
179
|
+
// not see each other's terminals. A missing owner is not workspace
|
|
180
|
+
// membership; unscoped callers can still list those legacy terminals.
|
|
181
|
+
if (options?.workspaceId !== undefined) {
|
|
182
|
+
return sessions.filter((session) => session.workspaceId === options.workspaceId);
|
|
183
|
+
}
|
|
112
184
|
return sessions;
|
|
113
185
|
},
|
|
114
186
|
async createTerminal(options) {
|
|
@@ -117,15 +189,35 @@ export function createTerminalManager() {
|
|
|
117
189
|
const defaultName = `Terminal ${terminals.length + 1}`;
|
|
118
190
|
const inheritedEnv = resolveDefaultEnvForCwd(options.cwd);
|
|
119
191
|
const mergedEnv = inheritedEnv || options.env ? { ...inheritedEnv, ...options.env } : undefined;
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
192
|
+
const terminalId = options.id ?? randomUUID();
|
|
193
|
+
const activityToken = options.activityToken ?? createActivityToken();
|
|
194
|
+
const terminalActivityUrl = options.activityUrl === undefined
|
|
195
|
+
? (managerOptions.getTerminalActivityUrl?.() ?? null)
|
|
196
|
+
: options.activityUrl;
|
|
197
|
+
const activityEnv = {
|
|
198
|
+
PASEO_TERMINAL_ID: terminalId,
|
|
199
|
+
PASEO_ACTIVITY_TOKEN: activityToken,
|
|
200
|
+
...(terminalActivityUrl ? { PASEO_TERMINAL_ACTIVITY_URL: terminalActivityUrl } : {}),
|
|
201
|
+
};
|
|
202
|
+
terminalActivityTokenById.set(terminalId, activityToken);
|
|
203
|
+
let session;
|
|
204
|
+
try {
|
|
205
|
+
session = registerSession(await createTerminal({
|
|
206
|
+
id: terminalId,
|
|
207
|
+
cwd: options.cwd,
|
|
208
|
+
workspaceId: options.workspaceId,
|
|
209
|
+
name: options.name ?? defaultName,
|
|
210
|
+
...(options.title ? { title: options.title } : {}),
|
|
211
|
+
...(options.command ? { command: options.command } : {}),
|
|
212
|
+
...(options.args ? { args: options.args } : {}),
|
|
213
|
+
...(mergedEnv ? { env: mergedEnv } : {}),
|
|
214
|
+
activityEnv,
|
|
215
|
+
}));
|
|
216
|
+
}
|
|
217
|
+
catch (error) {
|
|
218
|
+
terminalActivityTokenById.delete(terminalId);
|
|
219
|
+
throw error;
|
|
220
|
+
}
|
|
129
221
|
terminals.push(session);
|
|
130
222
|
terminalsByCwd.set(options.cwd, terminals);
|
|
131
223
|
emitTerminalsChanged({ cwd: options.cwd });
|
|
@@ -135,6 +227,13 @@ export function createTerminalManager() {
|
|
|
135
227
|
assertAbsolutePath(options.cwd);
|
|
136
228
|
defaultEnvByRootCwd.set(resolve(options.cwd), { ...options.env });
|
|
137
229
|
},
|
|
230
|
+
validateTerminalActivityToken(terminalId, token) {
|
|
231
|
+
const expected = terminalActivityTokenById.get(terminalId);
|
|
232
|
+
if (!expected) {
|
|
233
|
+
return "unknown";
|
|
234
|
+
}
|
|
235
|
+
return expected === token ? "valid" : "invalid";
|
|
236
|
+
},
|
|
138
237
|
getTerminal(id) {
|
|
139
238
|
return terminalsById.get(id);
|
|
140
239
|
},
|
|
@@ -149,6 +248,21 @@ export function createTerminalManager() {
|
|
|
149
248
|
session.setTitle(title);
|
|
150
249
|
return true;
|
|
151
250
|
},
|
|
251
|
+
async setTerminalActivity(id, state) {
|
|
252
|
+
const session = terminalsById.get(id);
|
|
253
|
+
if (!session) {
|
|
254
|
+
return false;
|
|
255
|
+
}
|
|
256
|
+
session.setActivity(state);
|
|
257
|
+
return true;
|
|
258
|
+
},
|
|
259
|
+
async clearTerminalAttention(id) {
|
|
260
|
+
const session = terminalsById.get(id);
|
|
261
|
+
if (!session) {
|
|
262
|
+
return false;
|
|
263
|
+
}
|
|
264
|
+
return session.clearActivityAttention();
|
|
265
|
+
},
|
|
152
266
|
killTerminal(id) {
|
|
153
267
|
removeSessionById(id, { kill: true });
|
|
154
268
|
},
|
|
@@ -188,6 +302,18 @@ export function createTerminalManager() {
|
|
|
188
302
|
terminalsChangedListeners.delete(listener);
|
|
189
303
|
};
|
|
190
304
|
},
|
|
305
|
+
subscribeTerminalActivity(listener) {
|
|
306
|
+
terminalActivityListeners.add(listener);
|
|
307
|
+
return () => {
|
|
308
|
+
terminalActivityListeners.delete(listener);
|
|
309
|
+
};
|
|
310
|
+
},
|
|
311
|
+
subscribeTerminalWorkspaceContributionChanged(listener) {
|
|
312
|
+
terminalWorkspaceContributionChangedListeners.add(listener);
|
|
313
|
+
return () => {
|
|
314
|
+
terminalWorkspaceContributionChangedListeners.delete(listener);
|
|
315
|
+
};
|
|
316
|
+
},
|
|
191
317
|
};
|
|
192
318
|
}
|
|
193
319
|
//# sourceMappingURL=terminal-manager.js.map
|
|
@@ -10,19 +10,23 @@ export interface TerminalOutputCoalescerFlush {
|
|
|
10
10
|
export interface TerminalOutputCoalescerOptions {
|
|
11
11
|
timers: TerminalOutputCoalescerTimers;
|
|
12
12
|
flushDelayMs?: number;
|
|
13
|
+
now?: () => number;
|
|
13
14
|
onFlush: (payload: TerminalOutputCoalescerFlush) => void;
|
|
14
15
|
}
|
|
15
16
|
export declare class TerminalOutputCoalescer {
|
|
16
17
|
private readonly timers;
|
|
17
18
|
private readonly flushDelayMs;
|
|
19
|
+
private readonly now;
|
|
18
20
|
private readonly onFlush;
|
|
19
21
|
private chunks;
|
|
20
22
|
private bytes;
|
|
21
23
|
private chars;
|
|
22
24
|
private flushTimer;
|
|
25
|
+
private lastFlushAt;
|
|
23
26
|
constructor(options: TerminalOutputCoalescerOptions);
|
|
24
27
|
handle(data: string): void;
|
|
25
28
|
flush(): void;
|
|
29
|
+
markFlushed(): void;
|
|
26
30
|
dispose(): void;
|
|
27
31
|
private clearTimer;
|
|
28
32
|
private clearPending;
|
|
@@ -5,8 +5,10 @@ export class TerminalOutputCoalescer {
|
|
|
5
5
|
this.bytes = 0;
|
|
6
6
|
this.chars = 0;
|
|
7
7
|
this.flushTimer = null;
|
|
8
|
+
this.lastFlushAt = null;
|
|
8
9
|
this.timers = options.timers;
|
|
9
10
|
this.flushDelayMs = options.flushDelayMs ?? DEFAULT_FLUSH_DELAY_MS;
|
|
11
|
+
this.now = options.now ?? Date.now;
|
|
10
12
|
this.onFlush = options.onFlush;
|
|
11
13
|
}
|
|
12
14
|
handle(data) {
|
|
@@ -17,7 +19,15 @@ export class TerminalOutputCoalescer {
|
|
|
17
19
|
this.chunks.push(chunk);
|
|
18
20
|
this.bytes += chunk.byteLength;
|
|
19
21
|
this.chars += data.length;
|
|
22
|
+
// Leading edge: if nothing is pending and no flush happened within the last
|
|
23
|
+
// flushDelayMs, flush immediately so interactive echo isn't delayed a full
|
|
24
|
+
// window. Otherwise accumulate and let the trailing timer drain the burst.
|
|
20
25
|
if (!this.flushTimer) {
|
|
26
|
+
const elapsed = this.lastFlushAt === null ? Number.POSITIVE_INFINITY : this.now() - this.lastFlushAt;
|
|
27
|
+
if (elapsed >= this.flushDelayMs) {
|
|
28
|
+
this.flush();
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
21
31
|
this.flushTimer = this.timers.setTimeout(() => {
|
|
22
32
|
this.flushTimer = null;
|
|
23
33
|
this.flush();
|
|
@@ -33,8 +43,16 @@ export class TerminalOutputCoalescer {
|
|
|
33
43
|
const bytes = this.bytes;
|
|
34
44
|
const chars = this.chars;
|
|
35
45
|
this.clearPending();
|
|
46
|
+
this.lastFlushAt = this.now();
|
|
36
47
|
this.onFlush({ payload, bytes, chars });
|
|
37
48
|
}
|
|
49
|
+
// Record that a frame was emitted out-of-band on this stream (e.g. a snapshot
|
|
50
|
+
// frame sent directly, bypassing the coalescer). This keeps the next handled
|
|
51
|
+
// chunk on the trailing path instead of leading-edge flushing it back-to-back
|
|
52
|
+
// with that frame, preserving the small gap consumers rely on after a snapshot.
|
|
53
|
+
markFlushed() {
|
|
54
|
+
this.lastFlushAt = this.now();
|
|
55
|
+
}
|
|
38
56
|
dispose() {
|
|
39
57
|
this.clearTimer();
|
|
40
58
|
this.clearPending();
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { SubscribeTerminalRequest } from "@getpaseo/protocol/messages";
|
|
2
2
|
import type { TerminalStateSnapshot, TerminalStateSnapshotOptions } from "./terminal.js";
|
|
3
3
|
export declare const MAX_TERMINAL_OUTPUT_FRAME_BYTES: number;
|
|
4
|
+
export declare const MAX_CLIENT_BUFFERED_BYTES: number;
|
|
4
5
|
export type TerminalRestoreOptions = NonNullable<SubscribeTerminalRequest["restore"]>;
|
|
5
6
|
export type TerminalSubscriptionSnapshotMode = "state" | "ready";
|
|
6
7
|
export declare function resolveTerminalSubscriptionSnapshotMode(restore: TerminalRestoreOptions | undefined): TerminalSubscriptionSnapshotMode;
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { TerminalStreamOpcode, encodeTerminalSnapshotPayload, encodeTerminalStreamFrame, } from "@getpaseo/protocol/binary-frames/index";
|
|
2
2
|
import { renderTerminalSnapshotToAnsi } from "@getpaseo/protocol/terminal-snapshot";
|
|
3
3
|
export const MAX_TERMINAL_OUTPUT_FRAME_BYTES = 256 * 1024;
|
|
4
|
+
// A client is only forced onto the snapshot catch-up path once its transport is
|
|
5
|
+
// genuinely backed up — measured by the socket's bufferedAmount, not by how much
|
|
6
|
+
// the terminal has produced. Set well above any normal in-flight burst (a
|
|
7
|
+
// keeping-up client drains continuously and never approaches this) but low
|
|
8
|
+
// enough that a stalled socket crosses it long before a large burst finishes.
|
|
9
|
+
export const MAX_CLIENT_BUFFERED_BYTES = 4 * 1024 * 1024;
|
|
4
10
|
const DEFAULT_VISIBLE_RESTORE_SCROLLBACK_LINES = 200;
|
|
5
11
|
const MAX_VISIBLE_RESTORE_SCROLLBACK_LINES = 500;
|
|
6
12
|
export function resolveTerminalSubscriptionSnapshotMode(restore) {
|
|
@@ -11,6 +11,7 @@ export interface TerminalSessionControllerOptions {
|
|
|
11
11
|
sessionLogger: pino.Logger;
|
|
12
12
|
listTerminalWorkspaceRoots?: () => Promise<readonly string[]>;
|
|
13
13
|
clientSupportsWrapReflow?: () => boolean;
|
|
14
|
+
getClientBufferedAmount?: () => number | null;
|
|
14
15
|
}
|
|
15
16
|
export interface TerminalSessionControllerMetrics {
|
|
16
17
|
directorySubscriptionCount: number;
|
|
@@ -25,6 +26,7 @@ export declare class TerminalSessionController {
|
|
|
25
26
|
private readonly sessionLogger;
|
|
26
27
|
private readonly listTerminalWorkspaceRoots;
|
|
27
28
|
private readonly clientSupportsWrapReflow;
|
|
29
|
+
private readonly getClientBufferedAmount;
|
|
28
30
|
private readonly subscribedDirectories;
|
|
29
31
|
private unsubscribeTerminalsChanged;
|
|
30
32
|
private readonly exitSubscriptions;
|
|
@@ -40,7 +42,7 @@ export declare class TerminalSessionController {
|
|
|
40
42
|
terminalId: string;
|
|
41
43
|
success: boolean;
|
|
42
44
|
};
|
|
43
|
-
|
|
45
|
+
killTerminalsForWorkspace(workspaceId: string): Promise<void>;
|
|
44
46
|
dispose(): void;
|
|
45
47
|
private ensureExitSubscription;
|
|
46
48
|
private handleTerminalExited;
|
|
@@ -49,7 +51,7 @@ export declare class TerminalSessionController {
|
|
|
49
51
|
private handleTerminalsChanged;
|
|
50
52
|
private handleSubscribeTerminalsRequest;
|
|
51
53
|
private handleUnsubscribeTerminalsRequest;
|
|
52
|
-
private
|
|
54
|
+
private emitTerminalsSnapshotForSubscription;
|
|
53
55
|
private handleListTerminalsRequest;
|
|
54
56
|
private getAllTerminalSessions;
|
|
55
57
|
private getTerminalsForWorkspaceRoot;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { killTerminalsForWorkspace as killWorkspaceTerminals } from "../server/workspace-archive-service.js";
|
|
2
2
|
import { TerminalStreamOpcode, decodeTerminalResizePayload, encodeTerminalStreamFrame, } from "@getpaseo/protocol/binary-frames/index";
|
|
3
3
|
import { TerminalOutputCoalescer } from "./terminal-output-coalescer.js";
|
|
4
|
-
import { MAX_TERMINAL_OUTPUT_FRAME_BYTES, encodeLegacyTerminalSnapshotFrame, encodeTerminalRestoreFrame, resolveRestoreAfterOutputOverflow, resolveTerminalRestoreSnapshotOptions, resolveTerminalSubscriptionSnapshotMode, } from "./terminal-restore.js";
|
|
4
|
+
import { MAX_CLIENT_BUFFERED_BYTES, MAX_TERMINAL_OUTPUT_FRAME_BYTES, encodeLegacyTerminalSnapshotFrame, encodeTerminalRestoreFrame, resolveRestoreAfterOutputOverflow, resolveTerminalRestoreSnapshotOptions, resolveTerminalSubscriptionSnapshotMode, } from "./terminal-restore.js";
|
|
5
|
+
import { terminalSubscriptionKey } from "@getpaseo/protocol/terminal-subscription-key";
|
|
5
6
|
const MAX_TERMINAL_STREAM_SLOTS = 256;
|
|
6
7
|
const TERMINAL_MESSAGE_TYPES = new Set([
|
|
7
8
|
"subscribe_terminals_request",
|
|
@@ -17,7 +18,11 @@ const TERMINAL_MESSAGE_TYPES = new Set([
|
|
|
17
18
|
]);
|
|
18
19
|
export class TerminalSessionController {
|
|
19
20
|
constructor(options) {
|
|
20
|
-
|
|
21
|
+
// A subscription is scoped to a (cwd, workspaceId) pair, keyed by
|
|
22
|
+
// terminalSubscriptionKey: two workspaces sharing a cwd subscribe and unsub
|
|
23
|
+
// independently, and each only receives its own workspace's terminals. The
|
|
24
|
+
// workspaceId is absent for old clients, which key to the cwd alone.
|
|
25
|
+
this.subscribedDirectories = new Map();
|
|
21
26
|
this.unsubscribeTerminalsChanged = null;
|
|
22
27
|
this.exitSubscriptions = new Map();
|
|
23
28
|
this.activeStreams = new Map();
|
|
@@ -31,6 +36,7 @@ export class TerminalSessionController {
|
|
|
31
36
|
this.sessionLogger = options.sessionLogger;
|
|
32
37
|
this.listTerminalWorkspaceRoots = options.listTerminalWorkspaceRoots ?? (async () => []);
|
|
33
38
|
this.clientSupportsWrapReflow = options.clientSupportsWrapReflow ?? (() => false);
|
|
39
|
+
this.getClientBufferedAmount = options.getClientBufferedAmount ?? (() => 0);
|
|
34
40
|
}
|
|
35
41
|
start() {
|
|
36
42
|
if (!this.terminalManager) {
|
|
@@ -120,14 +126,12 @@ export class TerminalSessionController {
|
|
|
120
126
|
this.killTracked(terminalId, { emitExit: true });
|
|
121
127
|
return { terminalId, success: true };
|
|
122
128
|
}
|
|
123
|
-
async
|
|
124
|
-
return
|
|
125
|
-
isPathWithinRoot: (pathRoot, candidatePath) => this.isPathWithinRoot(pathRoot, candidatePath),
|
|
126
|
-
killTrackedTerminal: (terminalId, options) => this.killTracked(terminalId, options),
|
|
129
|
+
async killTerminalsForWorkspace(workspaceId) {
|
|
130
|
+
return killWorkspaceTerminals({
|
|
127
131
|
detachTerminalStream: (terminalId, options) => void this.detachStream(terminalId, options),
|
|
128
132
|
sessionLogger: this.sessionLogger,
|
|
129
133
|
terminalManager: this.terminalManager,
|
|
130
|
-
},
|
|
134
|
+
}, workspaceId);
|
|
131
135
|
}
|
|
132
136
|
dispose() {
|
|
133
137
|
if (this.unsubscribeTerminalsChanged) {
|
|
@@ -171,10 +175,13 @@ export class TerminalSessionController {
|
|
|
171
175
|
}
|
|
172
176
|
toTerminalInfo(terminal) {
|
|
173
177
|
const title = terminal.getTitle();
|
|
178
|
+
const activity = terminal.getActivity();
|
|
174
179
|
return {
|
|
175
180
|
id: terminal.id,
|
|
176
181
|
name: terminal.name,
|
|
182
|
+
workspaceId: terminal.workspaceId,
|
|
177
183
|
...(title ? { title } : {}),
|
|
184
|
+
activity,
|
|
178
185
|
};
|
|
179
186
|
}
|
|
180
187
|
async handleTerminalsChanged(event) {
|
|
@@ -183,37 +190,39 @@ export class TerminalSessionController {
|
|
|
183
190
|
// or above the terminal's cwd, keyed by that root, carrying the full
|
|
184
191
|
// aggregated list — so the client's cache replacement doesn't drop the
|
|
185
192
|
// terminals that live directly at the root.
|
|
186
|
-
const
|
|
187
|
-
for (const
|
|
188
|
-
await this.
|
|
193
|
+
const matchingSubscriptions = Array.from(this.subscribedDirectories.values()).filter((subscription) => this.isPathWithinRoot(subscription.cwd, event.cwd));
|
|
194
|
+
for (const subscription of matchingSubscriptions) {
|
|
195
|
+
await this.emitTerminalsSnapshotForSubscription(subscription);
|
|
189
196
|
}
|
|
190
197
|
}
|
|
191
198
|
handleSubscribeTerminalsRequest(msg) {
|
|
192
|
-
|
|
193
|
-
|
|
199
|
+
const subscription = { cwd: msg.cwd, workspaceId: msg.workspaceId };
|
|
200
|
+
this.subscribedDirectories.set(terminalSubscriptionKey(msg.cwd, msg.workspaceId), subscription);
|
|
201
|
+
void this.emitTerminalsSnapshotForSubscription(subscription);
|
|
194
202
|
}
|
|
195
203
|
handleUnsubscribeTerminalsRequest(msg) {
|
|
196
|
-
this.subscribedDirectories.delete(msg.cwd);
|
|
204
|
+
this.subscribedDirectories.delete(terminalSubscriptionKey(msg.cwd, msg.workspaceId));
|
|
197
205
|
}
|
|
198
|
-
async
|
|
199
|
-
|
|
206
|
+
async emitTerminalsSnapshotForSubscription(subscription) {
|
|
207
|
+
const key = terminalSubscriptionKey(subscription.cwd, subscription.workspaceId);
|
|
208
|
+
if (!this.terminalManager || !this.subscribedDirectories.has(key)) {
|
|
200
209
|
return;
|
|
201
210
|
}
|
|
202
211
|
try {
|
|
203
|
-
const terminals = await this.getTerminalsForWorkspaceRoot(cwd);
|
|
212
|
+
const terminals = await this.getTerminalsForWorkspaceRoot(subscription.cwd, subscription.workspaceId);
|
|
204
213
|
for (const terminal of terminals) {
|
|
205
214
|
this.ensureExitSubscription(terminal);
|
|
206
215
|
}
|
|
207
|
-
if (!this.subscribedDirectories.has(
|
|
216
|
+
if (!this.subscribedDirectories.has(key)) {
|
|
208
217
|
return;
|
|
209
218
|
}
|
|
210
219
|
this.emitTerminalsChangedSnapshot({
|
|
211
|
-
cwd,
|
|
220
|
+
cwd: subscription.cwd,
|
|
212
221
|
terminals: terminals.map((terminal) => this.toTerminalInfo(terminal)),
|
|
213
222
|
});
|
|
214
223
|
}
|
|
215
224
|
catch (error) {
|
|
216
|
-
this.sessionLogger.warn({ err: error, cwd }, "Failed to emit initial terminal snapshot");
|
|
225
|
+
this.sessionLogger.warn({ err: error, cwd: subscription.cwd }, "Failed to emit initial terminal snapshot");
|
|
217
226
|
}
|
|
218
227
|
}
|
|
219
228
|
async handleListTerminalsRequest(msg) {
|
|
@@ -230,7 +239,7 @@ export class TerminalSessionController {
|
|
|
230
239
|
}
|
|
231
240
|
try {
|
|
232
241
|
const terminals = typeof msg.cwd === "string"
|
|
233
|
-
? await this.getTerminalsForWorkspaceRoot(msg.cwd)
|
|
242
|
+
? await this.getTerminalsForWorkspaceRoot(msg.cwd, msg.workspaceId)
|
|
234
243
|
: await this.getAllTerminalSessions();
|
|
235
244
|
for (const terminal of terminals) {
|
|
236
245
|
this.ensureExitSubscription(terminal);
|
|
@@ -265,11 +274,11 @@ export class TerminalSessionController {
|
|
|
265
274
|
const terminalsByDirectory = await Promise.all(directories.map((cwd) => manager.getTerminals(cwd)));
|
|
266
275
|
return terminalsByDirectory.flat();
|
|
267
276
|
}
|
|
268
|
-
async getTerminalsForWorkspaceRoot(cwd) {
|
|
277
|
+
async getTerminalsForWorkspaceRoot(cwd, workspaceId) {
|
|
269
278
|
if (!this.terminalManager) {
|
|
270
279
|
return [];
|
|
271
280
|
}
|
|
272
|
-
const terminals = await this.terminalManager.getTerminals(cwd);
|
|
281
|
+
const terminals = await this.terminalManager.getTerminals(cwd, { workspaceId });
|
|
273
282
|
const workspaceRoots = await this.listTerminalWorkspaceRoots();
|
|
274
283
|
if (workspaceRoots.length === 0) {
|
|
275
284
|
return terminals;
|
|
@@ -322,8 +331,20 @@ export class TerminalSessionController {
|
|
|
322
331
|
});
|
|
323
332
|
return;
|
|
324
333
|
}
|
|
334
|
+
if (!msg.workspaceId) {
|
|
335
|
+
this.emit({
|
|
336
|
+
type: "create_terminal_response",
|
|
337
|
+
payload: {
|
|
338
|
+
terminal: null,
|
|
339
|
+
error: "workspaceId is required",
|
|
340
|
+
requestId: msg.requestId,
|
|
341
|
+
},
|
|
342
|
+
});
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
325
345
|
const session = await this.terminalManager.createTerminal({
|
|
326
346
|
cwd: msg.cwd,
|
|
347
|
+
workspaceId: msg.workspaceId,
|
|
327
348
|
name: msg.name,
|
|
328
349
|
command: msg.command,
|
|
329
350
|
args: msg.args,
|
|
@@ -336,7 +357,9 @@ export class TerminalSessionController {
|
|
|
336
357
|
id: session.id,
|
|
337
358
|
name: session.name,
|
|
338
359
|
cwd: session.cwd,
|
|
360
|
+
workspaceId: session.workspaceId,
|
|
339
361
|
...(session.getTitle() ? { title: session.getTitle() } : {}),
|
|
362
|
+
activity: session.getActivity(),
|
|
340
363
|
},
|
|
341
364
|
error: null,
|
|
342
365
|
requestId: msg.requestId,
|
|
@@ -571,7 +594,19 @@ export class TerminalSessionController {
|
|
|
571
594
|
return;
|
|
572
595
|
}
|
|
573
596
|
activeStream.outputBytesSinceSnapshot += payload.byteLength;
|
|
574
|
-
|
|
597
|
+
// Catch up via a snapshot only when the client is BOTH far behind in
|
|
598
|
+
// produced output AND actually backed up on the wire. A client that
|
|
599
|
+
// keeps draining reports ~0 buffered, so it streams continuously even
|
|
600
|
+
// past the byte threshold. outputBytesSinceSnapshot keeps accumulating
|
|
601
|
+
// in that case — it's harmless, it only gates the snapshot decision at
|
|
602
|
+
// the instant backpressure appears, and trySendSnapshot resets it to 0.
|
|
603
|
+
// A null reading means the transport exposes no backpressure signal
|
|
604
|
+
// (e.g. the multiplexed relay socket); there we can't tell a slow client
|
|
605
|
+
// from a fast one, so fall back unconditionally at the byte threshold to
|
|
606
|
+
// keep a slow relay client from falling unboundedly behind.
|
|
607
|
+
const clientBufferedAmount = this.getClientBufferedAmount();
|
|
608
|
+
if (activeStream.outputBytesSinceSnapshot > MAX_TERMINAL_OUTPUT_FRAME_BYTES &&
|
|
609
|
+
(clientBufferedAmount === null || clientBufferedAmount > MAX_CLIENT_BUFFERED_BYTES)) {
|
|
575
610
|
activeStream.restore = resolveRestoreAfterOutputOverflow(activeStream.restore);
|
|
576
611
|
activeStream.needsSnapshot = true;
|
|
577
612
|
void this.trySendSnapshot(activeStream);
|
|
@@ -671,6 +706,9 @@ export class TerminalSessionController {
|
|
|
671
706
|
slot: activeStream.slot,
|
|
672
707
|
snapshot,
|
|
673
708
|
}));
|
|
709
|
+
// The snapshot frame went out-of-band; keep the replay that follows on the
|
|
710
|
+
// coalescer's trailing path so it doesn't flush back-to-back with it.
|
|
711
|
+
activeStream.outputCoalescer.markFlushed();
|
|
674
712
|
return { shouldContinue: true, replayRevision: snapshot.revision };
|
|
675
713
|
}
|
|
676
714
|
async emitRestoreSnapshot(activeStream, terminalManager, restore) {
|
|
@@ -693,6 +731,9 @@ export class TerminalSessionController {
|
|
|
693
731
|
slot: activeStream.slot,
|
|
694
732
|
snapshot,
|
|
695
733
|
}));
|
|
734
|
+
// The restore frame went out-of-band; keep the replay that follows on the
|
|
735
|
+
// coalescer's trailing path so it doesn't flush back-to-back with it.
|
|
736
|
+
activeStream.outputCoalescer.markFlushed();
|
|
696
737
|
return { shouldContinue: true, replayRevision: snapshot.revision };
|
|
697
738
|
}
|
|
698
739
|
replayTerminalOutputAfterSnapshot(activeStream, terminal, replayRevision) {
|