@getpaseo/server 0.1.87 → 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.
Files changed (69) hide show
  1. package/dist/server/server/agent/agent-manager.js +4 -1
  2. package/dist/server/server/agent/agent-storage.d.ts +22 -22
  3. package/dist/server/server/agent/create-agent/create.d.ts +2 -0
  4. package/dist/server/server/agent/create-agent/create.js +16 -5
  5. package/dist/server/server/agent/create-agent-lifecycle-dispatch.d.ts +1 -0
  6. package/dist/server/server/agent/create-agent-lifecycle-dispatch.js +4 -0
  7. package/dist/server/server/agent/mcp-server.d.ts +1 -0
  8. package/dist/server/server/agent/mcp-server.js +137 -63
  9. package/dist/server/server/agent/mcp-shared.d.ts +1 -0
  10. package/dist/server/server/agent/providers/pi/agent.js +13 -0
  11. package/dist/server/server/agent/providers/pi/rpc-types.d.ts +3 -0
  12. package/dist/server/server/agent/timeline-projection.d.ts +17 -1
  13. package/dist/server/server/agent/timeline-projection.js +82 -17
  14. package/dist/server/server/auto-archive-on-merge/archive-if-safe.d.ts +1 -0
  15. package/dist/server/server/auto-archive-on-merge/archive-if-safe.js +6 -1
  16. package/dist/server/server/bootstrap.d.ts +7 -2
  17. package/dist/server/server/bootstrap.js +152 -115
  18. package/dist/server/server/config.js +41 -0
  19. package/dist/server/server/loop-service.d.ts +22 -22
  20. package/dist/server/server/package-version.d.ts +2 -2
  21. package/dist/server/server/paseo-worktree-archive-service.d.ts +2 -0
  22. package/dist/server/server/paseo-worktree-archive-service.js +28 -9
  23. package/dist/server/server/persisted-config.d.ts +89 -33
  24. package/dist/server/server/persisted-config.js +17 -0
  25. package/dist/server/server/pid-lock.d.ts +2 -2
  26. package/dist/server/server/schedule/cron.js +52 -5
  27. package/dist/server/server/script-health-monitor.d.ts +4 -4
  28. package/dist/server/server/script-health-monitor.js +6 -6
  29. package/dist/server/server/script-proxy.d.ts +2 -39
  30. package/dist/server/server/script-proxy.js +1 -244
  31. package/dist/server/server/script-route-branch-handler.d.ts +2 -2
  32. package/dist/server/server/script-route-branch-handler.js +3 -37
  33. package/dist/server/server/script-status-projection.d.ts +6 -4
  34. package/dist/server/server/script-status-projection.js +85 -37
  35. package/dist/server/server/service-proxy.d.ts +237 -0
  36. package/dist/server/server/service-proxy.js +714 -0
  37. package/dist/server/server/session.d.ts +11 -4
  38. package/dist/server/server/session.js +96 -99
  39. package/dist/server/server/websocket-server.d.ts +7 -4
  40. package/dist/server/server/websocket-server.js +9 -4
  41. package/dist/server/server/workspace-directory.js +4 -0
  42. package/dist/server/server/workspace-git-service.d.ts +3 -0
  43. package/dist/server/server/workspace-git-service.js +53 -12
  44. package/dist/server/server/workspace-registry.d.ts +2 -2
  45. package/dist/server/server/workspace-service-env.d.ts +1 -0
  46. package/dist/server/server/workspace-service-env.js +23 -18
  47. package/dist/server/server/worktree/commands.d.ts +2 -0
  48. package/dist/server/server/worktree/commands.js +4 -1
  49. package/dist/server/server/worktree-bootstrap.d.ts +4 -3
  50. package/dist/server/server/worktree-bootstrap.js +14 -13
  51. package/dist/server/server/worktree-core.d.ts +1 -0
  52. package/dist/server/server/worktree-core.js +2 -0
  53. package/dist/server/server/worktree-session.d.ts +6 -2
  54. package/dist/server/server/worktree-session.js +3 -0
  55. package/dist/server/services/github-service.d.ts +1 -0
  56. package/dist/server/services/github-service.js +7 -1
  57. package/dist/server/terminal/terminal-manager.js +11 -1
  58. package/dist/server/terminal/terminal-session-controller.d.ts +3 -1
  59. package/dist/server/terminal/terminal-session-controller.js +22 -12
  60. package/dist/server/terminal/terminal.d.ts +1 -0
  61. package/dist/server/terminal/terminal.js +34 -0
  62. package/dist/server/utils/checkout-git.d.ts +6 -2
  63. package/dist/server/utils/checkout-git.js +136 -54
  64. package/dist/server/utils/worktree.d.ts +17 -12
  65. package/dist/server/utils/worktree.js +39 -22
  66. package/dist/src/server/persisted-config.js +17 -0
  67. package/package.json +5 -5
  68. package/dist/server/utils/script-hostname.d.ts +0 -8
  69. package/dist/server/utils/script-hostname.js +0 -14
@@ -71,9 +71,59 @@ function parseCronExpression(expression) {
71
71
  function startOfNextMinute(date) {
72
72
  return new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), date.getUTCHours(), date.getUTCMinutes() + 1, 0, 0));
73
73
  }
74
+ function assertValidTimeZone(timeZone) {
75
+ try {
76
+ new Intl.DateTimeFormat("en-US", { timeZone }).format(new Date(0));
77
+ }
78
+ catch {
79
+ throw new Error(`Invalid cron time zone: ${timeZone}`);
80
+ }
81
+ }
82
+ function createCronDatePartsReader(timeZone) {
83
+ if (timeZone === undefined) {
84
+ return (date) => ({
85
+ minute: date.getUTCMinutes(),
86
+ hour: date.getUTCHours(),
87
+ dayOfMonth: date.getUTCDate(),
88
+ month: date.getUTCMonth() + 1,
89
+ dayOfWeek: date.getUTCDay(),
90
+ });
91
+ }
92
+ assertValidTimeZone(timeZone);
93
+ const formatter = new Intl.DateTimeFormat("en-US", {
94
+ timeZone,
95
+ hourCycle: "h23",
96
+ year: "numeric",
97
+ month: "2-digit",
98
+ day: "2-digit",
99
+ hour: "2-digit",
100
+ minute: "2-digit",
101
+ });
102
+ return (date) => {
103
+ const values = {};
104
+ for (const part of formatter.formatToParts(date)) {
105
+ if (part.type !== "literal") {
106
+ values[part.type] = part.value;
107
+ }
108
+ }
109
+ const year = Number.parseInt(values.year, 10);
110
+ const month = Number.parseInt(values.month, 10);
111
+ const dayOfMonth = Number.parseInt(values.day, 10);
112
+ return {
113
+ minute: Number.parseInt(values.minute, 10),
114
+ hour: Number.parseInt(values.hour, 10),
115
+ dayOfMonth,
116
+ month,
117
+ dayOfWeek: new Date(Date.UTC(year, month - 1, dayOfMonth)).getUTCDay(),
118
+ };
119
+ };
120
+ }
74
121
  export function validateScheduleCadence(cadence) {
75
122
  if (cadence.type === "cron") {
76
123
  parseCronExpression(cadence.expression);
124
+ if (cadence.timezone !== undefined) {
125
+ assertValidTimeZone(cadence.timezone);
126
+ }
77
127
  }
78
128
  }
79
129
  export function computeNextRunAt(cadence, after) {
@@ -81,14 +131,11 @@ export function computeNextRunAt(cadence, after) {
81
131
  return new Date(after.getTime() + cadence.everyMs);
82
132
  }
83
133
  const cron = parseCronExpression(cadence.expression);
134
+ const readDateParts = createCronDatePartsReader(cadence.timezone);
84
135
  const limit = 366 * 24 * 60;
85
136
  let cursor = startOfNextMinute(after);
86
137
  for (let index = 0; index < limit; index += 1) {
87
- const minute = cursor.getUTCMinutes();
88
- const hour = cursor.getUTCHours();
89
- const dayOfMonth = cursor.getUTCDate();
90
- const month = cursor.getUTCMonth() + 1;
91
- const dayOfWeek = cursor.getUTCDay();
138
+ const { minute, hour, dayOfMonth, month, dayOfWeek } = readDateParts(cursor);
92
139
  if (cron.minute.matches(minute) &&
93
140
  cron.hour.matches(hour) &&
94
141
  cron.dayOfMonth.matches(dayOfMonth) &&
@@ -1,4 +1,4 @@
1
- import type { ScriptRouteStore } from "./script-proxy.js";
1
+ import type { ServiceProxySubsystem } from "./service-proxy.js";
2
2
  export type ScriptHealthState = "pending" | "healthy" | "unhealthy";
3
3
  export interface ScriptHealthEntry {
4
4
  scriptName: string;
@@ -7,7 +7,7 @@ export interface ScriptHealthEntry {
7
7
  health: ScriptHealthState;
8
8
  }
9
9
  export declare class ScriptHealthMonitor {
10
- private readonly routeStore;
10
+ private readonly serviceProxy;
11
11
  private readonly onChange;
12
12
  private readonly pollIntervalMs;
13
13
  private readonly probeTimeoutMs;
@@ -17,8 +17,8 @@ export declare class ScriptHealthMonitor {
17
17
  private readonly lastEmittedSnapshots;
18
18
  private intervalHandle;
19
19
  private pollInFlight;
20
- constructor({ routeStore, onChange, pollIntervalMs, probeTimeoutMs, graceMs, failuresBeforeStopped, }: {
21
- routeStore: ScriptRouteStore;
20
+ constructor({ serviceProxy, onChange, pollIntervalMs, probeTimeoutMs, graceMs, failuresBeforeStopped, }: {
21
+ serviceProxy: ServiceProxySubsystem;
22
22
  onChange: (workspaceId: string, scripts: ScriptHealthEntry[]) => void;
23
23
  pollIntervalMs?: number;
24
24
  probeTimeoutMs?: number;
@@ -1,11 +1,11 @@
1
1
  import net from "node:net";
2
2
  export class ScriptHealthMonitor {
3
- constructor({ routeStore, onChange, pollIntervalMs = 3000, probeTimeoutMs = 500, graceMs = 5000, failuresBeforeStopped = 2, }) {
3
+ constructor({ serviceProxy, onChange, pollIntervalMs = 3000, probeTimeoutMs = 500, graceMs = 5000, failuresBeforeStopped = 2, }) {
4
4
  this.routeStates = new Map();
5
5
  this.lastEmittedSnapshots = new Map();
6
6
  this.intervalHandle = null;
7
7
  this.pollInFlight = false;
8
- this.routeStore = routeStore;
8
+ this.serviceProxy = serviceProxy;
9
9
  this.onChange = onChange;
10
10
  this.pollIntervalMs = pollIntervalMs;
11
11
  this.probeTimeoutMs = probeTimeoutMs;
@@ -17,7 +17,7 @@ export class ScriptHealthMonitor {
17
17
  return;
18
18
  }
19
19
  const now = Date.now();
20
- for (const route of this.routeStore.listRoutes()) {
20
+ for (const route of this.serviceProxy.getHealthCheckTargets()) {
21
21
  this.getOrCreateState(route, now);
22
22
  }
23
23
  this.intervalHandle = setInterval(() => {
@@ -45,7 +45,7 @@ export class ScriptHealthMonitor {
45
45
  }
46
46
  this.pollInFlight = true;
47
47
  try {
48
- const routes = this.routeStore.listRoutes();
48
+ const routes = this.serviceProxy.getHealthCheckTargets();
49
49
  const activeHostnames = new Set(routes.map((route) => route.hostname));
50
50
  const changedWorkspaceIds = new Set();
51
51
  const now = Date.now();
@@ -110,7 +110,7 @@ export class ScriptHealthMonitor {
110
110
  }
111
111
  }
112
112
  buildWorkspaceScriptList(workspaceId) {
113
- return this.routeStore.listRoutesForWorkspace(workspaceId).flatMap((route) => {
113
+ return this.serviceProxy.getWorkspaceHealthTargets(workspaceId).flatMap((route) => {
114
114
  const state = this.routeStates.get(route.hostname);
115
115
  if (!state) {
116
116
  return [];
@@ -123,7 +123,7 @@ export class ScriptHealthMonitor {
123
123
  if (state) {
124
124
  return state.health;
125
125
  }
126
- const route = this.routeStore.getRouteEntry(hostname);
126
+ const route = this.serviceProxy.getHealthTargetForHostname(hostname);
127
127
  if (!route) {
128
128
  return null;
129
129
  }
@@ -1,40 +1,3 @@
1
- import net from "node:net";
2
- import type { IncomingMessage } from "node:http";
3
- import type { Logger } from "pino";
4
- import type { RequestHandler } from "express";
5
- export interface ScriptRoute {
6
- hostname: string;
7
- port: number;
8
- }
9
- export interface ScriptRouteEntry extends ScriptRoute {
10
- workspaceId: string;
11
- projectSlug: string;
12
- scriptName: string;
13
- }
14
- export declare class ScriptRouteStore {
15
- private routes;
16
- private workspaceHostnames;
17
- registerRoute(entry: ScriptRouteEntry): void;
18
- removeRoute(hostname: string): void;
19
- removeRouteForWorkspaceScript(params: {
20
- workspaceId: string;
21
- scriptName: string;
22
- }): void;
23
- removeRoutesForPort(port: number): void;
24
- findRoute(host: string): ScriptRoute | null;
25
- getRouteEntry(hostname: string): ScriptRouteEntry | null;
26
- listRoutes(): ScriptRouteEntry[];
27
- listRoutesForWorkspace(workspaceId: string): ScriptRouteEntry[];
28
- private addHostnameToWorkspaceIndex;
29
- private removeHostnameFromWorkspaceIndex;
30
- }
31
- export declare function createScriptProxyMiddleware({ routeStore, logger, }: {
32
- routeStore: ScriptRouteStore;
33
- logger: Logger;
34
- }): RequestHandler;
35
- export declare function createScriptProxyUpgradeHandler({ routeStore, logger, }: {
36
- routeStore: ScriptRouteStore;
37
- logger: Logger;
38
- }): (req: IncomingMessage, socket: net.Socket, head: Buffer) => void;
39
- export declare function findFreePort(): Promise<number>;
1
+ export { createScriptProxyMiddleware, createScriptProxyUpgradeHandler, findFreePort, ScriptRouteStore, } from "./service-proxy.js";
2
+ export type { ScriptRoute, ScriptRouteEntry } from "./service-proxy.js";
40
3
  //# sourceMappingURL=script-proxy.d.ts.map
@@ -1,245 +1,2 @@
1
- import http from "node:http";
2
- import net from "node:net";
3
- // ---------------------------------------------------------------------------
4
- // Hop-by-hop headers that must not be forwarded
5
- // ---------------------------------------------------------------------------
6
- const HOP_BY_HOP_HEADERS = new Set([
7
- "connection",
8
- "transfer-encoding",
9
- "keep-alive",
10
- "upgrade",
11
- "proxy-connection",
12
- "proxy-authenticate",
13
- "proxy-authorization",
14
- "te",
15
- "trailer",
16
- ]);
17
- export class ScriptRouteStore {
18
- constructor() {
19
- this.routes = new Map();
20
- this.workspaceHostnames = new Map();
21
- }
22
- registerRoute(entry) {
23
- const previous = this.routes.get(entry.hostname);
24
- if (previous) {
25
- this.removeHostnameFromWorkspaceIndex(previous.workspaceId, previous.hostname);
26
- }
27
- const storedEntry = { ...entry };
28
- this.routes.set(storedEntry.hostname, storedEntry);
29
- this.addHostnameToWorkspaceIndex(storedEntry.workspaceId, storedEntry.hostname);
30
- }
31
- removeRoute(hostname) {
32
- const entry = this.routes.get(hostname);
33
- if (!entry) {
34
- return;
35
- }
36
- this.routes.delete(hostname);
37
- this.removeHostnameFromWorkspaceIndex(entry.workspaceId, hostname);
38
- }
39
- removeRouteForWorkspaceScript(params) {
40
- const routes = this.listRoutesForWorkspace(params.workspaceId);
41
- const route = routes.find((entry) => entry.scriptName === params.scriptName);
42
- if (!route) {
43
- return;
44
- }
45
- this.removeRoute(route.hostname);
46
- }
47
- removeRoutesForPort(port) {
48
- for (const [hostname, entry] of this.routes) {
49
- if (entry.port === port) {
50
- this.routes.delete(hostname);
51
- this.removeHostnameFromWorkspaceIndex(entry.workspaceId, hostname);
52
- }
53
- }
54
- }
55
- findRoute(host) {
56
- // Strip port suffix from the Host header value
57
- const hostname = host.replace(/:\d+$/, "");
58
- // 1. Exact match
59
- const exactRoute = this.routes.get(hostname);
60
- if (exactRoute !== undefined) {
61
- return { hostname: exactRoute.hostname, port: exactRoute.port };
62
- }
63
- // 2. Subdomain match — walk up the labels looking for a registered parent
64
- const parts = hostname.split(".");
65
- for (let i = 1; i < parts.length; i++) {
66
- const candidate = parts.slice(i).join(".");
67
- const candidateRoute = this.routes.get(candidate);
68
- if (candidateRoute !== undefined) {
69
- return { hostname: candidateRoute.hostname, port: candidateRoute.port };
70
- }
71
- }
72
- return null;
73
- }
74
- getRouteEntry(hostname) {
75
- const entry = this.routes.get(hostname);
76
- return entry ? { ...entry } : null;
77
- }
78
- listRoutes() {
79
- return Array.from(this.routes.values()).map((entry) => Object.assign({}, entry));
80
- }
81
- listRoutesForWorkspace(workspaceId) {
82
- const hostnames = this.workspaceHostnames.get(workspaceId);
83
- if (!hostnames) {
84
- return [];
85
- }
86
- const routes = [];
87
- for (const hostname of hostnames) {
88
- const entry = this.routes.get(hostname);
89
- if (entry) {
90
- routes.push({ ...entry });
91
- }
92
- }
93
- return routes;
94
- }
95
- addHostnameToWorkspaceIndex(workspaceId, hostname) {
96
- const hostnames = this.workspaceHostnames.get(workspaceId) ?? new Set();
97
- hostnames.add(hostname);
98
- this.workspaceHostnames.set(workspaceId, hostnames);
99
- }
100
- removeHostnameFromWorkspaceIndex(workspaceId, hostname) {
101
- const hostnames = this.workspaceHostnames.get(workspaceId);
102
- if (!hostnames) {
103
- return;
104
- }
105
- hostnames.delete(hostname);
106
- if (hostnames.size === 0) {
107
- this.workspaceHostnames.delete(workspaceId);
108
- }
109
- }
110
- }
111
- // ---------------------------------------------------------------------------
112
- // Helpers
113
- // ---------------------------------------------------------------------------
114
- function stripHopByHopHeaders(rawHeaders) {
115
- const out = {};
116
- for (const [key, value] of Object.entries(rawHeaders)) {
117
- if (value === undefined)
118
- continue;
119
- if (HOP_BY_HOP_HEADERS.has(key.toLowerCase()))
120
- continue;
121
- out[key] = value;
122
- }
123
- return out;
124
- }
125
- // ---------------------------------------------------------------------------
126
- // createScriptProxyMiddleware
127
- // ---------------------------------------------------------------------------
128
- export function createScriptProxyMiddleware({ routeStore, logger, }) {
129
- return (req, res, next) => {
130
- const hostHeader = req.headers.host;
131
- if (!hostHeader) {
132
- next();
133
- return;
134
- }
135
- const route = routeStore.findRoute(hostHeader);
136
- if (!route) {
137
- next();
138
- return;
139
- }
140
- const forwardedHeaders = stripHopByHopHeaders(req.headers);
141
- forwardedHeaders["x-forwarded-for"] = req.socket.remoteAddress ?? "127.0.0.1";
142
- forwardedHeaders["x-forwarded-host"] = hostHeader.replace(/:\d+$/, "");
143
- forwardedHeaders["x-forwarded-proto"] = req.protocol;
144
- const proxyReq = http.request({
145
- hostname: "127.0.0.1",
146
- port: route.port,
147
- path: req.originalUrl,
148
- method: req.method,
149
- headers: forwardedHeaders,
150
- }, (proxyRes) => {
151
- const responseHeaders = stripHopByHopHeaders(proxyRes.headers);
152
- res.writeHead(proxyRes.statusCode ?? 502, responseHeaders);
153
- proxyRes.pipe(res, { end: true });
154
- });
155
- proxyReq.on("error", (err) => {
156
- logger.warn({ err, hostname: route.hostname, port: route.port }, "Script proxy: upstream unreachable");
157
- if (!res.headersSent) {
158
- res.writeHead(502, { "content-type": "text/plain" });
159
- res.end("502 Bad Gateway");
160
- }
161
- });
162
- req.pipe(proxyReq, { end: true });
163
- };
164
- }
165
- // ---------------------------------------------------------------------------
166
- // createScriptProxyUpgradeHandler
167
- // ---------------------------------------------------------------------------
168
- export function createScriptProxyUpgradeHandler({ routeStore, logger, }) {
169
- return (req, socket, head) => {
170
- const hostHeader = req.headers.host;
171
- if (!hostHeader) {
172
- return;
173
- }
174
- const route = routeStore.findRoute(hostHeader);
175
- if (!route) {
176
- return;
177
- }
178
- const targetSocket = net.connect({ host: "127.0.0.1", port: route.port }, () => {
179
- // Reconstruct the raw HTTP upgrade request to send to the target
180
- const forwardedHeaders = stripHopByHopHeaders(req.headers);
181
- forwardedHeaders["x-forwarded-for"] = req.socket.remoteAddress ?? "127.0.0.1";
182
- forwardedHeaders["x-forwarded-host"] = hostHeader.replace(/:\d+$/, "");
183
- forwardedHeaders["x-forwarded-proto"] = "http";
184
- // Re-include upgrade and connection headers — they are required for
185
- // WebSocket handshake even though they are hop-by-hop.
186
- forwardedHeaders["connection"] = "Upgrade";
187
- forwardedHeaders["upgrade"] = req.headers.upgrade ?? "websocket";
188
- const headerLines = [];
189
- headerLines.push(`${req.method ?? "GET"} ${req.url ?? "/"} HTTP/${req.httpVersion}`);
190
- for (const [key, value] of Object.entries(forwardedHeaders)) {
191
- if (Array.isArray(value)) {
192
- for (const v of value) {
193
- headerLines.push(`${key}: ${v}`);
194
- }
195
- }
196
- else {
197
- headerLines.push(`${key}: ${value}`);
198
- }
199
- }
200
- headerLines.push("\r\n");
201
- targetSocket.write(headerLines.join("\r\n"));
202
- if (head.length > 0) {
203
- targetSocket.write(head);
204
- }
205
- // Pipe in both directions
206
- targetSocket.pipe(socket);
207
- socket.pipe(targetSocket);
208
- });
209
- targetSocket.on("error", (err) => {
210
- logger.warn({ err, hostname: route.hostname, port: route.port }, "Script proxy: WebSocket upstream unreachable");
211
- socket.end();
212
- });
213
- socket.on("error", () => {
214
- targetSocket.destroy();
215
- });
216
- };
217
- }
218
- // ---------------------------------------------------------------------------
219
- // findFreePort
220
- // ---------------------------------------------------------------------------
221
- export function findFreePort() {
222
- return new Promise((resolve, reject) => {
223
- const server = net.createServer();
224
- server.unref();
225
- server.listen(0, "127.0.0.1", () => {
226
- const address = server.address();
227
- if (!address || typeof address === "string") {
228
- server.close();
229
- reject(new Error("Failed to get assigned port"));
230
- return;
231
- }
232
- const { port } = address;
233
- server.close((err) => {
234
- if (err) {
235
- reject(err);
236
- }
237
- else {
238
- resolve(port);
239
- }
240
- });
241
- });
242
- server.on("error", reject);
243
- });
244
- }
1
+ export { createScriptProxyMiddleware, createScriptProxyUpgradeHandler, findFreePort, ScriptRouteStore, } from "./service-proxy.js";
245
2
  //# sourceMappingURL=script-proxy.js.map
@@ -1,7 +1,7 @@
1
1
  import type { Logger } from "pino";
2
- import type { ScriptRouteStore } from "./script-proxy.js";
2
+ import type { ServiceProxySubsystem } from "./service-proxy.js";
3
3
  interface BranchChangeRouteHandlerOptions {
4
- routeStore: ScriptRouteStore;
4
+ serviceProxy: ServiceProxySubsystem;
5
5
  onRoutesChanged: (workspaceId: string) => void;
6
6
  logger?: Logger;
7
7
  }
@@ -1,44 +1,10 @@
1
- import { buildScriptHostname } from "../utils/script-hostname.js";
2
1
  export function createBranchChangeRouteHandler(options) {
3
2
  return (workspaceId, _oldBranch, newBranch) => {
4
- // Only service scripts register routes, so branch renames only touch services.
5
- const routes = options.routeStore.listRoutesForWorkspace(workspaceId);
6
- if (routes.length === 0) {
3
+ const changed = options.serviceProxy.replaceWorkspaceBranchRoutes({ workspaceId, newBranch });
4
+ if (!changed) {
7
5
  return;
8
6
  }
9
- const updates = [];
10
- for (const route of routes) {
11
- const newHostname = buildScriptHostname({
12
- projectSlug: route.projectSlug,
13
- branchName: newBranch,
14
- scriptName: route.scriptName,
15
- });
16
- if (newHostname !== route.hostname) {
17
- updates.push({
18
- oldHostname: route.hostname,
19
- newHostname,
20
- route,
21
- });
22
- }
23
- }
24
- if (updates.length === 0) {
25
- return;
26
- }
27
- for (const { oldHostname, newHostname, route } of updates) {
28
- options.routeStore.removeRoute(oldHostname);
29
- options.routeStore.registerRoute({
30
- hostname: newHostname,
31
- port: route.port,
32
- workspaceId: route.workspaceId,
33
- projectSlug: route.projectSlug,
34
- scriptName: route.scriptName,
35
- });
36
- options.logger?.info({
37
- oldHostname,
38
- newHostname,
39
- scriptName: route.scriptName,
40
- }, "Updated script route for branch rename");
41
- }
7
+ options.logger?.info({ workspaceId, newBranch }, "Updated service proxy routes for branch rename");
42
8
  options.onRoutesChanged(workspaceId);
43
9
  };
44
10
  }
@@ -2,7 +2,7 @@ import type { Logger } from "pino";
2
2
  import type { SessionOutboundMessage, WorkspaceScriptPayload } from "@getpaseo/protocol/messages";
3
3
  import type { PaseoConfig } from "@getpaseo/protocol/paseo-config-schema";
4
4
  import type { ScriptHealthEntry, ScriptHealthState } from "./script-health-monitor.js";
5
- import type { ScriptRouteStore } from "./script-proxy.js";
5
+ import type { ServiceProxySubsystem } from "./service-proxy.js";
6
6
  import type { WorkspaceScriptRuntimeStore } from "./workspace-script-runtime-store.js";
7
7
  interface SessionEmitter {
8
8
  emit(message: SessionOutboundMessage): void;
@@ -11,9 +11,10 @@ interface BuildWorkspaceScriptPayloadsOptions {
11
11
  workspaceId: string;
12
12
  workspaceDirectory: string;
13
13
  paseoConfig: PaseoConfig | null;
14
- routeStore: ScriptRouteStore;
14
+ serviceProxy: ServiceProxySubsystem;
15
15
  runtimeStore: WorkspaceScriptRuntimeStore;
16
16
  daemonPort: number | null;
17
+ serviceProxyPublicBaseUrl?: string | null;
17
18
  gitMetadata?: {
18
19
  projectSlug: string;
19
20
  currentBranch: string | null;
@@ -22,11 +23,12 @@ interface BuildWorkspaceScriptPayloadsOptions {
22
23
  }
23
24
  export declare function readPaseoConfigForProjection(workspaceDirectory: string, logger: Logger): PaseoConfig | null;
24
25
  export declare function buildWorkspaceScriptPayloads(options: BuildWorkspaceScriptPayloadsOptions): WorkspaceScriptPayload[];
25
- export declare function createScriptStatusEmitter({ sessions, routeStore, runtimeStore, daemonPort, resolveWorkspaceDirectory, logger, }: {
26
+ export declare function createScriptStatusEmitter({ sessions, serviceProxy, runtimeStore, daemonPort, serviceProxyPublicBaseUrl, resolveWorkspaceDirectory, logger, }: {
26
27
  sessions: () => SessionEmitter[];
27
- routeStore: ScriptRouteStore;
28
+ serviceProxy: ServiceProxySubsystem;
28
29
  runtimeStore: WorkspaceScriptRuntimeStore;
29
30
  daemonPort: number | null | (() => number | null);
31
+ serviceProxyPublicBaseUrl?: string | null;
30
32
  resolveWorkspaceDirectory: (workspaceId: string) => string | null | Promise<string | null>;
31
33
  logger: Logger;
32
34
  }): (workspaceId: string, scripts: ScriptHealthEntry[]) => void;