@runfusion/fusion 0.12.0 → 0.13.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 (39) hide show
  1. package/dist/bin.js +526 -157
  2. package/dist/client/assets/{AgentDetailView-B20ApPe1.js → AgentDetailView-B7j297GT.js} +4 -4
  3. package/dist/client/assets/AgentsView-Dvf_xUkx.js +522 -0
  4. package/dist/client/assets/{AgentsView-Bkk-uBij.css → AgentsView-V5GhlBYu.css} +1 -1
  5. package/dist/client/assets/{ChatView-oPMFwmoc.js → ChatView-BgUt38ty.js} +1 -1
  6. package/dist/client/assets/{DevServerView-DQrVLbK5.js → DevServerView-C2qTJch7.js} +1 -1
  7. package/dist/client/assets/{DirectoryPicker-DVmy6sLM.js → DirectoryPicker-DRfhg9zz.js} +1 -1
  8. package/dist/client/assets/{DocumentsView-DHEv-Q2a.js → DocumentsView-j8ic1xUw.js} +1 -1
  9. package/dist/client/assets/{InsightsView-ByyY7GX7.js → InsightsView-CpAz3o0i.js} +3 -3
  10. package/dist/client/assets/{MemoryView-Udiu0u8R.js → MemoryView-BcQsi_JK.js} +2 -2
  11. package/dist/client/assets/{NodesView-CupS-GGc.js → NodesView-Bo_Yhr4N.js} +4 -4
  12. package/dist/client/assets/{PiExtensionsManager-DXs2xI8K.js → PiExtensionsManager-DHt2zFg8.js} +3 -3
  13. package/dist/client/assets/PluginManager-BQhBHWrB.js +1 -0
  14. package/dist/client/assets/{ResearchView-BG9Feaeb.js → ResearchView-CLyyqAWE.js} +1 -1
  15. package/dist/client/assets/{RoadmapsView-BTJtmBnF.js → RoadmapsView-tG7IdOoc.js} +2 -2
  16. package/dist/client/assets/{SettingsModal-eNCZiHa6.js → SettingsModal-CXUGeZ0_.js} +1 -1
  17. package/dist/client/assets/{SettingsModal-DZ_LaEhd.js → SettingsModal-UziTDnLh.js} +3 -3
  18. package/dist/client/assets/{SetupWizardModal-yf79TN1L.js → SetupWizardModal-BMJL6eNR.js} +1 -1
  19. package/dist/client/assets/{SkillMultiselect-DOj5vX4U.js → SkillMultiselect-ILMft-Kz.js} +1 -1
  20. package/dist/client/assets/{SkillsView-CgnCnikX.js → SkillsView-x4_YwBz6.js} +1 -1
  21. package/dist/client/assets/{TodoView-67BMyICY.js → TodoView-BBYcMbXE.js} +2 -2
  22. package/dist/client/assets/{folder-open-D11gjHGK.js → folder-open-DDdJt8aE.js} +1 -1
  23. package/dist/client/assets/index-B15xwijw.css +1 -0
  24. package/dist/client/assets/index-DmSs2FGE.js +661 -0
  25. package/dist/client/assets/{list-checks-CBzPc3GA.js → list-checks-DFxQ9biT.js} +1 -1
  26. package/dist/client/assets/{star-BWcRk8nt.js → star-BKs1bgJN.js} +1 -1
  27. package/dist/client/assets/{upload-91TM4ljC.js → upload-Bb5Pidne.js} +1 -1
  28. package/dist/client/assets/{users-BAsI___L.js → users-BImNn91Q.js} +1 -1
  29. package/dist/client/index.html +2 -2
  30. package/dist/client/version.json +1 -1
  31. package/dist/extension.js +70 -23
  32. package/dist/pi-claude-cli/package.json +1 -1
  33. package/dist/pi-claude-cli/src/__tests__/prompt-builder.test.ts +36 -0
  34. package/dist/pi-claude-cli/src/prompt-builder.ts +19 -28
  35. package/package.json +1 -1
  36. package/dist/client/assets/AgentsView-ChN1tgQ0.js +0 -522
  37. package/dist/client/assets/PluginManager-BCpiZf4_.js +0 -1
  38. package/dist/client/assets/index-BLn1R7Ob.css +0 -1
  39. package/dist/client/assets/index-CLAHcGnI.js +0 -656
@@ -1,4 +1,4 @@
1
- import{c}from"./index-CLAHcGnI.js";/**
1
+ import{c}from"./index-DmSs2FGE.js";/**
2
2
  * @license lucide-react v1.7.0 - ISC
3
3
  *
4
4
  * This source code is licensed under the ISC license.
@@ -1,4 +1,4 @@
1
- import{c as a}from"./index-CLAHcGnI.js";/**
1
+ import{c as a}from"./index-DmSs2FGE.js";/**
2
2
  * @license lucide-react v1.7.0 - ISC
3
3
  *
4
4
  * This source code is licensed under the ISC license.
@@ -1,4 +1,4 @@
1
- import{c as a}from"./index-CLAHcGnI.js";/**
1
+ import{c as a}from"./index-DmSs2FGE.js";/**
2
2
  * @license lucide-react v1.7.0 - ISC
3
3
  *
4
4
  * This source code is licensed under the ISC license.
@@ -1,4 +1,4 @@
1
- import{c as e}from"./index-CLAHcGnI.js";/**
1
+ import{c as e}from"./index-DmSs2FGE.js";/**
2
2
  * @license lucide-react v1.7.0 - ISC
3
3
  *
4
4
  * This source code is licensed under the ISC license.
@@ -92,11 +92,11 @@
92
92
  }
93
93
  })();
94
94
  </script>
95
- <script type="module" crossorigin src="/assets/index-CLAHcGnI.js"></script>
95
+ <script type="module" crossorigin src="/assets/index-DmSs2FGE.js"></script>
96
96
  <link rel="modulepreload" crossorigin href="/assets/vendor-react-K0fH_qHe.js">
97
97
  <link rel="modulepreload" crossorigin href="/assets/vendor-xterm-DzcZoU0P.js">
98
98
  <link rel="stylesheet" crossorigin href="/assets/vendor-xterm-LZoznX6r.css">
99
- <link rel="stylesheet" crossorigin href="/assets/index-BLn1R7Ob.css">
99
+ <link rel="stylesheet" crossorigin href="/assets/index-B15xwijw.css">
100
100
  </head>
101
101
  <body>
102
102
  <div id="root"></div>
@@ -1 +1 @@
1
- {"version":"mon1feb8-f023d703"}
1
+ {"version":"mon8x2sx-75359cb6"}
package/dist/extension.js CHANGED
@@ -79,6 +79,7 @@ var init_settings_schema = __esm({
79
79
  showGitHubStarButton: true,
80
80
  modelOnboardingComplete: void 0,
81
81
  useClaudeCli: void 0,
82
+ useDroidCli: void 0,
82
83
  // Global baseline lanes for per-role model selection
83
84
  executionGlobalProvider: void 0,
84
85
  executionGlobalModelId: void 0,
@@ -2704,7 +2705,7 @@ var init_db = __esm({
2704
2705
  "use strict";
2705
2706
  init_sqlite_adapter();
2706
2707
  init_types();
2707
- SCHEMA_VERSION = 55;
2708
+ SCHEMA_VERSION = 57;
2708
2709
  SCHEMA_SQL = `
2709
2710
  -- Tasks table with JSON columns for nested data
2710
2711
  CREATE TABLE IF NOT EXISTS tasks (
@@ -4388,6 +4389,23 @@ CREATE INDEX IF NOT EXISTS idxTodoItemsSortOrder ON todo_items(listId, sortOrder
4388
4389
  this.db.exec(`CREATE INDEX IF NOT EXISTS idxResearchExportsRunId ON research_exports(runId)`);
4389
4390
  });
4390
4391
  }
4392
+ if (version < 56) {
4393
+ this.applyMigration(56, () => {
4394
+ if (this.hasTable("chat_sessions")) {
4395
+ this.addColumnIfMissing("chat_sessions", "cliSessionFile", "TEXT");
4396
+ }
4397
+ });
4398
+ }
4399
+ if (version < 57) {
4400
+ this.applyMigration(57, () => {
4401
+ if (this.hasTable("ai_sessions")) {
4402
+ this.addColumnIfMissing("ai_sessions", "archived", "INTEGER DEFAULT 0");
4403
+ this.db.exec(
4404
+ "CREATE INDEX IF NOT EXISTS idxAiSessionsArchived ON ai_sessions(archived)"
4405
+ );
4406
+ }
4407
+ });
4408
+ }
4391
4409
  }
4392
4410
  /**
4393
4411
  * Run a single migration step inside a transaction and bump the version.
@@ -38004,7 +38022,7 @@ function sanitizeTitle(raw) {
38004
38022
  const firstLine = raw.split(/\r?\n/).map((l) => l.trim()).find((l) => l.length > 0);
38005
38023
  if (!firstLine) return null;
38006
38024
  let title = firstLine.replace(/^[-*]\s+/, "").replace(/^["'`]+|["'`]+$/g, "").trim();
38007
- title = title.replace(/^(?:title|subject|here(?:'s| is)(?: the)? title|generated title)\s*[:\-]\s*/i, "").trim();
38025
+ title = title.replace(/^(?:title|subject|here(?:'s| is)(?: the)? title|generated title)\s*[:-]\s*/i, "").trim();
38008
38026
  title = title.replace(/\*\*([^*]+)\*\*/g, "$1").replace(/__([^_]+)__/g, "$1").replace(/(?<![*\w])\*([^*]+)\*(?![*\w])/g, "$1").replace(/(?<![_\w])_([^_]+)_(?![_\w])/g, "$1");
38009
38027
  title = title.replace(/[.!?,;:]+$/, "").trim();
38010
38028
  if (!title) return null;
@@ -49952,7 +49970,8 @@ var init_chat_store = __esm({
49952
49970
  modelProvider: row.modelProvider ?? null,
49953
49971
  modelId: row.modelId ?? null,
49954
49972
  createdAt: row.createdAt,
49955
- updatedAt: row.updatedAt
49973
+ updatedAt: row.updatedAt,
49974
+ cliSessionFile: row.cliSessionFile ?? null
49956
49975
  };
49957
49976
  }
49958
49977
  /**
@@ -49989,7 +50008,8 @@ var init_chat_store = __esm({
49989
50008
  modelProvider: input.modelProvider ?? null,
49990
50009
  modelId: input.modelId ?? null,
49991
50010
  createdAt: now,
49992
- updatedAt: now
50011
+ updatedAt: now,
50012
+ cliSessionFile: null
49993
50013
  };
49994
50014
  this.db.prepare(`
49995
50015
  INSERT INTO chat_sessions (id, agentId, title, status, projectId, modelProvider, modelId, createdAt, updatedAt)
@@ -50147,6 +50167,21 @@ var init_chat_store = __esm({
50147
50167
  archiveSession(id) {
50148
50168
  return this.updateSession(id, { status: "archived" });
50149
50169
  }
50170
+ /**
50171
+ * Persist the pi/Claude CLI session file path for a chat. Called once,
50172
+ * after the SessionManager for the chat first creates its on-disk file,
50173
+ * so subsequent turns can reopen it via SessionManager.open.
50174
+ *
50175
+ * Does not bump updatedAt or emit events — this is internal plumbing,
50176
+ * not a user-visible state change.
50177
+ *
50178
+ * @param id - Session ID
50179
+ * @param cliSessionFile - Absolute path to the session file, or null to clear
50180
+ */
50181
+ setCliSessionFile(id, cliSessionFile) {
50182
+ this.db.prepare("UPDATE chat_sessions SET cliSessionFile = ? WHERE id = ?").run(cliSessionFile, id);
50183
+ this.db.bumpLastModified();
50184
+ }
50150
50185
  /**
50151
50186
  * Delete a chat session and all its messages.
50152
50187
  * Messages are cascade-deleted via foreign key constraint.
@@ -83696,25 +83731,21 @@ async function ensureNtfyHelpersReady() {
83696
83731
  if (planningNtfyHelpers) {
83697
83732
  return;
83698
83733
  }
83699
- try {
83700
- const engine = await Promise.resolve().then(() => (init_src2(), src_exports2));
83701
- const hasNotificationService = "NotificationService" in engine && typeof engine.NotificationService === "function";
83702
- const hasAllHelpers = "isNtfyEventEnabled" in engine && "buildNtfyClickUrl" in engine && "sendNtfyNotification" in engine && typeof engine.isNtfyEventEnabled === "function" && typeof engine.buildNtfyClickUrl === "function" && typeof engine.sendNtfyNotification === "function";
83703
- if (!hasAllHelpers) {
83704
- return;
83705
- }
83706
- planningNtfyHelpers = {
83707
- isNtfyEventEnabled: engine.isNtfyEventEnabled,
83708
- buildNtfyClickUrl: engine.buildNtfyClickUrl,
83709
- sendNtfyNotification: engine.sendNtfyNotification
83710
- };
83711
- if (hasNotificationService) {
83712
- diagnostics.info(
83713
- "NotificationService abstraction detected in engine",
83714
- { operation: "notification-service-detection" }
83715
- );
83716
- }
83717
- } catch {
83734
+ const hasNotificationService = "NotificationService" in src_exports2 && typeof NotificationService === "function";
83735
+ const hasAllHelpers = "isNtfyEventEnabled" in src_exports2 && "buildNtfyClickUrl" in src_exports2 && "sendNtfyNotification" in src_exports2 && typeof isNtfyEventEnabled === "function" && typeof buildNtfyClickUrl === "function" && typeof sendNtfyNotification === "function";
83736
+ if (!hasAllHelpers) {
83737
+ return;
83738
+ }
83739
+ planningNtfyHelpers = {
83740
+ isNtfyEventEnabled,
83741
+ buildNtfyClickUrl,
83742
+ sendNtfyNotification
83743
+ };
83744
+ if (hasNotificationService) {
83745
+ diagnostics.info(
83746
+ "NotificationService abstraction detected in engine",
83747
+ { operation: "notification-service-detection" }
83748
+ );
83718
83749
  }
83719
83750
  }
83720
83751
  function safeParseJson(text, fallback, options) {
@@ -84504,6 +84535,7 @@ var init_planning = __esm({
84504
84535
  init_sse_buffer();
84505
84536
  init_ai_session_diagnostics();
84506
84537
  init_src2();
84538
+ init_src2();
84507
84539
  createFnAgent4 = createFnAgent2;
84508
84540
  diagnostics = createSessionDiagnostics("planning");
84509
84541
  PLANNING_SYSTEM_PROMPT = `You are a planning assistant for the fn task board system.
@@ -92836,6 +92868,7 @@ var init_register_agent_core_routes = __esm({
92836
92868
  "use strict";
92837
92869
  init_src();
92838
92870
  init_api_error();
92871
+ init_src2();
92839
92872
  }
92840
92873
  });
92841
92874
 
@@ -92848,10 +92881,13 @@ var init_register_agent_runtime_routes = __esm({
92848
92881
  });
92849
92882
 
92850
92883
  // ../dashboard/src/routes/register-agent-reflection-rating-routes.ts
92884
+ var AgentReflectionServiceBinding;
92851
92885
  var init_register_agent_reflection_rating_routes = __esm({
92852
92886
  "../dashboard/src/routes/register-agent-reflection-rating-routes.ts"() {
92853
92887
  "use strict";
92854
92888
  init_api_error();
92889
+ init_src2();
92890
+ AgentReflectionServiceBinding = "AgentReflectionService" in src_exports2 && typeof AgentReflectionService === "function" ? AgentReflectionService : void 0;
92855
92891
  }
92856
92892
  });
92857
92893
 
@@ -92994,12 +93030,20 @@ var init_claude_cli_probe = __esm({
92994
93030
  }
92995
93031
  });
92996
93032
 
93033
+ // ../dashboard/src/droid-cli-probe.ts
93034
+ var init_droid_cli_probe = __esm({
93035
+ "../dashboard/src/droid-cli-probe.ts"() {
93036
+ "use strict";
93037
+ }
93038
+ });
93039
+
92997
93040
  // ../dashboard/src/routes/register-auth-routes.ts
92998
93041
  var init_register_auth_routes = __esm({
92999
93042
  "../dashboard/src/routes/register-auth-routes.ts"() {
93000
93043
  "use strict";
93001
93044
  init_src();
93002
93045
  init_claude_cli_probe();
93046
+ init_droid_cli_probe();
93003
93047
  init_api_error();
93004
93048
  init_usage();
93005
93049
  init_project_store_resolver();
@@ -93860,6 +93904,7 @@ var init_insights_routes = __esm({
93860
93904
  "../dashboard/src/insights-routes.ts"() {
93861
93905
  "use strict";
93862
93906
  init_api_error();
93907
+ init_src2();
93863
93908
  }
93864
93909
  });
93865
93910
 
@@ -97725,6 +97770,7 @@ var init_terminal_websocket_diagnostics = __esm({
97725
97770
 
97726
97771
  // ../dashboard/src/chat.ts
97727
97772
  import { EventEmitter as EventEmitter30 } from "node:events";
97773
+ import { SessionManager as SessionManager3 } from "@mariozechner/pi-coding-agent";
97728
97774
  var defaultDiagnostics, _diagnostics, diagnostics7, RATE_LIMIT_WINDOW_MS6, MAX_REFERENCED_FILE_SIZE, ChatStreamManager, chatStreamManager;
97729
97775
  var init_chat = __esm({
97730
97776
  "../dashboard/src/chat.ts"() {
@@ -97732,6 +97778,7 @@ var init_chat = __esm({
97732
97778
  init_src();
97733
97779
  init_sse_buffer();
97734
97780
  init_src2();
97781
+ init_src2();
97735
97782
  defaultDiagnostics = {
97736
97783
  log(message, ...args) {
97737
97784
  console.log(`[chat] ${message}`, ...args);
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fusion/pi-claude-cli",
3
- "version": "0.12.0",
3
+ "version": "0.13.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,
@@ -1145,6 +1145,42 @@ describe("buildResumePrompt", () => {
1145
1145
  expect(buildResumePrompt(context)).toBe("Hello from blocks");
1146
1146
  });
1147
1147
 
1148
+ // Regression: multi-iteration tool loops re-anchor on the LAST assistant
1149
+ // turn, not the (only) user message at index 0. Previously this dumped the
1150
+ // entire transcript into a "user" prompt every iteration, ballooning the
1151
+ // resumed session.
1152
+ it("returns ONLY the trailing tool result during a multi-iteration tool loop", () => {
1153
+ const context = {
1154
+ messages: [
1155
+ { role: "user", content: "Find foo" },
1156
+ {
1157
+ role: "assistant",
1158
+ content: [{ type: "toolCall", name: "find", arguments: { pattern: "foo" } }],
1159
+ },
1160
+ { role: "toolResult", toolName: "find", content: "no matches (turn 1)" },
1161
+ {
1162
+ role: "assistant",
1163
+ content: [{ type: "toolCall", name: "find", arguments: { pattern: "foo" } }],
1164
+ },
1165
+ { role: "toolResult", toolName: "find", content: "no matches (turn 2)" },
1166
+ ],
1167
+ };
1168
+ const result = buildResumePrompt(context) as string;
1169
+ expect(result).toContain("no matches (turn 2)");
1170
+ expect(result).not.toContain("no matches (turn 1)");
1171
+ expect(result).not.toContain("Find foo");
1172
+ });
1173
+
1174
+ it("returns empty string mid-loop when only an assistant turn exists since the last delta", () => {
1175
+ const context = {
1176
+ messages: [
1177
+ { role: "user", content: "Hi" },
1178
+ { role: "assistant", content: "Working..." },
1179
+ ],
1180
+ };
1181
+ expect(buildResumePrompt(context)).toBe("");
1182
+ });
1183
+
1148
1184
  it("handles images in the final user message by returning ContentBlock[]", () => {
1149
1185
  const context = {
1150
1186
  messages: [
@@ -166,44 +166,36 @@ function buildCustomToolResultPrompt(messages: PiMessage[]): string | null {
166
166
  /**
167
167
  * Build a prompt for a resumed session.
168
168
  *
169
- * When resuming via --resume, the CLI already has the full conversation history.
170
- * We only need to send the new content since the last turn: the last assistant
171
- * response's tool results (if any) followed by the latest user message.
169
+ * When resuming via --resume, the CLI already has the full conversation history
170
+ * up through (and including) the most recent assistant turn that it produced.
171
+ * We only need to send the *delta* since that turn: any trailing tool results
172
+ * for the last assistant tool_use, and/or a new user message.
172
173
  *
173
- * For tool_use flows: pi sends [user, assistant(toolCall), toolResult, ...]
174
- * We need to include tool results so the resumed session sees them, plus the
175
- * final user message.
174
+ * Why anchor on the last assistant message (not the last user message)?
175
+ * Pi's tool-use loop appends `[user, assistant(toolUse), toolResult,
176
+ * assistant(toolUse), toolResult, ...]` — the only `user` entry stays at index
177
+ * 0 across many provider invocations. Anchoring on the last user message and
178
+ * walking forward (the prior implementation) re-sent the entire transcript on
179
+ * every tool-loop iteration, so each --resume turn appended a duplicate of the
180
+ * original query plus a growing stack of tool results to the on-disk session.
176
181
  *
177
- * Falls back to full prompt if the message structure is unexpected.
182
+ * Returns "" when there's nothing new to send (e.g. only an assistant message
183
+ * exists in the context — can happen mid-shutdown).
178
184
  */
179
185
  export function buildResumePrompt(context: PiContext): string | AnthropicContentBlock[] {
180
186
  const messages = context.messages;
181
187
  if (messages.length === 0) return "";
182
188
 
183
- // Find the last user message
184
- const finalUserIndex = findFinalUserMessageIndex(messages);
185
- if (finalUserIndex < 0) return "";
186
-
187
- // Collect new messages: everything from the last assistant turn onwards
188
- // (tool results from the last assistant + the new user message)
189
- const newMessages: PiMessage[] = [];
190
-
191
- // Walk backwards from finalUserIndex to find where new content starts.
192
- // Include trailing toolResult messages that follow the last assistant turn.
193
- let startIdx = finalUserIndex;
194
- for (let i = finalUserIndex - 1; i >= 0; i--) {
195
- if (messages[i].role === "toolResult") {
196
- startIdx = i;
197
- } else {
189
+ let lastAssistantIdx = -1;
190
+ for (let i = messages.length - 1; i >= 0; i--) {
191
+ if (messages[i].role === "assistant") {
192
+ lastAssistantIdx = i;
198
193
  break;
199
194
  }
200
195
  }
196
+ const newMessages = messages.slice(lastAssistantIdx + 1);
197
+ if (newMessages.length === 0) return "";
201
198
 
202
- for (let i = startIdx; i < messages.length; i++) {
203
- newMessages.push(messages[i]);
204
- }
205
-
206
- // If there are only tool results + one user message, build a combined prompt
207
199
  const parts: string[] = [];
208
200
  for (const msg of newMessages) {
209
201
  if (msg.role === "toolResult") {
@@ -217,7 +209,6 @@ export function buildResumePrompt(context: PiContext): string | AnthropicContent
217
209
  }
218
210
  parts.push(toolResultContentToText(msg.content));
219
211
  } else if (msg.role === "user") {
220
- // Check for images in the final user message
221
212
  if (contentHasImages(msg.content)) {
222
213
  const textSoFar = parts.join("\n");
223
214
  const userContent = buildFinalUserContent(msg.content);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@runfusion/fusion",
3
- "version": "0.12.0",
3
+ "version": "0.13.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",