@av-pi-studio/server 0.0.1
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/README.md +411 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/agent/agent-manager.d.ts +107 -0
- package/dist/agent/agent-manager.d.ts.map +1 -0
- package/dist/agent/agent-manager.js +218 -0
- package/dist/agent/agent-manager.js.map +1 -0
- package/dist/agent/agent-service.d.ts +36 -0
- package/dist/agent/agent-service.d.ts.map +1 -0
- package/dist/agent/agent-service.js +195 -0
- package/dist/agent/agent-service.js.map +1 -0
- package/dist/agent/index.d.ts +17 -0
- package/dist/agent/index.d.ts.map +1 -0
- package/dist/agent/index.js +18 -0
- package/dist/agent/index.js.map +1 -0
- package/dist/agent/manifest.d.ts +7 -0
- package/dist/agent/manifest.d.ts.map +1 -0
- package/dist/agent/manifest.js +27 -0
- package/dist/agent/manifest.js.map +1 -0
- package/dist/agent/mcp-server.d.ts +100 -0
- package/dist/agent/mcp-server.d.ts.map +1 -0
- package/dist/agent/mcp-server.js +163 -0
- package/dist/agent/mcp-server.js.map +1 -0
- package/dist/agent/permissions.d.ts +71 -0
- package/dist/agent/permissions.d.ts.map +1 -0
- package/dist/agent/permissions.js +132 -0
- package/dist/agent/permissions.js.map +1 -0
- package/dist/agent/provider-contract.d.ts +134 -0
- package/dist/agent/provider-contract.d.ts.map +1 -0
- package/dist/agent/provider-contract.js +2 -0
- package/dist/agent/provider-contract.js.map +1 -0
- package/dist/agent/provider-registry.d.ts +37 -0
- package/dist/agent/provider-registry.d.ts.map +1 -0
- package/dist/agent/provider-registry.js +92 -0
- package/dist/agent/provider-registry.js.map +1 -0
- package/dist/agent/provider-snapshot.d.ts +52 -0
- package/dist/agent/provider-snapshot.d.ts.map +1 -0
- package/dist/agent/provider-snapshot.js +73 -0
- package/dist/agent/provider-snapshot.js.map +1 -0
- package/dist/agent/providers/mock/mock-provider.d.ts +44 -0
- package/dist/agent/providers/mock/mock-provider.d.ts.map +1 -0
- package/dist/agent/providers/mock/mock-provider.js +162 -0
- package/dist/agent/providers/mock/mock-provider.js.map +1 -0
- package/dist/agent/providers/pi/agent.d.ts +80 -0
- package/dist/agent/providers/pi/agent.d.ts.map +1 -0
- package/dist/agent/providers/pi/agent.js +322 -0
- package/dist/agent/providers/pi/agent.js.map +1 -0
- package/dist/agent/providers/pi/event-mapper.d.ts +17 -0
- package/dist/agent/providers/pi/event-mapper.d.ts.map +1 -0
- package/dist/agent/providers/pi/event-mapper.js +150 -0
- package/dist/agent/providers/pi/event-mapper.js.map +1 -0
- package/dist/agent/providers/pi/rpc-transport.d.ts +53 -0
- package/dist/agent/providers/pi/rpc-transport.d.ts.map +1 -0
- package/dist/agent/providers/pi/rpc-transport.js +190 -0
- package/dist/agent/providers/pi/rpc-transport.js.map +1 -0
- package/dist/agent/providers/pi/session-hydration.d.ts +11 -0
- package/dist/agent/providers/pi/session-hydration.d.ts.map +1 -0
- package/dist/agent/providers/pi/session-hydration.js +167 -0
- package/dist/agent/providers/pi/session-hydration.js.map +1 -0
- package/dist/agent/rewind-rpc.d.ts +15 -0
- package/dist/agent/rewind-rpc.d.ts.map +1 -0
- package/dist/agent/rewind-rpc.js +30 -0
- package/dist/agent/rewind-rpc.js.map +1 -0
- package/dist/agent/session-operations.d.ts +32 -0
- package/dist/agent/session-operations.d.ts.map +1 -0
- package/dist/agent/session-operations.js +130 -0
- package/dist/agent/session-operations.js.map +1 -0
- package/dist/agent/structured-generation.d.ts +39 -0
- package/dist/agent/structured-generation.d.ts.map +1 -0
- package/dist/agent/structured-generation.js +78 -0
- package/dist/agent/structured-generation.js.map +1 -0
- package/dist/agent/timeline-rpc.d.ts +21 -0
- package/dist/agent/timeline-rpc.d.ts.map +1 -0
- package/dist/agent/timeline-rpc.js +59 -0
- package/dist/agent/timeline-rpc.js.map +1 -0
- package/dist/agent/timeline-store.d.ts +100 -0
- package/dist/agent/timeline-store.d.ts.map +1 -0
- package/dist/agent/timeline-store.js +255 -0
- package/dist/agent/timeline-store.js.map +1 -0
- package/dist/auth/index.d.ts +2 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +3 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/password-auth.d.ts +44 -0
- package/dist/auth/password-auth.d.ts.map +1 -0
- package/dist/auth/password-auth.js +78 -0
- package/dist/auth/password-auth.js.map +1 -0
- package/dist/config/daemon-config.d.ts +634 -0
- package/dist/config/daemon-config.d.ts.map +1 -0
- package/dist/config/daemon-config.js +258 -0
- package/dist/config/daemon-config.js.map +1 -0
- package/dist/config/index.d.ts +3 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +4 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/project-config.d.ts +104 -0
- package/dist/config/project-config.d.ts.map +1 -0
- package/dist/config/project-config.js +106 -0
- package/dist/config/project-config.js.map +1 -0
- package/dist/daemon/bootstrap.d.ts +37 -0
- package/dist/daemon/bootstrap.d.ts.map +1 -0
- package/dist/daemon/bootstrap.js +448 -0
- package/dist/daemon/bootstrap.js.map +1 -0
- package/dist/daemon/dev-bootstrap.d.ts +14 -0
- package/dist/daemon/dev-bootstrap.d.ts.map +1 -0
- package/dist/daemon/dev-bootstrap.js +231 -0
- package/dist/daemon/dev-bootstrap.js.map +1 -0
- package/dist/daemon/dev-main.d.ts +3 -0
- package/dist/daemon/dev-main.d.ts.map +1 -0
- package/dist/daemon/dev-main.js +36 -0
- package/dist/daemon/dev-main.js.map +1 -0
- package/dist/daemon/index.d.ts +15 -0
- package/dist/daemon/index.d.ts.map +1 -0
- package/dist/daemon/index.js +21 -0
- package/dist/daemon/index.js.map +1 -0
- package/dist/daemon/main.d.ts +3 -0
- package/dist/daemon/main.d.ts.map +1 -0
- package/dist/daemon/main.js +29 -0
- package/dist/daemon/main.js.map +1 -0
- package/dist/daemon/orchestration-rpc.d.ts +19 -0
- package/dist/daemon/orchestration-rpc.d.ts.map +1 -0
- package/dist/daemon/orchestration-rpc.js +136 -0
- package/dist/daemon/orchestration-rpc.js.map +1 -0
- package/dist/files/download-token-store.d.ts +14 -0
- package/dist/files/download-token-store.d.ts.map +1 -0
- package/dist/files/download-token-store.js +33 -0
- package/dist/files/download-token-store.js.map +1 -0
- package/dist/files/file-explorer.d.ts +54 -0
- package/dist/files/file-explorer.d.ts.map +1 -0
- package/dist/files/file-explorer.js +182 -0
- package/dist/files/file-explorer.js.map +1 -0
- package/dist/files/file-transfer.d.ts +30 -0
- package/dist/files/file-transfer.d.ts.map +1 -0
- package/dist/files/file-transfer.js +132 -0
- package/dist/files/file-transfer.js.map +1 -0
- package/dist/files/index.d.ts +4 -0
- package/dist/files/index.d.ts.map +1 -0
- package/dist/files/index.js +5 -0
- package/dist/files/index.js.map +1 -0
- package/dist/http/host-allowlist.d.ts +15 -0
- package/dist/http/host-allowlist.d.ts.map +1 -0
- package/dist/http/host-allowlist.js +52 -0
- package/dist/http/host-allowlist.js.map +1 -0
- package/dist/http/http-server.d.ts +27 -0
- package/dist/http/http-server.d.ts.map +1 -0
- package/dist/http/http-server.js +76 -0
- package/dist/http/http-server.js.map +1 -0
- package/dist/http/index.d.ts +3 -0
- package/dist/http/index.d.ts.map +1 -0
- package/dist/http/index.js +4 -0
- package/dist/http/index.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +35 -0
- package/dist/index.js.map +1 -0
- package/dist/logging/index.d.ts +2 -0
- package/dist/logging/index.d.ts.map +1 -0
- package/dist/logging/index.js +2 -0
- package/dist/logging/index.js.map +1 -0
- package/dist/logging/logger.d.ts +48 -0
- package/dist/logging/logger.d.ts.map +1 -0
- package/dist/logging/logger.js +53 -0
- package/dist/logging/logger.js.map +1 -0
- package/dist/orchestration/chat-service.d.ts +61 -0
- package/dist/orchestration/chat-service.d.ts.map +1 -0
- package/dist/orchestration/chat-service.js +156 -0
- package/dist/orchestration/chat-service.js.map +1 -0
- package/dist/orchestration/cron.d.ts +30 -0
- package/dist/orchestration/cron.d.ts.map +1 -0
- package/dist/orchestration/cron.js +136 -0
- package/dist/orchestration/cron.js.map +1 -0
- package/dist/orchestration/index.d.ts +5 -0
- package/dist/orchestration/index.d.ts.map +1 -0
- package/dist/orchestration/index.js +6 -0
- package/dist/orchestration/index.js.map +1 -0
- package/dist/orchestration/loop-service.d.ts +129 -0
- package/dist/orchestration/loop-service.d.ts.map +1 -0
- package/dist/orchestration/loop-service.js +234 -0
- package/dist/orchestration/loop-service.js.map +1 -0
- package/dist/orchestration/schedule-service.d.ts +65 -0
- package/dist/orchestration/schedule-service.d.ts.map +1 -0
- package/dist/orchestration/schedule-service.js +201 -0
- package/dist/orchestration/schedule-service.js.map +1 -0
- package/dist/persistence/atomic-store.d.ts +45 -0
- package/dist/persistence/atomic-store.d.ts.map +1 -0
- package/dist/persistence/atomic-store.js +92 -0
- package/dist/persistence/atomic-store.js.map +1 -0
- package/dist/persistence/entity-schemas.d.ts +1707 -0
- package/dist/persistence/entity-schemas.d.ts.map +1 -0
- package/dist/persistence/entity-schemas.js +250 -0
- package/dist/persistence/entity-schemas.js.map +1 -0
- package/dist/persistence/entity-stores.d.ts +46 -0
- package/dist/persistence/entity-stores.d.ts.map +1 -0
- package/dist/persistence/entity-stores.js +167 -0
- package/dist/persistence/entity-stores.js.map +1 -0
- package/dist/persistence/index.d.ts +4 -0
- package/dist/persistence/index.d.ts.map +1 -0
- package/dist/persistence/index.js +5 -0
- package/dist/persistence/index.js.map +1 -0
- package/dist/projects/checkout-diff-manager.d.ts +44 -0
- package/dist/projects/checkout-diff-manager.d.ts.map +1 -0
- package/dist/projects/checkout-diff-manager.js +82 -0
- package/dist/projects/checkout-diff-manager.js.map +1 -0
- package/dist/projects/git-checkout-rpc.d.ts +18 -0
- package/dist/projects/git-checkout-rpc.d.ts.map +1 -0
- package/dist/projects/git-checkout-rpc.js +57 -0
- package/dist/projects/git-checkout-rpc.js.map +1 -0
- package/dist/projects/git-detect.d.ts +21 -0
- package/dist/projects/git-detect.d.ts.map +1 -0
- package/dist/projects/git-detect.js +34 -0
- package/dist/projects/git-detect.js.map +1 -0
- package/dist/projects/git-operations.d.ts +67 -0
- package/dist/projects/git-operations.d.ts.map +1 -0
- package/dist/projects/git-operations.js +200 -0
- package/dist/projects/git-operations.js.map +1 -0
- package/dist/projects/github-service.d.ts +80 -0
- package/dist/projects/github-service.d.ts.map +1 -0
- package/dist/projects/github-service.js +172 -0
- package/dist/projects/github-service.js.map +1 -0
- package/dist/projects/index.d.ts +13 -0
- package/dist/projects/index.d.ts.map +1 -0
- package/dist/projects/index.js +19 -0
- package/dist/projects/index.js.map +1 -0
- package/dist/projects/open-project.d.ts +35 -0
- package/dist/projects/open-project.d.ts.map +1 -0
- package/dist/projects/open-project.js +106 -0
- package/dist/projects/open-project.js.map +1 -0
- package/dist/projects/reconciliation.d.ts +15 -0
- package/dist/projects/reconciliation.d.ts.map +1 -0
- package/dist/projects/reconciliation.js +63 -0
- package/dist/projects/reconciliation.js.map +1 -0
- package/dist/projects/status-projection.d.ts +33 -0
- package/dist/projects/status-projection.d.ts.map +1 -0
- package/dist/projects/status-projection.js +98 -0
- package/dist/projects/status-projection.js.map +1 -0
- package/dist/projects/workspace-activity.d.ts +28 -0
- package/dist/projects/workspace-activity.d.ts.map +1 -0
- package/dist/projects/workspace-activity.js +74 -0
- package/dist/projects/workspace-activity.js.map +1 -0
- package/dist/projects/workspace-git-service.d.ts +27 -0
- package/dist/projects/workspace-git-service.d.ts.map +1 -0
- package/dist/projects/workspace-git-service.js +60 -0
- package/dist/projects/workspace-git-service.js.map +1 -0
- package/dist/projects/workspace-registry.d.ts +59 -0
- package/dist/projects/workspace-registry.d.ts.map +1 -0
- package/dist/projects/workspace-registry.js +149 -0
- package/dist/projects/workspace-registry.js.map +1 -0
- package/dist/projects/worktree-service.d.ts +100 -0
- package/dist/projects/worktree-service.d.ts.map +1 -0
- package/dist/projects/worktree-service.js +216 -0
- package/dist/projects/worktree-service.js.map +1 -0
- package/dist/proxy/index.d.ts +4 -0
- package/dist/proxy/index.d.ts.map +1 -0
- package/dist/proxy/index.js +5 -0
- package/dist/proxy/index.js.map +1 -0
- package/dist/proxy/service-hostname.d.ts +21 -0
- package/dist/proxy/service-hostname.d.ts.map +1 -0
- package/dist/proxy/service-hostname.js +44 -0
- package/dist/proxy/service-hostname.js.map +1 -0
- package/dist/proxy/service-port-registry.d.ts +36 -0
- package/dist/proxy/service-port-registry.d.ts.map +1 -0
- package/dist/proxy/service-port-registry.js +55 -0
- package/dist/proxy/service-port-registry.js.map +1 -0
- package/dist/proxy/service-proxy.d.ts +34 -0
- package/dist/proxy/service-proxy.d.ts.map +1 -0
- package/dist/proxy/service-proxy.js +54 -0
- package/dist/proxy/service-proxy.js.map +1 -0
- package/dist/terminal/index.d.ts +4 -0
- package/dist/terminal/index.d.ts.map +1 -0
- package/dist/terminal/index.js +5 -0
- package/dist/terminal/index.js.map +1 -0
- package/dist/terminal/pty-backend.d.ts +44 -0
- package/dist/terminal/pty-backend.d.ts.map +1 -0
- package/dist/terminal/pty-backend.js +119 -0
- package/dist/terminal/pty-backend.js.map +1 -0
- package/dist/terminal/screen-buffer.d.ts +21 -0
- package/dist/terminal/screen-buffer.d.ts.map +1 -0
- package/dist/terminal/screen-buffer.js +55 -0
- package/dist/terminal/screen-buffer.js.map +1 -0
- package/dist/terminal/terminal-manager.d.ts +79 -0
- package/dist/terminal/terminal-manager.d.ts.map +1 -0
- package/dist/terminal/terminal-manager.js +174 -0
- package/dist/terminal/terminal-manager.js.map +1 -0
- package/dist/terminal/terminal-rpc.d.ts +20 -0
- package/dist/terminal/terminal-rpc.d.ts.map +1 -0
- package/dist/terminal/terminal-rpc.js +122 -0
- package/dist/terminal/terminal-rpc.js.map +1 -0
- package/dist/util/concurrency.d.ts +16 -0
- package/dist/util/concurrency.d.ts.map +1 -0
- package/dist/util/concurrency.js +23 -0
- package/dist/util/concurrency.js.map +1 -0
- package/dist/util/index.d.ts +2 -0
- package/dist/util/index.d.ts.map +1 -0
- package/dist/util/index.js +2 -0
- package/dist/util/index.js.map +1 -0
- package/dist/ws/capability-store.d.ts +11 -0
- package/dist/ws/capability-store.d.ts.map +1 -0
- package/dist/ws/capability-store.js +10 -0
- package/dist/ws/capability-store.js.map +1 -0
- package/dist/ws/index.d.ts +5 -0
- package/dist/ws/index.d.ts.map +1 -0
- package/dist/ws/index.js +6 -0
- package/dist/ws/index.js.map +1 -0
- package/dist/ws/router.d.ts +34 -0
- package/dist/ws/router.d.ts.map +1 -0
- package/dist/ws/router.js +97 -0
- package/dist/ws/router.js.map +1 -0
- package/dist/ws/session.d.ts +28 -0
- package/dist/ws/session.d.ts.map +1 -0
- package/dist/ws/session.js +36 -0
- package/dist/ws/session.js.map +1 -0
- package/dist/ws/ws-server.d.ts +41 -0
- package/dist/ws/ws-server.d.ts.map +1 -0
- package/dist/ws/ws-server.js +104 -0
- package/dist/ws/ws-server.js.map +1 -0
- package/package.json +48 -0
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { defaultGitRunner } from "./git-detect.js";
|
|
4
|
+
import { normalizePath, WorkspaceRegistryService } from "./workspace-registry.js";
|
|
5
|
+
/**
|
|
6
|
+
* Pi-Studio-managed git worktree service (features/worktrees.md § Operations, § Behavior,
|
|
7
|
+
* § Lifecycle config; architecture/agent-lifecycle.md § Archive). Handles create/setup, archive/
|
|
8
|
+
* teardown, and the `autoArchive`+`worktree` coupling.
|
|
9
|
+
*/
|
|
10
|
+
/** Agent label naming the worktree workspace it lives in (for auto-archive coupling). */
|
|
11
|
+
export const WORKTREE_WORKSPACE_ID_LABEL = "pi-studio.worktree-workspace-id";
|
|
12
|
+
/** Agent label flagging auto-archive after the first terminal turn. */
|
|
13
|
+
export const AUTO_ARCHIVE_LABEL = "pi-studio.auto-archive";
|
|
14
|
+
const MAX_WORKTREE_NAME = 60;
|
|
15
|
+
/** Slug a branch/worktree name: lowercase, keep `[a-z0-9-/]`, collapse separators. */
|
|
16
|
+
export function slugBranchName(name) {
|
|
17
|
+
const slug = name
|
|
18
|
+
.toLowerCase()
|
|
19
|
+
.replace(/[^a-z0-9/\s_-]/g, "")
|
|
20
|
+
.replace(/[\s_]+/g, "-")
|
|
21
|
+
.replace(/\/+/g, "/")
|
|
22
|
+
.replace(/-{2,}/g, "-")
|
|
23
|
+
.replace(/^[-/]+|[-/]+$/g, "");
|
|
24
|
+
return slug || "work";
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Resolve a unique, length-bounded worktree name. Over-long names are truncated and a deterministic
|
|
28
|
+
* 7-char hash suffix is appended; colliding names get a deterministic suffix that increments a salt
|
|
29
|
+
* until unique within `existing`.
|
|
30
|
+
*/
|
|
31
|
+
export function resolveWorktreeName(rawName, existing, maxLen = MAX_WORKTREE_NAME) {
|
|
32
|
+
const base = slugBranchName(rawName);
|
|
33
|
+
if (base.length <= maxLen && !existing.has(base))
|
|
34
|
+
return base;
|
|
35
|
+
const room = Math.max(1, maxLen - 8); // 1 sep + 7 hash chars
|
|
36
|
+
const truncated = base.slice(0, room).replace(/[-/]+$/, "");
|
|
37
|
+
for (let salt = 0; salt < 1000; salt++) {
|
|
38
|
+
const hash = createHash("sha256").update(`${rawName}:${salt}`).digest("hex").slice(0, 7);
|
|
39
|
+
const candidate = `${truncated}-${hash}`;
|
|
40
|
+
if (!existing.has(candidate))
|
|
41
|
+
return candidate;
|
|
42
|
+
}
|
|
43
|
+
// Extremely unlikely fallthrough.
|
|
44
|
+
return `${truncated}-${createHash("sha256").update(rawName).digest("hex").slice(0, 12)}`;
|
|
45
|
+
}
|
|
46
|
+
export class WorktreeService {
|
|
47
|
+
deps;
|
|
48
|
+
registry;
|
|
49
|
+
git;
|
|
50
|
+
runCommand;
|
|
51
|
+
constructor(deps) {
|
|
52
|
+
this.deps = deps;
|
|
53
|
+
this.registry = deps.registry ?? new WorkspaceRegistryService(deps.home);
|
|
54
|
+
this.git = deps.gitRunner ?? defaultGitRunner;
|
|
55
|
+
this.runCommand = deps.runCommand ?? (async () => ({ code: 0, stdout: "", stderr: "" }));
|
|
56
|
+
}
|
|
57
|
+
registerHandlers(registry, getActiveSessions) {
|
|
58
|
+
registry.register("create_pistudio_worktree_request", async (ctx) => {
|
|
59
|
+
const descriptor = await this.createWorktree({
|
|
60
|
+
projectRoot: String(ctx.message.projectRoot ?? ctx.message.cwd ?? ""),
|
|
61
|
+
projectId: String(ctx.message.projectId ?? ""),
|
|
62
|
+
name: ctx.message.name,
|
|
63
|
+
branch: ctx.message.branch,
|
|
64
|
+
baseRef: ctx.message.baseRef,
|
|
65
|
+
generatePrompt: ctx.message.generatePrompt,
|
|
66
|
+
}, getActiveSessions);
|
|
67
|
+
return { type: "create_pistudio_worktree_response", worktree: descriptor };
|
|
68
|
+
});
|
|
69
|
+
registry.register("pistudio_worktree_list_request", async () => ({
|
|
70
|
+
type: "pistudio_worktree_list_response",
|
|
71
|
+
worktrees: (await this.registry.listActiveWorkspaces()).filter((w) => w.kind === "worktree"),
|
|
72
|
+
}));
|
|
73
|
+
registry.register("pistudio_worktree_archive_request", async (ctx) => {
|
|
74
|
+
const workspaceId = String(ctx.message.workspaceId ?? "");
|
|
75
|
+
const ok = await this.archiveWorktree(workspaceId, getActiveSessions);
|
|
76
|
+
return { type: "pistudio_worktree_archive_response", workspaceId, ok };
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
worktreeRoot() {
|
|
80
|
+
return this.deps.worktreeRoot ?? join(this.deps.home, "worktrees");
|
|
81
|
+
}
|
|
82
|
+
/** Create a Pi-Studio worktree, register it, and run `setup`. */
|
|
83
|
+
async createWorktree(intent, getActiveSessions = this.deps.getActiveSessions ?? (() => [])) {
|
|
84
|
+
const root = this.worktreeRoot();
|
|
85
|
+
// Existing worktree names occupied under the root (from the active registry).
|
|
86
|
+
const occupied = new Set((await this.registry.listActiveWorkspaces())
|
|
87
|
+
.filter((w) => w.kind === "worktree")
|
|
88
|
+
.map((w) => basename(w.cwd)));
|
|
89
|
+
// Resolve target branch + worktree name.
|
|
90
|
+
let branch;
|
|
91
|
+
let isNewBranch;
|
|
92
|
+
if (intent.branch && intent.branch.trim().length > 0) {
|
|
93
|
+
branch = intent.branch.trim();
|
|
94
|
+
isNewBranch = false;
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
const raw = intent.name?.trim() ||
|
|
98
|
+
(intent.generatePrompt && this.deps.generateBranchName
|
|
99
|
+
? await this.deps.generateBranchName(intent.generatePrompt)
|
|
100
|
+
: "") ||
|
|
101
|
+
intent.generatePrompt ||
|
|
102
|
+
"work";
|
|
103
|
+
branch = slugBranchName(raw);
|
|
104
|
+
isNewBranch = true;
|
|
105
|
+
}
|
|
106
|
+
const name = resolveWorktreeName(intent.name?.trim() || branch, occupied);
|
|
107
|
+
const path = join(root, name);
|
|
108
|
+
const args = isNewBranch
|
|
109
|
+
? ["worktree", "add", "-b", branch, path, ...(intent.baseRef ? [intent.baseRef] : [])]
|
|
110
|
+
: ["worktree", "add", path, branch];
|
|
111
|
+
const added = await this.git(args, intent.projectRoot);
|
|
112
|
+
if (added.code !== 0) {
|
|
113
|
+
throw new Error(`git worktree add failed: ${added.stdout}`.trim());
|
|
114
|
+
}
|
|
115
|
+
const { workspace } = await this.registry.resolveOrCreateWorkspace({
|
|
116
|
+
projectId: intent.projectId,
|
|
117
|
+
cwd: path,
|
|
118
|
+
kind: "worktree",
|
|
119
|
+
displayName: name,
|
|
120
|
+
});
|
|
121
|
+
const setupOk = await this.runLifecycle("setup", intent.projectRoot, path, getActiveSessions);
|
|
122
|
+
this.deps.broadcast(getActiveSessions(), {
|
|
123
|
+
type: "workspace_update",
|
|
124
|
+
workspaceId: workspace.workspaceId,
|
|
125
|
+
projectId: workspace.projectId,
|
|
126
|
+
cwd: workspace.cwd,
|
|
127
|
+
kind: "worktree",
|
|
128
|
+
displayName: workspace.displayName,
|
|
129
|
+
archivedAt: null,
|
|
130
|
+
branch,
|
|
131
|
+
});
|
|
132
|
+
return { workspace, name, branch, path, setupOk };
|
|
133
|
+
}
|
|
134
|
+
/** Archive a worktree: teardown → archive contained agents → `git worktree remove`/prune. */
|
|
135
|
+
async archiveWorktree(workspaceId, getActiveSessions = this.deps.getActiveSessions ?? (() => [])) {
|
|
136
|
+
const workspaces = await this.registry.listWorkspaces();
|
|
137
|
+
const workspace = workspaces.find((w) => w.workspaceId === workspaceId);
|
|
138
|
+
if (!workspace || workspace.archivedAt)
|
|
139
|
+
return false;
|
|
140
|
+
const path = workspace.cwd;
|
|
141
|
+
await this.runLifecycle("teardown", null, path, getActiveSessions);
|
|
142
|
+
// Archive every agent whose cwd is inside the worktree.
|
|
143
|
+
const inside = (this.deps.listAgents?.() ?? []).filter((a) => isInside(path, a.cwd));
|
|
144
|
+
for (const agent of inside) {
|
|
145
|
+
await this.deps.archiveAgent?.(agent.id);
|
|
146
|
+
}
|
|
147
|
+
// Remove + prune the git worktree (best-effort; ignore failure so the record still archives).
|
|
148
|
+
await this.git(["worktree", "remove", "--force", path], path).catch(() => undefined);
|
|
149
|
+
await this.git(["worktree", "prune"], path).catch(() => undefined);
|
|
150
|
+
const archived = await this.registry.archiveWorkspace(workspaceId);
|
|
151
|
+
if (archived) {
|
|
152
|
+
this.deps.broadcast(getActiveSessions(), {
|
|
153
|
+
type: "workspace_update",
|
|
154
|
+
workspaceId,
|
|
155
|
+
archivedAt: archived.archivedAt,
|
|
156
|
+
kind: "worktree",
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
return true;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Auto-archive coupling: when an agent created with `autoArchive` + a `worktree` target reaches a
|
|
163
|
+
* terminal turn, archive the agent AND its worktree (which cascades to any agents inside it).
|
|
164
|
+
* Returns true when an auto-archive was performed.
|
|
165
|
+
*/
|
|
166
|
+
async maybeAutoArchiveOnTerminalTurn(agent, getActiveSessions = this.deps.getActiveSessions ?? (() => [])) {
|
|
167
|
+
const autoArchive = agent.labels[AUTO_ARCHIVE_LABEL] === "true";
|
|
168
|
+
const worktreeId = agent.labels[WORKTREE_WORKSPACE_ID_LABEL];
|
|
169
|
+
if (!autoArchive || !worktreeId)
|
|
170
|
+
return false;
|
|
171
|
+
return this.archiveWorktree(worktreeId, getActiveSessions);
|
|
172
|
+
}
|
|
173
|
+
async runLifecycle(phase, sourceCheckoutPath, worktreePath, getActiveSessions) {
|
|
174
|
+
const lifecycle = this.deps.readWorktreeLifecycle
|
|
175
|
+
? await this.deps.readWorktreeLifecycle(sourceCheckoutPath ?? worktreePath)
|
|
176
|
+
: { setup: [], teardown: [] };
|
|
177
|
+
const commands = lifecycle[phase];
|
|
178
|
+
if (commands.length === 0)
|
|
179
|
+
return true;
|
|
180
|
+
const env = {
|
|
181
|
+
PI_STUDIO_WORKTREE_PATH: worktreePath,
|
|
182
|
+
PI_STUDIO_SOURCE_CHECKOUT_PATH: sourceCheckoutPath ?? worktreePath,
|
|
183
|
+
};
|
|
184
|
+
let ok = true;
|
|
185
|
+
for (let i = 0; i < commands.length; i++) {
|
|
186
|
+
const command = commands[i];
|
|
187
|
+
const result = await this.runCommand(command, { cwd: worktreePath, env });
|
|
188
|
+
this.deps.broadcast(getActiveSessions(), {
|
|
189
|
+
type: "workspace_setup_progress",
|
|
190
|
+
worktreePath,
|
|
191
|
+
phase,
|
|
192
|
+
index: i,
|
|
193
|
+
total: commands.length,
|
|
194
|
+
command,
|
|
195
|
+
code: result.code,
|
|
196
|
+
});
|
|
197
|
+
if (result.code !== 0) {
|
|
198
|
+
ok = false;
|
|
199
|
+
break; // surface the failure; worktree remains for inspection
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return ok;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
function basename(p) {
|
|
206
|
+
const norm = normalizePath(p);
|
|
207
|
+
const parts = norm.split("/");
|
|
208
|
+
return parts[parts.length - 1] || norm;
|
|
209
|
+
}
|
|
210
|
+
/** True when `child` is the worktree path or nested under it. */
|
|
211
|
+
function isInside(root, child) {
|
|
212
|
+
const r = normalizePath(root);
|
|
213
|
+
const c = normalizePath(child);
|
|
214
|
+
return c === r || c.startsWith(`${r}/`);
|
|
215
|
+
}
|
|
216
|
+
//# sourceMappingURL=worktree-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worktree-service.js","sourceRoot":"","sources":["../../src/projects/worktree-service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/proxy/index.ts"],"names":[],"mappings":"AACA,cAAc,uBAAuB,CAAC;AACtC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,oBAAoB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/proxy/index.ts"],"names":[],"mappings":"AAAA,iFAAiF;AACjF,cAAc,uBAAuB,CAAC;AACtC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,oBAAoB,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/** Slug a hostname segment: lowercase, `[a-z0-9-]`, collapse + trim separators. */
|
|
2
|
+
export declare function slugSegment(value: string): string;
|
|
3
|
+
/** Build the combined leftmost label `script--branch--project` (branch omitted for main/master). */
|
|
4
|
+
export declare function serviceLabel(args: {
|
|
5
|
+
script: string;
|
|
6
|
+
branch?: string | null;
|
|
7
|
+
project: string;
|
|
8
|
+
}): string;
|
|
9
|
+
/** Full localhost hostname for a service. */
|
|
10
|
+
export declare function serviceHostname(args: {
|
|
11
|
+
script: string;
|
|
12
|
+
branch?: string | null;
|
|
13
|
+
project: string;
|
|
14
|
+
}): string;
|
|
15
|
+
/** Public hostname alias built from a `publicBaseUrl` (the combined label becomes the subdomain). */
|
|
16
|
+
export declare function publicServiceHostname(args: {
|
|
17
|
+
script: string;
|
|
18
|
+
branch?: string | null;
|
|
19
|
+
project: string;
|
|
20
|
+
}, publicBaseUrl: string): string;
|
|
21
|
+
//# sourceMappingURL=service-hostname.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service-hostname.d.ts","sourceRoot":"","sources":["../../src/proxy/service-hostname.ts"],"names":[],"mappings":"AAYA,mFAAmF;AACnF,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAQjD;AAED,oGAAoG;AACpG,wBAAgB,YAAY,CAAC,IAAI,EAAE;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;CACjB,GAAG,MAAM,CAOT;AAUD,6CAA6C;AAC7C,wBAAgB,eAAe,CAAC,IAAI,EAAE;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;CACjB,GAAG,MAAM,CAET;AAED,qGAAqG;AACrG,wBAAgB,qBAAqB,CACnC,IAAI,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,EACjE,aAAa,EAAE,MAAM,GACpB,MAAM,CAGR"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
/**
|
|
3
|
+
* Generated service hostnames (features/service-proxy.md § Generated hostname). One combined
|
|
4
|
+
* leftmost label `script--branch--project` keeps it single-level-wildcard friendly. The branch
|
|
5
|
+
* segment is omitted for main/master. Over-63-char labels are truncated with a deterministic hash
|
|
6
|
+
* suffix.
|
|
7
|
+
*/
|
|
8
|
+
const MAX_LABEL = 63;
|
|
9
|
+
const MAIN_BRANCHES = new Set(["main", "master"]);
|
|
10
|
+
/** Slug a hostname segment: lowercase, `[a-z0-9-]`, collapse + trim separators. */
|
|
11
|
+
export function slugSegment(value) {
|
|
12
|
+
return (value
|
|
13
|
+
.toLowerCase()
|
|
14
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
15
|
+
.replace(/-{2,}/g, "-")
|
|
16
|
+
.replace(/^-+|-+$/g, "") || "x");
|
|
17
|
+
}
|
|
18
|
+
/** Build the combined leftmost label `script--branch--project` (branch omitted for main/master). */
|
|
19
|
+
export function serviceLabel(args) {
|
|
20
|
+
const script = slugSegment(args.script);
|
|
21
|
+
const project = slugSegment(args.project);
|
|
22
|
+
const branch = args.branch ? slugSegment(args.branch) : "";
|
|
23
|
+
const omitBranch = !branch || MAIN_BRANCHES.has(branch);
|
|
24
|
+
const label = omitBranch ? `${script}--${project}` : `${script}--${branch}--${project}`;
|
|
25
|
+
return clampLabel(label);
|
|
26
|
+
}
|
|
27
|
+
/** Truncate an over-63-char label and append a deterministic 7-char hash suffix. */
|
|
28
|
+
function clampLabel(label) {
|
|
29
|
+
if (label.length <= MAX_LABEL)
|
|
30
|
+
return label;
|
|
31
|
+
const hash = createHash("sha256").update(label).digest("hex").slice(0, 7);
|
|
32
|
+
const head = label.slice(0, MAX_LABEL - 8).replace(/-+$/, "");
|
|
33
|
+
return `${head}-${hash}`;
|
|
34
|
+
}
|
|
35
|
+
/** Full localhost hostname for a service. */
|
|
36
|
+
export function serviceHostname(args) {
|
|
37
|
+
return `${serviceLabel(args)}.localhost`;
|
|
38
|
+
}
|
|
39
|
+
/** Public hostname alias built from a `publicBaseUrl` (the combined label becomes the subdomain). */
|
|
40
|
+
export function publicServiceHostname(args, publicBaseUrl) {
|
|
41
|
+
const base = publicBaseUrl.replace(/^https?:\/\//, "").replace(/\/+$/, "");
|
|
42
|
+
return `${serviceLabel(args)}.${base}`;
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=service-hostname.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service-hostname.js","sourceRoot":"","sources":["../../src/proxy/service-hostname.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC;;;;;GAKG;AAEH,MAAM,SAAS,GAAG,EAAE,CAAC;AACrB,MAAM,aAAa,GAAG,IAAI,GAAG,CAAC,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC;AAElD,mFAAmF;AACnF,MAAM,UAAU,WAAW,CAAC,KAAa;IACvC,OAAO,CACL,KAAK;SACF,WAAW,EAAE;SACb,OAAO,CAAC,aAAa,EAAE,GAAG,CAAC;SAC3B,OAAO,CAAC,QAAQ,EAAE,GAAG,CAAC;SACtB,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,IAAI,GAAG,CAClC,CAAC;AACJ,CAAC;AAED,oGAAoG;AACpG,MAAM,UAAU,YAAY,CAAC,IAI5B;IACC,MAAM,MAAM,GAAG,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACxC,MAAM,OAAO,GAAG,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAC1C,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAC3D,MAAM,UAAU,GAAG,CAAC,MAAM,IAAI,aAAa,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACxD,MAAM,KAAK,GAAG,UAAU,CAAC,CAAC,CAAC,GAAG,MAAM,KAAK,OAAO,EAAE,CAAC,CAAC,CAAC,GAAG,MAAM,KAAK,MAAM,KAAK,OAAO,EAAE,CAAC;IACxF,OAAO,UAAU,CAAC,KAAK,CAAC,CAAC;AAC3B,CAAC;AAED,oFAAoF;AACpF,SAAS,UAAU,CAAC,KAAa;IAC/B,IAAI,KAAK,CAAC,MAAM,IAAI,SAAS;QAAE,OAAO,KAAK,CAAC;IAC5C,MAAM,IAAI,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAC1E,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IAC9D,OAAO,GAAG,IAAI,IAAI,IAAI,EAAE,CAAC;AAC3B,CAAC;AAED,6CAA6C;AAC7C,MAAM,UAAU,eAAe,CAAC,IAI/B;IACC,OAAO,GAAG,YAAY,CAAC,IAAI,CAAC,YAAY,CAAC;AAC3C,CAAC;AAED,qGAAqG;AACrG,MAAM,UAAU,qBAAqB,CACnC,IAAiE,EACjE,aAAqB;IAErB,MAAM,IAAI,GAAG,aAAa,CAAC,OAAO,CAAC,cAAc,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAC3E,OAAO,GAAG,YAAY,CAAC,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC;AACzC,CAAC"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-memory service route table + port assignments (features/service-proxy.md § Behavior, § Data).
|
|
3
|
+
* Tied to running `type:"service"` scripts; not persisted across restarts.
|
|
4
|
+
*/
|
|
5
|
+
export interface ServiceRoute {
|
|
6
|
+
/** Owning script/terminal slot. */
|
|
7
|
+
slot: number;
|
|
8
|
+
script: string;
|
|
9
|
+
branch?: string | null;
|
|
10
|
+
project: string;
|
|
11
|
+
port: number;
|
|
12
|
+
/** All hostnames (localhost + optional public) that map to this route. */
|
|
13
|
+
hostnames: string[];
|
|
14
|
+
}
|
|
15
|
+
/** Bind to port 0 to obtain a free localhost port, then release it. */
|
|
16
|
+
export declare function assignFreePort(): Promise<number>;
|
|
17
|
+
export declare class ServicePortRegistry {
|
|
18
|
+
private readonly publicBaseUrl?;
|
|
19
|
+
private readonly byHostname;
|
|
20
|
+
private readonly bySlot;
|
|
21
|
+
constructor(publicBaseUrl?: string | undefined);
|
|
22
|
+
/** Register a route for a started service. Returns the route (incl. generated hostnames). */
|
|
23
|
+
register(args: {
|
|
24
|
+
slot: number;
|
|
25
|
+
script: string;
|
|
26
|
+
branch?: string | null;
|
|
27
|
+
project: string;
|
|
28
|
+
port: number;
|
|
29
|
+
}): ServiceRoute;
|
|
30
|
+
/** Deregister + release a route by slot (service stop). */
|
|
31
|
+
unregister(slot: number): boolean;
|
|
32
|
+
/** Look up a route by the raw `Host` header (strips any `:port`). */
|
|
33
|
+
lookup(host: string | undefined): ServiceRoute | undefined;
|
|
34
|
+
list(): ServiceRoute[];
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=service-port-registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service-port-registry.d.ts","sourceRoot":"","sources":["../../src/proxy/service-port-registry.ts"],"names":[],"mappings":"AAIA;;;GAGG;AAEH,MAAM,WAAW,YAAY;IAC3B,mCAAmC;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,0EAA0E;IAC1E,SAAS,EAAE,MAAM,EAAE,CAAC;CACrB;AAED,uEAAuE;AACvE,wBAAgB,cAAc,IAAI,OAAO,CAAC,MAAM,CAAC,CAWhD;AAED,qBAAa,mBAAmB;IAIlB,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAC;IAH3C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAmC;IAC9D,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAmC;gBAE7B,aAAa,CAAC,EAAE,MAAM,YAAA;IAEnD,6FAA6F;IAC7F,QAAQ,CAAC,IAAI,EAAE;QACb,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,MAAM,CAAC;QACf,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACvB,OAAO,EAAE,MAAM,CAAC;QAChB,IAAI,EAAE,MAAM,CAAC;KACd,GAAG,YAAY;IAUhB,2DAA2D;IAC3D,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAQjC,qEAAqE;IACrE,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,YAAY,GAAG,SAAS;IAM1D,IAAI,IAAI,YAAY,EAAE;CAGvB"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { createServer } from "node:net";
|
|
2
|
+
import { publicServiceHostname, serviceHostname } from "./service-hostname.js";
|
|
3
|
+
/** Bind to port 0 to obtain a free localhost port, then release it. */
|
|
4
|
+
export function assignFreePort() {
|
|
5
|
+
return new Promise((resolve, reject) => {
|
|
6
|
+
const server = createServer();
|
|
7
|
+
server.unref();
|
|
8
|
+
server.on("error", reject);
|
|
9
|
+
server.listen(0, "127.0.0.1", () => {
|
|
10
|
+
const address = server.address();
|
|
11
|
+
const port = typeof address === "object" && address ? address.port : 0;
|
|
12
|
+
server.close(() => resolve(port));
|
|
13
|
+
});
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
export class ServicePortRegistry {
|
|
17
|
+
publicBaseUrl;
|
|
18
|
+
byHostname = new Map();
|
|
19
|
+
bySlot = new Map();
|
|
20
|
+
constructor(publicBaseUrl) {
|
|
21
|
+
this.publicBaseUrl = publicBaseUrl;
|
|
22
|
+
}
|
|
23
|
+
/** Register a route for a started service. Returns the route (incl. generated hostnames). */
|
|
24
|
+
register(args) {
|
|
25
|
+
const hostnames = [serviceHostname(args)];
|
|
26
|
+
if (this.publicBaseUrl)
|
|
27
|
+
hostnames.push(publicServiceHostname(args, this.publicBaseUrl));
|
|
28
|
+
const route = { ...args, hostnames };
|
|
29
|
+
for (const hostname of hostnames)
|
|
30
|
+
this.byHostname.set(hostname.toLowerCase(), route);
|
|
31
|
+
this.bySlot.set(args.slot, route);
|
|
32
|
+
return route;
|
|
33
|
+
}
|
|
34
|
+
/** Deregister + release a route by slot (service stop). */
|
|
35
|
+
unregister(slot) {
|
|
36
|
+
const route = this.bySlot.get(slot);
|
|
37
|
+
if (!route)
|
|
38
|
+
return false;
|
|
39
|
+
for (const hostname of route.hostnames)
|
|
40
|
+
this.byHostname.delete(hostname.toLowerCase());
|
|
41
|
+
this.bySlot.delete(slot);
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
/** Look up a route by the raw `Host` header (strips any `:port`). */
|
|
45
|
+
lookup(host) {
|
|
46
|
+
if (!host)
|
|
47
|
+
return undefined;
|
|
48
|
+
const hostname = host.split(":")[0]?.toLowerCase() ?? "";
|
|
49
|
+
return this.byHostname.get(hostname);
|
|
50
|
+
}
|
|
51
|
+
list() {
|
|
52
|
+
return [...this.bySlot.values()];
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=service-port-registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service-port-registry.js","sourceRoot":"","sources":["../../src/proxy/service-port-registry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAExC,OAAO,EAAE,qBAAqB,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAkB/E,uEAAuE;AACvE,MAAM,UAAU,cAAc;IAC5B,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,MAAM,GAAG,YAAY,EAAE,CAAC;QAC9B,MAAM,CAAC,KAAK,EAAE,CAAC;QACf,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QAC3B,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,WAAW,EAAE,GAAG,EAAE;YACjC,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,EAAE,CAAC;YACjC,MAAM,IAAI,GAAG,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;YACvE,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;QACpC,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,OAAO,mBAAmB;IAID;IAHZ,UAAU,GAAG,IAAI,GAAG,EAAwB,CAAC;IAC7C,MAAM,GAAG,IAAI,GAAG,EAAwB,CAAC;IAE1D,YAA6B,aAAsB;QAAtB,kBAAa,GAAb,aAAa,CAAS;IAAG,CAAC;IAEvD,6FAA6F;IAC7F,QAAQ,CAAC,IAMR;QACC,MAAM,SAAS,GAAG,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC;QAC1C,IAAI,IAAI,CAAC,aAAa;YAAE,SAAS,CAAC,IAAI,CAAC,qBAAqB,CAAC,IAAI,EAAE,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC;QAExF,MAAM,KAAK,GAAiB,EAAE,GAAG,IAAI,EAAE,SAAS,EAAE,CAAC;QACnD,KAAK,MAAM,QAAQ,IAAI,SAAS;YAAE,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,QAAQ,CAAC,WAAW,EAAE,EAAE,KAAK,CAAC,CAAC;QACrF,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QAClC,OAAO,KAAK,CAAC;IACf,CAAC;IAED,2DAA2D;IAC3D,UAAU,CAAC,IAAY;QACrB,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACpC,IAAI,CAAC,KAAK;YAAE,OAAO,KAAK,CAAC;QACzB,KAAK,MAAM,QAAQ,IAAI,KAAK,CAAC,SAAS;YAAE,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC,CAAC;QACvF,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACzB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,qEAAqE;IACrE,MAAM,CAAC,IAAwB;QAC7B,IAAI,CAAC,IAAI;YAAE,OAAO,SAAS,CAAC;QAC5B,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC;QACzD,OAAO,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IACvC,CAAC;IAED,IAAI;QACF,OAAO,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;IACnC,CAAC;CACF"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { type IncomingMessage, type ServerResponse } from "node:http";
|
|
2
|
+
import { ServicePortRegistry } from "./service-port-registry.js";
|
|
3
|
+
/**
|
|
4
|
+
* HTTP service proxy (features/service-proxy.md § Behavior; architecture/config.md
|
|
5
|
+
* `daemon.serviceProxy`). Localhost service routes are always on; the optional public alias + a
|
|
6
|
+
* separate service-only listener are gated by config. Proxied service traffic is NOT gated by daemon
|
|
7
|
+
* password auth.
|
|
8
|
+
*/
|
|
9
|
+
export interface ServiceProxyConfig {
|
|
10
|
+
listen?: string;
|
|
11
|
+
publicBaseUrl?: string;
|
|
12
|
+
/** `false` suppresses ONLY the optional public/listen layers; the localhost proxy stays on. */
|
|
13
|
+
enabled: boolean;
|
|
14
|
+
}
|
|
15
|
+
/** Resolve effective config from the persisted daemon config + env overrides (env wins). */
|
|
16
|
+
export declare function resolveServiceProxyConfig(daemonServiceProxy: {
|
|
17
|
+
listen?: string;
|
|
18
|
+
publicBaseUrl?: string;
|
|
19
|
+
enabled?: boolean;
|
|
20
|
+
} | undefined, env?: NodeJS.ProcessEnv): ServiceProxyConfig;
|
|
21
|
+
export declare class ServiceProxy {
|
|
22
|
+
readonly registry: ServicePortRegistry;
|
|
23
|
+
private readonly config;
|
|
24
|
+
constructor(config: ServiceProxyConfig);
|
|
25
|
+
/** Optional separate service-only listener address (only when enabled + configured). */
|
|
26
|
+
get listenAddress(): string | undefined;
|
|
27
|
+
/**
|
|
28
|
+
* Try to handle a request as service traffic. Returns true when the Host matched a service route
|
|
29
|
+
* and the request was reverse-proxied; false when the caller should fall through to normal daemon
|
|
30
|
+
* handling. The `Host` header is forwarded UNCHANGED (routing depends on it).
|
|
31
|
+
*/
|
|
32
|
+
handleRequest(req: IncomingMessage, res: ServerResponse): boolean;
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=service-proxy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service-proxy.d.ts","sourceRoot":"","sources":["../../src/proxy/service-proxy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,eAAe,EAA0B,KAAK,cAAc,EAAE,MAAM,WAAW,CAAC;AAE9F,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAEjE;;;;;GAKG;AAEH,MAAM,WAAW,kBAAkB;IACjC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,+FAA+F;IAC/F,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,4FAA4F;AAC5F,wBAAgB,yBAAyB,CACvC,kBAAkB,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,SAAS,EAC9F,GAAG,GAAE,MAAM,CAAC,UAAwB,GACnC,kBAAkB,CAUpB;AAED,qBAAa,YAAY;IACvB,QAAQ,CAAC,QAAQ,EAAE,mBAAmB,CAAC;IACvC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAqB;gBAEhC,MAAM,EAAE,kBAAkB;IAMtC,wFAAwF;IACxF,IAAI,aAAa,IAAI,MAAM,GAAG,SAAS,CAEtC;IAED;;;;OAIG;IACH,aAAa,CAAC,GAAG,EAAE,eAAe,EAAE,GAAG,EAAE,cAAc,GAAG,OAAO;CAyBlE"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { request as httpRequest } from "node:http";
|
|
2
|
+
import { ServicePortRegistry } from "./service-port-registry.js";
|
|
3
|
+
/** Resolve effective config from the persisted daemon config + env overrides (env wins). */
|
|
4
|
+
export function resolveServiceProxyConfig(daemonServiceProxy, env = process.env) {
|
|
5
|
+
const listen = env.PI_STUDIO_SERVICE_PROXY_LISTEN ?? daemonServiceProxy?.listen;
|
|
6
|
+
const publicBaseUrl = env.PI_STUDIO_SERVICE_PROXY_PUBLIC_BASE_URL ?? daemonServiceProxy?.publicBaseUrl;
|
|
7
|
+
const enabledEnv = env.PI_STUDIO_SERVICE_PROXY_ENABLED;
|
|
8
|
+
const enabled = enabledEnv !== undefined
|
|
9
|
+
? enabledEnv === "true" || enabledEnv === "1"
|
|
10
|
+
: daemonServiceProxy?.enabled !== false;
|
|
11
|
+
return { listen, publicBaseUrl, enabled };
|
|
12
|
+
}
|
|
13
|
+
export class ServiceProxy {
|
|
14
|
+
registry;
|
|
15
|
+
config;
|
|
16
|
+
constructor(config) {
|
|
17
|
+
this.config = config;
|
|
18
|
+
// Public aliases are an optional layer → only when enabled.
|
|
19
|
+
this.registry = new ServicePortRegistry(config.enabled ? config.publicBaseUrl : undefined);
|
|
20
|
+
}
|
|
21
|
+
/** Optional separate service-only listener address (only when enabled + configured). */
|
|
22
|
+
get listenAddress() {
|
|
23
|
+
return this.config.enabled ? this.config.listen : undefined;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Try to handle a request as service traffic. Returns true when the Host matched a service route
|
|
27
|
+
* and the request was reverse-proxied; false when the caller should fall through to normal daemon
|
|
28
|
+
* handling. The `Host` header is forwarded UNCHANGED (routing depends on it).
|
|
29
|
+
*/
|
|
30
|
+
handleRequest(req, res) {
|
|
31
|
+
const route = this.registry.lookup(req.headers.host);
|
|
32
|
+
if (!route)
|
|
33
|
+
return false;
|
|
34
|
+
const proxyReq = httpRequest({
|
|
35
|
+
host: "127.0.0.1",
|
|
36
|
+
port: route.port,
|
|
37
|
+
method: req.method,
|
|
38
|
+
path: req.url,
|
|
39
|
+
headers: req.headers, // Host forwarded unchanged
|
|
40
|
+
}, (proxyRes) => {
|
|
41
|
+
res.writeHead(proxyRes.statusCode ?? 502, proxyRes.headers);
|
|
42
|
+
proxyRes.pipe(res);
|
|
43
|
+
});
|
|
44
|
+
proxyReq.on("error", () => {
|
|
45
|
+
// Service not yet listening / crashed → 502 (do not crash the daemon).
|
|
46
|
+
if (!res.headersSent)
|
|
47
|
+
res.writeHead(502, { "content-type": "text/plain" });
|
|
48
|
+
res.end("service unavailable");
|
|
49
|
+
});
|
|
50
|
+
req.pipe(proxyReq);
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=service-proxy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service-proxy.js","sourceRoot":"","sources":["../../src/proxy/service-proxy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAwB,OAAO,IAAI,WAAW,EAAuB,MAAM,WAAW,CAAC;AAE9F,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAgBjE,4FAA4F;AAC5F,MAAM,UAAU,yBAAyB,CACvC,kBAA8F,EAC9F,MAAyB,OAAO,CAAC,GAAG;IAEpC,MAAM,MAAM,GAAG,GAAG,CAAC,8BAA8B,IAAI,kBAAkB,EAAE,MAAM,CAAC;IAChF,MAAM,aAAa,GACjB,GAAG,CAAC,uCAAuC,IAAI,kBAAkB,EAAE,aAAa,CAAC;IACnF,MAAM,UAAU,GAAG,GAAG,CAAC,+BAA+B,CAAC;IACvD,MAAM,OAAO,GACX,UAAU,KAAK,SAAS;QACtB,CAAC,CAAC,UAAU,KAAK,MAAM,IAAI,UAAU,KAAK,GAAG;QAC7C,CAAC,CAAC,kBAAkB,EAAE,OAAO,KAAK,KAAK,CAAC;IAC5C,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,CAAC;AAC5C,CAAC;AAED,MAAM,OAAO,YAAY;IACd,QAAQ,CAAsB;IACtB,MAAM,CAAqB;IAE5C,YAAY,MAA0B;QACpC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,4DAA4D;QAC5D,IAAI,CAAC,QAAQ,GAAG,IAAI,mBAAmB,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;IAC7F,CAAC;IAED,wFAAwF;IACxF,IAAI,aAAa;QACf,OAAO,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC;IAC9D,CAAC;IAED;;;;OAIG;IACH,aAAa,CAAC,GAAoB,EAAE,GAAmB;QACrD,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QACrD,IAAI,CAAC,KAAK;YAAE,OAAO,KAAK,CAAC;QAEzB,MAAM,QAAQ,GAAG,WAAW,CAC1B;YACE,IAAI,EAAE,WAAW;YACjB,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,MAAM,EAAE,GAAG,CAAC,MAAM;YAClB,IAAI,EAAE,GAAG,CAAC,GAAG;YACb,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,2BAA2B;SAClD,EACD,CAAC,QAAQ,EAAE,EAAE;YACX,GAAG,CAAC,SAAS,CAAC,QAAQ,CAAC,UAAU,IAAI,GAAG,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAC;YAC5D,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACrB,CAAC,CACF,CAAC;QACF,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;YACxB,uEAAuE;YACvE,IAAI,CAAC,GAAG,CAAC,WAAW;gBAAE,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,YAAY,EAAE,CAAC,CAAC;YAC3E,GAAG,CAAC,GAAG,CAAC,qBAAqB,CAAC,CAAC;QACjC,CAAC,CAAC,CAAC;QACH,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACnB,OAAO,IAAI,CAAC;IACd,CAAC;CACF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/terminal/index.ts"],"names":[],"mappings":"AACA,cAAc,kBAAkB,CAAC;AACjC,cAAc,uBAAuB,CAAC;AACtC,cAAc,mBAAmB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/terminal/index.ts"],"names":[],"mappings":"AAAA,uEAAuE;AACvE,cAAc,kBAAkB,CAAC;AACjC,cAAc,uBAAuB,CAAC;AACtC,cAAc,mBAAmB,CAAC"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PTY backend abstraction (features/terminals.md § Behavior). The production backend is `node-pty`,
|
|
3
|
+
* which gives a **real TTY** (programs see `isatty`, get SIGWINCH on resize, and run full-screen
|
|
4
|
+
* apps like vim/htop correctly). A piped-`child_process` fallback remains for environments where the
|
|
5
|
+
* native module is unavailable and for deterministic tests (which inject their own fake backend).
|
|
6
|
+
*/
|
|
7
|
+
export interface PtyProcess {
|
|
8
|
+
write(data: Uint8Array): void;
|
|
9
|
+
resize(cols: number, rows: number): void;
|
|
10
|
+
kill(): void;
|
|
11
|
+
onData(cb: (data: Uint8Array) => void): void;
|
|
12
|
+
onExit(cb: (code: number | null) => void): void;
|
|
13
|
+
}
|
|
14
|
+
export interface PtySpawnOptions {
|
|
15
|
+
shell: string;
|
|
16
|
+
args?: string[];
|
|
17
|
+
cwd?: string;
|
|
18
|
+
env?: Record<string, string>;
|
|
19
|
+
cols: number;
|
|
20
|
+
rows: number;
|
|
21
|
+
}
|
|
22
|
+
export interface PtyBackend {
|
|
23
|
+
spawn(opts: PtySpawnOptions): PtyProcess;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Resolve a shell/binary name to an absolute path via `$PATH` (cross-platform, honors Windows
|
|
27
|
+
* PATHEXT). Returns the input unchanged when it already looks like a path or cannot be resolved
|
|
28
|
+
* (the spawn will then surface a clear ENOENT).
|
|
29
|
+
*/
|
|
30
|
+
export declare function resolveExecutable(bin: string, env?: Record<string, string | undefined>): string;
|
|
31
|
+
/** Production backend: a real PTY via `node-pty`. */
|
|
32
|
+
export declare class NodePtyBackend implements PtyBackend {
|
|
33
|
+
spawn(opts: PtySpawnOptions): PtyProcess;
|
|
34
|
+
}
|
|
35
|
+
/** Fallback backend: a piped child process. Not a real TTY, but streams stdout/stderr ⇄ stdin. */
|
|
36
|
+
export declare class ChildProcessPtyBackend implements PtyBackend {
|
|
37
|
+
spawn(opts: PtySpawnOptions): PtyProcess;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Default backend factory: prefer the real PTY (`node-pty`); if the native module cannot be loaded
|
|
41
|
+
* on this platform, fall back to the piped child-process backend.
|
|
42
|
+
*/
|
|
43
|
+
export declare function createDefaultPtyBackend(): PtyBackend;
|
|
44
|
+
//# sourceMappingURL=pty-backend.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pty-backend.d.ts","sourceRoot":"","sources":["../../src/terminal/pty-backend.ts"],"names":[],"mappings":"AAMA;;;;;GAKG;AAEH,MAAM,WAAW,UAAU;IACzB,KAAK,CAAC,IAAI,EAAE,UAAU,GAAG,IAAI,CAAC;IAC9B,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACzC,IAAI,IAAI,IAAI,CAAC;IACb,MAAM,CAAC,EAAE,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,IAAI,GAAG,IAAI,CAAC;IAC7C,MAAM,CAAC,EAAE,EAAE,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,GAAG,IAAI,CAAC;CACjD;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,UAAU;IACzB,KAAK,CAAC,IAAI,EAAE,eAAe,GAAG,UAAU,CAAC;CAC1C;AAID;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,GAAG,EAAE,MAAM,EACX,GAAG,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAe,GACpD,MAAM,CAIR;AAUD,qDAAqD;AACrD,qBAAa,cAAe,YAAW,UAAU;IAC/C,KAAK,CAAC,IAAI,EAAE,eAAe,GAAG,UAAU;CAmCzC;AAED,kGAAkG;AAClG,qBAAa,sBAAuB,YAAW,UAAU;IACvD,KAAK,CAAC,IAAI,EAAE,eAAe,GAAG,UAAU;CA2CzC;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,IAAI,UAAU,CAQpD"}
|