Merge upstream/master into dev (13 commits — includes #5922, #5938, blocked inbox, recovery actions)

This commit is contained in:
2026-05-13 22:35:18 -04:00
180 changed files with 31626 additions and 545 deletions
@@ -420,6 +420,12 @@ export interface ManagedRoutineMissingRef {
resourceKey: string;
}
export interface ManagedRoutineDefaultDrift {
changedFields: string[];
defaultTitle?: string | null;
defaultDescription?: string | null;
}
export interface ManagedRoutinesListItem {
key: string;
title: string;
@@ -434,6 +440,7 @@ export interface ManagedRoutinesListItem {
lastRunStatus?: string | null;
managedByPluginDisplayName?: string | null;
missingRefs?: ManagedRoutineMissingRef[];
defaultDrift?: ManagedRoutineDefaultDrift | null;
}
export interface ManagedRoutinesListProps {
+17 -3
View File
@@ -34,6 +34,7 @@
* @see PLUGIN_SPEC.md §14 — SDK Surface
*/
import fs from "node:fs";
import path from "node:path";
import { createInterface, type Interface as ReadlineInterface } from "node:readline";
import { fileURLToPath } from "node:url";
@@ -175,6 +176,21 @@ interface EventRegistration {
/** Default timeout for worker→host RPC calls. */
const DEFAULT_RPC_TIMEOUT_MS = 30_000;
function realpathOrResolvedPath(filePath: string): string {
const resolvedPath = path.resolve(filePath);
try {
return fs.realpathSync.native(resolvedPath);
} catch {
return resolvedPath;
}
}
export function isWorkerEntrypoint(entry: string, moduleUrl: string): boolean {
const thisFile = realpathOrResolvedPath(fileURLToPath(moduleUrl));
const entryPath = realpathOrResolvedPath(entry);
return thisFile === entryPath;
}
// ---------------------------------------------------------------------------
// startWorkerRpcHost
// ---------------------------------------------------------------------------
@@ -223,9 +239,7 @@ export function runWorker(
}
const entry = process.argv[1];
if (typeof entry !== "string") return;
const thisFile = path.resolve(fileURLToPath(moduleUrl));
const entryPath = path.resolve(entry);
if (thisFile === entryPath) {
if (isWorkerEntrypoint(entry, moduleUrl)) {
startWorkerRpcHost({ plugin });
}
}
@@ -0,0 +1,57 @@
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { pathToFileURL } from "node:url";
import { afterEach, describe, expect, it } from "vitest";
import { isWorkerEntrypoint } from "../src/worker-rpc-host.js";
describe("isWorkerEntrypoint", () => {
const tempRoots: string[] = [];
afterEach(() => {
for (const tempRoot of tempRoots.splice(0)) {
fs.rmSync(tempRoot, { recursive: true, force: true });
}
});
function createTempRoot(): string {
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-sdk-worker-"));
tempRoots.push(tempRoot);
return tempRoot;
}
it("matches an entrypoint reached through a symlinked directory", () => {
const tempRoot = createTempRoot();
const realDir = path.join(tempRoot, "real");
const linkDir = path.join(tempRoot, "link");
fs.mkdirSync(realDir);
fs.symlinkSync(realDir, linkDir, "dir");
const workerPath = path.join(realDir, "worker.js");
fs.writeFileSync(workerPath, "");
expect(
isWorkerEntrypoint(
path.join(linkDir, "worker.js"),
pathToFileURL(workerPath).toString(),
),
).toBe(true);
});
it("does not match a different entrypoint", () => {
const tempRoot = createTempRoot();
const workerPath = path.join(tempRoot, "worker.js");
const otherPath = path.join(tempRoot, "other.js");
fs.writeFileSync(workerPath, "");
fs.writeFileSync(otherPath, "");
expect(
isWorkerEntrypoint(
otherPath,
pathToFileURL(workerPath).toString(),
),
).toBe(false);
});
});
+8
View File
@@ -0,0 +1,8 @@
import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
environment: "node",
include: ["tests/**/*.test.ts"],
},
});