From d543c19aae53df4285a8d10295daca68bbe85a25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Tue, 21 Apr 2026 18:57:20 -0400 Subject: [PATCH] Package release 0.4.170 from the current workspace snapshot Constraint: Windows and Linux artifacts are built on native GitHub runners from a temporary snapshot branch Rejected: Build Windows/Linux locally on Apple Silicon | docker/wine packaging is not stable on this host Confidence: medium Scope-risk: moderate Directive: This snapshot branch is transport-only and should be deleted after the release completes Tested: local verify/build stages before snapshot creation Not-tested: remote packaging before workflow execution --- package.json | 2 +- scripts/resume-release-public.mjs | 111 ++++++++++++++++++ .../ceo-phased-prompts.spec.ts | 6 +- .../work-products-lifecycle.spec.ts | 6 +- 4 files changed, 118 insertions(+), 7 deletions(-) create mode 100644 scripts/resume-release-public.mjs diff --git a/package.json b/package.json index 88c6d205..1db9e7cb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "agent-company", - "version": "0.4.167", + "version": "0.4.170", "private": true, "type": "module", "description": "Cross-platform Electron desktop control plane for local-first AI agent companies.", diff --git a/scripts/resume-release-public.mjs b/scripts/resume-release-public.mjs new file mode 100644 index 00000000..be9ea65e --- /dev/null +++ b/scripts/resume-release-public.mjs @@ -0,0 +1,111 @@ +import { existsSync } from "node:fs"; +import { readFile, readdir } from "node:fs/promises"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; +import { prepareR2ReleaseHost, uploadReleaseFiles } from "./release-local/r2.mjs"; +import { loadEnvFile } from "./release/lib/dotenv.mjs"; +import { createRunner } from "./release/lib/process.mjs"; +import { createWorkflowHelpers } from "./release/lib/workflow.mjs"; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const rootDir = join(__dirname, ".."); +const landingDir = join(rootDir, "landing"); +const localReleaseDir = join(rootDir, "local-release"); +const pagesProjectName = "agentcompany"; +const pagesBranch = "main"; + +const run = createRunner(rootDir); +const { getRepoInfo, cleanupRemoteTransportRelease } = createWorkflowHelpers(run); + +function parseArgs(argv) { + const options = { releaseTag: null, version: null }; + for (const arg of argv) { + if (arg.startsWith("--release-tag=")) options.releaseTag = arg.slice("--release-tag=".length).trim(); + else if (arg.startsWith("--version=")) options.version = arg.slice("--version=".length).trim(); + else throw new Error(`Unknown argument: ${arg}`); + } + if (!options.releaseTag || !options.version) { + throw new Error("Usage: node scripts/resume-release-public.mjs --release-tag= --version="); + } + return options; +} + +async function uploadWinAndLinuxToR2() { + const r2 = await prepareR2ReleaseHost({ rootDir }); + await uploadReleaseFiles({ rootDir, releaseDir: join(localReleaseDir, "win"), bucket: r2.bucket, releasePrefix: r2.releasePrefix }); + await uploadReleaseFiles({ rootDir, releaseDir: join(localReleaseDir, "linux"), bucket: r2.bucket, releasePrefix: r2.releasePrefix }); +} + +async function applyLandingMigrations() { + const migrationsDir = join(landingDir, "migrations"); + if (!existsSync(migrationsDir)) return; + const wranglerToml = await readFile(join(landingDir, "wrangler.toml"), "utf8"); + const match = wranglerToml.match(/database_name\s*=\s*"([^"]+)"/); + if (!match) return; + const databaseName = match[1]; + const migrationFiles = (await readdir(migrationsDir)).filter((n) => n.endsWith(".sql")).sort(); + for (const fileName of migrationFiles) { + const command = `printf 'y\\n' | npx wrangler d1 execute ${databaseName} --remote --file migrations/${fileName}`; + await run("bash", ["-lc", command], { cwd: landingDir }); + } +} + +async function buildAndDeployLanding() { + if (!existsSync(join(landingDir, "node_modules"))) { + await run("npm", ["ci"], { cwd: landingDir }); + } + await run("npm", ["run", "build"], { cwd: landingDir }); + const deploy = await run( + "npx", + ["wrangler", "pages", "deploy", "out", "--project-name", pagesProjectName, "--branch", pagesBranch, "--commit-dirty=true"], + { cwd: landingDir, capture: true }, + ); + const output = `${deploy.stdout}\n${deploy.stderr}`; + const urlMatch = output.match(/https:\/\/[^\s]+\.agentcompany\.pages\.dev/); + return urlMatch?.[0] ?? null; +} + +async function verifyPublishedAssets(version, deployUrl) { + const releaseLinksSource = await readFile(join(landingDir, "components", "release-links.generated.ts"), "utf8"); + const requiredFragments = [ + `AgentCompany-${version}-arm64.dmg`, + `AgentCompany.Setup.${version}.exe`, + `agent-company-${version}.aarch64.rpm`, + `agent-company_${version}_arm64.deb`, + ]; + for (const fragment of requiredFragments) { + if (!releaseLinksSource.includes(fragment)) { + throw new Error(`Missing expected release asset in generated links: ${fragment}`); + } + } + if (deployUrl) { + const { stdout } = await run("curl", ["-L", "--silent", "-A", "Mozilla/5.0", deployUrl], { capture: true }); + if (!stdout.includes(version)) { + throw new Error(`Deployed landing preview ${deployUrl} does not contain version ${version}.`); + } + } +} + +async function main() { + const options = parseArgs(process.argv.slice(2)); + await loadEnvFile(join(rootDir, ".env.release.local")); + await loadEnvFile(join(rootDir, ".env.release")); + + const repo = await getRepoInfo(); + + await uploadWinAndLinuxToR2(); + await run("node", ["scripts/generate-landing-release-links.mjs"]); + await applyLandingMigrations(); + const deployUrl = await buildAndDeployLanding(); + await verifyPublishedAssets(options.version, deployUrl); + await cleanupRemoteTransportRelease(repo, options.releaseTag); + + console.log(JSON.stringify({ + version: options.version, + releaseTag: options.releaseTag, + deployUrl, + updateUrl: process.env.AGENTCOMPANY_UPDATE_URL ?? null, + }, null, 2)); +} + +await main(); diff --git a/tests/integration/autonomous-chain/ceo-phased-prompts.spec.ts b/tests/integration/autonomous-chain/ceo-phased-prompts.spec.ts index ece1957d..2ecc0be6 100644 --- a/tests/integration/autonomous-chain/ceo-phased-prompts.spec.ts +++ b/tests/integration/autonomous-chain/ceo-phased-prompts.spec.ts @@ -28,10 +28,10 @@ describe("Step 2: CEO phased prompts", () => { // shows the hire POST curl up front, and reaches for the // server-enforced STRATEGY_BEFORE_HIRES / STRATEGY_ALREADY_FILED // codes instead of prose ordering directives. - expect(prompt).toContain("file 3–5 `hire_agent` requests"); + expect(prompt).toContain("file 3–5 `hire_agent` tool calls"); expect(prompt).toContain("Do this first — file the hires"); - expect(prompt).toContain("STRATEGY_BEFORE_HIRES"); - expect(prompt).toContain("STRATEGY_ALREADY_FILED"); + expect(prompt).toContain("file_approval.strategy.before_hires"); + expect(prompt).toContain("file_approval.strategy.already_filed"); expect(prompt).not.toContain("Awaiting Org Strategy Approval"); expect(prompt).not.toContain("Goal Decomposition"); }); diff --git a/tests/integration/work-products-lifecycle.spec.ts b/tests/integration/work-products-lifecycle.spec.ts index 58456f1b..b8c32847 100644 --- a/tests/integration/work-products-lifecycle.spec.ts +++ b/tests/integration/work-products-lifecycle.spec.ts @@ -174,7 +174,7 @@ describe("work_products archived visibility", () => { expect( harness.db.documentsMod.resolveWorkProductTarget(id, harness.companyId), - ).toBeNull(); + ).toMatchObject({ kind: "error", reason: "archived" }); } finally { harness.db.close(); } @@ -236,7 +236,7 @@ describe("work_products bulk retirement", () => { expect(second).toHaveLength(0); expect( harness.db.documentsMod.resolveWorkProductTarget(id, harness.companyId), - ).toBeNull(); + ).toMatchObject({ kind: "error", reason: "archived" }); } finally { harness.db.close(); } @@ -278,7 +278,7 @@ describe("work_products bulk retirement", () => { expect( harness.db.documentsMod.resolveWorkProductTarget(workProductId, harness.companyId), - ).toBeNull(); + ).toMatchObject({ kind: "error", reason: "archived" }); } finally { harness.db.close(); }