feat(FN-3033): align agent asset directory naming and heartbeat path compat

The merge delivers three major features: a droid CLI path reconciliation extension that resolves workspace path mismatches for spawned agents, session-first quick chat with improved heartbeat prompts and a dramatically simplified QuickChatFAB component, and canonical agent asset directory naming wit

Fusion-Task-Id: FN-3033
This commit is contained in:
Fusion
2026-05-01 12:51:23 -07:00
committed by gsxdsm
parent 95cd107523
commit a34c819514
11 changed files with 392 additions and 36 deletions

View File

@@ -3,6 +3,11 @@ import { mkdtemp, rm, mkdir, writeFile, readFile, access } from "node:fs/promise
import { join } from "node:path";
import { tmpdir } from "node:os";
import { AgentStore } from "../agent-store.js";
import {
getCanonicalAgentInstructionsBundleDirName,
getLegacyAgentInstructionsBundleDirName,
getSafeAgentAssetIdSegment,
} from "../types.js";
describe("AgentStore — instructions bundle", () => {
let testDir: string;
@@ -62,7 +67,9 @@ describe("AgentStore — instructions bundle", () => {
it("getInstructionsDir returns the managed bundle directory path", async () => {
const agent = await store.createAgent({ name: "dir-agent", role: "executor" });
createdAgentIds.push(agent.id);
expect(store.getInstructionsDir(agent.id)).toBe(join(testDir, "agents", `${agent.id}-instructions`));
expect(store.getInstructionsDir(agent.id)).toBe(
join(testDir, "agents", getCanonicalAgentInstructionsBundleDirName(agent.name, agent.id)),
);
});
it("listBundleFiles returns empty for missing directory and sorted .md files only", async () => {
@@ -273,4 +280,27 @@ describe("AgentStore — instructions bundle", () => {
expect(migrated.instructionsText).toBeUndefined();
expect(migrated.instructionsPath).toBeUndefined();
});
it("uses existing legacy id-only instructions directory when present", async () => {
const agent = await store.createAgent({ name: "Legacy Bundle", role: "executor" });
createdAgentIds.push(agent.id);
const legacyDir = join(testDir, "agents", getLegacyAgentInstructionsBundleDirName(agent.id));
await mkdir(legacyDir, { recursive: true });
await writeFile(join(legacyDir, "AGENTS.md"), "legacy content", "utf-8");
await expect(store.readBundleFile(agent.id, "AGENTS.md")).resolves.toBe("legacy content");
});
it("uses previously-created display-name instructions directory for same id", async () => {
const agent = await store.createAgent({ name: "Current Name", role: "executor" });
createdAgentIds.push(agent.id);
const priorDirName = `previous-name-${getSafeAgentAssetIdSegment(agent.id)}-instructions`;
const priorDir = join(testDir, "agents", priorDirName);
await mkdir(priorDir, { recursive: true });
await writeFile(join(priorDir, "AGENTS.md"), "existing display path", "utf-8");
await expect(store.readBundleFile(agent.id, "AGENTS.md")).resolves.toBe("existing display path");
});
});

View File

@@ -18,7 +18,12 @@ import { join } from "node:path";
import { mkdtempSync, existsSync, mkdirSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { createHash } from "node:crypto";
import { CheckoutConflictError, type AgentCapability, type AgentState } from "../types.js";
import {
CheckoutConflictError,
getCanonicalAgentAssetDirectoryName,
type AgentCapability,
type AgentState,
} from "../types.js";
function makeTmpDir(): string {
return mkdtempSync(join(tmpdir(), "fn-agent-store-test-"));
@@ -150,6 +155,27 @@ describe("AgentStore", () => {
expect(new Date(agent.updatedAt).getTime()).not.toBeNaN();
});
it("defaults heartbeat procedure path to canonical display-name directory", async () => {
const agent = await store.createAgent({
name: "CEO",
role: "executor",
});
const expectedDir = getCanonicalAgentAssetDirectoryName(agent.name, agent.id);
expect(agent.heartbeatProcedurePath).toBe(`.fusion/agents/${expectedDir}/HEARTBEAT.md`);
});
it("falls back to id-based segment when display-name slug is empty", async () => {
const agent = await store.createAgent({
name: "!!!",
role: "executor",
});
const expectedDir = getCanonicalAgentAssetDirectoryName(agent.name, agent.id);
expect(expectedDir).toContain("agent-");
expect(agent.heartbeatProcedurePath).toBe(`.fusion/agents/${expectedDir}/HEARTBEAT.md`);
});
it("preserves custom metadata", async () => {
const agent = await store.createAgent({
name: "With Meta",