@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.
Files changed (139) 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/create-agent-title.d.ts +2 -0
  18. package/dist/server/server/agent/create-agent-title.js +5 -0
  19. package/dist/server/server/agent/import-sessions.d.ts +1 -10
  20. package/dist/server/server/agent/import-sessions.js +1 -53
  21. package/dist/server/server/agent/lifecycle-command.js +5 -4
  22. package/dist/server/server/agent/mcp-server.d.ts +8 -5
  23. package/dist/server/server/agent/mcp-server.js +41 -14
  24. package/dist/server/server/agent/mcp-shared.d.ts +6 -3
  25. package/dist/server/server/agent/mcp-shared.js +3 -0
  26. package/dist/server/server/agent/provider-launch-config.js +1 -1
  27. package/dist/server/server/agent/providers/acp-agent.d.ts +5 -0
  28. package/dist/server/server/agent/providers/acp-agent.js +31 -26
  29. package/dist/server/server/agent/providers/claude/agent.js +45 -6
  30. package/dist/server/server/agent/providers/codex-app-server-agent.js +1 -1
  31. package/dist/server/server/agent/providers/copilot-acp-agent.js +1 -0
  32. package/dist/server/server/agent/providers/cursor-acp-agent.d.ts +0 -7
  33. package/dist/server/server/agent/providers/cursor-acp-agent.js +0 -78
  34. package/dist/server/server/agent/providers/mock-load-test-agent.d.ts +2 -0
  35. package/dist/server/server/agent/providers/mock-load-test-agent.js +73 -1
  36. package/dist/server/server/agent/providers/opencode/server-manager.js +1 -1
  37. package/dist/server/server/agent/structured-generation-providers.js +45 -1
  38. package/dist/server/server/agent-attention-policy.d.ts +12 -3
  39. package/dist/server/server/agent-attention-policy.js +15 -3
  40. package/dist/server/server/auto-archive-on-merge/archive-if-safe.d.ts +7 -6
  41. package/dist/server/server/auto-archive-on-merge/archive-if-safe.js +21 -16
  42. package/dist/server/server/bootstrap.d.ts +3 -0
  43. package/dist/server/server/bootstrap.js +125 -64
  44. package/dist/server/server/config.js +1 -0
  45. package/dist/server/server/daemon-config-store.js +1 -0
  46. package/dist/server/server/exports.d.ts +1 -1
  47. package/dist/server/server/exports.js +1 -1
  48. package/dist/server/server/loop-service.d.ts +24 -24
  49. package/dist/server/server/migrations/backfill-workspace-id.migration.d.ts +9 -0
  50. package/dist/server/server/migrations/backfill-workspace-id.migration.js +60 -0
  51. package/dist/server/server/paseo-worktree-service.d.ts +9 -0
  52. package/dist/server/server/paseo-worktree-service.js +74 -12
  53. package/dist/server/server/path-utils.d.ts +1 -0
  54. package/dist/server/server/path-utils.js +6 -1
  55. package/dist/server/server/persisted-config.d.ts +7 -0
  56. package/dist/server/server/persisted-config.js +1 -0
  57. package/dist/server/server/persistence-hooks.d.ts +1 -0
  58. package/dist/server/server/persistence-hooks.js +13 -5
  59. package/dist/server/server/resolve-workspace-id-for-path.d.ts +3 -0
  60. package/dist/server/server/resolve-workspace-id-for-path.js +41 -0
  61. package/dist/server/server/script-proxy.d.ts +1 -1
  62. package/dist/server/server/script-proxy.js +1 -1
  63. package/dist/server/server/service-proxy.js +1 -1
  64. package/dist/server/server/session.d.ts +33 -6
  65. package/dist/server/server/session.js +691 -202
  66. package/dist/server/server/websocket-server.d.ts +5 -0
  67. package/dist/server/server/websocket-server.js +137 -3
  68. package/dist/server/server/workspace-archive-service.d.ts +60 -3
  69. package/dist/server/server/workspace-archive-service.js +217 -4
  70. package/dist/server/server/workspace-directory.d.ts +20 -2
  71. package/dist/server/server/workspace-directory.js +148 -70
  72. package/dist/server/server/workspace-git-service.js +21 -21
  73. package/dist/server/server/workspace-reconciliation-service.d.ts +1 -1
  74. package/dist/server/server/workspace-reconciliation-service.js +21 -22
  75. package/dist/server/server/workspace-registry-bootstrap.js +23 -10
  76. package/dist/server/server/workspace-registry-model.d.ts +3 -3
  77. package/dist/server/server/workspace-registry-model.js +9 -10
  78. package/dist/server/server/workspace-registry.d.ts +17 -4
  79. package/dist/server/server/workspace-registry.js +27 -0
  80. package/dist/server/server/worktree/commands.d.ts +7 -5
  81. package/dist/server/server/worktree/commands.js +38 -18
  82. package/dist/server/server/worktree-bootstrap.d.ts +1 -0
  83. package/dist/server/server/worktree-bootstrap.js +4 -1
  84. package/dist/server/server/worktree-branch-name-generator.d.ts +5 -1
  85. package/dist/server/server/worktree-branch-name-generator.js +29 -7
  86. package/dist/server/server/worktree-session.d.ts +4 -5
  87. package/dist/server/server/worktree-session.js +9 -3
  88. package/dist/server/services/github-service.js +1 -1
  89. package/dist/server/terminal/activity/terminal-activity-tracker.d.ts +20 -0
  90. package/dist/server/terminal/activity/terminal-activity-tracker.js +59 -0
  91. package/dist/server/terminal/agent-hooks/agent-hook-installer.d.ts +62 -0
  92. package/dist/server/terminal/agent-hooks/agent-hook-installer.js +117 -0
  93. package/dist/server/terminal/agent-hooks/claude/claude-settings.d.ts +7 -0
  94. package/dist/server/terminal/agent-hooks/claude/claude-settings.js +88 -0
  95. package/dist/server/terminal/agent-hooks/claude/claude.d.ts +4 -0
  96. package/dist/server/terminal/agent-hooks/claude/claude.js +47 -0
  97. package/dist/server/terminal/agent-hooks/codex/codex-settings.d.ts +7 -0
  98. package/dist/server/terminal/agent-hooks/codex/codex-settings.js +99 -0
  99. package/dist/server/terminal/agent-hooks/codex/codex.d.ts +4 -0
  100. package/dist/server/terminal/agent-hooks/codex/codex.js +30 -0
  101. package/dist/server/terminal/agent-hooks/opencode/opencode-plugin.d.ts +4 -0
  102. package/dist/server/terminal/agent-hooks/opencode/opencode-plugin.js +46 -0
  103. package/dist/server/terminal/agent-hooks/opencode/opencode.d.ts +3 -0
  104. package/dist/server/terminal/agent-hooks/opencode/opencode.js +23 -0
  105. package/dist/server/terminal/agent-hooks/provider-registry.d.ts +24 -0
  106. package/dist/server/terminal/agent-hooks/provider-registry.js +36 -0
  107. package/dist/server/terminal/agent-hooks/terminal-agent-hook-setting.d.ts +10 -0
  108. package/dist/server/terminal/agent-hooks/terminal-agent-hook-setting.js +26 -0
  109. package/dist/server/terminal/terminal-manager-factory.d.ts +4 -1
  110. package/dist/server/terminal/terminal-manager-factory.js +2 -2
  111. package/dist/server/terminal/terminal-manager.d.ts +33 -2
  112. package/dist/server/terminal/terminal-manager.js +144 -18
  113. package/dist/server/terminal/terminal-output-coalescer.d.ts +4 -0
  114. package/dist/server/terminal/terminal-output-coalescer.js +18 -0
  115. package/dist/server/terminal/terminal-restore.d.ts +1 -0
  116. package/dist/server/terminal/terminal-restore.js +6 -0
  117. package/dist/server/terminal/terminal-session-controller.d.ts +4 -2
  118. package/dist/server/terminal/terminal-session-controller.js +65 -24
  119. package/dist/server/terminal/terminal-worker-process.js +146 -63
  120. package/dist/server/terminal/terminal-worker-protocol.d.ts +19 -14
  121. package/dist/server/terminal/terminal.d.ts +42 -0
  122. package/dist/server/terminal/terminal.js +235 -16
  123. package/dist/server/terminal/worker-terminal-manager.d.ts +1 -0
  124. package/dist/server/terminal/worker-terminal-manager.js +220 -36
  125. package/dist/server/utils/build-metadata-prompt.d.ts +8 -3
  126. package/dist/server/utils/build-metadata-prompt.js +10 -9
  127. package/dist/server/utils/github-remote.js +1 -1
  128. package/dist/server/utils/tree-kill.d.ts +2 -2
  129. package/dist/src/{utils/executable.js → executable-resolution/executable-resolution.js} +16 -14
  130. package/dist/src/executable-resolution/windows.js +62 -0
  131. package/dist/src/server/agent/provider-launch-config.js +1 -1
  132. package/dist/src/server/persisted-config.js +1 -0
  133. package/package.json +10 -5
  134. package/dist/server/server/agent/agent-metadata-generator.d.ts +0 -36
  135. package/dist/server/server/agent/agent-metadata-generator.js +0 -112
  136. package/dist/server/server/paseo-worktree-archive-service.d.ts +0 -41
  137. package/dist/server/server/paseo-worktree-archive-service.js +0 -144
  138. package/dist/server/utils/wrap-user-instructions.d.ts +0 -2
  139. package/dist/server/utils/wrap-user-instructions.js +0 -13
@@ -0,0 +1,36 @@
1
+ import { agentHooksAreInstalled, installAgentHooks, uninstallAgentHooks, } from "./agent-hook-installer.js";
2
+ import { claudeAgentHookProvider } from "./claude/claude.js";
3
+ import { codexAgentHookProvider } from "./codex/codex.js";
4
+ import { opencodeAgentHookProvider } from "./opencode/opencode.js";
5
+ export const AGENT_HOOK_PROVIDERS = {
6
+ [claudeAgentHookProvider.id]: claudeAgentHookProvider,
7
+ [codexAgentHookProvider.id]: codexAgentHookProvider,
8
+ [opencodeAgentHookProvider.id]: opencodeAgentHookProvider,
9
+ };
10
+ export function installRegisteredAgentHooks(options = {}) {
11
+ const results = [];
12
+ for (const provider of Object.values(AGENT_HOOK_PROVIDERS)) {
13
+ try {
14
+ results.push(installAgentHooks(provider, options));
15
+ }
16
+ catch (error) {
17
+ options.logger?.warn({ err: error, provider: provider.id }, "Failed to install terminal activity hook provider");
18
+ }
19
+ }
20
+ return results;
21
+ }
22
+ export function uninstallRegisteredAgentHooks(options = {}) {
23
+ for (const provider of Object.values(AGENT_HOOK_PROVIDERS)) {
24
+ uninstallAgentHooks(provider, options);
25
+ }
26
+ }
27
+ export function registeredAgentHooksAreInstalled(options = {}) {
28
+ return Object.values(AGENT_HOOK_PROVIDERS).every((provider) => agentHooksAreInstalled(provider, options));
29
+ }
30
+ export async function resolveHookActivity(request) {
31
+ const provider = AGENT_HOOK_PROVIDERS[request.provider.toLowerCase()];
32
+ if (!provider)
33
+ return null;
34
+ return provider.resolveActivity({ event: request.event, input: request.input });
35
+ }
36
+ //# sourceMappingURL=provider-registry.js.map
@@ -0,0 +1,10 @@
1
+ import type { AgentHookInstallLogger, AgentHookInstallOptions } from "./agent-hook-installer.js";
2
+ import type { DaemonConfigStore } from "../../server/daemon-config-store.js";
3
+ interface ApplyTerminalAgentHookSettingOptions {
4
+ store: DaemonConfigStore;
5
+ logger?: AgentHookInstallLogger;
6
+ install?: AgentHookInstallOptions;
7
+ }
8
+ export declare function applyTerminalAgentHookSetting(options: ApplyTerminalAgentHookSettingOptions): () => void;
9
+ export {};
10
+ //# sourceMappingURL=terminal-agent-hook-setting.d.ts.map
@@ -0,0 +1,26 @@
1
+ import { installRegisteredAgentHooks, uninstallRegisteredAgentHooks, } from "./provider-registry.js";
2
+ // Installing agent hooks edits the user's real agent config files, so it only
3
+ // happens when `enableTerminalAgentHooks` is on. At boot we install when enabled
4
+ // and otherwise leave the configs untouched; toggling the setting live installs
5
+ // on enable and removes our marker-matched hooks on disable so opting out cleans
6
+ // up after itself. Returns an unsubscribe for the field-change listener.
7
+ export function applyTerminalAgentHookSetting(options) {
8
+ const { store, logger, install } = options;
9
+ const installOptions = { ...install, logger };
10
+ if (store.get().enableTerminalAgentHooks) {
11
+ installRegisteredAgentHooks(installOptions);
12
+ }
13
+ return store.onFieldChange("enableTerminalAgentHooks", (value) => {
14
+ if (value === true) {
15
+ installRegisteredAgentHooks(installOptions);
16
+ return;
17
+ }
18
+ try {
19
+ uninstallRegisteredAgentHooks(install);
20
+ }
21
+ catch (error) {
22
+ logger?.warn({ err: error }, "Failed to remove terminal activity hooks");
23
+ }
24
+ });
25
+ }
26
+ //# sourceMappingURL=terminal-agent-hook-setting.js.map
@@ -1,3 +1,6 @@
1
1
  import type { TerminalManager } from "./terminal-manager.js";
2
- export declare function createConfiguredTerminalManager(): TerminalManager;
2
+ export interface ConfiguredTerminalManagerOptions {
3
+ getTerminalActivityUrl?: () => string | null;
4
+ }
5
+ export declare function createConfiguredTerminalManager(options?: ConfiguredTerminalManagerOptions): TerminalManager;
3
6
  //# sourceMappingURL=terminal-manager-factory.d.ts.map
@@ -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): Promise<TerminalSession[]>;
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 { resolve, sep, win32, posix } from "node:path";
4
- import { isSameOrDescendantPath } from "../server/path-utils.js";
5
- export function createTerminalManager() {
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 session = registerSession(await createTerminal({
121
- ...(options.id ? { id: options.id } : {}),
122
- cwd: options.cwd,
123
- name: options.name ?? defaultName,
124
- ...(options.title ? { title: options.title } : {}),
125
- ...(options.command ? { command: options.command } : {}),
126
- ...(options.args ? { args: options.args } : {}),
127
- ...(mergedEnv ? { env: mergedEnv } : {}),
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
- killTerminalsUnderPath(rootPath: string): Promise<void>;
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 emitTerminalsSnapshotForRoot;
54
+ private emitTerminalsSnapshotForSubscription;
53
55
  private handleListTerminalsRequest;
54
56
  private getAllTerminalSessions;
55
57
  private getTerminalsForWorkspaceRoot;