@ricsam/r5d-worker 0.0.164 → 0.0.165
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/package.json +1 -1
- package/dist/mjs/internal-r5dctl.cjs +1 -1
- package/dist/mjs/main.mjs +2 -2
- package/dist/mjs/package.json +1 -1
- package/dist/mjs/personal/client.mjs +4 -2
- package/dist/mjs/personal/runtime.mjs +22 -6
- package/dist/mjs/runtime/workspace/authority.mjs +21 -0
- package/dist/mjs/runtime/workspace/walk.mjs +94 -0
- package/dist/types/personal/runtime.d.ts +3 -1
- package/dist/types/runtime/workspace/authority.d.ts +5 -0
- package/dist/types/runtime/workspace/walk.d.ts +49 -0
- package/package.json +2 -2
package/dist/cjs/package.json
CHANGED
|
@@ -20605,7 +20605,7 @@ function resolveEntrypointPath(entrypoint) {
|
|
|
20605
20605
|
}
|
|
20606
20606
|
}
|
|
20607
20607
|
function getR5dctlVersion() {
|
|
20608
|
-
if (true) return "0.0.
|
|
20608
|
+
if (true) return "0.0.165";
|
|
20609
20609
|
const entrypoint = process.argv[1] ? resolveEntrypointPath(process.argv[1]) : null;
|
|
20610
20610
|
let current = entrypoint ? import_node_path2.default.dirname(entrypoint) : process.cwd();
|
|
20611
20611
|
for (let index = 0; index < 12; index += 1) {
|
package/dist/mjs/main.mjs
CHANGED
|
@@ -7,7 +7,7 @@ import { startManagerRpc } from "./runtime/releases/rpc-main.mjs";
|
|
|
7
7
|
import { ManagerRpcConfig } from "./runtime/releases/rpc-protocol.mjs";
|
|
8
8
|
const args = process.argv.slice(2);
|
|
9
9
|
if (args.includes("--version")) {
|
|
10
|
-
console.log(`r5d-worker ${true ? "0.0.
|
|
10
|
+
console.log(`r5d-worker ${true ? "0.0.165" : "development"}`);
|
|
11
11
|
} else if (!args.length || args.includes("--help")) {
|
|
12
12
|
console.log(
|
|
13
13
|
"Usage: r5d-worker start --label <label> [--root <dir>] [--base-url <url>] [--token <worker-token>]\n r5d-worker executor /absolute/private/config.json\n r5d-worker manager /absolute/private/config.json\n\nRun independently supervised durable runtime services. Provision configuration with r5dinfra."
|
|
@@ -15,7 +15,7 @@ if (args.includes("--version")) {
|
|
|
15
15
|
} else if (args[0] === "start") {
|
|
16
16
|
const runtime = await startPersonalWorker(
|
|
17
17
|
parsePersonalWorkerOptions(args.slice(1)),
|
|
18
|
-
true ? "0.0.
|
|
18
|
+
true ? "0.0.165" : "development"
|
|
19
19
|
);
|
|
20
20
|
console.log(`Worker connected: ${runtime.resourceId}`);
|
|
21
21
|
let closing = false;
|
package/dist/mjs/package.json
CHANGED
|
@@ -103,7 +103,7 @@ async function startPersonalWorker(options, version) {
|
|
|
103
103
|
platform: process.platform,
|
|
104
104
|
arch: process.arch,
|
|
105
105
|
hostname: os.hostname(),
|
|
106
|
-
capabilities: { updateClis: true, durableWorkspace: true, outerWorkspace: true }
|
|
106
|
+
capabilities: { updateClis: true, durableWorkspace: true, outerWorkspace: true, fileWalk: true }
|
|
107
107
|
});
|
|
108
108
|
const grant = PersonalWorkerGrant.parse(
|
|
109
109
|
await request("/api/personal/resources/register", { kind: "worker", ...identity, label: options.label, metadata: metadata() })
|
|
@@ -127,7 +127,9 @@ async function startPersonalWorker(options, version) {
|
|
|
127
127
|
}
|
|
128
128
|
},
|
|
129
129
|
publicationReport: async (report) => {
|
|
130
|
-
await request(`${endpoint}/publication`, { instanceId: identity.instanceId, report });
|
|
130
|
+
const response = await request(`${endpoint}/publication`, { instanceId: identity.instanceId, report });
|
|
131
|
+
const result = response?.result;
|
|
132
|
+
return result && typeof result === "object" && typeof result.incidentId === "string" ? { incidentId: result.incidentId } : void 0;
|
|
131
133
|
}
|
|
132
134
|
});
|
|
133
135
|
const ledgerFile = path.join(root, "relay.sqlite");
|
|
@@ -261,10 +261,12 @@ exec ${quote(executable)} ${quote(cli)} "$@"
|
|
|
261
261
|
}
|
|
262
262
|
const failedPublicationAttempts = /* @__PURE__ */ new Map();
|
|
263
263
|
let publication = null, closing = false;
|
|
264
|
+
let pausedForIncident = null;
|
|
264
265
|
const synchronize = () => {
|
|
265
266
|
if (publication) return publication;
|
|
266
267
|
const next = (async () => {
|
|
267
268
|
const results = [];
|
|
269
|
+
if (pausedForIncident) return [{ workbenchId: "workspace", error: "remediation_paused" }];
|
|
268
270
|
try {
|
|
269
271
|
const outer = await authority.synchronizeOuter(grant.userId);
|
|
270
272
|
failedPublicationAttempts.delete("workspace");
|
|
@@ -290,7 +292,7 @@ exec ${quote(executable)} ${quote(cli)} "$@"
|
|
|
290
292
|
}
|
|
291
293
|
if (publicationRefusalElects(code, attempt.observations)) {
|
|
292
294
|
try {
|
|
293
|
-
await options.publicationReport(WorkspacePublicationReport.parse({
|
|
295
|
+
const elected = await options.publicationReport(WorkspacePublicationReport.parse({
|
|
294
296
|
protocol: 1,
|
|
295
297
|
attemptId: attempt.attemptId,
|
|
296
298
|
scope: "workspace",
|
|
@@ -300,6 +302,11 @@ exec ${quote(executable)} ${quote(cli)} "$@"
|
|
|
300
302
|
error: code,
|
|
301
303
|
...paths.length ? { paths } : {}
|
|
302
304
|
}));
|
|
305
|
+
if (elected?.incidentId && pausedForIncident !== elected.incidentId) {
|
|
306
|
+
pausedForIncident = elected.incidentId;
|
|
307
|
+
process.stderr.write(`[r5d-worker] workspace synchronization paused for remediation ${elected.incidentId}
|
|
308
|
+
`);
|
|
309
|
+
}
|
|
303
310
|
} catch (reportError) {
|
|
304
311
|
process.stderr.write(`[r5d-worker] publication report deferred: ${reportError instanceof WorkspaceError ? reportError.code : "publication_report_failed"}
|
|
305
312
|
`);
|
|
@@ -322,7 +329,7 @@ exec ${quote(executable)} ${quote(cli)} "$@"
|
|
|
322
329
|
publication = next;
|
|
323
330
|
void next.then(
|
|
324
331
|
(results) => {
|
|
325
|
-
const deferred = results.filter((result) => result.error && !TRANSIENT_PUBLICATION_CODES.has(result.error));
|
|
332
|
+
const deferred = results.filter((result) => result.error && result.error !== "remediation_paused" && !TRANSIENT_PUBLICATION_CODES.has(result.error));
|
|
326
333
|
if (deferred.length)
|
|
327
334
|
process.stderr.write(`[r5d-worker] periodic workspace publication deferred: ${deferred.map((result) => `${result.workbenchId}:${result.error}`).join(", ")}
|
|
328
335
|
`);
|
|
@@ -363,6 +370,7 @@ exec ${quote(executable)} ${quote(cli)} "$@"
|
|
|
363
370
|
if (command.method === "git") return authority.inspectGit(identity, { ...command, method: command.action });
|
|
364
371
|
if (command.method === "fileHistoryDates") return authority.fileHistoryDates(identity, command.path);
|
|
365
372
|
if (command.method === "directory" || command.method === "raw") return authority.inspectFiles(identity, command);
|
|
373
|
+
if (command.method === "walk") return authority.walkFiles(identity, command);
|
|
366
374
|
}
|
|
367
375
|
if (request.kind === "terminal") {
|
|
368
376
|
await authority.ensureHydrated(identity);
|
|
@@ -412,10 +420,18 @@ exec ${quote(executable)} ${quote(cli)} "$@"
|
|
|
412
420
|
if (command.method === "commit") return authority.commit(identity, command);
|
|
413
421
|
if (command.method === "push") return authority.push(identity, command);
|
|
414
422
|
if (command.method === "reset") return authority.reset(identity, command);
|
|
415
|
-
if (command.method === "publish")
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
423
|
+
if (command.method === "publish") {
|
|
424
|
+
const published = await authority.publish(identity, {
|
|
425
|
+
allowLargeDiff: request.remediation === true && command.allowLargeDiff === true,
|
|
426
|
+
allowBlockedConflict: request.remediation === true
|
|
427
|
+
});
|
|
428
|
+
if (request.remediation === true && pausedForIncident) {
|
|
429
|
+
process.stderr.write(`[r5d-worker] workspace synchronization resumed after remediation ${pausedForIncident}
|
|
430
|
+
`);
|
|
431
|
+
pausedForIncident = null;
|
|
432
|
+
}
|
|
433
|
+
return published;
|
|
434
|
+
}
|
|
419
435
|
if (command.method === "reconcilePublication") return authority.reconcilePublication(identity);
|
|
420
436
|
}
|
|
421
437
|
if (request.kind === "agent") {
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
import { OuterRepository, OuterSnapshotRefusal, OUTER_BRANCH, OUTER_CONFLICT_CODE } from "./outer.mjs";
|
|
15
15
|
import { SessionArtifactStore, SessionArtifactChunk, RESERVED_ARTIFACT_ENV } from "./artifacts.mjs";
|
|
16
16
|
import { WorkspaceFileWrite, WorkspaceFileWriteLookup } from "./file-write.mjs";
|
|
17
|
+
import { walkDirectoryTree } from "./walk.mjs";
|
|
17
18
|
import { WorkspaceStorageClient } from "./storage-client.mjs";
|
|
18
19
|
import {
|
|
19
20
|
durableJson,
|
|
@@ -317,6 +318,26 @@ class WorkspaceAuthority {
|
|
|
317
318
|
}));
|
|
318
319
|
return { directory: display, entries };
|
|
319
320
|
}
|
|
321
|
+
/** Walks a directory tree locally in one operation. Paths are reported
|
|
322
|
+
* relative to the walked directory; the shared ignore list and bounds live
|
|
323
|
+
* in `./walk` so the server's fallback agrees with them. */
|
|
324
|
+
async walkFiles(identity, command) {
|
|
325
|
+
const b = await this.bench(identity);
|
|
326
|
+
const relative = command.hostPath ? "" : command.path.replace(/^\/+/, "").replace(/\/+$/, "");
|
|
327
|
+
if (!command.hostPath && relative) safeTreePath(relative);
|
|
328
|
+
const target = command.hostPath ? this.resolveHostPath(command.path) : path.join(b.config.cwd, relative);
|
|
329
|
+
const display = command.hostPath ? target : `/${relative}`;
|
|
330
|
+
const inspect = command.hostPath ? fs.lstat(target).then((stat) => {
|
|
331
|
+
if (stat.isSymbolicLink()) throw new WorkspaceError("unsafe_path", "Final symlink paths are not inspected");
|
|
332
|
+
}) : noSymlinkAncestors(target);
|
|
333
|
+
await inspect.catch((error) => {
|
|
334
|
+
if (error.code === "ENOENT" || error.code === "ENOTDIR") throw new WorkspaceError("not_found", "File or directory not found");
|
|
335
|
+
throw error;
|
|
336
|
+
});
|
|
337
|
+
if (!(await fs.stat(target)).isDirectory()) throw new WorkspaceError("not_found", "Directory not found");
|
|
338
|
+
const walked = await walkDirectoryTree(target);
|
|
339
|
+
return { directory: display, ...walked };
|
|
340
|
+
}
|
|
320
341
|
async inspectGit(identity, command) {
|
|
321
342
|
const b = await this.bench(identity);
|
|
322
343
|
return this.serial(b, async () => {
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
const WORKSPACE_WALK_IGNORED = /* @__PURE__ */ new Set([
|
|
5
|
+
".git",
|
|
6
|
+
"node_modules",
|
|
7
|
+
".next",
|
|
8
|
+
"dist",
|
|
9
|
+
"build",
|
|
10
|
+
"coverage",
|
|
11
|
+
".cache",
|
|
12
|
+
".r5d",
|
|
13
|
+
".r5d-next",
|
|
14
|
+
".venv",
|
|
15
|
+
"venv",
|
|
16
|
+
"__pycache__",
|
|
17
|
+
".npm",
|
|
18
|
+
".pnpm-store",
|
|
19
|
+
".yarn",
|
|
20
|
+
".bun",
|
|
21
|
+
".turbo",
|
|
22
|
+
".parcel-cache",
|
|
23
|
+
".mypy_cache",
|
|
24
|
+
".pytest_cache",
|
|
25
|
+
".ruff_cache",
|
|
26
|
+
".gradle",
|
|
27
|
+
".tox",
|
|
28
|
+
".svelte-kit",
|
|
29
|
+
".nuxt",
|
|
30
|
+
".terraform"
|
|
31
|
+
]);
|
|
32
|
+
const WORKSPACE_WALK_LIMITS = Object.freeze({
|
|
33
|
+
/** Entries reported before the result is marked truncated. */
|
|
34
|
+
entries: 2e3,
|
|
35
|
+
/** Nesting depth below the walked directory. */
|
|
36
|
+
depth: 32,
|
|
37
|
+
/** Names read from any single directory; a larger directory truncates. */
|
|
38
|
+
directoryEntries: 2e5
|
|
39
|
+
});
|
|
40
|
+
const WorkspaceWalk = z.object({
|
|
41
|
+
method: z.literal("walk"),
|
|
42
|
+
path: z.string().max(4096),
|
|
43
|
+
hostPath: z.boolean().optional()
|
|
44
|
+
}).strict();
|
|
45
|
+
async function walkDirectoryTree(root, options = {}) {
|
|
46
|
+
const ignored = options.ignored ?? WORKSPACE_WALK_IGNORED, limits = options.limits ?? WORKSPACE_WALK_LIMITS;
|
|
47
|
+
const entries = [], queue = [{ relative: "", depth: 0 }];
|
|
48
|
+
let truncated = false;
|
|
49
|
+
while (queue.length) {
|
|
50
|
+
const next = queue.shift();
|
|
51
|
+
const directory = next.relative ? path.join(root, next.relative) : root;
|
|
52
|
+
let names;
|
|
53
|
+
try {
|
|
54
|
+
names = await fs.readdir(directory, { withFileTypes: true });
|
|
55
|
+
} catch (error) {
|
|
56
|
+
if (error.code === "ENOENT" || error.code === "ENOTDIR") continue;
|
|
57
|
+
throw error;
|
|
58
|
+
}
|
|
59
|
+
if (names.length > limits.directoryEntries) {
|
|
60
|
+
truncated = true;
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
names.sort((a, b) => a.name.localeCompare(b.name));
|
|
64
|
+
for (const entry of names) {
|
|
65
|
+
if (ignored.has(entry.name) || entry.isSymbolicLink() || !(entry.isFile() || entry.isDirectory())) continue;
|
|
66
|
+
if (entries.length >= limits.entries) {
|
|
67
|
+
truncated = true;
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
const relative = next.relative ? path.posix.join(next.relative, entry.name) : entry.name;
|
|
71
|
+
let size = null;
|
|
72
|
+
if (entry.isFile()) {
|
|
73
|
+
try {
|
|
74
|
+
size = (await fs.stat(path.join(directory, entry.name))).size;
|
|
75
|
+
} catch {
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
entries.push({ path: relative, type: entry.isDirectory() ? "directory" : "file", size });
|
|
80
|
+
if (entry.isDirectory()) {
|
|
81
|
+
if (next.depth < limits.depth) queue.push({ relative, depth: next.depth + 1 });
|
|
82
|
+
else truncated = true;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (entries.length >= limits.entries) break;
|
|
86
|
+
}
|
|
87
|
+
return { entries: entries.sort((a, b) => a.path.localeCompare(b.path)), truncated: truncated || queue.length > 0 };
|
|
88
|
+
}
|
|
89
|
+
export {
|
|
90
|
+
WORKSPACE_WALK_IGNORED,
|
|
91
|
+
WORKSPACE_WALK_LIMITS,
|
|
92
|
+
WorkspaceWalk,
|
|
93
|
+
walkDirectoryTree
|
|
94
|
+
};
|
|
@@ -111,7 +111,9 @@ export declare function openPersonalWorkerRuntime(options: {
|
|
|
111
111
|
publicationIntervalMs?: number;
|
|
112
112
|
/** Authenticated transport for autonomous publication evidence. Failures are
|
|
113
113
|
* retried with the same attempt identity on the next publication cycle. */
|
|
114
|
-
publicationReport?: (report: z.infer<typeof WorkspacePublicationReport>) => Promise<
|
|
114
|
+
publicationReport?: (report: z.infer<typeof WorkspacePublicationReport>) => Promise<{
|
|
115
|
+
incidentId?: string;
|
|
116
|
+
} | void>;
|
|
115
117
|
}): Promise<{
|
|
116
118
|
grant: {
|
|
117
119
|
[x: string]: unknown;
|
|
@@ -3,6 +3,7 @@ import { type PollResult, type RunIdentity } from "../protocol";
|
|
|
3
3
|
import { WorkspaceConfig, type ExecutorRoute, type WorkspaceIdentity, type OuterState, type ApprovedWorkbench as ApprovedWorkbenchInput } from "./contracts";
|
|
4
4
|
import { SessionArtifactChunk } from "./artifacts";
|
|
5
5
|
import { WorkspaceFileWrite } from "./file-write";
|
|
6
|
+
import { type WorkspaceWalk, type WorkspaceWalkResult } from "./walk";
|
|
6
7
|
import { WorkspaceStorageClient } from "./storage-client";
|
|
7
8
|
export interface WorkspaceAuthorityOptions {
|
|
8
9
|
config: WorkspaceConfig;
|
|
@@ -107,6 +108,10 @@ export declare class WorkspaceAuthority {
|
|
|
107
108
|
size?: undefined;
|
|
108
109
|
modifiedAt?: undefined;
|
|
109
110
|
}>;
|
|
111
|
+
/** Walks a directory tree locally in one operation. Paths are reported
|
|
112
|
+
* relative to the walked directory; the shared ignore list and bounds live
|
|
113
|
+
* in `./walk` so the server's fallback agrees with them. */
|
|
114
|
+
walkFiles(identity: WorkspaceIdentity, command: WorkspaceWalk): Promise<WorkspaceWalkResult>;
|
|
110
115
|
inspectGit(identity: WorkspaceIdentity, command: {
|
|
111
116
|
method: "status" | "diff" | "history" | "fileDiff";
|
|
112
117
|
path?: string;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/** Directory names never descended by find/grep: dependency trees, build
|
|
3
|
+
* output and package-manager caches. The worker walks a checkout locally, so
|
|
4
|
+
* this list decides what a search sees; it is shared with the server so its
|
|
5
|
+
* per-directory fallback (older workers, virtual directories) agrees.
|
|
6
|
+
*
|
|
7
|
+
* Package-manager caches are here deliberately: `npm install` writes a deep
|
|
8
|
+
* hash-sharded `.npm/_cacache` into the checkout whenever its cache resolves
|
|
9
|
+
* there, and before this walk moved to the worker a single find over it cost
|
|
10
|
+
* one network round-trip per directory — enough to exhaust the agent's tool
|
|
11
|
+
* timeout and strand a workspace-remediation run. */
|
|
12
|
+
export declare const WORKSPACE_WALK_IGNORED: ReadonlySet<string>;
|
|
13
|
+
/** Bounds a walk regardless of what a checkout contains. */
|
|
14
|
+
export declare const WORKSPACE_WALK_LIMITS: Readonly<{
|
|
15
|
+
/** Entries reported before the result is marked truncated. */
|
|
16
|
+
entries: 2000;
|
|
17
|
+
/** Nesting depth below the walked directory. */
|
|
18
|
+
depth: 32;
|
|
19
|
+
/** Names read from any single directory; a larger directory truncates. */
|
|
20
|
+
directoryEntries: 200000;
|
|
21
|
+
}>;
|
|
22
|
+
/** Inspect command: walk a directory tree in one worker operation. */
|
|
23
|
+
export declare const WorkspaceWalk: z.ZodObject<{
|
|
24
|
+
method: z.ZodLiteral<"walk">;
|
|
25
|
+
path: z.ZodString;
|
|
26
|
+
hostPath: z.ZodOptional<z.ZodBoolean>;
|
|
27
|
+
}, z.core.$strict>;
|
|
28
|
+
export type WorkspaceWalk = z.infer<typeof WorkspaceWalk>;
|
|
29
|
+
export type WorkspaceWalkEntry = {
|
|
30
|
+
path: string;
|
|
31
|
+
type: "file" | "directory";
|
|
32
|
+
size: number | null;
|
|
33
|
+
};
|
|
34
|
+
export type WorkspaceWalkResult = {
|
|
35
|
+
directory: string;
|
|
36
|
+
entries: WorkspaceWalkEntry[];
|
|
37
|
+
truncated: boolean;
|
|
38
|
+
};
|
|
39
|
+
/** Breadth-first walk of `root`, reporting paths relative to it (POSIX). Names
|
|
40
|
+
* in `ignored` are neither reported nor descended; symlinks and anything that
|
|
41
|
+
* is not a regular file or directory are skipped. The result is sorted by path
|
|
42
|
+
* and bounded by `limits`. */
|
|
43
|
+
export declare function walkDirectoryTree(root: string, options?: {
|
|
44
|
+
ignored?: ReadonlySet<string>;
|
|
45
|
+
limits?: typeof WORKSPACE_WALK_LIMITS;
|
|
46
|
+
}): Promise<{
|
|
47
|
+
entries: WorkspaceWalkEntry[];
|
|
48
|
+
truncated: boolean;
|
|
49
|
+
}>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ricsam/r5d-worker",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.165",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/mjs/main.mjs",
|
|
6
6
|
"module": "./dist/mjs/main.mjs",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"r5d-worker": "dist/mjs/main.mjs"
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@ricsam/r5d-api": "^0.0.
|
|
24
|
+
"@ricsam/r5d-api": "^0.0.165",
|
|
25
25
|
"node-pty": "1.1.0",
|
|
26
26
|
"zod": "^4.1.13",
|
|
27
27
|
"picomatch": "^4.0.3"
|