Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/sdk-node-engines-floor.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
---
"@openagentpack/sdk": patch
---

Lower the SDK `engines` floor from Node `>=22` to `>=18.17.0`.

The SDK runtime only relies on APIs available since Node 18.17 (`node:fs`/`path`/`crypto`/`os`, global `fetch`/`FormData`, `structuredClone`, web streams), so the previous floor was a repository baseline rather than a runtime requirement. The tsup build target is pinned to `es2022` so emitted syntax stays parseable on the new floor, and CI now runs an SDK-only packed-install smoke on Node 18 and 20 (`smoke-packed.ts --sdk-only`) to enforce the contract. The `@openagentpack/cli` and `@openagentpack/playground` packages keep their Node `>=22` requirement.
25 changes: 21 additions & 4 deletions .github/workflows/ci.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -52,12 +52,23 @@ jobs:
run: bun scripts/verify.ts full --step "${{ matrix.step }}"

package-compatibility:
name: Package compatibility / Node ${{ matrix.node }}
name: Package compatibility / Node ${{ matrix.node }}${{ matrix.scope == 'sdk' && ' (sdk-only)' || '' }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
node: [22, 24]
include:
# Full consumer smoke (sdk + playground + cli) on the repo baseline.
- node: 22
scope: all
- node: 24
scope: all
# SDK-only smoke on the sdk engines floor (>=18.17.0): cli/playground
# require Node >=22, so only the sdk tarball is installed and imported.
- node: 18.17.0
scope: sdk
- node: 20
scope: sdk
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
Expand All@@ -68,8 +79,14 @@ jobs:
node-version: ${{ matrix.node }}
package-manager-cache: false
- run: bun install --frozen-lockfile
- run: bun scripts/verify.ts release --step build-packages
- run: bun scripts/verify.ts release --step package-smoke
- if: matrix.scope == 'all'
run: |
bun scripts/verify.ts release --step build-packages
bun scripts/verify.ts release --step package-smoke
- if: matrix.scope == 'sdk'
run: |
bun run build:sdk
bun scripts/release/smoke-packed.ts --sdk-only

gate:
name: Gate
Expand Down
2 changes: 1 addition & 1 deletion packages/sdk/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,7 +20,7 @@
"url": "https://github.com/modelstudioai/OpenAgentPack/issues"
},
"engines": {
"node": ">=22"
"node": ">=18.17.0"
},
"type": "module",
"main": "./dist/index.js",
Expand Down
4 changes: 3 additions & 1 deletion packages/sdk/tsup.config.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -15,5 +15,7 @@ export default defineConfig({
},
},
clean: true,
target: "esnext",
// Keep the emitted syntax parseable by Node 18.17 (the package's engines
// floor): esnext would let future syntax (e.g. `using`) leak into dist.
target: "es2022",
});
9 changes: 7 additions & 2 deletions scripts/open-source.test.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -53,11 +53,16 @@ describe("open-source repository invariants", () => {
});

test("public packages require a maintained Node.js baseline", () => {
for (const pkg of ["sdk", "playground", "cli"]) {
const nodeBaselines: Record<string, string> = {
sdk: ">=18.17.0",
playground: ">=22",
cli: ">=22",
};
for (const [pkg, expectedNodeRange] of Object.entries(nodeBaselines)) {
const manifest = JSON.parse(readFileSync(resolve(root, `packages/${pkg}/package.json`), "utf8")) as {
engines?: { node?: string };
};
expect(manifest.engines?.node).toBe(">=22");
expect(manifest.engines?.node).toBe(expectedNodeRange);
}
});

Expand Down
18 changes: 17 additions & 1 deletion scripts/release/smoke-packed.test.ts
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
import { describe, expect, test } from "bun:test";
import { packedFilename } from "./smoke-packed.ts";
import { isSdkOnly, packedFilename, smokePackages } from "./smoke-packed.ts";

describe("npm pack output", () => {
test("accepts the npm 10 and 11 array format", () => {
Expand All@@ -14,3 +14,19 @@ describe("npm pack output", () => {
expect(() => packedFilename("[]", "sdk")).toThrow("Unexpected npm pack output for sdk");
});
});

describe("--sdk-only mode", () => {
test("detects the flag among CLI arguments", () => {
expect(isSdkOnly(["--sdk-only"])).toBe(true);
expect(isSdkOnly([])).toBe(false);
expect(isSdkOnly(["--verbose"])).toBe(false);
});

test("restricts the package set to sdk when enabled", () => {
expect(smokePackages(true)).toEqual(["sdk"]);
});

test("keeps the full package set when disabled", () => {
expect(smokePackages(false)).toEqual(["sdk", "playground", "cli"]);
});
});
51 changes: 34 additions & 17 deletions scripts/release/smoke-packed.ts
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
/**
* Pack the exact publish manifests, install them as an external npm consumer,
* then exercise every public SDK entry point plus the CLI and Playground bins.
*
* `--sdk-only` restricts the run to the SDK package: pack/install only the sdk
* tarball (still with --engine-strict) and execute the SDK entry-point imports.
* This is the mode CI uses on Node versions below the cli/playground engines
* floor (>=22) to enforce the SDK's own >=18.17.0 engines contract.
*/

import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
Expand All@@ -18,6 +23,14 @@ import {

const root = resolve(import.meta.dirname, "../..");

export function isSdkOnly(argv: readonly string[]): boolean {
return argv.includes("--sdk-only");
}

export function smokePackages(sdkOnly: boolean): readonly (typeof PACKAGES)[number][] {
return sdkOnly ? PACKAGES.filter((pkg) => pkg === "sdk") : PACKAGES;
}

type PackedPackage = { filename: string };

export function packedFilename(raw: string, pkg: string): string {
Expand All@@ -33,9 +46,9 @@ function run(command: string[], cwd: string, stdout: "inherit" | "pipe" = "inher
return stdout === "pipe" ? (result.stdout?.toString().trim() ?? "") : "";
}

function packPackages(destination: string): string[] {
function packPackages(destination: string, packages: readonly (typeof PACKAGES)[number][]): string[] {
const versions = workspaceVersions();
return PACKAGES.map((pkg) => {
return packages.map((pkg) => {
const pkgDir = join(root, "packages", pkg);
let originalLicense: string | undefined;
let originalManifest: string | undefined;
Expand DownExpand Up@@ -104,6 +117,8 @@ async function smokePlayground(consumer: string): Promise<void> {
}

async function main(): Promise<void> {
const sdkOnly = isSdkOnly(process.argv.slice(2));
const packages = smokePackages(sdkOnly);
const temporaryRoot = mkdtempSync(join(tmpdir(), "openagentpack-consumer-"));
try {
const tarballsDir = join(temporaryRoot, "tarballs");
Expand All@@ -112,7 +127,7 @@ async function main(): Promise<void> {
mkdirSync(consumer);
writeFileSync(join(consumer, "package.json"), '{"name":"agents-package-smoke","private":true,"type":"module"}\n');

const tarballs = packPackages(tarballsDir);
const tarballs = packPackages(tarballsDir, packages);
run(
[
"npm",
Expand All@@ -126,7 +141,7 @@ async function main(): Promise<void> {
],
consumer,
);
for (const pkg of PACKAGES) {
for (const pkg of packages) {
const license = readFileSync(join(consumer, `node_modules/@openagentpack/${pkg}/LICENSE`), "utf8");
if (license !== readFileSync(join(root, "LICENSE"), "utf8")) {
throw new Error(`${pkg} package is missing the repository license`);
Expand All@@ -143,21 +158,23 @@ async function main(): Promise<void> {
consumer,
);

const expectedVersion = JSON.parse(readFileSync(join(root, "packages/cli/package.json"), "utf8")) as {
version: string;
};
const cliVersion = run(
["node", join(consumer, "node_modules/@openagentpack/cli/dist/bin/agents.js"), "--version"],
consumer,
"pipe",
);
if (cliVersion !== expectedVersion.version) {
throw new Error(`Packed CLI version mismatch: expected ${expectedVersion.version}, received ${cliVersion}`);
}
if (!sdkOnly) {
const expectedVersion = JSON.parse(readFileSync(join(root, "packages/cli/package.json"), "utf8")) as {
version: string;
};
const cliVersion = run(
["node", join(consumer, "node_modules/@openagentpack/cli/dist/bin/agents.js"), "--version"],
consumer,
"pipe",
);
if (cliVersion !== expectedVersion.version) {
throw new Error(`Packed CLI version mismatch: expected ${expectedVersion.version}, received ${cliVersion}`);
}

await smokePlayground(consumer);
await smokePlayground(consumer);
}
const nodeVersion = run(["node", "--version"], consumer, "pipe");
console.log(`✓ Packed packages install and run under ${nodeVersion}`);
console.log(`✓ Packed ${sdkOnly ? "SDK package installs" : "packages install"} and run under ${nodeVersion}`);
} finally {
rmSync(temporaryRoot, { recursive: true, force: true });
}
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/sdk-node-engines-floor.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
---
"@openagentpack/sdk": patch
---

Lower the SDK `engines` floor from Node `>=22` to `>=18.17.0`.

The SDK runtime only relies on APIs available since Node 18.17 (`node:fs`/`path`/`crypto`/`os`, global `fetch`/`FormData`, `structuredClone`, web streams), so the previous floor was a repository baseline rather than a runtime requirement. The tsup build target is pinned to `es2022` so emitted syntax stays parseable on the new floor, and CI now runs an SDK-only packed-install smoke on Node 18 and 20 (`smoke-packed.ts --sdk-only`) to enforce the contract. The `@openagentpack/cli` and `@openagentpack/playground` packages keep their Node `>=22` requirement.
25 changes: 21 additions & 4 deletions .github/workflows/ci.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -52,12 +52,23 @@ jobs:
run: bun scripts/verify.ts full --step "${{ matrix.step }}"

package-compatibility:
name: Package compatibility / Node ${{ matrix.node }}
name: Package compatibility / Node ${{ matrix.node }}${{ matrix.scope == 'sdk' && ' (sdk-only)' || '' }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
node: [22, 24]
include:
# Full consumer smoke (sdk + playground + cli) on the repo baseline.
- node: 22
scope: all
- node: 24
scope: all
# SDK-only smoke on the sdk engines floor (>=18.17.0): cli/playground
# require Node >=22, so only the sdk tarball is installed and imported.
- node: 18.17.0
scope: sdk
- node: 20
scope: sdk
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
Expand All@@ -68,8 +79,14 @@ jobs:
node-version: ${{ matrix.node }}
package-manager-cache: false
- run: bun install --frozen-lockfile
- run: bun scripts/verify.ts release --step build-packages
- run: bun scripts/verify.ts release --step package-smoke
- if: matrix.scope == 'all'
run: |
bun scripts/verify.ts release --step build-packages
bun scripts/verify.ts release --step package-smoke
- if: matrix.scope == 'sdk'
run: |
bun run build:sdk
bun scripts/release/smoke-packed.ts --sdk-only

gate:
name: Gate
Expand Down
2 changes: 1 addition & 1 deletion packages/sdk/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,7 +20,7 @@
"url": "https://github.com/modelstudioai/OpenAgentPack/issues"
},
"engines": {
"node": ">=22"
"node": ">=18.17.0"
},
"type": "module",
"main": "./dist/index.js",
Expand Down
4 changes: 3 additions & 1 deletion packages/sdk/tsup.config.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -15,5 +15,7 @@ export default defineConfig({
},
},
clean: true,
target: "esnext",
// Keep the emitted syntax parseable by Node 18.17 (the package's engines
// floor): esnext would let future syntax (e.g. `using`) leak into dist.
target: "es2022",
});
9 changes: 7 additions & 2 deletions scripts/open-source.test.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -53,11 +53,16 @@ describe("open-source repository invariants", () => {
});

test("public packages require a maintained Node.js baseline", () => {
for (const pkg of ["sdk", "playground", "cli"]) {
const nodeBaselines: Record<string, string> = {
sdk: ">=18.17.0",
playground: ">=22",
cli: ">=22",
};
for (const [pkg, expectedNodeRange] of Object.entries(nodeBaselines)) {
const manifest = JSON.parse(readFileSync(resolve(root, `packages/${pkg}/package.json`), "utf8")) as {
engines?: { node?: string };
};
expect(manifest.engines?.node).toBe(">=22");
expect(manifest.engines?.node).toBe(expectedNodeRange);
}
});

Expand Down
18 changes: 17 additions & 1 deletion scripts/release/smoke-packed.test.ts
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
import { describe, expect, test } from "bun:test";
import { packedFilename } from "./smoke-packed.ts";
import { isSdkOnly, packedFilename, smokePackages } from "./smoke-packed.ts";

describe("npm pack output", () => {
test("accepts the npm 10 and 11 array format", () => {
Expand All@@ -14,3 +14,19 @@ describe("npm pack output", () => {
expect(() => packedFilename("[]", "sdk")).toThrow("Unexpected npm pack output for sdk");
});
});

describe("--sdk-only mode", () => {
test("detects the flag among CLI arguments", () => {
expect(isSdkOnly(["--sdk-only"])).toBe(true);
expect(isSdkOnly([])).toBe(false);
expect(isSdkOnly(["--verbose"])).toBe(false);
});

test("restricts the package set to sdk when enabled", () => {
expect(smokePackages(true)).toEqual(["sdk"]);
});

test("keeps the full package set when disabled", () => {
expect(smokePackages(false)).toEqual(["sdk", "playground", "cli"]);
});
});
51 changes: 34 additions & 17 deletions scripts/release/smoke-packed.ts
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
/**
* Pack the exact publish manifests, install them as an external npm consumer,
* then exercise every public SDK entry point plus the CLI and Playground bins.
*
* `--sdk-only` restricts the run to the SDK package: pack/install only the sdk
* tarball (still with --engine-strict) and execute the SDK entry-point imports.
* This is the mode CI uses on Node versions below the cli/playground engines
* floor (>=22) to enforce the SDK's own >=18.17.0 engines contract.
*/

import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
Expand All@@ -18,6 +23,14 @@ import {

const root = resolve(import.meta.dirname, "../..");

export function isSdkOnly(argv: readonly string[]): boolean {
return argv.includes("--sdk-only");
}

export function smokePackages(sdkOnly: boolean): readonly (typeof PACKAGES)[number][] {
return sdkOnly ? PACKAGES.filter((pkg) => pkg === "sdk") : PACKAGES;
}

type PackedPackage = { filename: string };

export function packedFilename(raw: string, pkg: string): string {
Expand All@@ -33,9 +46,9 @@ function run(command: string[], cwd: string, stdout: "inherit" | "pipe" = "inher
return stdout === "pipe" ? (result.stdout?.toString().trim() ?? "") : "";
}

function packPackages(destination: string): string[] {
function packPackages(destination: string, packages: readonly (typeof PACKAGES)[number][]): string[] {
const versions = workspaceVersions();
return PACKAGES.map((pkg) => {
return packages.map((pkg) => {
const pkgDir = join(root, "packages", pkg);
let originalLicense: string | undefined;
let originalManifest: string | undefined;
Expand DownExpand Up@@ -104,6 +117,8 @@ async function smokePlayground(consumer: string): Promise<void> {
}

async function main(): Promise<void> {
const sdkOnly = isSdkOnly(process.argv.slice(2));
const packages = smokePackages(sdkOnly);
const temporaryRoot = mkdtempSync(join(tmpdir(), "openagentpack-consumer-"));
try {
const tarballsDir = join(temporaryRoot, "tarballs");
Expand All@@ -112,7 +127,7 @@ async function main(): Promise<void> {
mkdirSync(consumer);
writeFileSync(join(consumer, "package.json"), '{"name":"agents-package-smoke","private":true,"type":"module"}\n');

const tarballs = packPackages(tarballsDir);
const tarballs = packPackages(tarballsDir, packages);
run(
[
"npm",
Expand All@@ -126,7 +141,7 @@ async function main(): Promise<void> {
],
consumer,
);
for (const pkg of PACKAGES) {
for (const pkg of packages) {
const license = readFileSync(join(consumer, `node_modules/@openagentpack/${pkg}/LICENSE`), "utf8");
if (license !== readFileSync(join(root, "LICENSE"), "utf8")) {
throw new Error(`${pkg} package is missing the repository license`);
Expand All@@ -143,21 +158,23 @@ async function main(): Promise<void> {
consumer,
);

const expectedVersion = JSON.parse(readFileSync(join(root, "packages/cli/package.json"), "utf8")) as {
version: string;
};
const cliVersion = run(
["node", join(consumer, "node_modules/@openagentpack/cli/dist/bin/agents.js"), "--version"],
consumer,
"pipe",
);
if (cliVersion !== expectedVersion.version) {
throw new Error(`Packed CLI version mismatch: expected ${expectedVersion.version}, received ${cliVersion}`);
}
if (!sdkOnly) {
const expectedVersion = JSON.parse(readFileSync(join(root, "packages/cli/package.json"), "utf8")) as {
version: string;
};
const cliVersion = run(
["node", join(consumer, "node_modules/@openagentpack/cli/dist/bin/agents.js"), "--version"],
consumer,
"pipe",
);
if (cliVersion !== expectedVersion.version) {
throw new Error(`Packed CLI version mismatch: expected ${expectedVersion.version}, received ${cliVersion}`);
}

await smokePlayground(consumer);
await smokePlayground(consumer);
}
const nodeVersion = run(["node", "--version"], consumer, "pipe");
console.log(`✓ Packed packages install and run under ${nodeVersion}`);
console.log(`✓ Packed ${sdkOnly ? "SDK package installs" : "packages install"} and run under ${nodeVersion}`);
} finally {
rmSync(temporaryRoot, { recursive: true, force: true });
}
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/sdk-node-engines-floor.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
---
"@openagentpack/sdk": patch
---

Lower the SDK `engines` floor from Node `>=22` to `>=18.17.0`.

The SDK runtime only relies on APIs available since Node 18.17 (`node:fs`/`path`/`crypto`/`os`, global `fetch`/`FormData`, `structuredClone`, web streams), so the previous floor was a repository baseline rather than a runtime requirement. The tsup build target is pinned to `es2022` so emitted syntax stays parseable on the new floor, and CI now runs an SDK-only packed-install smoke on Node 18 and 20 (`smoke-packed.ts --sdk-only`) to enforce the contract. The `@openagentpack/cli` and `@openagentpack/playground` packages keep their Node `>=22` requirement.
25 changes: 21 additions & 4 deletions .github/workflows/ci.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -52,12 +52,23 @@ jobs:
run: bun scripts/verify.ts full --step "${{ matrix.step }}"

package-compatibility:
name: Package compatibility / Node ${{ matrix.node }}
name: Package compatibility / Node ${{ matrix.node }}${{ matrix.scope == 'sdk' && ' (sdk-only)' || '' }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
node: [22, 24]
include:
# Full consumer smoke (sdk + playground + cli) on the repo baseline.
- node: 22
scope: all
- node: 24
scope: all
# SDK-only smoke on the sdk engines floor (>=18.17.0): cli/playground
# require Node >=22, so only the sdk tarball is installed and imported.
- node: 18.17.0
scope: sdk
- node: 20
scope: sdk
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
Expand All@@ -68,8 +79,14 @@ jobs:
node-version: ${{ matrix.node }}
package-manager-cache: false
- run: bun install --frozen-lockfile
- run: bun scripts/verify.ts release --step build-packages
- run: bun scripts/verify.ts release --step package-smoke
- if: matrix.scope == 'all'
run: |
bun scripts/verify.ts release --step build-packages
bun scripts/verify.ts release --step package-smoke
- if: matrix.scope == 'sdk'
run: |
bun run build:sdk
bun scripts/release/smoke-packed.ts --sdk-only

gate:
name: Gate
Expand Down
2 changes: 1 addition & 1 deletion packages/sdk/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,7 +20,7 @@
"url": "https://github.com/modelstudioai/OpenAgentPack/issues"
},
"engines": {
"node": ">=22"
"node": ">=18.17.0"
},
"type": "module",
"main": "./dist/index.js",
Expand Down
4 changes: 3 additions & 1 deletion packages/sdk/tsup.config.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -15,5 +15,7 @@ export default defineConfig({
},
},
clean: true,
target: "esnext",
// Keep the emitted syntax parseable by Node 18.17 (the package's engines
// floor): esnext would let future syntax (e.g. `using`) leak into dist.
target: "es2022",
});
9 changes: 7 additions & 2 deletions scripts/open-source.test.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -53,11 +53,16 @@ describe("open-source repository invariants", () => {
});

test("public packages require a maintained Node.js baseline", () => {
for (const pkg of ["sdk", "playground", "cli"]) {
const nodeBaselines: Record<string, string> = {
sdk: ">=18.17.0",
playground: ">=22",
cli: ">=22",
};
for (const [pkg, expectedNodeRange] of Object.entries(nodeBaselines)) {
const manifest = JSON.parse(readFileSync(resolve(root, `packages/${pkg}/package.json`), "utf8")) as {
engines?: { node?: string };
};
expect(manifest.engines?.node).toBe(">=22");
expect(manifest.engines?.node).toBe(expectedNodeRange);
}
});

Expand Down
18 changes: 17 additions & 1 deletion scripts/release/smoke-packed.test.ts
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
import { describe, expect, test } from "bun:test";
import { packedFilename } from "./smoke-packed.ts";
import { isSdkOnly, packedFilename, smokePackages } from "./smoke-packed.ts";

describe("npm pack output", () => {
test("accepts the npm 10 and 11 array format", () => {
Expand All@@ -14,3 +14,19 @@ describe("npm pack output", () => {
expect(() => packedFilename("[]", "sdk")).toThrow("Unexpected npm pack output for sdk");
});
});

describe("--sdk-only mode", () => {
test("detects the flag among CLI arguments", () => {
expect(isSdkOnly(["--sdk-only"])).toBe(true);
expect(isSdkOnly([])).toBe(false);
expect(isSdkOnly(["--verbose"])).toBe(false);
});

test("restricts the package set to sdk when enabled", () => {
expect(smokePackages(true)).toEqual(["sdk"]);
});

test("keeps the full package set when disabled", () => {
expect(smokePackages(false)).toEqual(["sdk", "playground", "cli"]);
});
});
51 changes: 34 additions & 17 deletions scripts/release/smoke-packed.ts
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
/**
* Pack the exact publish manifests, install them as an external npm consumer,
* then exercise every public SDK entry point plus the CLI and Playground bins.
*
* `--sdk-only` restricts the run to the SDK package: pack/install only the sdk
* tarball (still with --engine-strict) and execute the SDK entry-point imports.
* This is the mode CI uses on Node versions below the cli/playground engines
* floor (>=22) to enforce the SDK's own >=18.17.0 engines contract.
*/

import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
Expand All@@ -18,6 +23,14 @@ import {

const root = resolve(import.meta.dirname, "../..");

export function isSdkOnly(argv: readonly string[]): boolean {
return argv.includes("--sdk-only");
}

export function smokePackages(sdkOnly: boolean): readonly (typeof PACKAGES)[number][] {
return sdkOnly ? PACKAGES.filter((pkg) => pkg === "sdk") : PACKAGES;
}

type PackedPackage = { filename: string };

export function packedFilename(raw: string, pkg: string): string {
Expand All@@ -33,9 +46,9 @@ function run(command: string[], cwd: string, stdout: "inherit" | "pipe" = "inher
return stdout === "pipe" ? (result.stdout?.toString().trim() ?? "") : "";
}

function packPackages(destination: string): string[] {
function packPackages(destination: string, packages: readonly (typeof PACKAGES)[number][]): string[] {
const versions = workspaceVersions();
return PACKAGES.map((pkg) => {
return packages.map((pkg) => {
const pkgDir = join(root, "packages", pkg);
let originalLicense: string | undefined;
let originalManifest: string | undefined;
Expand DownExpand Up@@ -104,6 +117,8 @@ async function smokePlayground(consumer: string): Promise<void> {
}

async function main(): Promise<void> {
const sdkOnly = isSdkOnly(process.argv.slice(2));
const packages = smokePackages(sdkOnly);
const temporaryRoot = mkdtempSync(join(tmpdir(), "openagentpack-consumer-"));
try {
const tarballsDir = join(temporaryRoot, "tarballs");
Expand All@@ -112,7 +127,7 @@ async function main(): Promise<void> {
mkdirSync(consumer);
writeFileSync(join(consumer, "package.json"), '{"name":"agents-package-smoke","private":true,"type":"module"}\n');

const tarballs = packPackages(tarballsDir);
const tarballs = packPackages(tarballsDir, packages);
run(
[
"npm",
Expand All@@ -126,7 +141,7 @@ async function main(): Promise<void> {
],
consumer,
);
for (const pkg of PACKAGES) {
for (const pkg of packages) {
const license = readFileSync(join(consumer, `node_modules/@openagentpack/${pkg}/LICENSE`), "utf8");
if (license !== readFileSync(join(root, "LICENSE"), "utf8")) {
throw new Error(`${pkg} package is missing the repository license`);
Expand All@@ -143,21 +158,23 @@ async function main(): Promise<void> {
consumer,
);

const expectedVersion = JSON.parse(readFileSync(join(root, "packages/cli/package.json"), "utf8")) as {
version: string;
};
const cliVersion = run(
["node", join(consumer, "node_modules/@openagentpack/cli/dist/bin/agents.js"), "--version"],
consumer,
"pipe",
);
if (cliVersion !== expectedVersion.version) {
throw new Error(`Packed CLI version mismatch: expected ${expectedVersion.version}, received ${cliVersion}`);
}
if (!sdkOnly) {
const expectedVersion = JSON.parse(readFileSync(join(root, "packages/cli/package.json"), "utf8")) as {
version: string;
};
const cliVersion = run(
["node", join(consumer, "node_modules/@openagentpack/cli/dist/bin/agents.js"), "--version"],
consumer,
"pipe",
);
if (cliVersion !== expectedVersion.version) {
throw new Error(`Packed CLI version mismatch: expected ${expectedVersion.version}, received ${cliVersion}`);
}

await smokePlayground(consumer);
await smokePlayground(consumer);
}
const nodeVersion = run(["node", "--version"], consumer, "pipe");
console.log(`✓ Packed packages install and run under ${nodeVersion}`);
console.log(`✓ Packed ${sdkOnly ? "SDK package installs" : "packages install"} and run under ${nodeVersion}`);
} finally {
rmSync(temporaryRoot, { recursive: true, force: true });
}
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/sdk-node-engines-floor.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
---
"@openagentpack/sdk": patch
---

Lower the SDK `engines` floor from Node `>=22` to `>=18.17.0`.

The SDK runtime only relies on APIs available since Node 18.17 (`node:fs`/`path`/`crypto`/`os`, global `fetch`/`FormData`, `structuredClone`, web streams), so the previous floor was a repository baseline rather than a runtime requirement. The tsup build target is pinned to `es2022` so emitted syntax stays parseable on the new floor, and CI now runs an SDK-only packed-install smoke on Node 18 and 20 (`smoke-packed.ts --sdk-only`) to enforce the contract. The `@openagentpack/cli` and `@openagentpack/playground` packages keep their Node `>=22` requirement.
25 changes: 21 additions & 4 deletions .github/workflows/ci.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -52,12 +52,23 @@ jobs:
run: bun scripts/verify.ts full --step "${{ matrix.step }}"

package-compatibility:
name: Package compatibility / Node ${{ matrix.node }}
name: Package compatibility / Node ${{ matrix.node }}${{ matrix.scope == 'sdk' && ' (sdk-only)' || '' }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
node: [22, 24]
include:
# Full consumer smoke (sdk + playground + cli) on the repo baseline.
- node: 22
scope: all
- node: 24
scope: all
# SDK-only smoke on the sdk engines floor (>=18.17.0): cli/playground
# require Node >=22, so only the sdk tarball is installed and imported.
- node: 18.17.0
scope: sdk
- node: 20
scope: sdk
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
Expand All@@ -68,8 +79,14 @@ jobs:
node-version: ${{ matrix.node }}
package-manager-cache: false
- run: bun install --frozen-lockfile
- run: bun scripts/verify.ts release --step build-packages
- run: bun scripts/verify.ts release --step package-smoke
- if: matrix.scope == 'all'
run: |
bun scripts/verify.ts release --step build-packages
bun scripts/verify.ts release --step package-smoke
- if: matrix.scope == 'sdk'
run: |
bun run build:sdk
bun scripts/release/smoke-packed.ts --sdk-only

gate:
name: Gate
Expand Down
2 changes: 1 addition & 1 deletion packages/sdk/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,7 +20,7 @@
"url": "https://github.com/modelstudioai/OpenAgentPack/issues"
},
"engines": {
"node": ">=22"
"node": ">=18.17.0"
},
"type": "module",
"main": "./dist/index.js",
Expand Down
4 changes: 3 additions & 1 deletion packages/sdk/tsup.config.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -15,5 +15,7 @@ export default defineConfig({
},
},
clean: true,
target: "esnext",
// Keep the emitted syntax parseable by Node 18.17 (the package's engines
// floor): esnext would let future syntax (e.g. `using`) leak into dist.
target: "es2022",
});
9 changes: 7 additions & 2 deletions scripts/open-source.test.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -53,11 +53,16 @@ describe("open-source repository invariants", () => {
});

test("public packages require a maintained Node.js baseline", () => {
for (const pkg of ["sdk", "playground", "cli"]) {
const nodeBaselines: Record<string, string> = {
sdk: ">=18.17.0",
playground: ">=22",
cli: ">=22",
};
for (const [pkg, expectedNodeRange] of Object.entries(nodeBaselines)) {
const manifest = JSON.parse(readFileSync(resolve(root, `packages/${pkg}/package.json`), "utf8")) as {
engines?: { node?: string };
};
expect(manifest.engines?.node).toBe(">=22");
expect(manifest.engines?.node).toBe(expectedNodeRange);
}
});

Expand Down
18 changes: 17 additions & 1 deletion scripts/release/smoke-packed.test.ts
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
import { describe, expect, test } from "bun:test";
import { packedFilename } from "./smoke-packed.ts";
import { isSdkOnly, packedFilename, smokePackages } from "./smoke-packed.ts";

describe("npm pack output", () => {
test("accepts the npm 10 and 11 array format", () => {
Expand All@@ -14,3 +14,19 @@ describe("npm pack output", () => {
expect(() => packedFilename("[]", "sdk")).toThrow("Unexpected npm pack output for sdk");
});
});

describe("--sdk-only mode", () => {
test("detects the flag among CLI arguments", () => {
expect(isSdkOnly(["--sdk-only"])).toBe(true);
expect(isSdkOnly([])).toBe(false);
expect(isSdkOnly(["--verbose"])).toBe(false);
});

test("restricts the package set to sdk when enabled", () => {
expect(smokePackages(true)).toEqual(["sdk"]);
});

test("keeps the full package set when disabled", () => {
expect(smokePackages(false)).toEqual(["sdk", "playground", "cli"]);
});
});
51 changes: 34 additions & 17 deletions scripts/release/smoke-packed.ts
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
/**
* Pack the exact publish manifests, install them as an external npm consumer,
* then exercise every public SDK entry point plus the CLI and Playground bins.
*
* `--sdk-only` restricts the run to the SDK package: pack/install only the sdk
* tarball (still with --engine-strict) and execute the SDK entry-point imports.
* This is the mode CI uses on Node versions below the cli/playground engines
* floor (>=22) to enforce the SDK's own >=18.17.0 engines contract.
*/

import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
Expand All@@ -18,6 +23,14 @@ import {

const root = resolve(import.meta.dirname, "../..");

export function isSdkOnly(argv: readonly string[]): boolean {
return argv.includes("--sdk-only");
}

export function smokePackages(sdkOnly: boolean): readonly (typeof PACKAGES)[number][] {
return sdkOnly ? PACKAGES.filter((pkg) => pkg === "sdk") : PACKAGES;
}

type PackedPackage = { filename: string };

export function packedFilename(raw: string, pkg: string): string {
Expand All@@ -33,9 +46,9 @@ function run(command: string[], cwd: string, stdout: "inherit" | "pipe" = "inher
return stdout === "pipe" ? (result.stdout?.toString().trim() ?? "") : "";
}

function packPackages(destination: string): string[] {
function packPackages(destination: string, packages: readonly (typeof PACKAGES)[number][]): string[] {
const versions = workspaceVersions();
return PACKAGES.map((pkg) => {
return packages.map((pkg) => {
const pkgDir = join(root, "packages", pkg);
let originalLicense: string | undefined;
let originalManifest: string | undefined;
Expand DownExpand Up@@ -104,6 +117,8 @@ async function smokePlayground(consumer: string): Promise<void> {
}

async function main(): Promise<void> {
const sdkOnly = isSdkOnly(process.argv.slice(2));
const packages = smokePackages(sdkOnly);
const temporaryRoot = mkdtempSync(join(tmpdir(), "openagentpack-consumer-"));
try {
const tarballsDir = join(temporaryRoot, "tarballs");
Expand All@@ -112,7 +127,7 @@ async function main(): Promise<void> {
mkdirSync(consumer);
writeFileSync(join(consumer, "package.json"), '{"name":"agents-package-smoke","private":true,"type":"module"}\n');

const tarballs = packPackages(tarballsDir);
const tarballs = packPackages(tarballsDir, packages);
run(
[
"npm",
Expand All@@ -126,7 +141,7 @@ async function main(): Promise<void> {
],
consumer,
);
for (const pkg of PACKAGES) {
for (const pkg of packages) {
const license = readFileSync(join(consumer, `node_modules/@openagentpack/${pkg}/LICENSE`), "utf8");
if (license !== readFileSync(join(root, "LICENSE"), "utf8")) {
throw new Error(`${pkg} package is missing the repository license`);
Expand All@@ -143,21 +158,23 @@ async function main(): Promise<void> {
consumer,
);

const expectedVersion = JSON.parse(readFileSync(join(root, "packages/cli/package.json"), "utf8")) as {
version: string;
};
const cliVersion = run(
["node", join(consumer, "node_modules/@openagentpack/cli/dist/bin/agents.js"), "--version"],
consumer,
"pipe",
);
if (cliVersion !== expectedVersion.version) {
throw new Error(`Packed CLI version mismatch: expected ${expectedVersion.version}, received ${cliVersion}`);
}
if (!sdkOnly) {
const expectedVersion = JSON.parse(readFileSync(join(root, "packages/cli/package.json"), "utf8")) as {
version: string;
};
const cliVersion = run(
["node", join(consumer, "node_modules/@openagentpack/cli/dist/bin/agents.js"), "--version"],
consumer,
"pipe",
);
if (cliVersion !== expectedVersion.version) {
throw new Error(`Packed CLI version mismatch: expected ${expectedVersion.version}, received ${cliVersion}`);
}

await smokePlayground(consumer);
await smokePlayground(consumer);
}
const nodeVersion = run(["node", "--version"], consumer, "pipe");
console.log(`✓ Packed packages install and run under ${nodeVersion}`);
console.log(`✓ Packed ${sdkOnly ? "SDK package installs" : "packages install"} and run under ${nodeVersion}`);
} finally {
rmSync(temporaryRoot, { recursive: true, force: true });
}
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/sdk-node-engines-floor.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
---
"@openagentpack/sdk": patch
---

Lower the SDK `engines` floor from Node `>=22` to `>=18.17.0`.

The SDK runtime only relies on APIs available since Node 18.17 (`node:fs`/`path`/`crypto`/`os`, global `fetch`/`FormData`, `structuredClone`, web streams), so the previous floor was a repository baseline rather than a runtime requirement. The tsup build target is pinned to `es2022` so emitted syntax stays parseable on the new floor, and CI now runs an SDK-only packed-install smoke on Node 18 and 20 (`smoke-packed.ts --sdk-only`) to enforce the contract. The `@openagentpack/cli` and `@openagentpack/playground` packages keep their Node `>=22` requirement.
25 changes: 21 additions & 4 deletions .github/workflows/ci.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -52,12 +52,23 @@ jobs:
run: bun scripts/verify.ts full --step "${{ matrix.step }}"

package-compatibility:
name: Package compatibility / Node ${{ matrix.node }}
name: Package compatibility / Node ${{ matrix.node }}${{ matrix.scope == 'sdk' && ' (sdk-only)' || '' }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
node: [22, 24]
include:
# Full consumer smoke (sdk + playground + cli) on the repo baseline.
- node: 22
scope: all
- node: 24
scope: all
# SDK-only smoke on the sdk engines floor (>=18.17.0): cli/playground
# require Node >=22, so only the sdk tarball is installed and imported.
- node: 18.17.0
scope: sdk
- node: 20
scope: sdk
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
Expand All@@ -68,8 +79,14 @@ jobs:
node-version: ${{ matrix.node }}
package-manager-cache: false
- run: bun install --frozen-lockfile
- run: bun scripts/verify.ts release --step build-packages
- run: bun scripts/verify.ts release --step package-smoke
- if: matrix.scope == 'all'
run: |
bun scripts/verify.ts release --step build-packages
bun scripts/verify.ts release --step package-smoke
- if: matrix.scope == 'sdk'
run: |
bun run build:sdk
bun scripts/release/smoke-packed.ts --sdk-only

gate:
name: Gate
Expand Down
2 changes: 1 addition & 1 deletion packages/sdk/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,7 +20,7 @@
"url": "https://github.com/modelstudioai/OpenAgentPack/issues"
},
"engines": {
"node": ">=22"
"node": ">=18.17.0"
},
"type": "module",
"main": "./dist/index.js",
Expand Down
4 changes: 3 additions & 1 deletion packages/sdk/tsup.config.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -15,5 +15,7 @@ export default defineConfig({
},
},
clean: true,
target: "esnext",
// Keep the emitted syntax parseable by Node 18.17 (the package's engines
// floor): esnext would let future syntax (e.g. `using`) leak into dist.
target: "es2022",
});
9 changes: 7 additions & 2 deletions scripts/open-source.test.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -53,11 +53,16 @@ describe("open-source repository invariants", () => {
});

test("public packages require a maintained Node.js baseline", () => {
for (const pkg of ["sdk", "playground", "cli"]) {
const nodeBaselines: Record<string, string> = {
sdk: ">=18.17.0",
playground: ">=22",
cli: ">=22",
};
for (const [pkg, expectedNodeRange] of Object.entries(nodeBaselines)) {
const manifest = JSON.parse(readFileSync(resolve(root, `packages/${pkg}/package.json`), "utf8")) as {
engines?: { node?: string };
};
expect(manifest.engines?.node).toBe(">=22");
expect(manifest.engines?.node).toBe(expectedNodeRange);
}
});

Expand Down
18 changes: 17 additions & 1 deletion scripts/release/smoke-packed.test.ts
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
import { describe, expect, test } from "bun:test";
import { packedFilename } from "./smoke-packed.ts";
import { isSdkOnly, packedFilename, smokePackages } from "./smoke-packed.ts";

describe("npm pack output", () => {
test("accepts the npm 10 and 11 array format", () => {
Expand All@@ -14,3 +14,19 @@ describe("npm pack output", () => {
expect(() => packedFilename("[]", "sdk")).toThrow("Unexpected npm pack output for sdk");
});
});

describe("--sdk-only mode", () => {
test("detects the flag among CLI arguments", () => {
expect(isSdkOnly(["--sdk-only"])).toBe(true);
expect(isSdkOnly([])).toBe(false);
expect(isSdkOnly(["--verbose"])).toBe(false);
});

test("restricts the package set to sdk when enabled", () => {
expect(smokePackages(true)).toEqual(["sdk"]);
});

test("keeps the full package set when disabled", () => {
expect(smokePackages(false)).toEqual(["sdk", "playground", "cli"]);
});
});
51 changes: 34 additions & 17 deletions scripts/release/smoke-packed.ts
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
/**
* Pack the exact publish manifests, install them as an external npm consumer,
* then exercise every public SDK entry point plus the CLI and Playground bins.
*
* `--sdk-only` restricts the run to the SDK package: pack/install only the sdk
* tarball (still with --engine-strict) and execute the SDK entry-point imports.
* This is the mode CI uses on Node versions below the cli/playground engines
* floor (>=22) to enforce the SDK's own >=18.17.0 engines contract.
*/

import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
Expand All@@ -18,6 +23,14 @@ import {

const root = resolve(import.meta.dirname, "../..");

export function isSdkOnly(argv: readonly string[]): boolean {
return argv.includes("--sdk-only");
}

export function smokePackages(sdkOnly: boolean): readonly (typeof PACKAGES)[number][] {
return sdkOnly ? PACKAGES.filter((pkg) => pkg === "sdk") : PACKAGES;
}

type PackedPackage = { filename: string };

export function packedFilename(raw: string, pkg: string): string {
Expand All@@ -33,9 +46,9 @@ function run(command: string[], cwd: string, stdout: "inherit" | "pipe" = "inher
return stdout === "pipe" ? (result.stdout?.toString().trim() ?? "") : "";
}

function packPackages(destination: string): string[] {
function packPackages(destination: string, packages: readonly (typeof PACKAGES)[number][]): string[] {
const versions = workspaceVersions();
return PACKAGES.map((pkg) => {
return packages.map((pkg) => {
const pkgDir = join(root, "packages", pkg);
let originalLicense: string | undefined;
let originalManifest: string | undefined;
Expand DownExpand Up@@ -104,6 +117,8 @@ async function smokePlayground(consumer: string): Promise<void> {
}

async function main(): Promise<void> {
const sdkOnly = isSdkOnly(process.argv.slice(2));
const packages = smokePackages(sdkOnly);
const temporaryRoot = mkdtempSync(join(tmpdir(), "openagentpack-consumer-"));
try {
const tarballsDir = join(temporaryRoot, "tarballs");
Expand All@@ -112,7 +127,7 @@ async function main(): Promise<void> {
mkdirSync(consumer);
writeFileSync(join(consumer, "package.json"), '{"name":"agents-package-smoke","private":true,"type":"module"}\n');

const tarballs = packPackages(tarballsDir);
const tarballs = packPackages(tarballsDir, packages);
run(
[
"npm",
Expand All@@ -126,7 +141,7 @@ async function main(): Promise<void> {
],
consumer,
);
for (const pkg of PACKAGES) {
for (const pkg of packages) {
const license = readFileSync(join(consumer, `node_modules/@openagentpack/${pkg}/LICENSE`), "utf8");
if (license !== readFileSync(join(root, "LICENSE"), "utf8")) {
throw new Error(`${pkg} package is missing the repository license`);
Expand All@@ -143,21 +158,23 @@ async function main(): Promise<void> {
consumer,
);

const expectedVersion = JSON.parse(readFileSync(join(root, "packages/cli/package.json"), "utf8")) as {
version: string;
};
const cliVersion = run(
["node", join(consumer, "node_modules/@openagentpack/cli/dist/bin/agents.js"), "--version"],
consumer,
"pipe",
);
if (cliVersion !== expectedVersion.version) {
throw new Error(`Packed CLI version mismatch: expected ${expectedVersion.version}, received ${cliVersion}`);
}
if (!sdkOnly) {
const expectedVersion = JSON.parse(readFileSync(join(root, "packages/cli/package.json"), "utf8")) as {
version: string;
};
const cliVersion = run(
["node", join(consumer, "node_modules/@openagentpack/cli/dist/bin/agents.js"), "--version"],
consumer,
"pipe",
);
if (cliVersion !== expectedVersion.version) {
throw new Error(`Packed CLI version mismatch: expected ${expectedVersion.version}, received ${cliVersion}`);
}

await smokePlayground(consumer);
await smokePlayground(consumer);
}
const nodeVersion = run(["node", "--version"], consumer, "pipe");
console.log(`✓ Packed packages install and run under ${nodeVersion}`);
console.log(`✓ Packed ${sdkOnly ? "SDK package installs" : "packages install"} and run under ${nodeVersion}`);
} finally {
rmSync(temporaryRoot, { recursive: true, force: true });
}
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/sdk-node-engines-floor.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
---
"@openagentpack/sdk": patch
---

Lower the SDK `engines` floor from Node `>=22` to `>=18.17.0`.

The SDK runtime only relies on APIs available since Node 18.17 (`node:fs`/`path`/`crypto`/`os`, global `fetch`/`FormData`, `structuredClone`, web streams), so the previous floor was a repository baseline rather than a runtime requirement. The tsup build target is pinned to `es2022` so emitted syntax stays parseable on the new floor, and CI now runs an SDK-only packed-install smoke on Node 18 and 20 (`smoke-packed.ts --sdk-only`) to enforce the contract. The `@openagentpack/cli` and `@openagentpack/playground` packages keep their Node `>=22` requirement.
25 changes: 21 additions & 4 deletions .github/workflows/ci.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -52,12 +52,23 @@ jobs:
run: bun scripts/verify.ts full --step "${{ matrix.step }}"

package-compatibility:
name: Package compatibility / Node ${{ matrix.node }}
name: Package compatibility / Node ${{ matrix.node }}${{ matrix.scope == 'sdk' && ' (sdk-only)' || '' }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
node: [22, 24]
include:
# Full consumer smoke (sdk + playground + cli) on the repo baseline.
- node: 22
scope: all
- node: 24
scope: all
# SDK-only smoke on the sdk engines floor (>=18.17.0): cli/playground
# require Node >=22, so only the sdk tarball is installed and imported.
- node: 18.17.0
scope: sdk
- node: 20
scope: sdk
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
Expand All@@ -68,8 +79,14 @@ jobs:
node-version: ${{ matrix.node }}
package-manager-cache: false
- run: bun install --frozen-lockfile
- run: bun scripts/verify.ts release --step build-packages
- run: bun scripts/verify.ts release --step package-smoke
- if: matrix.scope == 'all'
run: |
bun scripts/verify.ts release --step build-packages
bun scripts/verify.ts release --step package-smoke
- if: matrix.scope == 'sdk'
run: |
bun run build:sdk
bun scripts/release/smoke-packed.ts --sdk-only

gate:
name: Gate
Expand Down
2 changes: 1 addition & 1 deletion packages/sdk/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,7 +20,7 @@
"url": "https://github.com/modelstudioai/OpenAgentPack/issues"
},
"engines": {
"node": ">=22"
"node": ">=18.17.0"
},
"type": "module",
"main": "./dist/index.js",
Expand Down
4 changes: 3 additions & 1 deletion packages/sdk/tsup.config.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -15,5 +15,7 @@ export default defineConfig({
},
},
clean: true,
target: "esnext",
// Keep the emitted syntax parseable by Node 18.17 (the package's engines
// floor): esnext would let future syntax (e.g. `using`) leak into dist.
target: "es2022",
});
9 changes: 7 additions & 2 deletions scripts/open-source.test.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -53,11 +53,16 @@ describe("open-source repository invariants", () => {
});

test("public packages require a maintained Node.js baseline", () => {
for (const pkg of ["sdk", "playground", "cli"]) {
const nodeBaselines: Record<string, string> = {
sdk: ">=18.17.0",
playground: ">=22",
cli: ">=22",
};
for (const [pkg, expectedNodeRange] of Object.entries(nodeBaselines)) {
const manifest = JSON.parse(readFileSync(resolve(root, `packages/${pkg}/package.json`), "utf8")) as {
engines?: { node?: string };
};
expect(manifest.engines?.node).toBe(">=22");
expect(manifest.engines?.node).toBe(expectedNodeRange);
}
});

Expand Down
18 changes: 17 additions & 1 deletion scripts/release/smoke-packed.test.ts
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
import { describe, expect, test } from "bun:test";
import { packedFilename } from "./smoke-packed.ts";
import { isSdkOnly, packedFilename, smokePackages } from "./smoke-packed.ts";

describe("npm pack output", () => {
test("accepts the npm 10 and 11 array format", () => {
Expand All@@ -14,3 +14,19 @@ describe("npm pack output", () => {
expect(() => packedFilename("[]", "sdk")).toThrow("Unexpected npm pack output for sdk");
});
});

describe("--sdk-only mode", () => {
test("detects the flag among CLI arguments", () => {
expect(isSdkOnly(["--sdk-only"])).toBe(true);
expect(isSdkOnly([])).toBe(false);
expect(isSdkOnly(["--verbose"])).toBe(false);
});

test("restricts the package set to sdk when enabled", () => {
expect(smokePackages(true)).toEqual(["sdk"]);
});

test("keeps the full package set when disabled", () => {
expect(smokePackages(false)).toEqual(["sdk", "playground", "cli"]);
});
});
51 changes: 34 additions & 17 deletions scripts/release/smoke-packed.ts
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
/**
* Pack the exact publish manifests, install them as an external npm consumer,
* then exercise every public SDK entry point plus the CLI and Playground bins.
*
* `--sdk-only` restricts the run to the SDK package: pack/install only the sdk
* tarball (still with --engine-strict) and execute the SDK entry-point imports.
* This is the mode CI uses on Node versions below the cli/playground engines
* floor (>=22) to enforce the SDK's own >=18.17.0 engines contract.
*/

import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
Expand All@@ -18,6 +23,14 @@ import {

const root = resolve(import.meta.dirname, "../..");

export function isSdkOnly(argv: readonly string[]): boolean {
return argv.includes("--sdk-only");
}

export function smokePackages(sdkOnly: boolean): readonly (typeof PACKAGES)[number][] {
return sdkOnly ? PACKAGES.filter((pkg) => pkg === "sdk") : PACKAGES;
}

type PackedPackage = { filename: string };

export function packedFilename(raw: string, pkg: string): string {
Expand All@@ -33,9 +46,9 @@ function run(command: string[], cwd: string, stdout: "inherit" | "pipe" = "inher
return stdout === "pipe" ? (result.stdout?.toString().trim() ?? "") : "";
}

function packPackages(destination: string): string[] {
function packPackages(destination: string, packages: readonly (typeof PACKAGES)[number][]): string[] {
const versions = workspaceVersions();
return PACKAGES.map((pkg) => {
return packages.map((pkg) => {
const pkgDir = join(root, "packages", pkg);
let originalLicense: string | undefined;
let originalManifest: string | undefined;
Expand DownExpand Up@@ -104,6 +117,8 @@ async function smokePlayground(consumer: string): Promise<void> {
}

async function main(): Promise<void> {
const sdkOnly = isSdkOnly(process.argv.slice(2));
const packages = smokePackages(sdkOnly);
const temporaryRoot = mkdtempSync(join(tmpdir(), "openagentpack-consumer-"));
try {
const tarballsDir = join(temporaryRoot, "tarballs");
Expand All@@ -112,7 +127,7 @@ async function main(): Promise<void> {
mkdirSync(consumer);
writeFileSync(join(consumer, "package.json"), '{"name":"agents-package-smoke","private":true,"type":"module"}\n');

const tarballs = packPackages(tarballsDir);
const tarballs = packPackages(tarballsDir, packages);
run(
[
"npm",
Expand All@@ -126,7 +141,7 @@ async function main(): Promise<void> {
],
consumer,
);
for (const pkg of PACKAGES) {
for (const pkg of packages) {
const license = readFileSync(join(consumer, `node_modules/@openagentpack/${pkg}/LICENSE`), "utf8");
if (license !== readFileSync(join(root, "LICENSE"), "utf8")) {
throw new Error(`${pkg} package is missing the repository license`);
Expand All@@ -143,21 +158,23 @@ async function main(): Promise<void> {
consumer,
);

const expectedVersion = JSON.parse(readFileSync(join(root, "packages/cli/package.json"), "utf8")) as {
version: string;
};
const cliVersion = run(
["node", join(consumer, "node_modules/@openagentpack/cli/dist/bin/agents.js"), "--version"],
consumer,
"pipe",
);
if (cliVersion !== expectedVersion.version) {
throw new Error(`Packed CLI version mismatch: expected ${expectedVersion.version}, received ${cliVersion}`);
}
if (!sdkOnly) {
const expectedVersion = JSON.parse(readFileSync(join(root, "packages/cli/package.json"), "utf8")) as {
version: string;
};
const cliVersion = run(
["node", join(consumer, "node_modules/@openagentpack/cli/dist/bin/agents.js"), "--version"],
consumer,
"pipe",
);
if (cliVersion !== expectedVersion.version) {
throw new Error(`Packed CLI version mismatch: expected ${expectedVersion.version}, received ${cliVersion}`);
}

await smokePlayground(consumer);
await smokePlayground(consumer);
}
const nodeVersion = run(["node", "--version"], consumer, "pipe");
console.log(`✓ Packed packages install and run under ${nodeVersion}`);
console.log(`✓ Packed ${sdkOnly ? "SDK package installs" : "packages install"} and run under ${nodeVersion}`);
} finally {
rmSync(temporaryRoot, { recursive: true, force: true });
}
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/sdk-node-engines-floor.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
---
"@openagentpack/sdk": patch
---

Lower the SDK `engines` floor from Node `>=22` to `>=18.17.0`.

The SDK runtime only relies on APIs available since Node 18.17 (`node:fs`/`path`/`crypto`/`os`, global `fetch`/`FormData`, `structuredClone`, web streams), so the previous floor was a repository baseline rather than a runtime requirement. The tsup build target is pinned to `es2022` so emitted syntax stays parseable on the new floor, and CI now runs an SDK-only packed-install smoke on Node 18 and 20 (`smoke-packed.ts --sdk-only`) to enforce the contract. The `@openagentpack/cli` and `@openagentpack/playground` packages keep their Node `>=22` requirement.
25 changes: 21 additions & 4 deletions .github/workflows/ci.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -52,12 +52,23 @@ jobs:
run: bun scripts/verify.ts full --step "${{ matrix.step }}"

package-compatibility:
name: Package compatibility / Node ${{ matrix.node }}
name: Package compatibility / Node ${{ matrix.node }}${{ matrix.scope == 'sdk' && ' (sdk-only)' || '' }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
node: [22, 24]
include:
# Full consumer smoke (sdk + playground + cli) on the repo baseline.
- node: 22
scope: all
- node: 24
scope: all
# SDK-only smoke on the sdk engines floor (>=18.17.0): cli/playground
# require Node >=22, so only the sdk tarball is installed and imported.
- node: 18.17.0
scope: sdk
- node: 20
scope: sdk
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
Expand All@@ -68,8 +79,14 @@ jobs:
node-version: ${{ matrix.node }}
package-manager-cache: false
- run: bun install --frozen-lockfile
- run: bun scripts/verify.ts release --step build-packages
- run: bun scripts/verify.ts release --step package-smoke
- if: matrix.scope == 'all'
run: |
bun scripts/verify.ts release --step build-packages
bun scripts/verify.ts release --step package-smoke
- if: matrix.scope == 'sdk'
run: |
bun run build:sdk
bun scripts/release/smoke-packed.ts --sdk-only

gate:
name: Gate
Expand Down
2 changes: 1 addition & 1 deletion packages/sdk/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,7 +20,7 @@
"url": "https://github.com/modelstudioai/OpenAgentPack/issues"
},
"engines": {
"node": ">=22"
"node": ">=18.17.0"
},
"type": "module",
"main": "./dist/index.js",
Expand Down
4 changes: 3 additions & 1 deletion packages/sdk/tsup.config.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -15,5 +15,7 @@ export default defineConfig({
},
},
clean: true,
target: "esnext",
// Keep the emitted syntax parseable by Node 18.17 (the package's engines
// floor): esnext would let future syntax (e.g. `using`) leak into dist.
target: "es2022",
});
9 changes: 7 additions & 2 deletions scripts/open-source.test.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -53,11 +53,16 @@ describe("open-source repository invariants", () => {
});

test("public packages require a maintained Node.js baseline", () => {
for (const pkg of ["sdk", "playground", "cli"]) {
const nodeBaselines: Record<string, string> = {
sdk: ">=18.17.0",
playground: ">=22",
cli: ">=22",
};
for (const [pkg, expectedNodeRange] of Object.entries(nodeBaselines)) {
const manifest = JSON.parse(readFileSync(resolve(root, `packages/${pkg}/package.json`), "utf8")) as {
engines?: { node?: string };
};
expect(manifest.engines?.node).toBe(">=22");
expect(manifest.engines?.node).toBe(expectedNodeRange);
}
});

Expand Down
18 changes: 17 additions & 1 deletion scripts/release/smoke-packed.test.ts
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
import { describe, expect, test } from "bun:test";
import { packedFilename } from "./smoke-packed.ts";
import { isSdkOnly, packedFilename, smokePackages } from "./smoke-packed.ts";

describe("npm pack output", () => {
test("accepts the npm 10 and 11 array format", () => {
Expand All@@ -14,3 +14,19 @@ describe("npm pack output", () => {
expect(() => packedFilename("[]", "sdk")).toThrow("Unexpected npm pack output for sdk");
});
});

describe("--sdk-only mode", () => {
test("detects the flag among CLI arguments", () => {
expect(isSdkOnly(["--sdk-only"])).toBe(true);
expect(isSdkOnly([])).toBe(false);
expect(isSdkOnly(["--verbose"])).toBe(false);
});

test("restricts the package set to sdk when enabled", () => {
expect(smokePackages(true)).toEqual(["sdk"]);
});

test("keeps the full package set when disabled", () => {
expect(smokePackages(false)).toEqual(["sdk", "playground", "cli"]);
});
});
51 changes: 34 additions & 17 deletions scripts/release/smoke-packed.ts
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
/**
* Pack the exact publish manifests, install them as an external npm consumer,
* then exercise every public SDK entry point plus the CLI and Playground bins.
*
* `--sdk-only` restricts the run to the SDK package: pack/install only the sdk
* tarball (still with --engine-strict) and execute the SDK entry-point imports.
* This is the mode CI uses on Node versions below the cli/playground engines
* floor (>=22) to enforce the SDK's own >=18.17.0 engines contract.
*/

import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
Expand All@@ -18,6 +23,14 @@ import {

const root = resolve(import.meta.dirname, "../..");

export function isSdkOnly(argv: readonly string[]): boolean {
return argv.includes("--sdk-only");
}

export function smokePackages(sdkOnly: boolean): readonly (typeof PACKAGES)[number][] {
return sdkOnly ? PACKAGES.filter((pkg) => pkg === "sdk") : PACKAGES;
}

type PackedPackage = { filename: string };

export function packedFilename(raw: string, pkg: string): string {
Expand All@@ -33,9 +46,9 @@ function run(command: string[], cwd: string, stdout: "inherit" | "pipe" = "inher
return stdout === "pipe" ? (result.stdout?.toString().trim() ?? "") : "";
}

function packPackages(destination: string): string[] {
function packPackages(destination: string, packages: readonly (typeof PACKAGES)[number][]): string[] {
const versions = workspaceVersions();
return PACKAGES.map((pkg) => {
return packages.map((pkg) => {
const pkgDir = join(root, "packages", pkg);
let originalLicense: string | undefined;
let originalManifest: string | undefined;
Expand DownExpand Up@@ -104,6 +117,8 @@ async function smokePlayground(consumer: string): Promise<void> {
}

async function main(): Promise<void> {
const sdkOnly = isSdkOnly(process.argv.slice(2));
const packages = smokePackages(sdkOnly);
const temporaryRoot = mkdtempSync(join(tmpdir(), "openagentpack-consumer-"));
try {
const tarballsDir = join(temporaryRoot, "tarballs");
Expand All@@ -112,7 +127,7 @@ async function main(): Promise<void> {
mkdirSync(consumer);
writeFileSync(join(consumer, "package.json"), '{"name":"agents-package-smoke","private":true,"type":"module"}\n');

const tarballs = packPackages(tarballsDir);
const tarballs = packPackages(tarballsDir, packages);
run(
[
"npm",
Expand All@@ -126,7 +141,7 @@ async function main(): Promise<void> {
],
consumer,
);
for (const pkg of PACKAGES) {
for (const pkg of packages) {
const license = readFileSync(join(consumer, `node_modules/@openagentpack/${pkg}/LICENSE`), "utf8");
if (license !== readFileSync(join(root, "LICENSE"), "utf8")) {
throw new Error(`${pkg} package is missing the repository license`);
Expand All@@ -143,21 +158,23 @@ async function main(): Promise<void> {
consumer,
);

const expectedVersion = JSON.parse(readFileSync(join(root, "packages/cli/package.json"), "utf8")) as {
version: string;
};
const cliVersion = run(
["node", join(consumer, "node_modules/@openagentpack/cli/dist/bin/agents.js"), "--version"],
consumer,
"pipe",
);
if (cliVersion !== expectedVersion.version) {
throw new Error(`Packed CLI version mismatch: expected ${expectedVersion.version}, received ${cliVersion}`);
}
if (!sdkOnly) {
const expectedVersion = JSON.parse(readFileSync(join(root, "packages/cli/package.json"), "utf8")) as {
version: string;
};
const cliVersion = run(
["node", join(consumer, "node_modules/@openagentpack/cli/dist/bin/agents.js"), "--version"],
consumer,
"pipe",
);
if (cliVersion !== expectedVersion.version) {
throw new Error(`Packed CLI version mismatch: expected ${expectedVersion.version}, received ${cliVersion}`);
}

await smokePlayground(consumer);
await smokePlayground(consumer);
}
const nodeVersion = run(["node", "--version"], consumer, "pipe");
console.log(`✓ Packed packages install and run under ${nodeVersion}`);
console.log(`✓ Packed ${sdkOnly ? "SDK package installs" : "packages install"} and run under ${nodeVersion}`);
} finally {
rmSync(temporaryRoot, { recursive: true, force: true });
}
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/sdk-node-engines-floor.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
---
"@openagentpack/sdk": patch
---

Lower the SDK `engines` floor from Node `>=22` to `>=18.17.0`.

The SDK runtime only relies on APIs available since Node 18.17 (`node:fs`/`path`/`crypto`/`os`, global `fetch`/`FormData`, `structuredClone`, web streams), so the previous floor was a repository baseline rather than a runtime requirement. The tsup build target is pinned to `es2022` so emitted syntax stays parseable on the new floor, and CI now runs an SDK-only packed-install smoke on Node 18 and 20 (`smoke-packed.ts --sdk-only`) to enforce the contract. The `@openagentpack/cli` and `@openagentpack/playground` packages keep their Node `>=22` requirement.
25 changes: 21 additions & 4 deletions .github/workflows/ci.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -52,12 +52,23 @@ jobs:
run: bun scripts/verify.ts full --step "${{ matrix.step }}"

package-compatibility:
name: Package compatibility / Node ${{ matrix.node }}
name: Package compatibility / Node ${{ matrix.node }}${{ matrix.scope == 'sdk' && ' (sdk-only)' || '' }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
node: [22, 24]
include:
# Full consumer smoke (sdk + playground + cli) on the repo baseline.
- node: 22
scope: all
- node: 24
scope: all
# SDK-only smoke on the sdk engines floor (>=18.17.0): cli/playground
# require Node >=22, so only the sdk tarball is installed and imported.
- node: 18.17.0
scope: sdk
- node: 20
scope: sdk
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
Expand All@@ -68,8 +79,14 @@ jobs:
node-version: ${{ matrix.node }}
package-manager-cache: false
- run: bun install --frozen-lockfile
- run: bun scripts/verify.ts release --step build-packages
- run: bun scripts/verify.ts release --step package-smoke
- if: matrix.scope == 'all'
run: |
bun scripts/verify.ts release --step build-packages
bun scripts/verify.ts release --step package-smoke
- if: matrix.scope == 'sdk'
run: |
bun run build:sdk
bun scripts/release/smoke-packed.ts --sdk-only

gate:
name: Gate
Expand Down
2 changes: 1 addition & 1 deletion packages/sdk/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,7 +20,7 @@
"url": "https://github.com/modelstudioai/OpenAgentPack/issues"
},
"engines": {
"node": ">=22"
"node": ">=18.17.0"
},
"type": "module",
"main": "./dist/index.js",
Expand Down
4 changes: 3 additions & 1 deletion packages/sdk/tsup.config.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -15,5 +15,7 @@ export default defineConfig({
},
},
clean: true,
target: "esnext",
// Keep the emitted syntax parseable by Node 18.17 (the package's engines
// floor): esnext would let future syntax (e.g. `using`) leak into dist.
target: "es2022",
});
9 changes: 7 additions & 2 deletions scripts/open-source.test.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -53,11 +53,16 @@ describe("open-source repository invariants", () => {
});

test("public packages require a maintained Node.js baseline", () => {
for (const pkg of ["sdk", "playground", "cli"]) {
const nodeBaselines: Record<string, string> = {
sdk: ">=18.17.0",
playground: ">=22",
cli: ">=22",
};
for (const [pkg, expectedNodeRange] of Object.entries(nodeBaselines)) {
const manifest = JSON.parse(readFileSync(resolve(root, `packages/${pkg}/package.json`), "utf8")) as {
engines?: { node?: string };
};
expect(manifest.engines?.node).toBe(">=22");
expect(manifest.engines?.node).toBe(expectedNodeRange);
}
});

Expand Down
18 changes: 17 additions & 1 deletion scripts/release/smoke-packed.test.ts
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
import { describe, expect, test } from "bun:test";
import { packedFilename } from "./smoke-packed.ts";
import { isSdkOnly, packedFilename, smokePackages } from "./smoke-packed.ts";

describe("npm pack output", () => {
test("accepts the npm 10 and 11 array format", () => {
Expand All@@ -14,3 +14,19 @@ describe("npm pack output", () => {
expect(() => packedFilename("[]", "sdk")).toThrow("Unexpected npm pack output for sdk");
});
});

describe("--sdk-only mode", () => {
test("detects the flag among CLI arguments", () => {
expect(isSdkOnly(["--sdk-only"])).toBe(true);
expect(isSdkOnly([])).toBe(false);
expect(isSdkOnly(["--verbose"])).toBe(false);
});

test("restricts the package set to sdk when enabled", () => {
expect(smokePackages(true)).toEqual(["sdk"]);
});

test("keeps the full package set when disabled", () => {
expect(smokePackages(false)).toEqual(["sdk", "playground", "cli"]);
});
});
51 changes: 34 additions & 17 deletions scripts/release/smoke-packed.ts
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
/**
* Pack the exact publish manifests, install them as an external npm consumer,
* then exercise every public SDK entry point plus the CLI and Playground bins.
*
* `--sdk-only` restricts the run to the SDK package: pack/install only the sdk
* tarball (still with --engine-strict) and execute the SDK entry-point imports.
* This is the mode CI uses on Node versions below the cli/playground engines
* floor (>=22) to enforce the SDK's own >=18.17.0 engines contract.
*/

import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
Expand All@@ -18,6 +23,14 @@ import {

const root = resolve(import.meta.dirname, "../..");

export function isSdkOnly(argv: readonly string[]): boolean {
return argv.includes("--sdk-only");
}

export function smokePackages(sdkOnly: boolean): readonly (typeof PACKAGES)[number][] {
return sdkOnly ? PACKAGES.filter((pkg) => pkg === "sdk") : PACKAGES;
}

type PackedPackage = { filename: string };

export function packedFilename(raw: string, pkg: string): string {
Expand All@@ -33,9 +46,9 @@ function run(command: string[], cwd: string, stdout: "inherit" | "pipe" = "inher
return stdout === "pipe" ? (result.stdout?.toString().trim() ?? "") : "";
}

function packPackages(destination: string): string[] {
function packPackages(destination: string, packages: readonly (typeof PACKAGES)[number][]): string[] {
const versions = workspaceVersions();
return PACKAGES.map((pkg) => {
return packages.map((pkg) => {
const pkgDir = join(root, "packages", pkg);
let originalLicense: string | undefined;
let originalManifest: string | undefined;
Expand DownExpand Up@@ -104,6 +117,8 @@ async function smokePlayground(consumer: string): Promise<void> {
}

async function main(): Promise<void> {
const sdkOnly = isSdkOnly(process.argv.slice(2));
const packages = smokePackages(sdkOnly);
const temporaryRoot = mkdtempSync(join(tmpdir(), "openagentpack-consumer-"));
try {
const tarballsDir = join(temporaryRoot, "tarballs");
Expand All@@ -112,7 +127,7 @@ async function main(): Promise<void> {
mkdirSync(consumer);
writeFileSync(join(consumer, "package.json"), '{"name":"agents-package-smoke","private":true,"type":"module"}\n');

const tarballs = packPackages(tarballsDir);
const tarballs = packPackages(tarballsDir, packages);
run(
[
"npm",
Expand All@@ -126,7 +141,7 @@ async function main(): Promise<void> {
],
consumer,
);
for (const pkg of PACKAGES) {
for (const pkg of packages) {
const license = readFileSync(join(consumer, `node_modules/@openagentpack/${pkg}/LICENSE`), "utf8");
if (license !== readFileSync(join(root, "LICENSE"), "utf8")) {
throw new Error(`${pkg} package is missing the repository license`);
Expand All@@ -143,21 +158,23 @@ async function main(): Promise<void> {
consumer,
);

const expectedVersion = JSON.parse(readFileSync(join(root, "packages/cli/package.json"), "utf8")) as {
version: string;
};
const cliVersion = run(
["node", join(consumer, "node_modules/@openagentpack/cli/dist/bin/agents.js"), "--version"],
consumer,
"pipe",
);
if (cliVersion !== expectedVersion.version) {
throw new Error(`Packed CLI version mismatch: expected ${expectedVersion.version}, received ${cliVersion}`);
}
if (!sdkOnly) {
const expectedVersion = JSON.parse(readFileSync(join(root, "packages/cli/package.json"), "utf8")) as {
version: string;
};
const cliVersion = run(
["node", join(consumer, "node_modules/@openagentpack/cli/dist/bin/agents.js"), "--version"],
consumer,
"pipe",
);
if (cliVersion !== expectedVersion.version) {
throw new Error(`Packed CLI version mismatch: expected ${expectedVersion.version}, received ${cliVersion}`);
}

await smokePlayground(consumer);
await smokePlayground(consumer);
}
const nodeVersion = run(["node", "--version"], consumer, "pipe");
console.log(`✓ Packed packages install and run under ${nodeVersion}`);
console.log(`✓ Packed ${sdkOnly ? "SDK package installs" : "packages install"} and run under ${nodeVersion}`);
} finally {
rmSync(temporaryRoot, { recursive: true, force: true });
}
Expand Down