@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
@@ -1,8 +1,22 @@
1
- import { fork } from "node:child_process";
2
1
  import { fileURLToPath } from "node:url";
3
- import { randomUUID } from "node:crypto";
4
- import { TerminalInputModeTracker } from "@getpaseo/protocol/terminal-input-mode";
2
+ import { fork } from "node:child_process";
3
+ import { randomBytes, randomUUID } from "node:crypto";
4
+ import { assertAbsolutePath, isSameOrDescendantPath } from "../server/path-utils.js";
5
+ import { deriveTerminalActivityStatusBucket } from "@getpaseo/protocol/terminal-activity";
5
6
  const REQUEST_TIMEOUT_MS = 10000;
7
+ function requiredWorkspaceId(workspaceId) {
8
+ if (workspaceId === undefined) {
9
+ throw new Error("workspaceId is required");
10
+ }
11
+ return workspaceId;
12
+ }
13
+ function asRequiredWorkerTerminalInfo(info) {
14
+ requiredWorkspaceId(info.workspaceId);
15
+ return info;
16
+ }
17
+ function createActivityToken() {
18
+ return randomBytes(32).toString("base64url");
19
+ }
6
20
  function resolveWorkerUrl() {
7
21
  const currentUrl = import.meta.url;
8
22
  if (currentUrl.endsWith(".ts")) {
@@ -34,7 +48,9 @@ function cloneTerminalInfo(info) {
34
48
  id: info.id,
35
49
  name: info.name,
36
50
  cwd: info.cwd,
51
+ workspaceId: info.workspaceId,
37
52
  ...(info.title ? { title: info.title } : {}),
53
+ activity: info.activity,
38
54
  };
39
55
  }
40
56
  function forkTerminalWorker() {
@@ -50,7 +66,10 @@ export function createWorkerTerminalManager(managerOptions = {}) {
50
66
  const pendingRequests = new Map();
51
67
  const recordsById = new Map();
52
68
  const terminalIdsByCwd = new Map();
69
+ const terminalActivityTokenById = new Map();
53
70
  const terminalsChangedListeners = new Set();
71
+ const terminalActivityListeners = new Set();
72
+ const terminalWorkspaceContributionChangedListeners = new Set();
54
73
  let workerExited = false;
55
74
  let workerShutdownTimer = null;
56
75
  function emitTerminalsChanged(event) {
@@ -63,6 +82,26 @@ export function createWorkerTerminalManager(managerOptions = {}) {
63
82
  }
64
83
  }
65
84
  }
85
+ function emitTerminalWorkspaceContributionChanged(event) {
86
+ for (const listener of Array.from(terminalWorkspaceContributionChangedListeners)) {
87
+ try {
88
+ listener(event);
89
+ }
90
+ catch {
91
+ // no-op
92
+ }
93
+ }
94
+ }
95
+ function emitTerminalActivityTransition(event) {
96
+ for (const listener of Array.from(terminalActivityListeners)) {
97
+ try {
98
+ listener(event);
99
+ }
100
+ catch {
101
+ // no-op
102
+ }
103
+ }
104
+ }
66
105
  function listTerminalItemsForCwd(cwd) {
67
106
  const terminalIds = terminalIdsByCwd.get(cwd);
68
107
  if (!terminalIds) {
@@ -78,7 +117,9 @@ export function createWorkerTerminalManager(managerOptions = {}) {
78
117
  id: record.info.id,
79
118
  name: record.info.name,
80
119
  cwd: record.info.cwd,
120
+ workspaceId: record.info.workspaceId,
81
121
  ...(record.info.title ? { title: record.info.title } : {}),
122
+ activity: record.activity,
82
123
  });
83
124
  }
84
125
  return terminals;
@@ -93,12 +134,14 @@ export function createWorkerTerminalManager(managerOptions = {}) {
93
134
  const record = {
94
135
  info: cloneTerminalInfo(input.info),
95
136
  state: input.state,
96
- inputModeTracker: new TerminalInputModeTracker(),
137
+ activity: input.info.activity,
138
+ replayPreamble: "",
97
139
  exitInfo: null,
98
140
  messageListeners: new Set(),
99
141
  exitListeners: new Set(),
100
142
  commandFinishedListeners: new Set(),
101
143
  titleChangeListeners: new Set(),
144
+ activityChangeListeners: new Set(),
102
145
  session: undefined,
103
146
  };
104
147
  const session = {
@@ -111,6 +154,9 @@ export function createWorkerTerminalManager(managerOptions = {}) {
111
154
  get cwd() {
112
155
  return record.info.cwd;
113
156
  },
157
+ get workspaceId() {
158
+ return record.info.workspaceId;
159
+ },
114
160
  send(message) {
115
161
  if (message.type === "resize") {
116
162
  record.state = {
@@ -163,6 +209,27 @@ export function createWorkerTerminalManager(managerOptions = {}) {
163
209
  record.titleChangeListeners.delete(listener);
164
210
  };
165
211
  },
212
+ onActivityChange(listener) {
213
+ record.activityChangeListeners.add(listener);
214
+ return () => {
215
+ record.activityChangeListeners.delete(listener);
216
+ };
217
+ },
218
+ getActivity() {
219
+ return record.activity;
220
+ },
221
+ setActivity(state) {
222
+ record.activity = { state, changedAt: Date.now() };
223
+ sendBestEffortRequest({ type: "setActivity", terminalId: record.info.id, state });
224
+ },
225
+ clearActivityAttention() {
226
+ if (record.activity?.attentionReason == null) {
227
+ return false;
228
+ }
229
+ record.activity = { state: record.activity.state, changedAt: Date.now() };
230
+ sendBestEffortRequest({ type: "clearAttention", terminalId: record.info.id });
231
+ return true;
232
+ },
166
233
  getSize() {
167
234
  return {
168
235
  rows: record.state.rows,
@@ -183,7 +250,12 @@ export function createWorkerTerminalManager(managerOptions = {}) {
183
250
  };
184
251
  },
185
252
  getReplayPreamble() {
186
- return record.inputModeTracker.getPreamble();
253
+ // Refreshed from every getTerminalState response, which the controller fetches
254
+ // before every snapshot replay (legacy + visible-snapshot restore). The one
255
+ // gap is restore.mode === "live", which replays without fetching state — there
256
+ // this can be stale/empty. No client sends "live" today; revisit (ship the
257
+ // preamble on the worker's snapshotReady) if one ever does.
258
+ return record.replayPreamble;
187
259
  },
188
260
  getTitle() {
189
261
  return record.info.title;
@@ -225,6 +297,7 @@ export function createWorkerTerminalManager(managerOptions = {}) {
225
297
  return undefined;
226
298
  }
227
299
  recordsById.delete(terminalId);
300
+ terminalActivityTokenById.delete(terminalId);
228
301
  const terminalIds = terminalIdsByCwd.get(record.info.cwd);
229
302
  if (terminalIds) {
230
303
  terminalIds.delete(terminalId);
@@ -242,8 +315,8 @@ export function createWorkerTerminalManager(managerOptions = {}) {
242
315
  if (message.message.type === "snapshot") {
243
316
  record.state = message.message.state;
244
317
  }
245
- if (message.message.type === "output") {
246
- record.inputModeTracker.feed(message.message.data);
318
+ if (message.message.type === "snapshotReady" && message.message.replayPreamble !== undefined) {
319
+ record.replayPreamble = message.message.replayPreamble;
247
320
  }
248
321
  for (const listener of Array.from(record.messageListeners)) {
249
322
  listener(message.message);
@@ -259,7 +332,15 @@ export function createWorkerTerminalManager(managerOptions = {}) {
259
332
  listener(message.info);
260
333
  }
261
334
  record.exitListeners.clear();
262
- removeRecord(message.terminalId);
335
+ const previousBucket = deriveTerminalActivityStatusBucket(record.activity);
336
+ const removedRecord = removeRecord(message.terminalId);
337
+ if (previousBucket !== null && removedRecord) {
338
+ emitTerminalWorkspaceContributionChanged({
339
+ terminalId: removedRecord.info.id,
340
+ cwd: removedRecord.info.cwd,
341
+ workspaceId: removedRecord.info.workspaceId,
342
+ });
343
+ }
263
344
  emitTerminalsChanged({
264
345
  cwd: record.info.cwd,
265
346
  terminals: listTerminalItemsForCwd(record.info.cwd),
@@ -299,28 +380,52 @@ export function createWorkerTerminalManager(managerOptions = {}) {
299
380
  listener(message.info);
300
381
  }
301
382
  }
302
- function handleTerminalsChangedEvent(message) {
383
+ function handleTerminalActivityChangeEvent(message) {
384
+ const record = recordsById.get(message.terminalId);
385
+ if (!record) {
386
+ return;
387
+ }
388
+ const previousActivity = record.activity;
389
+ record.activity = message.activity;
390
+ const transition = {
391
+ activity: message.activity,
392
+ previous: message.previous,
393
+ };
394
+ for (const listener of Array.from(record.activityChangeListeners)) {
395
+ listener(transition);
396
+ }
397
+ emitTerminalActivityTransition({
398
+ terminalId: record.info.id,
399
+ name: record.info.name,
400
+ cwd: record.info.cwd,
401
+ workspaceId: record.info.workspaceId,
402
+ activity: message.activity,
403
+ previous: message.previous,
404
+ });
405
+ const previousBucket = deriveTerminalActivityStatusBucket(previousActivity);
406
+ const nextBucket = deriveTerminalActivityStatusBucket(message.activity);
407
+ if (previousBucket !== nextBucket) {
408
+ emitTerminalWorkspaceContributionChanged({
409
+ terminalId: record.info.id,
410
+ cwd: record.info.cwd,
411
+ workspaceId: record.info.workspaceId,
412
+ });
413
+ }
303
414
  emitTerminalsChanged({
304
- cwd: message.cwd,
305
- terminals: message.terminals.map((terminal) => ({
306
- id: terminal.id,
307
- name: terminal.name,
308
- cwd: terminal.cwd,
309
- ...(terminal.title ? { title: terminal.title } : {}),
310
- })),
415
+ cwd: record.info.cwd,
416
+ terminals: listTerminalItemsForCwd(record.info.cwd),
311
417
  });
312
418
  }
313
419
  function handleWorkerEvent(message) {
314
420
  switch (message.type) {
315
421
  case "terminalCreated": {
316
- registerRecord({ info: message.terminal, state: message.state });
317
- return;
318
- }
319
- case "terminalRemoved": {
320
- removeRecord(message.terminalId);
422
+ registerRecord({
423
+ info: asRequiredWorkerTerminalInfo(message.terminal),
424
+ state: message.state,
425
+ });
321
426
  emitTerminalsChanged({
322
- cwd: message.cwd,
323
- terminals: listTerminalItemsForCwd(message.cwd),
427
+ cwd: message.terminal.cwd,
428
+ terminals: listTerminalItemsForCwd(message.terminal.cwd),
324
429
  });
325
430
  return;
326
431
  }
@@ -340,8 +445,8 @@ export function createWorkerTerminalManager(managerOptions = {}) {
340
445
  handleTerminalCommandFinishedEvent(message);
341
446
  return;
342
447
  }
343
- case "terminalsChanged": {
344
- handleTerminalsChangedEvent(message);
448
+ case "terminalActivityChange": {
449
+ handleTerminalActivityChangeEvent(message);
345
450
  return;
346
451
  }
347
452
  }
@@ -408,19 +513,56 @@ export function createWorkerTerminalManager(managerOptions = {}) {
408
513
  // lifecycle state; do not let fire-and-forget sends crash the daemon.
409
514
  });
410
515
  }
411
- function toSessions(terminals) {
412
- return terminals
413
- .map((terminal) => recordsById.get(terminal.id)?.session)
414
- .filter((session) => Boolean(session));
415
- }
416
516
  return {
417
- async getTerminals(cwd) {
418
- const result = (await sendRequest({ type: "getTerminals", cwd }));
419
- return toSessions(result);
517
+ async getTerminals(cwd, options) {
518
+ assertAbsolutePath(cwd);
519
+ // Served from the local mirror, exactly like every other parent read.
520
+ // Terminals are bucketed by exact cwd, but an agent can open a terminal in
521
+ // a subdirectory of the workspace. A query for the workspace root must
522
+ // surface those too, so aggregate every bucket at or below `cwd`.
523
+ const sessions = [];
524
+ for (const [bucketCwd, terminalIds] of terminalIdsByCwd) {
525
+ if (!isSameOrDescendantPath(cwd, bucketCwd)) {
526
+ continue;
527
+ }
528
+ for (const terminalId of terminalIds) {
529
+ const session = recordsById.get(terminalId)?.session;
530
+ if (session) {
531
+ sessions.push(session);
532
+ }
533
+ }
534
+ }
535
+ // When the query carries a workspaceId, two workspaces sharing a cwd must
536
+ // not see each other's terminals. A missing owner is not workspace
537
+ // membership; unscoped callers can still list those legacy terminals.
538
+ if (options?.workspaceId !== undefined) {
539
+ return sessions.filter((session) => session.workspaceId === options.workspaceId);
540
+ }
541
+ return sessions;
420
542
  },
421
543
  async createTerminal(options) {
422
- const result = (await sendRequest({ type: "createTerminal", options }));
423
- return registerRecord({ info: result.terminal, state: result.state });
544
+ const terminalId = options.id ?? randomUUID();
545
+ const activityToken = createActivityToken();
546
+ const terminalActivityUrl = managerOptions.getTerminalActivityUrl?.() ?? null;
547
+ terminalActivityTokenById.set(terminalId, activityToken);
548
+ let result;
549
+ try {
550
+ result = (await sendRequest({
551
+ type: "createTerminal",
552
+ options: {
553
+ ...options,
554
+ id: terminalId,
555
+ activityToken,
556
+ activityUrl: terminalActivityUrl,
557
+ },
558
+ }));
559
+ }
560
+ catch (error) {
561
+ terminalActivityTokenById.delete(terminalId);
562
+ throw error;
563
+ }
564
+ const session = registerRecord({ info: result.terminal, state: result.state });
565
+ return session;
424
566
  },
425
567
  registerCwdEnv(options) {
426
568
  sendBestEffortRequest({
@@ -429,15 +571,29 @@ export function createWorkerTerminalManager(managerOptions = {}) {
429
571
  env: options.env,
430
572
  });
431
573
  },
574
+ validateTerminalActivityToken(terminalId, token) {
575
+ const expected = terminalActivityTokenById.get(terminalId);
576
+ if (!expected) {
577
+ return "unknown";
578
+ }
579
+ return expected === token ? "valid" : "invalid";
580
+ },
432
581
  getTerminal(id) {
433
582
  return recordsById.get(id)?.session;
434
583
  },
435
584
  async getTerminalState(id, options) {
436
- return (await sendRequest({
585
+ const snapshot = (await sendRequest({
437
586
  type: "getTerminalState",
438
587
  terminalId: id,
439
588
  ...(options ? { options } : {}),
440
589
  }));
590
+ if (snapshot && snapshot.replayPreamble !== undefined) {
591
+ const record = recordsById.get(id);
592
+ if (record) {
593
+ record.replayPreamble = snapshot.replayPreamble;
594
+ }
595
+ }
596
+ return snapshot;
441
597
  },
442
598
  setTerminalTitle(id, title) {
443
599
  const session = recordsById.get(id)?.session;
@@ -447,6 +603,22 @@ export function createWorkerTerminalManager(managerOptions = {}) {
447
603
  session.setTitle(title);
448
604
  return true;
449
605
  },
606
+ async setTerminalActivity(id, state) {
607
+ const record = recordsById.get(id);
608
+ if (!record) {
609
+ return false;
610
+ }
611
+ await sendRequest({ type: "setActivity", terminalId: id, state });
612
+ return true;
613
+ },
614
+ async clearTerminalAttention(id) {
615
+ const record = recordsById.get(id);
616
+ if (!record || record.activity?.attentionReason == null) {
617
+ return false;
618
+ }
619
+ await sendRequest({ type: "clearAttention", terminalId: id });
620
+ return true;
621
+ },
450
622
  killTerminal(id) {
451
623
  void sendRequest({ type: "killTerminal", terminalId: id }).catch(() => {
452
624
  // no-op; kill is intentionally best-effort and synchronous in the public interface.
@@ -496,6 +668,18 @@ export function createWorkerTerminalManager(managerOptions = {}) {
496
668
  terminalsChangedListeners.delete(listener);
497
669
  };
498
670
  },
671
+ subscribeTerminalActivity(listener) {
672
+ terminalActivityListeners.add(listener);
673
+ return () => {
674
+ terminalActivityListeners.delete(listener);
675
+ };
676
+ },
677
+ subscribeTerminalWorkspaceContributionChanged(listener) {
678
+ terminalWorkspaceContributionChangedListeners.add(listener);
679
+ return () => {
680
+ terminalWorkspaceContributionChangedListeners.delete(listener);
681
+ };
682
+ },
499
683
  };
500
684
  }
501
685
  export function terminateWorkerTerminalManager(manager) {
@@ -1,4 +1,4 @@
1
- export type MetadataConfigKey = "agentTitle" | "branchName" | "commitMessage" | "pullRequest";
1
+ export type MetadataConfigKey = "branchName" | "commitMessage" | "pullRequest";
2
2
  export interface RepoRootResolver {
3
3
  resolveRepoRoot: (cwd: string) => Promise<string>;
4
4
  }
@@ -1,5 +1,5 @@
1
1
  import { isGitHubHost, normalizeHost, parseGitHubRemoteIdentity, parseGitRemoteLocation, } from "@getpaseo/protocol/git-remote";
2
- import { findExecutable } from "./executable.js";
2
+ import { findExecutable } from "../executable-resolution/executable-resolution.js";
3
3
  import { execCommand } from "./spawn.js";
4
4
  let sshExecutableLookup = null;
5
5
  const sshHostnameResolutionCache = new Map();
@@ -5,7 +5,7 @@ export interface TreeKillTarget {
5
5
  kill(signal?: NodeJS.Signals | number): boolean;
6
6
  once?(event: "exit", listener: () => void): unknown;
7
7
  }
8
- interface TerminateWithTreeKillOptions {
8
+ export interface TerminateWithTreeKillOptions {
9
9
  gracefulSignal?: NodeJS.Signals;
10
10
  forceSignal?: NodeJS.Signals;
11
11
  gracefulTimeoutMs: number;
@@ -13,6 +13,6 @@ interface TerminateWithTreeKillOptions {
13
13
  onForceSignal?: () => void;
14
14
  }
15
15
  export type TerminateWithTreeKillResult = "already-exited" | "terminated" | "killed" | "kill-timeout";
16
+ export type ProcessTerminator = (child: TreeKillTarget, options: TerminateWithTreeKillOptions) => Promise<TerminateWithTreeKillResult>;
16
17
  export declare function terminateWithTreeKill(child: TreeKillTarget, options: TerminateWithTreeKillOptions): Promise<TerminateWithTreeKillResult>;
17
- export {};
18
18
  //# sourceMappingURL=tree-kill.d.ts.map
@@ -1,9 +1,9 @@
1
1
  import { createRequire } from "node:module";
2
2
  import { existsSync } from "node:fs";
3
- import { extname } from "node:path";
4
- import { execCommand } from "./spawn.js";
5
- import { isWindowsCommandScript } from "./windows-command.js";
6
- export { quoteWindowsArgument, quoteWindowsCommand } from "./windows-command.js";
3
+ import { execCommand } from "../utils/spawn.js";
4
+ import { isWindowsCommandScript } from "../utils/windows-command.js";
5
+ import { windowsExecutableResolution } from "./windows.js";
6
+ export { quoteWindowsArgument, quoteWindowsCommand } from "../utils/windows-command.js";
7
7
  const require = createRequire(import.meta.url);
8
8
  const which = require("which");
9
9
  const PROBE_TIMEOUT_MS = 2000;
@@ -83,22 +83,24 @@ function classifyProbeError(error) {
83
83
  * Check a literal executable path. PATH search is handled by findExecutable().
84
84
  */
85
85
  export function executableExists(executablePath, exists = existsSync) {
86
- if (exists(executablePath))
87
- return executablePath;
88
- if (process.platform === "win32" && !extname(executablePath)) {
89
- for (const ext of [".exe", ".cmd"]) {
90
- const candidate = executablePath + ext;
91
- if (exists(candidate))
92
- return candidate;
93
- }
86
+ if (process.platform === "win32") {
87
+ return windowsExecutableResolution.exists(executablePath, { exists });
94
88
  }
95
- return null;
89
+ return exists(executablePath) ? executablePath : null;
96
90
  }
97
91
  export async function findExecutable(name, probeTimeoutMs = PROBE_TIMEOUT_MS) {
98
92
  const trimmed = name.trim();
99
93
  if (!trimmed) {
100
94
  return null;
101
95
  }
96
+ if (process.platform === "win32") {
97
+ return windowsExecutableResolution.find(trimmed, {
98
+ enumeratePathCandidates: enumerateCandidates,
99
+ probeExecutable,
100
+ exists: existsSync,
101
+ probeTimeoutMs,
102
+ });
103
+ }
102
104
  if (hasPathSeparator(trimmed)) {
103
105
  return (await probeExecutable(trimmed, probeTimeoutMs)) ? trimmed : null;
104
106
  }
@@ -113,4 +115,4 @@ export async function findExecutable(name, probeTimeoutMs = PROBE_TIMEOUT_MS) {
113
115
  export async function isCommandAvailable(command) {
114
116
  return (await findExecutable(command)) !== null;
115
117
  }
116
- //# sourceMappingURL=executable.js.map
118
+ //# sourceMappingURL=executable-resolution.js.map
@@ -0,0 +1,62 @@
1
+ import { readdirSync } from "node:fs";
2
+ import { extname, join } from "node:path";
3
+ function hasPathSeparator(value) {
4
+ return value.includes("/") || value.includes("\\");
5
+ }
6
+ function enumerateLiteralPathCandidates(executablePath) {
7
+ if (extname(executablePath)) {
8
+ return [executablePath];
9
+ }
10
+ return [executablePath, `${executablePath}.exe`, `${executablePath}.cmd`];
11
+ }
12
+ function enumerateWingetPackageCandidates(name, localAppData) {
13
+ if (!localAppData) {
14
+ return [];
15
+ }
16
+ const wingetPackages = join(localAppData, "Microsoft", "WinGet", "Packages");
17
+ let packageDirs;
18
+ try {
19
+ packageDirs = readdirSync(wingetPackages, { withFileTypes: true })
20
+ .filter((entry) => entry.isDirectory())
21
+ .map((entry) => entry.name);
22
+ }
23
+ catch {
24
+ return [];
25
+ }
26
+ const exeName = `${name}.exe`;
27
+ return packageDirs.map((packageDir) => join(wingetPackages, packageDir, exeName));
28
+ }
29
+ async function find(input, options) {
30
+ if (hasPathSeparator(input)) {
31
+ return findFirstProbeable(enumerateLiteralPathCandidates(input), options);
32
+ }
33
+ const pathCandidates = await options.enumeratePathCandidates(input);
34
+ const wingetCandidates = enumerateWingetPackageCandidates(input, options.localAppData ?? process.env.LOCALAPPDATA).filter(options.exists);
35
+ return findFirstProbeable([...pathCandidates, ...wingetCandidates], options);
36
+ }
37
+ async function findFirstProbeable(candidates, options) {
38
+ const seen = new Set();
39
+ for (const candidate of candidates) {
40
+ if (seen.has(candidate)) {
41
+ continue;
42
+ }
43
+ seen.add(candidate);
44
+ if (await options.probeExecutable(candidate, options.probeTimeoutMs)) {
45
+ return candidate;
46
+ }
47
+ }
48
+ return null;
49
+ }
50
+ function exists(executablePath, options) {
51
+ for (const candidate of enumerateLiteralPathCandidates(executablePath)) {
52
+ if (options.exists(candidate)) {
53
+ return candidate;
54
+ }
55
+ }
56
+ return null;
57
+ }
58
+ export const windowsExecutableResolution = {
59
+ exists,
60
+ find,
61
+ };
62
+ //# sourceMappingURL=windows.js.map
@@ -1,5 +1,5 @@
1
1
  import { isAbsolute } from "node:path";
2
- import { executableExists, findExecutable } from "../../utils/executable.js";
2
+ import { executableExists, findExecutable, } from "../../executable-resolution/executable-resolution.js";
3
3
  import { createExternalProcessEnv } from "../paseo-env.js";
4
4
  export { AgentProviderRuntimeSettingsMapSchema, ProviderCommandSchema, ProviderOverrideSchema, ProviderOverridesSchema, ProviderProfileModelSchema, ProviderRuntimeSettingsSchema, } from "@getpaseo/protocol/provider-config";
5
5
  import { ProviderOverrideSchema, ProviderOverridesSchema, ProviderRuntimeSettingsSchema, } from "@getpaseo/protocol/provider-config";
@@ -187,6 +187,7 @@ export const PersistedConfigSchema = z
187
187
  .passthrough()
188
188
  .optional(),
189
189
  autoArchiveAfterMerge: z.boolean().optional(),
190
+ enableTerminalAgentHooks: z.boolean().optional(),
190
191
  appendSystemPrompt: z.string().optional(),
191
192
  terminalProfiles: z.array(TerminalProfileSchema).optional(),
192
193
  cors: z
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getpaseo/server",
3
- "version": "0.1.95",
3
+ "version": "0.1.97-beta.1",
4
4
  "description": "Paseo backend server",
5
5
  "files": [
6
6
  "dist/server",
@@ -22,6 +22,11 @@
22
22
  "types": "./dist/server/utils/tool-call-parsers.d.ts",
23
23
  "source": "./src/utils/tool-call-parsers.ts",
24
24
  "default": "./dist/server/utils/tool-call-parsers.js"
25
+ },
26
+ "./agent-hooks": {
27
+ "types": "./dist/server/terminal/agent-hooks/provider-registry.d.ts",
28
+ "source": "./src/terminal/agent-hooks/provider-registry.ts",
29
+ "default": "./dist/server/terminal/agent-hooks/provider-registry.js"
25
30
  }
26
31
  },
27
32
  "publishConfig": {
@@ -59,10 +64,10 @@
59
64
  "dependencies": {
60
65
  "@agentclientprotocol/sdk": "^0.17.1",
61
66
  "@anthropic-ai/claude-agent-sdk": "^0.2.133",
62
- "@getpaseo/client": "0.1.95",
63
- "@getpaseo/highlight": "0.1.95",
64
- "@getpaseo/protocol": "0.1.95",
65
- "@getpaseo/relay": "0.1.95",
67
+ "@getpaseo/client": "0.1.97-beta.1",
68
+ "@getpaseo/highlight": "0.1.97-beta.1",
69
+ "@getpaseo/protocol": "0.1.97-beta.1",
70
+ "@getpaseo/relay": "0.1.97-beta.1",
66
71
  "@isaacs/ttlcache": "^2.1.4",
67
72
  "@modelcontextprotocol/sdk": "^1.20.1",
68
73
  "@opencode-ai/sdk": "1.14.46",
@@ -1,36 +0,0 @@
1
- import type { Logger } from "pino";
2
- import type { AgentManager } from "./agent-manager.js";
3
- import { generateStructuredAgentResponseWithFallback } from "./agent-response-loop.js";
4
- import { type StructuredGenerationDaemonConfig } from "./structured-generation-providers.js";
5
- import type { WorkspaceGitService } from "../workspace-git-service.js";
6
- import type { ProviderSnapshotManager } from "./provider-snapshot-manager.js";
7
- export interface AgentMetadataGeneratorDeps {
8
- generateStructuredAgentResponseWithFallback?: typeof generateStructuredAgentResponseWithFallback;
9
- }
10
- export interface AgentMetadataGenerationOptions {
11
- agentManager: AgentManager;
12
- agentId: string;
13
- cwd: string;
14
- workspaceGitService?: Pick<WorkspaceGitService, "resolveRepoRoot">;
15
- providerSnapshotManager?: Pick<ProviderSnapshotManager, "listProviders">;
16
- daemonConfig?: StructuredGenerationDaemonConfig | null;
17
- currentSelection?: {
18
- provider?: string | null;
19
- model?: string | null;
20
- thinkingOptionId?: string | null;
21
- };
22
- initialPrompt?: string | null;
23
- explicitTitle?: string | null;
24
- paseoHome?: string;
25
- logger: Logger;
26
- deps?: AgentMetadataGeneratorDeps;
27
- }
28
- interface AgentMetadataNeeds {
29
- prompt: string | null;
30
- needsTitle: boolean;
31
- }
32
- export declare function determineAgentMetadataNeeds(options: Pick<AgentMetadataGenerationOptions, "initialPrompt" | "explicitTitle" | "cwd" | "paseoHome" | "deps">): Promise<AgentMetadataNeeds>;
33
- export declare function generateAndApplyAgentMetadata(options: AgentMetadataGenerationOptions): Promise<void>;
34
- export declare function scheduleAgentMetadataGeneration(options: AgentMetadataGenerationOptions): void;
35
- export {};
36
- //# sourceMappingURL=agent-metadata-generator.d.ts.map