@runfusion/fusion 0.8.0 → 0.8.1

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 (32) hide show
  1. package/dist/bin.js +809 -533
  2. package/dist/client/assets/{AgentDetailView-C3Xcrxnp.js → AgentDetailView-CLzxf6Z7.js} +1 -1
  3. package/dist/client/assets/{AgentsView-EjE4y4rM.js → AgentsView-CXaYJX_G.js} +3 -3
  4. package/dist/client/assets/{ChatView-DQLvKCYj.js → ChatView-iXxGAaN1.js} +1 -1
  5. package/dist/client/assets/{DevServerView-CX7paFRQ.js → DevServerView-BeXfFkF4.js} +1 -1
  6. package/dist/client/assets/{DirectoryPicker-_cBPx6Nx.js → DirectoryPicker-BMn5fjn9.js} +1 -1
  7. package/dist/client/assets/{DocumentsView-Wz33aYqp.js → DocumentsView-CjrtI3TX.js} +1 -1
  8. package/dist/client/assets/{InsightsView-C7YPnS92.js → InsightsView-BkfQ-TV1.js} +1 -1
  9. package/dist/client/assets/{MemoryView-DKQtFzFQ.js → MemoryView-1G0zWu1i.js} +1 -1
  10. package/dist/client/assets/{NodesView-CI4rUQC4.js → NodesView-Bn_1R73N.js} +1 -1
  11. package/dist/client/assets/{PiExtensionsManager-BFmdKgHZ.js → PiExtensionsManager-CqGOtQnR.js} +1 -1
  12. package/dist/client/assets/{PluginManager-BGQU1IIw.js → PluginManager-CM5QGvSG.js} +1 -1
  13. package/dist/client/assets/{RoadmapsView-Cts3hoIS.js → RoadmapsView-B4VnQP83.js} +1 -1
  14. package/dist/client/assets/{SettingsModal-DvRd0ZOE.js → SettingsModal-BiLA-BeG.js} +4 -4
  15. package/dist/client/assets/{SettingsModal-DXvBGZHf.js → SettingsModal-C3LckzfT.js} +1 -1
  16. package/dist/client/assets/SetupWizardModal-Bk_8HfLm.js +1 -0
  17. package/dist/client/assets/{SkillsView-BXvrHzEZ.js → SkillsView-CRvqF8P1.js} +1 -1
  18. package/dist/client/assets/{TodoView-NZHkv9YQ.js → TodoView-Vzui5Eha.js} +1 -1
  19. package/dist/client/assets/{folder-open-Kh0ScTc5.js → folder-open-CMF89prE.js} +1 -1
  20. package/dist/client/assets/{index-D1gavMG-.js → index-B8kH5y4Q.js} +3 -3
  21. package/dist/client/assets/{index-CWz44REw.css → index-D2fXOwWF.css} +1 -1
  22. package/dist/client/assets/{list-checks-CvoT0bwU.js → list-checks-M95d1uAy.js} +1 -1
  23. package/dist/client/assets/{star-BdfwSLBU.js → star-DHhJD6ow.js} +1 -1
  24. package/dist/client/assets/{upload-Bx8Yk_7Q.js → upload-CEq8jic8.js} +1 -1
  25. package/dist/client/assets/{users-DgVaFEsz.js → users-CUA8Tv-d.js} +1 -1
  26. package/dist/client/index.html +2 -2
  27. package/dist/client/version.json +1 -1
  28. package/dist/extension.js +187 -41
  29. package/dist/pi-claude-cli/package.json +4 -1
  30. package/dist/pi-claude-cli/src/process-manager.ts +1 -1
  31. package/package.json +1 -1
  32. package/dist/client/assets/SetupWizardModal-Y2ewEE8Y.js +0 -1
@@ -1,4 +1,4 @@
1
- import{c}from"./index-D1gavMG-.js";/**
1
+ import{c}from"./index-B8kH5y4Q.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-D1gavMG-.js";/**
1
+ import{c as a}from"./index-B8kH5y4Q.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-D1gavMG-.js";/**
1
+ import{c as a}from"./index-B8kH5y4Q.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-D1gavMG-.js";/**
1
+ import{c as e}from"./index-B8kH5y4Q.js";/**
2
2
  * @license lucide-react v1.7.0 - ISC
3
3
  *
4
4
  * This source code is licensed under the ISC license.
@@ -78,11 +78,11 @@
78
78
  }
79
79
  })();
80
80
  </script>
81
- <script type="module" crossorigin src="/assets/index-D1gavMG-.js"></script>
81
+ <script type="module" crossorigin src="/assets/index-B8kH5y4Q.js"></script>
82
82
  <link rel="modulepreload" crossorigin href="/assets/vendor-react-K0fH_qHe.js">
83
83
  <link rel="modulepreload" crossorigin href="/assets/vendor-xterm-DzcZoU0P.js">
84
84
  <link rel="stylesheet" crossorigin href="/assets/vendor-xterm-LZoznX6r.css">
85
- <link rel="stylesheet" crossorigin href="/assets/index-CWz44REw.css">
85
+ <link rel="stylesheet" crossorigin href="/assets/index-D2fXOwWF.css">
86
86
  </head>
87
87
  <body>
88
88
  <div id="root"></div>
@@ -1 +1 @@
1
- {"version":"mojfmkla-bfbaa5e5"}
1
+ {"version":"mojjkega-50ad5fc1"}
package/dist/extension.js CHANGED
@@ -2432,7 +2432,7 @@ var init_db = __esm({
2432
2432
  "use strict";
2433
2433
  init_sqlite_adapter();
2434
2434
  init_types();
2435
- SCHEMA_VERSION = 50;
2435
+ SCHEMA_VERSION = 51;
2436
2436
  SCHEMA_SQL = `
2437
2437
  -- Tasks table with JSON columns for nested data
2438
2438
  CREATE TABLE IF NOT EXISTS tasks (
@@ -3965,6 +3965,13 @@ CREATE INDEX IF NOT EXISTS idxTodoItemsSortOrder ON todo_items(listId, sortOrder
3965
3965
  this.addColumnIfMissing("tasks", "effectiveNodeSource", "TEXT");
3966
3966
  });
3967
3967
  }
3968
+ if (version < 51) {
3969
+ this.applyMigration(51, () => {
3970
+ if (this.hasTable("chat_messages")) {
3971
+ this.addColumnIfMissing("chat_messages", "attachments", "TEXT");
3972
+ }
3973
+ });
3974
+ }
3968
3975
  }
3969
3976
  /**
3970
3977
  * Run a single migration step inside a transaction and bump the version.
@@ -48501,6 +48508,7 @@ var init_chat_store = __esm({
48501
48508
  content: row.content,
48502
48509
  thinkingOutput: row.thinkingOutput ?? null,
48503
48510
  metadata: fromJson(row.metadata) ?? null,
48511
+ attachments: fromJson(row.attachments) ?? void 0,
48504
48512
  createdAt: row.createdAt
48505
48513
  };
48506
48514
  }
@@ -48719,11 +48727,12 @@ var init_chat_store = __esm({
48719
48727
  content: input.content,
48720
48728
  thinkingOutput: input.thinkingOutput ?? null,
48721
48729
  metadata: input.metadata ?? null,
48730
+ attachments: input.attachments,
48722
48731
  createdAt: now
48723
48732
  };
48724
48733
  this.db.prepare(`
48725
- INSERT INTO chat_messages (id, sessionId, role, content, thinkingOutput, metadata, createdAt)
48726
- VALUES (?, ?, ?, ?, ?, ?, ?)
48734
+ INSERT INTO chat_messages (id, sessionId, role, content, thinkingOutput, metadata, attachments, createdAt)
48735
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
48727
48736
  `).run(
48728
48737
  message.id,
48729
48738
  message.sessionId,
@@ -48731,6 +48740,7 @@ var init_chat_store = __esm({
48731
48740
  message.content,
48732
48741
  message.thinkingOutput,
48733
48742
  toJsonNullable(message.metadata),
48743
+ toJsonNullable(message.attachments),
48734
48744
  message.createdAt
48735
48745
  );
48736
48746
  this.db.prepare("UPDATE chat_sessions SET updatedAt = ? WHERE id = ?").run(now, sessionId);
@@ -48738,6 +48748,28 @@ var init_chat_store = __esm({
48738
48748
  this.emit("chat:message:added", message);
48739
48749
  return message;
48740
48750
  }
48751
+ /**
48752
+ * Append a file attachment metadata record to an existing message.
48753
+ */
48754
+ addMessageAttachment(sessionId, messageId, attachment) {
48755
+ const message = this.getMessage(messageId);
48756
+ if (!message || message.sessionId !== sessionId) {
48757
+ throw new Error(`Message ${messageId} not found in session ${sessionId}`);
48758
+ }
48759
+ const updatedAttachments = [...message.attachments ?? [], attachment];
48760
+ this.db.prepare(`
48761
+ UPDATE chat_messages
48762
+ SET attachments = ?
48763
+ WHERE id = ?
48764
+ `).run(toJsonNullable(updatedAttachments), messageId);
48765
+ const updated = this.getMessage(messageId);
48766
+ if (!updated) {
48767
+ throw new Error(`Failed to update message ${messageId}`);
48768
+ }
48769
+ this.db.bumpLastModified();
48770
+ this.emit("chat:message:updated", updated);
48771
+ return updated;
48772
+ }
48741
48773
  /**
48742
48774
  * Get messages for a chat session with optional filtering.
48743
48775
  *
@@ -55407,21 +55439,31 @@ ${failureContext.output.slice(0, VERIFICATION_LOG_MAX_CHARS)}
55407
55439
  return false;
55408
55440
  }
55409
55441
  }
55410
- async function amendMergeCommitWithFixes(rootDir, taskId, authorArg) {
55442
+ function resetMergeWithWarn(rootDir, taskId, label) {
55443
+ try {
55444
+ execSync("git reset --merge", { cwd: rootDir, stdio: "pipe" });
55445
+ } catch (err) {
55446
+ const msg = err instanceof Error ? err.message : String(err);
55447
+ mergerLog.warn(`${taskId}: git reset --merge cleanup failed during ${label}: ${msg}`);
55448
+ }
55449
+ }
55450
+ function buildDeterministicMergeMessage(params) {
55451
+ const { taskId, branch, commitLog, includeTaskId } = params;
55452
+ const prefix = includeTaskId ? `feat(${taskId})` : "feat";
55453
+ const subject = `${prefix}: merge ${branch}`;
55454
+ const body = commitLog && commitLog.trim().length > 0 ? commitLog.trim() : `- merge ${branch}`;
55455
+ const escape = (s) => s.replace(/(["\\$`])/g, "\\$1");
55456
+ return {
55457
+ subjectArg: `-m "${escape(subject)}"`,
55458
+ bodyArg: `-m "${escape(body)}"`
55459
+ };
55460
+ }
55461
+ async function commitOrAmendMergeWithFixes(rootDir, taskId, branch, commitLog, includeTaskId, preAttemptHeadSha, authorArg) {
55411
55462
  try {
55412
- const { stdout: stagedFiles } = await execAsync2("git diff --cached --name-only", {
55413
- cwd: rootDir,
55414
- encoding: "utf-8"
55415
- });
55416
55463
  const { stdout: unstagedFiles } = await execAsync2("git diff --name-only", {
55417
55464
  cwd: rootDir,
55418
55465
  encoding: "utf-8"
55419
55466
  });
55420
- const hasChanges = stagedFiles.trim().length > 0 || unstagedFiles.trim().length > 0;
55421
- if (!hasChanges) {
55422
- mergerLog.log(`${taskId}: no changes to amend after verification fix`);
55423
- return false;
55424
- }
55425
55467
  if (unstagedFiles.trim().length > 0) {
55426
55468
  await execAsync2("git add -A", { cwd: rootDir });
55427
55469
  }
@@ -55429,12 +55471,10 @@ async function amendMergeCommitWithFixes(rootDir, taskId, authorArg) {
55429
55471
  cwd: rootDir,
55430
55472
  encoding: "utf-8"
55431
55473
  });
55432
- const gitlinkPaths = [];
55433
55474
  for (const line of staged.split("\n")) {
55434
55475
  const match = line.match(/^:\d{6} 160000 [^\t]+\t(.+)$/);
55435
- if (match) gitlinkPaths.push(match[1]);
55436
- }
55437
- for (const path2 of gitlinkPaths) {
55476
+ if (!match) continue;
55477
+ const path2 = match[1];
55438
55478
  mergerLog.warn(`${taskId}: refusing to stage gitlink "${path2}" (project uses no submodules \u2014 likely a nested worktree). Unstaging.`);
55439
55479
  try {
55440
55480
  await execAsync2(`git reset HEAD -- "${path2}"`, { cwd: rootDir });
@@ -55447,15 +55487,43 @@ async function amendMergeCommitWithFixes(rootDir, taskId, authorArg) {
55447
55487
  cwd: rootDir,
55448
55488
  encoding: "utf-8"
55449
55489
  });
55450
- if (finalStaged.trim().length > 0) {
55451
- await execAsync2(`git commit --amend --no-edit${authorArg}`, { cwd: rootDir });
55452
- mergerLog.log(`${taskId}: amended merge commit with verification fixes`);
55490
+ const hasStaged = finalStaged.trim().length > 0;
55491
+ const { stdout: currentHeadOut } = await execAsync2("git rev-parse HEAD", {
55492
+ cwd: rootDir,
55493
+ encoding: "utf-8"
55494
+ });
55495
+ const currentHead = currentHeadOut.trim();
55496
+ const headMoved = currentHead !== preAttemptHeadSha;
55497
+ if (!hasStaged && !headMoved) {
55498
+ mergerLog.warn(
55499
+ `${taskId}: refusing to record merge \u2014 no commit was created and no changes are staged. This usually means the AI agent never ran git commit and the in-merge fix had nothing to add.`
55500
+ );
55501
+ return false;
55502
+ }
55503
+ const { subjectArg, bodyArg } = buildDeterministicMergeMessage({
55504
+ taskId,
55505
+ branch,
55506
+ commitLog,
55507
+ includeTaskId
55508
+ });
55509
+ const trailerArg = buildTaskIdTrailerArg(taskId);
55510
+ if (!headMoved) {
55511
+ await execAsync2(
55512
+ `git commit ${subjectArg} ${bodyArg}${trailerArg}${authorArg}`,
55513
+ { cwd: rootDir }
55514
+ );
55515
+ mergerLog.log(`${taskId}: created fresh merge commit after verification fix (no prior commit to amend)`);
55453
55516
  return true;
55454
55517
  }
55455
- return false;
55518
+ await execAsync2(
55519
+ `git commit --amend ${subjectArg} ${bodyArg}${trailerArg}${authorArg}`,
55520
+ { cwd: rootDir }
55521
+ );
55522
+ mergerLog.log(`${taskId}: amended merge commit with verification fixes (deterministic message)`);
55523
+ return true;
55456
55524
  } catch (err) {
55457
55525
  const errorMessage = err instanceof Error ? err.message : String(err);
55458
- mergerLog.warn(`${taskId}: failed to amend merge commit: ${errorMessage}`);
55526
+ mergerLog.warn(`${taskId}: failed to finalize merge commit: ${errorMessage}`);
55459
55527
  return false;
55460
55528
  }
55461
55529
  }
@@ -55706,6 +55774,11 @@ function getCommitAuthorArg(settings) {
55706
55774
  const email = settings.commitAuthorEmail || "noreply@runfusion.ai";
55707
55775
  return ` --author="${name} <${email}>"`;
55708
55776
  }
55777
+ function buildSourceIssueRef(sourceIssue) {
55778
+ if (!sourceIssue || sourceIssue.provider !== "github") return "";
55779
+ if (!sourceIssue.repository || !sourceIssue.issueNumber) return "";
55780
+ return `${sourceIssue.repository}#${sourceIssue.issueNumber}`;
55781
+ }
55709
55782
  function buildMergeSystemPrompt(includeTaskId, agentPrompts, authorArg) {
55710
55783
  const commitFormat = includeTaskId ? `\`\`\`
55711
55784
  git commit -m "<type>(<scope>): <summary>" -m "<body>"${authorArg || ""}
@@ -55716,6 +55789,7 @@ Message format:
55716
55789
  - **Scope:** the task ID (e.g., KB-001)
55717
55790
  - **Summary:** one line describing what the squash brings in (imperative mood)
55718
55791
  - **Body:** 2-5 bullet points summarizing the key changes, each starting with "- "
55792
+ - **GitHub reference:** when the prompt includes a source issue reference, add \`Ref: owner/repo#N\` to the commit body
55719
55793
  ${authorArg ? `- **Author:** Always include the --author flag as shown in the example above.` : ""}
55720
55794
 
55721
55795
  Example:
@@ -55733,6 +55807,7 @@ Message format:
55733
55807
  - **Type:** feat, fix, refactor, docs, test, chore
55734
55808
  - **Summary:** one line describing what the squash brings in (imperative mood)
55735
55809
  - **Body:** 2-5 bullet points summarizing the key changes, each starting with "- "
55810
+ - **GitHub reference:** when the prompt includes a source issue reference, add \`Ref: owner/repo#N\` to the commit body
55736
55811
  ${authorArg ? `- **Author:** Always include the --author flag as shown in the example above.` : ""}
55737
55812
  Do NOT include a scope in the commit message type.
55738
55813
 
@@ -56116,6 +56191,7 @@ async function aiMergeTask(store, rootDir, taskId, options = {}) {
56116
56191
  throw new Error(`Cannot merge ${taskId}: ${mergeBlocker}`);
56117
56192
  }
56118
56193
  const branch = task.branch || `fusion/${taskId.toLowerCase()}`;
56194
+ const sourceIssueRef = buildSourceIssueRef(task.sourceIssue);
56119
56195
  const worktreePath = task.worktree;
56120
56196
  const result = {
56121
56197
  task,
@@ -56419,6 +56495,17 @@ async function aiMergeTask(store, rootDir, taskId, options = {}) {
56419
56495
  void 0,
56420
56496
  "merger"
56421
56497
  );
56498
+ let preAttemptHeadSha = "";
56499
+ try {
56500
+ const { stdout } = await execAsync2("git rev-parse HEAD", {
56501
+ cwd: rootDir,
56502
+ encoding: "utf-8"
56503
+ });
56504
+ preAttemptHeadSha = stdout.trim();
56505
+ } catch (err) {
56506
+ const msg = err instanceof Error ? err.message : String(err);
56507
+ mergerLog.warn(`${taskId}: failed to capture pre-attempt HEAD (${msg}) \u2014 verification-fix finalizer will fall back to amend`);
56508
+ }
56422
56509
  try {
56423
56510
  const success = await executeMergeAttempt({
56424
56511
  store,
@@ -56428,6 +56515,7 @@ async function aiMergeTask(store, rootDir, taskId, options = {}) {
56428
56515
  commitLog,
56429
56516
  diffStat,
56430
56517
  includeTaskId,
56518
+ sourceIssueRef,
56431
56519
  smartConflictResolution,
56432
56520
  mergeConflictStrategy,
56433
56521
  attemptNum,
@@ -56536,12 +56624,27 @@ async function aiMergeTask(store, rootDir, taskId, options = {}) {
56536
56624
  }
56537
56625
  if (fixSuccess) {
56538
56626
  const authorArg = getCommitAuthorArg(settings);
56539
- await amendMergeCommitWithFixes(rootDir, taskId, authorArg);
56627
+ const finalized = await commitOrAmendMergeWithFixes(
56628
+ rootDir,
56629
+ taskId,
56630
+ branch,
56631
+ commitLog,
56632
+ includeTaskId,
56633
+ preAttemptHeadSha,
56634
+ authorArg
56635
+ );
56636
+ if (!finalized) {
56637
+ resetMergeWithWarn(rootDir, taskId, "verification-fix finalize");
56638
+ throw new Error(
56639
+ `${taskId}: verification fix succeeded but no merge commit could be created \u2014 refusing to mark merge complete.`
56640
+ );
56641
+ }
56540
56642
  return true;
56541
56643
  }
56542
56644
  }
56543
56645
  }
56544
56646
  mergerLog.error(`${taskId}: deterministic verification failed \u2014 aborting merge (in-merge fix exhausted or disabled)`);
56647
+ resetMergeWithWarn(rootDir, taskId, "deterministic-verification rollback");
56545
56648
  throw error;
56546
56649
  }
56547
56650
  if (error.message?.includes("Build verification failed")) {
@@ -56612,7 +56715,21 @@ async function aiMergeTask(store, rootDir, taskId, options = {}) {
56612
56715
  }
56613
56716
  if (fixSuccess) {
56614
56717
  const authorArg = getCommitAuthorArg(settings);
56615
- await amendMergeCommitWithFixes(rootDir, taskId, authorArg);
56718
+ const finalized = await commitOrAmendMergeWithFixes(
56719
+ rootDir,
56720
+ taskId,
56721
+ branch,
56722
+ commitLog,
56723
+ includeTaskId,
56724
+ preAttemptHeadSha,
56725
+ authorArg
56726
+ );
56727
+ if (!finalized) {
56728
+ resetMergeWithWarn(rootDir, taskId, "build-verification fix finalize");
56729
+ throw new Error(
56730
+ `${taskId}: build verification fix succeeded but no merge commit could be created \u2014 refusing to mark merge complete.`
56731
+ );
56732
+ }
56616
56733
  return true;
56617
56734
  }
56618
56735
  }
@@ -56626,10 +56743,11 @@ async function aiMergeTask(store, rootDir, taskId, options = {}) {
56626
56743
  await audit.git({ type: "reset:hard", target: branch, metadata: { purpose: "build-retry" } });
56627
56744
  } catch (err) {
56628
56745
  const msg = err instanceof Error ? err.message : String(err);
56629
- mergerLog.warn(`${taskId}: git reset --merge cleanup failed (build-retry): ${msg}`);
56746
+ mergerLog.warn(`${taskId}: git reset --merge cleanup failed during build-verification rollback (build-retry): ${msg}`);
56630
56747
  }
56631
56748
  return false;
56632
56749
  }
56750
+ resetMergeWithWarn(rootDir, taskId, "build-verification rollback (no retries left)");
56633
56751
  throw error;
56634
56752
  }
56635
56753
  if (error.name === "MergeNonConflictError") {
@@ -56977,6 +57095,7 @@ async function executeMergeAttempt(params, aiTracker) {
56977
57095
  commitLog,
56978
57096
  diffStat,
56979
57097
  includeTaskId,
57098
+ sourceIssueRef,
56980
57099
  smartConflictResolution,
56981
57100
  attemptNum,
56982
57101
  options,
@@ -57167,19 +57286,12 @@ async function executeMergeAttempt(params, aiTracker) {
57167
57286
  simplifiedContext: attemptNum === 2,
57168
57287
  options,
57169
57288
  testCommand,
57170
- buildCommand: buildCommand2
57289
+ buildCommand: buildCommand2,
57290
+ sourceIssueRef
57171
57291
  });
57172
57292
  if (!agentResult.success) {
57173
57293
  const errorMessage = agentResult.error || "Build verification failed";
57174
57294
  await store.logEntry(taskId, "Build verification failed during merge", errorMessage);
57175
- try {
57176
- execSync("git reset --merge", { cwd: rootDir, stdio: "pipe" });
57177
- } catch (resetErr) {
57178
- const msg = resetErr instanceof Error ? resetErr.message : String(resetErr);
57179
- mergerLog.warn(
57180
- `${taskId}: git reset --merge cleanup failed during build-verification rollback (build-verification reset, build-retry): ${msg}`
57181
- );
57182
- }
57183
57295
  throw new Error(`Build verification failed for ${taskId}: ${errorMessage}`);
57184
57296
  }
57185
57297
  if (testCommand || buildCommand2) {
@@ -57195,6 +57307,24 @@ async function executeMergeAttempt(params, aiTracker) {
57195
57307
  options.signal
57196
57308
  );
57197
57309
  }
57310
+ try {
57311
+ const authorArg = getCommitAuthorArg(params.settings);
57312
+ const { subjectArg, bodyArg } = buildDeterministicMergeMessage({
57313
+ taskId,
57314
+ branch,
57315
+ commitLog,
57316
+ includeTaskId
57317
+ });
57318
+ const trailerArg = buildTaskIdTrailerArg(taskId);
57319
+ await execAsync2(
57320
+ `git commit --amend ${subjectArg} ${bodyArg}${trailerArg}${authorArg}`,
57321
+ { cwd: rootDir }
57322
+ );
57323
+ mergerLog.log(`${taskId}: rewrote AI-authored merge commit message with deterministic body`);
57324
+ } catch (err) {
57325
+ const msg = err instanceof Error ? err.message : String(err);
57326
+ mergerLog.warn(`${taskId}: failed to canonicalize merge commit message (${msg}) \u2014 keeping AI-written message`);
57327
+ }
57198
57328
  return true;
57199
57329
  } catch (error) {
57200
57330
  if (error instanceof Error && error.name === "MergeAbortedError") {
@@ -57217,7 +57347,7 @@ async function executeMergeAttempt(params, aiTracker) {
57217
57347
  }
57218
57348
  }
57219
57349
  async function attemptWithSideStrategy(params, side = "theirs", aiTracker) {
57220
- const { rootDir, branch, commitLog, includeTaskId, taskId, store, settings, testCommand, buildCommand: buildCommand2, testSource, buildSource } = params;
57350
+ const { rootDir, branch, commitLog, includeTaskId, sourceIssueRef, taskId, store, settings, testCommand, buildCommand: buildCommand2, testSource, buildSource } = params;
57221
57351
  mergerLog.log(`${taskId}: attempting merge with -X ${side} strategy`);
57222
57352
  try {
57223
57353
  throwIfAborted(params.options.signal, taskId);
@@ -57258,8 +57388,9 @@ async function attemptWithSideStrategy(params, side = "theirs", aiTracker) {
57258
57388
  const fallbackPrefix = includeTaskId ? `feat(${taskId})` : "feat";
57259
57389
  const authorArg = getCommitAuthorArg(settings);
57260
57390
  const trailerArg = buildTaskIdTrailerArg(taskId);
57391
+ const issueRefBodyArg = sourceIssueRef ? ` -m "Ref: ${sourceIssueRef}"` : "";
57261
57392
  await execAsync2(
57262
- `git commit -m "${fallbackPrefix}: merge ${branch} (auto-resolved)" -m "${escapedLog}"${trailerArg}${authorArg}`,
57393
+ `git commit -m "${fallbackPrefix}: merge ${branch} (auto-resolved)" -m "${escapedLog}"${issueRefBodyArg}${trailerArg}${authorArg}`,
57263
57394
  { cwd: rootDir }
57264
57395
  );
57265
57396
  mergerLog.log(`${taskId}: committed with -X ${side} auto-resolution`);
@@ -57296,6 +57427,7 @@ async function runAiAgentForCommit(params) {
57296
57427
  includeTaskId,
57297
57428
  hasConflicts,
57298
57429
  simplifiedContext,
57430
+ sourceIssueRef,
57299
57431
  options,
57300
57432
  testCommand,
57301
57433
  buildCommand: buildCommand2
@@ -57394,7 +57526,8 @@ async function runAiAgentForCommit(params) {
57394
57526
  simplifiedContext,
57395
57527
  testCommand,
57396
57528
  buildCommand: buildCommand2,
57397
- authorArg
57529
+ authorArg,
57530
+ sourceIssueRef
57398
57531
  });
57399
57532
  mergerLog.log(`${taskId}: starting fresh merge agent session`);
57400
57533
  try {
@@ -57426,7 +57559,8 @@ async function runAiAgentForCommit(params) {
57426
57559
  // Also skip detailed context
57427
57560
  testCommand,
57428
57561
  buildCommand: buildCommand2,
57429
- authorArg
57562
+ authorArg,
57563
+ sourceIssueRef
57430
57564
  });
57431
57565
  try {
57432
57566
  await withRateLimitRetry(async () => {
@@ -57468,8 +57602,9 @@ async function runAiAgentForCommit(params) {
57468
57602
  const fallbackPrefix = includeTaskId ? `feat(${taskId})` : "feat";
57469
57603
  const authorArg2 = getCommitAuthorArg(settings);
57470
57604
  const trailerArg = buildTaskIdTrailerArg(taskId);
57605
+ const issueRefBodyArg = sourceIssueRef ? ` -m "Ref: ${sourceIssueRef}"` : "";
57471
57606
  await execAsync2(
57472
- `git commit -m "${fallbackPrefix}: merge ${branch}" -m "${escapedLog}"${trailerArg}${authorArg2}`,
57607
+ `git commit -m "${fallbackPrefix}: merge ${branch}" -m "${escapedLog}"${issueRefBodyArg}${trailerArg}${authorArg2}`,
57473
57608
  { cwd: rootDir }
57474
57609
  );
57475
57610
  } else {
@@ -57492,7 +57627,7 @@ async function runAiAgentForCommit(params) {
57492
57627
  }
57493
57628
  }
57494
57629
  function buildMergePrompt(params) {
57495
- const { taskId, branch, commitLog, diffStat, hasConflicts, simplifiedContext, testCommand, buildCommand: buildCommand2, authorArg } = params;
57630
+ const { taskId, branch, commitLog, diffStat, hasConflicts, simplifiedContext, sourceIssueRef, testCommand, buildCommand: buildCommand2, authorArg } = params;
57496
57631
  const truncatedCommitLog = truncateWithEllipsis(commitLog, MERGE_COMMIT_LOG_MAX_CHARS);
57497
57632
  const truncatedDiffStat = truncateWithEllipsis(diffStat, MERGE_DIFF_STAT_MAX_CHARS);
57498
57633
  const parts = [
@@ -57528,6 +57663,13 @@ function buildMergePrompt(params) {
57528
57663
  `Write and run the \`git commit\` command with a good message summarizing the work.${authorArg ? ` Be sure to include \`${authorArg.trim()}\` in the commit command.` : ""}`
57529
57664
  );
57530
57665
  }
57666
+ if (sourceIssueRef) {
57667
+ parts.push(
57668
+ "",
57669
+ "Include this in the commit message body:",
57670
+ `- Ref: ${sourceIssueRef}`
57671
+ );
57672
+ }
57531
57673
  if (testCommand) {
57532
57674
  parts.push(
57533
57675
  "",
@@ -59451,6 +59593,7 @@ function buildExecutionPrompt(task, rootDir, settings, worktreePath) {
59451
59593
  const reviewMatch = prompt.match(/##\s*Review Level[:\s]*(\d)/);
59452
59594
  const reviewLevel = reviewMatch ? parseInt(reviewMatch[1], 10) : 0;
59453
59595
  const authorArg = settings?.commitAuthorEnabled !== false ? ` --author="${settings?.commitAuthorName || "Fusion"} <${settings?.commitAuthorEmail || "noreply@runfusion.ai"}>"` : "";
59596
+ const sourceIssueRef = task.sourceIssue?.provider === "github" && task.sourceIssue.repository && task.sourceIssue.issueNumber ? `${task.sourceIssue.repository}#${task.sourceIssue.issueNumber}` : "";
59454
59597
  const hasProgress = task.steps.length > 0 && task.steps.some((s) => s.status !== "pending");
59455
59598
  let progressSection = "";
59456
59599
  if (hasProgress) {
@@ -59553,7 +59696,7 @@ ${hasProgress ? `Resume from Step ${task.currentStep}. Do NOT redo completed ste
59553
59696
  Use \`fn_task_update\` to report progress on every step transition.
59554
59697
  Use \`fn_task_log\` for important actions and decisions.
59555
59698
  Use \`fn_task_create\` for truly separate follow-up work, not for fixes required to get tests, build, or typecheck back to green.
59556
- Commit at step boundaries: \`git commit -m "feat(${task.id}): complete Step N \u2014 description"${authorArg}\`
59699
+ Commit at step boundaries: \`git commit -m "feat(${task.id}): complete Step N \u2014 description"${sourceIssueRef ? ` -m "Ref: ${sourceIssueRef}"` : ""}${authorArg}\`
59557
59700
  When all steps are complete: call \`fn_task_done()\`
59558
59701
 
59559
59702
  If a build command is configured, run that exact command in this worktree before calling \`fn_task_done()\`.
@@ -59739,6 +59882,7 @@ If the task's PROMPT.md includes a "Documentation Requirements" section listing
59739
59882
  ## Git discipline
59740
59883
  - Commit after completing each step (not after every file change)
59741
59884
  - Use conventional commit messages prefixed with the task ID
59885
+ - When the task has a GitHub issue reference, include \`Ref: owner/repo#N\` in the commit body
59742
59886
  - Do NOT commit broken or half-implemented code
59743
59887
 
59744
59888
  ## Worktree Boundaries
@@ -79816,12 +79960,14 @@ var init_rate_limit = __esm({
79816
79960
  });
79817
79961
 
79818
79962
  // ../dashboard/src/routes/register-chat-routes.ts
79963
+ var CHAT_MAX_ATTACHMENT_SIZE;
79819
79964
  var init_register_chat_routes = __esm({
79820
79965
  "../dashboard/src/routes/register-chat-routes.ts"() {
79821
79966
  "use strict";
79822
79967
  init_api_error();
79823
79968
  init_rate_limit();
79824
79969
  init_sse_buffer();
79970
+ CHAT_MAX_ATTACHMENT_SIZE = 5 * 1024 * 1024;
79825
79971
  }
79826
79972
  });
79827
79973
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fusion/pi-claude-cli",
3
- "version": "0.8.0",
3
+ "version": "0.8.1",
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,
@@ -23,6 +23,9 @@
23
23
  "@mariozechner/pi-ai": "*",
24
24
  "@mariozechner/pi-coding-agent": "*"
25
25
  },
26
+ "dependencies": {
27
+ "cross-spawn": "^7.0.6"
28
+ },
26
29
  "devDependencies": {
27
30
  "@types/node": "^22.0.0",
28
31
  "typescript": "^5.7.0",
@@ -6,7 +6,7 @@
6
6
  * Also provides startup validation for CLI presence and authentication.
7
7
  */
8
8
 
9
- import { spawn, execSync, type ChildProcess } from "node:child_process";
9
+ import { execSync, spawn, type ChildProcess } from "node:child_process";
10
10
  import { writeFileSync, unlinkSync } from "node:fs";
11
11
  import { join } from "node:path";
12
12
  import { tmpdir } from "node:os";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@runfusion/fusion",
3
- "version": "0.8.0",
3
+ "version": "0.8.1",
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",
@@ -1 +0,0 @@
1
- import{r as l,j as e}from"./vendor-react-K0fH_qHe.js";import{bU as L,aw as B,bV as E,bW as $,bX as W,X as _,af as G,H as O,C as b,L as X}from"./index-D1gavMG-.js";import{D as Y}from"./DirectoryPicker-_cBPx6Nx.js";import"./vendor-xterm-DzcZoU0P.js";import"./folder-open-Kh0ScTc5.js";function f(c){const r=c.split(/[/\\]/).filter(h=>h.length>0);return(r[r.length-1]||"My Project").replace(/[^a-zA-Z0-9-_]/g,"-").replace(/-+/g,"-").replace(/^-|-$/g,"")||"My Project"}function K({onProjectRegistered:c,onClose:r}){const j="https://github.com/runfusion/fusion/discussions",[h,k]=l.useState(!0),[s,n]=l.useState({step:"manual",manualMode:"existing",manualPath:"",manualCloneUrl:"",manualName:"",manualIsolationMode:"in-process",manualNodeId:"",isRegistering:!1,error:null}),[x,C]=l.useState(!1),[u,g]=l.useState(""),[m,y]=l.useState(()=>L()),{nodes:N,loading:z}=B(),M=N.find(a=>a.type==="local")?.id,w=l.useCallback(()=>{k(!1),r?.()},[r]),I=l.useCallback(a=>{n(t=>{const d={manualPath:a};return a&&(!t.manualName||t.manualName===f(t.manualPath))&&(d.manualName=f(a)),{...t,...d}})},[]),P=l.useCallback(async()=>{const a=s.manualPath.trim(),t=s.manualName.trim(),d=s.manualCloneUrl.trim();if(!(!a||!t)&&!(s.manualMode==="clone"&&!d)){n(i=>({...i,isRegistering:!0,error:null}));try{const i={name:t,path:a,isolationMode:s.manualIsolationMode,nodeId:s.manualNodeId||void 0,cloneUrl:s.manualMode==="clone"?d:void 0},p=await E(i);c(p),n(D=>({...D,step:"complete",isRegistering:!1}))}catch(i){n(p=>({...p,isRegistering:!1,error:i instanceof Error?i.message:"Failed to register project"}))}}},[s.manualPath,s.manualName,s.manualCloneUrl,s.manualMode,s.manualIsolationMode,s.manualNodeId,c]),S=l.useCallback(()=>{const a=u.trim();a&&($(a),window.location.reload())},[u]),R=l.useCallback(()=>{W(),y(void 0),g(""),window.location.reload()},[]);if(!h)return null;const v=s.manualMode==="existing",o=s.manualMode==="clone",A=s.manualPath.trim().length>0,U=s.manualName.trim().length>0,F=s.manualCloneUrl.trim().length>0,T=s.isRegistering||!A||!U||o&&!F;return e.jsx("div",{className:"modal-overlay open setup-wizard-overlay",role:"dialog","aria-modal":"true","aria-labelledby":"wizard-title",children:e.jsxs("div",{className:"modal setup-wizard-modal",children:[e.jsxs("div",{className:"setup-wizard-header",children:[e.jsxs("div",{className:"setup-wizard-heading",children:[e.jsxs("div",{className:"setup-wizard-brand","aria-label":"Fusion",children:[e.jsxs("svg",{className:"setup-wizard-brand-logo",width:28,height:28,viewBox:"0 0 128 128",fill:"none","aria-label":"Fusion logo",role:"img",children:[e.jsx("circle",{cx:"64",cy:"64",r:"52",stroke:"currentColor",strokeWidth:"8"}),e.jsx("path",{d:"M26 101C44 82 62 64 82 45C90 37 98 30 104 24C96 35 89 47 81 60C70 79 57 95 43 108C38 112 32 108 26 101Z",fill:"currentColor"})]}),e.jsx("span",{className:"setup-wizard-brand-name",children:"Fusion"})]}),e.jsxs("h2",{id:"wizard-title",className:"setup-wizard-title",children:[s.step==="manual"&&"Welcome to Fusion",s.step==="complete"&&"Setup Complete!"]})]}),s.step!=="complete"&&e.jsx("button",{className:"modal-close",onClick:w,"aria-label":"Close wizard",children:e.jsx(_,{size:20})})]}),e.jsxs("div",{className:"setup-wizard-content",children:[s.step==="manual"&&e.jsxs("div",{className:"setup-wizard-manual",children:[e.jsx("div",{className:"welcome-icon",children:e.jsx(G,{size:32})}),e.jsx("p",{className:"welcome-text",children:"Let's set up your first project. Register an existing directory or clone a git repository into a destination folder, then register it."}),e.jsxs("fieldset",{className:"setup-wizard-mode-switch","aria-label":"Project setup mode",children:[e.jsx("legend",{children:"Setup Mode"}),e.jsxs("label",{className:`setup-wizard-mode-option${v?" selected":""}`,children:[e.jsx("input",{type:"radio",name:"setup-mode",value:"existing",checked:v,onChange:()=>n(a=>({...a,manualMode:"existing",error:null}))}),e.jsx("span",{children:"Use Existing Directory"})]}),e.jsxs("label",{className:`setup-wizard-mode-option${o?" selected":""}`,children:[e.jsx("input",{type:"radio",name:"setup-mode",value:"clone",checked:o,onChange:()=>n(a=>({...a,manualMode:"clone",error:null}))}),e.jsx("span",{children:"Clone Git Repository"})]})]}),o&&e.jsxs("div",{className:"form-group",children:[e.jsx("label",{htmlFor:"project-clone-url",children:"Repository URL"}),e.jsx("input",{id:"project-clone-url",type:"text",value:s.manualCloneUrl,onChange:a=>n(t=>({...t,manualCloneUrl:a.target.value})),placeholder:"https://github.com/owner/repo.git"}),e.jsx("p",{className:"form-hint",children:"Fusion will run git clone into the destination directory, then register that cloned folder."})]}),e.jsxs("div",{className:"form-group",children:[e.jsx("label",{htmlFor:"project-path",children:o?"Destination Directory":"Project Directory"}),e.jsx(Y,{value:s.manualPath,onChange:I,nodeId:s.manualNodeId||void 0,localNodeId:M,placeholder:o?"/path/for/new-clone":"/path/to/your/project"}),e.jsx("p",{className:"form-hint",children:o?"Select or type an absolute destination path. Fusion will clone into this directory.":"Select or type the absolute path to your project"})]}),e.jsxs("div",{className:"form-group",children:[e.jsx("label",{htmlFor:"project-name",children:"Project Name"}),e.jsx("input",{id:"project-name",type:"text",value:s.manualName,onChange:a=>n(t=>({...t,manualName:a.target.value})),placeholder:"my-project"}),e.jsx("p",{className:"form-hint",children:o?"By default this follows the destination folder name unless you edit it.":"By default this follows the selected directory name unless you edit it."})]}),e.jsxs("div",{className:"setup-wizard-advanced",children:[e.jsxs("button",{type:"button",className:"setup-wizard-advanced-toggle","aria-expanded":x,onClick:()=>C(a=>!a),children:[e.jsx(O,{size:16,className:"setup-wizard-advanced-chevron"}),e.jsx("span",{children:"Advanced settings"})]}),x&&e.jsxs("div",{className:"setup-wizard-advanced-panel",children:[e.jsx("div",{className:"form-group",children:e.jsxs("div",{className:"project-node-selector",children:[e.jsx("span",{className:"project-node-selector__label",children:"Runtime Node"}),e.jsxs("select",{value:s.manualNodeId,onChange:a=>n(t=>({...t,manualNodeId:a.target.value})),disabled:z||s.isRegistering,children:[e.jsx("option",{value:"",children:"Local node"}),N.map(a=>e.jsxs("option",{value:a.id,children:[a.name," (",a.type,")"]},a.id))]})]})}),e.jsxs("div",{className:"form-group",children:[e.jsx("label",{children:"Isolation Mode"}),e.jsxs("div",{className:"setup-wizard-isolation-options",children:[e.jsxs("label",{className:`setup-wizard-isolation-option${s.manualIsolationMode==="in-process"?" selected":""}`,children:[e.jsx("input",{type:"radio",name:"isolation-mode",value:"in-process",checked:s.manualIsolationMode==="in-process",onChange:()=>n(a=>({...a,manualIsolationMode:"in-process"}))}),e.jsxs("div",{className:"setup-wizard-isolation-option-content",children:[e.jsx("strong",{children:"In-Process"}),e.jsx("span",{children:"Lower overhead, shared memory. Best for most projects."}),e.jsx("span",{className:"wizard-option-recommended",children:"Recommended"})]})]}),e.jsxs("label",{className:`setup-wizard-isolation-option${s.manualIsolationMode==="child-process"?" selected":""}`,children:[e.jsx("input",{type:"radio",name:"isolation-mode",value:"child-process",checked:s.manualIsolationMode==="child-process",onChange:()=>n(a=>({...a,manualIsolationMode:"child-process"}))}),e.jsxs("div",{className:"setup-wizard-isolation-option-content",children:[e.jsx("strong",{children:"Child-Process"}),e.jsx("span",{children:"Isolated execution with crash containment."})]})]})]})]}),e.jsxs("div",{className:"form-group",children:[e.jsx("label",{htmlFor:"setup-auth-token",children:"Browser Auth Token"}),e.jsxs("div",{className:"setup-wizard-auth-token",children:[e.jsx("input",{id:"setup-auth-token",type:"password",value:u,onChange:a=>g(a.target.value),placeholder:m?"Enter a new token to replace the stored one":"Paste the auth token for this browser",autoComplete:"off",spellCheck:!1}),e.jsxs("div",{className:"setup-wizard-auth-token-actions",children:[e.jsx("button",{type:"button",className:"btn",onClick:S,disabled:u.trim().length===0,children:m?"Update token":"Set token"}),m&&e.jsx("button",{type:"button",className:"btn",onClick:R,children:"Reset token"})]})]}),e.jsx("p",{className:"form-hint",children:m?"A token is already stored in this browser. Updating or resetting it will reload the page.":"Store a token in this browser for authenticated dashboard requests, then reload the page."})]})]})]}),s.error&&e.jsx("div",{className:"wizard-error",role:"alert",children:s.error})]}),s.step==="complete"&&e.jsxs("div",{className:"setup-wizard-complete",children:[e.jsxs("div",{className:"setup-wizard-success-streak","aria-hidden":"true",children:[e.jsx("div",{className:"setup-wizard-success-streak-core"}),e.jsx("div",{className:"setup-wizard-success-streak-glow"})]}),e.jsx(b,{size:64,className:"success-icon"}),e.jsx("h3",{children:"All Set!"}),e.jsx("p",{children:"Your project has been registered successfully."}),e.jsx("p",{children:"You can add more projects anytime from the project overview."})]})]}),e.jsxs("div",{className:"setup-wizard-footer",children:[e.jsx("a",{className:"btn setup-wizard-help-link",href:j,target:"_blank",rel:"noreferrer",children:"Need help?"}),s.step==="manual"&&e.jsx("button",{className:"btn btn-primary",onClick:P,disabled:T,children:s.isRegistering?e.jsxs(e.Fragment,{children:[e.jsx(X,{size:16,className:"animate-spin"}),e.jsx("span",{children:"Registering..."})]}):e.jsx("span",{children:"Register Project"})}),s.step==="complete"&&e.jsxs("button",{className:"btn btn-primary",onClick:w,children:[e.jsx(b,{size:16}),e.jsx("span",{children:"Get Started"})]})]})]})})}export{K as SetupWizardModal};