feat(FN-3508): resolve node-specific working dirs and align runtime surfaces
- Add canonical working-directory resolution with node mapping support in core and engine paths - Update runtime/multi-project documentation and include published-package changesets for the merged work - Migrate roadmap dashboard surface to the bundled plugin registry flow and adjust lazy-view integration/tests - Default non-ephemeral agents to active state and extend related agent/route/executor test coverage Fusion-Task-Id: FN-3508
This commit is contained in:
@@ -862,6 +862,43 @@ describe("CentralCore", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("resolves working directories strictly from exact project/node mappings", async () => {
|
||||
const projectPath = join(tempDir, "mapping-resolver-project");
|
||||
mkdirSync(projectPath);
|
||||
projectPaths.push(projectPath);
|
||||
|
||||
const project = await central.registerProject({
|
||||
name: "Mapping Resolver",
|
||||
path: projectPath,
|
||||
});
|
||||
const remoteNode = await central.registerNode({ name: "mapping-resolver-remote", type: "remote", url: "http://remote.example" });
|
||||
const otherNode = await central.registerNode({ name: "mapping-resolver-other", type: "remote", url: "http://other.example" });
|
||||
|
||||
const localNode = (await central.listNodes()).find((node) => node.type === "local");
|
||||
expect(localNode).toBeDefined();
|
||||
|
||||
await expect(central.resolveLocalProjectWorkingDirectory(project.id)).resolves.toBe(projectPath);
|
||||
|
||||
await central.upsertProjectNodePathMapping({
|
||||
projectId: project.id,
|
||||
nodeId: remoteNode.id,
|
||||
path: "/remote/project/root",
|
||||
});
|
||||
|
||||
await expect(central.resolveProjectWorkingDirectory(project.id, remoteNode.id)).resolves.toBe(
|
||||
"/remote/project/root",
|
||||
);
|
||||
await expect(central.resolveProjectWorkingDirectory("proj_missing", remoteNode.id)).rejects.toThrow(
|
||||
"Project not found: proj_missing",
|
||||
);
|
||||
await expect(central.resolveProjectWorkingDirectory(project.id, "node_missing")).rejects.toThrow(
|
||||
"Node not found: node_missing",
|
||||
);
|
||||
await expect(central.resolveProjectWorkingDirectory(project.id, otherNode.id)).rejects.toThrow(
|
||||
`Project/node path mapping not found for projectId=${project.id} nodeId=${otherNode.id}`,
|
||||
);
|
||||
});
|
||||
|
||||
it("should check local node health and emit node:health:changed", async () => {
|
||||
const node = await central.registerNode({ name: "local-health", type: "local" });
|
||||
|
||||
|
||||
@@ -1779,6 +1779,40 @@ export class CentralCore extends EventEmitter<CentralCoreEvents> {
|
||||
return row?.path;
|
||||
}
|
||||
|
||||
async resolveProjectWorkingDirectory(projectId: string, nodeId: string): Promise<string> {
|
||||
this.ensureInitialized();
|
||||
|
||||
const project = await this.getProject(projectId);
|
||||
if (!project) {
|
||||
throw new Error(`Project not found: ${projectId}`);
|
||||
}
|
||||
|
||||
const node = await this.getNode(nodeId);
|
||||
if (!node) {
|
||||
throw new Error(`Node not found: ${nodeId}`);
|
||||
}
|
||||
|
||||
const mappedPath = await this.getProjectNodePath(projectId, nodeId);
|
||||
if (!mappedPath) {
|
||||
throw new Error(
|
||||
`Project/node path mapping not found for projectId=${projectId} nodeId=${nodeId}`,
|
||||
);
|
||||
}
|
||||
|
||||
return mappedPath;
|
||||
}
|
||||
|
||||
async resolveLocalProjectWorkingDirectory(projectId: string): Promise<string> {
|
||||
this.ensureInitialized();
|
||||
|
||||
const localNode = await this.getLocalNode();
|
||||
if (!localNode) {
|
||||
throw new Error("Local node not found");
|
||||
}
|
||||
|
||||
return this.resolveProjectWorkingDirectory(projectId, localNode.id);
|
||||
}
|
||||
|
||||
async listProjectNodePathMappings(filters?: {
|
||||
projectId?: string;
|
||||
nodeId?: string;
|
||||
|
||||
Reference in New Issue
Block a user