Skip to content
Draft
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
2 changes: 1 addition & 1 deletion .github/workflows/bench.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
node-version: 24
cache: pnpm
cache-dependency-path: pnpm-lock.yaml
- uses: dtolnay/rust-toolchain@stable
- uses: dtolnay/rust-toolchain@1.98.1
with:
targets: wasm32-wasip1
- uses: dtolnay/rust-toolchain@nightly
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ jobs:
node-version: 24
cache: pnpm
cache-dependency-path: pnpm-lock.yaml
- uses: dtolnay/rust-toolchain@stable
- uses: dtolnay/rust-toolchain@1.98.1
with:
components: rustfmt
- run: |
Expand Down Expand Up @@ -134,7 +134,7 @@ jobs:
node-version: 24
cache: pnpm
cache-dependency-path: pnpm-lock.yaml
- uses: dtolnay/rust-toolchain@stable
- uses: dtolnay/rust-toolchain@1.98.1
with:
components: rustfmt, clippy
- uses: Swatinem/rust-cache@v2
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/publish.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -421,7 +421,7 @@ jobs:
node-version: 24
cache: pnpm
cache-dependency-path: pnpm-lock.yaml
- uses: dtolnay/rust-toolchain@stable
- uses: dtolnay/rust-toolchain@1.98.1
- uses: Swatinem/rust-cache@v2
with:
workspaces: . -> target
Expand Down
22 changes: 8 additions & 14 deletions packages/core/scripts/stage-default-software.mjs
Original file line number Diff line number Diff line change
@@ -1,34 +1,28 @@
import { cpSync, mkdirSync, rmSync, statSync } from "node:fs";
import { cpSync, mkdirSync, readFileSync, rmSync, statSync } from "node:fs";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";

const DEFAULT_SOFTWARE = [
"coreutils",
"sed",
"grep",
"gawk",
"findutils",
"diffutils",
"tar",
"gzip",
];

const packageRoot = join(dirname(fileURLToPath(import.meta.url)), "..");
const repoRoot = join(packageRoot, "..", "..");
const listPath = join(repoRoot, "software", "default-software.json");
const outputDir = join(packageRoot, "dist", "default-software");
const defaultSoftware = JSON.parse(readFileSync(listPath, "utf8"));
Comment on lines +7 to +9

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 Medium · The shared default list is invisible to the Core build cache

@rivet-dev/agentos-core now derives a published dist/ artifact from software/default-software.json, but the Turbo build task hashes only src/**, tsconfig.json, and package.json (and it does not hash this scripts/ file either). After a cached Core build, changing only the shared list can therefore restore the old dist/default-software/default-software.json and package set while the release artifact publisher reads the new list directly, making the two consumers diverge. Add a Core-specific build input for this script and $TURBO_ROOT$/software/default-software.json (or make the JSON a global dependency) so list changes invalidate the staged output.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

silence


rmSync(outputDir, { recursive: true, force: true });
mkdirSync(outputDir, { recursive: true });

for (const name of DEFAULT_SOFTWARE) {
for (const name of defaultSoftware) {
const source = join(repoRoot, "software", name, "dist", "package.aospkg");
const size = statSync(source).size;
if (size === 0) {
throw new Error(`default software artifact is empty: ${source}`);
}
cpSync(source, join(outputDir, `${name}.aospkg`));
}
// The runtime reads the list from the staged directory, so the published
// package carries it next to the artifacts.
cpSync(listPath, join(outputDir, "default-software.json"));

process.stdout.write(
`staged ${DEFAULT_SOFTWARE.length} default software artifacts -> ${outputDir}\n`,
`staged ${defaultSoftware.length} default software artifacts -> ${outputDir}\n`,
);
26 changes: 10 additions & 16 deletions packages/core/src/default-software.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,12 @@
import { readFileSync } from "node:fs";
import type { SoftwarePackageRef } from "./agentos-package.js";

const DEFAULT_SOFTWARE = [
"coreutils",
"sed",
"grep",
"gawk",
"findutils",
"diffutils",
"tar",
"gzip",
] as const;

/**
* Default software for a bare `AgentOs.create()`. These immutable `.aospkg`
* files are vendored into the Core package at build time; runtime resolution
* never consults npm or scans node_modules. Opt out with
* `defaultSoftware: false`; add more trusted paths via `software`.
* Default software for a bare `AgentOs.create()`. The build stages the list
* from `software/default-software.json` and its immutable `.aospkg` files into
* the Core package; runtime resolution never consults npm or scans
* node_modules. Opt out with `defaultSoftware: false`; add more trusted paths
* via `software`.
*/
export function resolveDefaultSoftware(): SoftwarePackageRef[] {
// Published consumers execute this module from dist/, while Vitest executes
Expand All @@ -25,8 +16,11 @@ export function resolveDefaultSoftware(): SoftwarePackageRef[] {
const artifactDirectory = moduleDirectory.pathname.endsWith("/src/")
? new URL("../dist/default-software/", moduleDirectory)
: new URL("./default-software/", moduleDirectory);
const names: string[] = JSON.parse(
readFileSync(new URL("default-software.json", artifactDirectory), "utf8"),
);

return DEFAULT_SOFTWARE.map((name) => ({
return names.map((name) => ({
packagePath: new URL(`${name}.aospkg`, artifactDirectory).pathname,
}));
}
4 changes: 4 additions & 0 deletions rust-toolchain.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[toolchain]
channel = "1.98.1"
components = ["clippy", "rustfmt"]
profile = "minimal"
2 changes: 1 addition & 1 deletion scripts/publish/src/ci/bin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ program
// ---------------------------------------------------------------------------
program
.command("stage-software")
.description("Stage immutable .aospkg files and their manifest")
.description("Stage immutable .aospkg files, their manifest, and the default software list")
.requiredOption("--output <dir>", "Local artifact directory to replace")
.action((opts) => {
const repoRoot = findRepoRoot();
Expand Down
39 changes: 39 additions & 0 deletions scripts/publish/src/lib/software-artifacts.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ test("stages deterministic digest-addressed .aospkg artifacts", () => {
);
const bytes = Buffer.from("aospkg fixture");
writeFileSync(join(packageDir, "dist", "package.aospkg"), bytes);
writeFileSync(
join(root, "software", "default-software.json"),
JSON.stringify(["example"]),
);

const result = stageSoftwareArtifacts(
root,
Expand All @@ -60,6 +64,10 @@ test("stages deterministic digest-addressed .aospkg artifacts", () => {
JSON.parse(readFileSync(result.manifestPath, "utf8")),
result.manifest,
);
assert.deepEqual(
JSON.parse(readFileSync(result.defaultSoftwarePath, "utf8")),
result.manifest,
);
} finally {
rmSync(root, { recursive: true, force: true });
}
Expand Down Expand Up @@ -119,3 +127,34 @@ test("refuses output outside the repository or with an ambiguous name", () => {
rmSync(root, { recursive: true, force: true });
}
});

test("fails when a default package is not in the software catalog", () => {
const root = mkdtempSync(join(tmpdir(), "agentos-software-artifacts-"));
try {
const packageDir = join(root, "software", "example");
mkdirSync(join(packageDir, "dist"), { recursive: true });
writeFileSync(
join(packageDir, "package.json"),
JSON.stringify({ name: "@agentos-software/example", version: "0.0.1" }),
);
writeFileSync(
join(packageDir, "agentos-package.json"),
JSON.stringify({ commands: ["example"] }),
);
writeFileSync(join(packageDir, "dist", "package.aospkg"), "aospkg fixture");
writeFileSync(
join(root, "software", "default-software.json"),
JSON.stringify(["coreutils"]),
);
assert.throws(
() =>
stageSoftwareArtifacts(
root,
join(root, "target", "software-artifacts"),
),
/default software coreutils is not in the software catalog/,
);
} finally {
rmSync(root, { recursive: true, force: true });
}
});
24 changes: 23 additions & 1 deletion scripts/publish/src/lib/software-artifacts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ export interface SoftwareArtifactManifest {
export interface StageSoftwareArtifactsResult {
manifest: SoftwareArtifactManifest;
manifestPath: string;
/** Manifest entries of the default software, in install order. */
defaultSoftware: SoftwareArtifactManifest;
defaultSoftwarePath: string;
outputDir: string;
}

Expand Down Expand Up @@ -129,5 +132,24 @@ export function stageSoftwareArtifacts(
const manifest: SoftwareArtifactManifest = { schemaVersion: 1, artifacts };
const manifestPath = join(outputDir, "manifest.json");
writeFileSync(manifestPath, `${JSON.stringify(manifest, null, "\t")}\n`);
return { manifest, manifestPath, outputDir };

const defaultSoftwareNames: string[] = JSON.parse(
readFileSync(join(softwareRoot, "default-software.json"), "utf8"),
);
const defaultSoftware: SoftwareArtifactManifest = {
schemaVersion: 1,
artifacts: defaultSoftwareNames.map((name) => {
const artifact = artifacts.find((candidate) => candidate.name === name);
if (!artifact) {
throw new Error(`default software ${name} is not in the software catalog`);
}
return artifact;
}),
};
const defaultSoftwarePath = join(outputDir, "default-software.json");
writeFileSync(
defaultSoftwarePath,
`${JSON.stringify(defaultSoftware, null, "\t")}\n`,
);
return { manifest, manifestPath, defaultSoftware, defaultSoftwarePath, outputDir };
}
10 changes: 10 additions & 0 deletions software/default-software.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[
"coreutils",
"sed",
"grep",
"gawk",
"findutils",
"diffutils",
"tar",
"gzip"
]
Loading