@getpaseo/server 0.1.92 → 0.1.94
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.
- package/dist/server/server/agent/agent-manager.d.ts +14 -0
- package/dist/server/server/agent/agent-manager.js +36 -0
- package/dist/server/server/agent/agent-sdk-types.d.ts +1 -0
- package/dist/server/server/agent/providers/claude/agent.d.ts +1 -0
- package/dist/server/server/agent/providers/claude/agent.js +58 -3
- package/dist/server/server/agent/providers/claude/models.js +15 -0
- package/dist/server/server/agent/providers/claude/task-notification-tool-call.d.ts +2 -2
- package/dist/server/server/agent/providers/mock-load-test-agent.js +55 -28
- package/dist/server/server/agent/providers/pi/agent.js +3 -1
- package/dist/server/server/agent/providers/pi/session-descriptor.d.ts +5 -0
- package/dist/server/server/agent/providers/pi/session-descriptor.js +74 -12
- package/dist/server/server/agent/runtime-mcp-config.d.ts +6 -0
- package/dist/server/server/agent/runtime-mcp-config.js +3 -0
- package/dist/server/server/auth.d.ts +13 -0
- package/dist/server/server/auth.js +35 -0
- package/dist/server/server/bootstrap.d.ts +2 -0
- package/dist/server/server/bootstrap.js +28 -1
- package/dist/server/server/config.js +3 -1
- package/dist/server/server/daemon-config-store.js +3 -0
- package/dist/server/server/loop-service.d.ts +6 -6
- package/dist/server/server/persisted-config.d.ts +61 -0
- package/dist/server/server/persisted-config.js +2 -0
- package/dist/server/server/session.d.ts +1 -0
- package/dist/server/server/session.js +50 -3
- package/dist/server/server/websocket-server.js +2 -0
- package/dist/server/server/workspace-git-service.d.ts +4 -1
- package/dist/server/server/workspace-git-service.js +40 -22
- package/dist/server/services/github-service.d.ts +57 -0
- package/dist/server/services/github-service.js +327 -3
- package/dist/server/terminal/terminal-session-controller.d.ts +6 -0
- package/dist/server/terminal/terminal-session-controller.js +36 -2
- package/dist/src/server/persisted-config.js +2 -0
- package/package.json +5 -5
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { compare, compareSync, hashSync } from "bcryptjs";
|
|
2
|
+
import { timingSafeEqual } from "node:crypto";
|
|
2
3
|
export const DAEMON_PASSWORD_BCRYPT_COST = 12;
|
|
3
4
|
export function isBearerTokenValid(input) {
|
|
4
5
|
return isBearerTokenValidSync(input);
|
|
@@ -98,6 +99,16 @@ const BEARER_AUTH_BYPASS_PATHS = new Set([
|
|
|
98
99
|
// rejects requests without a valid token (400/403), so dropping the bearer
|
|
99
100
|
// here does not make the route unauthenticated.
|
|
100
101
|
"/api/files/download",
|
|
102
|
+
// The daemon injects its own agents' Paseo MCP connections at this endpoint
|
|
103
|
+
// (and connects its own per-client MCP client here). Those connections cannot
|
|
104
|
+
// carry the daemon password — it is only known in plaintext when set via env,
|
|
105
|
+
// never when set via the app — so the route authenticates them with a
|
|
106
|
+
// per-daemon-run capability token instead (see isAgentMcpRequestAuthorized).
|
|
107
|
+
// The token is injected only into local agent configs/sessions and never sent
|
|
108
|
+
// to remote clients, and the route still rejects callers presenting neither
|
|
109
|
+
// the token nor a valid daemon password, so dropping the global bearer here
|
|
110
|
+
// does not make the endpoint unauthenticated.
|
|
111
|
+
"/mcp/agents",
|
|
101
112
|
]);
|
|
102
113
|
export function shouldBypassBearerAuth(method, path) {
|
|
103
114
|
if (method === "OPTIONS") {
|
|
@@ -105,4 +116,28 @@ export function shouldBypassBearerAuth(method, path) {
|
|
|
105
116
|
}
|
|
106
117
|
return BEARER_AUTH_BYPASS_PATHS.has(path);
|
|
107
118
|
}
|
|
119
|
+
/**
|
|
120
|
+
* Authorizes a request to the Agent MCP endpoint (/mcp/agents), which is exempt
|
|
121
|
+
* from the global daemon-password middleware. Accepts either the per-daemon-run
|
|
122
|
+
* capability token the daemon injects into its own agents' configs and MCP
|
|
123
|
+
* client, or a valid daemon-password bearer (so existing password-authenticated
|
|
124
|
+
* callers keep working). When no daemon password is configured the endpoint is
|
|
125
|
+
* open, matching the global middleware's behavior.
|
|
126
|
+
*/
|
|
127
|
+
export async function isAgentMcpRequestAuthorized(input) {
|
|
128
|
+
if (!input.password) {
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
const token = extractHttpBearerToken(input.authorizationHeader);
|
|
132
|
+
if (input.capabilityToken !== null && token !== null) {
|
|
133
|
+
// Constant-time compare; length-guard first because timingSafeEqual throws
|
|
134
|
+
// on differing buffer lengths.
|
|
135
|
+
const provided = Buffer.from(token);
|
|
136
|
+
const expected = Buffer.from(input.capabilityToken);
|
|
137
|
+
if (provided.length === expected.length && timingSafeEqual(provided, expected)) {
|
|
138
|
+
return true;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return isBearerTokenValidAsync({ password: input.password, token });
|
|
142
|
+
}
|
|
108
143
|
//# sourceMappingURL=auth.js.map
|
|
@@ -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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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(),
|
|
@@ -415,6 +415,7 @@ export declare class Session {
|
|
|
415
415
|
private resolveCurrentPullRequest;
|
|
416
416
|
private handleCheckoutPrStatusRequest;
|
|
417
417
|
private handlePullRequestTimelineRequest;
|
|
418
|
+
private handleCheckoutGithubGetCheckDetailsRequest;
|
|
418
419
|
private handlePaseoWorktreeListRequest;
|
|
419
420
|
private handlePaseoWorktreeArchiveRequest;
|
|
420
421
|
/**
|
|
@@ -356,6 +356,12 @@ export class Session {
|
|
|
356
356
|
hasBinaryChannel: () => this.onBinaryMessage !== null,
|
|
357
357
|
isPathWithinRoot: (rootPath, candidatePath) => this.isPathWithinRoot(rootPath, candidatePath),
|
|
358
358
|
sessionLogger: this.sessionLogger,
|
|
359
|
+
listTerminalWorkspaceRoots: async () => {
|
|
360
|
+
const workspaces = await this.workspaceRegistry.list();
|
|
361
|
+
return workspaces
|
|
362
|
+
.filter((workspace) => !workspace.archivedAt)
|
|
363
|
+
.map((workspace) => workspace.cwd);
|
|
364
|
+
},
|
|
359
365
|
clientSupportsWrapReflow: () => this.clientCapabilities.has(CLIENT_CAPS.terminalReflowableSnapshot),
|
|
360
366
|
});
|
|
361
367
|
this.createAgentLifecycleDispatch = new CreateAgentLifecycleDispatch({
|
|
@@ -586,7 +592,10 @@ export class Session {
|
|
|
586
592
|
this.sessionLogger.info("Skipping Agent MCP initialization because no MCP base URL is configured");
|
|
587
593
|
return;
|
|
588
594
|
}
|
|
589
|
-
const
|
|
595
|
+
const authToken = this.agentManager.getMcpAuthToken();
|
|
596
|
+
const transport = new StreamableHTTPClientTransport(new URL(this.mcpBaseUrl), authToken
|
|
597
|
+
? { requestInit: { headers: { Authorization: `Bearer ${authToken}` } } }
|
|
598
|
+
: undefined);
|
|
590
599
|
this.agentMcpClient = await experimental_createMCPClient({
|
|
591
600
|
transport,
|
|
592
601
|
});
|
|
@@ -1279,6 +1288,8 @@ export class Session {
|
|
|
1279
1288
|
return this.handleCheckoutPrMergeRequest(msg);
|
|
1280
1289
|
case "checkout.github.set_auto_merge.request":
|
|
1281
1290
|
return this.handleCheckoutGithubSetAutoMergeRequest(msg);
|
|
1291
|
+
case "checkout.github.get_check_details.request":
|
|
1292
|
+
return this.handleCheckoutGithubGetCheckDetailsRequest(msg);
|
|
1282
1293
|
case "checkout_pr_status_request":
|
|
1283
1294
|
return this.handleCheckoutPrStatusRequest(msg);
|
|
1284
1295
|
case "pull_request_timeline_request":
|
|
@@ -4276,6 +4287,43 @@ export class Session {
|
|
|
4276
4287
|
});
|
|
4277
4288
|
}
|
|
4278
4289
|
}
|
|
4290
|
+
async handleCheckoutGithubGetCheckDetailsRequest(msg) {
|
|
4291
|
+
const { cwd, repoOwner, repoName, checkRunId, workflowRunId, requestId } = msg;
|
|
4292
|
+
try {
|
|
4293
|
+
const details = await this.github.getGitHubCheckDetails({
|
|
4294
|
+
cwd,
|
|
4295
|
+
repoOwner,
|
|
4296
|
+
repoName,
|
|
4297
|
+
checkRunId,
|
|
4298
|
+
workflowRunId,
|
|
4299
|
+
});
|
|
4300
|
+
this.emit({
|
|
4301
|
+
type: "checkout.github.get_check_details.response",
|
|
4302
|
+
payload: {
|
|
4303
|
+
cwd,
|
|
4304
|
+
success: true,
|
|
4305
|
+
details,
|
|
4306
|
+
error: null,
|
|
4307
|
+
requestId,
|
|
4308
|
+
},
|
|
4309
|
+
});
|
|
4310
|
+
}
|
|
4311
|
+
catch (error) {
|
|
4312
|
+
this.emit({
|
|
4313
|
+
type: "checkout.github.get_check_details.response",
|
|
4314
|
+
payload: {
|
|
4315
|
+
cwd,
|
|
4316
|
+
success: false,
|
|
4317
|
+
details: null,
|
|
4318
|
+
error: {
|
|
4319
|
+
code: "UNKNOWN",
|
|
4320
|
+
message: error instanceof Error ? error.message : String(error),
|
|
4321
|
+
},
|
|
4322
|
+
requestId,
|
|
4323
|
+
},
|
|
4324
|
+
});
|
|
4325
|
+
}
|
|
4326
|
+
}
|
|
4279
4327
|
async handlePaseoWorktreeListRequest(msg) {
|
|
4280
4328
|
return handleWorktreeListRequest({
|
|
4281
4329
|
emit: (message) => this.emit(message),
|
|
@@ -7135,7 +7183,6 @@ function isValidGitHubRepoSegment(value) {
|
|
|
7135
7183
|
return /^[A-Za-z0-9._-]+$/.test(value);
|
|
7136
7184
|
}
|
|
7137
7185
|
function toPullRequestTimelinePayloadItem(item) {
|
|
7138
|
-
|
|
7139
|
-
return payload;
|
|
7186
|
+
return item;
|
|
7140
7187
|
}
|
|
7141
7188
|
//# 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
|
|
238
|
+
private buildScheduledRefreshRequest;
|
|
239
|
+
private mergeRefreshRequests;
|
|
237
240
|
private runWorkspaceRefreshLoop;
|
|
238
241
|
private refreshSnapshot;
|
|
239
242
|
private refreshGitSnapshot;
|
|
@@ -12,7 +12,7 @@ import { listPaseoWorktrees } from "../utils/worktree.js";
|
|
|
12
12
|
import { READ_ONLY_GIT_ENV } from "./checkout-git-utils.js";
|
|
13
13
|
import { buildWorkspaceGitMetadataFromSnapshot, } from "./workspace-git-metadata.js";
|
|
14
14
|
import { checkoutLiteFromGitSnapshot, normalizeWorkspaceId } from "./workspace-registry-model.js";
|
|
15
|
-
const WORKSPACE_GIT_WATCH_DEBOUNCE_MS =
|
|
15
|
+
const WORKSPACE_GIT_WATCH_DEBOUNCE_MS = 1000;
|
|
16
16
|
const BACKGROUND_GIT_FETCH_INTERVAL_MS = 180000;
|
|
17
17
|
export const WORKSPACE_GIT_SELF_HEAL_INTERVAL_MS = 60000;
|
|
18
18
|
const WORKING_TREE_WATCH_FALLBACK_REFRESH_MS = 5000;
|
|
@@ -295,6 +295,19 @@ export class WorkspaceGitServiceImpl {
|
|
|
295
295
|
this.scheduleWorkspaceRefresh(target);
|
|
296
296
|
}
|
|
297
297
|
}
|
|
298
|
+
onWorkspaceStateMayHaveChanged(cwd) {
|
|
299
|
+
const normalizedCwd = normalizeWorkspaceId(cwd);
|
|
300
|
+
const target = this.workspaceTargets.get(normalizedCwd);
|
|
301
|
+
if (!target || target.closed) {
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
this.deps.github.invalidate({ cwd: normalizedCwd });
|
|
305
|
+
this.scheduleWorkspaceRefresh(target, {
|
|
306
|
+
force: true,
|
|
307
|
+
includeGitHub: true,
|
|
308
|
+
reason: "external-state-change",
|
|
309
|
+
});
|
|
310
|
+
}
|
|
298
311
|
dispose() {
|
|
299
312
|
for (const target of this.workspaceTargets.values()) {
|
|
300
313
|
this.closeWorkspaceTarget(target);
|
|
@@ -384,6 +397,7 @@ export class WorkspaceGitServiceImpl {
|
|
|
384
397
|
listeners: new Set(),
|
|
385
398
|
watchers: [],
|
|
386
399
|
debounceTimer: null,
|
|
400
|
+
pendingDebounceRequest: null,
|
|
387
401
|
selfHealTimer: null,
|
|
388
402
|
githubPollSubscription: null,
|
|
389
403
|
githubPollKey: null,
|
|
@@ -640,6 +654,8 @@ export class WorkspaceGitServiceImpl {
|
|
|
640
654
|
if (!target || target.closed || this.workspaceTargets.get(target.cwd) !== target) {
|
|
641
655
|
return;
|
|
642
656
|
}
|
|
657
|
+
const request = this.buildScheduledRefreshRequest(options);
|
|
658
|
+
target.pendingDebounceRequest = this.mergeRefreshRequests(target.pendingDebounceRequest, request);
|
|
643
659
|
if (target.debounceTimer) {
|
|
644
660
|
clearTimeout(target.debounceTimer);
|
|
645
661
|
}
|
|
@@ -648,12 +664,11 @@ export class WorkspaceGitServiceImpl {
|
|
|
648
664
|
return;
|
|
649
665
|
}
|
|
650
666
|
target.debounceTimer = null;
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
});
|
|
667
|
+
const merged = target.pendingDebounceRequest;
|
|
668
|
+
target.pendingDebounceRequest = null;
|
|
669
|
+
if (merged) {
|
|
670
|
+
void this.refreshWorkspaceTarget(target, merged);
|
|
671
|
+
}
|
|
657
672
|
}, WORKSPACE_GIT_WATCH_DEBOUNCE_MS);
|
|
658
673
|
}
|
|
659
674
|
startWorkspaceSubscriptionTimers(target) {
|
|
@@ -923,7 +938,7 @@ export class WorkspaceGitServiceImpl {
|
|
|
923
938
|
const needsForcedRefresh = request.force && !target.refreshState.force;
|
|
924
939
|
const needsGitHubRefresh = request.force && request.includeGitHub && !target.refreshState.includeGitHub;
|
|
925
940
|
if (needsForcedRefresh || needsGitHubRefresh) {
|
|
926
|
-
target.refreshState.queued = this.
|
|
941
|
+
target.refreshState.queued = this.mergeRefreshRequests(target.refreshState.queued, request);
|
|
927
942
|
}
|
|
928
943
|
return target.refreshState.promise;
|
|
929
944
|
}
|
|
@@ -975,23 +990,26 @@ export class WorkspaceGitServiceImpl {
|
|
|
975
990
|
}
|
|
976
991
|
return this.deps.now().getTime() - target.lastShellOutAtMs < WORKSPACE_GIT_INTERNAL_MIN_GAP_MS;
|
|
977
992
|
}
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
993
|
+
buildScheduledRefreshRequest(options) {
|
|
994
|
+
return {
|
|
995
|
+
force: options?.force === true,
|
|
996
|
+
includeGitHub: options?.includeGitHub ?? false,
|
|
997
|
+
reason: options?.reason ?? "watch",
|
|
998
|
+
notify: true,
|
|
999
|
+
};
|
|
1000
|
+
}
|
|
1001
|
+
mergeRefreshRequests(pending, request) {
|
|
1002
|
+
if (!pending) {
|
|
1003
|
+
return request;
|
|
986
1004
|
}
|
|
987
|
-
const force =
|
|
988
|
-
const upgradesForce = request.force && !
|
|
989
|
-
const upgradesGitHub = request.includeGitHub && !
|
|
1005
|
+
const force = pending.force || request.force;
|
|
1006
|
+
const upgradesForce = request.force && !pending.force;
|
|
1007
|
+
const upgradesGitHub = request.includeGitHub && !pending.includeGitHub;
|
|
990
1008
|
return {
|
|
991
1009
|
force,
|
|
992
|
-
includeGitHub:
|
|
993
|
-
reason: upgradesForce || upgradesGitHub ? request.reason :
|
|
994
|
-
notify:
|
|
1010
|
+
includeGitHub: pending.includeGitHub || request.includeGitHub,
|
|
1011
|
+
reason: upgradesForce || upgradesGitHub ? request.reason : pending.reason,
|
|
1012
|
+
notify: pending.notify || request.notify,
|
|
995
1013
|
};
|
|
996
1014
|
}
|
|
997
1015
|
async runWorkspaceRefreshLoop(target, initialRequest) {
|