feat: add opt-in chat provider and data foundation (#13100)

Add dormant provider contracts, qualified patched adapters, tenant-scoped persistence and lifecycle ownership without activating chat routes. Preserve the experimental integration as dependent PR #13038.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip authored and GitHub committed 2026-09-09 13:49:12 -05:00
1 parent 7d84b183fb
commit 6abeb67334
143 files changed
+789391 -162

No files matched your search

+86 -6
View File
@@ -49,6 +49,64 @@ const claudeAcpPatch = await readFile(
"utf8",
);
for (const version of ["0.12.0", "0.13.1"]) {
test(`ACPX ${version} release patch uses portable generated unified hunks`, async () => {
const patch = await readFile(
new URL(`../patches/acpx@${version}.patch`, import.meta.url),
"utf8",
);
const lines = patch.split("\n");
let hunkCount = 0;
for (let index = 0; index < lines.length; index += 1) {
const header = lines[index].match(
/^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/,
);
if (!header) continue;
hunkCount += 1;
const body = [];
let oldLines = 0;
let newLines = 0;
while (index + 1 < lines.length && (oldLines < Number(header[2] ?? 1) || newLines < Number(header[4] ?? 1))) {
const line = lines[++index];
// Unified diff's EOF marker is metadata, not a source/destination
// line, and may occur between the removed and added final lines.
if (line === "\\ No newline at end of file") continue;
// Git accepts an empty context line with its optional space omitted.
assert.ok(line === "" || /^[ +\-]/.test(line), `invalid unified hunk line: ${line}`);
const normalized = line === "" ? " " : line;
body.push(normalized);
if (!normalized.startsWith("+")) oldLines += 1;
if (!normalized.startsWith("-")) newLines += 1;
}
assert.equal(
body.filter((line) => !line.startsWith("+")).length,
Number(header[2] ?? 1),
);
assert.equal(
body.filter((line) => !line.startsWith("-")).length,
Number(header[4] ?? 1),
);
const prefix = body.findIndex((line) => !line.startsWith(" "));
const suffix = body
.slice()
.reverse()
.findIndex((line) => !line.startsWith(" "));
// pnpm patch-commit emits three context lines. Hand-added asymmetric
// context can force GNU patch's locate_hunk() to require EOF even when
// BSD patch and git apply accept the same source and hunk.
assert.ok(
prefix >= 0 && prefix <= 3,
`regenerate ${version} hunk at old line ${header[1]} with pnpm patch-commit (prefix ${prefix})`,
);
assert.ok(
suffix >= 0 && suffix <= 3,
`regenerate ${version} hunk at old line ${header[1]} with pnpm patch-commit (suffix ${suffix})`,
);
}
assert.ok(hunkCount > 0);
});
}
test("published packages preserve the patched ACPX runtime", () => {
assert.equal(
rootPackage.pnpm.patchedDependencies["acpx@0.12.0"],
@@ -61,7 +119,7 @@ test("published packages preserve the patched ACPX runtime", () => {
assert.equal(adapterUtilsPackage.dependencies.acpx, "0.12.0");
assert.deepEqual(adapterUtilsPackage.bundleDependencies, ["acpx"]);
assert.equal(serverPackage.dependencies.acpx, "0.13.1");
assert.deepEqual(serverPackage.bundleDependencies, ["acpx"]);
assert.ok(serverPackage.bundleDependencies.includes("acpx"));
assert.equal(bundledCliNpmDependencies.has("acpx"), true);
assert.equal(cliEsbuildConfig.external.includes("acpx"), false);
});
@@ -226,7 +284,7 @@ test("bundled package patch selection rejects an unpatched installed version", (
);
});
test("server package staging bundles and patches the vendored runner's acpx runtime", (t) => {
test("server package staging applies every bundled runtime patch and preserves the vendored runner", (t) => {
const fixtureDir = mkdtempSync(join(tmpdir(), "paperclip-bundled-stage-"));
const sourceDir = join(fixtureDir, "source");
const destinationDir = join(fixtureDir, "destination");
@@ -263,7 +321,7 @@ mkdir -p "$destination/node_modules/.pnpm"
set -euo pipefail
printf 'npm %s\\n' "$*" >> "$FAKE_CALL_LOG"
[ "$*" = "install --omit=dev --ignore-scripts --no-audit --no-fund" ]
node -e 'const pkg = require("./package.json"); if ("devDependencies" in pkg) process.exit(1)'
node -e 'const fs = require("node:fs"); const pkg = require("./package.json"); if ("devDependencies" in pkg) process.exit(1); for (const [name, version] of Object.entries(pkg.dependencies)) { const dir = "node_modules/" + name; fs.mkdirSync(dir + "/dist", { recursive: true }); fs.writeFileSync(dir + "/package.json", JSON.stringify({ name, version })); }'
mkdir -p node_modules/acpx/dist
printf 'unpatched runtime\\n' > node_modules/acpx/dist/runtime.js
printf '{"name":"acpx","version":"0.13.1"}\\n' > node_modules/acpx/package.json
@@ -284,6 +342,10 @@ while [ "$#" -gt 0 ]; do
fi
done
patch_input="$(cat)"
printf '%s\\n' "$patch_input" > "$target/applied.patch"
if [[ "$target" != */acpx ]]; then
exit 0
fi
grep -q spawnEnvironment <<< "$patch_input"
grep -q spawnAgent <<< "$patch_input"
grep -q onAgentStderr <<< "$patch_input"
@@ -293,7 +355,11 @@ printf 'patched spawnEnvironment runtime\\n' > "$target/dist/runtime.js"
execFileSync(
process.execPath,
[new URL("./prepare-bundled-package.mjs", import.meta.url).pathname, sourceDir, destinationDir],
[
new URL("./prepare-bundled-package.mjs", import.meta.url).pathname,
sourceDir,
destinationDir,
],
{
env: {
...process.env,
@@ -318,9 +384,23 @@ printf 'patched spawnEnvironment runtime\\n' > "$target/dist/runtime.js"
/patch -p1 --forward -d .*node_modules\/acpx/,
);
assert.equal(
readFileSync(callLog, "utf8").split("\n").filter((line) => line.startsWith("patch ")).length,
1,
readFileSync(callLog, "utf8")
.split("\n")
.filter((line) => line.startsWith("patch ")).length,
serverPackage.bundleDependencies.length,
);
for (const name of serverPackage.bundleDependencies) {
const specifier = `${name}@${serverPackage.dependencies[name]}`;
const patchPath = rootPackage.pnpm.patchedDependencies[specifier];
assert.equal(
readFileSync(
join(destinationDir, "node_modules", name, "applied.patch"),
"utf8",
),
`${readFileSync(new URL(`../${patchPath}`, import.meta.url), "utf8").trimEnd()}\n`,
`${specifier} receives its own full configured patch`,
);
}
});
test("bundled package dry runs preview without querying published versions", () => {
@@ -0,0 +1,122 @@
import assert from "node:assert/strict";
import { execFileSync } from "node:child_process";
import {
mkdirSync,
mkdtempSync,
readFileSync,
rmSync,
writeFileSync,
} from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { fileURLToPath } from "node:url";
import test from "node:test";
import {
createBundledInstallManifest,
materializePublishManifest,
selectBundledDependencyPatches,
} from "./prepare-bundled-package.mjs";
const repoRoot = fileURLToPath(new URL("..", import.meta.url));
const rootPackage = JSON.parse(
readFileSync(new URL("../package.json", import.meta.url), "utf8"),
);
const serverPackage = JSON.parse(
readFileSync(new URL("../server/package.json", import.meta.url), "utf8"),
);
const workspace = readFileSync(
new URL("../pnpm-workspace.yaml", import.meta.url),
"utf8",
);
const required = [
["@chat-adapter/discord", "4.39.0"],
["@chat-adapter/github", "4.39.0"],
["@chat-adapter/slack", "4.39.0"],
["@chat-adapter/teams", "4.39.0"],
["@chat-adapter/telegram", "4.39.0"],
["@discordjs/ws", "1.2.3"],
];
for (const [name, version] of required) {
test(`published server retains the patched ${name}@${version} runtime`, () => {
const published = materializePublishManifest(serverPackage);
const installed = createBundledInstallManifest(
published,
serverPackage.bundleDependencies,
);
assert.equal(
serverPackage.dependencies[name],
version,
"the patched runtime must have an exact direct version",
);
assert.ok(
serverPackage.bundleDependencies.includes(name),
"npm consumers cannot apply this repository's pnpm patches",
);
assert.equal(
installed.dependencies[name],
version,
"release staging must install the runtime before patching",
);
assert.ok(
published.bundleDependencies.includes(name),
"the patched runtime must remain in the published tarball",
);
const specifier = `${name}@${version}`;
const patchPath = rootPackage.pnpm.patchedDependencies[specifier];
assert.equal(typeof patchPath, "string");
assert.ok(
workspace.includes(`"${specifier}": ${patchPath}`),
"both supported pnpm configuration paths must match",
);
// Parse the actual full patch, not merely its configured filename. This is
// read-only; clean-package materialization is a separate qualification.
execFileSync("git", ["apply", "--stat", patchPath], {
cwd: repoRoot,
stdio: "pipe",
});
});
}
test("release selection includes every chat runtime patch and the existing ACPX patch", (t) => {
const destination = mkdtempSync(
join(tmpdir(), "paperclip-chat-release-contract-"),
);
t.after(() => rmSync(destination, { recursive: true, force: true }));
for (const [name, version] of [...required, ["acpx", "0.13.1"]]) {
const directory = join(destination, "node_modules", name);
mkdirSync(directory, { recursive: true });
writeFileSync(
join(directory, "package.json"),
JSON.stringify({ name, version }),
);
}
const selected = selectBundledDependencyPatches(
destination,
serverPackage.bundleDependencies,
rootPackage.pnpm.patchedDependencies,
);
assert.deepEqual(
selected.map(({ specifier }) => specifier).sort(),
[
...required.map(([name, version]) => `${name}@${version}`),
"acpx@0.13.1",
].sort(),
);
writeFileSync(
join(destination, "node_modules", "@discordjs/ws", "package.json"),
JSON.stringify({
name: "@discordjs/ws",
version: "1.2.4",
}),
);
assert.throws(
() =>
selectBundledDependencyPatches(
destination,
serverPackage.bundleDependencies,
rootPackage.pnpm.patchedDependencies,
),
/installed @discordjs\/ws@1\.2\.4/,
);
});