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
6 changes: 6 additions & 0 deletions scripts/build.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,12 @@ async function main() {
version: BUILD_VERSION,
});
console.log(` ✓ ${agentsResult.count} agent prompt(s) → dist/agents/ + agent-menu.json`);
if (agentsResult.baseUrl.startsWith('file:')) {
console.log(
' ! Unversioned build: prompts point at the local files, not at a release.\n'
+ ' Set AGENTS_BASE_URL, or run `npm run dev`, to serve them over HTTP.',
);
}

const releaseAssetDocs = docEntries.filter(d => d.release_asset);
if (releaseAssetDocs.length > 0) {
Expand Down
23 changes: 17 additions & 6 deletions scripts/lib/agent-generator.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,23 @@

import fs from 'fs';
import path from 'path';
import { pathToFileURL } from 'url';
import { REPO_URL } from './constants.js';

/** Release assets are a flat namespace: agent prompts live at the release root, like skills. */
function defaultAgentsBaseUrl(version) {
/**
* Where a build says its prompts live.
*
* Release assets are a flat namespace: agent prompts live at the release root,
* like skills. An unversioned build has no release, so it points at the files
* it just wrote: `releases/latest/download` made it advertise prompts the
* latest release does not carry, and a flow still on a branch then failed the
* whole run with a bare 404. Set `AGENTS_BASE_URL`, as the dev server does, to
* serve the same files over HTTP.
*/
function defaultAgentsBaseUrl(version, agentsDistDir) {
return version && version !== 'dev'
? `${REPO_URL}/releases/download/v${version}`
: `${REPO_URL}/releases/latest/download`;
: pathToFileURL(agentsDistDir).href;
}

/**
Expand Down Expand Up @@ -98,12 +108,13 @@ function assertRunnerSeededHasSink(flows) {
* Copy every agent-prompt markdown file into dist/agents/<flow>/ and write the
* menu the wizard fetches to discover available types. Each menu entry carries
* its flow and a full downloadUrl so the dev-server and the release host can
* differ without the wizard composing URLs. Returns { count, agentsDistDir }.
* differ without the wizard composing URLs. Returns
* { count, agentsDistDir, baseUrl }.
*/
export function buildAgents({ configDir, distDir, baseUrl, version = 'dev' }) {
const agentsSourceDir = path.join(configDir, 'agents');
const agentsDistDir = path.join(distDir, 'agents');
const resolvedBase = (baseUrl || defaultAgentsBaseUrl(version)).replace(/\/+$/, '');
const resolvedBase = (baseUrl || defaultAgentsBaseUrl(version, agentsDistDir)).replace(/\/+$/, '');

fs.mkdirSync(agentsDistDir, { recursive: true });

Expand Down Expand Up @@ -147,5 +158,5 @@ export function buildAgents({ configDir, distDir, baseUrl, version = 'dev' }) {
};
walk(agentsDistDir);

return { count: agents.length, agentsDistDir };
return { count: agents.length, agentsDistDir, baseUrl: resolvedBase };
}
39 changes: 39 additions & 0 deletions scripts/lib/tests/agent-generator-flow.test.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { mkdirSync, writeFileSync, mkdtempSync, rmSync, readFileSync, existsSync } from 'fs';
import { join } from 'path';
import { pathToFileURL, fileURLToPath } from 'url';
import { tmpdir } from 'os';

import { buildAgents } from '../agent-generator.js';
import { REPO_URL } from '../constants.js';

const prompt = (frontmatter) => `---\n${frontmatter}\n---\n\n## Goal\n\nDo the thing.\n`;

Expand Down Expand Up @@ -100,3 +102,40 @@ describe('buildAgents flow frontmatter', () => {
expect(existsSync(join(distDir, 'agents', 'my-flow', 'README.md'))).toBe(false);
});
});

describe('buildAgents download URLs', () => {
let tmpDir;
let configDir;
let distDir;

beforeEach(() => {
tmpDir = mkdtempSync(join(tmpdir(), 'agents-url-test-'));
configDir = join(tmpDir, 'context');
distDir = join(tmpDir, 'dist');
mkdirSync(join(configDir, 'agents', 'my-flow'), { recursive: true });
writeFileSync(
join(configDir, 'agents', 'my-flow', 'task.md'),
prompt('type: task\nflow: my-flow'),
);
});

afterEach(() => rmSync(tmpDir, { recursive: true, force: true }));

const menuOf = () =>
JSON.parse(readFileSync(join(distDir, 'agents', 'agent-menu.json'), 'utf8'));

it('pins a versioned build to its own release', () => {
buildAgents({ configDir, distDir, version: '1.47.0' });
expect(menuOf().agents[0].downloadUrl).toBe(
`${REPO_URL}/releases/download/v1.47.0/agents-my-flow-task.md`,
);
});

it('points an unversioned build at the files it just wrote', () => {
const { baseUrl } = buildAgents({ configDir, distDir });
const url = menuOf().agents[0].downloadUrl;
expect(baseUrl).toBe(pathToFileURL(join(distDir, 'agents')).href);
expect(url).toBe(pathToFileURL(join(distDir, 'agents', 'agents-my-flow-task.md')).href);
expect(existsSync(fileURLToPath(url))).toBe(true);
});
});
Loading