@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.
- package/dist/bin.js +4694 -2257
- package/dist/client/assets/{AgentDetailView-Du65-jDo.js → AgentDetailView-C3Xcrxnp.js} +3 -3
- package/dist/client/assets/{AgentDetailView-C1_lTTET.css → AgentDetailView-DIBOY8V-.css} +1 -1
- package/dist/client/assets/AgentsView-C2f7esMv.css +1 -0
- package/dist/client/assets/{AgentsView-BkgNEbNs.js → AgentsView-EjE4y4rM.js} +46 -46
- package/dist/client/assets/{ChatView-LDte0TdV.js → ChatView-DQLvKCYj.js} +1 -1
- package/dist/client/assets/DevServerView-CX7paFRQ.js +1 -0
- package/dist/client/assets/{DirectoryPicker-CSp3G115.js → DirectoryPicker-_cBPx6Nx.js} +1 -1
- package/dist/client/assets/{DocumentsView-DOe0E1WQ.js → DocumentsView-Wz33aYqp.js} +1 -1
- package/dist/client/assets/{InsightsView-DWZbS6z-.js → InsightsView-C7YPnS92.js} +1 -1
- package/dist/client/assets/MemoryView-DKQtFzFQ.js +2 -0
- package/dist/client/assets/{NodesView-DO9jxhM4.js → NodesView-CI4rUQC4.js} +4 -4
- package/dist/client/assets/{NodesView-BYVG2yY-.css → NodesView-DCoS6iYh.css} +1 -1
- package/dist/client/assets/{PiExtensionsManager-CfXZaWl7.js → PiExtensionsManager-BFmdKgHZ.js} +3 -3
- package/dist/client/assets/{PluginManager-r6CWD9u-.js → PluginManager-BGQU1IIw.js} +1 -1
- package/dist/client/assets/{RoadmapsView-CgAM3YfN.js → RoadmapsView-Cts3hoIS.js} +1 -1
- package/dist/client/assets/SettingsModal-D5hLoLXp.css +1 -0
- package/dist/client/assets/{SettingsModal-fmRtzH8z.js → SettingsModal-DXvBGZHf.js} +1 -1
- package/dist/client/assets/SettingsModal-DvRd0ZOE.js +31 -0
- package/dist/client/assets/SetupWizardModal-DRF5fOoR.css +1 -0
- package/dist/client/assets/{SetupWizardModal-oRNpImWR.js → SetupWizardModal-Y2ewEE8Y.js} +1 -1
- package/dist/client/assets/{SkillsView-BhwtL-0_.js → SkillsView-BXvrHzEZ.js} +1 -1
- package/dist/client/assets/{TodoView-CmhUBFNV.js → TodoView-NZHkv9YQ.js} +2 -2
- package/dist/client/assets/{folder-open-CrTagHrr.js → folder-open-Kh0ScTc5.js} +1 -1
- package/dist/client/assets/index-CWz44REw.css +1 -0
- package/dist/client/assets/index-D1gavMG-.js +656 -0
- package/dist/client/assets/{list-checks-DR26h_Io.js → list-checks-CvoT0bwU.js} +1 -1
- package/dist/client/assets/{star-CO_D42zy.js → star-BdfwSLBU.js} +1 -1
- package/dist/client/assets/{upload-BcrgS-iu.js → upload-Bx8Yk_7Q.js} +1 -1
- package/dist/client/assets/{users-4d8al5Sp.js → users-DgVaFEsz.js} +1 -1
- package/dist/client/brands/hermes-logo.svg +1 -1
- package/dist/client/index.html +2 -2
- package/dist/client/version.json +1 -1
- package/dist/extension.js +3459 -1459
- package/dist/pi-claude-cli/index.ts +27 -0
- package/dist/pi-claude-cli/package.json +1 -5
- package/dist/pi-claude-cli/src/__tests__/process-manager.test.ts +31 -9
- package/dist/pi-claude-cli/src/__tests__/provider.test.ts +122 -8
- package/dist/pi-claude-cli/src/process-manager.ts +25 -7
- package/dist/pi-claude-cli/src/provider.ts +31 -7
- package/dist/pi-claude-cli/src/types/cross-spawn.d.ts +7 -0
- package/package.json +4 -7
- package/skill/fusion/references/extension-tools.md +1 -0
- package/dist/client/assets/AgentsView-D12CuIFc.css +0 -1
- package/dist/client/assets/DevServerView-AXznq3jv.js +0 -6
- package/dist/client/assets/MemoryView-BxnlhBoa.js +0 -2
- package/dist/client/assets/SettingsModal-DPDQyWim.css +0 -1
- package/dist/client/assets/SettingsModal-GZdRt8fX.js +0 -31
- package/dist/client/assets/SetupWizardModal-BMa6p24b.css +0 -1
- package/dist/client/assets/index-BTeSa6vk.js +0 -646
- 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.
|
|
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
|
|
5
|
-
vi.mock("
|
|
6
|
-
|
|
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 "
|
|
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
|
|
6
|
-
vi.mock("
|
|
7
|
-
|
|
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 "
|
|
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 "
|
|
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
|
|
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
|
-
):
|
|
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
|
-
|
|
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 (!
|
|
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
|
-
|
|
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
|
|
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).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@runfusion/fusion",
|
|
3
|
-
"version": "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)}
|