@getpaseo/server 0.1.93 → 0.1.95

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 (33) hide show
  1. package/dist/server/server/agent/agent-manager.d.ts +14 -0
  2. package/dist/server/server/agent/agent-manager.js +36 -0
  3. package/dist/server/server/agent/agent-sdk-types.d.ts +1 -0
  4. package/dist/server/server/agent/providers/claude/agent.d.ts +1 -0
  5. package/dist/server/server/agent/providers/claude/agent.js +58 -3
  6. package/dist/server/server/agent/providers/claude/task-notification-tool-call.d.ts +2 -2
  7. package/dist/server/server/agent/providers/mock-load-test-agent.js +55 -28
  8. package/dist/server/server/agent/providers/pi/agent.js +3 -1
  9. package/dist/server/server/agent/providers/pi/session-descriptor.d.ts +5 -0
  10. package/dist/server/server/agent/providers/pi/session-descriptor.js +74 -12
  11. package/dist/server/server/agent/runtime-mcp-config.d.ts +6 -0
  12. package/dist/server/server/agent/runtime-mcp-config.js +3 -0
  13. package/dist/server/server/auth.d.ts +13 -0
  14. package/dist/server/server/auth.js +35 -0
  15. package/dist/server/server/bootstrap.d.ts +2 -0
  16. package/dist/server/server/bootstrap.js +28 -1
  17. package/dist/server/server/config.js +3 -1
  18. package/dist/server/server/daemon-config-store.js +3 -0
  19. package/dist/server/server/loop-service.d.ts +6 -6
  20. package/dist/server/server/persisted-config.d.ts +61 -0
  21. package/dist/server/server/persisted-config.js +2 -0
  22. package/dist/server/server/session.d.ts +6 -0
  23. package/dist/server/server/session.js +115 -7
  24. package/dist/server/server/websocket-server.js +2 -0
  25. package/dist/server/server/workspace-git-service.d.ts +4 -1
  26. package/dist/server/server/workspace-git-service.js +40 -22
  27. package/dist/server/server/workspace-reconciliation-service.js +10 -5
  28. package/dist/server/services/github-service.d.ts +57 -0
  29. package/dist/server/services/github-service.js +434 -5
  30. package/dist/server/terminal/terminal-session-controller.d.ts +6 -0
  31. package/dist/server/terminal/terminal-session-controller.js +36 -2
  32. package/dist/src/server/persisted-config.js +2 -0
  33. package/package.json +5 -5
@@ -19,6 +19,7 @@ import { AgentStorage } from "./agent/agent-storage.js";
19
19
  import type { TerminalManager } from "../terminal/terminal-manager.js";
20
20
  import type { PushNotificationSender } from "./push/notifications.js";
21
21
  import type { AgentClient, AgentProvider } from "./agent/agent-sdk-types.js";
22
+ import type { TerminalProfile } from "@getpaseo/protocol/messages";
22
23
  import type { AgentProviderRuntimeSettingsMap, ProviderOverride } from "./agent/provider-launch-config.js";
23
24
  import type { PersistedConfig } from "./persisted-config.js";
24
25
  import { type ServiceProxySubsystem } from "./service-proxy.js";
@@ -57,6 +58,7 @@ export interface PaseoDaemonConfig {
57
58
  mcpInjectIntoAgents?: boolean;
58
59
  autoArchiveAfterMerge?: boolean;
59
60
  appendSystemPrompt?: string;
61
+ terminalProfiles?: TerminalProfile[];
60
62
  staticDir: string;
61
63
  mcpDebug: boolean;
62
64
  isDev?: boolean;
@@ -104,7 +104,7 @@ import { ScriptHealthMonitor } from "./script-health-monitor.js";
104
104
  import { createScriptStatusEmitter } from "./script-status-projection.js";
105
105
  import { WorkspaceScriptRuntimeStore } from "./workspace-script-runtime-store.js";
106
106
  import { isHostnameAllowed } from "./hostnames.js";
107
- import { createRequireBearerMiddleware } from "./auth.js";
107
+ import { createRequireBearerMiddleware, isAgentMcpRequestAuthorized, } from "./auth.js";
108
108
  const MAX_MCP_DEBUG_BATCH_ITEMS = 10;
109
109
  const REDACTED_LOG_VALUE = "[redacted]";
110
110
  const DOWNLOAD_OPEN_FLAGS = process.platform === "win32" ? constants.O_RDONLY : constants.O_RDONLY | constants.O_NOFOLLOW;
@@ -174,6 +174,9 @@ export async function createPaseoDaemon(config, rootLogger) {
174
174
  },
175
175
  autoArchiveAfterMerge: config.autoArchiveAfterMerge ?? false,
176
176
  appendSystemPrompt: config.appendSystemPrompt ?? "",
177
+ ...(config.terminalProfiles !== undefined
178
+ ? { terminalProfiles: config.terminalProfiles }
179
+ : {}),
177
180
  }, logger);
178
181
  const serverId = getOrCreateServerId(config.paseoHome, { logger });
179
182
  const daemonKeyPair = await loadOrCreateDaemonKeyPair(config.paseoHome, logger);
@@ -183,6 +186,14 @@ export async function createPaseoDaemon(config, rootLogger) {
183
186
  const downloadTokenStore = new DownloadTokenStore({
184
187
  ttlMs: downloadTokenTtlMs,
185
188
  });
189
+ // Capability token authenticating the daemon's own agents to the loopback
190
+ // Agent MCP endpoint (/mcp/agents). Random per daemon run, injected only into
191
+ // local agent configs and the daemon's own MCP client — never sent to remote
192
+ // clients — so it cannot be replayed off-box. This lets the injected MCP
193
+ // authenticate even when the daemon password is set via the app (hash only,
194
+ // no plaintext available). Mirrors the /api/files/download capability-token
195
+ // pattern.
196
+ const agentMcpAuthToken = randomUUID();
186
197
  const listenTarget = parseListenString(config.listen);
187
198
  const app = express();
188
199
  let boundListenTarget = null;
@@ -375,6 +386,10 @@ export async function createPaseoDaemon(config, rootLogger) {
375
386
  providerDefinitions: initialAgentManagerState.providerDefinitions,
376
387
  registry: agentStorage,
377
388
  appendSystemPrompt: config.appendSystemPrompt,
389
+ onWorkspaceStateMayHaveChanged: ({ cwd }) => {
390
+ workspaceGitService.onWorkspaceStateMayHaveChanged(cwd);
391
+ },
392
+ mcpAuthToken: agentMcpAuthToken,
378
393
  logger,
379
394
  });
380
395
  const detachAgentStoragePersistence = attachAgentStoragePersistence(logger, agentManager, agentStorage);
@@ -586,6 +601,18 @@ export async function createPaseoDaemon(config, rootLogger) {
586
601
  return transport;
587
602
  };
588
603
  const runAgentMcpRequest = async (req, res) => {
604
+ // This route is exempt from the global daemon-password middleware, so it
605
+ // authenticates here using the injected capability token (or a valid
606
+ // daemon password). Without this, a password-protected daemon would be
607
+ // wide open on its agent control plane.
608
+ if (!(await isAgentMcpRequestAuthorized({
609
+ password: config.auth?.password,
610
+ capabilityToken: agentMcpAuthToken,
611
+ authorizationHeader: req.header("authorization"),
612
+ }))) {
613
+ res.status(401).json({ error: "Unauthorized" });
614
+ return;
615
+ }
589
616
  if (config.mcpDebug) {
590
617
  logger.debug({
591
618
  method: req.method,
@@ -192,6 +192,7 @@ function resolveStaticLoadConfigSettings(env, cli, persisted) {
192
192
  mcpInjectIntoAgents: cli?.mcpInjectIntoAgents ?? persisted.daemon?.mcp?.injectIntoAgents ?? false,
193
193
  autoArchiveAfterMerge: persisted.daemon?.autoArchiveAfterMerge ?? false,
194
194
  appendSystemPrompt: resolveAppendSystemPrompt(persisted),
195
+ terminalProfiles: persisted.daemon?.terminalProfiles,
195
196
  hostnames: mergeHostnames([
196
197
  persisted.daemon?.hostnames,
197
198
  parseHostnamesEnv(env.PASEO_HOSTNAMES ?? env.PASEO_ALLOWED_HOSTS),
@@ -204,7 +205,7 @@ export function loadConfig(paseoHome, options) {
204
205
  const env = options?.env ?? process.env;
205
206
  const persisted = loadPersistedConfig(paseoHome);
206
207
  const listen = resolveListenAddress(env, options?.cli, persisted);
207
- const { mcpEnabled, mcpInjectIntoAgents, autoArchiveAfterMerge, appendSystemPrompt, hostnames, appBaseUrl, } = resolveStaticLoadConfigSettings(env, options?.cli, persisted);
208
+ const { mcpEnabled, mcpInjectIntoAgents, autoArchiveAfterMerge, appendSystemPrompt, terminalProfiles, hostnames, appBaseUrl, } = resolveStaticLoadConfigSettings(env, options?.cli, persisted);
208
209
  const relay = resolveRelayConfig({
209
210
  env,
210
211
  persisted,
@@ -229,6 +230,7 @@ export function loadConfig(paseoHome, options) {
229
230
  mcpInjectIntoAgents,
230
231
  autoArchiveAfterMerge,
231
232
  appendSystemPrompt,
233
+ terminalProfiles,
232
234
  mcpDebug: env.MCP_DEBUG === "1",
233
235
  isDev: resolvePaseoNodeEnv(env) === "development",
234
236
  agentStoragePath: path.join(paseoHome, "agents"),
@@ -147,6 +147,9 @@ function mergeMutableConfigIntoPersistedConfig(params) {
147
147
  },
148
148
  autoArchiveAfterMerge: mutable.autoArchiveAfterMerge,
149
149
  appendSystemPrompt: mutable.appendSystemPrompt,
150
+ ...(mutable.terminalProfiles !== undefined
151
+ ? { terminalProfiles: mutable.terminalProfiles }
152
+ : {}),
150
153
  },
151
154
  agents: nextAgents,
152
155
  };
@@ -15,14 +15,14 @@ declare const LoopLogEntrySchema: z.ZodObject<{
15
15
  level: "error" | "info";
16
16
  seq: number;
17
17
  timestamp: string;
18
- source: "worker" | "loop" | "verifier" | "verify-check";
18
+ source: "loop" | "worker" | "verifier" | "verify-check";
19
19
  iteration: number | null;
20
20
  }, {
21
21
  text: string;
22
22
  level: "error" | "info";
23
23
  seq: number;
24
24
  timestamp: string;
25
- source: "worker" | "loop" | "verifier" | "verify-check";
25
+ source: "loop" | "worker" | "verifier" | "verify-check";
26
26
  iteration: number | null;
27
27
  }>;
28
28
  declare const LoopVerifyCheckResultSchema: z.ZodObject<{
@@ -314,14 +314,14 @@ declare const LoopRecordSchema: z.ZodObject<{
314
314
  level: "error" | "info";
315
315
  seq: number;
316
316
  timestamp: string;
317
- source: "worker" | "loop" | "verifier" | "verify-check";
317
+ source: "loop" | "worker" | "verifier" | "verify-check";
318
318
  iteration: number | null;
319
319
  }, {
320
320
  text: string;
321
321
  level: "error" | "info";
322
322
  seq: number;
323
323
  timestamp: string;
324
- source: "worker" | "loop" | "verifier" | "verify-check";
324
+ source: "loop" | "worker" | "verifier" | "verify-check";
325
325
  iteration: number | null;
326
326
  }>, "many">;
327
327
  nextLogSeq: z.ZodNumber;
@@ -384,7 +384,7 @@ declare const LoopRecordSchema: z.ZodObject<{
384
384
  level: "error" | "info";
385
385
  seq: number;
386
386
  timestamp: string;
387
- source: "worker" | "loop" | "verifier" | "verify-check";
387
+ source: "loop" | "worker" | "verifier" | "verify-check";
388
388
  iteration: number | null;
389
389
  }[];
390
390
  nextLogSeq: number;
@@ -445,7 +445,7 @@ declare const LoopRecordSchema: z.ZodObject<{
445
445
  level: "error" | "info";
446
446
  seq: number;
447
447
  timestamp: string;
448
- source: "worker" | "loop" | "verifier" | "verify-check";
448
+ source: "loop" | "worker" | "verifier" | "verify-check";
449
449
  iteration: number | null;
450
450
  }[];
451
451
  nextLogSeq: number;
@@ -21,6 +21,25 @@ export declare const PersistedConfigSchema: z.ZodObject<{
21
21
  }, z.ZodTypeAny, "passthrough">>>;
22
22
  autoArchiveAfterMerge: z.ZodOptional<z.ZodBoolean>;
23
23
  appendSystemPrompt: z.ZodOptional<z.ZodString>;
24
+ terminalProfiles: z.ZodOptional<z.ZodArray<z.ZodObject<{
25
+ id: z.ZodString;
26
+ name: z.ZodString;
27
+ command: z.ZodString;
28
+ args: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
29
+ icon: z.ZodOptional<z.ZodString>;
30
+ }, "passthrough", z.ZodTypeAny, z.objectOutputType<{
31
+ id: z.ZodString;
32
+ name: z.ZodString;
33
+ command: z.ZodString;
34
+ args: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
35
+ icon: z.ZodOptional<z.ZodString>;
36
+ }, z.ZodTypeAny, "passthrough">, z.objectInputType<{
37
+ id: z.ZodString;
38
+ name: z.ZodString;
39
+ command: z.ZodString;
40
+ args: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
41
+ icon: z.ZodOptional<z.ZodString>;
42
+ }, z.ZodTypeAny, "passthrough">>, "many">>;
24
43
  cors: z.ZodOptional<z.ZodObject<{
25
44
  allowedOrigins: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
26
45
  }, "strict", z.ZodTypeAny, {
@@ -80,6 +99,13 @@ export declare const PersistedConfigSchema: z.ZodObject<{
80
99
  allowedHosts?: true | string[] | undefined;
81
100
  autoArchiveAfterMerge?: boolean | undefined;
82
101
  appendSystemPrompt?: string | undefined;
102
+ terminalProfiles?: z.objectOutputType<{
103
+ id: z.ZodString;
104
+ name: z.ZodString;
105
+ command: z.ZodString;
106
+ args: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
107
+ icon: z.ZodOptional<z.ZodString>;
108
+ }, z.ZodTypeAny, "passthrough">[] | undefined;
83
109
  cors?: {
84
110
  allowedOrigins?: string[] | undefined;
85
111
  } | undefined;
@@ -108,6 +134,13 @@ export declare const PersistedConfigSchema: z.ZodObject<{
108
134
  allowedHosts?: true | string[] | undefined;
109
135
  autoArchiveAfterMerge?: boolean | undefined;
110
136
  appendSystemPrompt?: string | undefined;
137
+ terminalProfiles?: z.objectInputType<{
138
+ id: z.ZodString;
139
+ name: z.ZodString;
140
+ command: z.ZodString;
141
+ args: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
142
+ icon: z.ZodOptional<z.ZodString>;
143
+ }, z.ZodTypeAny, "passthrough">[] | undefined;
111
144
  cors?: {
112
145
  allowedOrigins?: string[] | undefined;
113
146
  } | undefined;
@@ -135,6 +168,13 @@ export declare const PersistedConfigSchema: z.ZodObject<{
135
168
  hostnames?: true | string[] | undefined;
136
169
  autoArchiveAfterMerge?: boolean | undefined;
137
170
  appendSystemPrompt?: string | undefined;
171
+ terminalProfiles?: z.objectOutputType<{
172
+ id: z.ZodString;
173
+ name: z.ZodString;
174
+ command: z.ZodString;
175
+ args: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
176
+ icon: z.ZodOptional<z.ZodString>;
177
+ }, z.ZodTypeAny, "passthrough">[] | undefined;
138
178
  cors?: {
139
179
  allowedOrigins?: string[] | undefined;
140
180
  } | undefined;
@@ -163,6 +203,13 @@ export declare const PersistedConfigSchema: z.ZodObject<{
163
203
  allowedHosts?: true | string[] | undefined;
164
204
  autoArchiveAfterMerge?: boolean | undefined;
165
205
  appendSystemPrompt?: string | undefined;
206
+ terminalProfiles?: z.objectInputType<{
207
+ id: z.ZodString;
208
+ name: z.ZodString;
209
+ command: z.ZodString;
210
+ args: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
211
+ icon: z.ZodOptional<z.ZodString>;
212
+ }, z.ZodTypeAny, "passthrough">[] | undefined;
166
213
  cors?: {
167
214
  allowedOrigins?: string[] | undefined;
168
215
  } | undefined;
@@ -853,6 +900,13 @@ export declare const PersistedConfigSchema: z.ZodObject<{
853
900
  hostnames?: true | string[] | undefined;
854
901
  autoArchiveAfterMerge?: boolean | undefined;
855
902
  appendSystemPrompt?: string | undefined;
903
+ terminalProfiles?: z.objectOutputType<{
904
+ id: z.ZodString;
905
+ name: z.ZodString;
906
+ command: z.ZodString;
907
+ args: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
908
+ icon: z.ZodOptional<z.ZodString>;
909
+ }, z.ZodTypeAny, "passthrough">[] | undefined;
856
910
  cors?: {
857
911
  allowedOrigins?: string[] | undefined;
858
912
  } | undefined;
@@ -992,6 +1046,13 @@ export declare const PersistedConfigSchema: z.ZodObject<{
992
1046
  allowedHosts?: true | string[] | undefined;
993
1047
  autoArchiveAfterMerge?: boolean | undefined;
994
1048
  appendSystemPrompt?: string | undefined;
1049
+ terminalProfiles?: z.objectInputType<{
1050
+ id: z.ZodString;
1051
+ name: z.ZodString;
1052
+ command: z.ZodString;
1053
+ args: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
1054
+ icon: z.ZodOptional<z.ZodString>;
1055
+ }, z.ZodTypeAny, "passthrough">[] | undefined;
995
1056
  cors?: {
996
1057
  allowedOrigins?: string[] | undefined;
997
1058
  } | undefined;
@@ -3,6 +3,7 @@ import path from "node:path";
3
3
  import { z } from "zod";
4
4
  import { AgentProviderRuntimeSettingsMapSchema, migrateProviderSettings, ProviderOverridesSchema, } from "./agent/provider-launch-config.js";
5
5
  import { ensurePrivateFile, writePrivateFileAtomicSync } from "./private-files.js";
6
+ import { TerminalProfileSchema } from "@getpaseo/protocol/messages";
6
7
  export const LogLevelSchema = z.enum(["trace", "debug", "info", "warn", "error", "fatal"]);
7
8
  export const LogFormatSchema = z.enum(["pretty", "json"]);
8
9
  const LogConfigSchema = z
@@ -187,6 +188,7 @@ export const PersistedConfigSchema = z
187
188
  .optional(),
188
189
  autoArchiveAfterMerge: z.boolean().optional(),
189
190
  appendSystemPrompt: z.string().optional(),
191
+ terminalProfiles: z.array(TerminalProfileSchema).optional(),
190
192
  cors: z
191
193
  .object({
192
194
  allowedOrigins: z.array(z.string()).optional(),
@@ -34,6 +34,9 @@ export interface SessionRuntimeMetrics {
34
34
  inflightRequests: number;
35
35
  peakInflightRequests: number;
36
36
  }
37
+ export interface SessionFileSystem {
38
+ isDirectory(path: string): Promise<boolean>;
39
+ }
37
40
  type AgentMcpTransportFactory = () => Promise<unknown>;
38
41
  export interface SessionOptions {
39
42
  clientId: string;
@@ -51,6 +54,7 @@ export interface SessionOptions {
51
54
  agentStorage: AgentStorage;
52
55
  projectRegistry: ProjectRegistry;
53
56
  workspaceRegistry: WorkspaceRegistry;
57
+ filesystem?: SessionFileSystem;
54
58
  chatService: FileBackedChatService;
55
59
  scheduleService: ScheduleService;
56
60
  loopService: LoopService;
@@ -149,6 +153,7 @@ export declare class Session {
149
153
  private readonly agentStorage;
150
154
  private readonly projectRegistry;
151
155
  private readonly workspaceRegistry;
156
+ private readonly filesystem;
152
157
  private readonly chatService;
153
158
  private readonly scheduleService;
154
159
  private readonly loopService;
@@ -415,6 +420,7 @@ export declare class Session {
415
420
  private resolveCurrentPullRequest;
416
421
  private handleCheckoutPrStatusRequest;
417
422
  private handlePullRequestTimelineRequest;
423
+ private handleCheckoutGithubGetCheckDetailsRequest;
418
424
  private handlePaseoWorktreeListRequest;
419
425
  private handlePaseoWorktreeArchiveRequest;
420
426
  /**
@@ -1,6 +1,7 @@
1
1
  import equal from "fast-deep-equal";
2
2
  import { v4 as uuidv4 } from "uuid";
3
3
  import { realpathSync } from "node:fs";
4
+ import { stat } from "node:fs/promises";
4
5
  import { basename, resolve, sep } from "path";
5
6
  import { homedir } from "node:os";
6
7
  import { z } from "zod";
@@ -215,6 +216,12 @@ const PCM_BYTES_PER_MS = (PCM_SAMPLE_RATE * PCM_CHANNELS * (PCM_BITS_PER_SAMPLE
215
216
  const MIN_STREAMING_SEGMENT_DURATION_MS = 1000;
216
217
  const MIN_STREAMING_SEGMENT_BYTES = Math.round(PCM_BYTES_PER_MS * MIN_STREAMING_SEGMENT_DURATION_MS);
217
218
  const AgentIdSchema = z.string().uuid();
219
+ const nodeSessionFileSystem = {
220
+ async isDirectory(path) {
221
+ const stats = await stat(path).catch(() => null);
222
+ return stats?.isDirectory() ?? false;
223
+ },
224
+ };
218
225
  class VoiceFeatureUnavailableError extends Error {
219
226
  constructor(context) {
220
227
  super(context.message);
@@ -258,6 +265,12 @@ function parseClientCapabilities(capabilities) {
258
265
  }
259
266
  return new Set(result);
260
267
  }
268
+ function describeRegistryTransition(record) {
269
+ if (!record) {
270
+ return "created";
271
+ }
272
+ return record.archivedAt ? "unarchived" : "existing";
273
+ }
261
274
  /**
262
275
  * Session represents a single connected client session.
263
276
  * It owns all state management, orchestration logic, and message processing.
@@ -318,7 +331,7 @@ export class Session {
318
331
  }
319
332
  },
320
333
  });
321
- const { clientId, appVersion, clientCapabilities, onMessage, onBinaryMessage, onLifecycleIntent, logger, downloadTokenStore, pushTokenStore, paseoHome, worktreesRoot, agentManager, agentStorage, projectRegistry, workspaceRegistry, chatService, scheduleService, loopService, checkoutDiffManager, github, workspaceGitService, daemonConfigStore, mcpBaseUrl, stt, sttLanguage, tts, terminalManager, providerSnapshotManager, serviceProxy, scriptRuntimeStore, workspaceSetupSnapshots, onBranchChanged, getDaemonTcpPort, getDaemonTcpHost, serviceProxyPublicBaseUrl, resolveScriptHealth, voice, voiceBridge, dictation, serverId, daemonVersion, daemonRuntimeConfig, } = options;
334
+ const { clientId, appVersion, clientCapabilities, onMessage, onBinaryMessage, onLifecycleIntent, logger, downloadTokenStore, pushTokenStore, paseoHome, worktreesRoot, agentManager, agentStorage, projectRegistry, workspaceRegistry, filesystem, chatService, scheduleService, loopService, checkoutDiffManager, github, workspaceGitService, daemonConfigStore, mcpBaseUrl, stt, sttLanguage, tts, terminalManager, providerSnapshotManager, serviceProxy, scriptRuntimeStore, workspaceSetupSnapshots, onBranchChanged, getDaemonTcpPort, getDaemonTcpHost, serviceProxyPublicBaseUrl, resolveScriptHealth, voice, voiceBridge, dictation, serverId, daemonVersion, daemonRuntimeConfig, } = options;
322
335
  this.clientId = clientId;
323
336
  this.appVersion = appVersion ?? null;
324
337
  this.clientCapabilities = parseClientCapabilities(clientCapabilities);
@@ -340,6 +353,7 @@ export class Session {
340
353
  this.agentStorage = agentStorage;
341
354
  this.projectRegistry = projectRegistry;
342
355
  this.workspaceRegistry = workspaceRegistry;
356
+ this.filesystem = filesystem ?? nodeSessionFileSystem;
343
357
  this.chatService = chatService;
344
358
  this.scheduleService = scheduleService;
345
359
  this.loopService = loopService;
@@ -356,6 +370,12 @@ export class Session {
356
370
  hasBinaryChannel: () => this.onBinaryMessage !== null,
357
371
  isPathWithinRoot: (rootPath, candidatePath) => this.isPathWithinRoot(rootPath, candidatePath),
358
372
  sessionLogger: this.sessionLogger,
373
+ listTerminalWorkspaceRoots: async () => {
374
+ const workspaces = await this.workspaceRegistry.list();
375
+ return workspaces
376
+ .filter((workspace) => !workspace.archivedAt)
377
+ .map((workspace) => workspace.cwd);
378
+ },
359
379
  clientSupportsWrapReflow: () => this.clientCapabilities.has(CLIENT_CAPS.terminalReflowableSnapshot),
360
380
  });
361
381
  this.createAgentLifecycleDispatch = new CreateAgentLifecycleDispatch({
@@ -586,7 +606,10 @@ export class Session {
586
606
  this.sessionLogger.info("Skipping Agent MCP initialization because no MCP base URL is configured");
587
607
  return;
588
608
  }
589
- const transport = new StreamableHTTPClientTransport(new URL(this.mcpBaseUrl));
609
+ const authToken = this.agentManager.getMcpAuthToken();
610
+ const transport = new StreamableHTTPClientTransport(new URL(this.mcpBaseUrl), authToken
611
+ ? { requestInit: { headers: { Authorization: `Bearer ${authToken}` } } }
612
+ : undefined);
590
613
  this.agentMcpClient = await experimental_createMCPClient({
591
614
  transport,
592
615
  });
@@ -1279,6 +1302,8 @@ export class Session {
1279
1302
  return this.handleCheckoutPrMergeRequest(msg);
1280
1303
  case "checkout.github.set_auto_merge.request":
1281
1304
  return this.handleCheckoutGithubSetAutoMergeRequest(msg);
1305
+ case "checkout.github.get_check_details.request":
1306
+ return this.handleCheckoutGithubGetCheckDetailsRequest(msg);
1282
1307
  case "checkout_pr_status_request":
1283
1308
  return this.handleCheckoutPrStatusRequest(msg);
1284
1309
  case "pull_request_timeline_request":
@@ -4276,6 +4301,43 @@ export class Session {
4276
4301
  });
4277
4302
  }
4278
4303
  }
4304
+ async handleCheckoutGithubGetCheckDetailsRequest(msg) {
4305
+ const { cwd, repoOwner, repoName, checkRunId, workflowRunId, requestId } = msg;
4306
+ try {
4307
+ const details = await this.github.getGitHubCheckDetails({
4308
+ cwd,
4309
+ repoOwner,
4310
+ repoName,
4311
+ checkRunId,
4312
+ workflowRunId,
4313
+ });
4314
+ this.emit({
4315
+ type: "checkout.github.get_check_details.response",
4316
+ payload: {
4317
+ cwd,
4318
+ success: true,
4319
+ details,
4320
+ error: null,
4321
+ requestId,
4322
+ },
4323
+ });
4324
+ }
4325
+ catch (error) {
4326
+ this.emit({
4327
+ type: "checkout.github.get_check_details.response",
4328
+ payload: {
4329
+ cwd,
4330
+ success: false,
4331
+ details: null,
4332
+ error: {
4333
+ code: "UNKNOWN",
4334
+ message: error instanceof Error ? error.message : String(error),
4335
+ },
4336
+ requestId,
4337
+ },
4338
+ });
4339
+ }
4340
+ }
4279
4341
  async handlePaseoWorktreeListRequest(msg) {
4280
4342
  return handleWorktreeListRequest({
4281
4343
  emit: (message) => this.emit(message),
@@ -5061,9 +5123,10 @@ export class Session {
5061
5123
  return result;
5062
5124
  }
5063
5125
  async archiveWorkspaceRecord(workspaceId, archivedAt) {
5126
+ const archiveTimestamp = archivedAt ?? new Date().toISOString();
5064
5127
  const existingWorkspace = await archivePersistedWorkspaceRecord({
5065
5128
  workspaceId,
5066
- archivedAt,
5129
+ archivedAt: archiveTimestamp,
5067
5130
  workspaceRegistry: this.workspaceRegistry,
5068
5131
  projectRegistry: this.projectRegistry,
5069
5132
  });
@@ -5071,6 +5134,16 @@ export class Session {
5071
5134
  this.removeWorkspaceGitSubscription(workspaceId);
5072
5135
  return;
5073
5136
  }
5137
+ if (!existingWorkspace.archivedAt) {
5138
+ const activeSiblings = (await this.workspaceRegistry.list()).filter((workspace) => workspace.projectId === existingWorkspace.projectId && !workspace.archivedAt);
5139
+ this.sessionLogger.info({
5140
+ workspaceId,
5141
+ workspaceCwd: existingWorkspace.cwd,
5142
+ projectId: existingWorkspace.projectId,
5143
+ projectArchived: activeSiblings.length === 0,
5144
+ archivedAt: archiveTimestamp,
5145
+ }, "Workspace archived");
5146
+ }
5074
5147
  await this.removeWorkspaceGitWatchTarget(existingWorkspace.cwd);
5075
5148
  this.scriptRuntimeStore?.removeForWorkspace(existingWorkspace.cwd);
5076
5149
  this.removeWorkspaceGitSubscription(workspaceId);
@@ -5389,11 +5462,47 @@ export class Session {
5389
5462
  }
5390
5463
  }
5391
5464
  async handleOpenProjectRequest(request) {
5465
+ const requestedCwd = request.cwd;
5466
+ const cwd = expandTilde(requestedCwd);
5467
+ const directoryExists = await this.filesystem.isDirectory(cwd).catch(() => false);
5468
+ if (!directoryExists) {
5469
+ this.sessionLogger.info({ requestedCwd, resolvedCwd: cwd, reason: "directory_not_found" }, "Open project rejected");
5470
+ this.emit({
5471
+ type: "open_project_response",
5472
+ payload: {
5473
+ requestId: request.requestId,
5474
+ workspace: null,
5475
+ error: `Directory not found: ${cwd}`,
5476
+ errorCode: "directory_not_found",
5477
+ },
5478
+ });
5479
+ return;
5480
+ }
5392
5481
  try {
5393
- const workspace = await this.findOrCreateWorkspaceForDirectory(request.cwd);
5482
+ const projectsBefore = new Map();
5483
+ for (const project of await this.projectRegistry.list()) {
5484
+ projectsBefore.set(project.projectId, project);
5485
+ }
5486
+ const workspacesBefore = new Map();
5487
+ for (const workspaceRecord of await this.workspaceRegistry.list()) {
5488
+ workspacesBefore.set(workspaceRecord.workspaceId, workspaceRecord);
5489
+ }
5490
+ const workspace = await this.findOrCreateWorkspaceForDirectory(cwd);
5491
+ const project = await this.projectRegistry.get(workspace.projectId);
5394
5492
  await this.syncWorkspaceGitObserverForWorkspace(workspace);
5395
5493
  const descriptor = await this.describeWorkspaceRecord(workspace);
5396
5494
  await this.emitWorkspaceUpdateForCwd(workspace.cwd);
5495
+ this.sessionLogger.info({
5496
+ requestedCwd,
5497
+ resolvedCwd: cwd,
5498
+ workspaceCwd: workspace.cwd,
5499
+ workspaceId: workspace.workspaceId,
5500
+ workspaceKind: workspace.kind,
5501
+ workspaceTransition: describeRegistryTransition(workspacesBefore.get(workspace.workspaceId) ?? null),
5502
+ projectId: workspace.projectId,
5503
+ projectKind: project?.kind ?? null,
5504
+ projectTransition: describeRegistryTransition(projectsBefore.get(workspace.projectId) ?? null),
5505
+ }, "Project opened");
5397
5506
  this.emit({
5398
5507
  type: "open_project_response",
5399
5508
  payload: {
@@ -5414,7 +5523,7 @@ export class Session {
5414
5523
  }
5415
5524
  catch (error) {
5416
5525
  const message = error instanceof Error ? error.message : "Failed to open project";
5417
- this.sessionLogger.error({ err: error, cwd: request.cwd }, "Failed to open project");
5526
+ this.sessionLogger.error({ err: error, cwd }, "Failed to open project");
5418
5527
  this.emit({
5419
5528
  type: "open_project_response",
5420
5529
  payload: {
@@ -7135,7 +7244,6 @@ function isValidGitHubRepoSegment(value) {
7135
7244
  return /^[A-Za-z0-9._-]+$/.test(value);
7136
7245
  }
7137
7246
  function toPullRequestTimelinePayloadItem(item) {
7138
- const { authorUrl: _authorUrl, ...payload } = item;
7139
- return payload;
7247
+ return item;
7140
7248
  }
7141
7249
  //# sourceMappingURL=session.js.map
@@ -85,6 +85,7 @@ function createFallbackWorkspaceGitService() {
85
85
  unsubscribe: () => { },
86
86
  }),
87
87
  scheduleRefreshForCwd: () => { },
88
+ onWorkspaceStateMayHaveChanged: () => { },
88
89
  dispose: () => { },
89
90
  };
90
91
  }
@@ -719,6 +720,7 @@ export class VoiceAssistantWebSocketServer {
719
720
  providersSnapshot: true,
720
721
  // COMPAT(checkoutGithubSetAutoMerge): added in v0.1.75, remove gate after 2026-11-13.
721
722
  checkoutGithubSetAutoMerge: true,
723
+ githubCheckDetails: true,
722
724
  // COMPAT(daemonStatusRpc): added in v0.1.76, remove gate after 2026-11-18.
723
725
  daemonStatusRpc: true,
724
726
  // COMPAT(terminalRestoreModes): added in v0.1.81, remove gate after 2026-11-23.
@@ -89,6 +89,7 @@ export interface WorkspaceGitService {
89
89
  unsubscribe: () => void;
90
90
  }>;
91
91
  scheduleRefreshForCwd(cwd: string): void;
92
+ onWorkspaceStateMayHaveChanged(cwd: string): void;
92
93
  dispose(): void;
93
94
  }
94
95
  export type WorkspaceGitListener = (snapshot: WorkspaceGitRuntimeSnapshot) => void;
@@ -201,6 +202,7 @@ export declare class WorkspaceGitServiceImpl implements WorkspaceGitService {
201
202
  unsubscribe: () => void;
202
203
  }>;
203
204
  scheduleRefreshForCwd(cwd: string): void;
205
+ onWorkspaceStateMayHaveChanged(cwd: string): void;
204
206
  dispose(): void;
205
207
  private ensureWorkspaceTarget;
206
208
  private readAuxiliaryCache;
@@ -233,7 +235,8 @@ export declare class WorkspaceGitServiceImpl implements WorkspaceGitService {
233
235
  private normalizeRefreshRequest;
234
236
  private resolveGitHubRemoteForTarget;
235
237
  private shouldThrottleNonForcedRefresh;
236
- private mergeQueuedRefresh;
238
+ private buildScheduledRefreshRequest;
239
+ private mergeRefreshRequests;
237
240
  private runWorkspaceRefreshLoop;
238
241
  private refreshSnapshot;
239
242
  private refreshGitSnapshot;