@firstpick/pi-package-webui 0.3.6 → 0.3.8

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.
@@ -0,0 +1,147 @@
1
+ import assert from "node:assert/strict";
2
+ import { readFile } from "node:fs/promises";
3
+ import { dirname, join } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import {
6
+ assertNativeCommandTrust,
7
+ evaluateDispatchTrustGuards,
8
+ evaluateTrustGuards,
9
+ guardsForNativeCommand,
10
+ isLocalAddress,
11
+ isLocalRequest,
12
+ LOCALHOST_ONLY_POST_ROUTES,
13
+ remoteShellTrustWarning,
14
+ requireLocalhost,
15
+ requireLocalhostRoute,
16
+ TRUST_GUARD_TYPES,
17
+ } from "../lib/trust-boundaries.mjs";
18
+ import {
19
+ nativeCommandBlocked,
20
+ nativeCommandResponse,
21
+ nativeCommandUnavailable,
22
+ nativeParitySurfaceForCommand,
23
+ nativeSlashCommandEntries,
24
+ NATIVE_COMMAND_STATUSES,
25
+ NATIVE_REFRESH_TARGETS,
26
+ parseSlashCommand,
27
+ rpcSuccess,
28
+ } from "../lib/native-command-adapter.mjs";
29
+
30
+ const root = join(dirname(fileURLToPath(import.meta.url)), "..");
31
+ const parity = JSON.parse(await readFile(join(root, "WEBUI_TUI_NATIVE_PARITY.json"), "utf8"));
32
+
33
+ const localReq = { socket: { remoteAddress: "127.0.0.1" } };
34
+ const remoteReq = { socket: { remoteAddress: "192.168.1.50" } };
35
+
36
+ assert.equal(isLocalAddress("127.0.0.1"), true);
37
+ assert.equal(isLocalAddress("::ffff:127.0.0.1"), true);
38
+ assert.equal(isLocalAddress("::1"), true);
39
+ assert.equal(isLocalAddress("192.168.1.50"), false);
40
+ assert.equal(isLocalRequest(localReq), true);
41
+ assert.equal(isLocalRequest(remoteReq), false);
42
+
43
+ assert.throws(() => requireLocalhost(remoteReq, "blocked"), (error) => error.statusCode === 403);
44
+ assert.throws(() => requireLocalhostRoute(remoteReq, "/api/update"), (error) => error.statusCode === 403);
45
+ assert.doesNotThrow(() => requireLocalhostRoute(localReq, "/api/update"));
46
+
47
+ for (const pathname of LOCALHOST_ONLY_POST_ROUTES.keys()) {
48
+ assert.match(pathname, /^\/api\//, `${pathname} should be an API route`);
49
+ }
50
+
51
+ for (const guard of parity.guardTaxonomy) {
52
+ assert.ok(TRUST_GUARD_TYPES.has(guard), `trust-boundaries should know parity guard ${guard}`);
53
+ }
54
+
55
+ const exportGuards = guardsForNativeCommand("export", parity);
56
+ assert.ok(exportGuards.includes("localhost"), "export should declare localhost guard in parity matrix");
57
+
58
+ const exportDispatchLocal = evaluateDispatchTrustGuards(exportGuards, { isLocal: true, confirmed: false });
59
+ const exportDispatchRemote = evaluateDispatchTrustGuards(exportGuards, { isLocal: false, confirmed: false, networkOpen: true });
60
+ assert.equal(exportDispatchLocal.allowed, true);
61
+ assert.equal(exportDispatchRemote.allowed, false);
62
+ assert.ok(exportDispatchRemote.blocked.includes("localhost"));
63
+
64
+ const exportFullLocal = evaluateTrustGuards(exportGuards, { isLocal: true, confirmed: false });
65
+ assert.equal(exportFullLocal.allowed, false);
66
+ assert.ok(exportFullLocal.blocked.includes("confirmation"));
67
+
68
+ // Dispatch enforcement is guards-driven: every slash command declaring a
69
+ // localhost/trusted-context guard must block remote contexts, sensitive or not.
70
+ for (const surface of parity.surfaces) {
71
+ if (surface.kind !== "slash-command") continue;
72
+ const guards = guardsForNativeCommand(surface.command.name, parity);
73
+ const dispatchGuarded = guards.some((guard) => guard === "localhost" || guard === "trusted-context");
74
+ const remoteEvaluation = evaluateDispatchTrustGuards(guards, { isLocal: false, confirmed: true, networkOpen: true });
75
+ assert.equal(
76
+ remoteEvaluation.allowed,
77
+ !dispatchGuarded,
78
+ `/${surface.command.name} remote dispatch should be ${dispatchGuarded ? "blocked" : "allowed"} based on its guards`,
79
+ );
80
+ const localEvaluation = evaluateDispatchTrustGuards(guards, { isLocal: true, confirmed: true, networkOpen: false });
81
+ assert.equal(localEvaluation.allowed, true, `/${surface.command.name} localhost dispatch should be allowed`);
82
+ }
83
+
84
+ assert.throws(
85
+ () => assertNativeCommandTrust(remoteReq, "export", parity),
86
+ (error) => error.statusCode === 403 && error.trust?.command === "export",
87
+ );
88
+
89
+ const parsedExport = parseSlashCommand("/export out.html", new Set(["export", "copy"]));
90
+ assert.deepEqual(parsedExport, { name: "export", args: "out.html", text: "/export out.html" });
91
+ assert.equal(parseSlashCommand("/copy", new Set(["export"])), undefined);
92
+
93
+ const success = nativeCommandResponse(
94
+ "copy",
95
+ { status: "succeeded", message: "Copied the last assistant message.", copyText: "hello" },
96
+ parity,
97
+ );
98
+ assert.equal(success.command, "native_slash_command");
99
+ assert.equal(success.success, true);
100
+ assert.equal(success.data.command, "copy");
101
+ assert.equal(success.data.status, "succeeded");
102
+ assert.ok(Array.isArray(success.data.cards) && success.data.cards.length === 1);
103
+ assert.equal(success.data.cards[0].content, "Copied the last assistant message.");
104
+ assert.deepEqual(success.data.refresh, ["state"]);
105
+
106
+ const unavailable = nativeCommandUnavailable("import", {}, parity);
107
+ assert.equal(unavailable.data.status, "unavailable");
108
+ assert.ok(unavailable.data.message.includes("/import is not available"));
109
+ assert.ok(Array.isArray(unavailable.data.cards));
110
+
111
+ const blocked = nativeCommandBlocked("export", remoteReq, parity, { networkOpen: true });
112
+ assert.equal(blocked.data.status, "blocked");
113
+ assert.match(blocked.data.message, /blocked/i);
114
+
115
+ const hotkeysSurface = nativeParitySurfaceForCommand("hotkeys", parity);
116
+ assert.equal(hotkeysSurface.webStatus, "degraded");
117
+ const hotkeys = nativeCommandResponse("hotkeys", { status: "degraded", message: "keys" }, parity);
118
+ assert.equal(hotkeys.data.status, "degraded");
119
+
120
+ const reload = nativeCommandResponse("reload", { status: "succeeded", message: "ok", tab: { id: "t1" }, refresh: ["tabs", "state", "commands"] }, parity);
121
+ assert.deepEqual(reload.data.refresh, ["tabs", "state", "commands"]);
122
+
123
+ for (const status of ["succeeded", "degraded", "unavailable", "confirmation_required", "blocked"]) {
124
+ assert.ok(NATIVE_COMMAND_STATUSES.has(status), `adapter should support status ${status}`);
125
+ }
126
+
127
+ for (const target of ["state", "tabs", "commands", "themes", "workspace"]) {
128
+ assert.ok(NATIVE_REFRESH_TARGETS.has(target), `adapter should support refresh target ${target}`);
129
+ }
130
+
131
+ const slashCommands = nativeSlashCommandEntries(parity);
132
+ assert.equal(slashCommands.length, 24);
133
+ assert.equal(slashCommands[0].name, "settings");
134
+ assert.equal(slashCommands.at(-1).name, "quit");
135
+
136
+ const warning = remoteShellTrustWarning(remoteReq, true);
137
+ assert.match(warning, /not on localhost/i);
138
+ assert.equal(remoteShellTrustWarning(localReq, true), undefined);
139
+
140
+ assert.deepEqual(rpcSuccess("get_state", { ok: true }), {
141
+ type: "response",
142
+ command: "get_state",
143
+ success: true,
144
+ data: { ok: true },
145
+ });
146
+
147
+ console.log("native-parity-harness.test.mjs passed");
@@ -108,14 +108,28 @@ for (const id of [
108
108
  const surface = parity.surfaces.find((item) => item.id === id);
109
109
  assert.ok(surface, `P0 foundation surface ${id} should be tracked`);
110
110
  assert.equal(surface.priority, "P0", `${id} should remain P0`);
111
+ assert.equal(surface.webStatus, "implemented", `${id} should be implemented`);
111
112
  }
112
113
 
113
114
  assert.match(server, /WEBUI_TUI_NATIVE_PARITY\.json/, "server should load the native parity matrix file");
114
- assert.match(server, /function nativeSlashCommandEntries\(matrix = nativeParityMatrix\)/, "server should derive native slash commands from the parity matrix");
115
- assert.match(server, /const NATIVE_SLASH_COMMANDS = nativeSlashCommandEntries\(\)/, "native slash commands should use the matrix-derived source of truth");
116
- assert.match(server, /function nativeCommandResponse\(command, data = \{\}\)/, "server should define a centralized native command adapter response helper");
117
- assert.match(server, /function nativeCommandUnavailable\(command, details = \{\}\)/, "server should define structured unavailable native command output");
118
- assert.match(server, /default:\n\s+return nativeCommandUnavailable\(parsed\.name\)/, "unsupported native commands should return structured unavailable cards instead of raw HTTP errors");
115
+ assert.match(server, /from "\.\.\/lib\/native-command-adapter\.mjs"/, "server should import the native command adapter module");
116
+ assert.match(server, /from "\.\.\/lib\/trust-boundaries\.mjs"/, "server should import the shared trust-boundaries module");
117
+ assert.match(server, /const NATIVE_SLASH_COMMANDS = nativeSlashCommandEntries\(nativeParityMatrix\)/, "native slash commands should use the matrix-derived source of truth");
118
+ assert.match(server, /const respondNative = \(command, data = \{\}\) => nativeCommandResponse\(command, data, nativeParityMatrix\)/, "server should bind native command responses to the parity matrix");
119
+ assert.match(server, /default:\n\s+return unavailableNative\(parsed\.name\)/, "unsupported native commands should return structured unavailable cards instead of raw HTTP errors");
120
+ assert.match(server, /return nativeCommandBlocked\(parsed\.name, req, nativeParityMatrix/, "guarded native commands should return blocked adapter cards for failed trust checks");
121
+ assert.match(
122
+ server,
123
+ /const evaluation = evaluateDispatchTrustGuards\(guardsForNativeCommand\(parsed\.name, nativeParityMatrix\)/,
124
+ "native command dispatch should evaluate matrix guards for every command, not only sensitive ones",
125
+ );
126
+ assert.doesNotMatch(server, /if \(surface\?\.sensitive\)/, "native command dispatch must not key trust checks on the sensitive flag");
127
+ assert.match(server, /requireLocalhostRoute\(req, url\.pathname\)/, "localhost-only API routes should use the shared trust-boundaries helper");
128
+ assert.match(server, /remoteShellTrustWarning\(req, networkStatus\(\)\.open\)/, "remote bash clients should receive LAN shell trust warnings");
129
+ assert.match(server, /url\.pathname === "\/api\/session-rename" && req\.method === "POST"/, "server should expose POST /api/session-rename for resume metadata rename");
130
+ assert.match(server, /url\.pathname === "\/api\/session-delete" && req\.method === "POST"/, "server should expose localhost-only POST /api/session-delete");
131
+ assert.match(server, /url\.pathname === "\/api\/auth-providers" && req\.method === "GET"/, "server should expose GET /api/auth-providers");
132
+ assert.match(server, /url\.pathname === "\/api\/auth-logout" && req\.method === "POST"/, "server should expose localhost-only POST /api/auth-logout");
119
133
  assert.match(server, /url\.pathname === "\/api\/native-parity" && req\.method === "GET"/, "server should expose the native parity matrix for clients/tests");
120
134
  assert.match(server, /const NATIVE_DOWNLOAD_TOKEN_TTL_MS = 10 \* 60 \* 1000/, "native downloads should use short-lived tokens");
121
135
  assert.match(server, /const WEBUI_HELPER_COMMAND = "webui-helper"/, "server should declare the hidden Web UI RPC helper command");
@@ -136,7 +150,9 @@ assert.match(server, /tab\.rpc\.send\(\{ type: "export_html", outputPath \}\)/,
136
150
  assert.match(server, /registerNativeDownload\(exportedPath/, "no-path /export should return a short-lived browser download token");
137
151
  assert.match(server, /copyFile\(sessionFile, targetPath\)/, "explicit .jsonl /export should copy the active session file");
138
152
  assert.match(app, /function triggerNativeDownload\(download\)/, "frontend should know how to trigger native command downloads");
139
- assert.match(app, /response\?\.command === "native_slash_command" && response\.data\?\.download/, "frontend should handle download responses from native commands");
153
+ assert.match(app, /function applyNativeSlashCommandEffects\(response, message, tabContext/, "frontend should apply centralized native slash-command adapter effects");
154
+ assert.match(app, /data\.download && triggerNativeDownload\(data\.download\)/, "frontend should handle download responses from native commands");
155
+ assert.match(app, /for \(const warning of response\.warnings/, "frontend should surface remote bash trust warnings");
140
156
  assert.match(server, /case "\/api\/bash": \{[\s\S]*?return \{ type: "bash", command, excludeFromContext: body\.excludeFromContext === true \}/, "server should expose RPC bash with include/exclude context semantics");
141
157
  assert.match(server, /case "\/api\/abort-bash":[\s\S]*?return \{ type: "abort_bash" \}/, "server should expose abort_bash for user bash cancellation");
142
158
  assert.match(app, /function parseUserBashInput\(message\)[\s\S]*?text\.startsWith\("!!"\)/, "frontend should detect !! bash commands before prompt forwarding");
@@ -166,4 +182,7 @@ assert.match(app, /enqueueUserBashCommand\(parsed, \{ usesPromptInput, targetTab
166
182
  assert.match(server, /function sendQueuedBashCommand\(tab, command\)/, "server should serialize user bash commands per tab");
167
183
  assert.match(server, /command\.type === "bash"[\s\S]*?await sendQueuedBashCommand\(tab, command\)[\s\S]*?: await tab\.rpc\.send\(command\)/, "generic POST handling should route bash through the FIFO queue");
168
184
  assert.ok(pkg.files.includes("WEBUI_TUI_NATIVE_PARITY.json"), "published package should include the native parity matrix");
185
+ assert.ok(pkg.files.includes("lib"), "published package should include shared Web UI foundation modules");
169
186
  assert.ok(pkg.files.includes("webui-rpc-helper.mjs"), "published package should include the Web UI RPC helper extension");
187
+
188
+ console.log("native-parity.test.mjs passed");
@@ -0,0 +1,19 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import { readdir } from "node:fs/promises";
3
+ import { dirname, join } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+
6
+ const testsDir = dirname(fileURLToPath(import.meta.url));
7
+ const files = (await readdir(testsDir)).filter((name) => name.endsWith(".test.mjs")).sort();
8
+
9
+ const failures = [];
10
+ for (const file of files) {
11
+ const result = spawnSync(process.execPath, [join(testsDir, file)], { stdio: "inherit" });
12
+ if (result.status !== 0) failures.push(`${file} (exit ${result.status ?? "signal"})`);
13
+ }
14
+
15
+ if (failures.length) {
16
+ console.error(`\n${failures.length}/${files.length} test file(s) failed:\n ${failures.join("\n ")}`);
17
+ process.exit(1);
18
+ }
19
+ console.log(`\nall ${files.length} test files passed`);
@@ -0,0 +1,140 @@
1
+ import assert from "node:assert/strict";
2
+ import { existsSync } from "node:fs";
3
+ import { mkdtemp, rm, writeFile } from "node:fs/promises";
4
+ import { tmpdir } from "node:os";
5
+ import path from "node:path";
6
+ import { SessionManager } from "@earendil-works/pi-coding-agent";
7
+ import { authProvidersPayload, createAuthContext } from "../lib/auth-actions.mjs";
8
+ import {
9
+ collectOpenSessionFiles,
10
+ deleteSessionFile,
11
+ isSessionPathAllowed,
12
+ renameSessionMetadata,
13
+ validateSessionDelete,
14
+ } from "../lib/session-actions.mjs";
15
+ import { LOCALHOST_ONLY_POST_ROUTES } from "../lib/trust-boundaries.mjs";
16
+
17
+ function sessionHeaderLine(cwd, id = "sample-session") {
18
+ return `${JSON.stringify({
19
+ type: "session",
20
+ version: 3,
21
+ id,
22
+ timestamp: new Date().toISOString(),
23
+ cwd,
24
+ })}\n`;
25
+ }
26
+
27
+ const tempDir = await mkdtemp(path.join(tmpdir(), "pi-webui-session-auth-"));
28
+ const outsideDir = await mkdtemp(path.join(tmpdir(), "pi-webui-session-outside-"));
29
+ try {
30
+ const createdPath = path.join(tempDir, "sample.jsonl");
31
+ await writeFile(createdPath, sessionHeaderLine(tempDir), "utf8");
32
+
33
+ const renamed = await renameSessionMetadata(createdPath, "Live test session", tempDir);
34
+ assert.equal(renamed.name, "Live test session");
35
+
36
+ const reopened = SessionManager.open(createdPath, tempDir);
37
+ assert.equal(reopened.getSessionName(), "Live test session");
38
+
39
+ const openFiles = collectOpenSessionFiles([
40
+ { sessionFile: createdPath },
41
+ { lastState: { sessionFile: "/other/session.jsonl" } },
42
+ ]);
43
+ assert.ok(openFiles.has(path.resolve(createdPath)));
44
+
45
+ const needsConfirm = validateSessionDelete(createdPath, {
46
+ openSessionFiles: new Set(),
47
+ currentSessionFile: undefined,
48
+ confirmed: false,
49
+ });
50
+ assert.equal(needsConfirm.allowed, false);
51
+ assert.equal(needsConfirm.reason, "confirmation_required");
52
+
53
+ const blockedActive = validateSessionDelete(createdPath, {
54
+ openSessionFiles: new Set(),
55
+ currentSessionFile: createdPath,
56
+ confirmed: true,
57
+ });
58
+ assert.equal(blockedActive.allowed, false);
59
+ assert.equal(blockedActive.reason, "active_session");
60
+
61
+ const blockedOpenTab = validateSessionDelete(createdPath, {
62
+ openSessionFiles: new Set([path.resolve(createdPath)]),
63
+ currentSessionFile: undefined,
64
+ confirmed: true,
65
+ });
66
+ assert.equal(blockedOpenTab.allowed, false);
67
+ assert.equal(blockedOpenTab.reason, "session_in_use");
68
+
69
+ // Session-directory confinement (path traversal hardening).
70
+ const outsidePath = path.join(outsideDir, "outside.jsonl");
71
+ await writeFile(outsidePath, sessionHeaderLine(outsideDir, "outside-session"), "utf8");
72
+
73
+ assert.equal(isSessionPathAllowed(createdPath, [tempDir]), true);
74
+ assert.equal(isSessionPathAllowed(outsidePath, [tempDir]), false);
75
+ assert.equal(isSessionPathAllowed(path.join(tempDir, "..", "escape.jsonl"), [tempDir]), false);
76
+ assert.equal(isSessionPathAllowed(outsidePath, []), true, "empty allowedDirs must mean no confinement");
77
+
78
+ const blockedOutside = validateSessionDelete(outsidePath, {
79
+ openSessionFiles: new Set(),
80
+ currentSessionFile: undefined,
81
+ confirmed: true,
82
+ allowedDirs: [tempDir],
83
+ });
84
+ assert.equal(blockedOutside.allowed, false);
85
+ assert.equal(blockedOutside.reason, "outside_session_dir");
86
+
87
+ const allowedInside = validateSessionDelete(createdPath, {
88
+ openSessionFiles: new Set(),
89
+ currentSessionFile: undefined,
90
+ confirmed: true,
91
+ allowedDirs: [tempDir],
92
+ });
93
+ assert.equal(allowedInside.allowed, true);
94
+
95
+ await assert.rejects(
96
+ renameSessionMetadata(outsidePath, "blocked rename", tempDir, { allowedDirs: [tempDir] }),
97
+ /session directory/i,
98
+ "rename outside the session dir must be rejected",
99
+ );
100
+
101
+ await assert.rejects(
102
+ deleteSessionFile(outsidePath, { allowedDirs: [tempDir] }),
103
+ /session directory/i,
104
+ "delete outside the session dir must be rejected",
105
+ );
106
+ assert.ok(existsSync(outsidePath), "blocked delete must not remove the file");
107
+
108
+ // Unlink fallback: force `trash` lookup failure via empty PATH, file must still go away.
109
+ const deletablePath = path.join(tempDir, "deletable.jsonl");
110
+ await writeFile(deletablePath, sessionHeaderLine(tempDir, "deletable-session"), "utf8");
111
+ const savedPath = process.env.PATH;
112
+ process.env.PATH = path.join(tempDir, "no-bin");
113
+ let deleted;
114
+ try {
115
+ deleted = await deleteSessionFile(deletablePath, { allowedDirs: [tempDir] });
116
+ } finally {
117
+ process.env.PATH = savedPath;
118
+ }
119
+ assert.equal(deleted.method, "unlink");
120
+ assert.equal(existsSync(deletablePath), false);
121
+
122
+ const auth = createAuthContext();
123
+ const payload = authProvidersPayload(auth.modelRegistry);
124
+ assert.ok(Array.isArray(payload.loginProviders));
125
+ assert.ok(Array.isArray(payload.logoutProviders));
126
+ assert.equal(payload.browserLoginSupported, false);
127
+ assert.match(payload.guidance, /Pi TUI/i);
128
+
129
+ assert.ok(LOCALHOST_ONLY_POST_ROUTES.has("/api/session-delete"));
130
+ assert.ok(LOCALHOST_ONLY_POST_ROUTES.has("/api/auth-logout"));
131
+ assert.ok(
132
+ LOCALHOST_ONLY_POST_ROUTES.has("/api/network/close"),
133
+ "closing network access must be localhost-only like opening it",
134
+ );
135
+ } finally {
136
+ await rm(tempDir, { recursive: true, force: true });
137
+ await rm(outsideDir, { recursive: true, force: true });
138
+ }
139
+
140
+ console.log("session-auth-harness.test.mjs passed");
@@ -0,0 +1,38 @@
1
+ import assert from "node:assert/strict";
2
+ import { mkdir, mkdtemp, readdir, rm, utimes, writeFile } from "node:fs/promises";
3
+ import { tmpdir } from "node:os";
4
+ import path from "node:path";
5
+ import { sweepStaleTempEntries } from "../lib/temp-artifacts.mjs";
6
+
7
+ const root = await mkdtemp(path.join(tmpdir(), "pi-webui-temp-sweep-"));
8
+ try {
9
+ const missing = await sweepStaleTempEntries(path.join(root, "missing"), { ttlMs: 1_000 });
10
+ assert.deepEqual(missing, [], "missing directory must sweep to nothing without throwing");
11
+
12
+ const staleDir = path.join(root, "stale-upload");
13
+ await mkdir(staleDir, { recursive: true });
14
+ await writeFile(path.join(staleDir, "attachment.txt"), "old", "utf8");
15
+ const freshDir = path.join(root, "fresh-upload");
16
+ await mkdir(freshDir, { recursive: true });
17
+ const staleFile = path.join(root, "stale-export.html");
18
+ await writeFile(staleFile, "old", "utf8");
19
+ const freshFile = path.join(root, "fresh-export.html");
20
+ await writeFile(freshFile, "new", "utf8");
21
+
22
+ const past = new Date(Date.now() - 60_000);
23
+ await utimes(staleDir, past, past);
24
+ await utimes(staleFile, past, past);
25
+
26
+ const removed = await sweepStaleTempEntries(root, { ttlMs: 30_000 });
27
+ assert.deepEqual(new Set(removed), new Set([staleDir, staleFile]), "only entries older than the TTL are removed");
28
+
29
+ const remaining = (await readdir(root)).sort();
30
+ assert.deepEqual(remaining, ["fresh-export.html", "fresh-upload"], "fresh entries must survive the sweep");
31
+
32
+ const repeat = await sweepStaleTempEntries(root, { ttlMs: 30_000 });
33
+ assert.deepEqual(repeat, [], "repeat sweep with no stale entries removes nothing");
34
+ } finally {
35
+ await rm(root, { recursive: true, force: true });
36
+ }
37
+
38
+ console.log("temp-artifacts-harness.test.mjs passed");