@ricsam/r5d-worker 0.0.47 → 0.0.49
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 +3 -1
- package/dist/cjs/main.cjs +201 -171
- package/dist/cjs/package.json +1 -1
- package/dist/cjs/port-forward-client.cjs +148 -0
- package/dist/cjs/workspace-sync.cjs +38 -0
- package/dist/mjs/main.mjs +197 -171
- package/dist/mjs/package.json +1 -1
- package/dist/mjs/port-forward-client.mjs +114 -0
- package/dist/mjs/workspace-sync.mjs +37 -0
- package/dist/types/main.d.ts +45 -6
- package/dist/types/port-forward-client.d.ts +8 -0
- package/dist/types/workspace-sync.d.ts +1 -0
- package/package.json +1 -1
package/dist/mjs/package.json
CHANGED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import net from "node:net";
|
|
2
|
+
import WebSocket, {} from "ws";
|
|
3
|
+
const RELAY_FRAME_BYTES = 64 * 1024;
|
|
4
|
+
const RELAY_PAUSE_BYTES = 1024 * 1024;
|
|
5
|
+
const RELAY_RESUME_BYTES = 256 * 1024;
|
|
6
|
+
function relayUrl(baseUrl, label, forwardId, relayConnectionId) {
|
|
7
|
+
const url = new URL("/worker/port-forward/ws", baseUrl);
|
|
8
|
+
url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
|
|
9
|
+
url.searchParams.set("label", label);
|
|
10
|
+
url.searchParams.set("forward", forwardId);
|
|
11
|
+
url.searchParams.set("connection", relayConnectionId);
|
|
12
|
+
return url.toString();
|
|
13
|
+
}
|
|
14
|
+
function rawDataBuffer(data) {
|
|
15
|
+
if (Buffer.isBuffer(data)) return data;
|
|
16
|
+
if (Array.isArray(data)) return Buffer.concat(data);
|
|
17
|
+
return Buffer.from(data);
|
|
18
|
+
}
|
|
19
|
+
function openWorkerPortForwardRelay(options) {
|
|
20
|
+
const socket = net.createConnection({ host: "127.0.0.1", port: options.workerPort, allowHalfOpen: true });
|
|
21
|
+
socket.pause();
|
|
22
|
+
socket.setNoDelay(true);
|
|
23
|
+
const relay = new WebSocket(relayUrl(options.baseUrl, options.label, options.forwardId, options.relayConnectionId), {
|
|
24
|
+
headers: { Authorization: `Bearer ${options.token}` },
|
|
25
|
+
perMessageDeflate: false
|
|
26
|
+
});
|
|
27
|
+
let relayReady = false;
|
|
28
|
+
let targetReady = false;
|
|
29
|
+
const pendingChunks = [];
|
|
30
|
+
let pendingBytes = 0;
|
|
31
|
+
let closing = false;
|
|
32
|
+
let resumeTimer;
|
|
33
|
+
const maybeResume = () => {
|
|
34
|
+
if (relayReady && targetReady && !resumeTimer) socket.resume();
|
|
35
|
+
};
|
|
36
|
+
const cleanup = () => {
|
|
37
|
+
if (closing) return;
|
|
38
|
+
closing = true;
|
|
39
|
+
if (resumeTimer) clearInterval(resumeTimer);
|
|
40
|
+
socket.destroy();
|
|
41
|
+
if (relay.readyState === WebSocket.OPEN || relay.readyState === WebSocket.CONNECTING) relay.close();
|
|
42
|
+
};
|
|
43
|
+
const sendError = (message) => {
|
|
44
|
+
if (relay.readyState === WebSocket.OPEN) relay.send(JSON.stringify({ type: "error", error: message }));
|
|
45
|
+
cleanup();
|
|
46
|
+
};
|
|
47
|
+
const waitForRelayDrain = () => {
|
|
48
|
+
if (resumeTimer || relay.bufferedAmount <= RELAY_PAUSE_BYTES) return;
|
|
49
|
+
socket.pause();
|
|
50
|
+
resumeTimer = setInterval(() => {
|
|
51
|
+
if (relay.readyState !== WebSocket.OPEN) return cleanup();
|
|
52
|
+
if (relay.bufferedAmount > RELAY_RESUME_BYTES) return;
|
|
53
|
+
clearInterval(resumeTimer);
|
|
54
|
+
resumeTimer = void 0;
|
|
55
|
+
maybeResume();
|
|
56
|
+
}, 10);
|
|
57
|
+
};
|
|
58
|
+
const sendChunk = (chunk) => {
|
|
59
|
+
for (let offset = 0; offset < chunk.byteLength; offset += RELAY_FRAME_BYTES) {
|
|
60
|
+
relay.send(chunk.subarray(offset, Math.min(chunk.byteLength, offset + RELAY_FRAME_BYTES)), { binary: true });
|
|
61
|
+
}
|
|
62
|
+
waitForRelayDrain();
|
|
63
|
+
};
|
|
64
|
+
socket.once("connect", () => {
|
|
65
|
+
targetReady = true;
|
|
66
|
+
maybeResume();
|
|
67
|
+
});
|
|
68
|
+
socket.on("data", (chunk) => {
|
|
69
|
+
if (!relayReady || relay.readyState !== WebSocket.OPEN) {
|
|
70
|
+
pendingBytes += chunk.byteLength;
|
|
71
|
+
if (pendingBytes > RELAY_PAUSE_BYTES) return cleanup();
|
|
72
|
+
pendingChunks.push(Buffer.from(chunk));
|
|
73
|
+
socket.pause();
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
sendChunk(chunk);
|
|
77
|
+
});
|
|
78
|
+
socket.on("end", () => {
|
|
79
|
+
if (relay.readyState === WebSocket.OPEN) relay.send(JSON.stringify({ type: "end" }));
|
|
80
|
+
});
|
|
81
|
+
socket.on("error", (error) => sendError(`Could not connect to 127.0.0.1:${options.workerPort}: ${error.message}`));
|
|
82
|
+
socket.on("close", cleanup);
|
|
83
|
+
relay.on("message", (data, isBinary) => {
|
|
84
|
+
if (!isBinary) {
|
|
85
|
+
let control;
|
|
86
|
+
try {
|
|
87
|
+
control = JSON.parse(rawDataBuffer(data).toString("utf8"));
|
|
88
|
+
} catch {
|
|
89
|
+
return cleanup();
|
|
90
|
+
}
|
|
91
|
+
if (control.type === "ready") {
|
|
92
|
+
relayReady = true;
|
|
93
|
+
for (const chunk of pendingChunks.splice(0)) sendChunk(chunk);
|
|
94
|
+
pendingBytes = 0;
|
|
95
|
+
maybeResume();
|
|
96
|
+
} else if (control.type === "end") {
|
|
97
|
+
socket.end();
|
|
98
|
+
} else if (control.type === "error") {
|
|
99
|
+
socket.destroy(new Error(control.error || "Port-forward relay failed."));
|
|
100
|
+
}
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
if (!socket.write(rawDataBuffer(data))) {
|
|
104
|
+
relay.pause();
|
|
105
|
+
socket.once("drain", () => relay.resume());
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
relay.once("error", cleanup);
|
|
109
|
+
relay.once("close", cleanup);
|
|
110
|
+
socket.pause();
|
|
111
|
+
}
|
|
112
|
+
export {
|
|
113
|
+
openWorkerPortForwardRelay
|
|
114
|
+
};
|
|
@@ -84,12 +84,16 @@ function workspaceProjectBranchRelativePath(projectId, branchName) {
|
|
|
84
84
|
function workspacePlansRelativePath(projectId, branchName) {
|
|
85
85
|
return path.posix.join("plans", projectId, encodeWorkspaceBranch(branchName));
|
|
86
86
|
}
|
|
87
|
+
const WORKSPACE_NATIVE_PLANS_RELATIVE_PATH = "workspace-plans";
|
|
87
88
|
function visibleProjectBranchPath(projectsRoot, manifest, branchName) {
|
|
88
89
|
return managedBranchPath(projectsRoot, manifest.checkoutPathSegments, branchName);
|
|
89
90
|
}
|
|
90
91
|
function localPlansBranchPath(plansRoot, projectId, branchName) {
|
|
91
92
|
return path.join(plansRoot, projectId, branchName);
|
|
92
93
|
}
|
|
94
|
+
function localWorkspacePlansPath(plansRoot) {
|
|
95
|
+
return path.join(plansRoot, "workspace");
|
|
96
|
+
}
|
|
93
97
|
function assertInside(root, candidate, label) {
|
|
94
98
|
const resolvedRoot = path.resolve(root);
|
|
95
99
|
const resolvedCandidate = path.resolve(candidate);
|
|
@@ -667,12 +671,43 @@ function mirrorShadowPlansToLocal(input, manifest, branchName) {
|
|
|
667
671
|
listFilesRecursively(targetRoot, planFilter)
|
|
668
672
|
);
|
|
669
673
|
}
|
|
674
|
+
function mirrorLocalWorkspacePlansToShadow(input) {
|
|
675
|
+
if (input.trigger.canonicalCheckoutOnly) return;
|
|
676
|
+
const sourceRoot = localWorkspacePlansPath(input.plansRoot);
|
|
677
|
+
if (!fs.existsSync(sourceRoot)) return;
|
|
678
|
+
const targetRoot = path.join(input.shadowRoot, WORKSPACE_NATIVE_PLANS_RELATIVE_PATH);
|
|
679
|
+
const planFilter = (relativePath) => relativePath.endsWith(".plan.md");
|
|
680
|
+
mirrorFileSet(sourceRoot, targetRoot, listFilesRecursively(sourceRoot, planFilter), listFilesRecursively(targetRoot, planFilter));
|
|
681
|
+
}
|
|
682
|
+
function mirrorShadowWorkspacePlansToLocal(input) {
|
|
683
|
+
if (input.trigger.canonicalCheckoutOnly) return;
|
|
684
|
+
const sourceRoot = path.join(input.shadowRoot, WORKSPACE_NATIVE_PLANS_RELATIVE_PATH);
|
|
685
|
+
const targetRoot = localWorkspacePlansPath(input.plansRoot);
|
|
686
|
+
const planFilter = (relativePath) => relativePath.endsWith(".plan.md");
|
|
687
|
+
mirrorFileSet(
|
|
688
|
+
sourceRoot,
|
|
689
|
+
targetRoot,
|
|
690
|
+
listShadowTrackedFiles(input, WORKSPACE_NATIVE_PLANS_RELATIVE_PATH).filter(planFilter),
|
|
691
|
+
listFilesRecursively(targetRoot, planFilter)
|
|
692
|
+
);
|
|
693
|
+
}
|
|
694
|
+
function reconcileWorkspaceNativePlans(input) {
|
|
695
|
+
if (input.trigger.canonicalCheckoutOnly) return;
|
|
696
|
+
if (fs.existsSync(localWorkspacePlansPath(input.plansRoot))) {
|
|
697
|
+
mirrorLocalWorkspacePlansToShadow(input);
|
|
698
|
+
return;
|
|
699
|
+
}
|
|
700
|
+
if (listShadowTrackedFiles(input, WORKSPACE_NATIVE_PLANS_RELATIVE_PATH).length > 0 || restoreShadowRootFromRemote(input, WORKSPACE_NATIVE_PLANS_RELATIVE_PATH)) {
|
|
701
|
+
mirrorShadowWorkspacePlansToLocal(input);
|
|
702
|
+
}
|
|
703
|
+
}
|
|
670
704
|
function mergeGitlinkProjection(target, source) {
|
|
671
705
|
target.entries.push(...source.entries);
|
|
672
706
|
target.opaqueRoots.push(...source.opaqueRoots);
|
|
673
707
|
}
|
|
674
708
|
function mirrorVisibleWorkspaceToShadow(input, excludedCheckouts = /* @__PURE__ */ new Set()) {
|
|
675
709
|
const projection = { entries: [], opaqueRoots: [] };
|
|
710
|
+
reconcileWorkspaceNativePlans(input);
|
|
676
711
|
for (const manifest of [...input.projects].sort((left, right) => left.projectId.localeCompare(right.projectId))) {
|
|
677
712
|
for (const branchName of [...new Set(manifest.branches)].sort()) {
|
|
678
713
|
if (excludedCheckouts.has(`${manifest.projectId}\0${branchName}`)) continue;
|
|
@@ -708,6 +743,7 @@ function reconcileNewVisibleCheckouts(input) {
|
|
|
708
743
|
return projection;
|
|
709
744
|
}
|
|
710
745
|
function mirrorShadowWorkspaceToVisible(input, opaqueWorkspaceRoots = []) {
|
|
746
|
+
mirrorShadowWorkspacePlansToLocal(input);
|
|
711
747
|
for (const manifest of [...input.projects].sort((left, right) => left.projectId.localeCompare(right.projectId))) {
|
|
712
748
|
for (const branchName of [...new Set(manifest.branches)].sort()) {
|
|
713
749
|
const relativeRoot = workspaceProjectBranchRelativePath(manifest.projectId, branchName);
|
|
@@ -1291,6 +1327,7 @@ class WorkspaceSyncSingleFlight {
|
|
|
1291
1327
|
export {
|
|
1292
1328
|
MAX_WORKSPACE_SYNC_DIFF_BYTES,
|
|
1293
1329
|
WORKSPACE_BRANCH,
|
|
1330
|
+
WORKSPACE_NATIVE_PLANS_RELATIVE_PATH,
|
|
1294
1331
|
WORKSPACE_PERIODIC_SCAN_INTERVAL_MS,
|
|
1295
1332
|
WorkspaceSyncSingleFlight,
|
|
1296
1333
|
assertCanonicalCheckoutIndexMatchesWorktree,
|
package/dist/types/main.d.ts
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
import { type WorkspaceProjectManifestEntry } from "./workspace-sync";
|
|
3
|
+
export type WorkerSessionTarget = {
|
|
4
|
+
type: "project";
|
|
5
|
+
projectId: string;
|
|
6
|
+
branchName: string;
|
|
7
|
+
} | {
|
|
8
|
+
type: "workspace";
|
|
9
|
+
ownerUserId: string;
|
|
10
|
+
rootProfile: "visible_projects" | "canonical_sync";
|
|
11
|
+
};
|
|
3
12
|
type WorkerReadFileResult = {
|
|
4
13
|
type: "read";
|
|
5
14
|
kind: "text";
|
|
@@ -60,22 +69,17 @@ export declare function isArtifactEnvPath(filePath: string): boolean;
|
|
|
60
69
|
export declare function syncSessionArtifacts(input: {
|
|
61
70
|
baseUrl: string;
|
|
62
71
|
token: string;
|
|
63
|
-
projectId: string;
|
|
64
|
-
branchName: string;
|
|
65
72
|
sessionId: string;
|
|
66
73
|
artifactRoot: string;
|
|
67
74
|
}): Promise<string>;
|
|
68
75
|
export declare function prepareArtifactEnvForShell(input: {
|
|
69
76
|
baseUrl: string;
|
|
70
77
|
token: string;
|
|
71
|
-
projectId: string;
|
|
72
|
-
branchName: string;
|
|
73
78
|
sessionId?: string;
|
|
74
79
|
artifactRoot: string;
|
|
75
80
|
}): Promise<Record<string, string>>;
|
|
76
81
|
export declare function preparePlanEnvForShell(input: {
|
|
77
|
-
|
|
78
|
-
branchName: string;
|
|
82
|
+
target: WorkerSessionTarget;
|
|
79
83
|
planRoot: string;
|
|
80
84
|
activePlanId?: string;
|
|
81
85
|
}): Record<string, string>;
|
|
@@ -136,6 +140,21 @@ export declare function ensureVisibleGitCheckout(input: {
|
|
|
136
140
|
allowRequiredRemoteMigration?: boolean;
|
|
137
141
|
clearPersistentAuth?: boolean;
|
|
138
142
|
}): string;
|
|
143
|
+
type ResolvedWorkerSessionTarget = {
|
|
144
|
+
target: WorkerSessionTarget;
|
|
145
|
+
rootPath: string;
|
|
146
|
+
manifest?: WorkerProjectManifestEntry;
|
|
147
|
+
};
|
|
148
|
+
export declare function describeWorkerSessionTarget(target: WorkerSessionTarget): string;
|
|
149
|
+
export declare function resolveWorkerSessionTarget(input: {
|
|
150
|
+
target: WorkerSessionTarget;
|
|
151
|
+
baseUrl: string;
|
|
152
|
+
token: string;
|
|
153
|
+
projectsRoot: string;
|
|
154
|
+
syncRoot: string;
|
|
155
|
+
workspaceShadowRoot: string;
|
|
156
|
+
manifestByProjectId: Map<string, WorkerProjectManifestEntry>;
|
|
157
|
+
}): ResolvedWorkerSessionTarget;
|
|
139
158
|
type VisibleCheckoutTarget = {
|
|
140
159
|
projectId: string;
|
|
141
160
|
branchName: string;
|
|
@@ -146,6 +165,17 @@ export declare function workspaceSyncCheckoutTargets(input: {
|
|
|
146
165
|
includeAllManifestCheckouts: boolean;
|
|
147
166
|
canonicalCheckoutOnly?: VisibleCheckoutTarget;
|
|
148
167
|
}): VisibleCheckoutTarget[];
|
|
168
|
+
export declare function prepareBuiltInToolPathsForTarget(input: {
|
|
169
|
+
target: WorkerSessionTarget;
|
|
170
|
+
filePath: string;
|
|
171
|
+
baseUrl: string;
|
|
172
|
+
token: string;
|
|
173
|
+
sessionId?: string;
|
|
174
|
+
activePlanId?: string;
|
|
175
|
+
artifactRoot: string;
|
|
176
|
+
planRoot: string;
|
|
177
|
+
access: "read" | "write";
|
|
178
|
+
}): Promise<WorkerBuiltInToolPaths | undefined>;
|
|
149
179
|
export declare function readWorkerTextFile(branchPath: string, filePath: string, offset?: number, limit?: number, builtInPaths?: WorkerBuiltInToolPaths): WorkerReadFileResult;
|
|
150
180
|
export declare function writeWorkerTextFile(branchPath: string, filePath: string, content: string, builtInPaths?: WorkerBuiltInToolPaths): WorkerWriteFileResult;
|
|
151
181
|
export declare function editWorkerTextFile(branchPath: string, filePath: string, edits: Array<{
|
|
@@ -167,6 +197,15 @@ export declare function findWorkerFiles(branchPath: string, input: {
|
|
|
167
197
|
}, builtInPaths?: WorkerBuiltInToolPaths): WorkerFindResult;
|
|
168
198
|
export declare function listWorkerDirectory(branchPath: string, inputPath?: string, inputLimit?: number, builtInPaths?: WorkerBuiltInToolPaths): WorkerLsResult;
|
|
169
199
|
export declare function readWorkerImageFile(branchPath: string, filePath: string, builtInPaths?: WorkerBuiltInToolPaths): WorkerViewFileBytesResult;
|
|
200
|
+
export declare function prepareShellEnvForTarget(input: {
|
|
201
|
+
target: WorkerSessionTarget;
|
|
202
|
+
baseUrl: string;
|
|
203
|
+
token: string;
|
|
204
|
+
sessionId?: string;
|
|
205
|
+
activePlanId?: string;
|
|
206
|
+
artifactRoot: string;
|
|
207
|
+
planRoot: string;
|
|
208
|
+
}): Promise<Record<string, string>>;
|
|
170
209
|
export declare function resolveHostShell(command?: string, platform?: NodeJS.Platform): {
|
|
171
210
|
file: string;
|
|
172
211
|
args: string[];
|
|
@@ -78,6 +78,7 @@ export declare function encodeWorkspaceBranch(branchName: string): string;
|
|
|
78
78
|
export declare function workspaceProjectsForSync(projects: readonly WorkspaceProjectManifestEntry[], trigger: WorkspaceSyncTrigger): WorkspaceProjectManifestEntry[];
|
|
79
79
|
export declare function workspaceProjectBranchRelativePath(projectId: string, branchName: string): string;
|
|
80
80
|
export declare function workspacePlansRelativePath(projectId: string, branchName: string): string;
|
|
81
|
+
export declare const WORKSPACE_NATIVE_PLANS_RELATIVE_PATH = "workspace-plans";
|
|
81
82
|
export declare function visibleProjectBranchPath(projectsRoot: string, manifest: WorkspaceProjectManifestEntry, branchName: string): string;
|
|
82
83
|
type GitlinkEntry = {
|
|
83
84
|
filePath: string;
|