diff --git a/.agentworkforce/trajectories/completed/2026-06/traj_t83sls93qtdp/summary.md b/.agentworkforce/trajectories/completed/2026-06/traj_t83sls93qtdp/summary.md new file mode 100644 index 000000000..b2605fa25 --- /dev/null +++ b/.agentworkforce/trajectories/completed/2026-06/traj_t83sls93qtdp/summary.md @@ -0,0 +1,39 @@ +# Trajectory: Fix GitHub issue 1169 + +> **Status:** ✅ Completed +> **Confidence:** 92% +> **Started:** June 19, 2026 at 01:30 PM +> **Completed:** June 19, 2026 at 01:34 PM + +--- + +## Summary + +Fixed fleet serve node-definition loading for Bun-compiled CLI by using native import for JavaScript node files, lazy-loading jiti only for TypeScript sources, and validating the fleet marker locally to avoid compiled runtime import failures. + +**Approach:** Standard approach + +--- + +## Key Decisions + +### Use native dynamic import for JS fleet node definitions + +- **Chose:** Use native dynamic import for JS fleet node definitions +- **Reasoning:** The Bun-compiled CLI fails when jiti constructs its transform backend; compiled JS can be loaded by the runtime directly, so jiti should only be loaded for TypeScript-like sources that need transpilation. + +### Validate loaded fleet node definitions locally in the CLI + +- **Chose:** Validate loaded fleet node definitions locally in the CLI +- **Reasoning:** The Bun-compiled smoke showed the fleet SDK validator import can be undefined in the standalone binary; the public validator only checks the \_\_agentRelayFleetNode marker, so a local marker check avoids another runtime import failure without changing validation behavior. + +--- + +## Chapters + +### 1. Work + +_Agent: default_ + +- Use native dynamic import for JS fleet node definitions: Use native dynamic import for JS fleet node definitions +- Validate loaded fleet node definitions locally in the CLI: Validate loaded fleet node definitions locally in the CLI diff --git a/.agentworkforce/trajectories/completed/2026-06/traj_t83sls93qtdp/trajectory.json b/.agentworkforce/trajectories/completed/2026-06/traj_t83sls93qtdp/trajectory.json new file mode 100644 index 000000000..02c2e9510 --- /dev/null +++ b/.agentworkforce/trajectories/completed/2026-06/traj_t83sls93qtdp/trajectory.json @@ -0,0 +1,65 @@ +{ + "id": "traj_t83sls93qtdp", + "version": 1, + "task": { + "title": "Fix GitHub issue 1169" + }, + "status": "completed", + "startedAt": "2026-06-19T11:30:05.358Z", + "completedAt": "2026-06-19T11:34:06.470Z", + "agents": [ + { + "name": "default", + "role": "lead", + "joinedAt": "2026-06-19T11:31:29.687Z" + } + ], + "chapters": [ + { + "id": "chap_h8ukvp6tcyks", + "title": "Work", + "agentName": "default", + "startedAt": "2026-06-19T11:31:29.687Z", + "endedAt": "2026-06-19T11:34:06.470Z", + "events": [ + { + "ts": 1781868689689, + "type": "decision", + "content": "Use native dynamic import for JS fleet node definitions: Use native dynamic import for JS fleet node definitions", + "raw": { + "question": "Use native dynamic import for JS fleet node definitions", + "chosen": "Use native dynamic import for JS fleet node definitions", + "alternatives": [], + "reasoning": "The Bun-compiled CLI fails when jiti constructs its transform backend; compiled JS can be loaded by the runtime directly, so jiti should only be loaded for TypeScript-like sources that need transpilation." + }, + "significance": "high" + }, + { + "ts": 1781868801483, + "type": "decision", + "content": "Validate loaded fleet node definitions locally in the CLI: Validate loaded fleet node definitions locally in the CLI", + "raw": { + "question": "Validate loaded fleet node definitions locally in the CLI", + "chosen": "Validate loaded fleet node definitions locally in the CLI", + "alternatives": [], + "reasoning": "The Bun-compiled smoke showed the fleet SDK validator import can be undefined in the standalone binary; the public validator only checks the __agentRelayFleetNode marker, so a local marker check avoids another runtime import failure without changing validation behavior." + }, + "significance": "high" + } + ] + } + ], + "retrospective": { + "summary": "Fixed fleet serve node-definition loading for Bun-compiled CLI by using native import for JavaScript node files, lazy-loading jiti only for TypeScript sources, and validating the fleet marker locally to avoid compiled runtime import failures.", + "approach": "Standard approach", + "confidence": 0.92 + }, + "commits": [], + "filesChanged": [], + "projectId": "AgentWorkforce/relay", + "tags": [], + "_trace": { + "startRef": "66f972f23cafb958fa1956d3654710423e2391b5", + "endRef": "66f972f23cafb958fa1956d3654710423e2391b5" + } +} diff --git a/.agentworkforce/trajectories/completed/2026-06/traj_upshg9a9y5tx/summary.md b/.agentworkforce/trajectories/completed/2026-06/traj_upshg9a9y5tx/summary.md new file mode 100644 index 000000000..4094ab904 --- /dev/null +++ b/.agentworkforce/trajectories/completed/2026-06/traj_upshg9a9y5tx/summary.md @@ -0,0 +1,33 @@ +# Trajectory: Address PR 1170 bot feedback + +> **Status:** ✅ Completed +> **Confidence:** 90% +> **Started:** June 19, 2026 at 01:41 PM +> **Completed:** June 19, 2026 at 01:42 PM + +--- + +## Summary + +Addressed PR 1170 automated review feedback by adding marker-aware default unwrapping for CommonJS default wrappers and a Node-only syntax-error fallback to jiti for ESM-syntax .js files in CommonJS projects. + +**Approach:** Standard approach + +--- + +## Key Decisions + +### Preserve Node.js JS interop while keeping Bun standalone off the jiti fallback + +- **Chose:** Preserve Node.js JS interop while keeping Bun standalone off the jiti fallback +- **Reasoning:** Native import fixes compiled JavaScript node definitions in the Bun binary. Node.js can still hit SyntaxError for ESM-syntax .js files in CommonJS projects, so the loader falls back to jiti only for syntax failures outside Bun and unwraps nested CommonJS default wrappers. + +--- + +## Chapters + +### 1. Work + +_Agent: default_ + +- Preserve Node.js JS interop while keeping Bun standalone off the jiti fallback: Preserve Node.js JS interop while keeping Bun standalone off the jiti fallback diff --git a/.agentworkforce/trajectories/completed/2026-06/traj_upshg9a9y5tx/trajectory.json b/.agentworkforce/trajectories/completed/2026-06/traj_upshg9a9y5tx/trajectory.json new file mode 100644 index 000000000..ad4fce0d6 --- /dev/null +++ b/.agentworkforce/trajectories/completed/2026-06/traj_upshg9a9y5tx/trajectory.json @@ -0,0 +1,53 @@ +{ + "id": "traj_upshg9a9y5tx", + "version": 1, + "task": { + "title": "Address PR 1170 bot feedback" + }, + "status": "completed", + "startedAt": "2026-06-19T11:41:01.857Z", + "completedAt": "2026-06-19T11:42:49.228Z", + "agents": [ + { + "name": "default", + "role": "lead", + "joinedAt": "2026-06-19T11:42:15.525Z" + } + ], + "chapters": [ + { + "id": "chap_tvtdh06tamro", + "title": "Work", + "agentName": "default", + "startedAt": "2026-06-19T11:42:15.525Z", + "endedAt": "2026-06-19T11:42:49.228Z", + "events": [ + { + "ts": 1781869335526, + "type": "decision", + "content": "Preserve Node.js JS interop while keeping Bun standalone off the jiti fallback: Preserve Node.js JS interop while keeping Bun standalone off the jiti fallback", + "raw": { + "question": "Preserve Node.js JS interop while keeping Bun standalone off the jiti fallback", + "chosen": "Preserve Node.js JS interop while keeping Bun standalone off the jiti fallback", + "alternatives": [], + "reasoning": "Native import fixes compiled JavaScript node definitions in the Bun binary. Node.js can still hit SyntaxError for ESM-syntax .js files in CommonJS projects, so the loader falls back to jiti only for syntax failures outside Bun and unwraps nested CommonJS default wrappers." + }, + "significance": "high" + } + ] + } + ], + "retrospective": { + "summary": "Addressed PR 1170 automated review feedback by adding marker-aware default unwrapping for CommonJS default wrappers and a Node-only syntax-error fallback to jiti for ESM-syntax .js files in CommonJS projects.", + "approach": "Standard approach", + "confidence": 0.9 + }, + "commits": [], + "filesChanged": [], + "projectId": "AgentWorkforce/relay", + "tags": [], + "_trace": { + "startRef": "29a9c0361c067786c9771b87c5837d6d261b093e", + "endRef": "29a9c0361c067786c9771b87c5837d6d261b093e" + } +} diff --git a/CHANGELOG.md b/CHANGELOG.md index ddff7f109..4ba21fb1b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +- `agent-relay fleet serve ` loads plain JavaScript node definitions without `jiti`, so the published Bun-compiled CLI can serve compiled JS node files. - Spawned opencode worker agents no longer pause for interactive tool-approval prompts; the broker injects a wildcard allow-all permission block into every generated `opencode.json`, augmenting existing partial permission objects rather than replacing them. ### Added diff --git a/packages/cli/src/cli/commands/fleet.test.ts b/packages/cli/src/cli/commands/fleet.test.ts index 2e4fb33ad..8769d4ece 100644 --- a/packages/cli/src/cli/commands/fleet.test.ts +++ b/packages/cli/src/cli/commands/fleet.test.ts @@ -1,4 +1,6 @@ import { once } from 'node:events'; +import { mkdtemp, rm, writeFile } from 'node:fs/promises'; +import os from 'node:os'; import path from 'node:path'; import { Command } from 'commander'; @@ -70,6 +72,80 @@ describe('fleet command support', () => { expect(Object.keys(node.capabilities)).toContain('spawn:codex'); }); + it('loads a plain JS node file through native import', async () => { + const dir = await mkdtemp(path.join(os.tmpdir(), 'relay-node-def-')); + try { + await writeFile(path.join(dir, 'package.json'), JSON.stringify({ type: 'module' })); + const file = path.join(dir, 'node-def.js'); + await writeFile( + file, + [ + 'export default {', + ' __agentRelayFleetNode: true,', + ' name: "plain-js-node",', + ' capabilities: {},', + ' triggers: [],', + '};', + ].join('\n') + ); + + const node = await loadNodeDefinition(file); + + expect(node.name).toBe('plain-js-node'); + } finally { + await rm(dir, { recursive: true, force: true }); + } + }); + + it('loads CommonJS compiled JS node files that export default wrappers', async () => { + const dir = await mkdtemp(path.join(os.tmpdir(), 'relay-node-def-')); + try { + const file = path.join(dir, 'node-def.js'); + await writeFile( + file, + [ + 'exports.default = {', + ' __agentRelayFleetNode: true,', + ' name: "compiled-cjs-node",', + ' capabilities: {},', + ' triggers: [],', + '};', + ].join('\n') + ); + + const node = await loadNodeDefinition(file); + + expect(node.name).toBe('compiled-cjs-node'); + } finally { + await rm(dir, { recursive: true, force: true }); + } + }); + + it('falls back to jiti for ESM-syntax JS node files in CommonJS projects', async () => { + const dir = await mkdtemp(path.join(os.tmpdir(), 'relay-node-def-')); + try { + await writeFile(path.join(dir, 'package.json'), JSON.stringify({ type: 'commonjs' })); + const file = path.join(dir, 'node-def.js'); + await writeFile( + file, + [ + 'export default {', + ' __agentRelayFleetNode: true,', + ' name: "commonjs-project-esm-node",', + ' capabilities: {},', + ' triggers: [],', + '};', + ].join('\n') + ); + + const node = await loadNodeDefinition(file); + + expect(node.name).toBe('commonjs-project-esm-node'); + } finally { + await rm(dir, { recursive: true, force: true }); + } + }); + it('registers a served node and dispatches invoke_handler over a stub broker', async () => { const node = defineNode({ name: 'stub-node', diff --git a/packages/cli/src/cli/commands/fleet.ts b/packages/cli/src/cli/commands/fleet.ts index b5747b8b0..f78b48ba2 100644 --- a/packages/cli/src/cli/commands/fleet.ts +++ b/packages/cli/src/cli/commands/fleet.ts @@ -2,14 +2,9 @@ import path from 'node:path'; import { pathToFileURL } from 'node:url'; import type { Command } from 'commander'; -import { createJiti } from 'jiti'; import { HarnessDriverClient } from '@agent-relay/harness-driver'; import { enrollFleetNode, type FleetNodeEnrollment } from '@agent-relay/cloud'; import type { FleetNodeDefinition } from '@agent-relay/fleet'; -// Namespace import sidesteps bun --compile's named-import validation against the -// package .d.ts (see cli/lib/fleet-sidecar.ts). -import * as fleetSdk from '@agent-relay/fleet'; -const { isFleetNodeDefinition } = fleetSdk; import { withDefaults, type CoreDependencies, type CoreProjectPaths } from './core.js'; import { readBrokerConnection, startBrokerWithPortFallback } from '../lib/broker-lifecycle.js'; @@ -30,6 +25,8 @@ import { type SdkCommandDeps, } from '../lib/sdk-command.js'; +const JITI_NODE_DEFINITION_EXTENSIONS = new Set(['.ts', '.tsx', '.mts', '.cts']); + export interface FleetCommandDependencies { core: CoreDependencies; sdk: SdkCommandDeps; @@ -158,20 +155,81 @@ export function registerFleetCommands( export async function loadNodeDefinition(file: string): Promise { const absolutePath = path.resolve(file); - const jiti = createJiti(pathToFileURL(process.cwd()).href, { - interopDefault: true, - }); - const loaded = (await jiti.import(absolutePath, { default: true })) as unknown; - const definition = - loaded && typeof loaded === 'object' && 'default' in loaded - ? (loaded as { default?: unknown }).default - : loaded; - if (!isFleetNodeDefinition(definition)) { + const loaded = await importNodeDefinition(absolutePath); + const definition = unwrapNodeDefinitionExport(loaded); + if (!isFleetNodeDefinitionLike(definition)) { throw new Error(`Fleet node file ${absolutePath} must default-export defineNode(...)`); } return definition; } +function unwrapNodeDefinitionExport(loaded: unknown): unknown { + let candidate = loaded; + for (let depth = 0; depth < 3; depth += 1) { + if (isFleetNodeDefinitionLike(candidate)) { + return candidate; + } + if (!candidate || typeof candidate !== 'object' || !('default' in candidate)) { + return candidate; + } + candidate = (candidate as { default?: unknown }).default; + } + return candidate; +} + +function isFleetNodeDefinitionLike(value: unknown): value is FleetNodeDefinition { + return Boolean( + value && + typeof value === 'object' && + (value as { __agentRelayFleetNode?: unknown }).__agentRelayFleetNode === true + ); +} + +async function importNodeDefinition(absolutePath: string): Promise { + if (!JITI_NODE_DEFINITION_EXTENSIONS.has(path.extname(absolutePath).toLowerCase())) { + try { + return (await import(pathToFileURL(absolutePath).href)) as unknown; + } catch (error) { + if (!shouldFallbackToJiti(error)) { + throw error; + } + + try { + return await importNodeDefinitionWithJiti(absolutePath); + } catch { + throw error; + } + } + } + + return importNodeDefinitionWithJiti(absolutePath); +} + +function shouldFallbackToJiti(error: unknown): boolean { + if (isBunRuntime()) { + return false; + } + return error instanceof SyntaxError && getErrorCode(error) !== 'ERR_MODULE_NOT_FOUND'; +} + +function isBunRuntime(): boolean { + return typeof (process.versions as NodeJS.ProcessVersions & { bun?: string }).bun === 'string'; +} + +function getErrorCode(error: unknown): string | undefined { + return error && typeof error === 'object' && 'code' in error + ? String((error as { code?: unknown }).code) + : undefined; +} + +async function importNodeDefinitionWithJiti(absolutePath: string): Promise { + const { createJiti } = await import('jiti'); + const jiti = createJiti(pathToFileURL(process.cwd()).href, { + interopDefault: true, + }); + return jiti.import(absolutePath, { default: true }) as Promise; +} + /** * In enrollment mode the one-time token is exchanged for durable node * credentials BEFORE the broker boots. The returned credentials populate the env