feat(FN-2943): merge fusion/fn-2943
- **Active Agents panel overhaul**: Hoisted above the main agent list with a new `ActiveAgentsPanel.css` stylesheet; shows next heartbeat ETA and prevents cards from getting stuck in "Connecting..." state - **Node routing policy**: Scheduler now enforces unavailable-node scheduling rules and routes tasks to eligible nodes more reliably (`packages/engine/src/scheduler.ts`, `node-routing-policy.ts`) - **Task card merge timer**: Keeps the card timer live while a task is actively merging rather than resetting on merge start - **Settings modal scroll**: Scrolls to top when auth actions succeed, with regression tests added - **Task steps reset**: Resets steps when reopening a task that returns to todo or triage (FN-2966) - **Core store additions**: New `packages/core/src/store.ts` test coverage for task state mutations - **Dashboard SSE bus**: New `sse-bus.ts` for event bus communication in the dashboard - **Version bumps**: `v0.9.0` release across all packages; `cli-alias`, `dashboard`, `engine`, `plugin-sdk`, and plugin changelogs updated - **Changeset cleanup**: Removed 20 stale changeset files consumed by the v0.9.0 release; added two new ones for Active Agents panel fixes Commits merged: - test(FN-2943): complete Step 3 — add auth scroll-to-top regressions - feat(FN-2943): complete Step 2 — scroll on successful auth actions - feat(FN-2943): complete Step 1 — add settings content scroll ref - feat(FN-2951): enforce unavailable-node scheduling and improve active agents panel - chore: add changeset for Active Agents panel hoist + heartbeat ETA - feat(dashboard): hoist Active Agents panel above main list, show next heartbeat - fix(dashboard): add top margin between agent list and Active Agents panel - fix(dashboard): unstick Active Agents panel cards from "Connecting..." - chore(release): v0.9.0 - chore: add changeset for live merge timer on task card - fix(dashboard): keep card timer live while task is actively merging - feat(FN-2966): reset task steps when reopening to todo or triage Files changed: .changeset/active-agents-live-status-and-logs.md | 13 - .changeset/active-agents-no-stuck-connecting.md | 13 + .changeset/active-agents-panel-hoist-heartbeat.md | 13 + .changeset/dashboard-merge-base-diff.md | 13 - .changeset/dashboard-task-changed-files-only.md | 13 - .changeset/engine-concurrency-races.md | 13 - .changeset/fix-agent-heartbeat-scheduling.md | 5 - .changeset/memory-dream-session-state.md | 13 - .changeset/merger-ai-summarized-commits.md | 13 - .changeset/multi-node-routing.md | 21 - .changeset/pluggable-notification-providers.md | 4 - .changeset/pr-mode-push-branch-and-cleanup.md | 16 - .../recover-mergeable-respects-merge-settings.md | 13 - .changeset/remote-tailscale-qr-tui.md | 7 - .changeset/require-pr-approval-setting.md | 17 - .changeset/respec-from-in-review.md | 13 - .changeset/restore-task-card-fallbacks.md | 13 - .changeset/tui-log-copy-feedback.md | 13 - .changeset/tui-wide-stats-stack.md | 13 - CHANGELOG.md | 449 +++++++++++++++++++++ package.json | 2 +- packages/cli-alias/CHANGELOG.md | 60 +++ packages/cli-alias/package.json | 2 +- packages/cli/CHANGELOG.md | 44 ++ packages/cli/package.json | 2 +- packages/core/CHANGELOG.md | 41 ++ packages/core/package.json | 2 +- packages/core/src/__tests__/store.test.ts | 127 ++++++ packages/core/src/store.ts | 35 +- packages/dashboard/CHANGELOG.md | 63 +++ .../dashboard/app/components/ActiveAgentsPanel.css | 29 ++ .../dashboard/app/components/ActiveAgentsPanel.tsx | 35 +- packages/dashboard/app/components/AgentsView.tsx | 5 +- .../dashboard/app/components/SettingsModal.tsx | 13 +- packages/dashboard/app/components/TaskCard.tsx | 30 +- .../__tests__/ActiveAgentsPanel.test.tsx | 42 ++ .../app/components/__tests__/AgentsView.test.tsx | 11 +- .../components/__tests__/SettingsModal.test.tsx | 68 ++++ .../app/components/__tests__/TaskCard.test.tsx | 36 ++ packages/dashboard/app/sse-bus.ts | 13 + packages/dashboard/package.json | 2 +- packages/desktop/CHANGELOG.md | 41 ++ packages/desktop/package.json | 2 +- packages/engine/CHANGELOG.md | 59 +++ packages/engine/package.json | 2 +- .../src/__tests__/node-routing-policy.test.ts | 102 +++-- .../src/__tests__/scheduler-node-routing.test.ts | 43 +- packages/engine/src/index.ts | 1 + packages/engine/src/node-routing-policy.ts | 41 +- packages/engine/src/scheduler.ts | 48 +-- packages/mobile/CHANGELOG.md | 41 ++ packages/mobile/package.json | 2 +- packages/pi-claude-cli/CHANGELOG.md | 41 ++ packages/pi-claude-cli/package.json | 2 +- packages/plugin-sdk/CHANGELOG.md | 57 +++ packages/plugin-sdk/package.json | 2 +- .../examples/fusion-plugin-auto-label/CHANGELOG.md | 21 + .../examples/fusion-plugin-auto-label/package.json | 2 +- .../examples/fusion-plugin-ci-status/CHANGELOG.md | 21 + .../examples/fusion-plugin-ci-status/package.json | 2 +- .../fusion-plugin-notification/CHANGELOG.md | 21 + .../fusion-plugin-notification/package.json | 2 +- .../fusion-plugin-settings-demo/CHANGELOG.md | 21 + .../fusion-plugin-settings-demo/package.json | 2 +- plugins/fusion-plugin-hermes-runtime/CHANGELOG.md | 21 + plugins/fusion-plugin-hermes-runtime/package.json | 2 +- .../fusion-plugin-openclaw-runtime/CHANGELOG.md | 21 + .../fusion-plugin-openclaw-runtime/package.json | 2 +- .../fusion-plugin-paperclip-runtime/CHANGELOG.md | 21 + .../fusion-plugin-paperclip-runtime/package.json | 2 +- 70 files changed, 1617 insertions(+), 378 deletions(-) Fusion-Task-Id: FN-2943
This commit is contained in:
@@ -251,6 +251,62 @@ describe("resolveAgentPrompt", () => {
|
||||
const result = resolveAgentPrompt("triage", config);
|
||||
expect(result).toContain("task_document_write");
|
||||
});
|
||||
|
||||
it("default role prompts include explicit heartbeat run guidance", () => {
|
||||
expect(resolveAgentPrompt("executor")).toContain("## Heartbeat Run Behavior");
|
||||
expect(resolveAgentPrompt("triage")).toContain("## Heartbeat Run Behavior");
|
||||
expect(resolveAgentPrompt("reviewer")).toContain("## Heartbeat Run Behavior");
|
||||
expect(resolveAgentPrompt("merger")).toContain("## Heartbeat Run Behavior");
|
||||
});
|
||||
|
||||
it("executor heartbeat guidance covers no-task engineering work", () => {
|
||||
const result = resolveAgentPrompt("executor");
|
||||
expect(result).toContain("execute your standing instructions");
|
||||
expect(result).toContain("blocked or failing engineering work");
|
||||
});
|
||||
|
||||
it("reviewer heartbeat guidance is findings-focused and customized per variant", () => {
|
||||
const defaultReviewer = resolveAgentPrompt("reviewer");
|
||||
expect(defaultReviewer).toContain("Look for work waiting on review");
|
||||
|
||||
const strictConfig: AgentPromptsConfig = {
|
||||
roleAssignments: {
|
||||
reviewer: "strict-reviewer",
|
||||
},
|
||||
};
|
||||
const strictReviewer = resolveAgentPrompt("reviewer", strictConfig);
|
||||
expect(strictReviewer).toContain("worst-case failure modes first");
|
||||
expect(strictReviewer).toContain("under-reviewed");
|
||||
});
|
||||
|
||||
it("triage heartbeat guidance is customized for standard and concise templates", () => {
|
||||
const defaultTriage = resolveAgentPrompt("triage");
|
||||
expect(defaultTriage).toContain("Patrol for vague requests");
|
||||
|
||||
const conciseConfig: AgentPromptsConfig = {
|
||||
roleAssignments: {
|
||||
triage: "concise-triage",
|
||||
},
|
||||
};
|
||||
const conciseTriage = resolveAgentPrompt("triage", conciseConfig);
|
||||
expect(conciseTriage).toContain("Keep heartbeat output lean and useful");
|
||||
expect(conciseTriage).toContain("minimum complete PROMPT.md");
|
||||
});
|
||||
|
||||
it("merger and senior-engineer heartbeat guidance is role-specific", () => {
|
||||
const merger = resolveAgentPrompt("merger");
|
||||
expect(merger).toContain("keep merge-ready work from stalling");
|
||||
expect(merger).toContain("in-review and merge-ready queue");
|
||||
|
||||
const seniorConfig: AgentPromptsConfig = {
|
||||
roleAssignments: {
|
||||
executor: "senior-engineer",
|
||||
},
|
||||
};
|
||||
const senior = resolveAgentPrompt("executor", seniorConfig);
|
||||
expect(senior).toContain("autonomous senior-engineering pass");
|
||||
expect(senior).toContain("architectural drift");
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@@ -24,6 +24,7 @@ import { runCommandAsync } from "../run-command.js";
|
||||
const mockedRunCommandAsync = vi.mocked(runCommandAsync);
|
||||
|
||||
import { TaskStore, TaskHasDependentsError } from "../store.js";
|
||||
import { AgentStore } from "../agent-store.js";
|
||||
import { appendFile, readFile, writeFile, mkdir, rm, readdir, unlink } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import { mkdtempSync, existsSync } from "node:fs";
|
||||
@@ -4626,6 +4627,28 @@ Task with acceptance criteria
|
||||
expect(after.count).toBe(0);
|
||||
});
|
||||
|
||||
it("deleteTask clears linked agent task assignments", async () => {
|
||||
store.close();
|
||||
store = new TaskStore(rootDir, globalDir);
|
||||
await store.init();
|
||||
|
||||
const agentStore = new AgentStore({ rootDir: store.getFusionDir() });
|
||||
await agentStore.init();
|
||||
|
||||
try {
|
||||
const task = await store.createTask({ description: "Delete me" });
|
||||
const agent = await agentStore.createAgent({ name: "Delete watcher", role: "executor" });
|
||||
await agentStore.assignTask(agent.id, task.id);
|
||||
|
||||
await store.deleteTask(task.id);
|
||||
|
||||
const updatedAgent = await agentStore.getAgent(agent.id);
|
||||
expect(updatedAgent?.taskId).toBeUndefined();
|
||||
} finally {
|
||||
agentStore.close();
|
||||
}
|
||||
});
|
||||
|
||||
it("importLegacyAgentLogs imports JSONL entries from existing agent.log files", async () => {
|
||||
const task = await createTestTask();
|
||||
const dir = join(rootDir, ".fusion", "tasks", task.id);
|
||||
@@ -7727,6 +7750,33 @@ Task with acceptance criteria
|
||||
const dir = join(rootDir, ".fusion", "tasks", task.id);
|
||||
expect(existsSync(dir)).toBe(false);
|
||||
});
|
||||
|
||||
it("archiveTask clears stale linked agent assignments", async () => {
|
||||
store.close();
|
||||
store = new TaskStore(rootDir, globalDir);
|
||||
await store.init();
|
||||
|
||||
const agentStore = new AgentStore({ rootDir: store.getFusionDir() });
|
||||
await agentStore.init();
|
||||
|
||||
try {
|
||||
const task = await store.createTask({ description: "Archive clears links" });
|
||||
await store.moveTask(task.id, "todo");
|
||||
await store.moveTask(task.id, "in-progress");
|
||||
await store.moveTask(task.id, "in-review");
|
||||
await store.moveTask(task.id, "done");
|
||||
|
||||
const agent = await agentStore.createAgent({ name: "Archive watcher", role: "executor" });
|
||||
await agentStore.assignTask(agent.id, task.id);
|
||||
|
||||
await store.archiveTask(task.id, false);
|
||||
|
||||
const updatedAgent = await agentStore.getAgent(agent.id);
|
||||
expect(updatedAgent?.taskId).toBeUndefined();
|
||||
} finally {
|
||||
agentStore.close();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("archive log persistence", () => {
|
||||
|
||||
Reference in New Issue
Block a user