@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
@@ -2,31 +2,32 @@ import type { Logger } from "pino";
2
2
  import type { AgentManager } from "../agent/agent-manager.js";
3
3
  import type { AgentStorage } from "../agent/agent-storage.js";
4
4
  import type { DaemonConfigStore } from "../daemon-config-store.js";
5
- import { archivePaseoWorktree, killTerminalsUnderPath } from "../paseo-worktree-archive-service.js";
6
- import { isSameOrDescendantPath } from "../path-utils.js";
5
+ import { archiveByScope, type ActiveWorkspaceRef, killTerminalsForWorkspace, resolveWorkspaceIdAtPath } from "../workspace-archive-service.js";
7
6
  import type { WorkspaceGitRuntimeSnapshot, WorkspaceGitServiceImpl } from "../workspace-git-service.js";
8
7
  import type { GitHubService } from "../../services/github-service.js";
9
8
  import type { TerminalManager } from "../../terminal/terminal-manager.js";
10
9
  import { isPaseoOwnedWorktreeCwd } from "../../utils/worktree.js";
11
10
  export interface AutoArchiveArchiveOptions {
12
11
  paseoHome: string;
13
- worktreesRoot?: string;
12
+ paseoWorktreesBaseRoot?: string;
14
13
  daemonConfigStore: DaemonConfigStore;
15
14
  workspaceGitService: WorkspaceGitServiceImpl;
16
15
  github: GitHubService;
17
16
  agentManager: AgentManager;
18
17
  agentStorage: AgentStorage;
19
18
  terminalManager: TerminalManager;
19
+ findWorkspaceIdForCwd: (cwd: string) => Promise<string | null>;
20
+ listActiveWorkspaces: () => Promise<ActiveWorkspaceRef[]>;
20
21
  archiveWorkspaceRecord: (workspaceId: string) => Promise<void>;
21
22
  markWorkspaceArchiving: (workspaceIds: Iterable<string>, archivingAt: string) => void;
22
23
  clearWorkspaceArchiving: (workspaceIds: Iterable<string>) => void;
23
24
  emitWorkspaceUpdatesForWorkspaceIds: (workspaceIds: Iterable<string>) => Promise<void>;
24
25
  }
25
26
  export interface ArchiveIfSafeDependencies {
26
- archivePaseoWorktree: typeof archivePaseoWorktree;
27
+ archiveByScope: typeof archiveByScope;
28
+ resolveWorkspaceIdAtPath: typeof resolveWorkspaceIdAtPath;
27
29
  isPaseoOwnedWorktreeCwd: typeof isPaseoOwnedWorktreeCwd;
28
- killTerminalsUnderPath: typeof killTerminalsUnderPath;
29
- isPathWithinRoot: typeof isSameOrDescendantPath;
30
+ killTerminalsForWorkspace: typeof killTerminalsForWorkspace;
30
31
  }
31
32
  export declare function archiveIfSafe(input: {
32
33
  cwd: string;
@@ -1,11 +1,10 @@
1
- import { archivePaseoWorktree, killTerminalsUnderPath } from "../paseo-worktree-archive-service.js";
2
- import { isSameOrDescendantPath } from "../path-utils.js";
1
+ import { archiveByScope, killTerminalsForWorkspace, resolveWorkspaceIdAtPath, } from "../workspace-archive-service.js";
3
2
  import { isPaseoOwnedWorktreeCwd } from "../../utils/worktree.js";
4
3
  const defaultDependencies = {
5
- archivePaseoWorktree,
4
+ archiveByScope,
5
+ resolveWorkspaceIdAtPath,
6
6
  isPaseoOwnedWorktreeCwd,
7
- killTerminalsUnderPath,
8
- isPathWithinRoot: isSameOrDescendantPath,
7
+ killTerminalsForWorkspace,
9
8
  };
10
9
  export async function archiveIfSafe(input) {
11
10
  const { cwd, pullRequest, inFlight, options, log } = input;
@@ -42,36 +41,42 @@ export async function archiveIfSafe(input) {
42
41
  }
43
42
  const ownership = await deps.isPaseoOwnedWorktreeCwd(cwd, {
44
43
  paseoHome: options.paseoHome,
45
- worktreesRoot: options.worktreesRoot,
44
+ worktreesRoot: options.paseoWorktreesBaseRoot,
46
45
  });
47
46
  if (!ownership.allowed) {
48
47
  return;
49
48
  }
50
49
  try {
51
- await deps.archivePaseoWorktree({
50
+ const workspaceId = await deps.resolveWorkspaceIdAtPath({
51
+ findWorkspaceIdForCwd: options.findWorkspaceIdForCwd,
52
+ listActiveWorkspaces: options.listActiveWorkspaces,
53
+ }, cwd);
54
+ if (!workspaceId) {
55
+ log.warn({ cwd }, "Auto-archive could not resolve a workspace for cwd; skipping");
56
+ return;
57
+ }
58
+ await deps.archiveByScope({
52
59
  paseoHome: options.paseoHome,
53
- worktreesRoot: options.worktreesRoot,
60
+ paseoWorktreesBaseRoot: options.paseoWorktreesBaseRoot,
54
61
  github: options.github,
55
62
  workspaceGitService: options.workspaceGitService,
56
63
  agentManager: options.agentManager,
57
64
  agentStorage: options.agentStorage,
65
+ findWorkspaceIdForCwd: options.findWorkspaceIdForCwd,
66
+ listActiveWorkspaces: options.listActiveWorkspaces,
58
67
  archiveWorkspaceRecord: options.archiveWorkspaceRecord,
59
68
  emitWorkspaceUpdatesForWorkspaceIds: options.emitWorkspaceUpdatesForWorkspaceIds,
60
69
  markWorkspaceArchiving: options.markWorkspaceArchiving,
61
70
  clearWorkspaceArchiving: options.clearWorkspaceArchiving,
62
- isPathWithinRoot: deps.isPathWithinRoot,
63
- killTerminalsUnderPath: (rootPath) => deps.killTerminalsUnderPath({
71
+ killTerminalsForWorkspace: (workspaceIdToKill) => deps.killTerminalsForWorkspace({
64
72
  terminalManager: options.terminalManager,
65
- isPathWithinRoot: deps.isPathWithinRoot,
66
- killTrackedTerminal: () => { },
67
73
  sessionLogger: log,
68
- }, rootPath),
74
+ }, workspaceIdToKill),
69
75
  sessionLogger: log,
70
76
  }, {
71
- targetPath: cwd,
77
+ scope: { kind: "workspace", workspaceId },
72
78
  repoRoot: ownership.repoRoot ?? null,
73
- worktreesRoot: ownership.worktreeRoot,
74
- worktreesBaseRoot: options.worktreesRoot,
79
+ paseoWorktreesBaseRoot: options.paseoWorktreesBaseRoot,
75
80
  requestId: "auto-archive-on-merge",
76
81
  });
77
82
  log.info({ cwd }, "Auto-archived worktree after PR merge");
@@ -1,3 +1,4 @@
1
+ import express from "express";
1
2
  import type { Logger } from "pino";
2
3
  export type ListenTarget = {
3
4
  type: "tcp";
@@ -26,6 +27,7 @@ import { type ServiceProxySubsystem } from "./service-proxy.js";
26
27
  import { WorkspaceScriptRuntimeStore } from "./workspace-script-runtime-store.js";
27
28
  import { type HostnamesConfig } from "./hostnames.js";
28
29
  import { type DaemonAuthConfig } from "./auth.js";
30
+ export declare function createTerminalActivityRouteHandler(terminalManager: TerminalManager): express.RequestHandler;
29
31
  export type PaseoOpenAIConfig = OpenAiSpeechProviderConfig;
30
32
  export type PaseoLocalSpeechConfig = LocalSpeechProviderConfig;
31
33
  export interface PaseoSpeechSttLanguages {
@@ -57,6 +59,7 @@ export interface PaseoDaemonConfig {
57
59
  mcpEnabled?: boolean;
58
60
  mcpInjectIntoAgents?: boolean;
59
61
  autoArchiveAfterMerge?: boolean;
62
+ enableTerminalAgentHooks?: boolean;
60
63
  appendSystemPrompt?: string;
61
64
  terminalProfiles?: TerminalProfile[];
62
65
  staticDir: string;
@@ -6,7 +6,7 @@ import { randomUUID } from "node:crypto";
6
6
  import { hostname as getHostname } from "node:os";
7
7
  import path from "node:path";
8
8
  import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
9
- import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
9
+ import { z } from "zod";
10
10
  import { createBranchChangeRouteHandler } from "./script-route-branch-handler.js";
11
11
  function resolveBoundListenTarget(listenTarget, httpServer) {
12
12
  if (listenTarget.type !== "tcp") {
@@ -72,7 +72,7 @@ function formatListenTarget(listenTarget) {
72
72
  }
73
73
  import { VoiceAssistantWebSocketServer } from "./websocket-server.js";
74
74
  import { createGitHubService } from "../services/github-service.js";
75
- import { createPaseoWorktree as createRegisteredPaseoWorktree } from "./paseo-worktree-service.js";
75
+ import { createPaseoWorktree as createRegisteredPaseoWorktree, createLocalCheckoutWorkspace, } from "./paseo-worktree-service.js";
76
76
  import { createPaseoWorktreeWorkflow } from "./worktree-session.js";
77
77
  import { DownloadTokenStore } from "./file-download/token-store.js";
78
78
  import { createSpeechService } from "./speech/speech-runtime.js";
@@ -90,10 +90,12 @@ import { LoopService } from "./loop-service.js";
90
90
  import { ScheduleService } from "./schedule/service.js";
91
91
  import { DaemonConfigStore } from "./daemon-config-store.js";
92
92
  import { WorkspaceGitServiceImpl } from "./workspace-git-service.js";
93
- import { archivePersistedWorkspaceRecord } from "./workspace-archive-service.js";
93
+ import { resolveWorkspaceIdForPath } from "./resolve-workspace-id-for-path.js";
94
+ import { archivePersistedWorkspaceRecord, } from "./workspace-archive-service.js";
94
95
  import { setupAutoArchiveOnMerge } from "./auto-archive-on-merge/index.js";
95
96
  import { wrapSessionMessage } from "./messages.js";
96
97
  import { createConfiguredTerminalManager } from "../terminal/terminal-manager-factory.js";
98
+ import { applyTerminalAgentHookSetting } from "../terminal/agent-hooks/terminal-agent-hook-setting.js";
97
99
  import { createConnectionOfferV2, encodeOfferToFragmentUrl } from "./connection-offer.js";
98
100
  import { loadOrCreateDaemonKeyPair } from "./daemon-keypair.js";
99
101
  import { startRelayTransport } from "./relay-transport.js";
@@ -127,6 +129,56 @@ function createAgentMcpBaseUrl(listenTarget) {
127
129
  const host = resolveAgentMcpClientHost(listenTarget.host);
128
130
  return new URL("/mcp/agents", `http://${formatHostForHttpUrl(host)}:${listenTarget.port}`).toString();
129
131
  }
132
+ function createTerminalActivityUrl(listenTarget) {
133
+ if (!listenTarget || listenTarget.type !== "tcp") {
134
+ return null;
135
+ }
136
+ const host = resolveAgentMcpClientHost(listenTarget.host);
137
+ return new URL("/api/terminal-activity", `http://${formatHostForHttpUrl(host)}:${listenTarget.port}`).toString();
138
+ }
139
+ const TerminalActivityReportSchema = z.object({
140
+ terminalId: z.string().min(1),
141
+ token: z.string().min(1),
142
+ state: z.enum(["running", "idle", "needs-input"]),
143
+ });
144
+ const TERMINAL_ACTIVITY_STATE_MAP = {
145
+ running: "working",
146
+ idle: "idle",
147
+ "needs-input": "attention",
148
+ };
149
+ const LOOPBACK_REMOTE_ADDRESSES = new Set(["127.0.0.1", "::1", "::ffff:127.0.0.1"]);
150
+ function isLoopbackRemoteAddress(remoteAddress) {
151
+ return remoteAddress !== undefined && LOOPBACK_REMOTE_ADDRESSES.has(remoteAddress);
152
+ }
153
+ export function createTerminalActivityRouteHandler(terminalManager) {
154
+ return async (req, res) => {
155
+ if (!isLoopbackRemoteAddress(req.socket.remoteAddress)) {
156
+ res.status(403).json({ error: "Forbidden" });
157
+ return;
158
+ }
159
+ const parsed = TerminalActivityReportSchema.safeParse(req.body);
160
+ if (!parsed.success) {
161
+ res.status(400).json({ error: "Invalid terminal activity report" });
162
+ return;
163
+ }
164
+ const validation = terminalManager.validateTerminalActivityToken(parsed.data.terminalId, parsed.data.token);
165
+ if (validation !== "valid") {
166
+ res.status(403).json({ error: "Forbidden" });
167
+ return;
168
+ }
169
+ try {
170
+ const updated = await terminalManager.setTerminalActivity(parsed.data.terminalId, TERMINAL_ACTIVITY_STATE_MAP[parsed.data.state]);
171
+ if (!updated) {
172
+ res.status(403).json({ error: "Forbidden" });
173
+ return;
174
+ }
175
+ res.status(204).end();
176
+ }
177
+ catch {
178
+ res.status(500).json({ error: "Failed to update terminal activity" });
179
+ }
180
+ };
181
+ }
130
182
  function summarizeAgentMcpDebugMessage(body) {
131
183
  if (!body || typeof body !== "object" || Array.isArray(body)) {
132
184
  return {
@@ -173,6 +225,7 @@ export async function createPaseoDaemon(config, rootLogger) {
173
225
  providers: config.metadataGeneration?.providers ?? [],
174
226
  },
175
227
  autoArchiveAfterMerge: config.autoArchiveAfterMerge ?? false,
228
+ enableTerminalAgentHooks: config.enableTerminalAgentHooks ?? false,
176
229
  appendSystemPrompt: config.appendSystemPrompt ?? "",
177
230
  ...(config.terminalProfiles !== undefined
178
231
  ? { terminalProfiles: config.terminalProfiles }
@@ -198,6 +251,10 @@ export async function createPaseoDaemon(config, rootLogger) {
198
251
  const app = express();
199
252
  let boundListenTarget = null;
200
253
  let workspaceRegistry = null;
254
+ const terminalManager = createConfiguredTerminalManager({
255
+ getTerminalActivityUrl: () => createTerminalActivityUrl(boundListenTarget),
256
+ });
257
+ applyTerminalAgentHookSetting({ store: daemonConfigStore, logger });
201
258
  const serviceProxyPublicBaseUrl = config.serviceProxy?.publicBaseUrl
202
259
  ? config.serviceProxy.publicBaseUrl
203
260
  : null;
@@ -274,13 +331,14 @@ export async function createPaseoDaemon(config, rootLogger) {
274
331
  }
275
332
  next();
276
333
  });
334
+ // Local, harmless, and token-gated; deliberately skips daemon auth.
335
+ app.post("/api/terminal-activity", express.json(), createTerminalActivityRouteHandler(terminalManager));
277
336
  app.use(createRequireBearerMiddleware(config.auth, (context) => {
278
337
  logger.warn(context, "Rejected HTTP request with invalid daemon password");
279
338
  }));
339
+ app.use(express.json());
280
340
  // Serve static files from public directory
281
341
  app.use("/public", express.static(staticDir));
282
- // Middleware
283
- app.use(express.json());
284
342
  // Health check endpoint
285
343
  app.get("/api/health", (_req, res) => {
286
344
  res.json({ status: "ok", timestamp: new Date().toISOString() });
@@ -361,7 +419,6 @@ export async function createPaseoDaemon(config, rootLogger) {
361
419
  paseoHome: config.paseoHome,
362
420
  logger,
363
421
  });
364
- const terminalManager = createConfiguredTerminalManager();
365
422
  const github = createGitHubService();
366
423
  const workspaceGitService = new WorkspaceGitServiceImpl({
367
424
  logger,
@@ -468,9 +525,27 @@ export async function createPaseoDaemon(config, rootLogger) {
468
525
  await archivePersistedWorkspaceRecord({
469
526
  workspaceId,
470
527
  workspaceRegistry,
471
- projectRegistry,
472
528
  });
473
529
  };
530
+ // external path→workspace adapter, not ownership: archive-by-path requests that
531
+ // arrive with a worktree path and no workspaceId (old clients / CLI).
532
+ const findWorkspaceIdForCwdExternal = async (cwd) => {
533
+ return resolveWorkspaceIdForPath(cwd, await workspaceRegistry.list());
534
+ };
535
+ const ensureWorkspaceForCreateExternal = async (cwd) => {
536
+ const workspace = await createLocalCheckoutWorkspace({ cwd }, { projectRegistry, workspaceRegistry, workspaceGitService });
537
+ return workspace.workspaceId;
538
+ };
539
+ const listActiveWorkspacesExternal = async () => {
540
+ const workspaces = await workspaceRegistry.list();
541
+ return workspaces
542
+ .filter((workspace) => !workspace.archivedAt)
543
+ .map((workspace) => ({
544
+ workspaceId: workspace.workspaceId,
545
+ cwd: workspace.cwd,
546
+ kind: workspace.kind,
547
+ }));
548
+ };
474
549
  const markWorkspaceArchivingExternal = (workspaceIds, archivingAt) => {
475
550
  const workspaceIdList = Array.from(workspaceIds);
476
551
  for (const session of wsServer?.listActiveSessions() ?? []) {
@@ -492,7 +567,7 @@ export async function createPaseoDaemon(config, rootLogger) {
492
567
  };
493
568
  setupAutoArchiveOnMerge({
494
569
  paseoHome: config.paseoHome,
495
- worktreesRoot: config.worktreesRoot,
570
+ paseoWorktreesBaseRoot: config.worktreesRoot,
496
571
  daemonConfigStore,
497
572
  workspaceGitService,
498
573
  github,
@@ -500,6 +575,8 @@ export async function createPaseoDaemon(config, rootLogger) {
500
575
  agentStorage,
501
576
  terminalManager,
502
577
  logger,
578
+ findWorkspaceIdForCwd: findWorkspaceIdForCwdExternal,
579
+ listActiveWorkspaces: listActiveWorkspacesExternal,
503
580
  archiveWorkspaceRecord: archiveWorkspaceRecordExternal,
504
581
  markWorkspaceArchiving: markWorkspaceArchivingExternal,
505
582
  clearWorkspaceArchiving: clearWorkspaceArchivingExternal,
@@ -509,8 +586,7 @@ export async function createPaseoDaemon(config, rootLogger) {
509
586
  let agentMcpBaseUrl = null;
510
587
  if (mcpEnabled) {
511
588
  const agentMcpRoute = "/mcp/agents";
512
- const agentMcpTransports = new Map();
513
- const createAgentMcpTransport = async (callerAgentId) => {
589
+ const createAgentMcpSession = async (callerAgentId) => {
514
590
  const agentMcpServer = await createAgentMcpServer({
515
591
  agentManager,
516
592
  agentStorage,
@@ -520,10 +596,13 @@ export async function createPaseoDaemon(config, rootLogger) {
520
596
  providerSnapshotManager,
521
597
  github,
522
598
  workspaceGitService,
599
+ findWorkspaceIdForCwd: findWorkspaceIdForCwdExternal,
600
+ listActiveWorkspaces: listActiveWorkspacesExternal,
523
601
  archiveWorkspaceRecord: archiveWorkspaceRecordExternal,
524
602
  emitWorkspaceUpdatesForWorkspaceIds: emitWorkspaceUpdatesExternal,
525
603
  markWorkspaceArchiving: markWorkspaceArchivingExternal,
526
604
  clearWorkspaceArchiving: clearWorkspaceArchivingExternal,
605
+ ensureWorkspaceForCreate: ensureWorkspaceForCreateExternal,
527
606
  createPaseoWorktree: async (input, serviceOptions) => {
528
607
  return createPaseoWorktreeWorkflow({
529
608
  paseoHome: config.paseoHome,
@@ -546,11 +625,8 @@ export async function createPaseoDaemon(config, rootLogger) {
546
625
  ?.listActiveSessions()
547
626
  .map((session) => session.warmWorkspaceGitDataForWorkspace(workspace)) ?? []);
548
627
  },
549
- emitWorkspaceUpdateForCwd: async (cwd, emitOptions) => {
550
- await Promise.all(wsServer
551
- ?.listActiveSessions()
552
- .map((session) => session.emitWorkspaceUpdatesForExternalCwds([cwd])) ?? []);
553
- void emitOptions;
628
+ emitWorkspaceUpdateForWorkspaceId: async (workspaceId) => {
629
+ await emitWorkspaceUpdatesExternal([workspaceId]);
554
630
  },
555
631
  cacheWorkspaceSetupSnapshot: () => { },
556
632
  emit: emitExternalSessionMessage,
@@ -573,32 +649,24 @@ export async function createPaseoDaemon(config, rootLogger) {
573
649
  resolveCallerContext: (agentId) => wsServer?.resolveVoiceCallerContext(agentId) ?? null,
574
650
  logger,
575
651
  });
652
+ // Stateless mode: each HTTP request builds a fresh server + transport that is
653
+ // torn down when the response closes, so no per-session state is retained between
654
+ // requests. The agent control plane only lists and calls tools, neither of which
655
+ // needs cross-request state, so sessions would only pin memory for the life of the
656
+ // daemon (agents that exit without a clean DELETE never get reaped).
576
657
  const transport = new StreamableHTTPServerTransport({
577
- sessionIdGenerator: () => randomUUID(),
578
- onsessioninitialized: (sessionId) => {
579
- agentMcpTransports.set(sessionId, transport);
580
- logger.debug({ sessionId }, "Agent MCP session initialized");
581
- },
582
- onsessionclosed: (sessionId) => {
583
- agentMcpTransports.delete(sessionId);
584
- logger.debug({ sessionId }, "Agent MCP session closed");
585
- },
658
+ sessionIdGenerator: undefined,
586
659
  // NOTE: We enforce a Vite-like host allowlist at the app/websocket layer.
587
660
  // StreamableHTTPServerTransport's built-in check requires exact Host header matches.
588
661
  enableDnsRebindingProtection: false,
589
662
  });
590
663
  Object.assign(transport, {
591
- onclose: () => {
592
- if (transport.sessionId) {
593
- agentMcpTransports.delete(transport.sessionId);
594
- }
595
- },
596
664
  onerror: (err) => {
597
665
  logger.error({ err }, "Agent MCP transport error");
598
666
  },
599
667
  });
600
668
  await agentMcpServer.connect(transport);
601
- return transport;
669
+ return { server: agentMcpServer, transport };
602
670
  };
603
671
  const runAgentMcpRequest = async (req, res) => {
604
672
  // This route is exempt from the global daemon-password middleware, so it
@@ -623,41 +691,33 @@ export async function createPaseoDaemon(config, rootLogger) {
623
691
  }, "Agent MCP request");
624
692
  }
625
693
  try {
626
- const sessionId = req.header("mcp-session-id");
627
- let transport = sessionId ? agentMcpTransports.get(sessionId) : undefined;
628
- if (!transport) {
629
- if (req.method !== "POST") {
630
- res.status(400).json({
631
- jsonrpc: "2.0",
632
- error: {
633
- code: -32000,
634
- message: "Missing or invalid MCP session",
635
- },
636
- id: null,
637
- });
638
- return;
639
- }
640
- if (!isInitializeRequest(req.body)) {
641
- res.status(400).json({
642
- jsonrpc: "2.0",
643
- error: {
644
- code: -32000,
645
- message: "Initialization request expected",
646
- },
647
- id: null,
648
- });
649
- return;
650
- }
651
- const callerAgentIdRaw = req.query.callerAgentId;
652
- let callerAgentId;
653
- if (typeof callerAgentIdRaw === "string") {
654
- callerAgentId = callerAgentIdRaw;
655
- }
656
- else if (Array.isArray(callerAgentIdRaw) && typeof callerAgentIdRaw[0] === "string") {
657
- callerAgentId = callerAgentIdRaw[0];
658
- }
659
- transport = await createAgentMcpTransport(callerAgentId);
694
+ // Stateless: GET (standalone SSE) and DELETE (session termination) have no
695
+ // meaning without sessions. The MCP client tolerates 405 on the GET stream
696
+ // and never issues a DELETE because it is never handed a session id.
697
+ if (req.method !== "POST") {
698
+ res.status(405).json({
699
+ jsonrpc: "2.0",
700
+ error: {
701
+ code: -32000,
702
+ message: "Method not allowed",
703
+ },
704
+ id: null,
705
+ });
706
+ return;
660
707
  }
708
+ const callerAgentIdRaw = req.query.callerAgentId;
709
+ let callerAgentId;
710
+ if (typeof callerAgentIdRaw === "string") {
711
+ callerAgentId = callerAgentIdRaw;
712
+ }
713
+ else if (Array.isArray(callerAgentIdRaw) && typeof callerAgentIdRaw[0] === "string") {
714
+ callerAgentId = callerAgentIdRaw[0];
715
+ }
716
+ const { server, transport } = await createAgentMcpSession(callerAgentId);
717
+ res.on("close", () => {
718
+ void transport.close();
719
+ void server.close();
720
+ });
661
721
  await transport.handleRequest(req, res, req.body);
662
722
  }
663
723
  catch (err) {
@@ -762,6 +822,7 @@ export async function createPaseoDaemon(config, rootLogger) {
762
822
  }, projectRegistry, workspaceRegistry, chatService, loopService, scheduleService, checkoutDiffManager, serviceProxy, scriptRuntimeStore, handleBranchChange, () => (boundListenTarget?.type === "tcp" ? boundListenTarget.port : null), () => (boundListenTarget?.type === "tcp" ? boundListenTarget.host : null), (hostname) => scriptHealthMonitor.getHealthForHostname(hostname), workspaceGitService, github, config.pushNotificationSender, providerSnapshotManager, {
763
823
  listen: formatListenTarget(boundListenTarget ?? listenTarget),
764
824
  worktreesRoot: config.worktreesRoot,
825
+ appBaseUrl: config.appBaseUrl,
765
826
  relay: {
766
827
  enabled: relayEnabled,
767
828
  endpoint: relayEndpoint,
@@ -229,6 +229,7 @@ export function loadConfig(paseoHome, options) {
229
229
  mcpEnabled,
230
230
  mcpInjectIntoAgents,
231
231
  autoArchiveAfterMerge,
232
+ enableTerminalAgentHooks: persisted.daemon?.enableTerminalAgentHooks ?? false,
232
233
  appendSystemPrompt,
233
234
  terminalProfiles,
234
235
  mcpDebug: env.MCP_DEBUG === "1",
@@ -146,6 +146,7 @@ function mergeMutableConfigIntoPersistedConfig(params) {
146
146
  injectIntoAgents: mutable.mcp.injectIntoAgents,
147
147
  },
148
148
  autoArchiveAfterMerge: mutable.autoArchiveAfterMerge,
149
+ enableTerminalAgentHooks: mutable.enableTerminalAgentHooks,
149
150
  appendSystemPrompt: mutable.appendSystemPrompt,
150
151
  ...(mutable.terminalProfiles !== undefined
151
152
  ? { terminalProfiles: mutable.terminalProfiles }
@@ -14,7 +14,7 @@ export { DirectTcpHostConnectionSchema, type DirectTcpHostConnection, type Norma
14
14
  export { ensureLocalSpeechModels, listLocalSpeechModels, type LocalSpeechModelId, type LocalSttModelId, type LocalTtsModelId, } from "./speech/providers/local/models.js";
15
15
  export { applySherpaLoaderEnv, resolveSherpaLoaderEnv, sherpaLoaderEnvKey, sherpaPlatformArch, sherpaPlatformPackageName, type SherpaLoaderEnvKey, type SherpaLoaderEnvResolution, } from "./speech/providers/local/sherpa/sherpa-runtime-env.js";
16
16
  export { type ProviderOverride, type ProviderProfileModel, } from "./agent/provider-launch-config.js";
17
- export { findExecutable } from "../utils/executable.js";
17
+ export { findExecutable } from "../executable-resolution/executable-resolution.js";
18
18
  export { execCommand, spawnProcess } from "../utils/spawn.js";
19
19
  export { AGENT_PROVIDER_DEFINITIONS, BUILTIN_PROVIDER_IDS, type AgentProviderDefinition, } from "@getpaseo/protocol/provider-manifest";
20
20
  export type { AgentMode, AgentUsage, AgentCapabilityFlags, AgentPermissionRequest, AgentTimelineItem, ProviderSnapshotEntry, } from "./agent/agent-sdk-types.js";
@@ -14,7 +14,7 @@ export { PARENT_AGENT_ID_LABEL } from "@getpaseo/protocol/agent-labels";
14
14
  export { DirectTcpHostConnectionSchema, } from "@getpaseo/protocol/host-connection-schema";
15
15
  export { ensureLocalSpeechModels, listLocalSpeechModels, } from "./speech/providers/local/models.js";
16
16
  export { applySherpaLoaderEnv, resolveSherpaLoaderEnv, sherpaLoaderEnvKey, sherpaPlatformArch, sherpaPlatformPackageName, } from "./speech/providers/local/sherpa/sherpa-runtime-env.js";
17
- export { findExecutable } from "../utils/executable.js";
17
+ export { findExecutable } from "../executable-resolution/executable-resolution.js";
18
18
  export { execCommand, spawnProcess } from "../utils/spawn.js";
19
19
  // Provider manifest (source of truth for provider definitions)
20
20
  export { AGENT_PROVIDER_DEFINITIONS, BUILTIN_PROVIDER_IDS, } from "@getpaseo/protocol/provider-manifest";