@runfusion/fusion 0.7.0 → 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 (51) hide show
  1. package/dist/bin.js +4694 -2257
  2. package/dist/client/assets/{AgentDetailView-Du65-jDo.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-C2f7esMv.css +1 -0
  5. package/dist/client/assets/{AgentsView-BkgNEbNs.js → AgentsView-EjE4y4rM.js} +46 -46
  6. package/dist/client/assets/{ChatView-LDte0TdV.js → ChatView-DQLvKCYj.js} +1 -1
  7. package/dist/client/assets/DevServerView-CX7paFRQ.js +1 -0
  8. package/dist/client/assets/{DirectoryPicker-CSp3G115.js → DirectoryPicker-_cBPx6Nx.js} +1 -1
  9. package/dist/client/assets/{DocumentsView-DOe0E1WQ.js → DocumentsView-Wz33aYqp.js} +1 -1
  10. package/dist/client/assets/{InsightsView-DWZbS6z-.js → InsightsView-C7YPnS92.js} +1 -1
  11. package/dist/client/assets/MemoryView-DKQtFzFQ.js +2 -0
  12. package/dist/client/assets/{NodesView-DO9jxhM4.js → NodesView-CI4rUQC4.js} +4 -4
  13. package/dist/client/assets/{NodesView-BYVG2yY-.css → NodesView-DCoS6iYh.css} +1 -1
  14. package/dist/client/assets/{PiExtensionsManager-CfXZaWl7.js → PiExtensionsManager-BFmdKgHZ.js} +3 -3
  15. package/dist/client/assets/{PluginManager-r6CWD9u-.js → PluginManager-BGQU1IIw.js} +1 -1
  16. package/dist/client/assets/{RoadmapsView-CgAM3YfN.js → RoadmapsView-Cts3hoIS.js} +1 -1
  17. package/dist/client/assets/SettingsModal-D5hLoLXp.css +1 -0
  18. package/dist/client/assets/{SettingsModal-fmRtzH8z.js → SettingsModal-DXvBGZHf.js} +1 -1
  19. package/dist/client/assets/SettingsModal-DvRd0ZOE.js +31 -0
  20. package/dist/client/assets/SetupWizardModal-DRF5fOoR.css +1 -0
  21. package/dist/client/assets/{SetupWizardModal-oRNpImWR.js → SetupWizardModal-Y2ewEE8Y.js} +1 -1
  22. package/dist/client/assets/{SkillsView-BhwtL-0_.js → SkillsView-BXvrHzEZ.js} +1 -1
  23. package/dist/client/assets/{TodoView-CmhUBFNV.js → TodoView-NZHkv9YQ.js} +2 -2
  24. package/dist/client/assets/{folder-open-CrTagHrr.js → folder-open-Kh0ScTc5.js} +1 -1
  25. package/dist/client/assets/index-CWz44REw.css +1 -0
  26. package/dist/client/assets/index-D1gavMG-.js +656 -0
  27. package/dist/client/assets/{list-checks-DR26h_Io.js → list-checks-CvoT0bwU.js} +1 -1
  28. package/dist/client/assets/{star-CO_D42zy.js → star-BdfwSLBU.js} +1 -1
  29. package/dist/client/assets/{upload-BcrgS-iu.js → upload-Bx8Yk_7Q.js} +1 -1
  30. package/dist/client/assets/{users-4d8al5Sp.js → users-DgVaFEsz.js} +1 -1
  31. package/dist/client/brands/hermes-logo.svg +1 -1
  32. package/dist/client/index.html +2 -2
  33. package/dist/client/version.json +1 -1
  34. package/dist/extension.js +3459 -1459
  35. package/dist/pi-claude-cli/index.ts +27 -0
  36. package/dist/pi-claude-cli/package.json +1 -5
  37. package/dist/pi-claude-cli/src/__tests__/process-manager.test.ts +31 -9
  38. package/dist/pi-claude-cli/src/__tests__/provider.test.ts +122 -8
  39. package/dist/pi-claude-cli/src/process-manager.ts +25 -7
  40. package/dist/pi-claude-cli/src/provider.ts +31 -7
  41. package/dist/pi-claude-cli/src/types/cross-spawn.d.ts +7 -0
  42. package/package.json +4 -7
  43. package/skill/fusion/references/extension-tools.md +1 -0
  44. package/dist/client/assets/AgentsView-D12CuIFc.css +0 -1
  45. package/dist/client/assets/DevServerView-AXznq3jv.js +0 -6
  46. package/dist/client/assets/MemoryView-BxnlhBoa.js +0 -2
  47. package/dist/client/assets/SettingsModal-DPDQyWim.css +0 -1
  48. package/dist/client/assets/SettingsModal-GZdRt8fX.js +0 -31
  49. package/dist/client/assets/SetupWizardModal-BMa6p24b.css +0 -1
  50. package/dist/client/assets/index-BTeSa6vk.js +0 -646
  51. package/dist/client/assets/index-S9oR77v2.css +0 -1
@@ -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.0",
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.0",
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",
@@ -68,17 +68,14 @@
68
68
  "@types/node": "^22.0.0",
69
69
  "@types/react": "^19.0.0",
70
70
  "@vitest/coverage-v8": "^3.1.0",
71
+ "cross-env": "^7.0.0",
71
72
  "ink-testing-library": "^4.0.0",
72
73
  "tsup": "^8.5.1",
73
74
  "tsx": "^4.19.0",
74
75
  "typebox": "^1.0.0",
75
76
  "typescript": "^5.7.0",
76
77
  "vitest": "^3.1.0",
77
- "yaml": "^2.8.3",
78
- "@fusion/core": "0.7.0",
79
- "@fusion/dashboard": "0.7.0",
80
- "@fusion/engine": "0.7.0",
81
- "@fusion/pi-claude-cli": "0.7.0"
78
+ "yaml": "^2.8.3"
82
79
  },
83
80
  "repository": {
84
81
  "type": "git",
@@ -92,6 +89,6 @@
92
89
  "build:exe:all": "bun run build.ts --all",
93
90
  "typecheck": "tsc --noEmit",
94
91
  "test": "vitest run --silent=passed-only --reporter=dot",
95
- "test:build-exe": "FUSION_TEST_BUILD_EXE=1 vitest run --config vitest.build-exe.config.ts --silent=passed-only --reporter=dot"
92
+ "test:build-exe": "cross-env FUSION_TEST_BUILD_EXE=1 vitest run --config vitest.build-exe.config.ts --silent=passed-only --reporter=dot"
96
93
  }
97
94
  }
@@ -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 +0,0 @@
1
- .active-agents-panel{margin-bottom:var(--space-lg)}.active-agents-panel-header{display:flex;align-items:center;gap:var(--space-sm);font-size:13px;font-weight:600;color:var(--text-muted);margin-bottom:var(--space-md)}.active-agents-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:var(--space-md)}.live-agent-card{display:flex;flex-direction:column;gap:var(--space-sm);padding:var(--space-md);background:var(--card);border:1px solid var(--border);border-radius:var(--radius-md)}.live-agent-card-header{display:flex;align-items:center;justify-content:space-between;gap:var(--space-sm)}.live-agent-card-name{display:flex;align-items:center;gap:var(--space-sm);font-weight:600;font-size:13px}.live-agent-pulse{width:8px;height:8px;background:var(--color-success);border-radius:50%;animation:pulse 1.5s infinite}.live-agent-task{font-family:var(--font-mono)}.live-agent-card-transcript{font-family:var(--font-mono);font-size:11px;line-height:1.5;color:var(--text-muted);max-height:120px;overflow-y:auto}.live-agent-card-empty{font-style:italic;opacity:.6}.live-agent-card-line{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.live-agent-card-footer{display:flex;align-items:center;justify-content:space-between;font-size:calc(var(--space-sm) + var(--space-xs) * .75);color:var(--text-muted);padding-top:var(--space-sm);border-top:1px solid var(--border)}.live-agent-streaming-dot{color:var(--color-success);animation:pulse 1.5s infinite}@media(max-width:768px){.active-agents-grid{grid-template-columns:1fr}.live-agent-card{min-width:0;overflow:hidden}}.agent-token-stats-panel{margin-bottom:var(--space-lg);padding:var(--space-md);border:1px solid var(--border);border-radius:var(--radius-md);background:var(--card);box-shadow:var(--shadow-sm)}.agent-token-stats-panel__header{margin-bottom:var(--space-md)}.agent-token-stats-panel__title{margin:0;font-size:calc(var(--space-md) + var(--space-xs));font-weight:600;color:var(--text)}.agent-token-stats-panel__totals{display:grid;grid-template-columns:repeat(auto-fit,minmax(calc(var(--space-2xl) * 4),1fr));gap:var(--space-sm);margin-bottom:var(--space-md)}.agent-token-stats-panel__total-card{display:flex;flex-direction:column;gap:var(--space-xs);padding:var(--space-sm) var(--space-md);border:1px solid var(--border);border-radius:var(--radius-sm);background:var(--surface)}.agent-token-stats-panel__total-label{color:var(--text-muted);font-size:calc(var(--space-sm) + var(--space-xs));text-transform:uppercase;letter-spacing:calc(var(--space-xs) / 8)}.agent-token-stats-panel__total-value{color:var(--text);font-size:calc(var(--space-lg) + var(--space-xs));line-height:1.2;font-family:var(--font-mono);font-weight:700}.agent-token-stats-panel__table-wrapper{overflow-x:auto}.agent-token-stats-panel__table{width:100%;border-collapse:collapse}.agent-token-stats-panel__table th,.agent-token-stats-panel__table td{padding:var(--space-sm);border-bottom:1px solid var(--border);color:var(--text);text-align:right;font-size:calc(var(--space-sm) + var(--space-xs))}.agent-token-stats-panel__table thead th{text-transform:uppercase;letter-spacing:calc(var(--space-xs) / 8);color:var(--text-muted);font-weight:600}.agent-token-stats-panel__table th:first-child,.agent-token-stats-panel__table td:first-child{text-align:left}.agent-token-stats-panel__agent-cell{display:flex;flex-direction:column;gap:calc(var(--space-xs) * .5)}.agent-token-stats-panel__agent-name{color:var(--text)}.agent-token-stats-panel__agent-id{color:var(--text-muted);font-family:var(--font-mono);font-size:calc(var(--space-sm) + var(--space-xs) * .75);text-transform:none;letter-spacing:normal}.agent-token-stats-panel__total-cell{font-family:var(--font-mono);font-weight:700}.agent-token-stats-panel__empty{padding:var(--space-md);border:1px dashed var(--border);border-radius:var(--radius-sm);color:var(--text-muted);background:color-mix(in srgb,var(--surface) 85%,transparent);font-size:calc(var(--space-sm) + var(--space-xs))}@media(max-width:768px){.agent-token-stats-panel{padding:var(--space-sm)}.agent-token-stats-panel__totals{grid-template-columns:1fr}.agent-token-stats-panel__table th,.agent-token-stats-panel__table td{padding:var(--space-xs) var(--space-sm)}}.agent-dialog-overlay{position:fixed;inset:0;background:color-mix(in srgb,var(--bg) 60%,transparent);backdrop-filter:blur(var(--space-xs));display:flex;align-items:center;justify-content:center;z-index:100;padding:calc(var(--space-lg) + var(--space-xs))}.agent-dialog{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius-lg);width:100%;max-width:calc(var(--space-xl) * 21 + var(--space-md));max-height:calc(100vh - (var(--space-xl) + var(--space-lg)));display:flex;flex-direction:column;overflow:hidden;box-shadow:var(--shadow-lg)}.agent-dialog-header{display:flex;align-items:center;justify-content:space-between;padding:var(--space-lg) calc(var(--space-lg) + var(--space-xs));border-bottom:1px solid var(--border)}.agent-dialog-header-title{font-weight:600;font-size:calc(var(--space-md) + var(--space-xs) * .75)}.agent-dialog-body{flex:1;overflow-y:auto;padding:calc(var(--space-lg) + var(--space-xs))}.agent-dialog-footer{display:flex;align-items:center;justify-content:flex-end;gap:var(--space-sm);padding:var(--space-lg) calc(var(--space-lg) + var(--space-xs));border-top:1px solid var(--border)}.agent-dialog-steps{display:flex;gap:calc(var(--space-sm) - var(--space-xs) * .5);padding:var(--space-md) calc(var(--space-lg) + var(--space-xs));justify-content:center}.agent-dialog-step{width:calc(var(--space-2xl) + var(--space-sm));height:var(--space-xs);border-radius:calc(var(--space-xs) * .5);background:var(--border);transition:background var(--transition-fast)}.agent-dialog-step.active{background:var(--todo)}.agent-dialog-step.completed{background:var(--color-success)}.agent-dialog-field{margin-bottom:var(--space-lg)}.agent-dialog-field label{display:block;font-size:calc(var(--space-md) + var(--space-xs) * .25);font-weight:500;margin-bottom:calc(var(--space-sm) - var(--space-xs) * .5)}.agent-dialog-field .input,.agent-dialog-field .select{width:100%;box-sizing:border-box}.agent-dialog-section{margin-bottom:var(--space-lg)}.agent-dialog-section-header{font-size:calc(var(--space-sm) + var(--space-xs));font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:var(--text-muted);margin-bottom:var(--space-md);padding-bottom:var(--space-xs);border-bottom:1px solid var(--border)}.agent-dialog-tabs{display:grid;grid-template-columns:1fr 1fr;gap:var(--space-sm);margin-bottom:var(--space-lg)}.agent-dialog-tab{border:1px solid var(--border);border-radius:var(--radius-md);background:var(--card);color:var(--text-muted);padding:var(--space-sm) var(--space-md);font-size:calc(var(--space-sm) + var(--space-xs));font-weight:600;cursor:pointer;transition:border-color var(--transition-fast),background var(--transition-fast),color var(--transition-fast),box-shadow var(--transition-fast)}.agent-dialog-tab:hover{border-color:var(--todo);color:var(--text);background:var(--card-hover)}.agent-dialog-tab:focus-visible{outline:none;border-color:var(--todo);box-shadow:var(--focus-ring-strong)}.agent-dialog-tab.active{border-color:var(--todo);color:var(--text);background:color-mix(in srgb,var(--todo) 12%,transparent)}.agent-dialog-tab-panel{display:flex;flex-direction:column;gap:var(--space-sm)}.agent-runtime-mode-toggle{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:var(--space-sm)}.agent-runtime-mode-option{position:relative;margin:0}.agent-runtime-mode-option input{position:absolute;opacity:0;pointer-events:none}.agent-runtime-mode-option span{display:flex;align-items:center;justify-content:center;min-height:calc(var(--space-xl) + var(--space-lg));border:1px solid var(--border);border-radius:var(--radius-md);background:var(--card);color:var(--text-muted);font-size:calc(var(--space-sm) + var(--space-xs));font-weight:600;padding:var(--space-sm) var(--space-md);cursor:pointer;transition:border-color var(--transition-fast),background var(--transition-fast),color var(--transition-fast),box-shadow var(--transition-fast)}.agent-runtime-mode-option span:hover{border-color:var(--todo);color:var(--text);background:var(--card-hover)}.agent-runtime-mode-option input:focus-visible+span{outline:none;border-color:var(--todo);box-shadow:var(--focus-ring-strong)}.agent-runtime-mode-option--active span{border-color:var(--todo);color:var(--text);background:color-mix(in srgb,var(--todo) 12%,transparent)}.agent-role-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(calc(var(--space-xl) * 3 + var(--space-sm)),1fr));gap:var(--space-sm)}.agent-role-option{display:flex;flex-direction:column;align-items:center;padding:var(--space-md) var(--space-sm);background:var(--card);border:1px solid var(--border);border-radius:var(--radius-md);cursor:pointer;transition:border-color var(--transition-fast),background var(--transition-fast);font-family:var(--font-primary);color:var(--text)}.agent-role-option:hover{border-color:var(--text-muted);background:var(--card-hover)}.agent-role-option:focus-visible{outline:none;border-color:var(--todo);box-shadow:var(--focus-ring-strong)}.agent-role-option.selected{border-color:var(--todo);background:color-mix(in srgb,var(--todo) 12%,transparent);box-shadow:var(--focus-ring)}.agent-role-option-icon{font-size:calc(var(--space-lg) + var(--space-xs));line-height:1}.agent-role-option-label{font-size:calc(var(--space-sm) + var(--space-xs));margin-top:var(--space-xs)}.agent-dialog-icon-prefix{margin-right:calc(var(--space-sm) - var(--space-xs) * .5)}.agent-dialog-ai-generate{margin-top:var(--space-sm);border-top:1px solid var(--border);padding-top:var(--space-md)}.btn--ai-generate{width:100%;display:flex;align-items:center;justify-content:center;gap:calc(var(--space-sm) - var(--space-xs) * .5);background:var(--cta-bg, var(--todo));color:var(--cta-text, var(--text));font-weight:600;border:1px solid transparent}.btn--ai-generate:hover{opacity:.9;filter:brightness(1.1)}.agent-dialog-ai-hint{color:var(--text-muted);font-size:calc(var(--space-sm) + var(--space-xs) * .75);text-align:center;margin:calc(var(--space-sm) - var(--space-xs) * .5) 0 0}.agent-dialog-summary{display:flex;flex-direction:column;gap:var(--space-sm)}.agent-dialog-summary-row{display:flex;justify-content:space-between;align-items:center;padding:var(--space-sm) 0;border-bottom:1px solid color-mix(in srgb,var(--border) 50%,transparent)}.agent-dialog-summary-row:last-child{border-bottom:none}.agent-dialog-summary-row-label{color:var(--text-muted);font-size:13px}.agent-dialog-summary-row-label--fixed{flex:0 0 calc(var(--space-xl) * 4 - var(--space-xs) * 1.5)}.agent-dialog-summary-row-value{font-weight:600}.agent-dialog-summary-row-value--body{font-weight:400}.agent-dialog-summary--spaced{margin-bottom:var(--space-md)}.agent-dialog-summary-row-value--muted{font-style:italic;color:var(--text-muted)}.agent-dialog-summary-row-value--capitalize{text-transform:capitalize}.agent-presets{margin-bottom:var(--space-lg)}.agent-presets-header{font-size:calc(var(--space-md) + var(--space-xs) * .5);font-weight:600;color:var(--text-muted);margin-bottom:var(--space-md);display:flex;align-items:center;gap:var(--space-sm)}.agent-presets-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(calc(var(--space-xl) * 5 + var(--space-lg) + var(--space-xs)),1fr));gap:var(--space-sm);margin-bottom:var(--space-lg);max-height:calc(var(--space-xl) * 16 + var(--space-lg));overflow-y:auto}.agent-preset-card{display:flex;flex-direction:column;align-items:center;padding:var(--space-md);border:1px solid var(--border);border-radius:var(--radius-md);cursor:pointer;transition:border-color var(--transition-fast),background var(--transition-fast),box-shadow var(--transition-fast),transform var(--transition-fast);background:var(--bg-secondary);font-family:var(--font-primary);color:var(--text)}.agent-preset-card:hover{border-color:var(--todo);background:var(--bg-tertiary)}.agent-preset-card:focus-visible{outline:none;border-color:var(--todo);box-shadow:var(--focus-ring-strong)}.agent-preset-card.selected{border-color:var(--todo);background:color-mix(in srgb,var(--todo) 12%,transparent)}.agent-preset-icon{font-size:var(--space-xl);margin-bottom:var(--space-xs)}.agent-preset-name{font-size:calc(var(--space-md) + var(--space-xs) * .25);font-weight:500;text-align:center;margin-bottom:var(--space-xs)}.agent-preset-role{font-size:calc(var(--space-sm) + var(--space-xs) * .75);color:var(--text-muted);text-transform:capitalize}.agent-preset-description{font-size:calc(var(--space-sm) + var(--space-xs) * .75);color:var(--text-muted);line-height:1.3;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;margin-top:calc(var(--space-xs) * .5)}.agent-dialog-header-sparkle{margin-right:var(--space-sm)}.agent-dialog-error-banner{color:var(--color-error);font-size:calc(var(--space-md) + var(--space-xs) * .25);padding:var(--space-sm) var(--space-md);background:color-mix(in srgb,var(--color-error) 10%,transparent);border-radius:var(--radius-md);margin-bottom:var(--space-md)}.agent-dialog-textarea{resize:vertical}.agent-dialog-hint{font-size:calc(var(--space-sm) + var(--space-xs) * .75);color:var(--text-muted);margin-top:var(--space-xs);display:flex;justify-content:space-between;gap:var(--space-sm)}.agent-dialog-loading-center{display:flex;flex-direction:column;align-items:center;padding:var(--space-2xl) var(--space-lg);gap:var(--space-md)}.agent-dialog-spinner{width:var(--space-2xl);height:var(--space-2xl);border:calc(var(--space-xs) * .75) solid var(--border);border-top-color:var(--todo);border-radius:50%}.agent-dialog-loading-text{color:var(--text-muted);font-size:calc(var(--space-md) + var(--space-xs) * .25);margin:0}.agent-dialog-expand-btn{background:none;border:none;color:var(--todo);cursor:pointer;font-size:calc(var(--space-sm) + var(--space-xs));margin-left:var(--space-sm);padding:0}.agent-dialog-expand-btn:focus-visible{outline:none;box-shadow:var(--focus-ring-strong);border-radius:var(--radius-sm)}.agent-generation-prompt-box{background:var(--bg-secondary);border:1px solid var(--border);border-radius:var(--radius-md);padding:var(--space-md);font-size:calc(var(--space-sm) + var(--space-xs));font-family:var(--font-mono);line-height:1.5;white-space:pre-wrap;word-break:break-word;overflow:auto;position:relative}.agent-generation-prompt-box--collapsed{max-height:calc(var(--space-xl) * 6 + var(--space-sm) + var(--space-xs) * .5);overflow:hidden}.agent-generation-prompt-fade{position:absolute;bottom:0;left:0;right:0;height:calc(var(--space-lg) + var(--space-xl));background:linear-gradient(transparent,var(--bg-secondary));pointer-events:none}.agent-dialog-required{color:var(--color-error)}.agent-dialog-optional{color:var(--text-muted);font-weight:400}.agent-dialog-error{color:var(--color-error);font-size:calc(var(--space-md) + var(--space-xs) * .25);margin-top:var(--space-md)}.agent-dialog-info{color:var(--text-muted);font-size:calc(var(--space-md) + var(--space-xs) * .25);margin-top:0;margin-bottom:var(--space-md)}.agent-dialog-loading{color:var(--text-muted);font-size:calc(var(--space-md) + var(--space-xs) * .25);padding:var(--space-sm) 0}.skill-multiselect{display:flex;flex-direction:column;gap:var(--space-sm)}.skill-multiselect-label{font-size:13px;font-weight:500;color:var(--text-secondary)}.skill-multiselect-chips{display:flex;flex-wrap:wrap;gap:4px}.skill-chip{display:inline-flex;align-items:center;gap:4px;padding:2px 6px;background:var(--bg-tertiary);color:var(--text-secondary);border-radius:4px;font-size:12px;line-height:1.4}.skill-chip-name{max-width:150px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.skill-chip-remove{display:inline-flex;align-items:center;justify-content:center;width:14px;height:14px;padding:0;background:transparent;border:none;border-radius:2px;color:var(--text-muted);font-size:14px;line-height:1;cursor:pointer;transition:color .15s,background .15s}.skill-chip-remove:hover:not(:disabled){color:var(--text-primary);background:var(--bg-hover)}.skill-chip-remove:disabled{opacity:.5;cursor:not-allowed}.skill-multiselect-add{min-height:32px}.skill-multiselect-loading,.skill-multiselect-empty{font-size:13px;color:var(--text-muted);padding:4px 0}.skill-multiselect-dropdown{width:100%;font-size:13px}.agent-dialog-skills-hint{margin-top:4px;font-size:12px}.skill-badge-row{display:flex;flex-wrap:wrap;gap:4px}.skill-badge{display:inline-block;padding:2px 6px;background:var(--bg-tertiary);color:var(--text-secondary);border-radius:4px;font-size:11px;line-height:1.4}.badge-skill{background:var(--bg-tertiary);color:var(--text-secondary)}.agent-board-skills{display:flex;flex-wrap:wrap;gap:4px;padding:4px 8px}.skill-badge-sm{display:inline-block;padding:1px 5px;background:var(--bg-tertiary);color:var(--text-secondary);border-radius:3px;font-size:10px;line-height:1.4}.skill-badge-extra{background:var(--bg-secondary)}.agent-tree__skill{display:inline-block;padding:1px 5px;background:var(--bg-tertiary);color:var(--text-secondary);border-radius:3px;font-size:10px;line-height:1.4;margin-left:6px}.org-chart-node__skill{display:inline-block;padding:1px 5px;background:var(--bg-tertiary);color:var(--text-secondary);border-radius:3px;font-size:10px;line-height:1.4}.divider{color:var(--border)}.text-muted{color:var(--text-muted)}.link{display:inline-flex;align-items:center;gap:4px;color:var(--todo);text-decoration:none}.link:hover{text-decoration:underline}.text-secondary{color:var(--text-muted)}.agent-import-skills-section{margin-top:var(--space-lg)}.agent-import-skill-list{display:flex;flex-direction:column;gap:var(--space-xs);max-height:calc(var(--space-2xl) * 6);overflow-y:auto}.agent-import-skill-item{display:flex;align-items:flex-start;gap:var(--space-sm);padding:var(--space-sm) var(--space-md);border-radius:var(--radius-sm);background:var(--surface);border:1px solid var(--border)}.agent-import-skill-icon{flex-shrink:0}.agent-import-skill-details{display:flex;flex-direction:column;gap:var(--space-xs);min-width:0}.agent-import-skill-name{font-weight:500;color:var(--text)}.agent-import-skill-description{color:var(--text-muted)}.agent-import-empty{color:var(--text-muted);font-size:calc(var(--space-sm) + var(--space-xs) * 1.25);text-align:center;padding:var(--space-lg)}.agent-import-result{display:flex;flex-direction:column;align-items:center;text-align:center;gap:var(--space-sm)}.agent-import-result-icon{color:var(--color-success);margin-bottom:var(--space-xs)}.agent-import-result-title{margin:0;font-size:calc(var(--space-md) + var(--space-xs));font-weight:600}.agent-import-result-company{color:var(--text-muted);font-size:calc(var(--space-md) + var(--space-xs) * .25);margin:0 0 var(--space-md) 0}.agent-import-result-stats{display:flex;gap:var(--space-lg);margin-bottom:var(--space-md);padding:var(--space-md);background:var(--bg-secondary);border-radius:var(--radius-md)}.agent-import-result-stat{display:flex;align-items:center;gap:var(--space-sm);font-size:calc(var(--space-sm) + var(--space-xs) * 1.25);padding:var(--space-xs) var(--space-sm)}.agent-import-result-stat--success{color:var(--color-success)}.agent-import-result-stat--skipped{color:var(--color-warning)}.agent-import-result-stat--error{color:var(--color-error)}.agent-import-result-stat--success:before,.agent-import-result-stat--skipped:before,.agent-import-result-stat--error:before{content:"";width:var(--space-sm);height:var(--space-sm);border-radius:50%;flex-shrink:0}.agent-import-result-stat--success:before{background:var(--color-success)}.agent-import-result-stat--skipped:before{background:var(--color-warning)}.agent-import-result-stat--error:before{background:var(--color-error)}.agent-import-result-agents{display:flex;flex-direction:column;gap:var(--space-xs);width:100%;text-align:left}.agent-import-result-agent{display:flex;align-items:center;gap:calc(var(--space-sm) - var(--space-xs) * .5);font-size:calc(var(--space-md) + var(--space-xs) * .25);color:var(--color-success);padding:var(--space-xs) var(--space-sm);background:color-mix(in srgb,var(--color-success) 8%,transparent);border-radius:var(--radius-sm)}.agent-import-result-errors{display:flex;flex-direction:column;gap:var(--space-xs);width:100%;text-align:left;margin-top:var(--space-sm)}.agent-import-result-error{display:flex;align-items:center;gap:calc(var(--space-sm) - var(--space-xs) * .5);font-size:calc(var(--space-sm) + var(--space-xs));color:var(--color-error);padding:var(--space-xs) var(--space-sm);background:color-mix(in srgb,var(--color-error) 8%,transparent);border-radius:var(--radius-sm)}.agent-import-result-divider{align-self:stretch;height:1px;background:var(--border);margin:var(--space-md) 0}.agent-import-result-section-title{font-size:calc(var(--space-sm) + var(--space-xs));font-weight:600;color:var(--text-muted);text-transform:uppercase;letter-spacing:.04em;margin:0 0 var(--space-sm) 0}.agent-import-browse{margin-bottom:var(--space-md)}.agent-import-browse-header{display:flex;flex-direction:column;gap:var(--space-sm);margin-bottom:var(--space-md)}.agent-import-browse-search{position:relative;display:flex;align-items:center}.agent-import-browse-search-icon{position:absolute;left:var(--space-sm);color:var(--text-muted);pointer-events:none}.agent-import-browse-search-input{width:100%;padding:var(--space-sm) var(--space-sm) var(--space-sm) calc(var(--space-sm) + var(--space-lg));border:1px solid var(--border);border-radius:var(--radius-md);background:var(--surface);color:var(--text);font-size:calc(var(--space-md) + var(--space-xs) * .5)}.agent-import-browse-search-input:focus{outline:none;border-color:var(--todo);box-shadow:var(--focus-ring)}.agent-import-browse-selected{display:flex;align-items:center;gap:var(--space-sm);padding:var(--space-sm);background:color-mix(in srgb,var(--todo) 10%,transparent);border:1px solid color-mix(in srgb,var(--todo) 30%,transparent);border-radius:var(--radius-md)}.agent-import-browse-selected-label{color:var(--text-muted);font-size:calc(var(--space-sm) + var(--space-xs))}.agent-import-browse-selected-name{flex:1;font-weight:500;color:var(--todo)}.agent-import-browse-loading{display:flex;align-items:center;justify-content:center;gap:var(--space-sm);padding:var(--space-xl) 0;color:var(--text-muted)}.agent-import-browse-error{display:flex;align-items:center;gap:var(--space-sm);padding:var(--space-md);background:color-mix(in srgb,var(--color-error) 8%,transparent);border-radius:var(--radius-md);color:var(--color-error);font-size:calc(var(--space-md) + var(--space-xs) * .25)}.agent-import-browse-list{display:flex;flex-direction:column;gap:var(--space-xs);max-height:calc(var(--space-xl) * 12 + var(--space-md));overflow-y:auto;border:1px solid var(--border);border-radius:var(--radius-md);background:var(--surface)}.agent-import-browse-item{display:flex;flex-direction:column;gap:calc(var(--space-xs) * .5);padding:var(--space-sm);cursor:pointer;border-bottom:1px solid var(--border);transition:background-color var(--transition-fast)}.agent-import-browse-item:last-child{border-bottom:none}.agent-import-browse-item:hover{background:var(--surface-hover)}.agent-import-browse-item:focus-visible{outline:none;background:var(--surface-hover);box-shadow:var(--focus-ring)}.agent-import-browse-item--selected{background:color-mix(in srgb,var(--todo) 10%,transparent);border-color:var(--todo)}.agent-import-browse-item-header{display:flex;align-items:center;justify-content:space-between;gap:var(--space-sm)}.agent-import-browse-item-name{font-weight:500;color:var(--text)}.agent-import-browse-item-installs{font-size:calc(var(--space-sm) + var(--space-xs) * .75);color:var(--text-muted);white-space:nowrap}.agent-import-browse-item-tagline{font-size:calc(var(--space-sm) + var(--space-xs));color:var(--text-muted);line-height:1.4}.agent-import-browse-item-repo{font-size:calc(var(--space-sm) + var(--space-xs) * .75);color:var(--text-muted);font-family:var(--font-mono)}.agent-import-browse-empty{padding:var(--space-lg);text-align:center;color:var(--text-muted);font-size:calc(var(--space-sm) + var(--space-xs) * 1.25)}.agent-controls-actions{display:flex;gap:var(--space-sm)}.agent-global-controls{display:flex;align-items:center;padding:var(--space-md) var(--space-lg);background:var(--surface);border:1px solid var(--border);border-radius:var(--radius-md);margin-bottom:var(--space-lg)}.heartbeat-multiplier-label{font-weight:500;color:var(--text);white-space:nowrap}.agent-import-dialog{max-width:calc(var(--space-xl) * 23 + var(--space-sm))}.agent-import-description{color:var(--text-muted);font-size:calc(var(--space-sm) + var(--space-xs) * 1.25);margin:0 0 var(--space-md) 0;line-height:1.5}.agent-import-description code{background:var(--surface-hover);padding:calc(var(--space-xs) * .25) var(--space-xs);border-radius:calc(var(--radius-sm) - var(--space-xs) * .25);font-size:calc(var(--space-sm) + var(--space-xs))}.agent-import-file-upload{display:flex;align-items:center;gap:var(--space-sm);margin-bottom:var(--space-md)}.agent-import-file-input{display:none}.agent-import-upload-btn{display:inline-flex;align-items:center;gap:calc(var(--space-sm) - var(--space-xs) * .5)}.agent-import-file-hint{color:var(--text-muted);font-size:calc(var(--space-sm) + var(--space-xs))}.agent-import-divider{display:flex;align-items:center;gap:var(--space-md);margin-bottom:var(--space-md);color:var(--text-muted);font-size:calc(var(--space-sm) + var(--space-xs))}.agent-import-divider:before,.agent-import-divider:after{content:"";flex:1;height:1px;background:var(--border)}.agent-import-textarea{width:100%;min-height:calc(var(--space-xl) * 5);padding:var(--space-sm);border:1px solid var(--border);border-radius:var(--radius-md);background:var(--surface);color:var(--text);font-family:var(--font-mono);font-size:calc(var(--space-sm) + var(--space-xs));line-height:1.5;resize:vertical}.agent-import-textarea:focus{outline:none;border-color:var(--todo);box-shadow:var(--focus-ring)}.agent-import-company{display:flex;align-items:center;gap:var(--space-sm);margin-bottom:var(--space-md)}.agent-import-company-label{color:var(--text-muted);font-size:calc(var(--space-sm) + var(--space-xs));text-transform:uppercase;letter-spacing:.04em}.agent-import-company-name{font-weight:600;font-size:calc(var(--space-md) + var(--space-xs) * .5)}.agent-import-count{display:flex;align-items:center;gap:calc(var(--space-sm) - var(--space-xs) * .5);color:var(--text-muted);font-size:calc(var(--space-sm) + var(--space-xs) * 1.25);margin-bottom:var(--space-md)}.agent-import-selection-controls{display:flex;gap:var(--space-sm);flex-wrap:wrap;margin-bottom:var(--space-md)}.agent-import-agent-list{display:flex;flex-direction:column;gap:calc(var(--space-sm) - var(--space-xs) * .5);max-height:calc(var(--space-xl) * 11 + var(--space-lg));overflow-y:auto}.agent-import-agent-item{display:flex;align-items:center;gap:var(--space-sm);padding:var(--space-sm);border:1px solid var(--border);border-radius:var(--radius-md);background:var(--surface)}.agent-import-agent-icon{font-size:calc(var(--space-md) + var(--space-xs) * 1.5);line-height:1;flex-shrink:0}.agent-import-agent-details{display:flex;flex-direction:column;min-width:0}.agent-import-agent-name{font-weight:500;font-size:calc(var(--space-sm) + var(--space-xs) * 1.25)}.agent-import-agent-meta{color:var(--text-muted);font-size:calc(var(--space-sm) + var(--space-xs) * .75)}.agent-import-agent-title{color:var(--text-muted)}.agent-import-agent-role{text-transform:capitalize}.agent-import-agent-model{color:var(--text-muted)}