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:
@@ -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");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user