@getpaseo/server 0.1.88 → 0.1.89
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.js +4 -1
- package/dist/server/server/agent/agent-storage.d.ts +22 -22
- package/dist/server/server/agent/create-agent/create.d.ts +2 -0
- package/dist/server/server/agent/create-agent/create.js +16 -5
- package/dist/server/server/agent/create-agent-lifecycle-dispatch.d.ts +1 -0
- package/dist/server/server/agent/create-agent-lifecycle-dispatch.js +4 -0
- package/dist/server/server/agent/mcp-server.d.ts +1 -0
- package/dist/server/server/agent/mcp-server.js +113 -70
- package/dist/server/server/agent/providers/pi/agent.js +13 -0
- package/dist/server/server/agent/providers/pi/rpc-types.d.ts +3 -0
- package/dist/server/server/auto-archive-on-merge/archive-if-safe.d.ts +1 -0
- package/dist/server/server/auto-archive-on-merge/archive-if-safe.js +6 -1
- package/dist/server/server/bootstrap.d.ts +7 -2
- package/dist/server/server/bootstrap.js +152 -115
- package/dist/server/server/config.js +41 -0
- package/dist/server/server/loop-service.d.ts +22 -22
- package/dist/server/server/package-version.d.ts +2 -2
- package/dist/server/server/paseo-worktree-archive-service.d.ts +2 -0
- package/dist/server/server/paseo-worktree-archive-service.js +28 -9
- package/dist/server/server/persisted-config.d.ts +84 -28
- package/dist/server/server/persisted-config.js +17 -0
- package/dist/server/server/pid-lock.d.ts +2 -2
- package/dist/server/server/script-health-monitor.d.ts +4 -4
- package/dist/server/server/script-health-monitor.js +6 -6
- package/dist/server/server/script-proxy.d.ts +2 -39
- package/dist/server/server/script-proxy.js +1 -244
- package/dist/server/server/script-route-branch-handler.d.ts +2 -2
- package/dist/server/server/script-route-branch-handler.js +3 -37
- package/dist/server/server/script-status-projection.d.ts +6 -4
- package/dist/server/server/script-status-projection.js +85 -37
- package/dist/server/server/service-proxy.d.ts +237 -0
- package/dist/server/server/service-proxy.js +714 -0
- package/dist/server/server/session.d.ts +7 -3
- package/dist/server/server/session.js +22 -10
- package/dist/server/server/websocket-server.d.ts +7 -4
- package/dist/server/server/websocket-server.js +9 -4
- package/dist/server/server/workspace-directory.js +4 -0
- package/dist/server/server/workspace-git-service.d.ts +3 -0
- package/dist/server/server/workspace-git-service.js +53 -12
- package/dist/server/server/workspace-registry.d.ts +2 -2
- package/dist/server/server/workspace-service-env.d.ts +1 -0
- package/dist/server/server/workspace-service-env.js +23 -18
- package/dist/server/server/worktree/commands.d.ts +2 -0
- package/dist/server/server/worktree/commands.js +4 -1
- package/dist/server/server/worktree-bootstrap.d.ts +4 -3
- package/dist/server/server/worktree-bootstrap.js +14 -13
- package/dist/server/server/worktree-core.d.ts +1 -0
- package/dist/server/server/worktree-core.js +2 -0
- package/dist/server/server/worktree-session.d.ts +6 -2
- package/dist/server/server/worktree-session.js +3 -0
- package/dist/server/services/github-service.d.ts +1 -0
- package/dist/server/services/github-service.js +7 -1
- package/dist/server/utils/checkout-git.d.ts +6 -2
- package/dist/server/utils/checkout-git.js +17 -7
- package/dist/server/utils/worktree.d.ts +17 -12
- package/dist/server/utils/worktree.js +39 -22
- package/dist/src/server/persisted-config.js +17 -0
- package/package.json +5 -5
- package/dist/server/utils/script-hostname.d.ts +0 -8
- package/dist/server/utils/script-hostname.js +0 -14
|
@@ -17,6 +17,7 @@ export async function createPaseoWorktreeCommand(dependencies, input) {
|
|
|
17
17
|
...input,
|
|
18
18
|
runSetup: false,
|
|
19
19
|
paseoHome: input.paseoHome ?? dependencies.paseoHome,
|
|
20
|
+
worktreesRoot: input.worktreesRoot ?? dependencies.worktreesRoot,
|
|
20
21
|
});
|
|
21
22
|
return { ok: true, createdWorktree };
|
|
22
23
|
}
|
|
@@ -32,6 +33,7 @@ export async function archivePaseoWorktreeCommand(dependencies, input) {
|
|
|
32
33
|
const resolvedTarget = await resolveArchiveTarget(dependencies, input);
|
|
33
34
|
const ownership = await isPaseoOwnedWorktreeCwd(resolvedTarget.targetPath, {
|
|
34
35
|
paseoHome: dependencies.paseoHome,
|
|
36
|
+
worktreesRoot: dependencies.worktreesRoot,
|
|
35
37
|
});
|
|
36
38
|
if (!ownership.allowed) {
|
|
37
39
|
return {
|
|
@@ -46,6 +48,7 @@ export async function archivePaseoWorktreeCommand(dependencies, input) {
|
|
|
46
48
|
targetPath: resolvedTarget.targetPath,
|
|
47
49
|
repoRoot,
|
|
48
50
|
worktreesRoot: ownership.worktreeRoot,
|
|
51
|
+
worktreesBaseRoot: dependencies.worktreesRoot,
|
|
49
52
|
requestId: input.requestId,
|
|
50
53
|
});
|
|
51
54
|
return {
|
|
@@ -78,7 +81,7 @@ async function resolveArchiveTarget(dependencies, input) {
|
|
|
78
81
|
throw new Error("worktreePath, worktreeSlug, or repoRoot+branchName is required");
|
|
79
82
|
}
|
|
80
83
|
async function resolveWorktreeSlugPath(dependencies, repoRoot, worktreeSlug) {
|
|
81
|
-
const worktreesRoot = await getPaseoWorktreesRoot(repoRoot, dependencies.paseoHome);
|
|
84
|
+
const worktreesRoot = await getPaseoWorktreesRoot(repoRoot, dependencies.paseoHome, dependencies.worktreesRoot);
|
|
82
85
|
return join(worktreesRoot, worktreeSlug);
|
|
83
86
|
}
|
|
84
87
|
//# sourceMappingURL=commands.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Logger } from "pino";
|
|
2
2
|
import type { TerminalManager } from "../terminal/terminal-manager.js";
|
|
3
3
|
import { runWorktreeSetupCommands, type WorktreeConfig, type WorktreeSetupCommandResult } from "../utils/worktree.js";
|
|
4
|
-
import { type
|
|
4
|
+
import { type ServiceProxySubsystem } from "./service-proxy.js";
|
|
5
5
|
import type { WorkspaceScriptRuntimeStore } from "./workspace-script-runtime-store.js";
|
|
6
6
|
import type { AgentTimelineItem, ToolCallDetail } from "./agent/agent-sdk-types.js";
|
|
7
7
|
export interface WorktreeBootstrapTerminalResult {
|
|
@@ -58,7 +58,8 @@ interface SpawnWorkspaceScriptOptions {
|
|
|
58
58
|
scriptName: string;
|
|
59
59
|
daemonPort?: number | null;
|
|
60
60
|
daemonListenHost?: string | null;
|
|
61
|
-
|
|
61
|
+
serviceProxyPublicBaseUrl?: string | null;
|
|
62
|
+
serviceProxy: ServiceProxySubsystem;
|
|
62
63
|
runtimeStore: WorkspaceScriptRuntimeStore;
|
|
63
64
|
terminalManager: TerminalManager;
|
|
64
65
|
logger?: Logger;
|
|
@@ -67,7 +68,7 @@ interface SpawnWorkspaceScriptOptions {
|
|
|
67
68
|
export declare function spawnWorkspaceScript(options: SpawnWorkspaceScriptOptions): Promise<WorktreeScriptResult>;
|
|
68
69
|
export declare function teardownWorktreeScripts(options: {
|
|
69
70
|
hostnames: string[];
|
|
70
|
-
|
|
71
|
+
serviceProxy: Pick<ServiceProxySubsystem, "removeServiceRoutesByHostnames">;
|
|
71
72
|
logger: Logger;
|
|
72
73
|
}): void;
|
|
73
74
|
export {};
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { v4 as uuidv4 } from "uuid";
|
|
2
|
-
import { buildScriptHostname } from "../utils/script-hostname.js";
|
|
3
2
|
import { getScriptConfigs, getWorktreeTerminalSpecs, isServiceScript, paseoConfigParseError, processCarriageReturns, readPaseoConfig, resolveWorktreeRuntimeEnv, runWorktreeSetupCommands, WorktreeSetupError, } from "../utils/worktree.js";
|
|
4
|
-
import { findFreePort } from "./
|
|
3
|
+
import { findFreePort } from "./service-proxy.js";
|
|
5
4
|
import { assertNoServiceEnvNameCollisions, buildWorkspaceServiceEnv, } from "./workspace-service-env.js";
|
|
6
5
|
import { ensureWorkspaceServicePortPlan, requirePlannedWorkspaceServicePort, refreshWorkspaceServicePort, } from "./workspace-service-port-registry.js";
|
|
7
6
|
const MAX_WORKTREE_SETUP_COMMAND_OUTPUT_BYTES = 64 * 1024;
|
|
@@ -501,8 +500,7 @@ export async function runAsyncWorktreeBootstrap(options) {
|
|
|
501
500
|
await runWorktreeTerminalBootstrap(options, runtimeEnv);
|
|
502
501
|
}
|
|
503
502
|
async function setupServiceScriptRoute(params) {
|
|
504
|
-
const { scriptConfigs, config, scriptName, projectSlug, branchName, workspaceId, daemonPort, daemonListenHost, existingRuntimeEntry,
|
|
505
|
-
const hostname = buildScriptHostname({ projectSlug, branchName, scriptName });
|
|
503
|
+
const { scriptConfigs, config, scriptName, projectSlug, branchName, workspaceId, daemonPort, daemonListenHost, serviceProxyPublicBaseUrl, existingRuntimeEntry, serviceProxy, } = params;
|
|
506
504
|
const serviceDeclarations = [];
|
|
507
505
|
for (const [configuredScriptName, scriptConfig] of scriptConfigs) {
|
|
508
506
|
if (isServiceScript(scriptConfig)) {
|
|
@@ -538,16 +536,18 @@ async function setupServiceScriptRoute(params) {
|
|
|
538
536
|
branchName,
|
|
539
537
|
daemonPort,
|
|
540
538
|
daemonListenHost,
|
|
539
|
+
serviceProxyPublicBaseUrl,
|
|
541
540
|
peers,
|
|
542
541
|
});
|
|
543
|
-
|
|
544
|
-
hostname,
|
|
542
|
+
const registeredRoute = serviceProxy.registerWorkspaceService({
|
|
545
543
|
port,
|
|
546
544
|
workspaceId,
|
|
547
545
|
projectSlug,
|
|
546
|
+
branchName,
|
|
548
547
|
scriptName,
|
|
548
|
+
publicBaseUrl: serviceProxyPublicBaseUrl ?? null,
|
|
549
549
|
});
|
|
550
|
-
return { hostname, port, env };
|
|
550
|
+
return { hostname: registeredRoute.hostname, port, env };
|
|
551
551
|
}
|
|
552
552
|
async function acquireWorkspaceScriptTerminal(params) {
|
|
553
553
|
const { serviceScript, existingRuntimeEntry, terminalManager, repoRoot, scriptName, env } = params;
|
|
@@ -565,7 +565,7 @@ async function acquireWorkspaceScriptTerminal(params) {
|
|
|
565
565
|
return { terminal, reusableTerminal };
|
|
566
566
|
}
|
|
567
567
|
export async function spawnWorkspaceScript(options) {
|
|
568
|
-
const { repoRoot, workspaceId, projectSlug, branchName, scriptName, daemonPort, daemonListenHost,
|
|
568
|
+
const { repoRoot, workspaceId, projectSlug, branchName, scriptName, daemonPort, daemonListenHost, serviceProxyPublicBaseUrl, serviceProxy, runtimeStore, terminalManager, logger, onLifecycleChanged, } = options;
|
|
569
569
|
const configResult = readPaseoConfig(repoRoot);
|
|
570
570
|
if (!configResult.ok) {
|
|
571
571
|
throw paseoConfigParseError(configResult);
|
|
@@ -598,8 +598,9 @@ export async function spawnWorkspaceScript(options) {
|
|
|
598
598
|
workspaceId,
|
|
599
599
|
daemonPort,
|
|
600
600
|
daemonListenHost,
|
|
601
|
+
serviceProxyPublicBaseUrl,
|
|
601
602
|
existingRuntimeEntry,
|
|
602
|
-
|
|
603
|
+
serviceProxy,
|
|
603
604
|
});
|
|
604
605
|
hostname = serviceSetup.hostname;
|
|
605
606
|
port = serviceSetup.port;
|
|
@@ -631,7 +632,7 @@ export async function spawnWorkspaceScript(options) {
|
|
|
631
632
|
disposeLifecycleListeners?.();
|
|
632
633
|
disposeLifecycleListeners = null;
|
|
633
634
|
if (input.removeRoute && hostname) {
|
|
634
|
-
|
|
635
|
+
serviceProxy.removeWorkspaceService({ workspaceId, scriptName });
|
|
635
636
|
}
|
|
636
637
|
runtimeStore.set({
|
|
637
638
|
workspaceId,
|
|
@@ -689,7 +690,7 @@ export async function spawnWorkspaceScript(options) {
|
|
|
689
690
|
catch (error) {
|
|
690
691
|
disposeLifecycleListeners?.();
|
|
691
692
|
if (routeRegistered && hostname) {
|
|
692
|
-
|
|
693
|
+
serviceProxy.removeServiceRoutesByHostnames([hostname]);
|
|
693
694
|
}
|
|
694
695
|
if (runtimeRegistered) {
|
|
695
696
|
runtimeStore.remove({ workspaceId, scriptName });
|
|
@@ -707,9 +708,9 @@ export async function spawnWorkspaceScript(options) {
|
|
|
707
708
|
}
|
|
708
709
|
}
|
|
709
710
|
export function teardownWorktreeScripts(options) {
|
|
710
|
-
const { hostnames,
|
|
711
|
+
const { hostnames, serviceProxy, logger } = options;
|
|
712
|
+
serviceProxy.removeServiceRoutesByHostnames(hostnames);
|
|
711
713
|
for (const hostname of hostnames) {
|
|
712
|
-
routeStore.removeRoute(hostname);
|
|
713
714
|
logger.info({ hostname }, "Removed script proxy route");
|
|
714
715
|
}
|
|
715
716
|
}
|
|
@@ -54,6 +54,7 @@ export async function createWorktreeCore(input, deps) {
|
|
|
54
54
|
slug: normalizedSlug,
|
|
55
55
|
repoRoot,
|
|
56
56
|
paseoHome: input.paseoHome,
|
|
57
|
+
worktreesRoot: input.worktreesRoot,
|
|
57
58
|
});
|
|
58
59
|
if (existingWorktree) {
|
|
59
60
|
return { worktree: existingWorktree, intent, repoRoot, created: false };
|
|
@@ -65,6 +66,7 @@ export async function createWorktreeCore(input, deps) {
|
|
|
65
66
|
source: intent,
|
|
66
67
|
runSetup: input.runSetup ?? true,
|
|
67
68
|
paseoHome: input.paseoHome,
|
|
69
|
+
worktreesRoot: input.worktreesRoot,
|
|
68
70
|
}),
|
|
69
71
|
intent,
|
|
70
72
|
repoRoot,
|
|
@@ -5,7 +5,7 @@ import type { PersistedWorkspaceRecord } from "./workspace-registry.js";
|
|
|
5
5
|
import type { WorkspaceGitService } from "./workspace-git-service.js";
|
|
6
6
|
import { runAsyncWorktreeBootstrap } from "./worktree-bootstrap.js";
|
|
7
7
|
import type { TerminalManager } from "../terminal/terminal-manager.js";
|
|
8
|
-
import type {
|
|
8
|
+
import type { ServiceProxySubsystem } from "./service-proxy.js";
|
|
9
9
|
import type { WorkspaceScriptRuntimeStore } from "./workspace-script-runtime-store.js";
|
|
10
10
|
import type { GitHubService } from "../services/github-service.js";
|
|
11
11
|
import type { CheckoutExistingBranchResult } from "../utils/checkout-git.js";
|
|
@@ -31,6 +31,7 @@ type AgentWorktreeSetupTimelineWriter = (input: {
|
|
|
31
31
|
}) => Promise<boolean>;
|
|
32
32
|
interface BuildAgentSessionConfigDependencies {
|
|
33
33
|
paseoHome?: string;
|
|
34
|
+
worktreesRoot?: string;
|
|
34
35
|
sessionLogger: Logger;
|
|
35
36
|
workspaceGitService?: WorkspaceGitService;
|
|
36
37
|
createPaseoWorktree: (input: CreatePaseoWorktreeInput, options?: {
|
|
@@ -47,6 +48,7 @@ interface BuildAgentSessionConfigDependencies {
|
|
|
47
48
|
}
|
|
48
49
|
interface CreatePaseoWorktreeInBackgroundDependencies {
|
|
49
50
|
paseoHome?: string;
|
|
51
|
+
worktreesRoot?: string;
|
|
50
52
|
emitWorkspaceUpdateForCwd: (cwd: string, options?: {
|
|
51
53
|
dedupeGitState?: boolean;
|
|
52
54
|
}) => Promise<void>;
|
|
@@ -55,10 +57,11 @@ interface CreatePaseoWorktreeInBackgroundDependencies {
|
|
|
55
57
|
sessionLogger: Logger;
|
|
56
58
|
terminalManager: TerminalManager | null;
|
|
57
59
|
archiveWorkspaceRecord: (workspaceId: string) => Promise<void>;
|
|
58
|
-
|
|
60
|
+
serviceProxy: ServiceProxySubsystem | null;
|
|
59
61
|
scriptRuntimeStore: WorkspaceScriptRuntimeStore | null;
|
|
60
62
|
getDaemonTcpPort: (() => number | null) | null;
|
|
61
63
|
getDaemonTcpHost: (() => string | null) | null;
|
|
64
|
+
serviceProxyPublicBaseUrl?: string | null;
|
|
62
65
|
onScriptsChanged: ((workspaceId: string, workspaceDirectory: string) => void) | null;
|
|
63
66
|
}
|
|
64
67
|
interface CreatePaseoWorktreeWorkflowDependencies extends CreatePaseoWorktreeInBackgroundDependencies {
|
|
@@ -100,6 +103,7 @@ interface HandleWorkspaceSetupStatusRequestDependencies {
|
|
|
100
103
|
}
|
|
101
104
|
interface HandleCreatePaseoWorktreeRequestDependencies {
|
|
102
105
|
paseoHome?: string;
|
|
106
|
+
worktreesRoot?: string;
|
|
103
107
|
describeWorkspaceRecord: (result: CreatePaseoWorktreeResult) => Promise<WorkspaceDescriptorPayload>;
|
|
104
108
|
emit: EmitSessionMessage;
|
|
105
109
|
sessionLogger: Logger;
|
|
@@ -41,6 +41,7 @@ export async function buildAgentSessionConfig(dependencies, config, gitOptions,
|
|
|
41
41
|
firstAgentContext,
|
|
42
42
|
runSetup: false,
|
|
43
43
|
paseoHome: dependencies.paseoHome,
|
|
44
|
+
worktreesRoot: dependencies.worktreesRoot,
|
|
44
45
|
}, {
|
|
45
46
|
resolveDefaultBranch: normalized.baseBranch
|
|
46
47
|
? async () => normalized.baseBranch
|
|
@@ -241,6 +242,7 @@ export async function handleCreatePaseoWorktreeRequest(dependencies, request) {
|
|
|
241
242
|
try {
|
|
242
243
|
const commandResult = await createPaseoWorktreeCommand({
|
|
243
244
|
paseoHome: dependencies.paseoHome,
|
|
245
|
+
worktreesRoot: dependencies.worktreesRoot,
|
|
244
246
|
createPaseoWorktreeWorkflow: dependencies.createPaseoWorktreeWorkflow,
|
|
245
247
|
}, {
|
|
246
248
|
cwd: request.cwd,
|
|
@@ -304,6 +306,7 @@ export async function createPaseoWorktreeWorkflow(dependencies, input, options)
|
|
|
304
306
|
...input,
|
|
305
307
|
runSetup: false,
|
|
306
308
|
paseoHome: input.paseoHome ?? dependencies.paseoHome,
|
|
309
|
+
worktreesRoot: input.worktreesRoot ?? dependencies.worktreesRoot,
|
|
307
310
|
}, options?.resolveDefaultBranch
|
|
308
311
|
? { resolveDefaultBranch: options.resolveDefaultBranch }
|
|
309
312
|
: undefined);
|
|
@@ -228,6 +228,7 @@ export interface GitHubService {
|
|
|
228
228
|
retainCurrentPullRequestStatusPoll?(options: {
|
|
229
229
|
cwd: string;
|
|
230
230
|
headRef: string;
|
|
231
|
+
headRepositoryOwner?: string;
|
|
231
232
|
onStatus?: (status: GitHubCurrentPullRequestStatus | null) => void;
|
|
232
233
|
onError?: (error: unknown) => void;
|
|
233
234
|
}): {
|
|
@@ -425,7 +425,10 @@ export function createGitHubService(options = {}) {
|
|
|
425
425
|
return buildCacheKey({
|
|
426
426
|
cwd: target.cwd,
|
|
427
427
|
method: "getCurrentPullRequestStatus",
|
|
428
|
-
args: {
|
|
428
|
+
args: {
|
|
429
|
+
headRef: target.headRef,
|
|
430
|
+
headRepositoryOwner: target.headRepositoryOwner,
|
|
431
|
+
},
|
|
429
432
|
});
|
|
430
433
|
}
|
|
431
434
|
function updatePollTargetAfterSuccess(update) {
|
|
@@ -465,6 +468,7 @@ export function createGitHubService(options = {}) {
|
|
|
465
468
|
await api.getCurrentPullRequestStatus({
|
|
466
469
|
cwd: target.cwd,
|
|
467
470
|
headRef: target.headRef,
|
|
471
|
+
headRepositoryOwner: target.headRepositoryOwner,
|
|
468
472
|
reason: "self-heal-github",
|
|
469
473
|
});
|
|
470
474
|
}
|
|
@@ -601,6 +605,7 @@ export function createGitHubService(options = {}) {
|
|
|
601
605
|
updatePollTargetAfterSuccess({
|
|
602
606
|
cwd: input.cwd,
|
|
603
607
|
headRef: input.headRef,
|
|
608
|
+
headRepositoryOwner: input.headRepositoryOwner,
|
|
604
609
|
status,
|
|
605
610
|
notify: input.reason === "self-heal-github",
|
|
606
611
|
});
|
|
@@ -795,6 +800,7 @@ export function createGitHubService(options = {}) {
|
|
|
795
800
|
target = {
|
|
796
801
|
cwd: input.cwd,
|
|
797
802
|
headRef: input.headRef,
|
|
803
|
+
headRepositoryOwner: input.headRepositoryOwner,
|
|
798
804
|
retainCount: 0,
|
|
799
805
|
timer: null,
|
|
800
806
|
latestStatus: null,
|
|
@@ -130,6 +130,7 @@ export interface MergeFromBaseOptions {
|
|
|
130
130
|
}
|
|
131
131
|
export interface CheckoutContext {
|
|
132
132
|
paseoHome?: string;
|
|
133
|
+
worktreesRoot?: string;
|
|
133
134
|
logger?: Pick<Logger, "trace">;
|
|
134
135
|
facts?: CheckoutSnapshotFacts | null;
|
|
135
136
|
}
|
|
@@ -159,8 +160,11 @@ export interface GitWorktreeEntry {
|
|
|
159
160
|
branchRef?: string;
|
|
160
161
|
isBare?: boolean;
|
|
161
162
|
}
|
|
162
|
-
/** Check whether a path
|
|
163
|
-
export declare function isPaseoWorktreePath(p: string
|
|
163
|
+
/** Check whether a path is under Paseo's worktree root. */
|
|
164
|
+
export declare function isPaseoWorktreePath(p: string, options?: {
|
|
165
|
+
paseoHome?: string;
|
|
166
|
+
worktreesRoot?: string;
|
|
167
|
+
}): boolean;
|
|
164
168
|
/** True when `child` is strictly inside `parent` (handles both `/` and `\`). */
|
|
165
169
|
export declare function isDescendantPath(child: string, parent: string): boolean;
|
|
166
170
|
export declare function parseWorktreeList(output: string): GitWorktreeEntry[];
|
|
@@ -7,7 +7,7 @@ import { parseGitHubRepoFromRemote } from "../server/workspace-git-metadata.js";
|
|
|
7
7
|
import { GitHubAuthenticationError, GitHubCliMissingError, GitHubCommandError, createGitHubService, resolveGitHubRepo, } from "../services/github-service.js";
|
|
8
8
|
import { parseGitRevParsePath, resolveGitRevParsePath } from "./git-rev-parse-path.js";
|
|
9
9
|
import { runGitCommand } from "./run-git-command.js";
|
|
10
|
-
import { isPaseoOwnedWorktreeCwd } from "./worktree.js";
|
|
10
|
+
import { isPaseoOwnedWorktreeCwd, resolvePaseoWorktreesBaseRoot } from "./worktree.js";
|
|
11
11
|
import { readPaseoWorktreeMetadata } from "./worktree-metadata.js";
|
|
12
12
|
const READ_ONLY_GIT_ENV = {
|
|
13
13
|
GIT_OPTIONAL_LOCKS: "0",
|
|
@@ -570,7 +570,7 @@ export async function getMainRepoRoot(cwd) {
|
|
|
570
570
|
});
|
|
571
571
|
return getMainRepoRootFromCommonDir(cwd, resolveGitRevParsePath(cwd, commonDirOut));
|
|
572
572
|
}
|
|
573
|
-
async function getMainRepoRootFromCommonDir(cwd, commonDir) {
|
|
573
|
+
async function getMainRepoRootFromCommonDir(cwd, commonDir, context) {
|
|
574
574
|
if (!commonDir) {
|
|
575
575
|
throw new Error("Not in a git repository");
|
|
576
576
|
}
|
|
@@ -583,13 +583,20 @@ async function getMainRepoRootFromCommonDir(cwd, commonDir) {
|
|
|
583
583
|
envOverlay: READ_ONLY_GIT_ENV,
|
|
584
584
|
});
|
|
585
585
|
const worktrees = parseWorktreeList(worktreeOut);
|
|
586
|
-
const nonBareNonPaseo = worktrees.filter((wt) => !wt.isBare &&
|
|
586
|
+
const nonBareNonPaseo = worktrees.filter((wt) => !wt.isBare &&
|
|
587
|
+
!isPaseoWorktreePath(wt.path, {
|
|
588
|
+
paseoHome: context?.paseoHome,
|
|
589
|
+
worktreesRoot: context?.worktreesRoot,
|
|
590
|
+
}));
|
|
587
591
|
const childrenOfBareRepo = nonBareNonPaseo.filter((wt) => isDescendantPath(wt.path, normalized));
|
|
588
592
|
const mainChild = childrenOfBareRepo.find((wt) => basename(wt.path) === "main");
|
|
589
593
|
return mainChild?.path ?? childrenOfBareRepo[0]?.path ?? nonBareNonPaseo[0]?.path ?? normalized;
|
|
590
594
|
}
|
|
591
|
-
/** Check whether a path
|
|
592
|
-
export function isPaseoWorktreePath(p) {
|
|
595
|
+
/** Check whether a path is under Paseo's worktree root. */
|
|
596
|
+
export function isPaseoWorktreePath(p, options) {
|
|
597
|
+
if (options?.worktreesRoot || options?.paseoHome) {
|
|
598
|
+
return isDescendantPath(p, resolvePaseoWorktreesBaseRoot(options));
|
|
599
|
+
}
|
|
593
600
|
return /[/\\]\.paseo[/\\]worktrees[/\\]/.test(p);
|
|
594
601
|
}
|
|
595
602
|
/** True when `child` is strictly inside `parent` (handles both `/` and `\`). */
|
|
@@ -669,7 +676,10 @@ async function getPaseoWorktreeForCwd(cwd, context, knownWorktreeRoot) {
|
|
|
669
676
|
if (!/[\\/]worktrees[\\/]/.test(cwd)) {
|
|
670
677
|
return { isPaseoOwnedWorktree: false };
|
|
671
678
|
}
|
|
672
|
-
const ownership = await isPaseoOwnedWorktreeCwd(cwd, {
|
|
679
|
+
const ownership = await isPaseoOwnedWorktreeCwd(cwd, {
|
|
680
|
+
paseoHome: context?.paseoHome,
|
|
681
|
+
worktreesRoot: context?.worktreesRoot,
|
|
682
|
+
});
|
|
673
683
|
if (!ownership.allowed) {
|
|
674
684
|
return { isPaseoOwnedWorktree: false };
|
|
675
685
|
}
|
|
@@ -1076,7 +1086,7 @@ export async function getCheckoutSnapshotFacts(cwd, context) {
|
|
|
1076
1086
|
? readPaseoWorktreeBaseRef(inspected.paseoWorktree.worktreeRoot)
|
|
1077
1087
|
: null;
|
|
1078
1088
|
const resolvedBaseRef = storedBaseRef ?? (await resolveBaseRef(cwd));
|
|
1079
|
-
const mainRepoRoot = await getMainRepoRootFromCommonDir(cwd, inspected.gitCommonDir).catch(() => null);
|
|
1089
|
+
const mainRepoRoot = await getMainRepoRootFromCommonDir(cwd, inspected.gitCommonDir, context).catch(() => null);
|
|
1080
1090
|
let comparisonBaseRef = null;
|
|
1081
1091
|
if (resolvedBaseRef &&
|
|
1082
1092
|
inspected.currentBranch &&
|
|
@@ -83,6 +83,10 @@ export interface PaseoWorktreeOwnership {
|
|
|
83
83
|
worktreeRoot?: string;
|
|
84
84
|
worktreePath?: string;
|
|
85
85
|
}
|
|
86
|
+
export interface WorktreeRootOptions {
|
|
87
|
+
paseoHome?: string;
|
|
88
|
+
worktreesRoot?: string;
|
|
89
|
+
}
|
|
86
90
|
export type WorktreeSource = {
|
|
87
91
|
kind: "branch-off";
|
|
88
92
|
baseBranch: string;
|
|
@@ -104,11 +108,13 @@ export interface CreateWorktreeOptions {
|
|
|
104
108
|
source: WorktreeSource;
|
|
105
109
|
runSetup: boolean;
|
|
106
110
|
paseoHome?: string;
|
|
111
|
+
worktreesRoot?: string;
|
|
107
112
|
}
|
|
108
113
|
interface ResolveExistingWorktreeForSlugOptions {
|
|
109
114
|
slug: string;
|
|
110
115
|
repoRoot: string;
|
|
111
116
|
paseoHome?: string;
|
|
117
|
+
worktreesRoot?: string;
|
|
112
118
|
}
|
|
113
119
|
export declare class BranchAlreadyCheckedOutError extends Error {
|
|
114
120
|
readonly branchName: string;
|
|
@@ -164,32 +170,31 @@ export declare function runWorktreeTeardownCommands(options: {
|
|
|
164
170
|
*/
|
|
165
171
|
export declare function getGitCommonDir(cwd: string): Promise<string>;
|
|
166
172
|
export declare function deriveWorktreeProjectHash(cwd: string): Promise<string>;
|
|
167
|
-
export declare function
|
|
168
|
-
export declare function
|
|
169
|
-
export declare function
|
|
170
|
-
|
|
171
|
-
}
|
|
172
|
-
export declare function listPaseoWorktrees({ cwd, paseoHome, }: {
|
|
173
|
+
export declare function resolvePaseoWorktreesBaseRoot(options?: WorktreeRootOptions): string;
|
|
174
|
+
export declare function getPaseoWorktreesRoot(cwd: string, paseoHome?: string, worktreesRoot?: string): Promise<string>;
|
|
175
|
+
export declare function computeWorktreePath(cwd: string, slug: string, paseoHome?: string, worktreesRoot?: string): Promise<string>;
|
|
176
|
+
export declare function isPaseoOwnedWorktreeCwd(cwd: string, options?: WorktreeRootOptions): Promise<PaseoWorktreeOwnership>;
|
|
177
|
+
export declare function listPaseoWorktrees({ cwd, paseoHome, worktreesRoot, }: {
|
|
173
178
|
cwd: string;
|
|
174
179
|
paseoHome?: string;
|
|
180
|
+
worktreesRoot?: string;
|
|
175
181
|
}): Promise<PaseoWorktreeInfo[]>;
|
|
176
|
-
export declare function resolveExistingWorktreeForSlug({ slug, repoRoot, paseoHome, }: ResolveExistingWorktreeForSlugOptions): Promise<WorktreeConfig | null>;
|
|
177
|
-
export declare function resolvePaseoWorktreeRootForCwd(cwd: string, options?: {
|
|
178
|
-
paseoHome?: string;
|
|
179
|
-
}): Promise<{
|
|
182
|
+
export declare function resolveExistingWorktreeForSlug({ slug, repoRoot, paseoHome, worktreesRoot, }: ResolveExistingWorktreeForSlugOptions): Promise<WorktreeConfig | null>;
|
|
183
|
+
export declare function resolvePaseoWorktreeRootForCwd(cwd: string, options?: WorktreeRootOptions): Promise<{
|
|
180
184
|
repoRoot: string;
|
|
181
185
|
worktreeRoot: string;
|
|
182
186
|
worktreePath: string;
|
|
183
187
|
} | null>;
|
|
184
|
-
export declare function deletePaseoWorktree({ cwd, worktreePath, worktreeSlug, worktreesRoot, paseoHome, }: {
|
|
188
|
+
export declare function deletePaseoWorktree({ cwd, worktreePath, worktreeSlug, worktreesRoot, paseoHome, worktreesBaseRoot, }: {
|
|
185
189
|
cwd: string | null;
|
|
186
190
|
worktreePath?: string;
|
|
187
191
|
worktreeSlug?: string;
|
|
188
192
|
worktreesRoot?: string;
|
|
189
193
|
paseoHome?: string;
|
|
194
|
+
worktreesBaseRoot?: string;
|
|
190
195
|
}): Promise<void>;
|
|
191
196
|
/**
|
|
192
197
|
* Create a git worktree with proper naming conventions
|
|
193
198
|
*/
|
|
194
|
-
export declare const createWorktree: ({ cwd, source, worktreeSlug, runSetup, paseoHome, }: CreateWorktreeOptions) => Promise<WorktreeConfig>;
|
|
199
|
+
export declare const createWorktree: ({ cwd, source, worktreeSlug, runSetup, paseoHome, worktreesRoot, }: CreateWorktreeOptions) => Promise<WorktreeConfig>;
|
|
195
200
|
//# sourceMappingURL=worktree.d.ts.map
|
|
@@ -2,7 +2,7 @@ import { execFile } from "child_process";
|
|
|
2
2
|
import { promisify } from "util";
|
|
3
3
|
import { existsSync, mkdirSync, realpathSync, rmSync, statSync } from "fs";
|
|
4
4
|
import { copyFile, rm, stat } from "fs/promises";
|
|
5
|
-
import { join, basename, dirname, resolve, sep } from "path";
|
|
5
|
+
import { join, basename, dirname, isAbsolute, resolve, sep } from "path";
|
|
6
6
|
import net from "node:net";
|
|
7
7
|
import { createHash } from "node:crypto";
|
|
8
8
|
import stripAnsi from "strip-ansi";
|
|
@@ -17,6 +17,7 @@ import { resolvePaseoHome } from "../server/paseo-home.js";
|
|
|
17
17
|
import { createExternalProcessEnv } from "../server/paseo-env.js";
|
|
18
18
|
import { parseGitRevParsePath, resolveGitRevParsePath } from "./git-rev-parse-path.js";
|
|
19
19
|
import { validateBranchSlug } from "@getpaseo/protocol/branch-slug";
|
|
20
|
+
import { expandTilde } from "./path.js";
|
|
20
21
|
export { slugify, validateBranchSlug } from "@getpaseo/protocol/branch-slug";
|
|
21
22
|
const execFileAsync = promisify(execFile);
|
|
22
23
|
const READ_ONLY_GIT_ENV = {
|
|
@@ -529,14 +530,26 @@ export async function deriveWorktreeProjectHash(cwd) {
|
|
|
529
530
|
return deriveShortAlphanumericHash(normalizePathForOwnership(cwd));
|
|
530
531
|
}
|
|
531
532
|
}
|
|
532
|
-
export
|
|
533
|
-
|
|
533
|
+
export function resolvePaseoWorktreesBaseRoot(options) {
|
|
534
|
+
if (options?.worktreesRoot) {
|
|
535
|
+
const expandedRoot = expandTilde(options.worktreesRoot);
|
|
536
|
+
if (isAbsolute(expandedRoot)) {
|
|
537
|
+
return resolve(expandedRoot);
|
|
538
|
+
}
|
|
539
|
+
const home = options.paseoHome ? resolve(options.paseoHome) : resolvePaseoHome();
|
|
540
|
+
return resolve(home, expandedRoot);
|
|
541
|
+
}
|
|
542
|
+
const home = options?.paseoHome ? resolve(options.paseoHome) : resolvePaseoHome();
|
|
543
|
+
return join(home, "worktrees");
|
|
544
|
+
}
|
|
545
|
+
export async function getPaseoWorktreesRoot(cwd, paseoHome, worktreesRoot) {
|
|
546
|
+
const baseRoot = resolvePaseoWorktreesBaseRoot({ paseoHome, worktreesRoot });
|
|
534
547
|
const projectHash = await deriveWorktreeProjectHash(cwd);
|
|
535
|
-
return join(
|
|
548
|
+
return join(baseRoot, projectHash);
|
|
536
549
|
}
|
|
537
|
-
export async function computeWorktreePath(cwd, slug, paseoHome) {
|
|
538
|
-
const
|
|
539
|
-
return join(
|
|
550
|
+
export async function computeWorktreePath(cwd, slug, paseoHome, worktreesRoot) {
|
|
551
|
+
const projectWorktreesRoot = await getPaseoWorktreesRoot(cwd, paseoHome, worktreesRoot);
|
|
552
|
+
return join(projectWorktreesRoot, slug);
|
|
540
553
|
}
|
|
541
554
|
function normalizePathForOwnership(input) {
|
|
542
555
|
try {
|
|
@@ -565,9 +578,9 @@ export async function isPaseoOwnedWorktreeCwd(cwd, options) {
|
|
|
565
578
|
catch {
|
|
566
579
|
// ignore
|
|
567
580
|
}
|
|
568
|
-
const
|
|
569
|
-
const paseoWorktreesPrefix = normalizePathForOwnership(
|
|
570
|
-
// Ownership is defined by the path living under
|
|
581
|
+
const worktreesBaseRoot = resolvePaseoWorktreesBaseRoot(options);
|
|
582
|
+
const paseoWorktreesPrefix = normalizePathForOwnership(worktreesBaseRoot) + sep;
|
|
583
|
+
// Ownership is defined by the path living under <worktrees-root>/<hash>/<slug>[/...].
|
|
571
584
|
// The <hash>/<slug> prefix is Paseo-private — nothing else writes there — so the
|
|
572
585
|
// path shape alone is sufficient proof of ownership, even when git has already
|
|
573
586
|
// forgotten about the worktree.
|
|
@@ -587,7 +600,7 @@ export async function isPaseoOwnedWorktreeCwd(cwd, options) {
|
|
|
587
600
|
worktreePath: resolvedCwd,
|
|
588
601
|
};
|
|
589
602
|
}
|
|
590
|
-
const worktreesRoot = join(
|
|
603
|
+
const worktreesRoot = join(worktreesBaseRoot, parts[0]);
|
|
591
604
|
return {
|
|
592
605
|
allowed: true,
|
|
593
606
|
...(repoRoot !== undefined ? { repoRoot } : {}),
|
|
@@ -639,22 +652,23 @@ function resolveWorktreeCreatedAtIso(worktreePath) {
|
|
|
639
652
|
return new Date(0).toISOString();
|
|
640
653
|
}
|
|
641
654
|
}
|
|
642
|
-
export async function listPaseoWorktrees({ cwd, paseoHome, }) {
|
|
643
|
-
const
|
|
655
|
+
export async function listPaseoWorktrees({ cwd, paseoHome, worktreesRoot, }) {
|
|
656
|
+
const projectWorktreesRoot = await getPaseoWorktreesRoot(cwd, paseoHome, worktreesRoot);
|
|
644
657
|
const { stdout } = await runGitCommand(["worktree", "list", "--porcelain"], {
|
|
645
658
|
cwd,
|
|
646
659
|
envOverlay: READ_ONLY_GIT_ENV,
|
|
647
660
|
});
|
|
648
|
-
const rootPrefix = normalizePathForOwnership(
|
|
661
|
+
const rootPrefix = normalizePathForOwnership(projectWorktreesRoot) + sep;
|
|
649
662
|
return parseWorktreeList(stdout)
|
|
650
663
|
.map((entry) => Object.assign({}, entry, { path: normalizePathForOwnership(entry.path) }))
|
|
651
664
|
.filter((entry) => entry.path.startsWith(rootPrefix))
|
|
652
665
|
.map((entry) => Object.assign({}, entry, { createdAt: resolveWorktreeCreatedAtIso(entry.path) }));
|
|
653
666
|
}
|
|
654
|
-
export async function resolveExistingWorktreeForSlug({ slug, repoRoot, paseoHome, }) {
|
|
667
|
+
export async function resolveExistingWorktreeForSlug({ slug, repoRoot, paseoHome, worktreesRoot, }) {
|
|
655
668
|
const worktrees = await listPaseoWorktrees({
|
|
656
669
|
cwd: repoRoot,
|
|
657
670
|
paseoHome,
|
|
671
|
+
worktreesRoot,
|
|
658
672
|
});
|
|
659
673
|
const slugSuffix = `${sep}${slug}`;
|
|
660
674
|
const existingWorktree = worktrees.find((worktree) => worktree.path.endsWith(slugSuffix));
|
|
@@ -682,7 +696,7 @@ export async function resolvePaseoWorktreeRootForCwd(cwd, options) {
|
|
|
682
696
|
catch {
|
|
683
697
|
return null;
|
|
684
698
|
}
|
|
685
|
-
const worktreesRoot = await getPaseoWorktreesRoot(cwd, options?.paseoHome);
|
|
699
|
+
const worktreesRoot = await getPaseoWorktreesRoot(cwd, options?.paseoHome, options?.worktreesRoot);
|
|
686
700
|
const resolvedRoot = normalizePathForOwnership(worktreesRoot) + sep;
|
|
687
701
|
let worktreeRoot = null;
|
|
688
702
|
try {
|
|
@@ -705,6 +719,7 @@ export async function resolvePaseoWorktreeRootForCwd(cwd, options) {
|
|
|
705
719
|
const knownWorktrees = await listPaseoWorktrees({
|
|
706
720
|
cwd,
|
|
707
721
|
paseoHome: options?.paseoHome,
|
|
722
|
+
worktreesRoot: options?.worktreesRoot,
|
|
708
723
|
});
|
|
709
724
|
const match = knownWorktrees.find((entry) => entry.path === resolvedWorktreeRoot);
|
|
710
725
|
if (!match) {
|
|
@@ -716,7 +731,7 @@ export async function resolvePaseoWorktreeRootForCwd(cwd, options) {
|
|
|
716
731
|
worktreePath: match.path,
|
|
717
732
|
};
|
|
718
733
|
}
|
|
719
|
-
export async function deletePaseoWorktree({ cwd, worktreePath, worktreeSlug, worktreesRoot, paseoHome, }) {
|
|
734
|
+
export async function deletePaseoWorktree({ cwd, worktreePath, worktreeSlug, worktreesRoot, paseoHome, worktreesBaseRoot, }) {
|
|
720
735
|
if (!worktreePath && !worktreeSlug) {
|
|
721
736
|
throw new Error("worktreePath or worktreeSlug is required");
|
|
722
737
|
}
|
|
@@ -728,7 +743,7 @@ export async function deletePaseoWorktree({ cwd, worktreePath, worktreeSlug, wor
|
|
|
728
743
|
resolvedWorktreesRoot = worktreesRoot;
|
|
729
744
|
}
|
|
730
745
|
else if (cwd) {
|
|
731
|
-
resolvedWorktreesRoot = await getPaseoWorktreesRoot(cwd, paseoHome);
|
|
746
|
+
resolvedWorktreesRoot = await getPaseoWorktreesRoot(cwd, paseoHome, worktreesBaseRoot);
|
|
732
747
|
}
|
|
733
748
|
else {
|
|
734
749
|
throw new Error("cwd or worktreesRoot is required to delete a Paseo worktree");
|
|
@@ -736,8 +751,10 @@ export async function deletePaseoWorktree({ cwd, worktreePath, worktreeSlug, wor
|
|
|
736
751
|
const resolvedRoot = normalizePathForOwnership(resolvedWorktreesRoot) + sep;
|
|
737
752
|
const requestedPath = worktreePath ?? join(resolvedWorktreesRoot, worktreeSlug);
|
|
738
753
|
const resolvedRequested = normalizePathForOwnership(requestedPath);
|
|
739
|
-
const resolvedWorktree = (await resolvePaseoWorktreeRootForCwd(requestedPath, {
|
|
740
|
-
|
|
754
|
+
const resolvedWorktree = (await resolvePaseoWorktreeRootForCwd(requestedPath, {
|
|
755
|
+
paseoHome,
|
|
756
|
+
worktreesRoot: worktreesBaseRoot,
|
|
757
|
+
}))?.worktreePath ?? resolvedRequested;
|
|
741
758
|
if (!resolvedWorktree.startsWith(resolvedRoot)) {
|
|
742
759
|
throw new Error("Refusing to delete non-Paseo worktree");
|
|
743
760
|
}
|
|
@@ -812,9 +829,9 @@ async function removeDirectoryWithRetries(path) {
|
|
|
812
829
|
/**
|
|
813
830
|
* Create a git worktree with proper naming conventions
|
|
814
831
|
*/
|
|
815
|
-
export const createWorktree = async ({ cwd, source, worktreeSlug, runSetup, paseoHome, }) => {
|
|
832
|
+
export const createWorktree = async ({ cwd, source, worktreeSlug, runSetup, paseoHome, worktreesRoot, }) => {
|
|
816
833
|
const sourcePlan = await resolveWorktreeSourcePlan({ cwd, source, desiredSlug: worktreeSlug });
|
|
817
|
-
let worktreePath = join(await getPaseoWorktreesRoot(cwd, paseoHome), worktreeSlug);
|
|
834
|
+
let worktreePath = join(await getPaseoWorktreesRoot(cwd, paseoHome, worktreesRoot), worktreeSlug);
|
|
818
835
|
mkdirSync(dirname(worktreePath), { recursive: true });
|
|
819
836
|
// Also handle worktree path collision
|
|
820
837
|
let finalWorktreePath = worktreePath;
|
|
@@ -49,6 +49,11 @@ const ProvidersSchema = z
|
|
|
49
49
|
local: LocalSpeechProviderSchema.optional(),
|
|
50
50
|
})
|
|
51
51
|
.strict();
|
|
52
|
+
const WorktreesConfigSchema = z
|
|
53
|
+
.object({
|
|
54
|
+
root: z.string().min(1).optional(),
|
|
55
|
+
})
|
|
56
|
+
.strict();
|
|
52
57
|
const BcryptHashSchema = z.string().regex(/^\$2[aby]\$\d{2}\$[./A-Za-z0-9]{53}$/, {
|
|
53
58
|
message: "Expected a bcrypt hash",
|
|
54
59
|
});
|
|
@@ -197,6 +202,17 @@ export const PersistedConfigSchema = z
|
|
|
197
202
|
})
|
|
198
203
|
.strict()
|
|
199
204
|
.optional(),
|
|
205
|
+
serviceProxy: z
|
|
206
|
+
.object({
|
|
207
|
+
// COMPAT(serviceProxyEnabled): added 2026-06-02, remove after 2026-12-02.
|
|
208
|
+
// Parsed only to suppress optional public/listen layers for old configs;
|
|
209
|
+
// localhost service proxying remains always enabled.
|
|
210
|
+
enabled: z.boolean().optional(),
|
|
211
|
+
listen: z.string().optional(),
|
|
212
|
+
publicBaseUrl: z.string().url().optional(),
|
|
213
|
+
})
|
|
214
|
+
.strict()
|
|
215
|
+
.optional(),
|
|
200
216
|
auth: DaemonAuthSchema.optional(),
|
|
201
217
|
})
|
|
202
218
|
.strict()
|
|
@@ -212,6 +228,7 @@ export const PersistedConfigSchema = z
|
|
|
212
228
|
.strict()
|
|
213
229
|
.optional(),
|
|
214
230
|
providers: ProvidersSchema.optional(),
|
|
231
|
+
worktrees: WorktreesConfigSchema.optional(),
|
|
215
232
|
agents: z
|
|
216
233
|
.object({
|
|
217
234
|
providers: z.preprocess(normalizeAgentProviders, ProviderOverridesSchema).optional(),
|