fix(dashboard,core,engine): statically import @fusion/engine to fix createFnAgent undefined in published CLI
The dashboard modules used a variable-specifier dynamic import (`const m = "@fusion/engine"; await import(m)`) to defeat bundler static analysis. tsup honored that and left the dynamic import in dist/bin.js, so the published `@runfusion/fusion` package failed at runtime with "createFnAgent2 is not a function" — `@fusion/engine` isn't on npm and the silent catch set the binding to undefined. Replaces the trick with static imports across planning, chat, subtask-breakdown, mission-interview, agent-generation, ai-refine, roadmap-suggestions, milestone-slice-interview, and routes. Core can't statically import engine (cycle), so it now exposes setCreateFnAgent and engine wires itself in at module load. Documents the pattern in AGENTS.md. Fixes Runfusion/Fusion#9. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
22
AGENTS.md
22
AGENTS.md
@@ -46,6 +46,28 @@ The release script is also the required path for keeping both public npm package
|
||||
|
||||
Only `@runfusion/fusion` is published. The others are internal workspace packages.
|
||||
|
||||
### Importing across `@fusion/*` packages
|
||||
|
||||
Because `@fusion/core`, `@fusion/dashboard`, and `@fusion/engine` are **not** on npm, the published CLI bundle (`packages/cli/dist/bin.js`) only works because tsup is configured with `noExternal: [/^@fusion\//]` — every `@fusion/*` import gets inlined into the bundle.
|
||||
|
||||
For that inlining to happen the import must be **statically analyzable**. The following anti-pattern silently breaks on the published `npm i -g @runfusion/fusion`:
|
||||
|
||||
```ts
|
||||
// ❌ BROKEN: variable specifier defeats static analysis
|
||||
const engineModule = "@fusion/engine";
|
||||
const engine = await import(/* @vite-ignore */ engineModule);
|
||||
createFnAgent = engine.createFnAgent;
|
||||
```
|
||||
|
||||
esbuild leaves the dynamic `import("@fusion/engine")` in the output, the package isn't installed at runtime, the import throws, the catch silently sets the binding to `undefined`, and the next call fails with a confusing TypeError like `createFnAgent2 is not a function` (issue Runfusion/Fusion#9, FN-2613). Affects every AI flow in the dashboard.
|
||||
|
||||
**Rules:**
|
||||
|
||||
1. **Default to a static import** — `import { createFnAgent } from "@fusion/engine"` — so esbuild can bundle it and tests can `vi.mock("@fusion/engine", …)` it.
|
||||
2. **The one exception is `@fusion/core` itself**, which can't statically import engine without a circular dependency (engine → core). Core uses dependency injection: `setCreateFnAgent` (in `packages/core/src/ai-engine-loader.ts`) is called by `packages/engine/src/index.ts` at module load. Don't add new dynamic `import("@fusion/engine")` calls in core — extend the loader instead.
|
||||
3. **Never reintroduce the `engineModule = "@fusion/engine"` + `await import(/* @vite-ignore */ engineModule)` trick.** If you find one, treat it as a bug.
|
||||
4. Test mocking still works with static imports — vitest's module-level `vi.mock("@fusion/engine", …)` hoists above the static import.
|
||||
|
||||
## Storage Model
|
||||
|
||||
Fusion uses a hybrid storage architecture: structured metadata lives in SQLite (`.fusion/fusion.db`) while large blob files (PROMPT.md, attachments) remain on the filesystem under `.fusion/tasks/{ID}/`. The database runs in WAL mode for concurrent access.
|
||||
|
||||
Reference in New Issue
Block a user