@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.
Files changed (134) hide show
  1. package/dist/server/{utils/executable.d.ts → executable-resolution/executable-resolution.d.ts} +2 -2
  2. package/dist/server/{utils/executable.js → executable-resolution/executable-resolution.js} +16 -14
  3. package/dist/server/executable-resolution/windows.d.ts +18 -0
  4. package/dist/server/executable-resolution/windows.js +62 -0
  5. package/dist/server/server/agent/agent-loading.js +4 -1
  6. package/dist/server/server/agent/agent-manager.d.ts +10 -2
  7. package/dist/server/server/agent/agent-manager.js +34 -46
  8. package/dist/server/server/agent/agent-projections.js +3 -0
  9. package/dist/server/server/agent/agent-prompt.js +19 -1
  10. package/dist/server/server/agent/agent-response-loop.js +2 -4
  11. package/dist/server/server/agent/agent-storage.d.ts +18 -19
  12. package/dist/server/server/agent/agent-storage.js +6 -23
  13. package/dist/server/server/agent/create-agent/create.d.ts +2 -12
  14. package/dist/server/server/agent/create-agent/create.js +28 -30
  15. package/dist/server/server/agent/create-agent-lifecycle-dispatch.d.ts +4 -2
  16. package/dist/server/server/agent/create-agent-lifecycle-dispatch.js +31 -22
  17. package/dist/server/server/agent/import-sessions.d.ts +1 -10
  18. package/dist/server/server/agent/import-sessions.js +1 -53
  19. package/dist/server/server/agent/lifecycle-command.js +5 -4
  20. package/dist/server/server/agent/mcp-server.d.ts +8 -5
  21. package/dist/server/server/agent/mcp-server.js +41 -14
  22. package/dist/server/server/agent/mcp-shared.d.ts +6 -3
  23. package/dist/server/server/agent/mcp-shared.js +3 -0
  24. package/dist/server/server/agent/provider-launch-config.js +1 -1
  25. package/dist/server/server/agent/providers/acp-agent.d.ts +5 -0
  26. package/dist/server/server/agent/providers/acp-agent.js +31 -26
  27. package/dist/server/server/agent/providers/claude/agent.js +45 -6
  28. package/dist/server/server/agent/providers/codex-app-server-agent.js +1 -1
  29. package/dist/server/server/agent/providers/copilot-acp-agent.js +1 -0
  30. package/dist/server/server/agent/providers/cursor-acp-agent.d.ts +0 -7
  31. package/dist/server/server/agent/providers/cursor-acp-agent.js +0 -78
  32. package/dist/server/server/agent/providers/mock-load-test-agent.d.ts +2 -0
  33. package/dist/server/server/agent/providers/mock-load-test-agent.js +73 -1
  34. package/dist/server/server/agent/providers/opencode/server-manager.js +1 -1
  35. package/dist/server/server/agent/structured-generation-providers.js +45 -1
  36. package/dist/server/server/agent-attention-policy.d.ts +12 -3
  37. package/dist/server/server/agent-attention-policy.js +15 -3
  38. package/dist/server/server/auto-archive-on-merge/archive-if-safe.d.ts +7 -6
  39. package/dist/server/server/auto-archive-on-merge/archive-if-safe.js +21 -16
  40. package/dist/server/server/bootstrap.d.ts +3 -0
  41. package/dist/server/server/bootstrap.js +91 -12
  42. package/dist/server/server/config.js +1 -0
  43. package/dist/server/server/daemon-config-store.js +1 -0
  44. package/dist/server/server/exports.d.ts +1 -1
  45. package/dist/server/server/exports.js +1 -1
  46. package/dist/server/server/loop-service.d.ts +24 -24
  47. package/dist/server/server/migrations/backfill-workspace-id.migration.d.ts +9 -0
  48. package/dist/server/server/migrations/backfill-workspace-id.migration.js +60 -0
  49. package/dist/server/server/paseo-worktree-service.d.ts +9 -0
  50. package/dist/server/server/paseo-worktree-service.js +71 -12
  51. package/dist/server/server/path-utils.d.ts +1 -0
  52. package/dist/server/server/path-utils.js +6 -1
  53. package/dist/server/server/persisted-config.d.ts +7 -0
  54. package/dist/server/server/persisted-config.js +1 -0
  55. package/dist/server/server/persistence-hooks.d.ts +1 -0
  56. package/dist/server/server/persistence-hooks.js +13 -5
  57. package/dist/server/server/resolve-workspace-id-for-path.d.ts +3 -0
  58. package/dist/server/server/resolve-workspace-id-for-path.js +41 -0
  59. package/dist/server/server/script-proxy.d.ts +1 -1
  60. package/dist/server/server/script-proxy.js +1 -1
  61. package/dist/server/server/service-proxy.js +1 -1
  62. package/dist/server/server/session.d.ts +31 -6
  63. package/dist/server/server/session.js +640 -196
  64. package/dist/server/server/websocket-server.d.ts +5 -0
  65. package/dist/server/server/websocket-server.js +137 -3
  66. package/dist/server/server/workspace-archive-service.d.ts +60 -3
  67. package/dist/server/server/workspace-archive-service.js +217 -4
  68. package/dist/server/server/workspace-directory.d.ts +20 -2
  69. package/dist/server/server/workspace-directory.js +148 -70
  70. package/dist/server/server/workspace-git-service.js +21 -21
  71. package/dist/server/server/workspace-reconciliation-service.d.ts +1 -1
  72. package/dist/server/server/workspace-reconciliation-service.js +21 -22
  73. package/dist/server/server/workspace-registry-bootstrap.js +23 -10
  74. package/dist/server/server/workspace-registry-model.d.ts +3 -3
  75. package/dist/server/server/workspace-registry-model.js +9 -10
  76. package/dist/server/server/workspace-registry.d.ts +17 -4
  77. package/dist/server/server/workspace-registry.js +27 -0
  78. package/dist/server/server/worktree/commands.d.ts +7 -5
  79. package/dist/server/server/worktree/commands.js +38 -18
  80. package/dist/server/server/worktree-bootstrap.d.ts +1 -0
  81. package/dist/server/server/worktree-bootstrap.js +4 -1
  82. package/dist/server/server/worktree-branch-name-generator.d.ts +5 -1
  83. package/dist/server/server/worktree-branch-name-generator.js +8 -2
  84. package/dist/server/server/worktree-session.d.ts +4 -5
  85. package/dist/server/server/worktree-session.js +9 -3
  86. package/dist/server/services/github-service.js +1 -1
  87. package/dist/server/terminal/activity/terminal-activity-tracker.d.ts +20 -0
  88. package/dist/server/terminal/activity/terminal-activity-tracker.js +59 -0
  89. package/dist/server/terminal/agent-hooks/agent-hook-installer.d.ts +62 -0
  90. package/dist/server/terminal/agent-hooks/agent-hook-installer.js +117 -0
  91. package/dist/server/terminal/agent-hooks/claude/claude-settings.d.ts +7 -0
  92. package/dist/server/terminal/agent-hooks/claude/claude-settings.js +88 -0
  93. package/dist/server/terminal/agent-hooks/claude/claude.d.ts +4 -0
  94. package/dist/server/terminal/agent-hooks/claude/claude.js +47 -0
  95. package/dist/server/terminal/agent-hooks/codex/codex-settings.d.ts +7 -0
  96. package/dist/server/terminal/agent-hooks/codex/codex-settings.js +99 -0
  97. package/dist/server/terminal/agent-hooks/codex/codex.d.ts +4 -0
  98. package/dist/server/terminal/agent-hooks/codex/codex.js +30 -0
  99. package/dist/server/terminal/agent-hooks/opencode/opencode-plugin.d.ts +4 -0
  100. package/dist/server/terminal/agent-hooks/opencode/opencode-plugin.js +46 -0
  101. package/dist/server/terminal/agent-hooks/opencode/opencode.d.ts +3 -0
  102. package/dist/server/terminal/agent-hooks/opencode/opencode.js +23 -0
  103. package/dist/server/terminal/agent-hooks/provider-registry.d.ts +24 -0
  104. package/dist/server/terminal/agent-hooks/provider-registry.js +36 -0
  105. package/dist/server/terminal/agent-hooks/terminal-agent-hook-setting.d.ts +10 -0
  106. package/dist/server/terminal/agent-hooks/terminal-agent-hook-setting.js +26 -0
  107. package/dist/server/terminal/terminal-manager-factory.d.ts +4 -1
  108. package/dist/server/terminal/terminal-manager-factory.js +2 -2
  109. package/dist/server/terminal/terminal-manager.d.ts +33 -2
  110. package/dist/server/terminal/terminal-manager.js +144 -18
  111. package/dist/server/terminal/terminal-output-coalescer.d.ts +4 -0
  112. package/dist/server/terminal/terminal-output-coalescer.js +18 -0
  113. package/dist/server/terminal/terminal-restore.d.ts +1 -0
  114. package/dist/server/terminal/terminal-restore.js +6 -0
  115. package/dist/server/terminal/terminal-session-controller.d.ts +4 -2
  116. package/dist/server/terminal/terminal-session-controller.js +65 -24
  117. package/dist/server/terminal/terminal-worker-process.js +146 -63
  118. package/dist/server/terminal/terminal-worker-protocol.d.ts +19 -14
  119. package/dist/server/terminal/terminal.d.ts +42 -0
  120. package/dist/server/terminal/terminal.js +235 -16
  121. package/dist/server/terminal/worker-terminal-manager.d.ts +1 -0
  122. package/dist/server/terminal/worker-terminal-manager.js +220 -36
  123. package/dist/server/utils/build-metadata-prompt.d.ts +1 -1
  124. package/dist/server/utils/github-remote.js +1 -1
  125. package/dist/server/utils/tree-kill.d.ts +2 -2
  126. package/dist/src/{utils/executable.js → executable-resolution/executable-resolution.js} +16 -14
  127. package/dist/src/executable-resolution/windows.js +62 -0
  128. package/dist/src/server/agent/provider-launch-config.js +1 -1
  129. package/dist/src/server/persisted-config.js +1 -0
  130. package/package.json +10 -5
  131. package/dist/server/server/agent/agent-metadata-generator.d.ts +0 -36
  132. package/dist/server/server/agent/agent-metadata-generator.js +0 -112
  133. package/dist/server/server/paseo-worktree-archive-service.d.ts +0 -41
  134. package/dist/server/server/paseo-worktree-archive-service.js +0 -144
@@ -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(?=\/|$)/, ".asar.unpacked");
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 baseEnv;
248
+ return envWithHookCli;
132
249
  }
133
- const originalZdotdir = baseEnv.ZDOTDIR ?? "";
250
+ const originalZdotdir = envWithHookCli.ZDOTDIR ?? "";
134
251
  return {
135
- ...baseEnv,
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
- let recentOutputText = "";
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?.trim() ? "manual" : "auto";
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 ?? resolvedShell;
447
- const spawnArgs = command ? args : [];
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({ shell: spawnCommand, env }),
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(recentOutputText, TERMINAL_EXIT_OUTPUT_LINE_LIMIT),
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
- recentOutputText = `${recentOutputText}${data}`;
642
- if (recentOutputText.length > TERMINAL_EXIT_OUTPUT_CHAR_LIMIT) {
643
- recentOutputText = recentOutputText.slice(-TERMINAL_EXIT_OUTPUT_CHAR_LIMIT);
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
- listener({ type: "snapshotReady", revision: stateRevision });
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;