@runfusion/fusion 0.7.1 → 0.8.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.
Files changed (48) hide show
  1. package/dist/bin.js +2932 -844
  2. package/dist/client/assets/{AgentDetailView-DyzuiJas.js → AgentDetailView-C3Xcrxnp.js} +3 -3
  3. package/dist/client/assets/{AgentDetailView-C1_lTTET.css → AgentDetailView-DIBOY8V-.css} +1 -1
  4. package/dist/client/assets/{AgentsView-CgweOTe6.js → AgentsView-EjE4y4rM.js} +3 -3
  5. package/dist/client/assets/{ChatView-DrY8FMIt.js → ChatView-DQLvKCYj.js} +1 -1
  6. package/dist/client/assets/DevServerView-CX7paFRQ.js +1 -0
  7. package/dist/client/assets/{DirectoryPicker-D5KQ-im_.js → DirectoryPicker-_cBPx6Nx.js} +1 -1
  8. package/dist/client/assets/{DocumentsView-D2wK7FYJ.js → DocumentsView-Wz33aYqp.js} +1 -1
  9. package/dist/client/assets/{InsightsView-DfY3sa1j.js → InsightsView-C7YPnS92.js} +1 -1
  10. package/dist/client/assets/MemoryView-DKQtFzFQ.js +2 -0
  11. package/dist/client/assets/{NodesView-g26-j7rg.js → NodesView-CI4rUQC4.js} +1 -1
  12. package/dist/client/assets/{NodesView-BYVG2yY-.css → NodesView-DCoS6iYh.css} +1 -1
  13. package/dist/client/assets/{PiExtensionsManager-DfMr3Gls.js → PiExtensionsManager-BFmdKgHZ.js} +3 -3
  14. package/dist/client/assets/{PluginManager-DiMOD-Kj.js → PluginManager-BGQU1IIw.js} +1 -1
  15. package/dist/client/assets/{RoadmapsView-DJC4F4CD.js → RoadmapsView-Cts3hoIS.js} +1 -1
  16. package/dist/client/assets/SettingsModal-D5hLoLXp.css +1 -0
  17. package/dist/client/assets/{SettingsModal-Cx3iMWDs.js → SettingsModal-DXvBGZHf.js} +1 -1
  18. package/dist/client/assets/SettingsModal-DvRd0ZOE.js +31 -0
  19. package/dist/client/assets/SetupWizardModal-DRF5fOoR.css +1 -0
  20. package/dist/client/assets/{SetupWizardModal-Cow6woq6.js → SetupWizardModal-Y2ewEE8Y.js} +1 -1
  21. package/dist/client/assets/{SkillsView-DTB2cmXQ.js → SkillsView-BXvrHzEZ.js} +1 -1
  22. package/dist/client/assets/{TodoView-CyxdHUdz.js → TodoView-NZHkv9YQ.js} +2 -2
  23. package/dist/client/assets/{folder-open-C3zB1vmh.js → folder-open-Kh0ScTc5.js} +1 -1
  24. package/dist/client/assets/index-CWz44REw.css +1 -0
  25. package/dist/client/assets/index-D1gavMG-.js +656 -0
  26. package/dist/client/assets/{list-checks-CK3_6p5e.js → list-checks-CvoT0bwU.js} +1 -1
  27. package/dist/client/assets/{star-BQhDgM9V.js → star-BdfwSLBU.js} +1 -1
  28. package/dist/client/assets/{upload-DDdZveEJ.js → upload-Bx8Yk_7Q.js} +1 -1
  29. package/dist/client/assets/{users-DWWgd19M.js → users-DgVaFEsz.js} +1 -1
  30. package/dist/client/index.html +2 -2
  31. package/dist/client/version.json +1 -1
  32. package/dist/extension.js +2219 -433
  33. package/dist/pi-claude-cli/index.ts +27 -0
  34. package/dist/pi-claude-cli/package.json +1 -5
  35. package/dist/pi-claude-cli/src/__tests__/process-manager.test.ts +31 -9
  36. package/dist/pi-claude-cli/src/__tests__/provider.test.ts +122 -8
  37. package/dist/pi-claude-cli/src/process-manager.ts +25 -7
  38. package/dist/pi-claude-cli/src/provider.ts +31 -7
  39. package/dist/pi-claude-cli/src/types/cross-spawn.d.ts +7 -0
  40. package/package.json +2 -6
  41. package/skill/fusion/references/extension-tools.md +1 -0
  42. package/dist/client/assets/DevServerView-fvjo36sF.js +0 -6
  43. package/dist/client/assets/MemoryView-CyAQgXwO.js +0 -2
  44. package/dist/client/assets/SettingsModal-BnekMOV2.css +0 -1
  45. package/dist/client/assets/SettingsModal-DjVE27r5.js +0 -31
  46. package/dist/client/assets/SetupWizardModal-BMa6p24b.css +0 -1
  47. package/dist/client/assets/index-Belw0PQt.css +0 -1
  48. package/dist/client/assets/index-DJDWSrju.js +0 -646
@@ -144,6 +144,33 @@ export default function (pi: ExtensionAPI) {
144
144
  contextWindow: 1_000_000,
145
145
  maxTokens: 128_000,
146
146
  },
147
+ {
148
+ id: "claude-sonnet-4-6",
149
+ name: "Claude Sonnet 4.6",
150
+ reasoning: true,
151
+ input: ["text", "image"],
152
+ cost: { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 },
153
+ contextWindow: 200_000,
154
+ maxTokens: 16_384,
155
+ },
156
+ {
157
+ id: "claude-sonnet-4-5",
158
+ name: "Claude Sonnet 4.5",
159
+ reasoning: true,
160
+ input: ["text", "image"],
161
+ cost: { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 },
162
+ contextWindow: 200_000,
163
+ maxTokens: 8_192,
164
+ },
165
+ {
166
+ id: "claude-haiku-4-5",
167
+ name: "Claude Haiku 4.5",
168
+ reasoning: true,
169
+ input: ["text", "image"],
170
+ cost: { input: 0.8, output: 4, cacheRead: 0.08, cacheWrite: 1 },
171
+ contextWindow: 200_000,
172
+ maxTokens: 8_192,
173
+ },
147
174
  ];
148
175
 
149
176
  const seen = new Set(catalogModels.map((m) => m.id));
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fusion/pi-claude-cli",
3
- "version": "0.7.1",
3
+ "version": "0.8.0",
4
4
  "description": "Fusion vendored fork: pi coding-agent extension that routes LLM calls through the Claude Code CLI. Forked from rchern/pi-claude-cli (MIT). See UPSTREAM.md.",
5
5
  "license": "MIT",
6
6
  "private": true,
@@ -19,15 +19,11 @@
19
19
  "url": "https://github.com/Runfusion/Fusion",
20
20
  "directory": "packages/pi-claude-cli"
21
21
  },
22
- "dependencies": {
23
- "cross-spawn": "^7.0.6"
24
- },
25
22
  "peerDependencies": {
26
23
  "@mariozechner/pi-ai": "*",
27
24
  "@mariozechner/pi-coding-agent": "*"
28
25
  },
29
26
  "devDependencies": {
30
- "@types/cross-spawn": "^6.0.6",
31
27
  "@types/node": "^22.0.0",
32
28
  "typescript": "^5.7.0",
33
29
  "vitest": "^3.0.0"
@@ -1,9 +1,9 @@
1
1
  import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
2
2
  import type { ChildProcess } from "node:child_process";
3
3
 
4
- // Mock cross-spawn before importing process-manager
5
- vi.mock("cross-spawn", () => ({
6
- default: vi.fn(() => {
4
+ // Mock child_process.spawn before importing process-manager
5
+ vi.mock("node:child_process", () => ({
6
+ spawn: vi.fn(() => {
7
7
  const EventEmitter = require("node:events");
8
8
  const proc = new EventEmitter();
9
9
  proc.stdin = { write: vi.fn(), end: vi.fn() };
@@ -16,10 +16,6 @@ vi.mock("cross-spawn", () => ({
16
16
  proc.pid = 12345;
17
17
  return proc;
18
18
  }),
19
- }));
20
-
21
- // Mock child_process.execSync for validation tests
22
- vi.mock("node:child_process", () => ({
23
19
  execSync: vi.fn(),
24
20
  }));
25
21
 
@@ -42,10 +38,10 @@ vi.mock("node:os", () => ({
42
38
  tmpdir: mocks.tmpdir,
43
39
  }));
44
40
 
45
- import spawn from "cross-spawn";
46
- import { execSync } from "node:child_process";
41
+ import { spawn, execSync } from "node:child_process";
47
42
  import {
48
43
  spawnClaude,
44
+ buildClaudeSpawnArgs,
49
45
  writeUserMessage,
50
46
  cleanupProcess,
51
47
  captureStderr,
@@ -57,6 +53,32 @@ import {
57
53
  cleanupSystemPromptFile,
58
54
  } from "../process-manager";
59
55
 
56
+ describe("buildClaudeSpawnArgs", () => {
57
+ beforeEach(() => {
58
+ vi.clearAllMocks();
59
+ mocks.writeFileSync.mockReset();
60
+ mocks.tmpdir.mockReset();
61
+ mocks.tmpdir.mockReturnValue("/mock-tmp");
62
+ });
63
+
64
+ it("builds args including model and optional session/mcp flags", () => {
65
+ const args = buildClaudeSpawnArgs("claude-sonnet-4-6", undefined, {
66
+ resumeSessionId: "sess-1",
67
+ effort: "high",
68
+ mcpConfigPath: "/tmp/mcp.json",
69
+ });
70
+
71
+ expect(args).toContain("--model");
72
+ expect(args).toContain("claude-sonnet-4-6");
73
+ expect(args).toContain("--resume");
74
+ expect(args).toContain("sess-1");
75
+ expect(args).toContain("--effort");
76
+ expect(args).toContain("high");
77
+ expect(args).toContain("--mcp-config");
78
+ expect(args).toContain("/tmp/mcp.json");
79
+ });
80
+ });
81
+
60
82
  describe("spawnClaude", () => {
61
83
  beforeEach(() => {
62
84
  vi.clearAllMocks();
@@ -2,9 +2,9 @@ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
2
2
  import { EventEmitter } from "node:events";
3
3
  import { PassThrough } from "node:stream";
4
4
 
5
- // Mock cross-spawn with PassThrough streams for readline compatibility
6
- vi.mock("cross-spawn", () => ({
7
- default: vi.fn(() => {
5
+ // Mock child_process.spawn with PassThrough streams for readline compatibility
6
+ vi.mock("node:child_process", () => ({
7
+ spawn: vi.fn(() => {
8
8
  const proc = new EventEmitter();
9
9
  const stdin = { write: vi.fn(), end: vi.fn() };
10
10
  const stdout = new PassThrough();
@@ -20,10 +20,6 @@ vi.mock("cross-spawn", () => ({
20
20
  (proc as any).pid = 99999;
21
21
  return proc;
22
22
  }),
23
- }));
24
-
25
- // Mock child_process.execSync for validateCliPresence/validateCliAuth
26
- vi.mock("node:child_process", () => ({
27
23
  execSync: vi.fn(() => Buffer.from("1.0.0")),
28
24
  }));
29
25
 
@@ -69,7 +65,8 @@ vi.mock("@mariozechner/pi-ai", () => ({
69
65
  calculateCost: vi.fn(),
70
66
  }));
71
67
 
72
- import spawn from "cross-spawn";
68
+ import { spawn } from "node:child_process";
69
+ import { getModels } from "@mariozechner/pi-ai";
73
70
  import { streamViaCli } from "../provider";
74
71
 
75
72
  describe("provider registration (default export)", () => {
@@ -119,16 +116,71 @@ describe("provider registration (default export)", () => {
119
116
  expect(firstModel.maxTokens).toBe(8192);
120
117
  expect(firstModel.cost).toBeDefined();
121
118
  });
119
+
120
+ it("includes all extra Claude model entries", async () => {
121
+ const registerProvider = vi.fn();
122
+ const mockPi = { registerProvider, on: vi.fn() } as any;
123
+
124
+ const mod = await import("../../index");
125
+ mod.default(mockPi);
126
+
127
+ const config = registerProvider.mock.calls[0][1];
128
+ const modelIds = new Set(config.models.map((m: { id: string }) => m.id));
129
+
130
+ for (const id of [
131
+ "claude-opus-4-7",
132
+ "claude-sonnet-4-6",
133
+ "claude-sonnet-4-5",
134
+ "claude-haiku-4-5",
135
+ ]) {
136
+ expect(modelIds.has(id)).toBe(true);
137
+ }
138
+ });
139
+
140
+ it("deduplicates extra models when catalog already includes them", async () => {
141
+ const registerProvider = vi.fn();
142
+ const mockPi = { registerProvider, on: vi.fn() } as any;
143
+ const getModelsMock = vi.mocked(getModels);
144
+
145
+ getModelsMock.mockReturnValueOnce([
146
+ ...mockModels,
147
+ {
148
+ id: "claude-sonnet-4-6",
149
+ name: "Claude Sonnet 4.6",
150
+ api: "anthropic",
151
+ provider: "anthropic",
152
+ reasoning: true,
153
+ input: ["text", "image"],
154
+ cost: { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 },
155
+ contextWindow: 200000,
156
+ maxTokens: 16384,
157
+ } as any,
158
+ ] as any);
159
+
160
+ const mod = await import("../../index");
161
+ mod.default(mockPi);
162
+
163
+ const config = registerProvider.mock.calls[0][1];
164
+ const matches = config.models.filter(
165
+ (m: { id: string }) => m.id === "claude-sonnet-4-6",
166
+ );
167
+ expect(matches).toHaveLength(1);
168
+ });
122
169
  });
123
170
 
124
171
  describe("streamViaCli", () => {
125
172
  beforeEach(() => {
126
173
  vi.clearAllMocks();
127
174
  vi.useFakeTimers();
175
+ vi.spyOn(console, "warn").mockImplementation(() => {});
176
+ vi.spyOn(console, "error").mockImplementation(() => {});
177
+ delete process.env.PI_CLAUDE_CLI_DEBUG;
128
178
  });
129
179
 
130
180
  afterEach(() => {
131
181
  vi.useRealTimers();
182
+ vi.restoreAllMocks();
183
+ delete process.env.PI_CLAUDE_CLI_DEBUG;
132
184
  });
133
185
 
134
186
  it("returns an AssistantMessageEventStream", () => {
@@ -144,6 +196,24 @@ describe("streamViaCli", () => {
144
196
  expect(result.end).toBeDefined();
145
197
  });
146
198
 
199
+ it("logs PID and spawn args when debug mode is enabled", async () => {
200
+ process.env.PI_CLAUDE_CLI_DEBUG = "1";
201
+
202
+ const model = mockModels[0] as any;
203
+ const context = {
204
+ messages: [{ role: "user", content: "Hello" }],
205
+ };
206
+
207
+ const errorSpy = vi.spyOn(console, "error");
208
+
209
+ streamViaCli(model, context);
210
+ await vi.advanceTimersByTimeAsync(0);
211
+
212
+ expect(errorSpy).toHaveBeenCalledWith(
213
+ expect.stringContaining("spawned claude subprocess pid=99999 args="),
214
+ );
215
+ });
216
+
147
217
  it("spawns subprocess and writes user message to stdin", async () => {
148
218
  const model = mockModels[0] as any;
149
219
  const context = {
@@ -1183,6 +1253,50 @@ describe("streamViaCli", () => {
1183
1253
  expect(doneEvent.message.content).toBeDefined();
1184
1254
  });
1185
1255
 
1256
+ it("logs stderr at warn level on close even with exit code 0", async () => {
1257
+ const model = mockModels[0] as any;
1258
+ const context = {
1259
+ messages: [{ role: "user", content: "Hello" }],
1260
+ };
1261
+
1262
+ const warnSpy = vi.spyOn(console, "warn");
1263
+
1264
+ streamViaCli(model, context);
1265
+ await vi.advanceTimersByTimeAsync(0);
1266
+
1267
+ const proc = (spawn as any).mock.results[0].value;
1268
+
1269
+ proc.stderr.emit("data", Buffer.from("minor warning from cli"));
1270
+ proc.emit("close", 0, null);
1271
+ proc.stdout.end();
1272
+ await vi.advanceTimersByTimeAsync(100);
1273
+
1274
+ expect(warnSpy).toHaveBeenCalledWith(
1275
+ expect.stringContaining("minor warning from cli"),
1276
+ );
1277
+ });
1278
+
1279
+ it("warns when subprocess closes successfully with no content events", async () => {
1280
+ const model = mockModels[0] as any;
1281
+ const context = {
1282
+ messages: [{ role: "user", content: "Hello" }],
1283
+ };
1284
+
1285
+ const warnSpy = vi.spyOn(console, "warn");
1286
+
1287
+ streamViaCli(model, context);
1288
+ await vi.advanceTimersByTimeAsync(0);
1289
+
1290
+ const proc = (spawn as any).mock.results[0].value;
1291
+ proc.emit("close", 0, null);
1292
+ proc.stdout.end();
1293
+ await vi.advanceTimersByTimeAsync(100);
1294
+
1295
+ expect(warnSpy).toHaveBeenCalledWith(
1296
+ expect.stringContaining("closed without content events"),
1297
+ );
1298
+ });
1299
+
1186
1300
  it("does not push error on normal close (code 0)", async () => {
1187
1301
  const model = mockModels[0] as any;
1188
1302
  const context = {
@@ -6,12 +6,10 @@
6
6
  * Also provides startup validation for CLI presence and authentication.
7
7
  */
8
8
 
9
- import spawn from "cross-spawn";
10
- import { execSync } from "node:child_process";
9
+ import { spawn, execSync, type ChildProcess } from "node:child_process";
11
10
  import { writeFileSync, unlinkSync } from "node:fs";
12
11
  import { join } from "node:path";
13
12
  import { tmpdir } from "node:os";
14
- import type { ChildProcess } from "node:child_process";
15
13
 
16
14
  /**
17
15
  * Spawn a Claude CLI subprocess with all required flags for stream-json communication.
@@ -21,18 +19,16 @@ import type { ChildProcess } from "node:child_process";
21
19
  * @param options - Optional cwd, AbortSignal, and effort level
22
20
  * @returns The spawned ChildProcess with piped stdin/stdout/stderr
23
21
  */
24
- export function spawnClaude(
22
+ export function buildClaudeSpawnArgs(
25
23
  modelId: string,
26
24
  systemPrompt?: string,
27
25
  options?: {
28
- cwd?: string;
29
- signal?: AbortSignal;
30
26
  effort?: string;
31
27
  mcpConfigPath?: string;
32
28
  resumeSessionId?: string;
33
29
  newSessionId?: string;
34
30
  },
35
- ): ChildProcess {
31
+ ): string[] {
36
32
  const args = [
37
33
  "-p",
38
34
  "--input-format",
@@ -74,6 +70,28 @@ export function spawnClaude(
74
70
  args.push("--mcp-config", options.mcpConfigPath);
75
71
  }
76
72
 
73
+ return args;
74
+ }
75
+
76
+ export function spawnClaude(
77
+ modelId: string,
78
+ systemPrompt?: string,
79
+ options?: {
80
+ cwd?: string;
81
+ signal?: AbortSignal;
82
+ effort?: string;
83
+ mcpConfigPath?: string;
84
+ resumeSessionId?: string;
85
+ newSessionId?: string;
86
+ },
87
+ ): ChildProcess {
88
+ const args = buildClaudeSpawnArgs(modelId, systemPrompt, {
89
+ effort: options?.effort,
90
+ mcpConfigPath: options?.mcpConfigPath,
91
+ resumeSessionId: options?.resumeSessionId,
92
+ newSessionId: options?.newSessionId,
93
+ });
94
+
77
95
  const proc = spawn("claude", args, {
78
96
  stdio: ["pipe", "pipe", "pipe"],
79
97
  cwd: options?.cwd ?? process.cwd(),
@@ -38,6 +38,7 @@ import {
38
38
  forceKillProcess,
39
39
  registerProcess,
40
40
  cleanupSystemPromptFile,
41
+ buildClaudeSpawnArgs,
41
42
  } from "./process-manager.js";
42
43
  import { parseLine } from "./stream-parser.js";
43
44
  import { createEventBridge } from "./event-bridge.js";
@@ -61,10 +62,12 @@ import { isPiKnownClaudeTool } from "./tool-mapping.js";
61
62
  * arrives (e.g. someone embeds pi-claude-cli without a stuck detector).
62
63
  */
63
64
  const INACTIVITY_TIMEOUT_MS = 30 * 60_000;
64
- const DEBUG_STREAM = process.env.PI_CLAUDE_CLI_DEBUG === "1";
65
+ function isDebugStreamEnabled(): boolean {
66
+ return process.env.PI_CLAUDE_CLI_DEBUG === "1";
67
+ }
65
68
 
66
69
  function debugLog(message: string): void {
67
- if (!DEBUG_STREAM) return;
70
+ if (!isDebugStreamEnabled()) return;
68
71
  console.error(`[pi-claude-cli] ${message}`);
69
72
  }
70
73
 
@@ -131,19 +134,30 @@ export function streamViaCli(
131
134
  options?.thinkingBudgets,
132
135
  );
133
136
 
134
- // Spawn subprocess
135
- proc = spawnClaude(model.id, systemPrompt || undefined, {
137
+ const spawnOptions = {
136
138
  cwd,
137
139
  signal: options?.signal,
138
140
  effort,
139
141
  mcpConfigPath: options?.mcpConfigPath,
140
142
  resumeSessionId,
141
143
  newSessionId: !resumeSessionId ? options?.sessionId : undefined,
142
- });
144
+ };
145
+
146
+ // Spawn subprocess
147
+ proc = spawnClaude(model.id, systemPrompt || undefined, spawnOptions);
143
148
  const getStderr = captureStderr(proc);
144
149
 
145
150
  // Register in global process registry for teardown cleanup
146
151
  registerProcess(proc);
152
+ const spawnArgs = buildClaudeSpawnArgs(model.id, undefined, {
153
+ effort,
154
+ mcpConfigPath: options?.mcpConfigPath,
155
+ resumeSessionId,
156
+ newSessionId: !resumeSessionId ? options?.sessionId : undefined,
157
+ });
158
+ debugLog(
159
+ `spawned claude subprocess pid=${proc.pid ?? "unknown"} args=${JSON.stringify(spawnArgs)}`,
160
+ );
147
161
 
148
162
  // Write user message to subprocess stdin
149
163
  writeUserMessage(proc, prompt);
@@ -234,10 +248,13 @@ export function streamViaCli(
234
248
  proc.on("close", (code: number | null, _signal: string | null) => {
235
249
  clearTimeout(inactivityTimer);
236
250
  if (broken) return; // Break-early kill, expected
251
+ const stderr = getStderr().trim();
252
+ if (stderr) {
253
+ console.warn(`[pi-claude-cli] Claude CLI stderr on close: ${stderr}`);
254
+ }
237
255
  if (code !== 0 && code !== null) {
238
- const stderr = getStderr();
239
256
  const message = stderr
240
- ? `Claude CLI exited with code ${code}: ${stderr.trim()}`
257
+ ? `Claude CLI exited with code ${code}: ${stderr}`
241
258
  : `Claude CLI exited unexpectedly with code ${code}`;
242
259
  endStreamWithError(message);
243
260
  }
@@ -324,6 +341,13 @@ export function streamViaCli(
324
341
  // Guard with streamEnded to avoid pushing done after an error was already pushed.
325
342
  if (!streamEnded) {
326
343
  const output = bridge.getOutput();
344
+ const contentEvents = output.content || [];
345
+
346
+ if (contentEvents.length === 0) {
347
+ console.warn(
348
+ `[pi-claude-cli] Claude CLI closed without content events (model=${model.id}, sessionId=${options?.sessionId ?? "none"})`,
349
+ );
350
+ }
327
351
 
328
352
  // If stopReason is toolUse but there are no pi-known tool calls in content,
329
353
  // it means only user MCP tools were called (filtered by event bridge).
@@ -0,0 +1,7 @@
1
+ declare module "cross-spawn" {
2
+ const spawn: typeof import("node:child_process").spawn & {
3
+ sync: typeof import("node:child_process").spawnSync;
4
+ };
5
+
6
+ export default spawn;
7
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@runfusion/fusion",
3
- "version": "0.7.1",
3
+ "version": "0.8.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",
@@ -75,11 +75,7 @@
75
75
  "typebox": "^1.0.0",
76
76
  "typescript": "^5.7.0",
77
77
  "vitest": "^3.1.0",
78
- "yaml": "^2.8.3",
79
- "@fusion/core": "0.7.1",
80
- "@fusion/pi-claude-cli": "0.7.1",
81
- "@fusion/engine": "0.7.1",
82
- "@fusion/dashboard": "0.7.1"
78
+ "yaml": "^2.8.3"
83
79
  },
84
80
  "repository": {
85
81
  "type": "git",
@@ -29,6 +29,7 @@ Update fields on an existing task. Supports modifying the title, description, de
29
29
  | `description` | string | — | New task description |
30
30
  | `depends` | array | — | New dependency list — replaces existing dependencies (e.g. ['FN-001', 'FN-002']) |
31
31
  | `agentId` | union | — | Agent ID to assign this task to, or null to clear (e.g. 'agent-abc123') |
32
+ | `nodeId` | union | — | Node ID override for this task, or null to clear |
32
33
 
33
34
  ### fn_task_list
34
35
 
@@ -1,6 +0,0 @@
1
- import{r,j as t}from"./vendor-react-K0fH_qHe.js";import{c as Ze,bp as De,bq as de,br as et,bs as ue,s as Me,bt as ge,bu as we,bv as xe,bw as ye,bx as tt,by as ae,bz as rt,bA as st,bB as nt,bC as at,bD as it,bE as ie,L as ee,S as lt,bF as ct,bG as ot,N as dt,bH as Ue,T as Ae,bI as ut,J as vt,aM as mt,a5 as ft,aj as pt,R as ht}from"./index-DJDWSrju.js";import"./vendor-xterm-DzcZoU0P.js";/**
2
- * @license lucide-react v1.7.0 - ISC
3
- *
4
- * This source code is licensed under the ISC license.
5
- * See the LICENSE file in the root directory of this source tree.
6
- */const bt=[["path",{d:"M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8",key:"1p45f6"}],["path",{d:"M21 3v5h-5",key:"1q7to0"}]],gt=Ze("rotate-cw",bt),ve=500,wt=3e3;function B(e){return e instanceof Error?e.message:String(e)}function Pe(e){return e.length<=ve?e:e.slice(-ve)}function le(e,i){return Pe([...e,i])}function G(e){try{return JSON.parse(e)}catch{return null}}function Y(e){const i=e.text??"";return e.stream==="stderr"?`[stderr] ${i}`:i}function xt(e){return e?.previewUrl??null}async function Ne(e){return rt(e)}async function Se(e,i){return st(e,i)}async function yt(e){return nt(e)}async function Nt(e){return at(e)}async function je(e){return it(e)}function V(e){try{return e()}catch{return null}}function ce(){return typeof V(()=>de)=="function"&&typeof V(()=>De)=="function"}function I(e){return{config:{id:e.id??"default",name:e.name??"Dev Server",command:e.command??"",cwd:e.cwd??"."},status:e.status,runtime:e.pid?{pid:e.pid,startedAt:e.startedAt??new Date().toISOString(),exitCode:e.exitCode??void 0,previewUrl:e.previewUrl}:void 0,previewUrl:e.previewUrl??e.detectedUrl??e.manualUrl??void 0,logHistory:(e.logs??[]).map(i=>({timestamp:new Date().toISOString(),stream:i.startsWith("[stderr]")?"stderr":"stdout",text:i.replace(/^\[stderr\]\s*/,"")}))}}function St(e){const[i,o]=r.useState(null),[b,y]=r.useState([]),[w,f]=r.useState([]),[k,M]=r.useState([]),[C,P]=r.useState(!0),[N,u]=r.useState(null),n=r.useRef(0),[d,U]=r.useState(null),R=r.useCallback(a=>{if(o(a),a?.logHistory){const s=a.logHistory.slice(-ve).map(Y);f(s)}},[]),A=r.useCallback(async()=>{const a=n.current;try{if(ce())if(d){const s=await De(d,e);if(n.current!==a)return;R(s)}else{const s=await de(e);if(n.current!==a)return;y(s),s.length>0&&(U(s[0].config.id),R(s[0]))}else{const s=await Ne(e);if(n.current!==a)return;const c=I(s);y([c]),R(c)}u(null)}catch(s){if(n.current!==a)return;u(B(s))}},[R,e,d]);r.useEffect(()=>{n.current+=1;const a=n.current;o(null),y([]),f([]),M([]),P(!0),u(null),U(null),(async()=>{try{const[c,l]=await Promise.allSettled([ce()?de(e):Ne(e).then(S=>[I(S)]),typeof V(()=>ae)=="function"?ae(e):je(e).then(S=>S.map(g=>({name:g.name,command:g.command,cwd:g.cwd,scriptName:g.scriptName,packagePath:g.packagePath})))]);if(n.current!==a)return;let v=null;if(c.status==="fulfilled"){const S=c.value;if(y(S),S.length>0){const g=S[0];ce()&&U(g.config.id),R(g)}}else v=B(c.reason);l.status==="fulfilled"&&M(l.value),v&&u(v)}catch(c){if(n.current!==a)return;u(B(c))}finally{n.current===a&&P(!1)}})()},[R,e]),r.useEffect(()=>{const a=d?et(d,e):typeof V(()=>ue)=="function"?ue(e):null;if(!a)return;const s=n.current,c=Me(a,{events:{history:l=>{if(n.current!==s)return;const v=G(l.data);if(v?.lines){const S=v.lines.map(Y);f(Pe(S))}},log:l=>{if(n.current!==s)return;const v=G(l.data);if(v){const S=typeof v.line=="string"?v.line:Y(v);f(g=>le(g,S))}},"dev-server:log":l=>{if(n.current!==s)return;const v=G(l.data);if(v){const S=typeof v.line=="string"?v.line:Y(v);f(g=>le(g,S))}},"dev-server:output":l=>{if(n.current!==s)return;const v=G(l.data);v?.line&&f(S=>le(S,v.line))},status:l=>{if(n.current!==s)return;const v=G(l.data),S=v?.status;S&&o(g=>g&&{...g,status:S,runtime:v.pid?{...g.runtime??{startedAt:new Date().toISOString()},pid:v.pid}:g.runtime})},"dev-server:status":l=>{if(n.current!==s)return;const v=G(l.data);if(v?.status){const S=I(v);o(S),y([S])}},stopped:()=>{n.current},failed:()=>{n.current}},onReconnect:()=>{n.current===s&&A()},onError:()=>{n.current===s&&u(l=>l??"Lost log stream connection.")}});return()=>{c()}},[e,A,d]),r.useEffect(()=>{if(i?.status!=="running"&&i?.status!=="starting")return;const a=setInterval(()=>{A()},wt);return()=>{clearInterval(a)}},[A,i?.status]);const _=r.useCallback(async(a,s)=>{n.current+=1;const c=n.current;try{let l;if(d&&typeof V(()=>ge)=="function")l=await ge(d,e);else{const v=await Se({command:a,cwd:s},e);l=I(v)}if(n.current!==c)return;o(l),u(null)}catch(l){if(n.current!==c)return;throw u(B(l)),l}},[e,d]),D=r.useCallback(async()=>{n.current+=1;const a=n.current;try{let s;if(d&&typeof V(()=>we)=="function")s=await we(d,e);else{const c=await yt(e);s=I(c)}if(n.current!==a)return;o(s),u(null)}catch(s){if(n.current!==a)return;throw u(B(s)),s}},[e,d]),j=r.useCallback(async()=>{n.current+=1;const a=n.current;try{let s;if(d&&typeof V(()=>xe)=="function")s=await xe(d,e);else{const c=await Nt(e);s=I(c)}if(n.current!==a)return;o(s),u(null)}catch(s){if(n.current!==a)return;throw u(B(s)),s}},[e,d]),T=r.useCallback(async a=>{n.current+=1;const s=n.current;try{let c;if(d&&typeof V(()=>ye)=="function")c=await ye(d,a,e);else{const l=await tt({url:a},e);c={url:l.manualUrl??l.previewUrl??l.detectedUrl??null,source:l.manualUrl?"manual":"auto"}}if(n.current!==s)return;o(l=>l?{...l,previewUrl:c.url??void 0}:null),u(null)}catch(c){if(n.current!==s)return;throw u(B(c)),c}},[e,d]),h=r.useCallback(async()=>{n.current+=1;const a=n.current;try{let s;try{s=await ae(e)}catch{s=(await je(e)).map(l=>({name:l.name,command:l.command,cwd:l.cwd,scriptName:l.scriptName,packagePath:l.packagePath}))}if(n.current!==a)return;M(s),u(null)}catch(s){if(n.current!==a)return;throw u(B(s)),s}},[e]),p=r.useCallback(async(a,s)=>{if(typeof a!="string"&&!d)try{const v=a,S=await Se({command:v.command,cwd:v.cwd,scriptName:v.scriptName,packagePath:v.packagePath??v.cwd},e),g=I(S);o(g),y([g]),u(null);return}catch(v){throw u(B(v)),v}const c=typeof a=="string"?a:a.command,l=typeof a=="string"?s:a.cwd;await _(c,l)},[e,_,d]),E=r.useCallback(async()=>{await D()},[D]),L=r.useCallback(async()=>{await j()},[j]),z=r.useCallback(async a=>{await T(a)},[T]),$=r.useCallback(async()=>{await h()},[h]),O=r.useCallback(async()=>{await A()},[A]),H=xt(i),x=i?{...i,pid:i.runtime?.pid}:null;return{session:i,sessions:b,logs:w,detectedCommands:k,previewUrl:H,isLoading:C,error:N,startServer:_,stopServer:D,restartServer:j,setPreviewUrl:T,detectCommands:h,refresh:A,candidates:k,serverState:x,loading:C,start:p,stop:E,restart:L,setManualUrl:z,detect:$,refreshStatus:O}}const ke=500,Ce=100;function Te(e){return e.length>ke?e.slice(-ke):e}function ze(e){return e==="stderr"?"stderr":"stdout"}function K(e){try{return JSON.parse(e.data)}catch{return null}}function Z(e,i){return{id:typeof e.id=="number"&&Number.isFinite(e.id)?e.id:i,text:typeof e.text=="string"?e.text:"",stream:ze(e.stream),timestamp:typeof e.timestamp=="string"?e.timestamp:""}}function jt(e,i){return{id:i,text:e,stream:"stdout",timestamp:""}}function oe(e,i){if(i.length===0)return e;const o=[...e],b=new Set(e.map(y=>y.id));for(const y of i)b.has(y.id)||(b.add(y.id),o.push(y));return o.sort((y,w)=>y.id-w.id),Te(o)}function kt(e,i){const[o,b]=r.useState([]),[y,w]=r.useState(!1),[f,k]=r.useState(!1),[M,C]=r.useState(!1),[P,N]=r.useState(null),u=r.useRef(null),n=r.useRef(!1),d=r.useRef(0),U=r.useRef(0),R=r.useRef(e),A=r.useRef(i),_=r.useRef(0),D=r.useRef(1);(R.current!==e||A.current!==i)&&(R.current=e,A.current=i,d.current++,n.current=!0,_.current=0,D.current=1,b([]),w(!1),k(!1),C(!1),N(null),u.current&&(u.current(),u.current=null)),r.useEffect(()=>{if(!i){u.current&&(u.current(),u.current=null);return}const E=d.current,L=++U.current;n.current=!1,w(!0);const z=(x,a)=>{const s=Te(x);b(s),N(a),C(a!==null?a>s.length:!1);const c=s.length>0?s[s.length-1].id:0;_.current=c,D.current=c+1},$=x=>{if(n.current||d.current!==E||!x||typeof x!="object")return;const a=x.lines;if(!Array.isArray(a))return;if(a.length>0&&typeof a[0]=="string"){const c=a.filter(l=>typeof l=="string").map((l,v)=>jt(l,v+1));z(c,c.length);return}const s=a.filter(c=>!!c&&typeof c=="object").map((c,l)=>Z(c,l+1));z(s,s.length)},O=x=>{if(n.current||d.current!==E)return;const a=typeof x.text=="string"?x.text:typeof x.line=="string"?x.line:null;if(!a)return;const s=D.current,c=typeof x.id=="number"&&Number.isFinite(x.id)?x.id:s,l={id:c,text:a,stream:ze(x.stream),timestamp:typeof x.timestamp=="string"?x.timestamp:""};_.current=Math.max(_.current,c),D.current=Math.max(D.current,c+1),b(v=>oe(v,[l])),N(v=>v===null?v:Math.max(v+1,l.id))};async function H(){try{const a=await ie({maxLines:Ce},e);if(n.current||d.current!==E||U.current!==L)return;const s=a.lines.map((c,l)=>Z(c,l+1));z(s,a.totalLines)}catch{if(n.current||d.current!==E||U.current!==L)return;z([],null)}finally{!n.current&&d.current===E&&U.current===L&&w(!1)}const x=ue(e);u.current=Me(x,{events:{"dev-server:log":a=>{const s=K(a);s&&O(s)},log:a=>{const s=K(a);s&&O(s)},history:a=>{const s=K(a);$(s)},"dev-server:history":a=>{const s=K(a);$(s)}},onReconnect:()=>{n.current||d.current!==E||ie({lastEventId:_.current,maxLines:50},e).then(a=>{if(n.current||d.current!==E)return;const s=a.lines.map((c,l)=>Z(c,D.current+l));if(s.length>0){const c=s[s.length-1].id;_.current=Math.max(_.current,c),D.current=Math.max(D.current,c+1)}b(c=>oe(c,s)),N(a.totalLines)}).catch(()=>{})}})}return H(),()=>{n.current=!0,u.current&&(u.current(),u.current=null)}},[i,e]);const T=r.useCallback(async()=>{if(!i||f)return;const E=d.current,L=o.length;k(!0);try{const z=await ie({maxLines:Ce,offset:L},e);if(n.current||d.current!==E)return;const $=z.lines.map((O,H)=>Z(O,H+1));b(O=>oe($,O)),C(z.totalLines>L+$.length),N(z.totalLines)}catch{}finally{k(!1)}},[i,o.length,f,e]),h=r.useCallback(()=>{b([])},[]),p=u.current!==null&&!y&&!n.current;return{entries:o,loading:y,loadingMore:f,hasMore:M,total:P,loadMore:T,clear:h,logs:o,isStreaming:p,clearLogs:h}}const Ct="This preview appears to block iframe embedding. Open it in a new tab instead.",Et="The preview URL could not be loaded. Verify the server is running and the URL is correct.",Lt="Preview is taking longer than expected and may block iframe embedding.";function Rt(e){return e==="blocked"?Ct:e==="error"?Et:null}function _t(e,i={}){const{loadTimeoutMs:o=1e4,detectionMethod:b=null}=i,y=r.useRef(null),w=r.useRef(null),[f,k]=r.useState("unknown"),[M,C]=r.useState(null),[P]=r.useState(b),N=r.useCallback(()=>{w.current!==null&&(window.clearTimeout(w.current),w.current=null)},[]),u=r.useCallback(j=>{k(j),C(Rt(j))},[]),n=r.useCallback(()=>{k("blocked"),C(Lt)},[]);r.useEffect(()=>{if(N(),!e){k("unknown"),C(null);return}k("unknown"),C(null);let j=!1;return queueMicrotask(()=>{j||(k("loading"),C(null))}),()=>{j=!0,N()}},[N,e]),r.useEffect(()=>{if(f!=="loading"){N();return}const j=setTimeout(()=>{w.current=null,n()},o);return w.current=j,()=>{clearTimeout(j),w.current===j&&(w.current=null)}},[N,f,o,n]);const d=r.useCallback(()=>{const j=y.current;if(!j){u("embedded");return}try{if(j.contentWindow?.location?.href==="about:blank"&&j.src!=="about:blank"){u("blocked");return}}catch{}u("embedded")},[u]),U=r.useCallback(()=>{u("error")},[u]);r.useEffect(()=>{if(N(),!e){k("unknown"),C(null);return}k("unknown"),C(null)},[N]);const R=r.useCallback(()=>{N(),k("unknown"),C(null)},[N]),A=R,_=r.useMemo(()=>f==="embedded",[f]),D=r.useMemo(()=>f==="blocked"||f==="error",[f]);return{embedStatus:f,isEmbedded:_,isBlocked:D,blockReason:M,detectionMethod:P,iframeRef:y,resetEmbedStatus:R,setEmbedStatus:u,retry:A,embedContext:M,handleIframeLoad:d,handleIframeError:U}}const Dt=/\x1b\[[0-9;]*m/g;function me(e){return e.replace(Dt,"")}function Mt(e){if(!e)return"";const i=new Date(e);return Number.isNaN(i.getTime())?"":i.toLocaleTimeString([],{hour:"2-digit",minute:"2-digit",second:"2-digit",hour12:!1})}function Ut(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function At(e){if(e.stream==="stderr")return"error";const i=me(e.text).toLowerCase();return/\b(warn|warning)\b/.test(i)?"warn":/\b(error|fatal)\b/.test(i)?"error":"info"}function Pt(e,i){if(!i)return e;const o=new RegExp(`(${Ut(i)})`,"ig"),b=e.split(o),y=i.toLowerCase();return t.jsx(t.Fragment,{children:b.map((w,f)=>w.toLowerCase()===y?t.jsx("mark",{children:w},`${w}-${f}`):t.jsx("span",{children:w},`${w}-${f}`))})}function Tt({entries:e,loading:i,loadingMore:o,hasMore:b,total:y,onLoadMore:w,isRunning:f}){const k=r.useRef(null),M=r.useRef(e.length),C=r.useRef(f),[P,N]=r.useState(!1),[u,n]=r.useState(!1),[d,U]=r.useState(""),[R,A]=r.useState("all"),_=r.useMemo(()=>R==="all"?e:e.filter(p=>At(p)===R),[e,R]),D=r.useMemo(()=>{const p=d.trim().toLowerCase();return p?_.filter(E=>me(E.text).toLowerCase().includes(p)):_},[_,d]),j=D.length,T=r.useCallback(()=>{const p=k.current;p&&(p.scrollTop=p.scrollHeight,n(!1))},[]);r.useEffect(()=>{const p=C.current,E=M.current,L=e.length>E;f&&(!p||!u&&L)&&T(),C.current=f,M.current=e.length},[e.length,f,u,T]);const h=r.useCallback(()=>{const p=k.current;if(!p)return;const L=p.scrollTop+p.clientHeight>=p.scrollHeight-50;n(!L)},[]);return r.useEffect(()=>{i||e.length===0||!u&&f&&T()},[e,f,u,i,T]),i&&e.length===0?t.jsx("section",{className:"devserver-log-viewer","data-testid":"devserver-log-viewer",children:t.jsxs("div",{className:"devserver-log-viewer__loading","data-testid":"devserver-log-loading",children:[t.jsx(ee,{size:16,className:"devserver-log-viewer__spinner"}),t.jsx("span",{children:"Loading logs…"})]})}):t.jsxs("section",{className:`devserver-log-viewer${P?" devserver-log-viewer--fullscreen":""}`,"data-testid":"devserver-log-viewer",children:[t.jsxs("header",{className:"devserver-log-viewer__toolbar",children:[t.jsxs("div",{className:"devserver-log-viewer__toolbar-meta",children:[t.jsx("span",{className:"devserver-log-viewer__title",children:"Logs"}),t.jsxs("span",{className:"devserver-log-viewer__count","data-testid":"devserver-log-count",children:[y!==null?`${e.length}/${y}`:`${e.length}`," lines"]})]}),t.jsxs("div",{className:"devserver-log-viewer__toolbar-actions",children:[t.jsxs("label",{className:"devserver-log-viewer__severity",htmlFor:"devserver-log-severity-filter",children:[t.jsx("span",{className:"visually-hidden",children:"Filter logs by severity"}),t.jsxs("select",{id:"devserver-log-severity-filter",className:"select devserver-log-viewer__severity-select",value:R,onChange:p=>A(p.target.value),"data-testid":"devserver-log-severity-filter","aria-label":"Filter logs by severity",children:[t.jsx("option",{value:"all",children:"All severities"}),t.jsx("option",{value:"info",children:"Info"}),t.jsx("option",{value:"warn",children:"Warn"}),t.jsx("option",{value:"error",children:"Error"})]})]}),t.jsxs("label",{className:"devserver-log-viewer__search",htmlFor:"devserver-log-search",children:[t.jsx("span",{className:"visually-hidden",children:"Search logs"}),t.jsx(lt,{size:14}),t.jsx("input",{id:"devserver-log-search",className:"input devserver-log-viewer__search-input",type:"text",value:d,onChange:p=>U(p.target.value),placeholder:"Search logs","data-testid":"devserver-log-search-input","aria-label":"Search logs"})]}),d.trim().length>0&&t.jsxs("span",{className:"devserver-log-viewer__matches","data-testid":"devserver-log-match-count",children:[j," match",j===1?"":"es"]}),t.jsx("button",{type:"button",className:"btn btn-sm btn-icon",onClick:()=>N(p=>!p),"data-testid":"devserver-log-fullscreen-toggle","aria-label":P?"Exit fullscreen logs":"Enter fullscreen logs",children:P?t.jsx(ct,{size:14}):t.jsx(ot,{size:14})})]})]}),t.jsxs("div",{className:"devserver-log-viewer__body",children:[b&&t.jsx("div",{className:"devserver-log-viewer__load-more","data-testid":"devserver-log-load-more",children:t.jsx("button",{type:"button",className:"btn btn-sm touch-target",onClick:w,disabled:o,"data-testid":"devserver-log-load-more-button",children:o?t.jsxs(t.Fragment,{children:[t.jsx(ee,{size:14,className:"devserver-log-viewer__spinner"}),"Loading older logs…"]}):"Load older logs"})}),t.jsxs("div",{ref:k,className:"devserver-log-viewer__content",onScroll:h,"data-testid":"devserver-log-content",children:[!i&&D.length===0&&t.jsx("p",{className:"devserver-log-viewer__empty","data-testid":"devserver-log-empty",children:e.length===0?"No logs yet. Start the dev server to see output.":_.length===0?"No log lines match the selected severity.":"No log lines match your search."}),D.map(p=>{const E=me(p.text),L=Mt(p.timestamp);return t.jsxs("div",{className:"devserver-log-line",children:[L&&t.jsx("span",{className:"devserver-log-timestamp","data-testid":"devserver-log-timestamp",children:L}),p.stream==="stderr"&&t.jsx("span",{className:"devserver-log-stream-badge","data-testid":"devserver-log-stderr-badge",children:"ERR"}),t.jsx("span",{className:"devserver-log-text",children:Pt(E,d.trim())})]},p.id)})]}),u&&f&&t.jsxs("button",{type:"button",className:"btn btn-sm devserver-log-viewer__new-logs-button",onClick:T,"data-testid":"devserver-log-jump-button",children:[t.jsx(dt,{size:14}),"New logs"]})]})]})}const zt="devserver-preview-iframe";function Ft({url:e,embedStatus:i,onEmbedStatusChange:o,iframeRef:b,blockReason:y,onRetry:w,className:f=zt,embedContext:k}){const M=y??k??null,[C,P]=r.useState(0);r.useEffect(()=>{!e||i!=="unknown"||(P(d=>d+1),o("loading"))},[i,o,e]);const N=r.useCallback(()=>{const d=b.current;if(!d){o("embedded");return}try{if(d.contentWindow?.location?.href==="about:blank"&&d.src!=="about:blank"){o("blocked");return}}catch{}o("embedded")},[b,o]),u=r.useCallback(d=>{d.stopPropagation(),o("error")},[o]),n=r.useCallback(()=>{e&&window.open(e,"_blank","noopener,noreferrer")},[e]);return e?t.jsxs("div",{className:"devserver-preview-iframe-shell",children:[t.jsx("iframe",{src:e,ref:b,sandbox:"allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox",className:f,title:"Dev server preview",onLoad:N,onError:u,onErrorCapture:u,"data-testid":"devserver-preview-iframe"},`${e}-${C}`),i==="loading"&&t.jsxs("div",{className:"devserver-preview-overlay","data-testid":"devserver-preview-loading",children:[t.jsx(ee,{size:16,className:"dev-server-spin"}),t.jsx("span",{children:"Loading preview..."})]}),i==="blocked"&&t.jsxs("div",{className:"devserver-preview-blocked-panel",role:"alert","data-testid":"devserver-preview-blocked-panel",children:[t.jsx(Ue,{className:"devserver-preview-blocked-icon","aria-hidden":"true"}),t.jsxs("div",{children:[t.jsx("p",{className:"devserver-preview-blocked-title",children:"Preview cannot be embedded"}),M&&t.jsx("p",{className:"devserver-preview-blocked-context",children:M})]}),t.jsx("p",{className:"devserver-preview-blocked-description",children:"You can view the preview in a separate browser tab."}),t.jsxs("div",{className:"devserver-preview-blocked-actions",children:[t.jsx("button",{type:"button",className:"btn btn-primary",onClick:n,children:"Open in new tab"}),w&&t.jsx("button",{type:"button",className:"btn btn-sm",onClick:w,children:"Retry"})]})]}),i==="error"&&t.jsxs("div",{className:"devserver-preview-error-panel",role:"alert","data-testid":"devserver-preview-error-panel",children:[t.jsx(Ae,{className:"devserver-preview-blocked-icon","aria-hidden":"true"}),t.jsxs("div",{children:[t.jsx("p",{className:"devserver-preview-blocked-title",children:"Unable to load preview"}),M&&t.jsx("p",{className:"devserver-preview-blocked-context",children:M})]}),t.jsx("p",{className:"devserver-preview-blocked-description",children:"You can view the preview in a separate browser tab."}),t.jsxs("div",{className:"devserver-preview-blocked-actions",children:[t.jsx("button",{type:"button",className:"btn btn-primary",onClick:n,children:"Open in new tab"}),w&&t.jsx("button",{type:"button",className:"btn btn-sm",onClick:w,children:"Retry"})]})]})]}):null}const Ee={stopped:{className:"dev-server-status-badge--stopped",label:"Stopped"},starting:{className:"dev-server-status-badge--starting",label:"Starting..."},running:{className:"dev-server-status-badge--running",label:"Running"},stopping:{className:"dev-server-status-badge--starting",label:"Stopping..."},failed:{className:"dev-server-status-badge--failed",label:"Failed"}};function Le(e){return e instanceof Error?e.message:String(e)}function Fe(e){return e==="."?"root":e}function Re(e){return e?e==="root"?".":e:null}function _e(e,i,o){return!i||e.scriptName!==i?!1:o?Fe(e.cwd)===o:!0}function $t(e){return e.cwd==="."?"root":e.cwd}function Ot(e){return e.length<=60?e:`${e.slice(0,60)}…`}function qt({addToast:e,projectId:i}){const{session:o,detectedCommands:b,previewUrl:y,isLoading:w,error:f,startServer:k,stopServer:M,restartServer:C,setPreviewUrl:P,detectCommands:N,refresh:u}=St(i),n=o?.status??"stopped",d=n==="running"||n==="starting",U=Ee[n]??Ee.stopped,{entries:R,loading:A,loadingMore:_,hasMore:D,total:j,loadMore:T}=kt(i,!!i),h=y,p=o?.config?.cwd??null,[E,L]=r.useState(!0),[z,$]=r.useState(""),[O,H]=r.useState(""),[x,a]=r.useState(null),[s,c]=r.useState(null),[l,v]=r.useState("embedded"),S=l==="embedded"?h:null,{embedStatus:g,setEmbedStatus:$e,resetEmbedStatus:X,iframeRef:W,isEmbedded:Oe,isBlocked:te,blockReason:re,retry:fe}=_t(S),[pe,q]=r.useState(!1),he=r.useRef(g);r.useEffect(()=>{const m=he.current!==g;te&&m&&q(!0),g==="embedded"&&q(!1),he.current=g},[g,te]),r.useEffect(()=>{q(!1)},[h]);const J=r.useMemo(()=>{if(!x)return null;const m=Re(p);return b.find(F=>!(F.scriptName!==x||m&&F.cwd!==m||o?.config?.command&&F.command!==o.config.command))??b.find(F=>_e(F,x,p))??null},[b,o?.config?.command,x,p]);r.useEffect(()=>{typeof N=="function"&&N().catch(m=>{e(Le(m),"error")})},[e,N]),r.useEffect(()=>{if(x){L(!1);return}L(!0)},[x]),r.useEffect(()=>{if(o?.status==="running"||o?.status==="starting"){o.config?.command?.trim().length>0&&$(o.config.command);return}if(J){$(J.command);return}b.length>0&&$(m=>m.trim().length>0?m:b[0]?.command??"")},[b,J,o?.config?.command,o?.status]),r.useEffect(()=>{H(h??"")},[h]);const se=r.useCallback(()=>{h&&window.open(h,"_blank","noopener,noreferrer")},[h]),be=r.useCallback(()=>{q(!1),fe()},[fe]),Be=r.useCallback(()=>{try{const m=W.current;if(m?.contentWindow){m.contentWindow.location.reload(),q(!1),X();return}}catch{}if(!(!h||!W.current))try{const m=new URL(h);m.searchParams.set("_t",Date.now().toString()),W.current.src=m.toString(),q(!1),X()}catch{W.current.src=h,q(!1),X()}},[h,W,X]),Q=r.useCallback(async(m,F,ne)=>{c(m);try{await F(),e(ne,"success")}catch(Ke){e(Le(Ke),"error")}finally{c(null)}},[e]),He=r.useCallback(m=>{a(m.scriptName),L(!1),$(m.command),e(`Selected ${m.scriptName} script.`,"success")},[e]),Ve=r.useCallback(()=>{a(null),L(!0),e("Cleared selected dev server script.","success")},[e]),qe=()=>{const m=z.trim();if(m.length===0){e("Enter a command before starting the dev server.","warning");return}const F=Re(p)??".",ne=J?.cwd??F;Q("start",()=>k(m,ne),"Dev server started.")},Ie=()=>{Q("stop",M,"Dev server stopped.")},We=()=>{Q("restart",C,"Dev server restarted.")},Ge=()=>{const m=O.trim(),F=m.length>0?m:null;Q("preview",()=>P(F),F?"Preview URL updated.":"Preview URL override cleared.")},Xe=r.useCallback(()=>{f&&u()},[f,u]),Je=n==="starting"||n==="running"||s!==null,Qe=n==="stopped"||s!==null,Ye=n==="stopped"||n==="starting"||s!==null;return t.jsxs("div",{className:"dev-server-view","data-testid":"dev-server-view",children:[t.jsxs("section",{className:"dev-server-header","aria-label":"Dev server controls header",children:[t.jsxs("div",{className:"dev-server-header-title",children:[t.jsx(ut,{size:16}),t.jsx("h2",{children:"Dev Server"}),t.jsx("span",{className:`dev-server-status-badge ${U.className}`,"data-testid":"dev-server-status-badge",children:U.label})]}),t.jsxs("div",{className:"dev-server-header-actions",children:[t.jsxs("button",{type:"button",className:"btn btn-primary btn-sm",onClick:qe,disabled:Je,"data-testid":"dev-server-start-button",children:[t.jsx(vt,{size:14}),t.jsx("span",{children:s==="start"?"Starting...":"Start"})]}),t.jsxs("button",{type:"button",className:"btn btn-danger btn-sm",onClick:Ie,disabled:Qe,"data-testid":"dev-server-stop-button",children:[t.jsx(mt,{size:14}),t.jsx("span",{children:s==="stop"?"Stopping...":"Stop"})]}),t.jsxs("button",{type:"button",className:"btn btn-sm",onClick:We,disabled:Ye,"data-testid":"dev-server-restart-button",children:[t.jsx(gt,{size:14}),t.jsx("span",{children:s==="restart"?"Restarting...":"Restart"})]})]})]}),t.jsxs("section",{className:"dev-server-panel dev-server-config","aria-label":"Dev server configuration",children:[t.jsxs("div",{className:"dev-server-section-header",children:[t.jsx("h3",{children:"Configuration"}),w&&t.jsx("span",{className:"dev-server-muted",children:"Loading..."})]}),w&&!o&&b.length===0&&t.jsxs("div",{className:"dev-server-loading-state","data-testid":"dev-server-loading-state",children:[t.jsx(ee,{size:16,className:"dev-server-spin"}),t.jsx("span",{children:"Loading dev server configuration..."})]}),f&&t.jsxs("div",{className:"dev-server-error-box",role:"alert","data-testid":"dev-server-error-box",children:[t.jsx("p",{children:f}),t.jsx("button",{type:"button",className:"btn btn-sm",onClick:Xe,children:"Retry"})]}),t.jsxs("div",{className:"dev-server-section",children:[t.jsx("h3",{children:"Script Selection"}),x&&t.jsxs("div",{className:"dev-server-selected","data-testid":"dev-server-selected-summary",children:[t.jsx("span",{className:"dev-server-candidate-name",children:x}),t.jsx("span",{className:"dev-server-candidate-source",children:p??"root"}),t.jsx("button",{type:"button",className:"btn btn-sm",onClick:()=>L(!0),"data-testid":"dev-server-change-selection",children:"Change"}),t.jsx("button",{type:"button",className:"btn btn-danger btn-sm",onClick:Ve,"data-testid":"dev-server-clear-selection",children:"Clear"})]}),E&&b.length===0&&t.jsxs("p",{className:"dev-server-empty-state","data-testid":"dev-server-empty-candidates",children:["No dev server scripts detected. Check that your project has a ",t.jsx("code",{children:"package.json"})," with a ",t.jsx("code",{children:"dev"}),", ",t.jsx("code",{children:"start"}),", or similar script."]}),E&&b.length>0&&t.jsx("div",{className:"dev-server-candidates","data-testid":"dev-server-candidates",children:b.map(m=>{const F=_e(m,x,p);return t.jsxs("button",{type:"button",className:`dev-server-candidate ${F?"dev-server-candidate--selected":""}`,onClick:()=>He(m),"data-testid":`dev-server-candidate-${m.scriptName}-${Fe(m.cwd)}`,children:[t.jsx("span",{className:"dev-server-candidate-name",children:m.scriptName}),t.jsx("span",{className:"dev-server-candidate-command",children:Ot(m.command)}),t.jsx("span",{className:"dev-server-candidate-source",children:$t(m)})]},`${m.cwd}::${m.scriptName}::${m.command}`)})})]}),t.jsxs("div",{className:"dev-server-field-group",children:[t.jsx("label",{htmlFor:"dev-server-command",className:"dev-server-label",children:"Command"}),t.jsx("input",{id:"dev-server-command",className:"input",value:z,onChange:m=>$(m.target.value),placeholder:"pnpm dev","data-testid":"dev-server-command-input",readOnly:n==="running"||n==="starting"})]}),(n==="running"||n==="starting")&&o&&t.jsxs("div",{className:"dev-server-current-command","data-testid":"dev-server-current-command",children:[t.jsx("span",{className:"dev-server-label",children:"Running command"}),t.jsx("code",{children:o.config?.command??z})]}),t.jsxs("div",{className:"dev-server-preview-override",children:[t.jsx("label",{htmlFor:"dev-server-preview-input",className:"dev-server-label",children:"Preview URL Override"}),t.jsx("input",{id:"dev-server-preview-input",className:"input",type:"url",value:O,onChange:m=>H(m.target.value),placeholder:"http://localhost:3000","data-testid":"dev-server-preview-input"}),t.jsx("button",{type:"button",className:"btn btn-primary btn-sm",onClick:Ge,disabled:s==="preview","data-testid":"dev-server-set-preview",children:"Save"})]}),h&&t.jsxs("p",{className:"dev-server-preview-hint",children:["Auto-detected: ",h]})]}),t.jsx("div",{className:"dev-server-content",children:t.jsxs("section",{className:"dev-server-panel dev-server-logs-panel","data-testid":"dev-server-logs-panel","aria-label":"Dev server logs",children:[t.jsxs("div",{className:"dev-server-section-header",children:[t.jsx("h3",{children:"Logs"}),t.jsxs("span",{className:"dev-server-muted",children:[j??R.length," lines"]})]}),t.jsx("div",{className:"dev-server-logs-viewer","data-testid":"dev-server-log-viewer",children:t.jsx(Tt,{entries:R,loading:A,loadingMore:_,hasMore:D,total:j,onLoadMore:T,isRunning:d})})]})}),t.jsxs("section",{className:"dev-server-panel devserver-preview-panel","data-testid":"devserver-preview-panel","aria-label":"Dev server preview",children:[t.jsxs("div",{className:"devserver-preview-header",children:[t.jsxs("div",{className:"devserver-preview-title",children:[t.jsx(ft,{size:14}),t.jsx("span",{children:"Preview"})]}),t.jsxs("span",{className:"devserver-preview-url-badge devserver-preview-url-badge--auto",title:h??"No preview URL","data-testid":"devserver-preview-url-badge",children:["Auto",h?` · ${h}`:" · Not available"]}),t.jsxs("div",{className:"devserver-preview-actions",children:[t.jsx("button",{type:"button",className:"btn btn-sm",onClick:()=>v(m=>m==="embedded"?"external":"embedded"),"data-testid":"devserver-preview-mode-toggle",children:l==="embedded"?"External only":"Embedded"}),t.jsx("button",{type:"button",className:"btn btn-sm btn-icon",title:"Open in new tab",onClick:se,disabled:!h,"data-testid":"devserver-preview-open-tab",children:t.jsx(pt,{size:14})}),t.jsx("button",{type:"button",className:"btn btn-sm btn-icon",title:"Refresh preview",onClick:Be,disabled:!h,"data-testid":"devserver-preview-refresh",children:t.jsx(ht,{size:14})})]})]}),t.jsxs("div",{className:"devserver-preview-container","data-embed-status":g,"data-embedded":Oe?"true":"false",children:[!h&&!d&&t.jsx("p",{className:"devserver-preview-empty",children:"Start a dev server to see a live preview here."}),!h&&d&&t.jsx("p",{className:"devserver-preview-empty",children:"No preview URL detected. Start the dev server or set a manual URL to preview your app."}),h&&l==="external"&&t.jsxs("div",{className:"devserver-preview-external-only","data-testid":"devserver-preview-external-only",children:[t.jsx("p",{children:"Embedded preview is disabled. Open your app in a separate browser tab."}),t.jsx("button",{type:"button",className:"btn btn-primary btn-sm touch-target",onClick:se,"data-testid":"devserver-preview-external-open-tab",children:"Open in new tab"})]}),h&&l==="embedded"&&pe&&te&&t.jsxs("div",{className:g==="error"?"devserver-preview-error-panel":"devserver-preview-blocked-panel","data-testid":"devserver-preview-fallback",role:"alert",children:[g==="error"?t.jsx(Ae,{className:"devserver-preview-blocked-icon","aria-hidden":"true"}):t.jsx(Ue,{className:"devserver-preview-blocked-icon","aria-hidden":"true"}),t.jsxs("div",{children:[t.jsx("p",{className:"devserver-preview-blocked-title",children:g==="error"?"Preview failed":"Preview blocked"}),re&&t.jsx("p",{className:"devserver-preview-blocked-context",children:re})]}),t.jsx("p",{className:"devserver-preview-blocked-description",children:"Open the preview in a new tab, or retry embedded mode after checking your server settings."}),t.jsxs("div",{className:"devserver-preview-blocked-actions",children:[t.jsx("button",{type:"button",className:"btn btn-primary",onClick:se,"data-testid":"devserver-preview-fallback-open-tab",children:"Open preview in new tab"}),t.jsx("button",{type:"button",className:"btn btn-sm",onClick:be,"data-testid":"devserver-preview-fallback-retry",children:"Retry embedded preview"})]})]}),h&&l==="embedded"&&!pe&&t.jsx(Ft,{url:h,embedStatus:g,onEmbedStatusChange:$e,iframeRef:W,blockReason:re,onRetry:be})]})]})]})}export{qt as DevServerView};