feat(HAI-114): remove --engine CLI flag and make engine always-on

- Remove --engine flag from dashboard command CLI options
- Make AI engine start automatically with the dashboard
- Update dashboard tests to reflect always-on engine behavior
- Update README.md to remove --engine references and examples
This commit is contained in:
Dustin Byrne
2026-03-26 21:50:37 -04:00
parent 78f799dc8f
commit 323ce09c1b
4 changed files with 16 additions and 34 deletions

View File

@@ -29,12 +29,9 @@ Tasks with dependencies are processed sequentially. Independent tasks run in par
# Install dependencies
pnpm install
# Start the board (UI only)
# Start the board (with AI engine)
pnpm dev dashboard
# Start with AI engine (auto-specify + auto-execute)
pnpm dev dashboard -- --engine
# Create a task via CLI
pnpm dev task create "Fix the login redirect bug"
@@ -121,9 +118,9 @@ Every API response includes standard rate limit headers:
When a client exceeds the limit, the API returns `429 Too Many Requests`.
### AI Engine (`--engine`)
### AI Engine
When enabled, three components run:
The AI engine starts automatically with the dashboard. Three components run:
- **TriageProcessor** — Watches triage column. Spawns a pi agent session that reads the project, understands context, and writes a full PROMPT.md specification. Moves task to todo.
@@ -141,8 +138,7 @@ Each pi agent session gets:
```bash
pnpm install
pnpm dev dashboard # Board only
pnpm dev dashboard -- --engine # Board + AI engine
pnpm dev dashboard # Board + AI engine
pnpm dev task list # CLI commands
```

View File

@@ -59,7 +59,6 @@ Usage:
Options:
--port, -p <port> Dashboard port (default: 4040)
--engine Enable AI engine (auto-specify + execute tasks)
--attach <file> Attach file(s) on task create (repeatable)
--help, -h Show this help
@@ -87,9 +86,8 @@ async function main() {
const portIdxShort = args.indexOf("-p");
const pi = portIdx !== -1 ? portIdx : portIdxShort;
const port = pi !== -1 ? parseInt(args[pi + 1], 10) : 4040;
const engine = args.includes("--engine");
const open = !args.includes("--no-open");
await runDashboard(port, { engine, open });
await runDashboard(port, { open });
break;
}

View File

@@ -97,7 +97,7 @@ describe("runDashboard — WorktreePool wiring", () => {
});
it("passes a WorktreePool instance to TaskExecutor", async () => {
await runDashboard(0, { engine: true, open: false });
await runDashboard(0, { open: false });
expect(capturedExecutorOpts).toBeDefined();
expect(capturedExecutorOpts!.pool).toBeInstanceOf(WorktreePool);
@@ -107,7 +107,7 @@ describe("runDashboard — WorktreePool wiring", () => {
const { aiMergeTask } = await import("@hai/engine");
const { createServer } = await import("@hai/dashboard");
await runDashboard(0, { engine: false, open: false });
await runDashboard(0, { open: false });
// rawMerge is exposed as the onMerge callback wired into createServer.
const createServerCall = (createServer as ReturnType<typeof vi.fn>).mock.calls[0];
@@ -125,7 +125,7 @@ describe("runDashboard — WorktreePool wiring", () => {
const { aiMergeTask } = await import("@hai/engine");
const { createServer } = await import("@hai/dashboard");
await runDashboard(0, { engine: true, open: false });
await runDashboard(0, { open: false });
// Trigger merger via onMerge
const createServerCall = (createServer as ReturnType<typeof vi.fn>).mock.calls[0];
@@ -170,7 +170,7 @@ describe("runDashboard — auto-merge pause exclusion", () => {
pollIntervalMs: 60_000,
});
await runDashboard(0, { engine: true, open: false });
await runDashboard(0, { open: false });
const { aiMergeTask } = await import("@hai/engine");
@@ -205,7 +205,7 @@ describe("runDashboard — auto-merge pause exclusion", () => {
Promise.resolve({ merged: true }),
);
await runDashboard(0, { engine: true, open: false });
await runDashboard(0, { open: false });
// Give async handlers time to process
await new Promise((r) => setTimeout(r, 50));

View File

@@ -12,7 +12,7 @@ function openBrowser(url: string): void {
exec(cmd, () => {});
}
export async function runDashboard(port: number, opts: { engine?: boolean; open?: boolean } = {}) {
export async function runDashboard(port: number, opts: { open?: boolean } = {}) {
const cwd = process.cwd();
const store = new TaskStore(cwd);
await store.init();
@@ -143,16 +143,8 @@ export async function runDashboard(port: number, opts: { engine?: boolean; open?
// Start the web server with AI merge, auth, and model registry wired in
const app = createServer(store, { onMerge, authStorage, modelRegistry });
// Clean shutdown for file watcher when engine is not active
if (!opts.engine) {
process.on("SIGINT", () => {
store.stopWatching();
process.exit(0);
});
}
// Optionally start the AI engine
if (opts.engine) {
// Start the AI engine
{
const triage = new TriageProcessor(store, cwd, {
semaphore,
onSpecifyStart: (t) => console.log(`[engine] Specifying ${t.id}...`),
@@ -242,13 +234,9 @@ export async function runDashboard(port: number, opts: { engine?: boolean; open?
console.log();
console.log(` Tasks stored in .hai/tasks/`);
console.log(` Merge: AI-assisted (conflict resolution + commit messages)`);
if (opts.engine) {
console.log(` AI engine: ✓ active`);
console.log(`triage: auto-specifying tasks`);
console.log(` • scheduler: dependency-aware execution`);
} else {
console.log(` AI engine: off (use --engine to enable)`);
}
console.log(` AI engine: ✓ active`);
console.log(` • triage: auto-specifying tasks`);
console.log(`scheduler: dependency-aware execution`);
console.log(` File watcher: ✓ active`);
console.log(` Press Ctrl+C to stop`);
console.log();