@runfusion/fusion 0.15.0 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -19
- package/dist/bin.js +7005 -2992
- package/dist/client/assets/AgentDetailView-DGqT1oDt.js +18 -0
- package/dist/client/assets/AgentDetailView-yu8Xltqk.css +1 -0
- package/dist/client/assets/AgentsView-BmemrfrO.js +517 -0
- package/dist/client/assets/AgentsView-Bs03ptrd.css +1 -0
- package/dist/client/assets/ChatView-CZQUBFlV.js +1 -0
- package/dist/client/assets/{DevServerView-CV_PpbnZ.js → DevServerView-C3Q0XqDA.js} +1 -1
- package/dist/client/assets/{DirectoryPicker-DPfkGnj5.js → DirectoryPicker-BZWVA9ND.js} +1 -1
- package/dist/client/assets/{DocumentsView-CESb6RI7.js → DocumentsView-DO48ivSq.js} +1 -1
- package/dist/client/assets/InsightsView-CAngTfMf.js +11 -0
- package/dist/client/assets/MemoryView-B3rNcAOW.js +2 -0
- package/dist/client/assets/NodesView-BnV1LWa8.js +14 -0
- package/dist/client/assets/NodesView-DuAXX_0j.css +1 -0
- package/dist/client/assets/{PiExtensionsManager-C4fTzemh.js → PiExtensionsManager-C3_Lw4sa.js} +3 -3
- package/dist/client/assets/{PluginManager-C2-dExUL.js → PluginManager-Vv3nzrJ1.js} +1 -1
- package/dist/client/assets/ResearchView-BzCcDAS4.css +1 -0
- package/dist/client/assets/ResearchView-Dfdsuc21.js +1 -0
- package/dist/client/assets/RoadmapsView-BiIpE-b8.js +6 -0
- package/dist/client/assets/RoadmapsView-DdGlfuu-.css +1 -0
- package/dist/client/assets/SettingsModal-BN00HYJ2.js +31 -0
- package/dist/client/assets/{SettingsModal-BGnSAeqa.js → SettingsModal-CK4w8Ztb.js} +1 -1
- package/dist/client/assets/SettingsModal-Dq4a5KSX.css +1 -0
- package/dist/client/assets/{SetupWizardModal-C_d9clJp.js → SetupWizardModal-Dw6N4UvY.js} +1 -1
- package/dist/client/assets/{SkillsView-C096TB7i.js → SkillsView-C1196wgA.js} +1 -1
- package/dist/client/assets/{folder-open-CKivQd8c.js → folder-open-WVtgE4k3.js} +1 -1
- package/dist/client/assets/index-BIJgrHEn.css +1 -0
- package/dist/client/assets/index-Bv0TGzDH.js +682 -0
- package/dist/client/assets/{star-damu_EYz.js → star-MSImEC8V.js} +1 -1
- package/dist/client/assets/{upload-uH6CHlEw.js → upload-Dmvy3xXd.js} +1 -1
- package/dist/client/assets/{users-CUySbfji.js → users-CncYvHNf.js} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/client/version.json +1 -1
- package/dist/extension.js +6220 -3829
- package/dist/pi-claude-cli/package.json +1 -1
- package/dist/pi-claude-cli/src/__tests__/process-manager.test.ts +11 -0
- package/dist/pi-claude-cli/src/__tests__/provider.test.ts +25 -0
- package/dist/plugins/fusion-plugin-dependency-graph/manifest.json +16 -0
- package/dist/plugins/fusion-plugin-dependency-graph/package.json +34 -0
- package/dist/plugins/fusion-plugin-dependency-graph/src/DependencyGraphView.css +132 -0
- package/dist/plugins/fusion-plugin-dependency-graph/src/DependencyGraphView.tsx +428 -0
- package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/DependencyGraphView.test.tsx +261 -0
- package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/storage.test.ts +31 -0
- package/dist/plugins/fusion-plugin-dependency-graph/src/index.ts +25 -0
- package/dist/plugins/fusion-plugin-dependency-graph/src/storage.ts +23 -0
- package/package.json +8 -4
- package/skill/fusion/SKILL.md +5 -5
- package/skill/fusion/references/engine-tools.md +4 -4
- package/skill/fusion/references/extension-tools.md +3 -3
- package/skill/fusion/references/fusion-capabilities.md +1 -1
- package/skill/fusion/references/skill-patterns.md +1 -1
- package/skill/fusion/workflows/dashboard-cli.md +3 -3
- package/skill/fusion/workflows/task-management.md +1 -1
- package/dist/client/assets/AgentDetailView-B1zViykq.js +0 -18
- package/dist/client/assets/AgentDetailView-B5tq9ius.css +0 -1
- package/dist/client/assets/AgentsView-Bl9JH5C8.js +0 -522
- package/dist/client/assets/AgentsView-V5GhlBYu.css +0 -1
- package/dist/client/assets/ChatView-liNErE53.js +0 -1
- package/dist/client/assets/InsightsView-BKhvyEyQ.js +0 -11
- package/dist/client/assets/MemoryView-DB-l2miV.js +0 -2
- package/dist/client/assets/NodesView-DCoS6iYh.css +0 -1
- package/dist/client/assets/NodesView-DgTXO8mm.js +0 -14
- package/dist/client/assets/ResearchView-BzRdUzNq.css +0 -1
- package/dist/client/assets/ResearchView-CkVwRDVA.js +0 -1
- package/dist/client/assets/RoadmapsView-BOYnyMCh.css +0 -1
- package/dist/client/assets/RoadmapsView-Cu85_XrQ.js +0 -6
- package/dist/client/assets/SettingsModal-C0DokcId.js +0 -31
- package/dist/client/assets/SettingsModal-DcGFm6NR.css +0 -1
- package/dist/client/assets/SkillMultiselect-DDHJnrkn.css +0 -1
- package/dist/client/assets/SkillMultiselect-DwGWYZi6.js +0 -1
- package/dist/client/assets/TodoView-CUiAt2mR.js +0 -6
- package/dist/client/assets/TodoView-SeO9o7km.css +0 -1
- package/dist/client/assets/index-B4StE1qN.js +0 -662
- package/dist/client/assets/index-DYJk0WDc.css +0 -1
- package/dist/client/assets/list-checks-B3oufblU.js +0 -6
package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/DependencyGraphView.test.tsx
ADDED
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import { describe, expect, it, vi, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import { render, fireEvent, screen, act, cleanup } from "@testing-library/react";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { DependencyGraphView } from "../DependencyGraphView";
|
|
5
|
+
import type { DependencyGraphHostContext, PluginDashboardViewComponentProps } from "../DependencyGraphView";
|
|
6
|
+
import type { Task } from "@fusion/core";
|
|
7
|
+
|
|
8
|
+
// Mock storage to avoid localStorage dependency
|
|
9
|
+
vi.mock("../storage", () => ({
|
|
10
|
+
loadPositions: () => ({}),
|
|
11
|
+
savePositions: () => {},
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
// Helper to create a minimal Task
|
|
15
|
+
function createTask(overrides: Partial<Task> & { id: string; column: Task["column"] }): Task {
|
|
16
|
+
return {
|
|
17
|
+
description: overrides.description ?? `Task ${overrides.id}`,
|
|
18
|
+
column: overrides.column,
|
|
19
|
+
dependencies: overrides.dependencies ?? [],
|
|
20
|
+
steps: overrides.steps ?? [],
|
|
21
|
+
currentStep: overrides.currentStep ?? 0,
|
|
22
|
+
log: overrides.log ?? [],
|
|
23
|
+
...overrides,
|
|
24
|
+
} as Task;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function createMockContext(tasks: Task[] = []): DependencyGraphHostContext {
|
|
28
|
+
return {
|
|
29
|
+
projectId: "test-project",
|
|
30
|
+
tasks,
|
|
31
|
+
openTaskDetail: vi.fn(),
|
|
32
|
+
renderTaskCard: (task: Task) => React.createElement("div", { "data-testid": "task-card" }, task.id),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function renderView(context?: DependencyGraphHostContext) {
|
|
37
|
+
const props: PluginDashboardViewComponentProps = {
|
|
38
|
+
context: context ?? createMockContext(),
|
|
39
|
+
};
|
|
40
|
+
return render(React.createElement(DependencyGraphView, props));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
describe("DependencyGraphView", () => {
|
|
44
|
+
beforeEach(() => {
|
|
45
|
+
// Mock getComputedStyle for rem calculations
|
|
46
|
+
vi.spyOn(window, "getComputedStyle").mockImplementation((() => {
|
|
47
|
+
return { fontSize: "16px" } as CSSStyleDeclaration;
|
|
48
|
+
}) as typeof window.getComputedStyle);
|
|
49
|
+
|
|
50
|
+
// Mock matchMedia to default to desktop
|
|
51
|
+
window.matchMedia = vi.fn().mockImplementation((query: string) => ({
|
|
52
|
+
matches: false,
|
|
53
|
+
media: query,
|
|
54
|
+
onchange: null,
|
|
55
|
+
addListener: vi.fn(),
|
|
56
|
+
removeListener: vi.fn(),
|
|
57
|
+
addEventListener: vi.fn(),
|
|
58
|
+
removeEventListener: vi.fn(),
|
|
59
|
+
dispatchEvent: vi.fn(),
|
|
60
|
+
}));
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
afterEach(() => {
|
|
64
|
+
cleanup();
|
|
65
|
+
vi.restoreAllMocks();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("renders empty state when no active-column tasks provided", () => {
|
|
69
|
+
const { container } = renderView(createMockContext([]));
|
|
70
|
+
const empty = container.querySelector(".dependency-graph-empty");
|
|
71
|
+
expect(empty).toBeTruthy();
|
|
72
|
+
expect(empty?.textContent).toContain("No tasks to display");
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("renders nodes for active-column tasks", () => {
|
|
76
|
+
const tasks = [
|
|
77
|
+
createTask({ id: "FN-001", column: "todo" }),
|
|
78
|
+
createTask({ id: "FN-002", column: "in-progress" }),
|
|
79
|
+
];
|
|
80
|
+
const { container } = renderView(createMockContext(tasks));
|
|
81
|
+
const nodes = container.querySelectorAll(".dependency-graph-node");
|
|
82
|
+
expect(nodes.length).toBe(2);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("filters out tasks not in active columns", () => {
|
|
86
|
+
const tasks = [
|
|
87
|
+
createTask({ id: "FN-001", column: "todo" }),
|
|
88
|
+
createTask({ id: "FN-002", column: "done" }),
|
|
89
|
+
createTask({ id: "FN-003", column: "archived" }),
|
|
90
|
+
];
|
|
91
|
+
const { container } = renderView(createMockContext(tasks));
|
|
92
|
+
const nodes = container.querySelectorAll(".dependency-graph-node");
|
|
93
|
+
expect(nodes.length).toBe(1);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("renders edges for tasks with dependencies in the active set", () => {
|
|
97
|
+
const tasks = [
|
|
98
|
+
createTask({ id: "FN-001", column: "todo" }),
|
|
99
|
+
createTask({ id: "FN-002", column: "todo", dependencies: ["FN-001"] }),
|
|
100
|
+
];
|
|
101
|
+
const { container } = renderView(createMockContext(tasks));
|
|
102
|
+
const edges = container.querySelectorAll(".dependency-graph-edge");
|
|
103
|
+
expect(edges.length).toBe(1);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("does not render edges for dependencies not in the active set", () => {
|
|
107
|
+
const tasks = [
|
|
108
|
+
createTask({ id: "FN-002", column: "todo", dependencies: ["FN-999"] }),
|
|
109
|
+
];
|
|
110
|
+
const { container } = renderView(createMockContext(tasks));
|
|
111
|
+
const edges = container.querySelectorAll(".dependency-graph-edge");
|
|
112
|
+
expect(edges.length).toBe(0);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it("zoom-in button increases scale", () => {
|
|
116
|
+
const tasks = [createTask({ id: "FN-001", column: "todo" })];
|
|
117
|
+
const { container, unmount } = renderView(createMockContext(tasks));
|
|
118
|
+
const zoomInBtn = Array.from(container.querySelectorAll("button")).find((b) => b.textContent === "Zoom In")!;
|
|
119
|
+
fireEvent.click(zoomInBtn);
|
|
120
|
+
const scene = container.querySelector(".dependency-graph-scene") as HTMLElement;
|
|
121
|
+
expect(scene).toBeTruthy();
|
|
122
|
+
const transform = scene.style.transform;
|
|
123
|
+
expect(transform).toMatch(/scale\([1-9]/);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it("zoom-out button decreases scale", () => {
|
|
127
|
+
const tasks = [createTask({ id: "FN-001", column: "todo" })];
|
|
128
|
+
const { container, unmount } = renderView(createMockContext(tasks));
|
|
129
|
+
const zoomOutBtn = Array.from(container.querySelectorAll("button")).find((b) => b.textContent === "Zoom Out")!;
|
|
130
|
+
fireEvent.click(zoomOutBtn);
|
|
131
|
+
const scene = container.querySelector(".dependency-graph-scene") as HTMLElement;
|
|
132
|
+
expect(scene).toBeTruthy();
|
|
133
|
+
const transform = scene.style.transform;
|
|
134
|
+
expect(transform).toMatch(/scale\(0\./);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it("fit-to-graph button produces a valid scale", () => {
|
|
138
|
+
const tasks = [createTask({ id: "FN-001", column: "todo" })];
|
|
139
|
+
const { container, unmount } = renderView(createMockContext(tasks));
|
|
140
|
+
|
|
141
|
+
// Mock canvas dimensions for fitToGraph
|
|
142
|
+
const canvas = container.querySelector(".dependency-graph-canvas") as HTMLElement;
|
|
143
|
+
if (canvas) {
|
|
144
|
+
Object.defineProperty(canvas, "clientWidth", { value: 800, configurable: true });
|
|
145
|
+
Object.defineProperty(canvas, "clientHeight", { value: 600, configurable: true });
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const fitBtn = Array.from(container.querySelectorAll("button")).find((b) => b.textContent === "Fit")!;
|
|
149
|
+
fireEvent.click(fitBtn);
|
|
150
|
+
|
|
151
|
+
const scene = container.querySelector(".dependency-graph-scene") as HTMLElement;
|
|
152
|
+
expect(scene).toBeTruthy();
|
|
153
|
+
const transform = scene.style.transform;
|
|
154
|
+
const scaleMatch = transform.match(/scale\(([\d.]+)\)/);
|
|
155
|
+
expect(scaleMatch).toBeTruthy();
|
|
156
|
+
const scaleValue = parseFloat(scaleMatch![1]);
|
|
157
|
+
expect(scaleValue).toBeGreaterThanOrEqual(0.4);
|
|
158
|
+
expect(scaleValue).toBeLessThanOrEqual(2);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it("wheel event zooms the graph", () => {
|
|
162
|
+
const tasks = [createTask({ id: "FN-001", column: "todo" })];
|
|
163
|
+
const { container } = renderView(createMockContext(tasks));
|
|
164
|
+
const canvas = container.querySelector(".dependency-graph-canvas") as HTMLElement;
|
|
165
|
+
|
|
166
|
+
expect(canvas).toBeTruthy();
|
|
167
|
+
|
|
168
|
+
// Mock getBoundingClientRect on the canvas
|
|
169
|
+
canvas.getBoundingClientRect = vi.fn().mockReturnValue({
|
|
170
|
+
left: 0, top: 0, right: 800, bottom: 600, width: 800, height: 600, x: 0, y: 0,
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
const initialScene = container.querySelector(".dependency-graph-scene") as HTMLElement;
|
|
174
|
+
const initialTransform = initialScene.style.transform;
|
|
175
|
+
|
|
176
|
+
// Negative deltaY = zoom in
|
|
177
|
+
fireEvent.wheel(canvas, { deltaY: -100, clientX: 400, clientY: 300 });
|
|
178
|
+
|
|
179
|
+
const updatedScene = container.querySelector(".dependency-graph-scene") as HTMLElement;
|
|
180
|
+
const updatedTransform = updatedScene.style.transform;
|
|
181
|
+
|
|
182
|
+
expect(updatedTransform).not.toBe(initialTransform);
|
|
183
|
+
const scaleMatch = updatedTransform.match(/scale\(([\d.]+)\)/);
|
|
184
|
+
expect(scaleMatch).toBeTruthy();
|
|
185
|
+
expect(parseFloat(scaleMatch![1])).toBeGreaterThan(1);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it("pinch gesture changes scale", () => {
|
|
189
|
+
const tasks = [createTask({ id: "FN-001", column: "todo" })];
|
|
190
|
+
const { container } = renderView(createMockContext(tasks));
|
|
191
|
+
const canvas = container.querySelector(".dependency-graph-canvas") as HTMLElement;
|
|
192
|
+
|
|
193
|
+
expect(canvas).toBeTruthy();
|
|
194
|
+
|
|
195
|
+
// First pointer down (finger 1)
|
|
196
|
+
fireEvent.pointerDown(canvas, {
|
|
197
|
+
pointerId: 1,
|
|
198
|
+
pointerType: "touch",
|
|
199
|
+
button: 0,
|
|
200
|
+
clientX: 200,
|
|
201
|
+
clientY: 300,
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
// Second pointer down (finger 2) — starts pinch
|
|
205
|
+
fireEvent.pointerDown(canvas, {
|
|
206
|
+
pointerId: 2,
|
|
207
|
+
pointerType: "touch",
|
|
208
|
+
button: 0,
|
|
209
|
+
clientX: 600,
|
|
210
|
+
clientY: 300,
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
// Move fingers apart (increasing distance)
|
|
214
|
+
fireEvent.pointerMove(canvas, {
|
|
215
|
+
pointerId: 1,
|
|
216
|
+
pointerType: "touch",
|
|
217
|
+
clientX: 100,
|
|
218
|
+
clientY: 300,
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
fireEvent.pointerMove(canvas, {
|
|
222
|
+
pointerId: 2,
|
|
223
|
+
pointerType: "touch",
|
|
224
|
+
clientX: 700,
|
|
225
|
+
clientY: 300,
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
const scene = container.querySelector(".dependency-graph-scene") as HTMLElement;
|
|
229
|
+
const transform = scene.style.transform;
|
|
230
|
+
const scaleMatch = transform.match(/scale\(([\d.]+)\)/);
|
|
231
|
+
expect(scaleMatch).toBeTruthy();
|
|
232
|
+
expect(parseFloat(scaleMatch![1])).toBeGreaterThan(1);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
it("scale is clamped between MIN_SCALE and MAX_SCALE", () => {
|
|
236
|
+
const tasks = [createTask({ id: "FN-001", column: "todo" })];
|
|
237
|
+
const { container, unmount } = renderView(createMockContext(tasks));
|
|
238
|
+
|
|
239
|
+
// Try to zoom in beyond MAX_SCALE (2.0)
|
|
240
|
+
const zoomInBtn = Array.from(container.querySelectorAll("button")).find((b) => b.textContent === "Zoom In")!;
|
|
241
|
+
for (let i = 0; i < 50; i++) {
|
|
242
|
+
fireEvent.click(zoomInBtn);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const scene = container.querySelector(".dependency-graph-scene") as HTMLElement;
|
|
246
|
+
let scaleMatch = scene.style.transform.match(/scale\(([\d.]+)\)/);
|
|
247
|
+
expect(scaleMatch).toBeTruthy();
|
|
248
|
+
expect(parseFloat(scaleMatch![1])).toBeLessThanOrEqual(2);
|
|
249
|
+
|
|
250
|
+
// Try to zoom out beyond MIN_SCALE (0.4)
|
|
251
|
+
const zoomOutBtn = Array.from(container.querySelectorAll("button")).find((b) => b.textContent === "Zoom Out")!;
|
|
252
|
+
for (let i = 0; i < 100; i++) {
|
|
253
|
+
fireEvent.click(zoomOutBtn);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const updatedScene = container.querySelector(".dependency-graph-scene") as HTMLElement;
|
|
257
|
+
scaleMatch = updatedScene.style.transform.match(/scale\(([\d.]+)\)/);
|
|
258
|
+
expect(scaleMatch).toBeTruthy();
|
|
259
|
+
expect(parseFloat(scaleMatch![1])).toBeGreaterThanOrEqual(0.4);
|
|
260
|
+
});
|
|
261
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { describe, expect, it, beforeEach } from "vitest";
|
|
2
|
+
import { projectScopedKey, loadPositions, savePositions } from "../storage";
|
|
3
|
+
|
|
4
|
+
const createMemoryStorage = () => {
|
|
5
|
+
const map = new Map<string, string>();
|
|
6
|
+
return {
|
|
7
|
+
getItem: (key: string) => map.get(key) ?? null,
|
|
8
|
+
setItem: (key: string, value: string) => {
|
|
9
|
+
map.set(key, value);
|
|
10
|
+
},
|
|
11
|
+
clear: () => map.clear(),
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
describe("storage", () => {
|
|
16
|
+
const localStorage = createMemoryStorage();
|
|
17
|
+
|
|
18
|
+
beforeEach(() => {
|
|
19
|
+
(globalThis as { window?: { localStorage?: typeof localStorage } }).window = { localStorage };
|
|
20
|
+
localStorage.clear();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("builds project-scoped key", () => {
|
|
24
|
+
expect(projectScopedKey("proj_123")).toBe("kb:proj_123:dependency-graph-positions");
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("persists and restores positions", () => {
|
|
28
|
+
savePositions("proj_123", { "FN-1": { x: 10, y: 20 } });
|
|
29
|
+
expect(loadPositions("proj_123")).toEqual({ "FN-1": { x: 10, y: 20 } });
|
|
30
|
+
});
|
|
31
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { definePlugin } from "@fusion/plugin-sdk";
|
|
2
|
+
|
|
3
|
+
const plugin = definePlugin({
|
|
4
|
+
manifest: {
|
|
5
|
+
id: "fusion-plugin-dependency-graph",
|
|
6
|
+
name: "Dependency Graph",
|
|
7
|
+
version: "0.1.0",
|
|
8
|
+
description: "Top-level dependency graph dashboard view",
|
|
9
|
+
},
|
|
10
|
+
state: "installed",
|
|
11
|
+
hooks: {},
|
|
12
|
+
dashboardViews: [
|
|
13
|
+
{
|
|
14
|
+
viewId: "graph",
|
|
15
|
+
label: "Graph",
|
|
16
|
+
componentPath: "./src/DependencyGraphView.tsx",
|
|
17
|
+
icon: "Network",
|
|
18
|
+
placement: "more",
|
|
19
|
+
order: 40,
|
|
20
|
+
},
|
|
21
|
+
],
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
export default plugin;
|
|
25
|
+
export { DependencyGraphView } from "./DependencyGraphView";
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
const BASE_KEY = "dependency-graph-positions";
|
|
2
|
+
|
|
3
|
+
export function projectScopedKey(projectId?: string): string {
|
|
4
|
+
const suffix = projectId ?? "default";
|
|
5
|
+
return `kb:${suffix}:${BASE_KEY}`;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function loadPositions(projectId?: string): Record<string, { x: number; y: number }> {
|
|
9
|
+
if (typeof window === "undefined") return {};
|
|
10
|
+
try {
|
|
11
|
+
const raw = window.localStorage.getItem(projectScopedKey(projectId));
|
|
12
|
+
if (!raw) return {};
|
|
13
|
+
const parsed = JSON.parse(raw) as Record<string, { x: number; y: number }>;
|
|
14
|
+
return parsed ?? {};
|
|
15
|
+
} catch {
|
|
16
|
+
return {};
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function savePositions(projectId: string | undefined, positions: Record<string, { x: number; y: number }>): void {
|
|
21
|
+
if (typeof window === "undefined") return;
|
|
22
|
+
window.localStorage.setItem(projectScopedKey(projectId), JSON.stringify(positions));
|
|
23
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@runfusion/fusion",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Fusion CLI: HTTP API server, daemon, dashboard launcher, and task tooling for the Fusion AI coding agent.",
|
|
6
6
|
"homepage": "https://github.com/Runfusion/Fusion#readme",
|
|
@@ -35,12 +35,13 @@
|
|
|
35
35
|
"dist/client/**",
|
|
36
36
|
"dist/pi-claude-cli/**",
|
|
37
37
|
"dist/droid-cli/**",
|
|
38
|
+
"dist/plugins/**",
|
|
38
39
|
"skill/**",
|
|
39
40
|
"README.md"
|
|
40
41
|
],
|
|
41
42
|
"dependencies": {
|
|
42
|
-
"@mariozechner/pi-ai": "^0.
|
|
43
|
-
"@mariozechner/pi-coding-agent": "^0.
|
|
43
|
+
"@mariozechner/pi-ai": "^0.72.1",
|
|
44
|
+
"@mariozechner/pi-coding-agent": "^0.72.1",
|
|
44
45
|
"express": "^5.1.0",
|
|
45
46
|
"ink": "^6.8.0",
|
|
46
47
|
"ink-spinner": "^5.0.0",
|
|
@@ -90,6 +91,9 @@
|
|
|
90
91
|
"build:exe:all": "bun run build.ts --all",
|
|
91
92
|
"typecheck": "tsc --noEmit",
|
|
92
93
|
"test": "vitest run --silent=passed-only --reporter=dot",
|
|
93
|
-
"test:
|
|
94
|
+
"test:slow-cli": "cross-env FUSION_TEST_SLOW_CLI=1 vitest run src/commands/__tests__/agent-export.test.ts --silent=passed-only --reporter=dot",
|
|
95
|
+
"test:extension-integration": "cross-env FUSION_TEST_EXTENSION_INTEGRATION=1 vitest run src/__tests__/extension.test.ts --silent=passed-only --reporter=dot",
|
|
96
|
+
"test:build-exe": "cross-env FUSION_TEST_BUILD_EXE=1 vitest run --config vitest.build-exe.config.ts --silent=passed-only --reporter=dot",
|
|
97
|
+
"test:pre-release": "pnpm test:slow-cli && pnpm test:build-exe"
|
|
94
98
|
}
|
|
95
99
|
}
|
package/skill/fusion/SKILL.md
CHANGED
|
@@ -19,11 +19,11 @@ Fusion is an AI-orchestrated task board. You throw in rough ideas; AI specifies,
|
|
|
19
19
|
**Missions** provide hierarchical planning above tasks:
|
|
20
20
|
Mission → Milestone → Slice → Feature → Task
|
|
21
21
|
|
|
22
|
-
**Available tools:** Fusion registers tools
|
|
22
|
+
**Available tools:** Fusion registers tools (prefixed `fn_*`). No CLI commands or Bash needed — use the registered tools directly.
|
|
23
23
|
|
|
24
|
-
**Naming boundary:** The published
|
|
24
|
+
**Naming boundary:** The published skill surface uses `fn_*` tool names (for example `fn_task_create`, `fn_mission_create`). Engine runtime sessions also inject additional `fn_*` tools (for example `fn_review_spec`, `fn_review_step`, `fn_spawn_agent`) that are not part of the published skill surface.
|
|
25
25
|
|
|
26
|
-
**Engine runtime tools:** Triage/executor/merger/heartbeat sessions include auto-injected engine tools that
|
|
26
|
+
**Engine runtime tools:** Triage/executor/merger/heartbeat sessions include auto-injected engine tools that are not part of the published skill surface. See `references/engine-tools.md` for the canonical runtime-only catalog and usage boundaries.
|
|
27
27
|
|
|
28
28
|
**Tool categories:**
|
|
29
29
|
<!-- BEGIN: tool-categories (auto-generated by scripts/sync-fusion-skill-tools.mjs — do not edit by hand) -->
|
|
@@ -85,7 +85,7 @@ Use `fn_mission_create` for high-level objectives, then add milestones, slices,
|
|
|
85
85
|
|
|
86
86
|
<known_limitations>
|
|
87
87
|
|
|
88
|
-
These operations are **not available** via
|
|
88
|
+
These operations are **not available** via extension tools and require the dashboard or CLI:
|
|
89
89
|
|
|
90
90
|
- **Moving tasks between columns** — No tool for column moves (handled by the AI engine)
|
|
91
91
|
- **Workflow steps** — Creating/managing workflow step definitions requires the dashboard
|
|
@@ -103,7 +103,7 @@ For these operations, guide the user to the dashboard (`/fn`) or CLI commands do
|
|
|
103
103
|
|-----------|-------------|
|
|
104
104
|
| references/cli-commands.md | Full CLI command reference |
|
|
105
105
|
| references/task-structure.md | Task file structure and storage |
|
|
106
|
-
| references/extension-tools.md | All
|
|
106
|
+
| references/extension-tools.md | All extension tools with parameters |
|
|
107
107
|
| references/best-practices.md | Tips for effective Fusion usage |
|
|
108
108
|
| references/fusion-capabilities.md | Complete feature catalog |
|
|
109
109
|
| references/engine-tools.md | Engine session-scoped runtime tools (not extension-invokable) |
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# Engine Session-Scoped Tools
|
|
2
2
|
|
|
3
|
-
These tools are **not** part of the
|
|
3
|
+
These tools are **not** part of the user-invokable extension surface. They are injected by the engine at runtime for specific agent session types.
|
|
4
4
|
|
|
5
5
|
- Source files: `packages/engine/src/agent-tools.ts`, `triage.ts`, `executor.ts`, `merger.ts`, `agent-heartbeat.ts`
|
|
6
6
|
- Availability: only when the engine creates a session for the matching agent role
|
|
7
|
-
- Important: do not tell users to call these directly from the generic
|
|
7
|
+
- Important: do not tell users to call these directly from the generic extension tool list
|
|
8
8
|
|
|
9
9
|
## Shared runtime tools (`agent-tools.ts`)
|
|
10
10
|
|
|
@@ -14,8 +14,8 @@ These tools are **not** part of the pi extension's user-invokable `extension.ts`
|
|
|
14
14
|
| `fn_task_log` | executor, heartbeat | Write significant task log entries | `message` (string), `outcome?` (string) |
|
|
15
15
|
| `fn_task_document_write` | triage, executor, heartbeat | Save/update a named task document revision | `key` (string), `content` (string), `author?` (string) |
|
|
16
16
|
| `fn_task_document_read` | triage, executor, heartbeat | Read one task document or list all | `key?` (string) |
|
|
17
|
-
| `fn_memory_search` | triage, executor, heartbeat | Search project
|
|
18
|
-
| `fn_memory_get` | triage, executor, heartbeat | Read a bounded memory file window | `path` (string), `startLine?` (number), `lineCount?` (number) |
|
|
17
|
+
| `fn_memory_search` | triage, executor, heartbeat | Search project memory plus per-agent layered memory snippets | `query` (string), `limit?` (number) |
|
|
18
|
+
| `fn_memory_get` | triage, executor, heartbeat | Read a bounded memory file window (including bounded per-agent layered paths) | `path` (string), `startLine?` (number), `lineCount?` (number) |
|
|
19
19
|
| `fn_memory_append` | executor, heartbeat (when writable backend enabled) | Append long-term/daily memory notes | `scope?` (`project` \| `agent`), `layer` (`long-term` \| `daily`), `content` (string) |
|
|
20
20
|
| `fn_research_run` | triage, executor | Start a bounded research run (optionally wait for completion) and return structured findings metadata | `query` (string), `wait_for_completion?` (boolean), `max_wait_ms?` (number) |
|
|
21
21
|
| `fn_research_list` | triage, executor | List recent research runs with status/summary metadata | `status?` (`pending` \| `running` \| `completed` \| `failed` \| `cancelled`), `limit?` (number) |
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
# Fusion
|
|
1
|
+
# Fusion Extension Tools
|
|
2
2
|
|
|
3
|
-
All tools are registered via the
|
|
3
|
+
All tools are registered via the Fusion extension. They are available in any agent session when Fusion is configured.
|
|
4
4
|
|
|
5
5
|
> Naming contract: all externally exposed Fusion extension tools are `fn_*` (for example `fn_task_create`). Engine runtime sessions also inject additional `fn_*` tools (for example `fn_review_step`, `fn_spawn_agent`, `fn_task_document_write`) that are separate from this extension surface and documented in `engine-tools.md`.
|
|
6
6
|
|
|
@@ -364,7 +364,7 @@ Cancel a research run.
|
|
|
364
364
|
|
|
365
365
|
### /fn
|
|
366
366
|
|
|
367
|
-
Start or stop the Fusion dashboard from within
|
|
367
|
+
Start or stop the Fusion dashboard from within an agent session.
|
|
368
368
|
|
|
369
369
|
| Command | Description |
|
|
370
370
|
|---------|-------------|
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
Fusion is an AI-orchestrated task board. Tasks flow through columns:
|
|
6
6
|
Triage → Todo → In Progress → In Review → Done → Archived
|
|
7
7
|
|
|
8
|
-
##
|
|
8
|
+
## Extension Tools (Available to Agents)
|
|
9
9
|
|
|
10
10
|
All skill/extension tool invocations in this catalog use the public `fn_*` namespace. Engine runtime sessions also have additional runtime-only `fn_*` tools that are intentionally not listed here (see `references/engine-tools.md`).
|
|
11
11
|
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
## Key Takeaways for Fusion Skill
|
|
31
31
|
|
|
32
32
|
1. **Use router pattern** — Fusion has multiple distinct workflows (task management, lifecycle, specs, dashboard/CLI)
|
|
33
|
-
2. **No `allowed-tools` needed** — Fusion tools are registered via
|
|
33
|
+
2. **No `allowed-tools` needed** — Fusion tools are registered via the extension, not Bash CLI
|
|
34
34
|
3. **Inline essential concepts** — Task columns, workflow overview in SKILL.md
|
|
35
35
|
4. **Progressive disclosure** — SKILL.md routes to workflows, workflows reference detailed docs
|
|
36
36
|
5. **Pure XML structure** — No markdown headings (#, ##, ###) in body
|
|
@@ -3,14 +3,14 @@
|
|
|
3
3
|
</required_reading>
|
|
4
4
|
|
|
5
5
|
<objective>
|
|
6
|
-
Guide the agent through using the Fusion dashboard and CLI for operations not available via
|
|
6
|
+
Guide the agent through using the Fusion dashboard and CLI for operations not available via extension tools.
|
|
7
7
|
</objective>
|
|
8
8
|
|
|
9
9
|
<process>
|
|
10
10
|
|
|
11
11
|
**Starting the dashboard:**
|
|
12
12
|
|
|
13
|
-
Use the `/fn` command (registered by the
|
|
13
|
+
Use the `/fn` command (registered by the Fusion extension):
|
|
14
14
|
- `/fn` or `/fn 4040` — Start dashboard + AI engine on specified port (default 4040)
|
|
15
15
|
- `/fn stop` — Stop the dashboard
|
|
16
16
|
- `/fn status` — Check if dashboard is running
|
|
@@ -28,7 +28,7 @@ The dashboard provides:
|
|
|
28
28
|
|
|
29
29
|
**Operations that require CLI or dashboard:**
|
|
30
30
|
|
|
31
|
-
These cannot be done with
|
|
31
|
+
These cannot be done with extension tools:
|
|
32
32
|
|
|
33
33
|
| Operation | CLI Command | Dashboard |
|
|
34
34
|
|-----------|-------------|-----------|
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
</required_reading>
|
|
5
5
|
|
|
6
6
|
<objective>
|
|
7
|
-
Guide the agent through creating, viewing, and managing tasks on the Fusion board using
|
|
7
|
+
Guide the agent through creating, viewing, and managing tasks on the Fusion board using extension tools.
|
|
8
8
|
|
|
9
9
|
Use only the public `fn_*` extension tools in this workflow. Do not substitute internal engine runtime tools like `task_create`, `task_update`, `task_log`, or `task_done`.
|
|
10
10
|
</objective>
|