@runfusion/fusion 0.2.5 → 0.2.7

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 +329 -53
  2. package/dist/client/assets/{AgentDetailView-CkuuGA1O.js → AgentDetailView-B4lRk--v.js} +1 -1
  3. package/dist/client/assets/{AgentsView-CWFLMIDP.js → AgentsView-yCYBY2km.js} +3 -3
  4. package/dist/client/assets/{ChatView-C_T91ebd.js → ChatView-CH9T0dDs.js} +1 -1
  5. package/dist/client/assets/{DevServerView-ChWTzTvy.js → DevServerView-jXXtoQUx.js} +1 -1
  6. package/dist/client/assets/DirectoryPicker-izgMlS27.js +1 -0
  7. package/dist/client/assets/{DocumentsView-Co9to4Zp.css → DocumentsView-DV2DrCZb.css} +1 -1
  8. package/dist/client/assets/{DocumentsView-CjfVl8mZ.js → DocumentsView-DkkoHRwL.js} +1 -1
  9. package/dist/client/assets/{InsightsView-Rb735C9_.js → InsightsView-DaRtUPHX.js} +1 -1
  10. package/dist/client/assets/{MemoryView-LLc_uNtG.js → MemoryView-85NKuU3h.js} +1 -1
  11. package/dist/client/assets/{NodesView-BviqBWRA.js → NodesView-BsUk_oiU.js} +1 -1
  12. package/dist/client/assets/PiExtensionsManager-BF5pxrSE.js +11 -0
  13. package/dist/client/assets/PiExtensionsManager-K7HQ08L4.css +1 -0
  14. package/dist/client/assets/PluginManager-ccq3uK50.css +1 -0
  15. package/dist/client/assets/{PluginManager-BywTPbLB.js → PluginManager-s6btydh5.js} +1 -1
  16. package/dist/client/assets/{RoadmapsView-Dhl--4vY.js → RoadmapsView-SQol126Y.js} +1 -1
  17. package/dist/client/assets/{SetupWizardModal-CVtmwoJC.js → SetupWizardModal-CQc1uGSq.js} +1 -1
  18. package/dist/client/assets/{SkillsView-CG9y4fsE.js → SkillsView-BtUhs_QW.js} +1 -1
  19. package/dist/client/assets/{folder-open-BVDq27HP.js → folder-open-CI4TCD7P.js} +1 -1
  20. package/dist/client/assets/index-Ct-OqLpP.css +1 -0
  21. package/dist/client/assets/index-rNf7s96d.js +649 -0
  22. package/dist/client/assets/{upload-BDvpReDO.js → upload-CAlKC4qI.js} +1 -1
  23. package/dist/client/index.html +2 -2
  24. package/dist/extension.js +75 -8
  25. package/dist/pi-claude-cli/package.json +1 -1
  26. package/package.json +5 -5
  27. package/dist/client/assets/DirectoryPicker-BMJIT7HD.js +0 -1
  28. package/dist/client/assets/PiExtensionsManager-CPgmJgDk.css +0 -1
  29. package/dist/client/assets/PiExtensionsManager-CriZBkQe.js +0 -11
  30. package/dist/client/assets/PluginManager-D64RIzmL.css +0 -1
  31. package/dist/client/assets/index-CikysL-d.js +0 -644
  32. package/dist/client/assets/index-Da1qmtc7.css +0 -1
package/dist/bin.js CHANGED
@@ -91,7 +91,10 @@ var init_settings_schema = __esm({
91
91
  settingsSyncConflictResolution: "last-write-wins",
92
92
  // Dashboard session state (persisted to global settings for PWA/offline restore)
93
93
  dashboardCurrentNodeId: void 0,
94
- dashboardCurrentProjectIdByNode: void 0
94
+ dashboardCurrentProjectIdByNode: void 0,
95
+ // Dashboard TUI memory guard
96
+ vitestAutoKillEnabled: true,
97
+ vitestKillThresholdPct: 90
95
98
  };
96
99
  DEFAULT_PROJECT_SETTINGS = {
97
100
  globalPause: false,
@@ -65592,6 +65595,63 @@ async function validateDiffScope(store, taskId, diffStat, strict = false) {
65592
65595
  }
65593
65596
  return result;
65594
65597
  }
65598
+ async function resolveTaskDiffBaseRef({
65599
+ cwd,
65600
+ headRef,
65601
+ baseBranch,
65602
+ baseCommitSha
65603
+ }) {
65604
+ const resolvedBaseBranch = baseBranch?.trim() || "main";
65605
+ const quotedHeadRef = quoteArg(headRef);
65606
+ let mergeBase;
65607
+ try {
65608
+ try {
65609
+ const { stdout } = await execAsync2(`git merge-base ${quotedHeadRef} ${quoteArg(resolvedBaseBranch)}`, {
65610
+ cwd,
65611
+ encoding: "utf-8"
65612
+ });
65613
+ mergeBase = stdout.trim() || void 0;
65614
+ } catch {
65615
+ const { stdout } = await execAsync2(`git merge-base ${quotedHeadRef} ${quoteArg(`origin/${resolvedBaseBranch}`)}`, {
65616
+ cwd,
65617
+ encoding: "utf-8"
65618
+ });
65619
+ mergeBase = stdout.trim() || void 0;
65620
+ }
65621
+ } catch {
65622
+ }
65623
+ if (mergeBase) {
65624
+ try {
65625
+ const { stdout } = await execAsync2(`git rev-parse ${quotedHeadRef}`, {
65626
+ cwd,
65627
+ encoding: "utf-8"
65628
+ });
65629
+ const headSha = stdout.trim();
65630
+ if (headSha && headSha !== mergeBase) return mergeBase;
65631
+ } catch {
65632
+ return mergeBase;
65633
+ }
65634
+ }
65635
+ if (baseCommitSha) {
65636
+ try {
65637
+ await execAsync2(`git merge-base --is-ancestor ${quoteArg(baseCommitSha)} ${quotedHeadRef}`, {
65638
+ cwd,
65639
+ encoding: "utf-8"
65640
+ });
65641
+ return baseCommitSha;
65642
+ } catch {
65643
+ }
65644
+ }
65645
+ try {
65646
+ const { stdout } = await execAsync2(`git rev-parse ${quoteArg(`${headRef}~1`)}`, {
65647
+ cwd,
65648
+ encoding: "utf-8"
65649
+ });
65650
+ return stdout.trim() || void 0;
65651
+ } catch {
65652
+ return void 0;
65653
+ }
65654
+ }
65595
65655
  async function getConflictedFiles(cwd) {
65596
65656
  try {
65597
65657
  const { stdout } = await execAsync2("git diff --name-only --diff-filter=U", {
@@ -66208,10 +66268,17 @@ async function aiMergeTask(store, rootDir, taskId, options = {}) {
66208
66268
  mergerLog.warn(`${taskId}: pre-merge rebase pipeline failed (${msg}) \u2014 proceeding without rebase`);
66209
66269
  }
66210
66270
  }
66271
+ const diffBaseRef = await resolveTaskDiffBaseRef({
66272
+ cwd: rootDir,
66273
+ headRef: branch,
66274
+ baseBranch: task.baseBranch,
66275
+ baseCommitSha: task.baseCommitSha
66276
+ });
66277
+ const contextDiffRange = diffBaseRef ? `${diffBaseRef}..${branch}` : `HEAD..${branch}`;
66211
66278
  let commitLog = "";
66212
66279
  let diffStat = "";
66213
66280
  try {
66214
- const { stdout: logOutput } = await execAsync2(`git log HEAD..${branch} --format="- %s"`, {
66281
+ const { stdout: logOutput } = await execAsync2(`git log ${contextDiffRange} --format="- %s"`, {
66215
66282
  cwd: rootDir,
66216
66283
  encoding: "utf-8"
66217
66284
  });
@@ -66220,12 +66287,7 @@ async function aiMergeTask(store, rootDir, taskId, options = {}) {
66220
66287
  commitLog = "(unable to read commit log)";
66221
66288
  }
66222
66289
  try {
66223
- const { stdout: mergeBaseOutput } = await execAsync2(`git merge-base HEAD ${branch}`, {
66224
- cwd: rootDir,
66225
- encoding: "utf-8"
66226
- });
66227
- const mergeBase = mergeBaseOutput.trim();
66228
- const { stdout: diffOutput } = await execAsync2(`git diff ${mergeBase}..${branch} --stat`, {
66290
+ const { stdout: diffOutput } = await execAsync2(`git diff ${contextDiffRange} --stat`, {
66229
66291
  cwd: rootDir,
66230
66292
  encoding: "utf-8"
66231
66293
  });
@@ -85752,6 +85814,11 @@ async function createPlanningAgent(session, rootDir, modelProvider, modelId, pro
85752
85814
  },
85753
85815
  onText: (delta) => {
85754
85816
  session.thinkingOutput += delta;
85817
+ persistThinking2(session.id, session.thinkingOutput);
85818
+ planningStreamManager.broadcast(session.id, {
85819
+ type: "thinking",
85820
+ data: delta
85821
+ });
85755
85822
  }
85756
85823
  });
85757
85824
  }
@@ -87837,6 +87904,9 @@ function detectAngularLine(line) {
87837
87904
  }
87838
87905
  return null;
87839
87906
  }
87907
+ function isNodeInspectorLine(line) {
87908
+ return /\binspector\b/i.test(line);
87909
+ }
87840
87910
  function detectGenericUrl(line) {
87841
87911
  const genericUrlMatch = line.match(/((?:https?:\/\/)?(?:localhost|127\.0\.0\.1):\d{2,5}(?:\/\S*)?)/i);
87842
87912
  if (!genericUrlMatch) {
@@ -87844,6 +87914,9 @@ function detectGenericUrl(line) {
87844
87914
  }
87845
87915
  return withSource("generic-url", genericUrlMatch[1]);
87846
87916
  }
87917
+ function isInspectorDiagnosticLine(line) {
87918
+ return /\b(?:inspector|debugger)\b/i.test(line) || /\b(node:)?\s*--inspect(?:-brk)?\b/i.test(line) || /\bws:\/\/(?:127\.0\.0\.1|localhost):\d{2,5}\b/i.test(line);
87919
+ }
87847
87920
  function detectGenericPortLine(line) {
87848
87921
  const keywordPortMatch = line.match(/\b(?:ready|listening|started|available|compiled|running|server)\b[^\d]{0,50}(?:on\s+)?(?:port\s*[:=]?\s*)?(\d{2,5})\b/i);
87849
87922
  if (!keywordPortMatch) {
@@ -87870,7 +87943,10 @@ function detectPortFromLogLine(line) {
87870
87943
  return null;
87871
87944
  }
87872
87945
  const cleanLine = stripAnsi(line).trim();
87873
- if (!cleanLine) {
87946
+ if (!cleanLine || isInspectorDiagnosticLine(cleanLine)) {
87947
+ return null;
87948
+ }
87949
+ if (isNodeInspectorLine(cleanLine)) {
87874
87950
  return null;
87875
87951
  }
87876
87952
  return detectViteLine(cleanLine) ?? detectNextLine(cleanLine) ?? detectStorybookLine(cleanLine) ?? detectAngularLine(cleanLine) ?? detectGenericUrl(cleanLine) ?? detectGenericPortLine(cleanLine);
@@ -115277,6 +115353,38 @@ async function runGitCommand(args, cwd, timeout2 = 1e4) {
115277
115353
  }
115278
115354
  return "";
115279
115355
  }
115356
+ async function resolveDiffBase(task, cwd, headRef = "HEAD", runGit = runGitCommand) {
115357
+ const baseBranch = task.baseBranch ?? "main";
115358
+ let mergeBase;
115359
+ try {
115360
+ try {
115361
+ mergeBase = (await runGit(["merge-base", headRef, baseBranch], cwd, 5e3)).trim() || void 0;
115362
+ } catch {
115363
+ mergeBase = (await runGit(["merge-base", headRef, `origin/${baseBranch}`], cwd, 5e3)).trim() || void 0;
115364
+ }
115365
+ } catch {
115366
+ }
115367
+ if (mergeBase) {
115368
+ try {
115369
+ const head = (await runGit(["rev-parse", headRef], cwd, 5e3)).trim();
115370
+ if (head && head !== mergeBase) return mergeBase;
115371
+ } catch {
115372
+ return mergeBase;
115373
+ }
115374
+ }
115375
+ if (task.baseCommitSha) {
115376
+ try {
115377
+ await runGit(["merge-base", "--is-ancestor", task.baseCommitSha, headRef], cwd, 5e3);
115378
+ return task.baseCommitSha;
115379
+ } catch {
115380
+ }
115381
+ }
115382
+ try {
115383
+ return (await runGit(["rev-parse", `${headRef}~1`], cwd, 5e3)).trim() || void 0;
115384
+ } catch {
115385
+ return void 0;
115386
+ }
115387
+ }
115280
115388
  function slugifyPresetName(name) {
115281
115389
  const slug = name.trim().toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/-+/g, "-").replace(/^[-_]+|[-_]+$/g, "").slice(0, 32);
115282
115390
  return slug || "preset";
@@ -118110,38 +118218,6 @@ function createApiRoutes(store, options) {
118110
118218
  }
118111
118219
  }
118112
118220
  });
118113
- async function resolveDiffBase(task, cwd) {
118114
- const baseBranch = task.baseBranch ?? "main";
118115
- let mergeBase;
118116
- try {
118117
- try {
118118
- mergeBase = (await runGitCommand(["merge-base", "HEAD", baseBranch], cwd, 5e3)).trim() || void 0;
118119
- } catch {
118120
- mergeBase = (await runGitCommand(["merge-base", "HEAD", `origin/${baseBranch}`], cwd, 5e3)).trim() || void 0;
118121
- }
118122
- } catch {
118123
- }
118124
- if (mergeBase) {
118125
- try {
118126
- const head = (await runGitCommand(["rev-parse", "HEAD"], cwd, 5e3)).trim();
118127
- if (head && head !== mergeBase) return mergeBase;
118128
- } catch {
118129
- return mergeBase;
118130
- }
118131
- }
118132
- if (task.baseCommitSha) {
118133
- try {
118134
- await runGitCommand(["merge-base", "--is-ancestor", task.baseCommitSha, "HEAD"], cwd, 5e3);
118135
- return task.baseCommitSha;
118136
- } catch {
118137
- }
118138
- }
118139
- try {
118140
- return (await runGitCommand(["rev-parse", "HEAD~1"], cwd, 5e3)).trim() || void 0;
118141
- } catch {
118142
- return void 0;
118143
- }
118144
- }
118145
118221
  router.get("/tasks/:id/session-files", async (req, res) => {
118146
118222
  try {
118147
118223
  const { store: scopedStore } = await getProjectContext2(req);
@@ -118662,7 +118738,7 @@ function createApiRoutes(store, options) {
118662
118738
  router.patch("/tasks/:id", async (req, res) => {
118663
118739
  try {
118664
118740
  const { store: scopedStore } = await getProjectContext2(req);
118665
- const { title, description, prompt, dependencies, enabledWorkflowSteps, modelProvider, modelId, validatorModelProvider, validatorModelId, planningModelProvider, planningModelId, thinkingLevel, assigneeUserId, reviewLevel, executionMode } = req.body;
118741
+ const { title, description, prompt, dependencies, enabledWorkflowSteps, modelProvider, modelId, validatorModelProvider, validatorModelId, planningModelProvider, planningModelId, thinkingLevel, assigneeUserId, reviewLevel, executionMode, sourceIssue } = req.body;
118666
118742
  const hasBodyField = (field) => Object.prototype.hasOwnProperty.call(req.body, field);
118667
118743
  const validateModelField = (value, name) => {
118668
118744
  if (value === void 0) return void 0;
@@ -118697,6 +118773,40 @@ function createApiRoutes(store, options) {
118697
118773
  throw new Error("enabledWorkflowSteps must be an array of strings");
118698
118774
  }
118699
118775
  }
118776
+ let validatedSourceIssue;
118777
+ if (hasBodyField("sourceIssue")) {
118778
+ if (sourceIssue === null) {
118779
+ validatedSourceIssue = null;
118780
+ } else if (sourceIssue === void 0) {
118781
+ validatedSourceIssue = void 0;
118782
+ } else if (typeof sourceIssue === "object") {
118783
+ const candidate = sourceIssue;
118784
+ if (typeof candidate.provider !== "string" || candidate.provider.trim().length === 0) {
118785
+ throw new Error("sourceIssue.provider must be a non-empty string");
118786
+ }
118787
+ if (typeof candidate.repository !== "string" || candidate.repository.trim().length === 0) {
118788
+ throw new Error("sourceIssue.repository must be a non-empty string");
118789
+ }
118790
+ if (typeof candidate.externalIssueId !== "string" || candidate.externalIssueId.trim().length === 0) {
118791
+ throw new Error("sourceIssue.externalIssueId must be a non-empty string");
118792
+ }
118793
+ if (typeof candidate.issueNumber !== "number" || !Number.isFinite(candidate.issueNumber) || !Number.isInteger(candidate.issueNumber) || candidate.issueNumber <= 0) {
118794
+ throw new Error("sourceIssue.issueNumber must be a positive integer");
118795
+ }
118796
+ if (candidate.url !== void 0 && candidate.url !== null && typeof candidate.url !== "string") {
118797
+ throw new Error("sourceIssue.url must be a string when provided");
118798
+ }
118799
+ validatedSourceIssue = {
118800
+ provider: candidate.provider.trim(),
118801
+ repository: candidate.repository.trim(),
118802
+ externalIssueId: candidate.externalIssueId.trim(),
118803
+ issueNumber: candidate.issueNumber,
118804
+ ...typeof candidate.url === "string" && candidate.url.trim().length > 0 ? { url: candidate.url.trim() } : {}
118805
+ };
118806
+ } else {
118807
+ throw new Error("sourceIssue must be an object or null");
118808
+ }
118809
+ }
118700
118810
  const updates = {};
118701
118811
  if (title !== void 0) updates.title = title;
118702
118812
  if (description !== void 0) updates.description = description;
@@ -118713,13 +118823,14 @@ function createApiRoutes(store, options) {
118713
118823
  if (hasBodyField("assigneeUserId")) updates.assigneeUserId = validatedAssigneeUserId;
118714
118824
  if (hasBodyField("reviewLevel")) updates.reviewLevel = reviewLevel;
118715
118825
  if (hasBodyField("executionMode")) updates.executionMode = executionMode === null ? null : executionMode;
118826
+ if (hasBodyField("sourceIssue")) updates.sourceIssue = validatedSourceIssue === void 0 ? void 0 : validatedSourceIssue;
118716
118827
  const task = await scopedStore.updateTask(req.params.id, updates);
118717
118828
  res.json(task);
118718
118829
  } catch (err) {
118719
118830
  if (err instanceof ApiError) {
118720
118831
  throw err;
118721
118832
  }
118722
- const status = (err instanceof Error ? err.message : String(err)).includes("must be a string") || (err instanceof Error ? err.message : String(err)).includes("must be an array of strings") || (err instanceof Error ? err.message : String(err)).includes("thinkingLevel must be one of") || (err instanceof Error ? err.message : String(err)).includes("reviewLevel must be an integer") || (err instanceof Error ? err.message : String(err)).includes("executionMode must be one of") ? 400 : 500;
118833
+ const status = (err instanceof Error ? err.message : String(err)).includes("must be a string") || (err instanceof Error ? err.message : String(err)).includes("must be an array of strings") || (err instanceof Error ? err.message : String(err)).includes("thinkingLevel must be one of") || (err instanceof Error ? err.message : String(err)).includes("reviewLevel must be an integer") || (err instanceof Error ? err.message : String(err)).includes("executionMode must be one of") || (err instanceof Error ? err.message : String(err)).includes("sourceIssue") ? 400 : 500;
118723
118834
  throw new ApiError(status, err instanceof Error ? err.message : String(err));
118724
118835
  }
118725
118836
  });
@@ -121747,6 +121858,12 @@ ${qaSection}` : `Source: ${session.initialPlan.slice(0, 200)}`;
121747
121858
  return;
121748
121859
  }
121749
121860
  }
121861
+ } else if (lastEventId === void 0) {
121862
+ const buffered = planningStreamManager2.getBufferedEvents(sessionId, 0);
121863
+ if (!replayBufferedSSE2(res, buffered)) {
121864
+ res.end();
121865
+ return;
121866
+ }
121750
121867
  }
121751
121868
  const unsubscribe = planningStreamManager2.subscribe(sessionId, (event, eventId) => {
121752
121869
  const data = event.data;
@@ -136701,7 +136818,11 @@ function StatsPanel({ state, isFocused }) {
136701
136818
  /* @__PURE__ */ jsx(Text, { color: sysMemColor(sys.systemTotalMem - sys.systemFreeMem, sys.systemTotalMem), children: formatBytes2(sys.systemTotalMem - sys.systemFreeMem) }),
136702
136819
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: "used" }),
136703
136820
  /* @__PURE__ */ jsx(Text, { children: formatBytes2(sys.systemFreeMem) }),
136704
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "free" })
136821
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "free" }),
136822
+ sys.systemTotalMem > 0 && /* @__PURE__ */ jsxs(Text, { color: sysMemColor(sys.systemTotalMem - sys.systemFreeMem, sys.systemTotalMem), children: [
136823
+ ((sys.systemTotalMem - sys.systemFreeMem) / sys.systemTotalMem * 100).toFixed(1),
136824
+ "%"
136825
+ ] })
136705
136826
  ] }),
136706
136827
  /* @__PURE__ */ jsxs(StatRow, { label: "Cores", children: [
136707
136828
  /* @__PURE__ */ jsx(Text, { children: sys.cpuCount }),
@@ -136860,11 +136981,16 @@ function ExpandedLog({ entry, index: index2, total }) {
136860
136981
  /* @__PURE__ */ jsx(Text, { wrap: "wrap", children: entry.message })
136861
136982
  ] });
136862
136983
  }
136863
- function UtilitiesPanel({ isFocused }) {
136984
+ function UtilitiesPanel({ state, isFocused }) {
136985
+ const autoKill = state.autoKillVitestOnPressure;
136986
+ const thresholdPct = Math.round(state.vitestKillThreshold * 100);
136864
136987
  const actions = [
136865
136988
  { key: "r", label: "Refresh Stats" },
136866
136989
  { key: "c", label: "Clear Logs" },
136867
136990
  { key: "t", label: "Toggle Engine Pause" },
136991
+ { key: "k", label: "Kill Vitest Processes" },
136992
+ { key: "v", label: `Auto-Kill Vitest >${thresholdPct}% Mem: ${autoKill ? "ON" : "OFF"}` },
136993
+ { key: "+/-", label: `Adjust Threshold (${thresholdPct}%)` },
136868
136994
  { key: "?", label: "Help" }
136869
136995
  ];
136870
136996
  return /* @__PURE__ */ jsx(Panel, { title: "Utilities", isFocused, flexGrow: 1, children: /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: actions.map((action) => /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
@@ -136892,6 +137018,9 @@ function HelpOverlay() {
136892
137018
  ["[\u2190] / [p]", "Previous panel (Main)"],
136893
137019
  ["[r]", "Refresh stats (Utilities)"],
136894
137020
  ["[c]", "Clear logs (Utilities)"],
137021
+ ["[k]", "Kill all vitest processes (Utilities)"],
137022
+ ["[v]", "Toggle auto-kill vitest on memory pressure (Utilities)"],
137023
+ ["[+/-]", "Adjust vitest kill memory threshold (Utilities)"],
136895
137024
  ["[\u2191/\u2193/k/j]", "Navigate list / log entries"],
136896
137025
  ["[Home / G]", "First / last log entry (Logs)"],
136897
137026
  ["[Enter/Space]", "Expand log entry (Logs)"],
@@ -136950,7 +137079,7 @@ function StatusModeGrid({
136950
137079
  }
136951
137080
  ),
136952
137081
  /* @__PURE__ */ jsxs(Box, { flexDirection: "row", overflow: "hidden", children: [
136953
- /* @__PURE__ */ jsx(Box, { flexDirection: "column", flexGrow: 1, overflow: "hidden", children: /* @__PURE__ */ jsx(UtilitiesPanel, { isFocused: focused === "utilities" }) }),
137082
+ /* @__PURE__ */ jsx(Box, { flexDirection: "column", flexGrow: 1, overflow: "hidden", children: /* @__PURE__ */ jsx(UtilitiesPanel, { state, isFocused: focused === "utilities" }) }),
136954
137083
  /* @__PURE__ */ jsx(Box, { flexDirection: "column", flexGrow: 1, overflow: "hidden", children: /* @__PURE__ */ jsx(SettingsPanel, { state, isFocused: focused === "settings" }) })
136955
137084
  ] })
136956
137085
  ] })
@@ -136970,7 +137099,7 @@ function StatusModeSingle({
136970
137099
  case "logs":
136971
137100
  return /* @__PURE__ */ jsx(LogsPanel, { state, isFocused: true, availableRows: Math.max(4, (process.stdout.rows ?? 24) - 8) });
136972
137101
  case "utilities":
136973
- return /* @__PURE__ */ jsx(UtilitiesPanel, { isFocused: true });
137102
+ return /* @__PURE__ */ jsx(UtilitiesPanel, { state, isFocused: true });
136974
137103
  case "stats":
136975
137104
  return /* @__PURE__ */ jsx(StatsPanel, { state, isFocused: true });
136976
137105
  case "settings":
@@ -136989,7 +137118,7 @@ function StatusBar({ state, controller: _controller }) {
136989
137118
  if (activeSection === "logs") {
136990
137119
  hotkeys.push("\u2191\u2193 navigate", "w wrap", "f filter", "Enter expand");
136991
137120
  } else if (activeSection === "utilities") {
136992
- hotkeys.push("r refresh", "c clear logs", "t toggle pause");
137121
+ hotkeys.push("r refresh", "c clear logs", "t toggle pause", "k kill vitest", "v auto-kill", "+/- threshold");
136993
137122
  } else {
136994
137123
  hotkeys.push("Tab cycle panel", "1-5 jump");
136995
137124
  }
@@ -139289,6 +139418,7 @@ var init_app = __esm({
139289
139418
  // src/commands/dashboard-tui/controller.ts
139290
139419
  import os3 from "node:os";
139291
139420
  import v8 from "node:v8";
139421
+ import { execSync as execSync2 } from "node:child_process";
139292
139422
  var DashboardTUI;
139293
139423
  var init_controller = __esm({
139294
139424
  "src/commands/dashboard-tui/controller.ts"() {
@@ -139319,6 +139449,15 @@ var init_controller = __esm({
139319
139449
  logsViewportStart = 0;
139320
139450
  loadingStatus = "Starting\u2026";
139321
139451
  mode = "status";
139452
+ // When true, sampleSystemStats() kills any running vitest processes if
139453
+ // system memory usage crosses 90%. Toggled by [v] in the Utilities panel.
139454
+ autoKillVitestOnPressure = true;
139455
+ // System-memory ratio (0..1) at which auto-kill triggers. Adjustable from
139456
+ // the Utilities panel via [+]/[-] in 5% steps. Clamped to [0.5, 0.99].
139457
+ vitestKillThreshold = 0.9;
139458
+ // Throttle so we don't spam kills while the sampler keeps firing during
139459
+ // sustained pressure (sampler runs every 2s).
139460
+ lastAutoKillAt = 0;
139322
139461
  interactiveData = null;
139323
139462
  interactiveView = "board";
139324
139463
  // Subscribers registered by the Ink App component.
@@ -139366,7 +139505,9 @@ var init_controller = __esm({
139366
139505
  loadingStatus: this.loadingStatus,
139367
139506
  mode: this.mode,
139368
139507
  interactiveData: this.interactiveData,
139369
- interactiveView: this.interactiveView
139508
+ interactiveView: this.interactiveView,
139509
+ autoKillVitestOnPressure: this.autoKillVitestOnPressure,
139510
+ vitestKillThreshold: this.vitestKillThreshold
139370
139511
  };
139371
139512
  return this.cachedSnapshot;
139372
139513
  }
@@ -139439,6 +139580,82 @@ var init_controller = __esm({
139439
139580
  nodeVersion: process.version,
139440
139581
  platform: `${process.platform}/${process.arch}`
139441
139582
  });
139583
+ if (this.autoKillVitestOnPressure) {
139584
+ const total = os3.totalmem();
139585
+ const free = os3.freemem();
139586
+ if (total > 0) {
139587
+ const usedRatio = (total - free) / total;
139588
+ if (usedRatio > this.vitestKillThreshold && now - this.lastAutoKillAt > 3e4) {
139589
+ this.lastAutoKillAt = now;
139590
+ const result = this.killVitestProcesses();
139591
+ if (result.killed > 0) {
139592
+ this.warn(
139593
+ `Auto-killed ${result.killed} vitest process${result.killed === 1 ? "" : "es"} (system memory at ${Math.round(usedRatio * 100)}%, threshold ${Math.round(this.vitestKillThreshold * 100)}%)`,
139594
+ "memory-guard"
139595
+ );
139596
+ }
139597
+ }
139598
+ }
139599
+ }
139600
+ }
139601
+ /**
139602
+ * Find and SIGKILL any running vitest processes, excluding this dashboard
139603
+ * itself. Returns a count of pids signalled (best-effort — a pid may be
139604
+ * gone by the time we send the signal).
139605
+ */
139606
+ killVitestProcesses() {
139607
+ const selfPid = process.pid;
139608
+ let pids = [];
139609
+ try {
139610
+ const out = execSync2("pgrep -f vitest", { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
139611
+ pids = out.split("\n").map((s) => Number.parseInt(s.trim(), 10)).filter((n) => Number.isFinite(n) && n > 0 && n !== selfPid);
139612
+ } catch {
139613
+ return { killed: 0, pids: [] };
139614
+ }
139615
+ let killed = 0;
139616
+ for (const pid of pids) {
139617
+ try {
139618
+ process.kill(pid, "SIGKILL");
139619
+ killed += 1;
139620
+ } catch {
139621
+ }
139622
+ }
139623
+ return { killed, pids };
139624
+ }
139625
+ adjustVitestKillThreshold(deltaPct) {
139626
+ const next = this.vitestKillThreshold + deltaPct / 100;
139627
+ this.vitestKillThreshold = Math.max(0.5, Math.min(0.99, Math.round(next * 100) / 100));
139628
+ this.notify();
139629
+ void this.persistVitestKillSettings({ thresholdPct: Math.round(this.vitestKillThreshold * 100) });
139630
+ return this.vitestKillThreshold;
139631
+ }
139632
+ toggleAutoKillVitest() {
139633
+ this.autoKillVitestOnPressure = !this.autoKillVitestOnPressure;
139634
+ if (!this.autoKillVitestOnPressure) {
139635
+ this.lastAutoKillAt = 0;
139636
+ }
139637
+ this.notify();
139638
+ void this.persistVitestKillSettings({ enabled: this.autoKillVitestOnPressure });
139639
+ return this.autoKillVitestOnPressure;
139640
+ }
139641
+ /** Apply persisted values from global settings on startup. Does not
139642
+ * trigger a write-back. */
139643
+ hydrateVitestKillSettings(values) {
139644
+ if (typeof values.enabled === "boolean") {
139645
+ this.autoKillVitestOnPressure = values.enabled;
139646
+ }
139647
+ if (typeof values.thresholdPct === "number" && Number.isFinite(values.thresholdPct)) {
139648
+ const ratio = values.thresholdPct / 100;
139649
+ this.vitestKillThreshold = Math.max(0.5, Math.min(0.99, ratio));
139650
+ }
139651
+ this.notify();
139652
+ }
139653
+ async persistVitestKillSettings(partial) {
139654
+ if (!this.callbacks?.onPersistVitestKillSettings) return;
139655
+ try {
139656
+ await this.callbacks.onPersistVitestKillSettings(partial);
139657
+ } catch {
139658
+ }
139442
139659
  }
139443
139660
  setSettings(settings) {
139444
139661
  this.settings = settings;
@@ -139552,6 +139769,38 @@ var init_controller = __esm({
139552
139769
  this.setSettings(newSettings);
139553
139770
  }
139554
139771
  break;
139772
+ case "k": {
139773
+ const result = this.killVitestProcesses();
139774
+ if (result.killed === 0) {
139775
+ this.log("No vitest processes found.", "kill-vitest");
139776
+ } else {
139777
+ this.warn(
139778
+ `Killed ${result.killed} vitest process${result.killed === 1 ? "" : "es"}: ${result.pids.join(", ")}`,
139779
+ "kill-vitest"
139780
+ );
139781
+ }
139782
+ break;
139783
+ }
139784
+ case "v": {
139785
+ const enabled = this.toggleAutoKillVitest();
139786
+ this.log(
139787
+ `Auto-kill vitest on memory pressure (>${Math.round(this.vitestKillThreshold * 100)}%): ${enabled ? "ON" : "OFF"}`,
139788
+ "memory-guard"
139789
+ );
139790
+ break;
139791
+ }
139792
+ case "+":
139793
+ case "=": {
139794
+ const v = this.adjustVitestKillThreshold(5);
139795
+ this.log(`Vitest kill threshold: ${Math.round(v * 100)}%`, "memory-guard");
139796
+ break;
139797
+ }
139798
+ case "-":
139799
+ case "_": {
139800
+ const v = this.adjustVitestKillThreshold(-5);
139801
+ this.log(`Vitest kill threshold: ${Math.round(v * 100)}%`, "memory-guard");
139802
+ break;
139803
+ }
139555
139804
  }
139556
139805
  }
139557
139806
  // ── Lifecycle ──────────────────────────────────────────────────────────────
@@ -140224,6 +140473,18 @@ async function runDashboard(port, opts = {}) {
140224
140473
  enginePaused: paused,
140225
140474
  globalPause: false
140226
140475
  };
140476
+ },
140477
+ onPersistVitestKillSettings: async (partial) => {
140478
+ if (!store) return;
140479
+ const patch = {};
140480
+ if (typeof partial.enabled === "boolean") {
140481
+ patch.vitestAutoKillEnabled = partial.enabled;
140482
+ }
140483
+ if (typeof partial.thresholdPct === "number") {
140484
+ patch.vitestKillThresholdPct = partial.thresholdPct;
140485
+ }
140486
+ if (Object.keys(patch).length === 0) return;
140487
+ await store.getGlobalSettingsStore().updateSettings(patch);
140227
140488
  }
140228
140489
  });
140229
140490
  await tui.start();
@@ -140960,6 +141221,14 @@ async function runDashboard(port, opts = {}) {
140960
141221
  enginePaused: settings.enginePaused ?? false,
140961
141222
  globalPause: settings.globalPause ?? false
140962
141223
  });
141224
+ try {
141225
+ const globalSettings = await store.getGlobalSettingsStore().getSettings();
141226
+ tui.hydrateVitestKillSettings({
141227
+ enabled: typeof globalSettings.vitestAutoKillEnabled === "boolean" ? globalSettings.vitestAutoKillEnabled : void 0,
141228
+ thresholdPct: typeof globalSettings.vitestKillThresholdPct === "number" ? globalSettings.vitestKillThresholdPct : void 0
141229
+ });
141230
+ } catch {
141231
+ }
140963
141232
  const tasks = await store.listTasks({ slim: true, includeArchived: false });
140964
141233
  const counts = /* @__PURE__ */ new Map();
140965
141234
  for (const task of tasks) {
@@ -147452,6 +147721,7 @@ import { existsSync as existsSync47, mkdtempSync as mkdtempSync2, readFileSync a
147452
147721
  import { createRequire as createRequire4 } from "node:module";
147453
147722
  import { join as join59, dirname as dirname24 } from "node:path";
147454
147723
  import { tmpdir as tmpdir4 } from "node:os";
147724
+ import { performance as performance3 } from "node:perf_hooks";
147455
147725
  var isBunBinary3 = typeof Bun !== "undefined" && !!Bun.embeddedFiles;
147456
147726
  function configurePiPackage() {
147457
147727
  if (process.env.PI_PACKAGE_DIR) {
@@ -147484,6 +147754,10 @@ function configurePiPackage() {
147484
147754
  process.env.PI_PACKAGE_DIR = tmp;
147485
147755
  }
147486
147756
  configurePiPackage();
147757
+ setInterval(() => {
147758
+ performance3.clearMeasures();
147759
+ performance3.clearMarks();
147760
+ }, 3e4).unref();
147487
147761
  function loadEnvFile(path4) {
147488
147762
  if (!existsSync47(path4)) return;
147489
147763
  const contents = readFileSync17(path4, "utf-8");
@@ -147700,7 +147974,8 @@ Usage:
147700
147974
  fn backup --restore <file> Restore database from a backup file
147701
147975
  fn backup --cleanup Remove old backups exceeding retention limit
147702
147976
  fn plugin list | ls List installed plugins
147703
- fn plugin install <path> Install a plugin from path
147977
+ fn plugin install <path-or-package> Install a plugin from path or package
147978
+ fn plugin add <path-or-package> Alias for plugin install
147704
147979
  fn plugin uninstall <id> [--force] Uninstall a plugin
147705
147980
  fn plugin enable <id> Enable a plugin
147706
147981
  fn plugin disable <id> Disable a plugin
@@ -148602,10 +148877,11 @@ async function main() {
148602
148877
  case "ls":
148603
148878
  await runPluginList2(projectName);
148604
148879
  break;
148605
- case "install": {
148880
+ case "install":
148881
+ case "add": {
148606
148882
  const source = args[2];
148607
148883
  if (!source) {
148608
- console.error("Usage: fn plugin install <path-or-package>");
148884
+ console.error("Usage: fn plugin install <path-or-package> (alias: fn plugin add <path-or-package>)");
148609
148885
  process.exit(1);
148610
148886
  }
148611
148887
  await runPluginInstall2(source, { projectName });
@@ -148650,7 +148926,7 @@ async function main() {
148650
148926
  }
148651
148927
  default:
148652
148928
  console.error(`Unknown subcommand: plugin ${sub || ""}`);
148653
- console.log("Try: fn plugin list | install | uninstall | enable | disable | create");
148929
+ console.log("Try: fn plugin list | install | add (alias for install) | uninstall | enable | disable | create");
148654
148930
  process.exit(1);
148655
148931
  }
148656
148932
  break;
@@ -1,4 +1,4 @@
1
- import{r as a,j as e}from"./vendor-react-K0fH_qHe.js";import{c as xe,c2 as ks,c3 as Cs,c4 as ws,c5 as Rs,L as O,R as ze,af as ts,Z as Ms,ah as Te,I as Ce,K as hs,D as gs,c6 as Fs,c7 as Ts,c8 as As,c9 as Ls,p as W,ca as Ds,s as as,B as ns,E as Ne,J as is,aK as ge,X as zs,F as we,A as Se,v as Ee,cb as Es,b_ as $s,ar as Ps,b5 as Bs,ai as fs,M as Is,U as Os,W as _s,cc as Le,cd as Hs,y as Vs,ce as Us,cf as Js,cg as Ws,ch as qs,ci as Gs,cj as $e,a3 as Pe,a5 as Be,a6 as Ie,C as X,a1 as Ks,ck as Ys,cl as Zs,i as Qs,h as Xs,j as et,cm as De,V as st,cn as tt,co as at,cp as nt,cq as it,cr as rt,cs as lt,ct as ot,cu as ct,Q as dt,cv as ut,N as mt}from"./index-CikysL-d.js";import{S as ht}from"./AgentsView-CWFLMIDP.js";import"./vendor-xterm-DzcZoU0P.js";import"./upload-BDvpReDO.js";import"./folder-open-BVDq27HP.js";/**
1
+ import{r as a,j as e}from"./vendor-react-K0fH_qHe.js";import{c as xe,c2 as ks,c3 as Cs,c4 as ws,c5 as Rs,L as O,R as ze,af as ts,Z as Ms,ah as Te,I as Ce,K as hs,D as gs,c6 as Fs,c7 as Ts,c8 as As,c9 as Ls,p as W,ca as Ds,s as as,B as ns,E as Ne,J as is,aK as ge,X as zs,F as we,A as Se,v as Ee,cb as Es,b_ as $s,ar as Ps,b5 as Bs,ai as fs,M as Is,U as Os,W as _s,cc as Le,cd as Hs,y as Vs,ce as Us,cf as Js,cg as Ws,ch as qs,ci as Gs,cj as $e,a3 as Pe,a5 as Be,a6 as Ie,C as X,a1 as Ks,ck as Ys,cl as Zs,i as Qs,h as Xs,j as et,cm as De,V as st,cn as tt,co as at,cp as nt,cq as it,cr as rt,cs as lt,ct as ot,cu as ct,Q as dt,cv as ut,N as mt}from"./index-rNf7s96d.js";import{S as ht}from"./AgentsView-yCYBY2km.js";import"./vendor-xterm-DzcZoU0P.js";import"./upload-CAlKC4qI.js";import"./folder-open-CI4TCD7P.js";/**
2
2
  * @license lucide-react v1.7.0 - ISC
3
3
  *
4
4
  * This source code is licensed under the ISC license.