@getpaseo/server 0.1.88 → 0.1.90

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 (94) hide show
  1. package/dist/server/server/agent/agent-manager.js +4 -1
  2. package/dist/server/server/agent/agent-prompt.js +4 -1
  3. package/dist/server/server/agent/agent-sdk-types.d.ts +1 -0
  4. package/dist/server/server/agent/agent-storage.d.ts +22 -22
  5. package/dist/server/server/agent/agent-storage.js +2 -9
  6. package/dist/server/server/agent/create-agent/create.d.ts +2 -0
  7. package/dist/server/server/agent/create-agent/create.js +26 -7
  8. package/dist/server/server/agent/create-agent-lifecycle-dispatch.d.ts +1 -0
  9. package/dist/server/server/agent/create-agent-lifecycle-dispatch.js +4 -0
  10. package/dist/server/server/agent/create-agent-mode.d.ts +3 -8
  11. package/dist/server/server/agent/create-agent-mode.js +16 -2
  12. package/dist/server/server/agent/import-sessions.js +1 -1
  13. package/dist/server/server/agent/mcp-server.d.ts +1 -0
  14. package/dist/server/server/agent/mcp-server.js +113 -70
  15. package/dist/server/server/agent/provider-snapshot-manager.d.ts +2 -1
  16. package/dist/server/server/agent/provider-snapshot-manager.js +18 -2
  17. package/dist/server/server/agent/providers/acp-agent.d.ts +3 -3
  18. package/dist/server/server/agent/providers/acp-agent.js +18 -13
  19. package/dist/server/server/agent/providers/codex-app-server-agent.js +16 -22
  20. package/dist/server/server/agent/providers/mock-load-test-agent.d.ts +2 -0
  21. package/dist/server/server/agent/providers/mock-load-test-agent.js +69 -2
  22. package/dist/server/server/agent/providers/opencode-agent.js +19 -8
  23. package/dist/server/server/agent/providers/pi/agent.js +13 -0
  24. package/dist/server/server/agent/providers/pi/rpc-types.d.ts +3 -0
  25. package/dist/server/server/agent/timeline-projection.js +30 -1
  26. package/dist/server/server/atomic-file.d.ts +3 -0
  27. package/dist/server/server/atomic-file.js +19 -0
  28. package/dist/server/server/auto-archive-on-merge/archive-if-safe.d.ts +1 -0
  29. package/dist/server/server/auto-archive-on-merge/archive-if-safe.js +10 -2
  30. package/dist/server/server/bootstrap.d.ts +7 -2
  31. package/dist/server/server/bootstrap.js +154 -115
  32. package/dist/server/server/chat/chat-service.js +2 -4
  33. package/dist/server/server/config.js +41 -0
  34. package/dist/server/server/daemon-keypair.js +2 -2
  35. package/dist/server/server/loop-service.d.ts +26 -22
  36. package/dist/server/server/loop-service.js +27 -9
  37. package/dist/server/server/package-version.d.ts +2 -2
  38. package/dist/server/server/paseo-worktree-archive-service.d.ts +2 -0
  39. package/dist/server/server/paseo-worktree-archive-service.js +28 -9
  40. package/dist/server/server/persisted-config.d.ts +84 -28
  41. package/dist/server/server/persisted-config.js +20 -3
  42. package/dist/server/server/pid-lock.d.ts +2 -2
  43. package/dist/server/server/private-files.d.ts +0 -1
  44. package/dist/server/server/private-files.js +0 -5
  45. package/dist/server/server/schedule/service.d.ts +6 -0
  46. package/dist/server/server/schedule/service.js +41 -18
  47. package/dist/server/server/schedule/store.js +3 -2
  48. package/dist/server/server/script-health-monitor.d.ts +4 -4
  49. package/dist/server/server/script-health-monitor.js +6 -6
  50. package/dist/server/server/script-proxy.d.ts +2 -39
  51. package/dist/server/server/script-proxy.js +1 -244
  52. package/dist/server/server/script-route-branch-handler.d.ts +2 -2
  53. package/dist/server/server/script-route-branch-handler.js +3 -37
  54. package/dist/server/server/script-status-projection.d.ts +6 -4
  55. package/dist/server/server/script-status-projection.js +85 -37
  56. package/dist/server/server/server-id.js +3 -3
  57. package/dist/server/server/service-proxy.d.ts +237 -0
  58. package/dist/server/server/service-proxy.js +714 -0
  59. package/dist/server/server/session.d.ts +12 -18
  60. package/dist/server/server/session.js +206 -117
  61. package/dist/server/server/speech/providers/local/worker-client.js +1 -11
  62. package/dist/server/server/websocket-server.d.ts +7 -4
  63. package/dist/server/server/websocket-server.js +9 -4
  64. package/dist/server/server/workspace-bootstrap-dedupe.d.ts +34 -0
  65. package/dist/server/server/workspace-bootstrap-dedupe.js +23 -0
  66. package/dist/server/server/workspace-directory.d.ts +8 -0
  67. package/dist/server/server/workspace-directory.js +141 -11
  68. package/dist/server/server/workspace-git-service.d.ts +3 -0
  69. package/dist/server/server/workspace-git-service.js +53 -12
  70. package/dist/server/server/workspace-registry.d.ts +2 -2
  71. package/dist/server/server/workspace-registry.js +2 -6
  72. package/dist/server/server/workspace-service-env.d.ts +1 -0
  73. package/dist/server/server/workspace-service-env.js +23 -18
  74. package/dist/server/server/worktree/commands.d.ts +2 -0
  75. package/dist/server/server/worktree/commands.js +4 -1
  76. package/dist/server/server/worktree-bootstrap.d.ts +4 -3
  77. package/dist/server/server/worktree-bootstrap.js +14 -13
  78. package/dist/server/server/worktree-core.d.ts +1 -0
  79. package/dist/server/server/worktree-core.js +2 -0
  80. package/dist/server/server/worktree-session.d.ts +6 -2
  81. package/dist/server/server/worktree-session.js +3 -0
  82. package/dist/server/services/github-service.d.ts +1 -0
  83. package/dist/server/services/github-service.js +7 -1
  84. package/dist/server/utils/checkout-git.d.ts +6 -3
  85. package/dist/server/utils/checkout-git.js +40 -38
  86. package/dist/server/utils/worktree.d.ts +17 -12
  87. package/dist/server/utils/worktree.js +39 -22
  88. package/dist/src/server/persisted-config.js +20 -3
  89. package/dist/src/server/private-files.js +0 -5
  90. package/package.json +9 -7
  91. package/dist/server/server/editor-targets.d.ts +0 -18
  92. package/dist/server/server/editor-targets.js +0 -109
  93. package/dist/server/utils/script-hostname.d.ts +0 -8
  94. package/dist/server/utils/script-hostname.js +0 -14
@@ -7,15 +7,15 @@ export declare const pidLockInfoSchema: z.ZodObject<{
7
7
  listen: z.ZodNullable<z.ZodString>;
8
8
  desktopManaged: z.ZodOptional<z.ZodBoolean>;
9
9
  }, "strip", z.ZodTypeAny, {
10
- uid: number;
11
10
  hostname: string;
11
+ uid: number;
12
12
  startedAt: string;
13
13
  listen: string | null;
14
14
  pid: number;
15
15
  desktopManaged?: boolean | undefined;
16
16
  }, {
17
- uid: number;
18
17
  hostname: string;
18
+ uid: number;
19
19
  startedAt: string;
20
20
  listen: string | null;
21
21
  pid: number;
@@ -2,6 +2,5 @@ export declare const PRIVATE_DIRECTORY_MODE = 448;
2
2
  export declare const PRIVATE_FILE_MODE = 384;
3
3
  export declare function ensurePrivateDirectory(directoryPath: string): void;
4
4
  export declare function ensurePrivateFile(filePath: string): void;
5
- export declare function writePrivateFileSync(filePath: string, data: string | NodeJS.ArrayBufferView): void;
6
5
  export declare function writePrivateFileAtomicSync(filePath: string, data: string | NodeJS.ArrayBufferView): void;
7
6
  //# sourceMappingURL=private-files.d.ts.map
@@ -21,11 +21,6 @@ export function ensurePrivateDirectory(directoryPath) {
21
21
  export function ensurePrivateFile(filePath) {
22
22
  chmodBestEffort(filePath, PRIVATE_FILE_MODE);
23
23
  }
24
- export function writePrivateFileSync(filePath, data) {
25
- ensurePrivateDirectory(path.dirname(filePath));
26
- writeFileSync(filePath, data, { mode: PRIVATE_FILE_MODE });
27
- ensurePrivateFile(filePath);
28
- }
29
24
  export function writePrivateFileAtomicSync(filePath, data) {
30
25
  ensurePrivateDirectory(path.dirname(filePath));
31
26
  const tmpPath = path.join(path.dirname(filePath), `.${path.basename(filePath)}.${process.pid}.${randomUUID()}.tmp`);
@@ -1,12 +1,15 @@
1
1
  import type { Logger } from "pino";
2
2
  import { AgentManager } from "../agent/agent-manager.js";
3
3
  import type { AgentStorage } from "../agent/agent-storage.js";
4
+ import type { ProviderSnapshotManager } from "../agent/provider-snapshot-manager.js";
4
5
  import type { CreateScheduleInput, ScheduleExecutionResult, ScheduleRun, StoredSchedule, UpdateScheduleInput } from "@getpaseo/protocol/schedule/types";
6
+ type CreateConfigResolver = Pick<ProviderSnapshotManager, "resolveCreateConfig">;
5
7
  export interface ScheduleServiceOptions {
6
8
  paseoHome: string;
7
9
  logger: Logger;
8
10
  agentManager: AgentManager;
9
11
  agentStorage: AgentStorage;
12
+ providerSnapshotManager: CreateConfigResolver;
10
13
  now?: () => Date;
11
14
  runner?: (schedule: StoredSchedule, runId: string) => Promise<ScheduleExecutionResult>;
12
15
  }
@@ -15,6 +18,7 @@ export declare class ScheduleService {
15
18
  private readonly logger;
16
19
  private readonly agentManager;
17
20
  private readonly agentStorage;
21
+ private readonly createConfigResolver;
18
22
  private readonly now;
19
23
  private readonly runner;
20
24
  private readonly runningScheduleIds;
@@ -37,5 +41,7 @@ export declare class ScheduleService {
37
41
  private runSchedule;
38
42
  private finishRun;
39
43
  private executeSchedule;
44
+ private resolveProviderCreateConfig;
40
45
  }
46
+ export {};
41
47
  //# sourceMappingURL=service.d.ts.map
@@ -3,7 +3,7 @@ import { join } from "node:path";
3
3
  import { curateAgentActivity } from "../agent/activity-curator.js";
4
4
  import { ensureAgentLoaded } from "../agent/agent-loading.js";
5
5
  import { formatSystemNotificationPrompt } from "../agent/agent-prompt.js";
6
- import { getUnattendedModeId } from "@getpaseo/protocol/provider-manifest";
6
+ import { resolveCreateAgentTitles } from "../agent/create-agent-title.js";
7
7
  import { ScheduleStore } from "./store.js";
8
8
  import { computeNextRunAt, validateScheduleCadence } from "./cron.js";
9
9
  const SCHEDULE_TICK_INTERVAL_MS = 1000;
@@ -113,6 +113,7 @@ export class ScheduleService {
113
113
  this.logger = options.logger.child({ module: "schedule-service" });
114
114
  this.agentManager = options.agentManager;
115
115
  this.agentStorage = options.agentStorage;
116
+ this.createConfigResolver = options.providerSnapshotManager;
116
117
  this.now = options.now ?? (() => new Date());
117
118
  this.runner = options.runner ?? ((schedule, runId) => this.executeSchedule(schedule, runId));
118
119
  }
@@ -418,8 +419,8 @@ export class ScheduleService {
418
419
  await this.store.put(updated);
419
420
  }
420
421
  async executeSchedule(schedule, runId) {
421
- const wrappedPrompt = formatSystemNotificationPrompt(buildScheduleFireBody(schedule, runId));
422
422
  if (schedule.target.type === "agent") {
423
+ const wrappedPrompt = formatSystemNotificationPrompt(buildScheduleFireBody(schedule, runId));
423
424
  const record = await this.agentStorage.get(schedule.target.agentId);
424
425
  if (record?.archivedAt) {
425
426
  throw new Error(`Agent ${schedule.target.agentId} is archived`);
@@ -443,30 +444,49 @@ export class ScheduleService {
443
444
  }),
444
445
  };
445
446
  }
447
+ const targetConfig = schedule.target.config;
448
+ const resolvedUnattendedConfig = targetConfig.modeId
449
+ ? { modeId: targetConfig.modeId, featureValues: targetConfig.featureValues }
450
+ : await this.resolveProviderCreateConfig({
451
+ provider: targetConfig.provider,
452
+ cwd: targetConfig.cwd,
453
+ requestedMode: undefined,
454
+ featureValues: targetConfig.featureValues,
455
+ parent: null,
456
+ unattended: true,
457
+ });
446
458
  const config = {
447
- provider: schedule.target.config.provider,
448
- cwd: schedule.target.config.cwd,
449
- modeId: schedule.target.config.modeId ?? getUnattendedModeId(schedule.target.config.provider),
450
- model: schedule.target.config.model,
451
- thinkingOptionId: schedule.target.config.thinkingOptionId,
452
- title: schedule.target.config.title,
453
- approvalPolicy: schedule.target.config.approvalPolicy,
454
- sandboxMode: schedule.target.config.sandboxMode,
455
- networkAccess: schedule.target.config.networkAccess,
456
- webSearch: schedule.target.config.webSearch,
457
- featureValues: schedule.target.config.featureValues,
458
- extra: schedule.target.config.extra,
459
- systemPrompt: schedule.target.config.systemPrompt,
460
- mcpServers: schedule.target.config.mcpServers,
459
+ provider: targetConfig.provider,
460
+ cwd: targetConfig.cwd,
461
+ modeId: resolvedUnattendedConfig.modeId,
462
+ model: targetConfig.model,
463
+ thinkingOptionId: targetConfig.thinkingOptionId,
464
+ title: targetConfig.title,
465
+ approvalPolicy: targetConfig.approvalPolicy,
466
+ sandboxMode: targetConfig.sandboxMode,
467
+ networkAccess: targetConfig.networkAccess,
468
+ webSearch: targetConfig.webSearch,
469
+ featureValues: resolvedUnattendedConfig.featureValues,
470
+ extra: targetConfig.extra,
471
+ systemPrompt: targetConfig.systemPrompt,
472
+ mcpServers: targetConfig.mcpServers,
461
473
  };
474
+ const { provisionalTitle } = resolveCreateAgentTitles({
475
+ configTitle: config.title,
476
+ initialPrompt: schedule.prompt,
477
+ });
462
478
  const labels = {
463
479
  "paseo.schedule-id": schedule.id,
464
480
  "paseo.schedule-run": runId,
465
481
  };
466
- const agent = await this.agentManager.createAgent(config, undefined, { labels });
482
+ const agent = await this.agentManager.createAgent(config, undefined, {
483
+ labels,
484
+ initialPrompt: schedule.prompt,
485
+ initialTitle: provisionalTitle,
486
+ });
467
487
  let result;
468
488
  try {
469
- result = await this.agentManager.runAgent(agent.id, wrappedPrompt);
489
+ result = await this.agentManager.runAgent(agent.id, schedule.prompt);
470
490
  }
471
491
  catch (error) {
472
492
  try {
@@ -488,5 +508,8 @@ export class ScheduleService {
488
508
  }),
489
509
  };
490
510
  }
511
+ async resolveProviderCreateConfig(input) {
512
+ return this.createConfigResolver.resolveCreateConfig(input);
513
+ }
491
514
  }
492
515
  //# sourceMappingURL=service.js.map
@@ -1,7 +1,8 @@
1
1
  import { randomBytes } from "node:crypto";
2
- import { mkdir, readFile, readdir, rm, writeFile } from "node:fs/promises";
2
+ import { mkdir, readFile, readdir, rm } from "node:fs/promises";
3
3
  import { join } from "node:path";
4
4
  import { StoredScheduleSchema } from "@getpaseo/protocol/schedule/types";
5
+ import { writeJsonFileAtomic } from "../atomic-file.js";
5
6
  function generateScheduleId() {
6
7
  return randomBytes(4).toString("hex");
7
8
  }
@@ -46,7 +47,7 @@ export class ScheduleStore {
46
47
  }
47
48
  async put(schedule) {
48
49
  await this.ensureDir();
49
- await writeFile(this.filePath(schedule.id), JSON.stringify(schedule, null, 2), "utf-8");
50
+ await writeJsonFileAtomic(this.filePath(schedule.id), schedule);
50
51
  }
51
52
  async delete(id) {
52
53
  await this.ensureDir();
@@ -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;