@getpaseo/server 0.1.96 → 0.1.97-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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/create-agent-title.d.ts +2 -0
- package/dist/server/server/agent/create-agent-title.js +5 -0
- 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 +125 -64
- 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 +74 -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 +33 -6
- package/dist/server/server/session.js +691 -202
- 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 +29 -7
- 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 +8 -3
- package/dist/server/utils/build-metadata-prompt.js +10 -9
- 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
- package/dist/server/utils/wrap-user-instructions.d.ts +0 -2
- package/dist/server/utils/wrap-user-instructions.js +0 -13
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { TerminalState } from "@getpaseo/protocol/messages";
|
|
2
|
+
import type { TerminalActivity, TerminalActivityState } from "@getpaseo/protocol/terminal-activity";
|
|
2
3
|
export interface TerminalExitInfo {
|
|
3
4
|
exitCode: number | null;
|
|
4
5
|
signal: number | null;
|
|
@@ -10,6 +11,7 @@ export interface TerminalCommandFinishedInfo {
|
|
|
10
11
|
export interface TerminalStateSnapshot {
|
|
11
12
|
state: TerminalState;
|
|
12
13
|
revision: number;
|
|
14
|
+
replayPreamble?: string;
|
|
13
15
|
}
|
|
14
16
|
export interface TerminalStateSnapshotOptions {
|
|
15
17
|
scrollbackLines?: number;
|
|
@@ -43,19 +45,26 @@ export type ServerMessage = {
|
|
|
43
45
|
} | {
|
|
44
46
|
type: "snapshotReady";
|
|
45
47
|
revision?: number;
|
|
48
|
+
replayPreamble?: string;
|
|
46
49
|
} | {
|
|
47
50
|
type: "titleChange";
|
|
48
51
|
title?: string;
|
|
49
52
|
};
|
|
53
|
+
export interface TerminalActivityTransition {
|
|
54
|
+
activity: TerminalActivity | null;
|
|
55
|
+
previous: TerminalActivity | null;
|
|
56
|
+
}
|
|
50
57
|
export interface TerminalSession {
|
|
51
58
|
id: string;
|
|
52
59
|
name: string;
|
|
53
60
|
cwd: string;
|
|
61
|
+
workspaceId: string;
|
|
54
62
|
send(msg: ClientMessage): void;
|
|
55
63
|
subscribe(listener: (msg: ServerMessage) => void, options?: TerminalSubscribeOptions): () => void;
|
|
56
64
|
onExit(listener: (info: TerminalExitInfo) => void): () => void;
|
|
57
65
|
onCommandFinished(listener: (info: TerminalCommandFinishedInfo) => void): () => void;
|
|
58
66
|
onTitleChange(listener: (title?: string) => void): () => void;
|
|
67
|
+
onActivityChange(listener: (transition: TerminalActivityTransition) => void): () => void;
|
|
59
68
|
getSize(): {
|
|
60
69
|
rows: number;
|
|
61
70
|
cols: number;
|
|
@@ -64,6 +73,9 @@ export interface TerminalSession {
|
|
|
64
73
|
getStateSnapshot(options?: TerminalStateSnapshotOptions): TerminalStateSnapshot;
|
|
65
74
|
getReplayPreamble(): string;
|
|
66
75
|
getTitle(): string | undefined;
|
|
76
|
+
getActivity(): TerminalActivity | null;
|
|
77
|
+
setActivity(state: TerminalActivityState): void;
|
|
78
|
+
clearActivityAttention(): boolean;
|
|
67
79
|
setTitle(title: string): void;
|
|
68
80
|
getExitInfo(): TerminalExitInfo | null;
|
|
69
81
|
kill(): void;
|
|
@@ -75,8 +87,10 @@ export interface TerminalSession {
|
|
|
75
87
|
export interface CreateTerminalOptions {
|
|
76
88
|
id?: string;
|
|
77
89
|
cwd: string;
|
|
90
|
+
workspaceId: string;
|
|
78
91
|
shell?: string;
|
|
79
92
|
env?: Record<string, string>;
|
|
93
|
+
activityEnv?: Record<string, string>;
|
|
80
94
|
rows?: number;
|
|
81
95
|
cols?: number;
|
|
82
96
|
name?: string;
|
|
@@ -88,6 +102,8 @@ interface BuildTerminalEnvironmentInput {
|
|
|
88
102
|
shell: string;
|
|
89
103
|
env: Record<string, string>;
|
|
90
104
|
zshShellIntegrationDir?: string;
|
|
105
|
+
paseoCliBinDir?: string | null;
|
|
106
|
+
paseoHookCliPath?: string | null;
|
|
91
107
|
}
|
|
92
108
|
interface EnsureNodePtySpawnHelperExecutableOptions {
|
|
93
109
|
packageRoot?: string;
|
|
@@ -100,7 +116,33 @@ export declare function resolveDefaultTerminalShell(options?: {
|
|
|
100
116
|
platform?: NodeJS.Platform;
|
|
101
117
|
env?: NodeJS.ProcessEnv;
|
|
102
118
|
}): string;
|
|
119
|
+
export interface ResolvedTerminalCommand {
|
|
120
|
+
command: string;
|
|
121
|
+
args: string[];
|
|
122
|
+
}
|
|
123
|
+
export interface ResolveTerminalSpawnCommandOptions {
|
|
124
|
+
platform?: NodeJS.Platform;
|
|
125
|
+
env?: Record<string, string | undefined>;
|
|
126
|
+
resolveExecutable?: (name: string) => Promise<string | null>;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Resolve a terminal profile command (e.g. `claude`) into something node-pty's
|
|
130
|
+
* conpty backend can actually launch on Windows.
|
|
131
|
+
*
|
|
132
|
+
* On Windows, conpty's underlying `CreateProcess` does not apply `PATHEXT`, so a
|
|
133
|
+
* bare `claude` (installed by npm as `claude.cmd`) fails with `error code: 2`
|
|
134
|
+
* (`ERROR_FILE_NOT_FOUND`). Worse, conpty completes the spawn asynchronously on
|
|
135
|
+
* its own conout worker thread, so that failure surfaces as an uncaught
|
|
136
|
+
* exception that takes down the whole terminal worker process. Resolving the
|
|
137
|
+
* real path up front — and routing `.cmd`/`.bat` shims through `cmd.exe /c`
|
|
138
|
+
* (node-pty has no `shell` option) — keeps the profile launchable.
|
|
139
|
+
*
|
|
140
|
+
* Non-Windows and the default-shell path (no explicit command) are unchanged.
|
|
141
|
+
*/
|
|
142
|
+
export declare function resolveTerminalSpawnCommand(command: string, args: string[], options?: ResolveTerminalSpawnCommandOptions): Promise<ResolvedTerminalCommand>;
|
|
103
143
|
export declare function resolveZshShellIntegrationDir(): string;
|
|
144
|
+
export declare function resolvePaseoCliBinDir(): string | null;
|
|
145
|
+
export declare function resolvePaseoCliExecutablePath(): string | null;
|
|
104
146
|
export declare function buildTerminalEnvironment(input: BuildTerminalEnvironmentInput): Record<string, string>;
|
|
105
147
|
export declare function normalizeProcessTitle(processTitle: string): string | undefined;
|
|
106
148
|
export declare function humanizeProcessTitle(processTitle: string): string | undefined;
|
|
@@ -3,14 +3,17 @@ import xterm from "@xterm/headless";
|
|
|
3
3
|
import { randomUUID } from "crypto";
|
|
4
4
|
import { chmodSync, existsSync, mkdirSync, readFileSync, statSync } from "node:fs";
|
|
5
5
|
import { tmpdir, userInfo } from "node:os";
|
|
6
|
-
import { basename, dirname, join } from "node:path";
|
|
6
|
+
import { basename, delimiter, dirname, extname, join, resolve as resolvePath } from "node:path";
|
|
7
7
|
import { createRequire } from "node:module";
|
|
8
8
|
import { fileURLToPath } from "node:url";
|
|
9
9
|
import { createExternalProcessEnv } from "../server/paseo-env.js";
|
|
10
10
|
import { writePrivateFileAtomicSync } from "../server/private-files.js";
|
|
11
|
+
import { findExecutable } from "../executable-resolution/executable-resolution.js";
|
|
11
12
|
import { TerminalInputModeTracker } from "@getpaseo/protocol/terminal-input-mode";
|
|
13
|
+
import { TerminalActivityTracker } from "./activity/terminal-activity-tracker.js";
|
|
12
14
|
const { Terminal } = xterm;
|
|
13
15
|
const require = createRequire(import.meta.url);
|
|
16
|
+
const PASEO_CLI_BIN_ENTRY = "@getpaseo/cli/bin/paseo";
|
|
14
17
|
let nodePtySpawnHelperChecked = false;
|
|
15
18
|
const TERMINAL_TITLE_DEBOUNCE_MS = 150;
|
|
16
19
|
const TERMINAL_EXIT_OUTPUT_LINE_LIMIT = 12;
|
|
@@ -36,6 +39,19 @@ function parseCommandFinishedOsc(data) {
|
|
|
36
39
|
}
|
|
37
40
|
return { exitCode: Number(parts[1]) };
|
|
38
41
|
}
|
|
42
|
+
function toTerminalActivity(snapshot) {
|
|
43
|
+
if (!snapshot.state) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
state: snapshot.state,
|
|
48
|
+
...(snapshot.attentionReason ? { attentionReason: snapshot.attentionReason } : {}),
|
|
49
|
+
changedAt: snapshot.changedAt,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
function resolveInitialTitleMode(presetTitle) {
|
|
53
|
+
return presetTitle?.trim() ? "manual" : "auto";
|
|
54
|
+
}
|
|
39
55
|
function resolveNodePtyPackageRoot() {
|
|
40
56
|
try {
|
|
41
57
|
const packageJsonPath = require.resolve("node-pty/package.json");
|
|
@@ -97,11 +113,110 @@ export function resolveDefaultTerminalShell(options = {}) {
|
|
|
97
113
|
}
|
|
98
114
|
return env.SHELL || "/bin/sh";
|
|
99
115
|
}
|
|
116
|
+
/**
|
|
117
|
+
* Resolve a terminal profile command (e.g. `claude`) into something node-pty's
|
|
118
|
+
* conpty backend can actually launch on Windows.
|
|
119
|
+
*
|
|
120
|
+
* On Windows, conpty's underlying `CreateProcess` does not apply `PATHEXT`, so a
|
|
121
|
+
* bare `claude` (installed by npm as `claude.cmd`) fails with `error code: 2`
|
|
122
|
+
* (`ERROR_FILE_NOT_FOUND`). Worse, conpty completes the spawn asynchronously on
|
|
123
|
+
* its own conout worker thread, so that failure surfaces as an uncaught
|
|
124
|
+
* exception that takes down the whole terminal worker process. Resolving the
|
|
125
|
+
* real path up front — and routing `.cmd`/`.bat` shims through `cmd.exe /c`
|
|
126
|
+
* (node-pty has no `shell` option) — keeps the profile launchable.
|
|
127
|
+
*
|
|
128
|
+
* Non-Windows and the default-shell path (no explicit command) are unchanged.
|
|
129
|
+
*/
|
|
130
|
+
export async function resolveTerminalSpawnCommand(command, args, options = {}) {
|
|
131
|
+
const platform = options.platform ?? process.platform;
|
|
132
|
+
if (platform !== "win32") {
|
|
133
|
+
return { command, args };
|
|
134
|
+
}
|
|
135
|
+
const resolveExecutable = options.resolveExecutable ?? findExecutable;
|
|
136
|
+
const resolved = await resolveExecutable(command);
|
|
137
|
+
if (!resolved) {
|
|
138
|
+
// Leave the command as-is so the terminal itself surfaces the "not found"
|
|
139
|
+
// error to the user instead of silently doing nothing.
|
|
140
|
+
return { command, args };
|
|
141
|
+
}
|
|
142
|
+
// `.cmd`/`.bat` shims are batch scripts that conpty's CreateProcess cannot
|
|
143
|
+
// launch directly; they must run through cmd.exe (node-pty has no `shell`
|
|
144
|
+
// option, so build the `cmd /c` invocation ourselves). Checked by extension
|
|
145
|
+
// rather than isWindowsCommandScript() because that helper gates on the live
|
|
146
|
+
// process.platform, which is wrong once we're already on the win32 branch.
|
|
147
|
+
const extension = extname(resolved).toLowerCase();
|
|
148
|
+
if (extension === ".cmd" || extension === ".bat") {
|
|
149
|
+
const env = options.env ?? process.env;
|
|
150
|
+
const comSpec = env.ComSpec || env.COMSPEC || "C:\\Windows\\System32\\cmd.exe";
|
|
151
|
+
return { command: comSpec, args: ["/c", resolved, ...args] };
|
|
152
|
+
}
|
|
153
|
+
return { command: resolved, args };
|
|
154
|
+
}
|
|
100
155
|
export function resolveZshShellIntegrationDir() {
|
|
101
156
|
return fileURLToPath(new URL("./shell-integration/zsh", import.meta.url));
|
|
102
157
|
}
|
|
103
158
|
function resolveExternalProcessPath(filePath) {
|
|
104
|
-
return filePath.replace(/\.asar(
|
|
159
|
+
return filePath.replace(/\.asar(?=[/\\]|$)/, ".asar.unpacked");
|
|
160
|
+
}
|
|
161
|
+
export function resolvePaseoCliBinDir() {
|
|
162
|
+
const cliEntrypoint = resolvePaseoCliBinEntrypoint();
|
|
163
|
+
if (!cliEntrypoint) {
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
const externalCliEntrypoint = resolveExternalProcessPath(cliEntrypoint);
|
|
167
|
+
return findNpmBinDir(dirname(externalCliEntrypoint)) ?? dirname(externalCliEntrypoint);
|
|
168
|
+
}
|
|
169
|
+
export function resolvePaseoCliExecutablePath() {
|
|
170
|
+
const cliEntrypoint = resolvePaseoCliBinEntrypoint();
|
|
171
|
+
if (!cliEntrypoint) {
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
const externalCliEntrypoint = resolveExternalProcessPath(cliEntrypoint);
|
|
175
|
+
const npmBinDir = findNpmBinDir(dirname(externalCliEntrypoint));
|
|
176
|
+
if (npmBinDir) {
|
|
177
|
+
const shim = resolvePaseoCliShim(npmBinDir);
|
|
178
|
+
if (shim) {
|
|
179
|
+
return shim;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return externalCliEntrypoint;
|
|
183
|
+
}
|
|
184
|
+
function resolvePaseoCliBinEntrypoint() {
|
|
185
|
+
try {
|
|
186
|
+
return require.resolve(PASEO_CLI_BIN_ENTRY);
|
|
187
|
+
}
|
|
188
|
+
catch {
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
function findNpmBinDir(startPath) {
|
|
193
|
+
let current = startPath;
|
|
194
|
+
while (true) {
|
|
195
|
+
const candidate = join(current, "node_modules", ".bin");
|
|
196
|
+
if (hasPaseoCliShim(candidate)) {
|
|
197
|
+
return candidate;
|
|
198
|
+
}
|
|
199
|
+
const parent = dirname(current);
|
|
200
|
+
if (parent === current) {
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
current = parent;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
function hasPaseoCliShim(binDir) {
|
|
207
|
+
return resolvePaseoCliShim(binDir) !== null;
|
|
208
|
+
}
|
|
209
|
+
function resolvePaseoCliShim(binDir) {
|
|
210
|
+
for (const name of paseoCliShimNames()) {
|
|
211
|
+
const candidate = join(binDir, name);
|
|
212
|
+
if (existsSync(candidate)) {
|
|
213
|
+
return candidate;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
function paseoCliShimNames() {
|
|
219
|
+
return process.platform === "win32" ? ["paseo.cmd", "paseo.exe", "paseo"] : ["paseo"];
|
|
105
220
|
}
|
|
106
221
|
function resolveZshShellIntegrationRuntimeDir() {
|
|
107
222
|
let username = "unknown";
|
|
@@ -127,16 +242,45 @@ export function buildTerminalEnvironment(input) {
|
|
|
127
242
|
TERM: "xterm-256color",
|
|
128
243
|
TERM_PROGRAM: "kitty",
|
|
129
244
|
});
|
|
245
|
+
const envWithAgentHooks = prependPaseoCliToPath(baseEnv, input.paseoCliBinDir === undefined ? resolvePaseoCliBinDir() : input.paseoCliBinDir);
|
|
246
|
+
const envWithHookCli = injectPaseoHookCli(envWithAgentHooks, input.paseoHookCliPath === undefined ? resolvePaseoCliExecutablePath() : input.paseoHookCliPath);
|
|
130
247
|
if (basename(input.shell) !== "zsh") {
|
|
131
|
-
return
|
|
248
|
+
return envWithHookCli;
|
|
132
249
|
}
|
|
133
|
-
const originalZdotdir =
|
|
250
|
+
const originalZdotdir = envWithHookCli.ZDOTDIR ?? "";
|
|
134
251
|
return {
|
|
135
|
-
...
|
|
252
|
+
...envWithHookCli,
|
|
136
253
|
PASEO_ZSH_ZDOTDIR: originalZdotdir,
|
|
137
254
|
ZDOTDIR: prepareZshShellIntegrationRuntimeDir(input.zshShellIntegrationDir),
|
|
138
255
|
};
|
|
139
256
|
}
|
|
257
|
+
function injectPaseoHookCli(env, cliPath) {
|
|
258
|
+
if (!cliPath) {
|
|
259
|
+
return env;
|
|
260
|
+
}
|
|
261
|
+
return {
|
|
262
|
+
...env,
|
|
263
|
+
PASEO_HOOK_CLI: resolvePath(resolveExternalProcessPath(cliPath)),
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
function prependPaseoCliToPath(env, cliBinDir) {
|
|
267
|
+
if (!cliBinDir) {
|
|
268
|
+
return env;
|
|
269
|
+
}
|
|
270
|
+
const pathKey = getPathEnvKey(env);
|
|
271
|
+
const currentPath = env[pathKey] ?? "";
|
|
272
|
+
return {
|
|
273
|
+
...env,
|
|
274
|
+
[pathKey]: prependPathEntry(currentPath, cliBinDir),
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
function getPathEnvKey(env) {
|
|
278
|
+
return Object.keys(env).find((key) => key.toLowerCase() === "path") ?? "PATH";
|
|
279
|
+
}
|
|
280
|
+
function prependPathEntry(currentPath, entry) {
|
|
281
|
+
const entries = currentPath.split(delimiter).filter((value) => value && value !== entry);
|
|
282
|
+
return [entry, ...entries].join(delimiter);
|
|
283
|
+
}
|
|
140
284
|
function extractCell(terminal, row, col) {
|
|
141
285
|
const buffer = terminal.buffer.active;
|
|
142
286
|
const line = buffer.getLine(row);
|
|
@@ -411,7 +555,7 @@ function extractLastOutputLinesFromText(text, limit) {
|
|
|
411
555
|
return lines.slice(-limit);
|
|
412
556
|
}
|
|
413
557
|
export async function createTerminal(options) {
|
|
414
|
-
const { cwd, shell, env = {}, rows = 24, cols = 80, name = "Terminal", title: presetTitle, command, args = [], } = options;
|
|
558
|
+
const { cwd, workspaceId, shell, env = {}, activityEnv = {}, rows = 24, cols = 80, name = "Terminal", title: presetTitle, command, args = [], } = options;
|
|
415
559
|
const resolvedShell = shell ?? resolveDefaultTerminalShell();
|
|
416
560
|
const id = options.id ?? randomUUID();
|
|
417
561
|
const listeners = new Set();
|
|
@@ -424,15 +568,22 @@ export async function createTerminal(options) {
|
|
|
424
568
|
let processExited = false;
|
|
425
569
|
const processExitWaiters = new Set();
|
|
426
570
|
let exitInfo = null;
|
|
427
|
-
|
|
571
|
+
// Recent output is retained as whole chunks plus a running char length so we
|
|
572
|
+
// avoid reallocating a ~16KB string on every pty chunk. We keep enough whole
|
|
573
|
+
// chunks that their join always contains at least the last
|
|
574
|
+
// TERMINAL_EXIT_OUTPUT_CHAR_LIMIT chars; the exact tail is sliced at exit.
|
|
575
|
+
const recentOutputChunks = [];
|
|
576
|
+
let recentOutputLength = 0;
|
|
428
577
|
let title;
|
|
429
|
-
let titleMode = presetTitle
|
|
578
|
+
let titleMode = resolveInitialTitleMode(presetTitle);
|
|
430
579
|
let pendingTitle;
|
|
431
580
|
let titleDebounceTimer = null;
|
|
432
581
|
let pendingInput = "";
|
|
433
582
|
let inputFlushImmediate = null;
|
|
434
583
|
let stateRevision = 0;
|
|
435
584
|
const inputModeTracker = new TerminalInputModeTracker();
|
|
585
|
+
const activityTracker = new TerminalActivityTracker();
|
|
586
|
+
const activityChangeListeners = new Set();
|
|
436
587
|
let titleChangeSubscription = null;
|
|
437
588
|
// Create xterm.js headless terminal
|
|
438
589
|
const terminal = new Terminal({
|
|
@@ -443,14 +594,22 @@ export async function createTerminal(options) {
|
|
|
443
594
|
});
|
|
444
595
|
ensureNodePtySpawnHelperExecutableForCurrentPlatform();
|
|
445
596
|
// Create PTY
|
|
446
|
-
const spawnCommand = command
|
|
447
|
-
|
|
597
|
+
const { command: spawnCommand, args: spawnArgs } = command
|
|
598
|
+
? await resolveTerminalSpawnCommand(command, args)
|
|
599
|
+
: { command: resolvedShell, args: [] };
|
|
448
600
|
const ptyProcess = pty.spawn(spawnCommand, spawnArgs, {
|
|
449
601
|
name: "xterm-256color",
|
|
450
602
|
cols,
|
|
451
603
|
rows,
|
|
452
604
|
cwd,
|
|
453
|
-
env: buildTerminalEnvironment({
|
|
605
|
+
env: buildTerminalEnvironment({
|
|
606
|
+
shell: spawnCommand,
|
|
607
|
+
env: {
|
|
608
|
+
...env,
|
|
609
|
+
...activityEnv,
|
|
610
|
+
PASEO_WORKSPACE_ID: workspaceId,
|
|
611
|
+
},
|
|
612
|
+
}),
|
|
454
613
|
});
|
|
455
614
|
function emitTitleChange(nextTitle) {
|
|
456
615
|
if (title === nextTitle) {
|
|
@@ -573,6 +732,23 @@ export async function createTerminal(options) {
|
|
|
573
732
|
}
|
|
574
733
|
return true;
|
|
575
734
|
});
|
|
735
|
+
activityTracker.onChange((snapshot, previousSnapshot) => {
|
|
736
|
+
if (disposed || killed) {
|
|
737
|
+
return;
|
|
738
|
+
}
|
|
739
|
+
const transition = {
|
|
740
|
+
activity: toTerminalActivity(snapshot),
|
|
741
|
+
previous: toTerminalActivity(previousSnapshot),
|
|
742
|
+
};
|
|
743
|
+
for (const listener of Array.from(activityChangeListeners)) {
|
|
744
|
+
try {
|
|
745
|
+
listener(transition);
|
|
746
|
+
}
|
|
747
|
+
catch {
|
|
748
|
+
// no-op
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
});
|
|
576
752
|
function buildExitInfo(input) {
|
|
577
753
|
const lastOutputLines = extractLastOutputLines(terminal, TERMINAL_EXIT_OUTPUT_LINE_LIMIT);
|
|
578
754
|
return {
|
|
@@ -580,7 +756,7 @@ export async function createTerminal(options) {
|
|
|
580
756
|
signal: input?.signal && input.signal > 0 ? input.signal : null,
|
|
581
757
|
lastOutputLines: lastOutputLines.length > 0
|
|
582
758
|
? lastOutputLines
|
|
583
|
-
: extractLastOutputLinesFromText(
|
|
759
|
+
: extractLastOutputLinesFromText(recentOutputChunks.join("").slice(-TERMINAL_EXIT_OUTPUT_CHAR_LIMIT), TERMINAL_EXIT_OUTPUT_LINE_LIMIT),
|
|
584
760
|
};
|
|
585
761
|
}
|
|
586
762
|
function emitExit(info) {
|
|
@@ -604,7 +780,10 @@ export async function createTerminal(options) {
|
|
|
604
780
|
return;
|
|
605
781
|
}
|
|
606
782
|
disposed = true;
|
|
783
|
+
activityTracker.clear();
|
|
607
784
|
pendingInput = "";
|
|
785
|
+
recentOutputChunks.length = 0;
|
|
786
|
+
recentOutputLength = 0;
|
|
608
787
|
inputModeTracker.reset();
|
|
609
788
|
if (inputFlushImmediate) {
|
|
610
789
|
clearImmediate(inputFlushImmediate);
|
|
@@ -613,11 +792,13 @@ export async function createTerminal(options) {
|
|
|
613
792
|
clearPendingTitleChange();
|
|
614
793
|
disposeTitleChangeSubscription();
|
|
615
794
|
disposeCommandLifecycleSubscription.dispose();
|
|
795
|
+
activityTracker.dispose();
|
|
616
796
|
terminal.dispose();
|
|
617
797
|
listeners.clear();
|
|
618
798
|
exitListeners.clear();
|
|
619
799
|
commandFinishedListeners.clear();
|
|
620
800
|
titleChangeListeners.clear();
|
|
801
|
+
activityChangeListeners.clear();
|
|
621
802
|
}
|
|
622
803
|
function writeOutputToHeadless(data) {
|
|
623
804
|
terminal.write(data, () => {
|
|
@@ -638,9 +819,21 @@ export async function createTerminal(options) {
|
|
|
638
819
|
for (const response of inputModeUpdate.responses) {
|
|
639
820
|
ptyProcess.write(response);
|
|
640
821
|
}
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
822
|
+
recentOutputChunks.push(data);
|
|
823
|
+
recentOutputLength += data.length;
|
|
824
|
+
// Drop whole leading chunks while the rest still covers the char limit, so
|
|
825
|
+
// the retained join always contains at least the last limit chars.
|
|
826
|
+
while (recentOutputChunks.length > 1 &&
|
|
827
|
+
recentOutputLength - recentOutputChunks[0].length >= TERMINAL_EXIT_OUTPUT_CHAR_LIMIT) {
|
|
828
|
+
recentOutputLength -= recentOutputChunks[0].length;
|
|
829
|
+
recentOutputChunks.shift();
|
|
830
|
+
}
|
|
831
|
+
// We never drop the last chunk, so a single chunk larger than the cap would
|
|
832
|
+
// grow the buffer unbounded; slice its tail to keep the cap hard.
|
|
833
|
+
if (recentOutputChunks.length === 1 && recentOutputLength > TERMINAL_EXIT_OUTPUT_CHAR_LIMIT) {
|
|
834
|
+
const tail = recentOutputChunks[0].slice(-TERMINAL_EXIT_OUTPUT_CHAR_LIMIT);
|
|
835
|
+
recentOutputChunks[0] = tail;
|
|
836
|
+
recentOutputLength = tail.length;
|
|
644
837
|
}
|
|
645
838
|
writeOutputToHeadless(data);
|
|
646
839
|
});
|
|
@@ -775,7 +968,13 @@ export async function createTerminal(options) {
|
|
|
775
968
|
if (!disposed && active && listeners.has(subscriptionListener)) {
|
|
776
969
|
snapshotDelivered = true;
|
|
777
970
|
if (initialSnapshot === "ready") {
|
|
778
|
-
|
|
971
|
+
// Carry the input-mode preamble so the snapshot-less "ready" path
|
|
972
|
+
// (live restore) can replay it without a separate state fetch.
|
|
973
|
+
listener({
|
|
974
|
+
type: "snapshotReady",
|
|
975
|
+
revision: stateRevision,
|
|
976
|
+
replayPreamble: getReplayPreamble(),
|
|
977
|
+
});
|
|
779
978
|
}
|
|
780
979
|
else {
|
|
781
980
|
listener({ type: "snapshot", ...getStateSnapshot() });
|
|
@@ -833,9 +1032,24 @@ export async function createTerminal(options) {
|
|
|
833
1032
|
titleChangeListeners.delete(listener);
|
|
834
1033
|
};
|
|
835
1034
|
}
|
|
1035
|
+
function onActivityChange(listener) {
|
|
1036
|
+
activityChangeListeners.add(listener);
|
|
1037
|
+
return () => {
|
|
1038
|
+
activityChangeListeners.delete(listener);
|
|
1039
|
+
};
|
|
1040
|
+
}
|
|
836
1041
|
function getTitle() {
|
|
837
1042
|
return title;
|
|
838
1043
|
}
|
|
1044
|
+
function getActivity() {
|
|
1045
|
+
return toTerminalActivity(activityTracker.getSnapshot());
|
|
1046
|
+
}
|
|
1047
|
+
function setActivity(state) {
|
|
1048
|
+
activityTracker.set(state);
|
|
1049
|
+
}
|
|
1050
|
+
function clearActivityAttention() {
|
|
1051
|
+
return activityTracker.clearAttention();
|
|
1052
|
+
}
|
|
839
1053
|
function getExitInfo() {
|
|
840
1054
|
return exitInfo;
|
|
841
1055
|
}
|
|
@@ -917,16 +1131,21 @@ export async function createTerminal(options) {
|
|
|
917
1131
|
id,
|
|
918
1132
|
name,
|
|
919
1133
|
cwd,
|
|
1134
|
+
workspaceId,
|
|
920
1135
|
send,
|
|
921
1136
|
subscribe,
|
|
922
1137
|
onExit,
|
|
923
1138
|
onCommandFinished,
|
|
924
1139
|
onTitleChange,
|
|
1140
|
+
onActivityChange,
|
|
925
1141
|
getSize,
|
|
926
1142
|
getState,
|
|
927
1143
|
getStateSnapshot,
|
|
928
1144
|
getReplayPreamble,
|
|
929
1145
|
getTitle,
|
|
1146
|
+
getActivity,
|
|
1147
|
+
setActivity,
|
|
1148
|
+
clearActivityAttention,
|
|
930
1149
|
setTitle,
|
|
931
1150
|
getExitInfo,
|
|
932
1151
|
kill,
|
|
@@ -12,6 +12,7 @@ interface TerminalWorkerProcess {
|
|
|
12
12
|
interface WorkerTerminalManagerOptions {
|
|
13
13
|
requestTimeoutMs?: number;
|
|
14
14
|
forkWorker?: () => TerminalWorkerProcess;
|
|
15
|
+
getTerminalActivityUrl?: () => string | null;
|
|
15
16
|
}
|
|
16
17
|
export declare function createWorkerTerminalManager(managerOptions?: WorkerTerminalManagerOptions): TerminalManager;
|
|
17
18
|
export declare function terminateWorkerTerminalManager(manager: TerminalManager): void;
|