@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
package/dist/bin.js CHANGED
@@ -81,6 +81,7 @@ var init_settings_schema = __esm({
81
81
  showGitHubStarButton: true,
82
82
  modelOnboardingComplete: void 0,
83
83
  useClaudeCli: void 0,
84
+ useDroidCli: void 0,
84
85
  // Global baseline lanes for per-role model selection
85
86
  executionGlobalProvider: void 0,
86
87
  executionGlobalModelId: void 0,
@@ -2706,7 +2707,7 @@ var init_db = __esm({
2706
2707
  "use strict";
2707
2708
  init_sqlite_adapter();
2708
2709
  init_types();
2709
- SCHEMA_VERSION = 55;
2710
+ SCHEMA_VERSION = 57;
2710
2711
  SCHEMA_SQL = `
2711
2712
  -- Tasks table with JSON columns for nested data
2712
2713
  CREATE TABLE IF NOT EXISTS tasks (
@@ -4390,6 +4391,23 @@ CREATE INDEX IF NOT EXISTS idxTodoItemsSortOrder ON todo_items(listId, sortOrder
4390
4391
  this.db.exec(`CREATE INDEX IF NOT EXISTS idxResearchExportsRunId ON research_exports(runId)`);
4391
4392
  });
4392
4393
  }
4394
+ if (version < 56) {
4395
+ this.applyMigration(56, () => {
4396
+ if (this.hasTable("chat_sessions")) {
4397
+ this.addColumnIfMissing("chat_sessions", "cliSessionFile", "TEXT");
4398
+ }
4399
+ });
4400
+ }
4401
+ if (version < 57) {
4402
+ this.applyMigration(57, () => {
4403
+ if (this.hasTable("ai_sessions")) {
4404
+ this.addColumnIfMissing("ai_sessions", "archived", "INTEGER DEFAULT 0");
4405
+ this.db.exec(
4406
+ "CREATE INDEX IF NOT EXISTS idxAiSessionsArchived ON ai_sessions(archived)"
4407
+ );
4408
+ }
4409
+ });
4410
+ }
4393
4411
  }
4394
4412
  /**
4395
4413
  * Run a single migration step inside a transaction and bump the version.
@@ -38006,7 +38024,7 @@ function sanitizeTitle(raw) {
38006
38024
  const firstLine = raw.split(/\r?\n/).map((l) => l.trim()).find((l) => l.length > 0);
38007
38025
  if (!firstLine) return null;
38008
38026
  let title = firstLine.replace(/^[-*]\s+/, "").replace(/^["'`]+|["'`]+$/g, "").trim();
38009
- title = title.replace(/^(?:title|subject|here(?:'s| is)(?: the)? title|generated title)\s*[:\-]\s*/i, "").trim();
38027
+ title = title.replace(/^(?:title|subject|here(?:'s| is)(?: the)? title|generated title)\s*[:-]\s*/i, "").trim();
38010
38028
  title = title.replace(/\*\*([^*]+)\*\*/g, "$1").replace(/__([^_]+)__/g, "$1").replace(/(?<![*\w])\*([^*]+)\*(?![*\w])/g, "$1").replace(/(?<![_\w])_([^_]+)_(?![_\w])/g, "$1");
38011
38029
  title = title.replace(/[.!?,;:]+$/, "").trim();
38012
38030
  if (!title) return null;
@@ -49954,7 +49972,8 @@ var init_chat_store = __esm({
49954
49972
  modelProvider: row.modelProvider ?? null,
49955
49973
  modelId: row.modelId ?? null,
49956
49974
  createdAt: row.createdAt,
49957
- updatedAt: row.updatedAt
49975
+ updatedAt: row.updatedAt,
49976
+ cliSessionFile: row.cliSessionFile ?? null
49958
49977
  };
49959
49978
  }
49960
49979
  /**
@@ -49991,7 +50010,8 @@ var init_chat_store = __esm({
49991
50010
  modelProvider: input.modelProvider ?? null,
49992
50011
  modelId: input.modelId ?? null,
49993
50012
  createdAt: now,
49994
- updatedAt: now
50013
+ updatedAt: now,
50014
+ cliSessionFile: null
49995
50015
  };
49996
50016
  this.db.prepare(`
49997
50017
  INSERT INTO chat_sessions (id, agentId, title, status, projectId, modelProvider, modelId, createdAt, updatedAt)
@@ -50149,6 +50169,21 @@ var init_chat_store = __esm({
50149
50169
  archiveSession(id) {
50150
50170
  return this.updateSession(id, { status: "archived" });
50151
50171
  }
50172
+ /**
50173
+ * Persist the pi/Claude CLI session file path for a chat. Called once,
50174
+ * after the SessionManager for the chat first creates its on-disk file,
50175
+ * so subsequent turns can reopen it via SessionManager.open.
50176
+ *
50177
+ * Does not bump updatedAt or emit events — this is internal plumbing,
50178
+ * not a user-visible state change.
50179
+ *
50180
+ * @param id - Session ID
50181
+ * @param cliSessionFile - Absolute path to the session file, or null to clear
50182
+ */
50183
+ setCliSessionFile(id, cliSessionFile) {
50184
+ this.db.prepare("UPDATE chat_sessions SET cliSessionFile = ? WHERE id = ?").run(cliSessionFile, id);
50185
+ this.db.bumpLastModified();
50186
+ }
50152
50187
  /**
50153
50188
  * Delete a chat session and all its messages.
50154
50189
  * Messages are cascade-deleted via foreign key constraint.
@@ -50982,7 +51017,8 @@ function toSummary(session, updatedAt) {
50982
51017
  title: session.title,
50983
51018
  projectId: session.projectId,
50984
51019
  lockedByTab: session.lockedByTab ?? null,
50985
- updatedAt
51020
+ updatedAt,
51021
+ archived: Number(session.archived ?? 0) === 1
50986
51022
  };
50987
51023
  }
50988
51024
  var MAX_THINKING_BYTES, THINKING_DEBOUNCE_MS, SESSION_CLEANUP_DEFAULT_MAX_AGE_MS, SESSION_CLEANUP_INTERVAL_MS, diagnostics, AiSessionStore;
@@ -51107,17 +51143,80 @@ var init_ai_session_store = __esm({
51107
51143
  listActive(projectId) {
51108
51144
  if (projectId) {
51109
51145
  return this.db.prepare(
51110
- `SELECT id, type, status, title, projectId, lockedByTab, updatedAt FROM ai_sessions
51111
- WHERE status IN ('generating', 'awaiting_input', 'error') AND projectId = ?
51146
+ `SELECT id, type, status, title, projectId, lockedByTab, updatedAt, archived FROM ai_sessions
51147
+ WHERE status IN ('generating', 'awaiting_input', 'error')
51148
+ AND COALESCE(archived, 0) = 0
51149
+ AND projectId = ?
51112
51150
  ORDER BY updatedAt DESC`
51113
51151
  ).all(projectId);
51114
51152
  }
51115
51153
  return this.db.prepare(
51116
- `SELECT id, type, status, title, projectId, lockedByTab, updatedAt FROM ai_sessions
51154
+ `SELECT id, type, status, title, projectId, lockedByTab, updatedAt, archived FROM ai_sessions
51117
51155
  WHERE status IN ('generating', 'awaiting_input', 'error')
51156
+ AND COALESCE(archived, 0) = 0
51118
51157
  ORDER BY updatedAt DESC`
51119
51158
  ).all();
51120
51159
  }
51160
+ /**
51161
+ * List sessions regardless of status (including `complete`).
51162
+ * Used by the planning sidebar so previously completed sessions remain
51163
+ * selectable on refresh — `listActive` filters them out, which would
51164
+ * otherwise hide a session that finished while the modal was closed.
51165
+ * By default archived sessions are excluded; pass `includeArchived` to
51166
+ * surface them too. Completed sessions are pruned by `cleanupOld` after
51167
+ * the configured TTL, so this list does not grow unbounded.
51168
+ */
51169
+ listAll(projectId, options) {
51170
+ const archivedClause = options?.includeArchived ? "" : " WHERE COALESCE(archived, 0) = 0";
51171
+ if (projectId) {
51172
+ const where = options?.includeArchived ? "WHERE projectId = ?" : "WHERE projectId = ? AND COALESCE(archived, 0) = 0";
51173
+ return this.db.prepare(
51174
+ `SELECT id, type, status, title, projectId, lockedByTab, updatedAt, archived FROM ai_sessions
51175
+ ${where}
51176
+ ORDER BY updatedAt DESC`
51177
+ ).all(projectId);
51178
+ }
51179
+ return this.db.prepare(
51180
+ `SELECT id, type, status, title, projectId, lockedByTab, updatedAt, archived FROM ai_sessions
51181
+ ${archivedClause}
51182
+ ORDER BY updatedAt DESC`
51183
+ ).all();
51184
+ }
51185
+ /**
51186
+ * Mark a session as archived (hidden from planning sidebar). Only
51187
+ * terminal sessions (`complete` or `error`) are archivable — archiving
51188
+ * an in-flight session would orphan the live agent. Returns true when
51189
+ * the row was updated. Emits `ai_session:updated` so other tabs sync.
51190
+ */
51191
+ archive(id) {
51192
+ const now = (/* @__PURE__ */ new Date()).toISOString();
51193
+ const result = this.db.prepare(
51194
+ `UPDATE ai_sessions
51195
+ SET archived = 1, updatedAt = ?
51196
+ WHERE id = ? AND status IN ('complete', 'error')`
51197
+ ).run(now, id);
51198
+ const changed = Number(result.changes ?? 0) > 0;
51199
+ if (changed) {
51200
+ const row = this.get(id);
51201
+ if (row) this.emit("ai_session:updated", toSummary(row, row.updatedAt));
51202
+ }
51203
+ return changed;
51204
+ }
51205
+ /** Restore an archived session so it reappears in the sidebar. */
51206
+ unarchive(id) {
51207
+ const now = (/* @__PURE__ */ new Date()).toISOString();
51208
+ const result = this.db.prepare(
51209
+ `UPDATE ai_sessions
51210
+ SET archived = 0, updatedAt = ?
51211
+ WHERE id = ?`
51212
+ ).run(now, id);
51213
+ const changed = Number(result.changes ?? 0) > 0;
51214
+ if (changed) {
51215
+ const row = this.get(id);
51216
+ if (row) this.emit("ai_session:updated", toSummary(row, row.updatedAt));
51217
+ }
51218
+ return changed;
51219
+ }
51121
51220
  /**
51122
51221
  * List recoverable sessions for in-memory rehydration.
51123
51222
  * Returns full rows for sessions still in progress.
@@ -84287,25 +84386,21 @@ async function ensureNtfyHelpersReady() {
84287
84386
  if (planningNtfyHelpers) {
84288
84387
  return;
84289
84388
  }
84290
- try {
84291
- const engine = await Promise.resolve().then(() => (init_src2(), src_exports2));
84292
- const hasNotificationService = "NotificationService" in engine && typeof engine.NotificationService === "function";
84293
- const hasAllHelpers = "isNtfyEventEnabled" in engine && "buildNtfyClickUrl" in engine && "sendNtfyNotification" in engine && typeof engine.isNtfyEventEnabled === "function" && typeof engine.buildNtfyClickUrl === "function" && typeof engine.sendNtfyNotification === "function";
84294
- if (!hasAllHelpers) {
84295
- return;
84296
- }
84297
- planningNtfyHelpers = {
84298
- isNtfyEventEnabled: engine.isNtfyEventEnabled,
84299
- buildNtfyClickUrl: engine.buildNtfyClickUrl,
84300
- sendNtfyNotification: engine.sendNtfyNotification
84301
- };
84302
- if (hasNotificationService) {
84303
- diagnostics2.info(
84304
- "NotificationService abstraction detected in engine",
84305
- { operation: "notification-service-detection" }
84306
- );
84307
- }
84308
- } catch {
84389
+ const hasNotificationService = "NotificationService" in src_exports2 && typeof NotificationService === "function";
84390
+ const hasAllHelpers = "isNtfyEventEnabled" in src_exports2 && "buildNtfyClickUrl" in src_exports2 && "sendNtfyNotification" in src_exports2 && typeof isNtfyEventEnabled === "function" && typeof buildNtfyClickUrl === "function" && typeof sendNtfyNotification === "function";
84391
+ if (!hasAllHelpers) {
84392
+ return;
84393
+ }
84394
+ planningNtfyHelpers = {
84395
+ isNtfyEventEnabled,
84396
+ buildNtfyClickUrl,
84397
+ sendNtfyNotification
84398
+ };
84399
+ if (hasNotificationService) {
84400
+ diagnostics2.info(
84401
+ "NotificationService abstraction detected in engine",
84402
+ { operation: "notification-service-detection" }
84403
+ );
84309
84404
  }
84310
84405
  }
84311
84406
  function safeParseJson(text, fallback2, options) {
@@ -85338,6 +85433,7 @@ var init_planning = __esm({
85338
85433
  init_sse_buffer();
85339
85434
  init_ai_session_diagnostics();
85340
85435
  init_src2();
85436
+ init_src2();
85341
85437
  createFnAgent4 = createFnAgent2;
85342
85438
  diagnostics2 = createSessionDiagnostics("planning");
85343
85439
  PLANNING_SYSTEM_PROMPT = `You are a planning assistant for the fn task board system.
@@ -91748,7 +91844,9 @@ __export(chat_exports, {
91748
91844
  resolveFileReferences: () => resolveFileReferences
91749
91845
  });
91750
91846
  import { EventEmitter as EventEmitter29 } from "node:events";
91847
+ import { existsSync as existsSync30 } from "node:fs";
91751
91848
  import { join as join40, resolve as resolve18, relative as relative9 } from "node:path";
91849
+ import { SessionManager as SessionManager3 } from "@mariozechner/pi-coding-agent";
91752
91850
  function __getChatDiagnostics() {
91753
91851
  return _diagnostics;
91754
91852
  }
@@ -91759,12 +91857,8 @@ async function ensureEngineReady5() {
91759
91857
  if (buildAgentChatPromptFn) {
91760
91858
  return;
91761
91859
  }
91762
- try {
91763
- const engine = await Promise.resolve().then(() => (init_src2(), src_exports2));
91764
- if ("buildAgentChatPrompt" in engine && typeof engine.buildAgentChatPrompt === "function") {
91765
- buildAgentChatPromptFn = engine.buildAgentChatPrompt;
91766
- }
91767
- } catch {
91860
+ if ("buildAgentChatPrompt" in src_exports2 && typeof buildAgentChatPrompt === "function") {
91861
+ buildAgentChatPromptFn = buildAgentChatPrompt;
91768
91862
  }
91769
91863
  }
91770
91864
  function formatAttachmentSize(size) {
@@ -91874,6 +91968,7 @@ var init_chat = __esm({
91874
91968
  init_src();
91875
91969
  init_sse_buffer();
91876
91970
  init_src2();
91971
+ init_src2();
91877
91972
  createFnAgent8 = createFnAgent2;
91878
91973
  defaultDiagnostics = {
91879
91974
  log(message, ...args) {
@@ -92000,6 +92095,48 @@ var init_chat = __esm({
92000
92095
  }
92001
92096
  agentStoreReady;
92002
92097
  activeGenerations = /* @__PURE__ */ new Map();
92098
+ /**
92099
+ * Resolve the per-chat pi/Claude CLI SessionManager.
92100
+ *
92101
+ * - If the chat has a recorded session file that still exists on disk,
92102
+ * reopen it so the CLI --resume sees the full prior transcript.
92103
+ * - Otherwise, create a fresh file-backed session and persist its path
92104
+ * on the chat row. The path is computed synchronously by SessionManager
92105
+ * on construction, so we can store it before the first prompt() call.
92106
+ * - If a recorded path has gone missing (manual cleanup, disk wipe), fall
92107
+ * through to "create" and overwrite the stale pointer.
92108
+ *
92109
+ * Note: we deliberately use file-backed sessions even though pi's history
92110
+ * is also tracked in chat_messages. The file is what the Claude CLI's
92111
+ * --resume reads, and its session id is what pi-claude-cli passes as
92112
+ * `--session-id`. Pinning both via SessionManager.open is the only way to
92113
+ * keep the CLI session stable across user messages.
92114
+ */
92115
+ resolveCliSessionManager(session) {
92116
+ if (session.cliSessionFile && existsSync30(session.cliSessionFile)) {
92117
+ try {
92118
+ return SessionManager3.open(session.cliSessionFile);
92119
+ } catch (err) {
92120
+ const message = err instanceof Error ? err.message : String(err);
92121
+ diagnostics6.warn(
92122
+ `Failed to reopen chat ${session.id} CLI session at ${session.cliSessionFile} (${message}); starting fresh`
92123
+ );
92124
+ }
92125
+ }
92126
+ const manager = SessionManager3.create(this.rootDir);
92127
+ const sessionFile = manager.getSessionFile();
92128
+ if (sessionFile) {
92129
+ try {
92130
+ this.chatStore.setCliSessionFile(session.id, sessionFile);
92131
+ } catch (err) {
92132
+ const message = err instanceof Error ? err.message : String(err);
92133
+ diagnostics6.warn(
92134
+ `Failed to persist CLI session file for chat ${session.id}: ${message}`
92135
+ );
92136
+ }
92137
+ }
92138
+ return manager;
92139
+ }
92003
92140
  async listAgentsForMentions() {
92004
92141
  if (!this.agentStore) {
92005
92142
  return [];
@@ -92197,30 +92334,15 @@ var init_chat = __esm({
92197
92334
  ${mentionContext}`;
92198
92335
  }
92199
92336
  }
92200
- const allMessages = this.chatStore.getMessages(sessionId, { limit: 1e4 }) ?? [];
92201
- const previousMessages = allMessages.slice(-51, -1);
92202
- const conversationMessages = previousMessages.filter(
92203
- (message) => message.role === "user" || message.role === "assistant"
92204
- );
92205
92337
  const resolvedContent = await resolveFileReferences(content, this.rootDir);
92206
92338
  const attachmentSummary = attachments && attachments.length > 0 ? `[User attached: ${attachments.map((attachment) => `${attachment.originalName} (${attachment.mimeType}, ${formatAttachmentSize(attachment.size)})`).join(", ")}]` : "";
92207
- const promptContent = conversationMessages.length > 0 ? [
92208
- "## Previous Conversation",
92209
- "",
92210
- ...conversationMessages.map((message) => {
92211
- const speaker = message.role === "user" ? "User" : "Assistant";
92212
- return `[${speaker}]: ${message.content}`;
92213
- }),
92214
- "",
92215
- "## Current Message",
92216
- "",
92217
- attachmentSummary,
92218
- resolvedContent
92219
- ].filter(Boolean).join("\n") : [attachmentSummary, resolvedContent].filter(Boolean).join("\n\n");
92339
+ const promptContent = [attachmentSummary, resolvedContent].filter(Boolean).join("\n\n");
92340
+ const sessionManager = this.resolveCliSessionManager(session);
92220
92341
  agentResult = await createFnAgent8({
92221
92342
  cwd: this.rootDir,
92222
92343
  systemPrompt,
92223
92344
  tools: "coding",
92345
+ sessionManager,
92224
92346
  ...effectiveModelProvider && effectiveModelId ? {
92225
92347
  defaultProvider: effectiveModelProvider,
92226
92348
  defaultModelId: effectiveModelId
@@ -98688,6 +98810,12 @@ function registerSettingsMemoryRoutes(ctx, deps) {
98688
98810
  prevUseClaudeCli = priorGlobal.useClaudeCli === true;
98689
98811
  } catch {
98690
98812
  }
98813
+ let prevUseDroidCli = false;
98814
+ try {
98815
+ const priorGlobal = await store.getGlobalSettingsStore().getSettings();
98816
+ prevUseDroidCli = priorGlobal.useDroidCli === true;
98817
+ } catch {
98818
+ }
98691
98819
  const settings = await store.updateGlobalSettings(req.body);
98692
98820
  invalidateAllGlobalSettingsCaches();
98693
98821
  const engineManager = options?.engineManager;
@@ -98706,6 +98834,16 @@ function registerSettingsMemoryRoutes(ctx, deps) {
98706
98834
  );
98707
98835
  }
98708
98836
  }
98837
+ const nextUseDroidCli = settings.useDroidCli === true;
98838
+ if (options?.onUseDroidCliToggled && prevUseDroidCli !== nextUseDroidCli) {
98839
+ try {
98840
+ options.onUseDroidCliToggled(prevUseDroidCli, nextUseDroidCli);
98841
+ } catch (hookErr) {
98842
+ runtimeLogger.warn(
98843
+ `onUseDroidCliToggled callback threw: ${hookErr instanceof Error ? hookErr.message : String(hookErr)}`
98844
+ );
98845
+ }
98846
+ }
98709
98847
  res.json(settings);
98710
98848
  } catch (err) {
98711
98849
  if (err instanceof ApiError) {
@@ -133696,8 +133834,7 @@ function registerAgentCoreListCreateRoutes(ctx, deps) {
133696
133834
  const expectedDefaultPath = getDefaultHeartbeatProcedurePath(agent.id);
133697
133835
  if (agent.heartbeatProcedurePath === expectedDefaultPath) {
133698
133836
  try {
133699
- const { ensureDefaultHeartbeatProcedureFile: ensureDefaultHeartbeatProcedureFile2, HEARTBEAT_PROCEDURE: HEARTBEAT_PROCEDURE2 } = await Promise.resolve().then(() => (init_src2(), src_exports2));
133700
- await ensureDefaultHeartbeatProcedureFile2(scopedStore.getRootDir(), expectedDefaultPath, HEARTBEAT_PROCEDURE2);
133837
+ await ensureDefaultHeartbeatProcedureFile(scopedStore.getRootDir(), expectedDefaultPath, HEARTBEAT_PROCEDURE);
133701
133838
  } catch {
133702
133839
  }
133703
133840
  }
@@ -133953,11 +134090,10 @@ function registerAgentCoreRoutes(ctx, deps) {
133953
134090
  throw notFound(`agent ${req.params.id} not found`);
133954
134091
  }
133955
134092
  const targetPath = getDefaultHeartbeatProcedurePath(req.params.id);
133956
- const { ensureDefaultHeartbeatProcedureFile: ensureDefaultHeartbeatProcedureFile2, HEARTBEAT_PROCEDURE: HEARTBEAT_PROCEDURE2 } = await Promise.resolve().then(() => (init_src2(), src_exports2));
133957
- const filePath = await ensureDefaultHeartbeatProcedureFile2(
134093
+ const filePath = await ensureDefaultHeartbeatProcedureFile(
133958
134094
  scopedStore.getRootDir(),
133959
134095
  targetPath,
133960
- HEARTBEAT_PROCEDURE2
134096
+ HEARTBEAT_PROCEDURE
133961
134097
  );
133962
134098
  const updated = await agentStore.updateAgent(req.params.id, {
133963
134099
  heartbeatProcedurePath: targetPath
@@ -134046,6 +134182,7 @@ var init_register_agent_core_routes = __esm({
134046
134182
  "use strict";
134047
134183
  init_src();
134048
134184
  init_api_error();
134185
+ init_src2();
134049
134186
  }
134050
134187
  });
134051
134188
 
@@ -135116,7 +135253,6 @@ function registerAgentReflectionRatingRoutes(ctx) {
135116
135253
  try {
135117
135254
  const { store: taskStore } = await getProjectContext3(req);
135118
135255
  const { AgentStore: AgentStore2, ReflectionStore: ReflectionStore2 } = await Promise.resolve().then(() => (init_src(), src_exports));
135119
- const { AgentReflectionService: AgentReflectionService2 } = await Promise.resolve().then(() => (init_src2(), src_exports2));
135120
135256
  const agentStore = new AgentStore2({ rootDir: taskStore.getFusionDir() });
135121
135257
  const reflectionStore = new ReflectionStore2({ rootDir: taskStore.getFusionDir() });
135122
135258
  await agentStore.init();
@@ -135129,6 +135265,11 @@ function registerAgentReflectionRatingRoutes(ctx) {
135129
135265
  if (!agent) {
135130
135266
  throw notFound("Agent not found");
135131
135267
  }
135268
+ const AgentReflectionService2 = AgentReflectionServiceBinding;
135269
+ if (!AgentReflectionService2) {
135270
+ res.status(503).json({ error: "Reflection service not available" });
135271
+ return;
135272
+ }
135132
135273
  const reflectionService = new AgentReflectionService2({
135133
135274
  agentStore,
135134
135275
  taskStore,
@@ -135190,14 +135331,7 @@ function registerAgentReflectionRatingRoutes(ctx) {
135190
135331
  if (!agent) {
135191
135332
  throw notFound("Agent not found");
135192
135333
  }
135193
- let AgentReflectionService2;
135194
- try {
135195
- const engine = await Promise.resolve().then(() => (init_src2(), src_exports2));
135196
- AgentReflectionService2 = engine.AgentReflectionService;
135197
- } catch {
135198
- res.status(503).json({ error: "Reflection service not available" });
135199
- return;
135200
- }
135334
+ const AgentReflectionService2 = AgentReflectionServiceBinding;
135201
135335
  if (!AgentReflectionService2) {
135202
135336
  res.status(503).json({ error: "Reflection service not available" });
135203
135337
  return;
@@ -135311,10 +135445,13 @@ function registerAgentReflectionRatingRoutes(ctx) {
135311
135445
  }
135312
135446
  });
135313
135447
  }
135448
+ var AgentReflectionServiceBinding;
135314
135449
  var init_register_agent_reflection_rating_routes = __esm({
135315
135450
  "../dashboard/src/routes/register-agent-reflection-rating-routes.ts"() {
135316
135451
  "use strict";
135317
135452
  init_api_error();
135453
+ init_src2();
135454
+ AgentReflectionServiceBinding = "AgentReflectionService" in src_exports2 && typeof AgentReflectionService === "function" ? AgentReflectionService : void 0;
135318
135455
  }
135319
135456
  });
135320
135457
 
@@ -138898,6 +139035,99 @@ var init_claude_cli_probe = __esm({
138898
139035
  }
138899
139036
  });
138900
139037
 
139038
+ // ../dashboard/src/droid-cli-probe.ts
139039
+ import { spawn as spawn11 } from "node:child_process";
139040
+ async function probeDroidCli(options = {}) {
139041
+ const startedAt = Date.now();
139042
+ const timeoutMs = options.timeoutMs ?? PROBE_TIMEOUT_MS2;
139043
+ const binaryPath = await tryResolveBinaryPath4("droid");
139044
+ return new Promise((resolvePromise) => {
139045
+ const finish = (result) => {
139046
+ resolvePromise({ ...result, probeDurationMs: Date.now() - startedAt });
139047
+ };
139048
+ let settled = false;
139049
+ const child = spawn11(binaryPath ?? "droid", ["--version"], {
139050
+ stdio: ["ignore", "pipe", "pipe"]
139051
+ });
139052
+ const timer = setTimeout(() => {
139053
+ if (settled) return;
139054
+ settled = true;
139055
+ try {
139056
+ child.kill("SIGKILL");
139057
+ } catch {
139058
+ }
139059
+ finish({
139060
+ available: false,
139061
+ binaryPath,
139062
+ reason: `Probe timed out after ${timeoutMs}ms`
139063
+ });
139064
+ }, timeoutMs);
139065
+ let stdout = "";
139066
+ let stderr = "";
139067
+ child.stdout?.on("data", (chunk) => {
139068
+ stdout += chunk.toString("utf-8");
139069
+ });
139070
+ child.stderr?.on("data", (chunk) => {
139071
+ stderr += chunk.toString("utf-8");
139072
+ });
139073
+ child.on("error", (err) => {
139074
+ if (settled) return;
139075
+ settled = true;
139076
+ clearTimeout(timer);
139077
+ const isNotFound = err.code === "ENOENT";
139078
+ finish({
139079
+ available: false,
139080
+ binaryPath,
139081
+ reason: isNotFound ? "`droid` not found on PATH" : err.message
139082
+ });
139083
+ });
139084
+ child.on("close", (code) => {
139085
+ if (settled) return;
139086
+ settled = true;
139087
+ clearTimeout(timer);
139088
+ if (code === 0) {
139089
+ finish({
139090
+ available: true,
139091
+ version: stdout.trim() || void 0,
139092
+ binaryPath
139093
+ });
139094
+ } else {
139095
+ finish({
139096
+ available: false,
139097
+ binaryPath,
139098
+ reason: stderr.trim() || `droid --version exited with code ${String(code)}`
139099
+ });
139100
+ }
139101
+ });
139102
+ });
139103
+ }
139104
+ async function tryResolveBinaryPath4(binary) {
139105
+ return new Promise((resolvePromise) => {
139106
+ const which = process.platform === "win32" ? "where" : "which";
139107
+ const child = spawn11(which, [binary], { stdio: ["ignore", "pipe", "ignore"] });
139108
+ let out = "";
139109
+ child.stdout?.on("data", (chunk) => {
139110
+ out += chunk.toString("utf-8");
139111
+ });
139112
+ child.on("error", () => resolvePromise(void 0));
139113
+ child.on("close", (code) => {
139114
+ if (code === 0) {
139115
+ const first = out.trim().split(/\r?\n/)[0];
139116
+ resolvePromise(first?.length ? first : void 0);
139117
+ } else {
139118
+ resolvePromise(void 0);
139119
+ }
139120
+ });
139121
+ });
139122
+ }
139123
+ var PROBE_TIMEOUT_MS2;
139124
+ var init_droid_cli_probe = __esm({
139125
+ "../dashboard/src/droid-cli-probe.ts"() {
139126
+ "use strict";
139127
+ PROBE_TIMEOUT_MS2 = 2e3;
139128
+ }
139129
+ });
139130
+
138901
139131
  // ../dashboard/src/routes/register-auth-routes.ts
138902
139132
  var registerAuthRoutes;
138903
139133
  var init_register_auth_routes = __esm({
@@ -138905,6 +139135,7 @@ var init_register_auth_routes = __esm({
138905
139135
  "use strict";
138906
139136
  init_src();
138907
139137
  init_claude_cli_probe();
139138
+ init_droid_cli_probe();
138908
139139
  init_api_error();
138909
139140
  init_usage();
138910
139141
  init_project_store_resolver();
@@ -139032,6 +139263,23 @@ var init_register_auth_routes = __esm({
139032
139263
  type: "cli"
139033
139264
  });
139034
139265
  }
139266
+ if (store) {
139267
+ let droidEnabled = false;
139268
+ try {
139269
+ const globalSettings = await store.getGlobalSettingsStore().getSettings();
139270
+ droidEnabled = globalSettings.useDroidCli === true;
139271
+ } catch {
139272
+ }
139273
+ const droidExtension = options?.getDroidCliExtensionStatus?.() ?? null;
139274
+ const droidBinary = await probeDroidCli();
139275
+ const droidExtensionOk = droidExtension === null || droidExtension.status === "ok";
139276
+ providers.push({
139277
+ id: "droid-cli",
139278
+ name: "Factory AI \u2014 via Droid CLI",
139279
+ authenticated: droidEnabled && droidBinary.available && droidExtensionOk,
139280
+ type: "cli"
139281
+ });
139282
+ }
139035
139283
  const ghCli = {
139036
139284
  available: isGhAvailable(),
139037
139285
  authenticated: isGhAuthenticated()
@@ -139100,6 +139348,61 @@ var init_register_auth_routes = __esm({
139100
139348
  rethrowAsApiError8(err);
139101
139349
  }
139102
139350
  });
139351
+ router.post("/auth/droid-cli", async (req, res) => {
139352
+ try {
139353
+ if (!store) {
139354
+ throw new ApiError(500, "Settings store unavailable");
139355
+ }
139356
+ const enabled = req.body?.enabled;
139357
+ if (typeof enabled !== "boolean") {
139358
+ throw badRequest("enabled must be a boolean");
139359
+ }
139360
+ if (enabled) {
139361
+ const binary = await probeDroidCli();
139362
+ if (!binary.available) {
139363
+ throw new ApiError(
139364
+ 400,
139365
+ `Cannot enable Droid CLI routing: ${binary.reason ?? "droid binary not available"}`
139366
+ );
139367
+ }
139368
+ }
139369
+ let prev = false;
139370
+ try {
139371
+ const priorGlobal = await store.getGlobalSettingsStore().getSettings();
139372
+ prev = priorGlobal.useDroidCli === true;
139373
+ } catch {
139374
+ }
139375
+ const settings = await store.updateGlobalSettings({ useDroidCli: enabled });
139376
+ invalidateAllGlobalSettingsCaches();
139377
+ const engineManager = options?.engineManager;
139378
+ if (engineManager) {
139379
+ for (const engine of engineManager.getAllEngines().values()) {
139380
+ engine.getTaskStore().getGlobalSettingsStore().invalidateCache();
139381
+ }
139382
+ }
139383
+ const next = settings.useDroidCli === true;
139384
+ if (options?.onUseDroidCliToggled && prev !== next) {
139385
+ try {
139386
+ options.onUseDroidCliToggled(prev, next);
139387
+ } catch (hookErr) {
139388
+ console.warn(
139389
+ `[auth/droid-cli] onUseDroidCliToggled callback threw: ${hookErr instanceof Error ? hookErr.message : String(hookErr)}`
139390
+ );
139391
+ }
139392
+ }
139393
+ res.json({
139394
+ enabled: next,
139395
+ // The droid-cli provider toggle flips provider routing state and takes
139396
+ // effect immediately for new model selections. No restart needed.
139397
+ restartRequired: false
139398
+ });
139399
+ } catch (err) {
139400
+ if (err instanceof ApiError) {
139401
+ throw err;
139402
+ }
139403
+ rethrowAsApiError8(err);
139404
+ }
139405
+ });
139103
139406
  router.get("/providers/claude-cli/status", async (_req, res) => {
139104
139407
  try {
139105
139408
  const binary = await probeClaudeCli();
@@ -139129,6 +139432,31 @@ var init_register_auth_routes = __esm({
139129
139432
  rethrowAsApiError8(err);
139130
139433
  }
139131
139434
  });
139435
+ router.get("/providers/droid-cli/status", async (_req, res) => {
139436
+ try {
139437
+ const binary = await probeDroidCli();
139438
+ let enabled = false;
139439
+ if (store) {
139440
+ try {
139441
+ const globalSettings = await store.getGlobalSettingsStore().getSettings();
139442
+ enabled = globalSettings.useDroidCli === true;
139443
+ } catch {
139444
+ }
139445
+ }
139446
+ const extension2 = options?.getDroidCliExtensionStatus?.() ?? null;
139447
+ res.json({
139448
+ binary,
139449
+ enabled,
139450
+ extension: extension2,
139451
+ ready: binary.available && enabled && (extension2 === null || extension2.status === "ok")
139452
+ });
139453
+ } catch (err) {
139454
+ if (err instanceof ApiError) {
139455
+ throw err;
139456
+ }
139457
+ rethrowAsApiError8(err);
139458
+ }
139459
+ });
139132
139460
  router.post("/auth/login", async (req, res) => {
139133
139461
  try {
139134
139462
  const { provider, origin } = req.body;
@@ -139620,7 +139948,7 @@ function stripAnsi2(str) {
139620
139948
  return str.replace(/\x1B\[[0-9;]*[mGKJHFABCDSTsu]/g, "");
139621
139949
  }
139622
139950
  async function mintAgentApiKeyViaCli(opts) {
139623
- const { spawn: spawn16 } = await import("node:child_process");
139951
+ const { spawn: spawn17 } = await import("node:child_process");
139624
139952
  const bin = opts.cliBinaryPath ?? "paperclipai";
139625
139953
  const args = [
139626
139954
  "agent",
@@ -139644,7 +139972,7 @@ async function mintAgentApiKeyViaCli(opts) {
139644
139972
  return new Promise((resolve40, reject2) => {
139645
139973
  let child;
139646
139974
  try {
139647
- child = spawn16(bin, args, { stdio: ["ignore", "pipe", "pipe"] });
139975
+ child = spawn17(bin, args, { stdio: ["ignore", "pipe", "pipe"] });
139648
139976
  } catch (err) {
139649
139977
  const code = err.code;
139650
139978
  if (code === "ENOENT") {
@@ -139727,7 +140055,7 @@ function remapSpawnError(err, bin) {
139727
140055
  return err instanceof Error ? err : new Error(String(err));
139728
140056
  }
139729
140057
  async function spawnPaperclipCliJson(args, opts) {
139730
- const { spawn: spawn16 } = await import("node:child_process");
140058
+ const { spawn: spawn17 } = await import("node:child_process");
139731
140059
  const bin = opts.cliBinaryPath ?? "paperclipai";
139732
140060
  const fullArgs = [...args, "--json"];
139733
140061
  if (opts.cliConfigPath) {
@@ -139738,7 +140066,7 @@ async function spawnPaperclipCliJson(args, opts) {
139738
140066
  return new Promise((resolve40, reject2) => {
139739
140067
  let child;
139740
140068
  try {
139741
- child = spawn16(bin, fullArgs, { stdio: ["ignore", "pipe", "pipe"] });
140069
+ child = spawn17(bin, fullArgs, { stdio: ["ignore", "pipe", "pipe"] });
139742
140070
  } catch (err) {
139743
140071
  reject2(remapSpawnError(err, bin));
139744
140072
  return;
@@ -140660,11 +140988,11 @@ var init_update_check = __esm({
140660
140988
  });
140661
140989
 
140662
140990
  // ../dashboard/src/cli-package-version.ts
140663
- import { existsSync as existsSync31, readFileSync as readFileSync11 } from "node:fs";
140991
+ import { existsSync as existsSync32, readFileSync as readFileSync11 } from "node:fs";
140664
140992
  import { dirname as dirname15, resolve as resolve23 } from "node:path";
140665
140993
  import { fileURLToPath as fileURLToPath4 } from "node:url";
140666
140994
  function readCliPackageVersion(pkgPath) {
140667
- if (!existsSync31(pkgPath)) {
140995
+ if (!existsSync32(pkgPath)) {
140668
140996
  return null;
140669
140997
  }
140670
140998
  try {
@@ -144301,9 +144629,8 @@ function createInsightsRouter(store) {
144301
144629
  }
144302
144630
  const existingInsights = await readInsightsMemory2(rootDir);
144303
144631
  try {
144304
- const { createFnAgent: createFnAgent13, promptWithFallback: promptWithFallback2 } = await Promise.resolve().then(() => (init_src2(), src_exports2));
144305
144632
  let responseText = "";
144306
- const { session } = await createFnAgent13({
144633
+ const { session } = await createFnAgent2({
144307
144634
  cwd: rootDir,
144308
144635
  systemPrompt: [
144309
144636
  "You extract durable project insights from working memory notes.",
@@ -144317,7 +144644,7 @@ function createInsightsRouter(store) {
144317
144644
  });
144318
144645
  try {
144319
144646
  const prompt = buildInsightExtractionPrompt2(workingMemory, existingInsights);
144320
- await promptWithFallback2(session, prompt);
144647
+ await promptWithFallback(session, prompt);
144321
144648
  } finally {
144322
144649
  try {
144323
144650
  session.dispose();
@@ -144491,6 +144818,7 @@ var init_insights_routes = __esm({
144491
144818
  "../dashboard/src/insights-routes.ts"() {
144492
144819
  "use strict";
144493
144820
  init_api_error();
144821
+ init_src2();
144494
144822
  VALID_CATEGORIES = [
144495
144823
  "quality",
144496
144824
  "performance",
@@ -145551,7 +145879,7 @@ var init_dev_server_port_detect = __esm({
145551
145879
 
145552
145880
  // ../dashboard/src/dev-server-process.ts
145553
145881
  import { EventEmitter as EventEmitter34 } from "node:events";
145554
- import { spawn as spawn11 } from "node:child_process";
145882
+ import { spawn as spawn12 } from "node:child_process";
145555
145883
  function killManagedProcess2(child, signal) {
145556
145884
  if (typeof child.pid !== "number") {
145557
145885
  return;
@@ -145630,7 +145958,7 @@ var init_dev_server_process = __esm({
145630
145958
  detectedUrl: void 0,
145631
145959
  detectedPort: void 0
145632
145960
  });
145633
- const child = spawn11(safeCommand, [], {
145961
+ const child = spawn12(safeCommand, [], {
145634
145962
  cwd: safeCwd,
145635
145963
  detached: process.platform !== "win32",
145636
145964
  shell: true,
@@ -148641,7 +148969,9 @@ Description: ${step.description}`
148641
148969
  return;
148642
148970
  }
148643
148971
  const projectId = typeof req.query.projectId === "string" ? req.query.projectId : void 0;
148644
- const sessions7 = aiSessionStore.listActive(projectId);
148972
+ const includeCompleted = req.query.includeCompleted === "1" || req.query.includeCompleted === "true";
148973
+ const includeArchived = req.query.includeArchived === "1" || req.query.includeArchived === "true";
148974
+ const sessions7 = includeCompleted ? aiSessionStore.listAll(projectId, { includeArchived }) : aiSessionStore.listActive(projectId);
148645
148975
  res.json({ sessions: sessions7 });
148646
148976
  });
148647
148977
  router.delete("/ai-sessions/cleanup", (req, res) => {
@@ -148674,6 +149004,32 @@ Description: ${step.description}`
148674
149004
  }
148675
149005
  res.json(session);
148676
149006
  });
149007
+ router.post("/ai-sessions/:id/archive", (req, res) => {
149008
+ if (!aiSessionStore) {
149009
+ throw notFound("AI sessions not available");
149010
+ }
149011
+ const session = aiSessionStore.get(req.params.id);
149012
+ if (!session) {
149013
+ throw notFound("Session not found");
149014
+ }
149015
+ if (session.status !== "complete" && session.status !== "error") {
149016
+ throw badRequest("Only completed or errored sessions can be archived");
149017
+ }
149018
+ aiSessionStore.archive(req.params.id);
149019
+ const after = aiSessionStore.get(req.params.id);
149020
+ res.json({ archived: Number(after?.archived ?? 0) === 1 });
149021
+ });
149022
+ router.post("/ai-sessions/:id/unarchive", (req, res) => {
149023
+ if (!aiSessionStore) {
149024
+ throw notFound("AI sessions not available");
149025
+ }
149026
+ if (!aiSessionStore.get(req.params.id)) {
149027
+ throw notFound("Session not found");
149028
+ }
149029
+ aiSessionStore.unarchive(req.params.id);
149030
+ const after = aiSessionStore.get(req.params.id);
149031
+ res.json({ archived: Number(after?.archived ?? 0) === 1 });
149032
+ });
148677
149033
  router.post("/ai-sessions/:id/lock", (req, res) => {
148678
149034
  if (!aiSessionStore) {
148679
149035
  throw notFound("AI sessions not available");
@@ -149038,7 +149394,7 @@ Description: ${step.description}`
149038
149394
  }
149039
149395
  });
149040
149396
  registerIntegratedDevServerRouter({ router, store });
149041
- router.use((err, req, res, next) => {
149397
+ router.use((err, _req, res, next) => {
149042
149398
  if (res.headersSent) {
149043
149399
  next(err);
149044
149400
  return;
@@ -149148,7 +149504,20 @@ async function executeAiPromptStep(step, timeoutMs, startedAt, taskStore) {
149148
149504
  completedAt: (/* @__PURE__ */ new Date()).toISOString()
149149
149505
  };
149150
149506
  }
149151
- const { createFnAgent: createFnAgent13, promptWithFallback: promptWithFallback2 } = await Promise.resolve().then(() => (init_src2(), src_exports2));
149507
+ const createFnAgent13 = createFnAgentForRefine;
149508
+ const promptWithFallback2 = promptWithFallback;
149509
+ if (!createFnAgent13) {
149510
+ return {
149511
+ stepId: step.id,
149512
+ stepName: step.name,
149513
+ stepIndex: 0,
149514
+ success: false,
149515
+ output: "",
149516
+ error: "AI agent not available",
149517
+ startedAt,
149518
+ completedAt: (/* @__PURE__ */ new Date()).toISOString()
149519
+ };
149520
+ }
149152
149521
  const settings = await taskStore.getSettings();
149153
149522
  const defaultModel = resolveProjectDefaultModel(settings);
149154
149523
  const modelProvider = step.modelProvider?.trim() || defaultModel.provider;
@@ -154393,7 +154762,7 @@ var init_auth_middleware = __esm({
154393
154762
  import express from "express";
154394
154763
  import { randomUUID as randomUUID21 } from "node:crypto";
154395
154764
  import { join as join51, dirname as dirname18 } from "node:path";
154396
- import { existsSync as existsSync33, readFileSync as readFileSync13 } from "node:fs";
154765
+ import { existsSync as existsSync34, readFileSync as readFileSync13 } from "node:fs";
154397
154766
  import { fileURLToPath as fileURLToPath5 } from "node:url";
154398
154767
  import { createSecureServer as createHttp2SecureServer } from "node:http2";
154399
154768
  function parseVersion2(version) {
@@ -154575,7 +154944,7 @@ function createServer(store, options) {
154575
154944
  getTerminalService(store.getRootDir());
154576
154945
  const isHeadless = options?.headless === true;
154577
154946
  const execDir = dirname18(process.execPath);
154578
- const clientDir = process.env.FUSION_CLIENT_DIR ? process.env.FUSION_CLIENT_DIR : existsSync33(join51(execDir, "client", "index.html")) ? join51(execDir, "client") : existsSync33(join51(__dirname, "..", "dist", "client")) ? join51(__dirname, "..", "dist", "client") : join51(__dirname, "..", "client");
154947
+ const clientDir = process.env.FUSION_CLIENT_DIR ? process.env.FUSION_CLIENT_DIR : existsSync34(join51(execDir, "client", "index.html")) ? join51(execDir, "client") : existsSync34(join51(__dirname, "..", "dist", "client")) ? join51(__dirname, "..", "dist", "client") : join51(__dirname, "..", "client");
154579
154948
  if (!isHeadless) {
154580
154949
  app.get("/version.json", (_req, res) => {
154581
154950
  res.setHeader("Cache-Control", "no-store, max-age=0");
@@ -156161,7 +156530,7 @@ var init_port_prompt = __esm({
156161
156530
  });
156162
156531
 
156163
156532
  // src/commands/provider-settings.ts
156164
- import { existsSync as existsSync34, readFileSync as readFileSync14, writeFileSync as writeFileSync2, mkdirSync as mkdirSync6 } from "node:fs";
156533
+ import { existsSync as existsSync35, readFileSync as readFileSync14, writeFileSync as writeFileSync2, mkdirSync as mkdirSync6 } from "node:fs";
156165
156534
  import { join as join53, dirname as dirname20, basename as basename15 } from "node:path";
156166
156535
  function siblingAgentDir2(agentDir, siblingRoot) {
156167
156536
  if (basename15(agentDir) !== "agent") {
@@ -156170,7 +156539,7 @@ function siblingAgentDir2(agentDir, siblingRoot) {
156170
156539
  return join53(dirname20(dirname20(agentDir)), siblingRoot, "agent");
156171
156540
  }
156172
156541
  function readJsonObject4(path5) {
156173
- if (!existsSync34(path5)) {
156542
+ if (!existsSync35(path5)) {
156174
156543
  return {};
156175
156544
  }
156176
156545
  try {
@@ -156202,7 +156571,7 @@ var init_provider_settings = __esm({
156202
156571
  });
156203
156572
 
156204
156573
  // src/commands/provider-auth.ts
156205
- import { existsSync as existsSync35, readFileSync as readFileSync15 } from "node:fs";
156574
+ import { existsSync as existsSync36, readFileSync as readFileSync15 } from "node:fs";
156206
156575
  import { getOAuthProvider as getOAuthProvider2 } from "@mariozechner/pi-ai/oauth";
156207
156576
  function getProviderDisplayName(providerId) {
156208
156577
  const knownProviderNames = new Map(
@@ -156331,7 +156700,7 @@ function createReadOnlyAuthFileStorage(authPaths) {
156331
156700
  const reload = () => {
156332
156701
  const nextCredentials = {};
156333
156702
  for (const authPath of authPaths) {
156334
- if (!existsSync35(authPath)) {
156703
+ if (!existsSync36(authPath)) {
156335
156704
  continue;
156336
156705
  }
156337
156706
  try {
@@ -156368,13 +156737,13 @@ var init_provider_auth = __esm({
156368
156737
  { id: "tavily", name: "Tavily" },
156369
156738
  { id: "zai", name: "Zai" }
156370
156739
  ];
156371
- CLI_PROVIDER_IDS = /* @__PURE__ */ new Set(["pi-claude-cli"]);
156740
+ CLI_PROVIDER_IDS = /* @__PURE__ */ new Set(["pi-claude-cli", "droid-cli"]);
156372
156741
  }
156373
156742
  });
156374
156743
 
156375
156744
  // src/commands/auth-paths.ts
156376
156745
  import { homedir as homedir9 } from "node:os";
156377
- import { existsSync as existsSync36, readFileSync as readFileSync16 } from "node:fs";
156746
+ import { existsSync as existsSync37, readFileSync as readFileSync16 } from "node:fs";
156378
156747
  import { join as join54 } from "node:path";
156379
156748
  function getFusionAgentDir3(home = process.env.HOME || process.env.USERPROFILE || homedir9()) {
156380
156749
  return join54(home, ".fusion", "agent");
@@ -156402,13 +156771,13 @@ function getLegacyModelsPaths2(home = process.env.HOME || process.env.USERPROFIL
156402
156771
  }
156403
156772
  function getModelRegistryModelsPath2(home = process.env.HOME || process.env.USERPROFILE || homedir9()) {
156404
156773
  const fusionModelsPath = getFusionModelsPath2(home);
156405
- if (existsSync36(fusionModelsPath)) {
156774
+ if (existsSync37(fusionModelsPath)) {
156406
156775
  return fusionModelsPath;
156407
156776
  }
156408
- return getLegacyModelsPaths2(home).find((modelsPath) => existsSync36(modelsPath)) ?? fusionModelsPath;
156777
+ return getLegacyModelsPaths2(home).find((modelsPath) => existsSync37(modelsPath)) ?? fusionModelsPath;
156409
156778
  }
156410
156779
  function readJsonObject5(path5) {
156411
- if (!existsSync36(path5)) {
156780
+ if (!existsSync37(path5)) {
156412
156781
  return {};
156413
156782
  }
156414
156783
  try {
@@ -156426,13 +156795,13 @@ function getPackageManagerAgentDir2(home = process.env.HOME || process.env.USERP
156426
156795
  const legacyAgentDir = getLegacyAgentDir(home);
156427
156796
  const fusionSettings = readJsonObject5(join54(fusionAgentDir, "settings.json"));
156428
156797
  const legacySettings = readJsonObject5(join54(legacyAgentDir, "settings.json"));
156429
- if (hasPackageManagerSettings3(fusionSettings) || !existsSync36(legacyAgentDir)) {
156798
+ if (hasPackageManagerSettings3(fusionSettings) || !existsSync37(legacyAgentDir)) {
156430
156799
  return fusionAgentDir;
156431
156800
  }
156432
156801
  if (hasPackageManagerSettings3(legacySettings)) {
156433
156802
  return legacyAgentDir;
156434
156803
  }
156435
- return existsSync36(fusionAgentDir) ? fusionAgentDir : legacyAgentDir;
156804
+ return existsSync37(fusionAgentDir) ? fusionAgentDir : legacyAgentDir;
156436
156805
  }
156437
156806
  var init_auth_paths2 = __esm({
156438
156807
  "src/commands/auth-paths.ts"() {
@@ -156590,7 +156959,7 @@ var init_project_context = __esm({
156590
156959
  // src/commands/claude-skills.ts
156591
156960
  import {
156592
156961
  cpSync,
156593
- existsSync as existsSync37,
156962
+ existsSync as existsSync38,
156594
156963
  lstatSync as lstatSync2,
156595
156964
  mkdirSync as mkdirSync7,
156596
156965
  readlinkSync,
@@ -156619,7 +156988,7 @@ function isPiClaudeCliConfigured(globalSettings) {
156619
156988
  function resolveFusionSkillSource() {
156620
156989
  const here = fileURLToPath6(import.meta.url);
156621
156990
  const candidate = resolve28(dirname22(here), "..", "..", "skill", FUSION_SKILL_NAME);
156622
- return existsSync37(candidate) ? candidate : null;
156991
+ return existsSync38(candidate) ? candidate : null;
156623
156992
  }
156624
156993
  function installFusionSkillIntoProject(projectPath, options = {}) {
156625
156994
  const target = join55(projectPath, ".claude", "skills", FUSION_SKILL_NAME);
@@ -156637,7 +157006,7 @@ function installFusionSkillIntoProject(projectPath, options = {}) {
156637
157006
  try {
156638
157007
  mkdirSync7(dirname22(target), { recursive: true });
156639
157008
  let replaced = false;
156640
- if (existsSync37(target) || isBrokenSymlink(target)) {
157009
+ if (existsSync38(target) || isBrokenSymlink(target)) {
156641
157010
  const stat12 = lstatSync2(target);
156642
157011
  if (stat12.isSymbolicLink()) {
156643
157012
  const current = safeReadlink(target);
@@ -156648,7 +157017,7 @@ function installFusionSkillIntoProject(projectPath, options = {}) {
156648
157017
  replaced = true;
156649
157018
  } else {
156650
157019
  const skillMd = join55(target, "SKILL.md");
156651
- if (!existsSync37(skillMd)) {
157020
+ if (!existsSync38(skillMd)) {
156652
157021
  return {
156653
157022
  outcome: "failed",
156654
157023
  target,
@@ -156707,7 +157076,7 @@ function isBrokenSymlink(path5) {
156707
157076
  try {
156708
157077
  const stat12 = lstatSync2(path5);
156709
157078
  if (!stat12.isSymbolicLink()) return false;
156710
- return !existsSync37(path5);
157079
+ return !existsSync38(path5);
156711
157080
  } catch {
156712
157081
  return false;
156713
157082
  }
@@ -156804,7 +157173,7 @@ var init_claude_skills_runner = __esm({
156804
157173
  });
156805
157174
 
156806
157175
  // src/commands/claude-cli-extension.ts
156807
- import { existsSync as existsSync38, readFileSync as readFileSync17 } from "node:fs";
157176
+ import { existsSync as existsSync39, readFileSync as readFileSync17 } from "node:fs";
156808
157177
  import { createRequire as createRequire4 } from "node:module";
156809
157178
  import { dirname as dirname23, resolve as resolve29 } from "node:path";
156810
157179
  import { fileURLToPath as fileURLToPath7 } from "node:url";
@@ -156813,7 +157182,7 @@ function resolveClaudeCliExtensionFromModuleUrl(moduleUrl) {
156813
157182
  const here = dirname23(fileURLToPath7(moduleUrl));
156814
157183
  for (const rel of ["pi-claude-cli", "../pi-claude-cli", "../../pi-claude-cli"]) {
156815
157184
  const candidate = resolve29(here, rel, "package.json");
156816
- if (existsSync38(candidate)) {
157185
+ if (existsSync39(candidate)) {
156817
157186
  pkgJsonPath = candidate;
156818
157187
  break;
156819
157188
  }
@@ -156849,7 +157218,7 @@ function resolveClaudeCliExtensionFromModuleUrl(moduleUrl) {
156849
157218
  };
156850
157219
  }
156851
157220
  const entryPath = resolve29(dirname23(pkgJsonPath), rawEntry);
156852
- if (!existsSync38(entryPath)) {
157221
+ if (!existsSync39(entryPath)) {
156853
157222
  return {
156854
157223
  status: "missing-entry",
156855
157224
  reason: `@fusion/pi-claude-cli extension file not found at ${entryPath}`
@@ -156936,7 +157305,7 @@ var init_update_cache = __esm({
156936
157305
  });
156937
157306
 
156938
157307
  // src/commands/self-extension.ts
156939
- import { existsSync as existsSync39, readFileSync as readFileSync19 } from "node:fs";
157308
+ import { existsSync as existsSync40, readFileSync as readFileSync19 } from "node:fs";
156940
157309
  import { dirname as dirname24, resolve as resolve30 } from "node:path";
156941
157310
  import { fileURLToPath as fileURLToPath8 } from "node:url";
156942
157311
  function resolveSelfExtension() {
@@ -156944,7 +157313,7 @@ function resolveSelfExtension() {
156944
157313
  let pkgDir;
156945
157314
  let cur = here;
156946
157315
  for (let i = 0; i < 5; i++) {
156947
- if (existsSync39(resolve30(cur, "package.json"))) {
157316
+ if (existsSync40(resolve30(cur, "package.json"))) {
156948
157317
  try {
156949
157318
  const parsed = JSON.parse(readFileSync19(resolve30(cur, "package.json"), "utf-8"));
156950
157319
  if (parsed.name === "@runfusion/fusion") {
@@ -156968,7 +157337,7 @@ function resolveSelfExtension() {
156968
157337
  return { status: "missing", reason: `Failed to read @runfusion/fusion package.json: ${err instanceof Error ? err.message : String(err)}` };
156969
157338
  }
156970
157339
  const srcEntry = resolve30(pkgDir, "src", "extension.ts");
156971
- if (existsSync39(srcEntry)) {
157340
+ if (existsSync40(srcEntry)) {
156972
157341
  return { status: "ok", path: srcEntry, packageVersion: pkgJson.version ?? "unknown" };
156973
157342
  }
156974
157343
  const extensions = pkgJson.pi?.extensions;
@@ -156980,7 +157349,7 @@ function resolveSelfExtension() {
156980
157349
  return { status: "missing", reason: "@runfusion/fusion pi.extensions[0] is not a valid path string" };
156981
157350
  }
156982
157351
  const entryPath = resolve30(pkgDir, rawEntry);
156983
- if (!existsSync39(entryPath)) {
157352
+ if (!existsSync40(entryPath)) {
156984
157353
  return { status: "missing", reason: `@runfusion/fusion extension file not found at ${entryPath}` };
156985
157354
  }
156986
157355
  return { status: "ok", path: entryPath, packageVersion: pkgJson.version ?? "unknown" };
@@ -157192,7 +157561,7 @@ var init_use_projects = __esm({
157192
157561
  });
157193
157562
 
157194
157563
  // src/commands/dashboard-tui/utils.ts
157195
- import { spawn as spawn12 } from "node:child_process";
157564
+ import { spawn as spawn13 } from "node:child_process";
157196
157565
  function isTTYAvailable() {
157197
157566
  return Boolean(process.stdout.isTTY && process.stdin.isTTY);
157198
157567
  }
@@ -157205,7 +157574,7 @@ async function copyToClipboard(text) {
157205
157574
  for (const { cmd, args } of candidates) {
157206
157575
  const ok = await new Promise((resolve40) => {
157207
157576
  try {
157208
- const child = spawn12(cmd, args, { stdio: ["pipe", "ignore", "ignore"] });
157577
+ const child = spawn13(cmd, args, { stdio: ["pipe", "ignore", "ignore"] });
157209
157578
  child.once("error", () => resolve40(false));
157210
157579
  child.once("close", (code) => resolve40(code === 0));
157211
157580
  child.stdin.end(text);
@@ -157232,7 +157601,7 @@ import { useState as useState2, useSyncExternalStore, useCallback as useCallback
157232
157601
  import { Box, Text, useInput, useApp, useStdout } from "ink";
157233
157602
  import Spinner from "ink-spinner";
157234
157603
  import TextInput from "ink-text-input";
157235
- import { spawn as spawn13 } from "node:child_process";
157604
+ import { spawn as spawn14 } from "node:child_process";
157236
157605
  import { appendFileSync } from "node:fs";
157237
157606
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
157238
157607
  function tuiDebug(tag, data) {
@@ -157258,7 +157627,7 @@ function openInBrowser(url) {
157258
157627
  args = [url];
157259
157628
  }
157260
157629
  try {
157261
- const child = spawn13(cmd, args, { detached: true, stdio: "ignore" });
157630
+ const child = spawn14(cmd, args, { detached: true, stdio: "ignore" });
157262
157631
  child.unref();
157263
157632
  } catch {
157264
157633
  }
@@ -164575,13 +164944,13 @@ var desktop_exports = {};
164575
164944
  __export(desktop_exports, {
164576
164945
  runDesktop: () => runDesktop
164577
164946
  });
164578
- import { spawn as spawn14 } from "node:child_process";
164947
+ import { spawn as spawn15 } from "node:child_process";
164579
164948
  import { once as once2 } from "node:events";
164580
164949
  import { join as join60 } from "node:path";
164581
164950
  import { createRequire as createRequire5 } from "node:module";
164582
164951
  function runCommand(command, args, cwd) {
164583
164952
  return new Promise((resolve40, reject2) => {
164584
- const child = spawn14(command, args, {
164953
+ const child = spawn15(command, args, {
164585
164954
  cwd,
164586
164955
  stdio: "inherit",
164587
164956
  env: process.env
@@ -164666,7 +165035,7 @@ async function runDesktop(options = {}) {
164666
165035
  electronEnv.FUSION_DASHBOARD_URL = process.env.FUSION_DASHBOARD_URL ?? "http://localhost:5173";
164667
165036
  electronEnv.NODE_ENV = "development";
164668
165037
  }
164669
- const electronProcess = spawn14(electronBinary, electronArgs, {
165038
+ const electronProcess = spawn15(electronBinary, electronArgs, {
164670
165039
  cwd: rootDir,
164671
165040
  stdio: "inherit",
164672
165041
  env: electronEnv
@@ -164741,7 +165110,7 @@ __export(task_exports, {
164741
165110
  runTaskUpdate: () => runTaskUpdate
164742
165111
  });
164743
165112
  import { createInterface as createInterface3 } from "node:readline/promises";
164744
- import { watchFile, unwatchFile, statSync as statSync6, existsSync as existsSync40, readFileSync as readFileSync20 } from "node:fs";
165113
+ import { watchFile, unwatchFile, statSync as statSync6, existsSync as existsSync41, readFileSync as readFileSync20 } from "node:fs";
164745
165114
  import { basename as basename17, join as join61 } from "node:path";
164746
165115
  function getGitHubIssueUrl(sourceMetadata) {
164747
165116
  if (!sourceMetadata || typeof sourceMetadata !== "object") return void 0;
@@ -165043,7 +165412,7 @@ async function runTaskLogs(id, options = {}, projectName) {
165043
165412
  if (options.follow) {
165044
165413
  const projectPath = projectContext?.projectPath ?? process.cwd();
165045
165414
  const logPath = join61(projectPath, ".fusion", "tasks", id, "agent.log");
165046
- if (!existsSync40(logPath)) {
165415
+ if (!existsSync41(logPath)) {
165047
165416
  console.log(`
165048
165417
  Waiting for log file to be created...`);
165049
165418
  }
@@ -166380,7 +166749,7 @@ var settings_import_exports = {};
166380
166749
  __export(settings_import_exports, {
166381
166750
  runSettingsImport: () => runSettingsImport
166382
166751
  });
166383
- import { existsSync as existsSync41 } from "node:fs";
166752
+ import { existsSync as existsSync42 } from "node:fs";
166384
166753
  import { resolve as resolve32 } from "node:path";
166385
166754
  async function runSettingsImport(filePath, options = {}) {
166386
166755
  const scope = options.scope ?? "both";
@@ -166391,7 +166760,7 @@ async function runSettingsImport(filePath, options = {}) {
166391
166760
  const skipConfirm = options.yes ?? false;
166392
166761
  try {
166393
166762
  const resolvedPath = resolve32(filePath);
166394
- if (!existsSync41(resolvedPath)) {
166763
+ if (!existsSync42(resolvedPath)) {
166395
166764
  console.error(`Error: File not found: ${filePath}`);
166396
166765
  process.exit(1);
166397
166766
  }
@@ -166864,7 +167233,7 @@ var init_backup2 = __esm({
166864
167233
  });
166865
167234
 
166866
167235
  // src/project-resolver.ts
166867
- import { existsSync as existsSync42, statSync as statSync7 } from "node:fs";
167236
+ import { existsSync as existsSync43, statSync as statSync7 } from "node:fs";
166868
167237
  import { basename as basename18, dirname as dirname25, resolve as resolve33, normalize as normalize5 } from "node:path";
166869
167238
  import { createInterface as createInterface5 } from "node:readline/promises";
166870
167239
  async function getCentralCore() {
@@ -166940,7 +167309,7 @@ async function resolveProject2(options = {}) {
166940
167309
  { searchedName: options.project, availableProjects: projects.map((p) => p.name) }
166941
167310
  );
166942
167311
  }
166943
- if (!existsSync42(match.path)) {
167312
+ if (!existsSync43(match.path)) {
166944
167313
  throw new ProjectResolutionError(
166945
167314
  `Project "${match.name}" is registered but the directory no longer exists: ${match.path}
166946
167315
 
@@ -166958,7 +167327,7 @@ Run \`fn project remove ` + match.name + "` to clean up the registry entry.",
166958
167327
  const normalizedKbDir = normalize5(fusionDir);
166959
167328
  const match = allProjects2.find((p) => normalize5(p.path) === normalizedKbDir);
166960
167329
  if (match) {
166961
- if (!existsSync42(match.path)) {
167330
+ if (!existsSync43(match.path)) {
166962
167331
  throw new ProjectResolutionError(
166963
167332
  `Project "${match.name}" is registered but the directory no longer exists: ${match.path}
166964
167333
 
@@ -167023,7 +167392,7 @@ Run \`fn project add ` + fusionDir + "` to register it, or use --project <name>.
167023
167392
  }
167024
167393
  if (allProjects.length === 1) {
167025
167394
  const project = allProjects[0];
167026
- if (!existsSync42(project.path)) {
167395
+ if (!existsSync43(project.path)) {
167027
167396
  throw new ProjectResolutionError(
167028
167397
  `The only registered project "${project.name}" has a missing directory: ${project.path}
167029
167398
 
@@ -167464,7 +167833,7 @@ __export(project_exports, {
167464
167833
  runProjectShow: () => runProjectShow
167465
167834
  });
167466
167835
  import { resolve as resolve34, isAbsolute as isAbsolute19, relative as relative14, basename as basename19 } from "node:path";
167467
- import { existsSync as existsSync43, statSync as statSync8 } from "node:fs";
167836
+ import { existsSync as existsSync44, statSync as statSync8 } from "node:fs";
167468
167837
  import { createInterface as createInterface7 } from "node:readline/promises";
167469
167838
  function formatDisplayPath(projectPath) {
167470
167839
  const rel = relative14(process.cwd(), projectPath);
@@ -167593,7 +167962,7 @@ async function runProjectAdd(name, path5, options = {}) {
167593
167962
  projectPath = pathInput.trim() || defaultPath;
167594
167963
  }
167595
167964
  const absolutePath2 = isAbsolute19(projectPath) ? projectPath : resolve34(process.cwd(), projectPath);
167596
- if (!existsSync43(absolutePath2)) {
167965
+ if (!existsSync44(absolutePath2)) {
167597
167966
  console.error(`
167598
167967
  \u2717 Path does not exist: ${projectPath}`);
167599
167968
  rl.close();
@@ -167606,7 +167975,7 @@ async function runProjectAdd(name, path5, options = {}) {
167606
167975
  process.exit(1);
167607
167976
  }
167608
167977
  const kbDbPath2 = resolve34(absolutePath2, ".fusion", "fusion.db");
167609
- if (!existsSync43(kbDbPath2) && !options.force) {
167978
+ if (!existsSync44(kbDbPath2) && !options.force) {
167610
167979
  console.log(`
167611
167980
  No fn project found at ${formatDisplayPath(absolutePath2)}`);
167612
167981
  const init = await rl.question(" Initialize fn here first? [Y/n] ");
@@ -167638,7 +168007,7 @@ async function runProjectAdd(name, path5, options = {}) {
167638
168007
  process.exit(1);
167639
168008
  }
167640
168009
  const absolutePath = isAbsolute19(projectPath) ? projectPath : resolve34(process.cwd(), projectPath);
167641
- if (!existsSync43(absolutePath)) {
168010
+ if (!existsSync44(absolutePath)) {
167642
168011
  console.error(`
167643
168012
  \u2717 Path does not exist: ${projectPath}
167644
168013
  `);
@@ -167651,7 +168020,7 @@ async function runProjectAdd(name, path5, options = {}) {
167651
168020
  process.exit(1);
167652
168021
  }
167653
168022
  const kbDbPath = resolve34(absolutePath, ".fusion", "fusion.db");
167654
- if (!existsSync43(kbDbPath) && !options.force) {
168023
+ if (!existsSync44(kbDbPath) && !options.force) {
167655
168024
  console.error(`
167656
168025
  \u2717 No fn project found at ${formatDisplayPath(absolutePath)}`);
167657
168026
  console.error(" Run `fn init` first to initialize the project.\n");
@@ -167907,7 +168276,7 @@ var init_project = __esm({
167907
168276
  });
167908
168277
 
167909
168278
  // src/commands/skill-installation.ts
167910
- import { cpSync as cpSync2, existsSync as existsSync44, mkdirSync as mkdirSync8 } from "node:fs";
168279
+ import { cpSync as cpSync2, existsSync as existsSync45, mkdirSync as mkdirSync8 } from "node:fs";
167911
168280
  import { homedir as homedir10 } from "node:os";
167912
168281
  import { dirname as dirname26, join as join63, resolve as resolve35 } from "node:path";
167913
168282
  import { fileURLToPath as fileURLToPath9 } from "node:url";
@@ -167921,7 +168290,7 @@ function getSupportedSkillInstallTargets(homeDir = process.env.HOME || process.e
167921
168290
  function resolveBundledFusionSkillSource() {
167922
168291
  const here = fileURLToPath9(import.meta.url);
167923
168292
  const source = resolve35(dirname26(here), "..", "..", "skill", FUSION_SKILL_NAME2);
167924
- return existsSync44(source) ? source : null;
168293
+ return existsSync45(source) ? source : null;
167925
168294
  }
167926
168295
  function installBundledFusionSkill(options = {}) {
167927
168296
  const sourceDir = options.sourceDir ?? resolveBundledFusionSkillSource();
@@ -167939,7 +168308,7 @@ function installBundledFusionSkill(options = {}) {
167939
168308
  }
167940
168309
  const results = targets.map((target) => {
167941
168310
  try {
167942
- if (existsSync44(target.targetDir)) {
168311
+ if (existsSync45(target.targetDir)) {
167943
168312
  return {
167944
168313
  client: target.client,
167945
168314
  targetDir: target.targetDir,
@@ -167978,7 +168347,7 @@ var init_exports = {};
167978
168347
  __export(init_exports, {
167979
168348
  runInit: () => runInit
167980
168349
  });
167981
- import { existsSync as existsSync45, mkdirSync as mkdirSync9, writeFileSync as writeFileSync3, readFileSync as readFileSync21 } from "node:fs";
168350
+ import { existsSync as existsSync46, mkdirSync as mkdirSync9, writeFileSync as writeFileSync3, readFileSync as readFileSync21 } from "node:fs";
167982
168351
  import { join as join64, resolve as resolve36, basename as basename20 } from "node:path";
167983
168352
  import { exec as exec12 } from "node:child_process";
167984
168353
  import { promisify as promisify17 } from "node:util";
@@ -167986,9 +168355,9 @@ async function runInit(options = {}) {
167986
168355
  const cwd = options.path ? resolve36(options.path) : process.cwd();
167987
168356
  const fusionDir = join64(cwd, ".fusion");
167988
168357
  const dbPath = join64(fusionDir, "fusion.db");
167989
- const hasDbPath = existsSync45(dbPath);
168358
+ const hasDbPath = existsSync46(dbPath);
167990
168359
  const hasValidDb = hasDbPath && isValidSqliteDatabaseFile(dbPath);
167991
- if (existsSync45(fusionDir) && hasDbPath && hasValidDb) {
168360
+ if (existsSync46(fusionDir) && hasDbPath && hasValidDb) {
167992
168361
  const central2 = new CentralCore();
167993
168362
  await central2.init();
167994
168363
  const existing = await central2.getProjectByPath(cwd);
@@ -168010,7 +168379,7 @@ async function runInit(options = {}) {
168010
168379
  await central2.close();
168011
168380
  return;
168012
168381
  }
168013
- if (existsSync45(fusionDir) && hasDbPath && !hasValidDb) {
168382
+ if (existsSync46(fusionDir) && hasDbPath && !hasValidDb) {
168014
168383
  throw new Error(
168015
168384
  `Existing database at ${dbPath} is not a valid SQLite database. Restore it from .fusion/backups or move it aside before re-running fn init.`
168016
168385
  );
@@ -168018,7 +168387,7 @@ async function runInit(options = {}) {
168018
168387
  const projectName = options.name ?? await detectProjectName(cwd);
168019
168388
  console.log(`Initializing fn project: "${projectName}"`);
168020
168389
  console.log(` Path: ${cwd}`);
168021
- if (!existsSync45(fusionDir)) {
168390
+ if (!existsSync46(fusionDir)) {
168022
168391
  mkdirSync9(fusionDir, { recursive: true });
168023
168392
  console.log(` \u2713 Created .fusion/ directory`);
168024
168393
  }
@@ -168031,7 +168400,7 @@ async function runInit(options = {}) {
168031
168400
  }
168032
168401
  await addLocalStorageToGitignore(cwd);
168033
168402
  await warnIfQmdMissing();
168034
- if (!existsSync45(dbPath)) {
168403
+ if (!existsSync46(dbPath)) {
168035
168404
  writeFileSync3(dbPath, "");
168036
168405
  console.log(` \u2713 Created fusion.db`);
168037
168406
  }
@@ -168081,7 +168450,7 @@ async function runInit(options = {}) {
168081
168450
  }
168082
168451
  }
168083
168452
  async function detectProjectName(dir2) {
168084
- if (!existsSync45(join64(dir2, ".git"))) {
168453
+ if (!existsSync46(join64(dir2, ".git"))) {
168085
168454
  return basename20(dir2) || "my-project";
168086
168455
  }
168087
168456
  try {
@@ -168103,7 +168472,7 @@ async function detectProjectName(dir2) {
168103
168472
  async function addLocalStorageToGitignore(cwd) {
168104
168473
  const gitignorePath = join64(cwd, ".gitignore");
168105
168474
  let content = "";
168106
- if (existsSync45(gitignorePath)) {
168475
+ if (existsSync46(gitignorePath)) {
168107
168476
  try {
168108
168477
  content = readFileSync21(gitignorePath, "utf-8");
168109
168478
  } catch {
@@ -168145,7 +168514,7 @@ async function initializeGitRepo(cwd) {
168145
168514
  await ensureGitConfig(cwd, "user.name", "Fusion");
168146
168515
  await ensureGitConfig(cwd, "user.email", "noreply@runfusion.ai");
168147
168516
  const gitkeepPath = join64(cwd, ".gitkeep");
168148
- if (!existsSync45(gitkeepPath)) {
168517
+ if (!existsSync46(gitkeepPath)) {
168149
168518
  writeFileSync3(gitkeepPath, "\n");
168150
168519
  }
168151
168520
  await execAsync11("git add .gitkeep", { cwd, timeout: 1e4 });
@@ -168283,7 +168652,7 @@ var agent_import_exports = {};
168283
168652
  __export(agent_import_exports, {
168284
168653
  runAgentImport: () => runAgentImport
168285
168654
  });
168286
- import { existsSync as existsSync46, mkdirSync as mkdirSync10, readFileSync as readFileSync22, statSync as statSync9, writeFileSync as writeFileSync4 } from "node:fs";
168655
+ import { existsSync as existsSync47, mkdirSync as mkdirSync10, readFileSync as readFileSync22, statSync as statSync9, writeFileSync as writeFileSync4 } from "node:fs";
168287
168656
  import { resolve as resolve37 } from "node:path";
168288
168657
  function slugifyPathSegment(input) {
168289
168658
  if (!input || typeof input !== "string") {
@@ -168342,7 +168711,7 @@ async function importSkillsToProject(projectPath, skills, companySlug, dryRun) {
168342
168711
  const skillSlug = slugifyPathSegment(skill.name);
168343
168712
  const skillDir = resolve37(baseSkillsDir, skillSlug);
168344
168713
  const skillPath = resolve37(skillDir, "SKILL.md");
168345
- if (existsSync46(skillPath)) {
168714
+ if (existsSync47(skillPath)) {
168346
168715
  result.skipped.push(skill.name);
168347
168716
  continue;
168348
168717
  }
@@ -168415,7 +168784,7 @@ async function runAgentImport(source, options) {
168415
168784
  const dryRun = options?.dryRun ?? false;
168416
168785
  const skipExisting = options?.skipExisting ?? false;
168417
168786
  const sourcePath = resolve37(source);
168418
- if (!existsSync46(sourcePath)) {
168787
+ if (!existsSync47(sourcePath)) {
168419
168788
  console.error(`Path not found: ${sourcePath}`);
168420
168789
  process.exit(1);
168421
168790
  }
@@ -168810,7 +169179,7 @@ __export(plugin_exports, {
168810
169179
  runPluginList: () => runPluginList,
168811
169180
  runPluginUninstall: () => runPluginUninstall
168812
169181
  });
168813
- import { existsSync as existsSync47 } from "node:fs";
169182
+ import { existsSync as existsSync48 } from "node:fs";
168814
169183
  import { join as join65 } from "node:path";
168815
169184
  import { readFile as readFile23 } from "node:fs/promises";
168816
169185
  import * as readline from "node:readline";
@@ -168850,7 +169219,7 @@ async function createPluginLoader(pluginStore, projectName) {
168850
169219
  }
168851
169220
  async function loadManifestFromPath(pluginPath) {
168852
169221
  const manifestPath = join65(pluginPath, "manifest.json");
168853
- if (!existsSync47(manifestPath)) {
169222
+ if (!existsSync48(manifestPath)) {
168854
169223
  throw new Error(`Plugin manifest not found at: ${manifestPath}`);
168855
169224
  }
168856
169225
  const content = await readFile23(manifestPath, "utf-8");
@@ -168908,7 +169277,7 @@ async function runPluginInstall(source, options) {
168908
169277
  console.error("Please provide a local path to the plugin directory.");
168909
169278
  process.exit(1);
168910
169279
  }
168911
- if (!existsSync47(source)) {
169280
+ if (!existsSync48(source)) {
168912
169281
  console.error(`Plugin path does not exist: ${source}`);
168913
169282
  process.exit(1);
168914
169283
  }
@@ -169040,7 +169409,7 @@ var plugin_scaffold_exports = {};
169040
169409
  __export(plugin_scaffold_exports, {
169041
169410
  runPluginCreate: () => runPluginCreate
169042
169411
  });
169043
- import { mkdirSync as mkdirSync11, writeFileSync as writeFileSync5, existsSync as existsSync48 } from "node:fs";
169412
+ import { mkdirSync as mkdirSync11, writeFileSync as writeFileSync5, existsSync as existsSync49 } from "node:fs";
169044
169413
  import { join as join66 } from "node:path";
169045
169414
  function toTitleCase(str) {
169046
169415
  return str.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
@@ -169175,7 +169544,7 @@ async function runPluginCreate(name, options) {
169175
169544
  }
169176
169545
  const targetDir = options?.output ?? name;
169177
169546
  const targetPath = join66(process.cwd(), targetDir);
169178
- if (existsSync48(targetPath)) {
169547
+ if (existsSync49(targetPath)) {
169179
169548
  console.error(`Error: Directory '${targetDir}' already exists.`);
169180
169549
  console.error("Please choose a different name or remove the existing directory.");
169181
169550
  process.exit(1);
@@ -169225,7 +169594,7 @@ __export(skills_exports, {
169225
169594
  runSkillsSearch: () => runSkillsSearch,
169226
169595
  searchSkills: () => searchSkills
169227
169596
  });
169228
- import { spawn as spawn15 } from "node:child_process";
169597
+ import { spawn as spawn16 } from "node:child_process";
169229
169598
  async function searchSkills(query, limit = 10) {
169230
169599
  const url = `${SKILLS_API_BASE}/api/search?q=${encodeURIComponent(query)}&limit=${limit}`;
169231
169600
  try {
@@ -169303,7 +169672,7 @@ async function runSkillsInstall(args, options) {
169303
169672
  npxArgs.push("--skill", options.skill);
169304
169673
  }
169305
169674
  npxArgs.push("-y", "-a", "pi");
169306
- const child = spawn15("npx", npxArgs, {
169675
+ const child = spawn16("npx", npxArgs, {
169307
169676
  cwd: process.cwd(),
169308
169677
  stdio: "inherit",
169309
169678
  shell: true
@@ -169565,7 +169934,7 @@ __export(native_patch_exports, {
169565
169934
  setupNativeResolution: () => setupNativeResolution
169566
169935
  });
169567
169936
  import { join as join68, basename as basename21, dirname as dirname27 } from "node:path";
169568
- import { existsSync as existsSync49, copyFileSync, mkdirSync as mkdirSync12, symlinkSync as symlinkSync2, rmSync as rmSync5, lstatSync as lstatSync3, readlinkSync as readlinkSync2 } from "node:fs";
169937
+ import { existsSync as existsSync50, copyFileSync, mkdirSync as mkdirSync12, symlinkSync as symlinkSync2, rmSync as rmSync5, lstatSync as lstatSync3, readlinkSync as readlinkSync2 } from "node:fs";
169569
169938
  import { tmpdir as tmpdir4 } from "node:os";
169570
169939
  function findStagedNativeDir2() {
169571
169940
  const platform3 = process.platform === "darwin" ? "darwin" : process.platform === "linux" ? "linux" : process.platform === "win32" ? "win32" : "unknown";
@@ -169573,12 +169942,12 @@ function findStagedNativeDir2() {
169573
169942
  const prebuildName = `${platform3}-${arch}`;
169574
169943
  const execDir = dirname27(process.execPath);
169575
169944
  const nextToBinary = join68(execDir, "runtime", prebuildName);
169576
- if (existsSync49(join68(nextToBinary, "pty.node"))) {
169945
+ if (existsSync50(join68(nextToBinary, "pty.node"))) {
169577
169946
  return nextToBinary;
169578
169947
  }
169579
169948
  if (process.env.FUSION_RUNTIME_DIR) {
169580
169949
  const envPath = join68(process.env.FUSION_RUNTIME_DIR, prebuildName);
169581
- if (existsSync49(join68(envPath, "pty.node"))) {
169950
+ if (existsSync50(join68(envPath, "pty.node"))) {
169582
169951
  return envPath;
169583
169952
  }
169584
169953
  }
@@ -169588,11 +169957,11 @@ function cleanupStaleBunfsLinks() {
169588
169957
  if (process.platform === "win32") return;
169589
169958
  const bunfsRoot = "/$bunfs/root";
169590
169959
  try {
169591
- if (existsSync49(bunfsRoot)) {
169960
+ if (existsSync50(bunfsRoot)) {
169592
169961
  const stats = lstatSync3(bunfsRoot);
169593
169962
  if (stats.isSymbolicLink()) {
169594
169963
  const target = readlinkSync2(bunfsRoot);
169595
- if (target.includes("fn-bunfs-") && !existsSync49(target)) {
169964
+ if (target.includes("fn-bunfs-") && !existsSync50(target)) {
169596
169965
  rmSync5(bunfsRoot);
169597
169966
  console.log("[fn-native-patch] Cleaned up stale /$bunfs/root symlink");
169598
169967
  }
@@ -169620,14 +169989,14 @@ function setupNativeResolution() {
169620
169989
  mkdirSync12(platformDir, { recursive: true });
169621
169990
  const ptyNodeDest = join68(platformDir, "pty.node");
169622
169991
  copyFileSync(join68(nativeDir, "pty.node"), ptyNodeDest);
169623
- if (existsSync49(join68(nativeDir, "spawn-helper"))) {
169992
+ if (existsSync50(join68(nativeDir, "spawn-helper"))) {
169624
169993
  copyFileSync(join68(nativeDir, "spawn-helper"), join68(platformDir, "spawn-helper"));
169625
169994
  }
169626
169995
  process.env.FUSION_FAKE_BUNFS_ROOT = tmpRoot;
169627
169996
  if (process.platform !== "win32") {
169628
169997
  const bunfsRoot = "/$bunfs/root";
169629
169998
  try {
169630
- if (existsSync49(bunfsRoot)) {
169999
+ if (existsSync50(bunfsRoot)) {
169631
170000
  const stats = lstatSync3(bunfsRoot);
169632
170001
  if (stats.isSymbolicLink()) {
169633
170002
  rmSync5(bunfsRoot);
@@ -169650,7 +170019,7 @@ function setupNativeResolution() {
169650
170019
  function cleanupNativeResolution() {
169651
170020
  if (bunfsSymlinkPath && process.platform !== "win32") {
169652
170021
  try {
169653
- if (existsSync49(bunfsSymlinkPath)) {
170022
+ if (existsSync50(bunfsSymlinkPath)) {
169654
170023
  const stats = lstatSync3(bunfsSymlinkPath);
169655
170024
  if (stats.isSymbolicLink()) {
169656
170025
  rmSync5(bunfsSymlinkPath);
@@ -169696,7 +170065,7 @@ var init_native_patch = __esm({
169696
170065
  });
169697
170066
 
169698
170067
  // src/bin.ts
169699
- import { existsSync as existsSync50, mkdtempSync as mkdtempSync2, readFileSync as readFileSync23, symlinkSync as symlinkSync3, writeFileSync as writeFileSync6 } from "node:fs";
170068
+ import { existsSync as existsSync51, mkdtempSync as mkdtempSync2, readFileSync as readFileSync23, symlinkSync as symlinkSync3, writeFileSync as writeFileSync6 } from "node:fs";
169700
170069
  import { createRequire as createRequire6 } from "node:module";
169701
170070
  import { join as join69, dirname as dirname28 } from "node:path";
169702
170071
  import { tmpdir as tmpdir5 } from "node:os";
@@ -169719,7 +170088,7 @@ function configurePiPackage() {
169719
170088
  packageJson = JSON.parse(readFileSync23(piPackagePath, "utf-8"));
169720
170089
  for (const entry of ["dist", "docs", "examples", "README.md", "CHANGELOG.md"]) {
169721
170090
  const source = join69(piPackageDir, entry);
169722
- if (existsSync50(source)) {
170091
+ if (existsSync51(source)) {
169723
170092
  symlinkSync3(source, join69(tmp, entry));
169724
170093
  }
169725
170094
  }
@@ -169738,7 +170107,7 @@ setInterval(() => {
169738
170107
  performance3.clearMarks();
169739
170108
  }, 3e4).unref();
169740
170109
  function loadEnvFile(path5) {
169741
- if (!existsSync50(path5)) return;
170110
+ if (!existsSync51(path5)) return;
169742
170111
  const contents = readFileSync23(path5, "utf-8");
169743
170112
  for (const rawLine of contents.split(/\r?\n/)) {
169744
170113
  const line = rawLine.trim();