@runfusion/fusion 0.12.0 → 0.14.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 (74) hide show
  1. package/README.md +13 -0
  2. package/dist/bin.js +1707 -610
  3. package/dist/client/assets/AgentDetailView-CBFUveyO.js +18 -0
  4. package/dist/client/assets/AgentsView-DPezXQ-U.js +522 -0
  5. package/dist/client/assets/{AgentsView-Bkk-uBij.css → AgentsView-V5GhlBYu.css} +1 -1
  6. package/dist/client/assets/ChatView-5N4-EuhD.js +1 -0
  7. package/dist/client/assets/{DevServerView-DQrVLbK5.js → DevServerView-Daft4YFc.js} +1 -1
  8. package/dist/client/assets/{DirectoryPicker-DVmy6sLM.js → DirectoryPicker-rew1y6qO.js} +1 -1
  9. package/dist/client/assets/{DocumentsView-DHEv-Q2a.js → DocumentsView-i72qJzwd.js} +1 -1
  10. package/dist/client/assets/{InsightsView-ByyY7GX7.js → InsightsView-BL5eZJ0a.js} +3 -3
  11. package/dist/client/assets/{MemoryView-Udiu0u8R.js → MemoryView-pl8Cdg_p.js} +2 -2
  12. package/dist/client/assets/{NodesView-CupS-GGc.js → NodesView-D6eJ15zc.js} +4 -4
  13. package/dist/client/assets/PiExtensionsManager-ExInwXWP.js +11 -0
  14. package/dist/client/assets/PluginManager-CYhtxHun.js +1 -0
  15. package/dist/client/assets/{ResearchView-BG9Feaeb.js → ResearchView-B_QPUEjB.js} +1 -1
  16. package/dist/client/assets/{RoadmapsView-BTJtmBnF.js → RoadmapsView-DBNLaEsK.js} +2 -2
  17. package/dist/client/assets/SettingsModal-1ET586M3.js +31 -0
  18. package/dist/client/assets/{SettingsModal-eNCZiHa6.js → SettingsModal-CL_gWmOj.js} +1 -1
  19. package/dist/client/assets/SettingsModal-D_AFkDJa.css +1 -0
  20. package/dist/client/assets/{SetupWizardModal-yf79TN1L.js → SetupWizardModal-CLkY9HFL.js} +1 -1
  21. package/dist/client/assets/{SkillMultiselect-DOj5vX4U.js → SkillMultiselect-B0qi32SQ.js} +1 -1
  22. package/dist/client/assets/{SkillsView-CgnCnikX.js → SkillsView-umVjRq6o.js} +1 -1
  23. package/dist/client/assets/TodoView-CFifSvrD.js +6 -0
  24. package/dist/client/assets/TodoView-SeO9o7km.css +1 -0
  25. package/dist/client/assets/{folder-open-D11gjHGK.js → folder-open-nYPrL1W3.js} +1 -1
  26. package/dist/client/assets/index-Bc8nfKeH.js +661 -0
  27. package/dist/client/assets/index-C1prPuSl.css +1 -0
  28. package/dist/client/assets/{list-checks-CBzPc3GA.js → list-checks-sK8xJeH_.js} +1 -1
  29. package/dist/client/assets/{star-BWcRk8nt.js → star-BRtXbYkB.js} +1 -1
  30. package/dist/client/assets/{upload-91TM4ljC.js → upload-BP60eBwN.js} +1 -1
  31. package/dist/client/assets/{users-BAsI___L.js → users-qSGAX2Pf.js} +1 -1
  32. package/dist/client/index.html +2 -2
  33. package/dist/client/sw.js +6 -0
  34. package/dist/client/version.json +1 -1
  35. package/dist/droid-cli/index.ts +127 -0
  36. package/dist/droid-cli/package.json +37 -0
  37. package/dist/droid-cli/src/__tests__/control-handler.test.ts +164 -0
  38. package/dist/droid-cli/src/__tests__/event-bridge.test.ts +1318 -0
  39. package/dist/droid-cli/src/__tests__/mcp-config.test.ts +310 -0
  40. package/dist/droid-cli/src/__tests__/process-manager.test.ts +818 -0
  41. package/dist/droid-cli/src/__tests__/prompt-builder.test.ts +1206 -0
  42. package/dist/droid-cli/src/__tests__/provider.test.ts +1894 -0
  43. package/dist/droid-cli/src/__tests__/setup-test-isolation.test.ts +32 -0
  44. package/dist/droid-cli/src/__tests__/setup-test-isolation.ts +14 -0
  45. package/dist/droid-cli/src/__tests__/stream-parser.test.ts +188 -0
  46. package/dist/droid-cli/src/__tests__/thinking-config.test.ts +141 -0
  47. package/dist/droid-cli/src/__tests__/tool-mapping.test.ts +253 -0
  48. package/dist/droid-cli/src/control-handler.ts +82 -0
  49. package/dist/droid-cli/src/event-bridge.ts +397 -0
  50. package/dist/droid-cli/src/mcp-config.ts +144 -0
  51. package/dist/droid-cli/src/mcp-schema-server.cjs +49 -0
  52. package/dist/droid-cli/src/process-manager.ts +358 -0
  53. package/dist/droid-cli/src/prompt-builder.ts +629 -0
  54. package/dist/droid-cli/src/provider.ts +447 -0
  55. package/dist/droid-cli/src/stream-parser.ts +37 -0
  56. package/dist/droid-cli/src/thinking-config.ts +83 -0
  57. package/dist/droid-cli/src/tool-mapping.ts +147 -0
  58. package/dist/droid-cli/src/types.ts +87 -0
  59. package/dist/extension.js +542 -141
  60. package/dist/pi-claude-cli/package.json +1 -1
  61. package/dist/pi-claude-cli/src/__tests__/prompt-builder.test.ts +36 -0
  62. package/dist/pi-claude-cli/src/prompt-builder.ts +19 -28
  63. package/package.json +2 -1
  64. package/dist/client/assets/AgentDetailView-B20ApPe1.js +0 -18
  65. package/dist/client/assets/AgentsView-ChN1tgQ0.js +0 -522
  66. package/dist/client/assets/ChatView-oPMFwmoc.js +0 -1
  67. package/dist/client/assets/PiExtensionsManager-DXs2xI8K.js +0 -11
  68. package/dist/client/assets/PluginManager-BCpiZf4_.js +0 -1
  69. package/dist/client/assets/SettingsModal-9HS8MnmW.css +0 -1
  70. package/dist/client/assets/SettingsModal-DZ_LaEhd.js +0 -31
  71. package/dist/client/assets/TodoView-67BMyICY.js +0 -6
  72. package/dist/client/assets/TodoView-C1g65hJo.css +0 -1
  73. package/dist/client/assets/index-BLn1R7Ob.css +0 -1
  74. package/dist/client/assets/index-CLAHcGnI.js +0 -656
package/dist/extension.js CHANGED
@@ -79,6 +79,7 @@ var init_settings_schema = __esm({
79
79
  showGitHubStarButton: true,
80
80
  modelOnboardingComplete: void 0,
81
81
  useClaudeCli: void 0,
82
+ useDroidCli: void 0,
82
83
  // Global baseline lanes for per-role model selection
83
84
  executionGlobalProvider: void 0,
84
85
  executionGlobalModelId: void 0,
@@ -1006,11 +1007,40 @@ function hasAgentIdentity(agent) {
1006
1007
  if (!agent) return false;
1007
1008
  return !!(agent.soul?.trim() || agent.instructionsText?.trim() || agent.instructionsPath?.trim() || agent.memory?.trim());
1008
1009
  }
1009
- function getDefaultHeartbeatProcedurePath(agentId) {
1010
+ function slugifyAgentAssetSegment(value) {
1011
+ return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
1012
+ }
1013
+ function getSafeAgentAssetIdSegment(agentId) {
1014
+ const slug = slugifyAgentAssetSegment(agentId);
1015
+ return slug || "agent";
1016
+ }
1017
+ function getCanonicalAgentAssetDirectoryName(agentName, agentId) {
1018
+ if (!agentId || typeof agentId !== "string") {
1019
+ throw new Error("getCanonicalAgentAssetDirectoryName requires a non-empty agentId");
1020
+ }
1021
+ const safeId = getSafeAgentAssetIdSegment(agentId);
1022
+ const nameSlug = slugifyAgentAssetSegment(agentName ?? "");
1023
+ const prefix = nameSlug || safeId;
1024
+ return `${prefix}-${safeId}`;
1025
+ }
1026
+ function getLegacyAgentAssetDirectoryName(agentId) {
1027
+ if (!agentId || typeof agentId !== "string") {
1028
+ throw new Error("getLegacyAgentAssetDirectoryName requires a non-empty agentId");
1029
+ }
1030
+ return agentId;
1031
+ }
1032
+ function getCanonicalAgentInstructionsBundleDirName(agentName, agentId) {
1033
+ return `${getCanonicalAgentAssetDirectoryName(agentName, agentId)}-instructions`;
1034
+ }
1035
+ function getLegacyAgentInstructionsBundleDirName(agentId) {
1036
+ return `${getLegacyAgentAssetDirectoryName(agentId)}-instructions`;
1037
+ }
1038
+ function getDefaultHeartbeatProcedurePath(agentId, agentName) {
1010
1039
  if (!agentId || typeof agentId !== "string") {
1011
1040
  throw new Error("getDefaultHeartbeatProcedurePath requires a non-empty agentId");
1012
1041
  }
1013
- return `.fusion/agents/${agentId}/HEARTBEAT.md`;
1042
+ const directory = agentName ? getCanonicalAgentAssetDirectoryName(agentName, agentId) : getLegacyAgentAssetDirectoryName(agentId);
1043
+ return `.fusion/agents/${directory}/HEARTBEAT.md`;
1014
1044
  }
1015
1045
  function agentToConfigSnapshot(agent) {
1016
1046
  return {
@@ -2704,7 +2734,7 @@ var init_db = __esm({
2704
2734
  "use strict";
2705
2735
  init_sqlite_adapter();
2706
2736
  init_types();
2707
- SCHEMA_VERSION = 55;
2737
+ SCHEMA_VERSION = 58;
2708
2738
  SCHEMA_SQL = `
2709
2739
  -- Tasks table with JSON columns for nested data
2710
2740
  CREATE TABLE IF NOT EXISTS tasks (
@@ -4388,6 +4418,44 @@ CREATE INDEX IF NOT EXISTS idxTodoItemsSortOrder ON todo_items(listId, sortOrder
4388
4418
  this.db.exec(`CREATE INDEX IF NOT EXISTS idxResearchExportsRunId ON research_exports(runId)`);
4389
4419
  });
4390
4420
  }
4421
+ if (version < 56) {
4422
+ this.applyMigration(56, () => {
4423
+ if (this.hasTable("chat_sessions")) {
4424
+ this.addColumnIfMissing("chat_sessions", "cliSessionFile", "TEXT");
4425
+ }
4426
+ });
4427
+ }
4428
+ if (version < 57) {
4429
+ this.applyMigration(57, () => {
4430
+ if (this.hasTable("ai_sessions")) {
4431
+ this.addColumnIfMissing("ai_sessions", "archived", "INTEGER DEFAULT 0");
4432
+ this.db.exec(
4433
+ "CREATE INDEX IF NOT EXISTS idxAiSessionsArchived ON ai_sessions(archived)"
4434
+ );
4435
+ }
4436
+ });
4437
+ }
4438
+ if (version < 58) {
4439
+ this.applyMigration(58, () => {
4440
+ const newCommand = "npx runfusion.ai backup --create";
4441
+ if (this.hasTable("automations") && this.hasColumn("automations", "command")) {
4442
+ this.db.prepare(
4443
+ `UPDATE automations
4444
+ SET command = ?, updatedAt = ?
4445
+ WHERE name = 'Database Backup'
4446
+ AND (command LIKE 'fn backup%' OR command LIKE 'kb backup%' OR command LIKE 'fusion backup%')`
4447
+ ).run(newCommand, (/* @__PURE__ */ new Date()).toISOString());
4448
+ }
4449
+ if (this.hasTable("routines") && this.hasColumn("routines", "command")) {
4450
+ this.db.prepare(
4451
+ `UPDATE routines
4452
+ SET command = ?, updatedAt = ?
4453
+ WHERE name = 'Database Backup'
4454
+ AND (command LIKE 'fn backup%' OR command LIKE 'kb backup%' OR command LIKE 'fusion backup%')`
4455
+ ).run(newCommand, (/* @__PURE__ */ new Date()).toISOString());
4456
+ }
4457
+ });
4458
+ }
4391
4459
  }
4392
4460
  /**
4393
4461
  * Run a single migration step inside a transaction and bump the version.
@@ -4651,7 +4719,7 @@ var init_agent_store = __esm({
4651
4719
  if (agent.heartbeatProcedurePath !== DEFAULT_HEARTBEAT_PROCEDURE_PATH) {
4652
4720
  continue;
4653
4721
  }
4654
- const newRelPath = getDefaultHeartbeatProcedurePath(agent.id);
4722
+ const newRelPath = await this.resolveCompatibleHeartbeatProcedurePath(agent);
4655
4723
  const newAbsPath = join3(this.rootDir, "..", newRelPath);
4656
4724
  if (legacyContent !== null) {
4657
4725
  try {
@@ -4812,7 +4880,7 @@ var init_agent_store = __esm({
4812
4880
  const metadata = input.metadata ?? {};
4813
4881
  const runtimeConfig = resolveCreationRuntimeConfig(input.runtimeConfig, metadata);
4814
4882
  const ephemeral = isEphemeralAgent({ metadata, name: input.name, role: input.role, reportsTo: input.reportsTo });
4815
- const resolvedHeartbeatProcedurePath = input.heartbeatProcedurePath ?? (ephemeral ? void 0 : getDefaultHeartbeatProcedurePath(agentId));
4883
+ const resolvedHeartbeatProcedurePath = input.heartbeatProcedurePath ?? (ephemeral ? void 0 : getDefaultHeartbeatProcedurePath(agentId, input.name));
4816
4884
  const agent = {
4817
4885
  id: agentId,
4818
4886
  name: input.name.trim(),
@@ -5049,14 +5117,16 @@ var init_agent_store = __esm({
5049
5117
  * Does not create the directory.
5050
5118
  */
5051
5119
  getInstructionsDir(agentId) {
5052
- return this.getBundleDir(agentId);
5120
+ const agent = this.readAgent(agentId);
5121
+ const agentName = agent?.name ?? "";
5122
+ return join3(this.agentsDir, getCanonicalAgentInstructionsBundleDirName(agentName, agentId));
5053
5123
  }
5054
5124
  /**
5055
5125
  * List markdown files in an agent's managed instructions bundle.
5056
5126
  * Returns [] when the bundle directory does not exist.
5057
5127
  */
5058
5128
  async listBundleFiles(agentId) {
5059
- const bundleDir = this.getBundleDir(agentId);
5129
+ const bundleDir = await this.resolveCompatibleBundleDir(agentId, false);
5060
5130
  try {
5061
5131
  const entries = await readdir(bundleDir, { withFileTypes: true });
5062
5132
  return entries.filter((entry) => entry.isFile() && entry.name.endsWith(".md")).map((entry) => entry.name).sort((a, b) => a.localeCompare(b));
@@ -5072,7 +5142,8 @@ var init_agent_store = __esm({
5072
5142
  */
5073
5143
  async readBundleFile(agentId, filePath) {
5074
5144
  this.validateBundleFilePath(filePath);
5075
- const resolvedPath = join3(this.getBundleDir(agentId), filePath);
5145
+ const bundleDir = await this.resolveCompatibleBundleDir(agentId, false);
5146
+ const resolvedPath = join3(bundleDir, filePath);
5076
5147
  return readFile(resolvedPath, "utf-8");
5077
5148
  }
5078
5149
  /**
@@ -5081,7 +5152,7 @@ var init_agent_store = __esm({
5081
5152
  async writeBundleFile(agentId, filePath, content) {
5082
5153
  return this.withLock(agentId, async () => {
5083
5154
  this.validateBundleFilePath(filePath);
5084
- const bundleDir = this.getBundleDir(agentId);
5155
+ const bundleDir = await this.resolveCompatibleBundleDir(agentId, true);
5085
5156
  await mkdir(bundleDir, { recursive: true });
5086
5157
  const existingFiles = await this.listBundleFiles(agentId);
5087
5158
  const isOverwrite = existingFiles.includes(filePath);
@@ -5100,7 +5171,8 @@ var init_agent_store = __esm({
5100
5171
  async deleteBundleFile(agentId, filePath) {
5101
5172
  return this.withLock(agentId, async () => {
5102
5173
  this.validateBundleFilePath(filePath);
5103
- await unlink(join3(this.getBundleDir(agentId), filePath));
5174
+ const bundleDir = await this.resolveCompatibleBundleDir(agentId, false);
5175
+ await unlink(join3(bundleDir, filePath));
5104
5176
  });
5105
5177
  }
5106
5178
  /**
@@ -5122,7 +5194,7 @@ var init_agent_store = __esm({
5122
5194
  };
5123
5195
  const updated = await this.updateAgent(agentId, { bundleConfig: normalizedConfig });
5124
5196
  if (normalizedConfig.mode === "managed") {
5125
- await mkdir(this.getBundleDir(agentId), { recursive: true });
5197
+ await mkdir(await this.resolveCompatibleBundleDir(agentId, true), { recursive: true });
5126
5198
  }
5127
5199
  return updated;
5128
5200
  }
@@ -5145,7 +5217,7 @@ var init_agent_store = __esm({
5145
5217
  bundleConfig: { mode: "managed", entryFile, files: [] }
5146
5218
  });
5147
5219
  }
5148
- await mkdir(this.getBundleDir(agentId), { recursive: true });
5220
+ await mkdir(await this.resolveCompatibleBundleDir(agentId, true), { recursive: true });
5149
5221
  const files = [];
5150
5222
  if (hasInstructionsText) {
5151
5223
  await this.writeBundleFile(agentId, entryFile, agent.instructionsText ?? "");
@@ -6106,8 +6178,87 @@ var init_agent_store = __esm({
6106
6178
  }
6107
6179
  return null;
6108
6180
  }
6109
- getBundleDir(agentId) {
6110
- return join3(this.agentsDir, `${agentId}-instructions`);
6181
+ getCanonicalBundleDir(agent) {
6182
+ return join3(this.agentsDir, getCanonicalAgentInstructionsBundleDirName(agent.name, agent.id));
6183
+ }
6184
+ getLegacyBundleDir(agentId) {
6185
+ return join3(this.agentsDir, getLegacyAgentInstructionsBundleDirName(agentId));
6186
+ }
6187
+ async resolveCompatibleBundleDir(agentId, createIfMissing) {
6188
+ const agent = this.readAgent(agentId);
6189
+ if (!agent) {
6190
+ throw new Error(`Agent ${agentId} not found`);
6191
+ }
6192
+ const canonicalDir = this.getCanonicalBundleDir(agent);
6193
+ if (await this.pathExists(canonicalDir)) {
6194
+ return canonicalDir;
6195
+ }
6196
+ const compatibleDir = await this.findExistingDisplayNameBundleDir(agent);
6197
+ if (compatibleDir) {
6198
+ return compatibleDir;
6199
+ }
6200
+ const legacyDir = this.getLegacyBundleDir(agent.id);
6201
+ if (await this.pathExists(legacyDir)) {
6202
+ return legacyDir;
6203
+ }
6204
+ return createIfMissing ? canonicalDir : canonicalDir;
6205
+ }
6206
+ async findExistingDisplayNameBundleDir(agent) {
6207
+ const safeId = getSafeAgentAssetIdSegment(agent.id);
6208
+ try {
6209
+ const entries = await readdir(this.agentsDir, { withFileTypes: true });
6210
+ const candidates = entries.filter((entry) => entry.isDirectory() && entry.name.endsWith("-instructions")).map((entry) => entry.name).filter((name) => {
6211
+ const base = name.slice(0, -"-instructions".length);
6212
+ return base.endsWith(`-${safeId}`);
6213
+ }).sort((a, b) => a.localeCompare(b));
6214
+ if (candidates.length === 0) {
6215
+ return null;
6216
+ }
6217
+ const canonicalName = getCanonicalAgentInstructionsBundleDirName(agent.name, agent.id);
6218
+ const selected = candidates.find((candidate) => candidate === canonicalName) ?? candidates[0];
6219
+ return join3(this.agentsDir, selected);
6220
+ } catch (err) {
6221
+ if (err.code === "ENOENT") {
6222
+ return null;
6223
+ }
6224
+ throw err;
6225
+ }
6226
+ }
6227
+ async resolveCompatibleHeartbeatProcedurePath(agent) {
6228
+ const canonicalPath = getDefaultHeartbeatProcedurePath(agent.id, agent.name);
6229
+ const canonicalAbs = join3(this.rootDir, "..", canonicalPath);
6230
+ if (await this.pathExists(canonicalAbs)) {
6231
+ return canonicalPath;
6232
+ }
6233
+ const safeId = getSafeAgentAssetIdSegment(agent.id);
6234
+ try {
6235
+ const entries = await readdir(this.agentsDir, { withFileTypes: true });
6236
+ const compatibleDir = entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name).find((name) => name.endsWith(`-${safeId}`));
6237
+ if (compatibleDir) {
6238
+ const candidatePath = `.fusion/agents/${compatibleDir}/HEARTBEAT.md`;
6239
+ if (await this.pathExists(join3(this.rootDir, "..", candidatePath))) {
6240
+ return candidatePath;
6241
+ }
6242
+ }
6243
+ } catch (err) {
6244
+ if (err.code !== "ENOENT") {
6245
+ throw err;
6246
+ }
6247
+ }
6248
+ const legacyPath = `.fusion/agents/${getLegacyAgentAssetDirectoryName(agent.id)}/HEARTBEAT.md`;
6249
+ const legacyAbs = join3(this.rootDir, "..", legacyPath);
6250
+ if (await this.pathExists(legacyAbs)) {
6251
+ return legacyPath;
6252
+ }
6253
+ return canonicalPath;
6254
+ }
6255
+ async pathExists(path2) {
6256
+ try {
6257
+ await access(path2, fsConstants.F_OK);
6258
+ return true;
6259
+ } catch {
6260
+ return false;
6261
+ }
6111
6262
  }
6112
6263
  validateBundleFilePath(filePath) {
6113
6264
  if (typeof filePath !== "string") {
@@ -35839,6 +35990,20 @@ function reconcileClaudeCliPaths(paths, vendoredPath) {
35839
35990
  }
35840
35991
  return filtered;
35841
35992
  }
35993
+ function isExternalDroidCliPath(p, vendoredPath) {
35994
+ if (vendoredPath && p === vendoredPath) return false;
35995
+ return /(^|[/\\])droid-cli([/\\]|$)/i.test(p);
35996
+ }
35997
+ function reconcileDroidCliPaths(paths, vendoredPath) {
35998
+ if (!vendoredPath) {
35999
+ return [...paths];
36000
+ }
36001
+ const filtered = paths.filter((p) => !isExternalDroidCliPath(p, vendoredPath));
36002
+ if (!filtered.includes(vendoredPath)) {
36003
+ return [vendoredPath, ...filtered];
36004
+ }
36005
+ return filtered;
36006
+ }
35842
36007
  function getDisplayPathWithinRoot(root, targetPath) {
35843
36008
  const usesWindowsPaths = /^[A-Za-z]:[\\/]/.test(root) || /^[A-Za-z]:[\\/]/.test(targetPath) || root.includes("\\") || targetPath.includes("\\");
35844
36009
  const pathApi = usesWindowsPaths ? win32 : { relative: relative2, isAbsolute: isAbsolute5, sep: sep4 };
@@ -36096,6 +36261,85 @@ var init_gh_cli = __esm({
36096
36261
  }
36097
36262
  });
36098
36263
 
36264
+ // ../core/src/fn-binary.ts
36265
+ import { spawn as spawn2 } from "node:child_process";
36266
+ import { platform as platform2 } from "node:os";
36267
+ function runProbe(command, args, timeoutMs) {
36268
+ return new Promise((resolve19) => {
36269
+ let stdout = "";
36270
+ let stderr = "";
36271
+ const child = spawn2(command, args, { stdio: ["ignore", "pipe", "pipe"], shell: false });
36272
+ const timer = setTimeout(() => {
36273
+ try {
36274
+ child.kill("SIGKILL");
36275
+ } catch {
36276
+ }
36277
+ }, timeoutMs);
36278
+ child.stdout?.on("data", (chunk) => {
36279
+ stdout += chunk.toString("utf8");
36280
+ });
36281
+ child.stderr?.on("data", (chunk) => {
36282
+ stderr += chunk.toString("utf8");
36283
+ });
36284
+ child.on("error", (err) => {
36285
+ clearTimeout(timer);
36286
+ resolve19({ exitCode: null, stdout, stderr: stderr || err.message });
36287
+ });
36288
+ child.on("close", (exitCode) => {
36289
+ clearTimeout(timer);
36290
+ resolve19({ exitCode, stdout, stderr });
36291
+ });
36292
+ });
36293
+ }
36294
+ async function whichBinary(name) {
36295
+ const isWindows = platform2() === "win32";
36296
+ const lookup = isWindows ? "where" : "which";
36297
+ const result = await runProbe(lookup, [name], 5e3);
36298
+ if (result.exitCode !== 0) return void 0;
36299
+ const firstLine = result.stdout.split(/\r?\n/).map((s) => s.trim()).find(Boolean);
36300
+ return firstLine || void 0;
36301
+ }
36302
+ async function probeVersion(binary) {
36303
+ const result = await runProbe(binary, ["--version"], 1e4);
36304
+ if (result.exitCode !== 0) return void 0;
36305
+ const text = (result.stdout || result.stderr).trim();
36306
+ if (!text) return void 0;
36307
+ const match = text.match(/\d+\.\d+\.\d+(?:-[\w.]+)?/);
36308
+ return match ? match[0] : text.split(/\s+/)[0];
36309
+ }
36310
+ async function detectFnBinary() {
36311
+ for (const candidate of CANDIDATES) {
36312
+ try {
36313
+ const resolvedPath = await whichBinary(candidate);
36314
+ if (!resolvedPath) continue;
36315
+ const version = await probeVersion(candidate);
36316
+ return {
36317
+ installed: true,
36318
+ binary: candidate,
36319
+ path: resolvedPath,
36320
+ version,
36321
+ invocation: candidate
36322
+ };
36323
+ } catch {
36324
+ }
36325
+ }
36326
+ return {
36327
+ installed: false,
36328
+ invocation: FN_NPX_INVOCATION
36329
+ };
36330
+ }
36331
+ var FN_NPM_PACKAGE, FN_INSTALL_CURL, FN_INSTALL_NPM, FN_NPX_INVOCATION, CANDIDATES;
36332
+ var init_fn_binary = __esm({
36333
+ "../core/src/fn-binary.ts"() {
36334
+ "use strict";
36335
+ FN_NPM_PACKAGE = "runfusion.ai";
36336
+ FN_INSTALL_CURL = "curl -fsSL https://runfusion.ai/install.sh | sh";
36337
+ FN_INSTALL_NPM = `npm install -g ${FN_NPM_PACKAGE}`;
36338
+ FN_NPX_INVOCATION = `npx -y ${FN_NPM_PACKAGE}`;
36339
+ CANDIDATES = ["fn", "fusion"];
36340
+ }
36341
+ });
36342
+
36099
36343
  // ../core/src/settings-validation.ts
36100
36344
  function validateUnavailableNodePolicy(value) {
36101
36345
  if (value === void 0) {
@@ -38004,7 +38248,7 @@ function sanitizeTitle(raw) {
38004
38248
  const firstLine = raw.split(/\r?\n/).map((l) => l.trim()).find((l) => l.length > 0);
38005
38249
  if (!firstLine) return null;
38006
38250
  let title = firstLine.replace(/^[-*]\s+/, "").replace(/^["'`]+|["'`]+$/g, "").trim();
38007
- title = title.replace(/^(?:title|subject|here(?:'s| is)(?: the)? title|generated title)\s*[:\-]\s*/i, "").trim();
38251
+ title = title.replace(/^(?:title|subject|here(?:'s| is)(?: the)? title|generated title)\s*[:-]\s*/i, "").trim();
38008
38252
  title = title.replace(/\*\*([^*]+)\*\*/g, "$1").replace(/__([^_]+)__/g, "$1").replace(/(?<![*\w])\*([^*]+)\*(?![*\w])/g, "$1").replace(/(?<![_\w])_([^_]+)_(?![_\w])/g, "$1");
38009
38253
  title = title.replace(/[.!?,;:]+$/, "").trim();
38010
38254
  if (!title) return null;
@@ -49952,7 +50196,8 @@ var init_chat_store = __esm({
49952
50196
  modelProvider: row.modelProvider ?? null,
49953
50197
  modelId: row.modelId ?? null,
49954
50198
  createdAt: row.createdAt,
49955
- updatedAt: row.updatedAt
50199
+ updatedAt: row.updatedAt,
50200
+ cliSessionFile: row.cliSessionFile ?? null
49956
50201
  };
49957
50202
  }
49958
50203
  /**
@@ -49989,7 +50234,8 @@ var init_chat_store = __esm({
49989
50234
  modelProvider: input.modelProvider ?? null,
49990
50235
  modelId: input.modelId ?? null,
49991
50236
  createdAt: now,
49992
- updatedAt: now
50237
+ updatedAt: now,
50238
+ cliSessionFile: null
49993
50239
  };
49994
50240
  this.db.prepare(`
49995
50241
  INSERT INTO chat_sessions (id, agentId, title, status, projectId, modelProvider, modelId, createdAt, updatedAt)
@@ -50147,6 +50393,21 @@ var init_chat_store = __esm({
50147
50393
  archiveSession(id) {
50148
50394
  return this.updateSession(id, { status: "archived" });
50149
50395
  }
50396
+ /**
50397
+ * Persist the pi/Claude CLI session file path for a chat. Called once,
50398
+ * after the SessionManager for the chat first creates its on-disk file,
50399
+ * so subsequent turns can reopen it via SessionManager.open.
50400
+ *
50401
+ * Does not bump updatedAt or emit events — this is internal plumbing,
50402
+ * not a user-visible state change.
50403
+ *
50404
+ * @param id - Session ID
50405
+ * @param cliSessionFile - Absolute path to the session file, or null to clear
50406
+ */
50407
+ setCliSessionFile(id, cliSessionFile) {
50408
+ this.db.prepare("UPDATE chat_sessions SET cliSessionFile = ? WHERE id = ?").run(cliSessionFile, id);
50409
+ this.db.bumpLastModified();
50410
+ }
50150
50411
  /**
50151
50412
  * Delete a chat session and all its messages.
50152
50413
  * Messages are cascade-deleted via foreign key constraint.
@@ -50366,6 +50627,10 @@ __export(src_exports, {
50366
50627
  EXECUTION_MODES: () => EXECUTION_MODES,
50367
50628
  FEATURE_LOOP_STATES: () => FEATURE_LOOP_STATES,
50368
50629
  FEATURE_STATUSES: () => FEATURE_STATUSES,
50630
+ FN_INSTALL_CURL: () => FN_INSTALL_CURL,
50631
+ FN_INSTALL_NPM: () => FN_INSTALL_NPM,
50632
+ FN_NPM_PACKAGE: () => FN_NPM_PACKAGE,
50633
+ FN_NPX_INVOCATION: () => FN_NPX_INVOCATION,
50369
50634
  FileMemoryBackend: () => FileMemoryBackend,
50370
50635
  FirstRunDetector: () => FirstRunDetector,
50371
50636
  GLOBAL_SETTINGS_KEYS: () => GLOBAL_SETTINGS_KEYS,
@@ -50477,6 +50742,7 @@ __export(src_exports, {
50477
50742
  createInsightExtractionAutomation: () => createInsightExtractionAutomation,
50478
50743
  createMemoryDreamsAutomation: () => createMemoryDreamsAutomation,
50479
50744
  dailyMemoryPath: () => dailyMemoryPath,
50745
+ detectFnBinary: () => detectFnBinary,
50480
50746
  detectLegacyData: () => detectLegacyData,
50481
50747
  diffConfigSnapshots: () => diffConfigSnapshots,
50482
50748
  discoverPiExtensions: () => discoverPiExtensions,
@@ -50598,6 +50864,7 @@ __export(src_exports, {
50598
50864
  readProjectMemoryWithBackend: () => readProjectMemoryWithBackend,
50599
50865
  readWorkingMemory: () => readWorkingMemory,
50600
50866
  reconcileClaudeCliPaths: () => reconcileClaudeCliPaths,
50867
+ reconcileDroidCliPaths: () => reconcileDroidCliPaths,
50601
50868
  refreshQmdProjectMemoryIndex: () => refreshQmdProjectMemoryIndex,
50602
50869
  registerMemoryBackend: () => registerMemoryBackend,
50603
50870
  renderMemoryAuditMarkdown: () => renderMemoryAuditMarkdown,
@@ -50687,6 +50954,7 @@ var init_src = __esm({
50687
50954
  init_automation();
50688
50955
  init_automation_store();
50689
50956
  init_run_command();
50957
+ init_fn_binary();
50690
50958
  init_node_override_guard();
50691
50959
  init_settings_validation();
50692
50960
  init_routine();
@@ -52673,6 +52941,21 @@ function resolveVendoredClaudeCliEntry() {
52673
52941
  return null;
52674
52942
  }
52675
52943
  }
52944
+ function resolveVendoredDroidCliEntry() {
52945
+ try {
52946
+ const require_ = createRequire2(import.meta.url);
52947
+ const pkgJsonPath = require_.resolve("@fusion/droid-cli/package.json");
52948
+ const pkgJson = JSON.parse(readFileSync8(pkgJsonPath, "utf-8"));
52949
+ const extensions = pkgJson.pi?.extensions;
52950
+ if (!Array.isArray(extensions) || extensions.length === 0) return null;
52951
+ const entry = extensions[0];
52952
+ if (typeof entry !== "string" || entry.length === 0) return null;
52953
+ const path2 = resolve11(dirname8(pkgJsonPath), entry);
52954
+ return existsSync20(path2) ? path2 : null;
52955
+ } catch {
52956
+ return null;
52957
+ }
52958
+ }
52676
52959
  async function registerExtensionProviders(cwd, modelRegistry) {
52677
52960
  try {
52678
52961
  const agentDir = getPackageManagerAgentDir();
@@ -52688,8 +52971,13 @@ async function registerExtensionProviders(cwd, modelRegistry) {
52688
52971
  [...getEnabledPiExtensionPaths(cwd), ...packageExtensionPaths],
52689
52972
  vendoredClaudeCli
52690
52973
  );
52691
- const extensionsResult = await discoverAndLoadExtensions(
52974
+ const vendoredDroidCli = resolveVendoredDroidCliEntry();
52975
+ const doubleReconciledPaths = reconcileDroidCliPaths(
52692
52976
  reconciledPaths,
52977
+ vendoredDroidCli
52978
+ );
52979
+ const extensionsResult = await discoverAndLoadExtensions(
52980
+ doubleReconciledPaths,
52693
52981
  cwd,
52694
52982
  join25(resolvePiExtensionProjectRoot(cwd), ".fusion", "disabled-auto-extension-discovery")
52695
52983
  );
@@ -54346,14 +54634,25 @@ async function getAgentMemoryWindow(rootDir, agentMemory, path2, startLine = 1,
54346
54634
  backend: "agent-memory"
54347
54635
  };
54348
54636
  }
54349
- function createTaskCreateTool(store, provenance) {
54637
+ async function createAgentTask(store, input, options) {
54638
+ const settings = typeof store.getSettings === "function" ? await store.getSettings() : {};
54639
+ const rootDir = options?.rootDir;
54640
+ return store.createTask(input, {
54641
+ settings: { autoSummarizeTitles: settings.autoSummarizeTitles === true },
54642
+ onSummarize: rootDir ? async (description) => {
54643
+ const resolved = resolveTitleSummarizerSettingsModel(settings);
54644
+ return summarizeTitle(description, rootDir, resolved.provider, resolved.modelId);
54645
+ } : void 0
54646
+ });
54647
+ }
54648
+ function createTaskCreateTool(store, provenance, options) {
54350
54649
  return {
54351
54650
  name: "fn_task_create",
54352
54651
  label: "Create Task",
54353
54652
  description: "Create a new task for out-of-scope work discovered during execution. The task goes into triage where it will be specified by the AI. Optionally set dependencies (e.g., the new task depends on the current one, or the current task should wait for the new one).",
54354
54653
  parameters: taskCreateParams,
54355
54654
  execute: async (_id, params) => {
54356
- const task = await store.createTask({
54655
+ const task = await createAgentTask(store, {
54357
54656
  description: params.description,
54358
54657
  dependencies: params.dependencies,
54359
54658
  column: "triage",
@@ -54362,7 +54661,7 @@ function createTaskCreateTool(store, provenance) {
54362
54661
  sourceAgentId: provenance.sourceAgentId,
54363
54662
  sourceRunId: provenance.sourceRunId
54364
54663
  } : void 0
54365
- });
54664
+ }, options);
54366
54665
  const deps = task.dependencies.length ? ` (depends on: ${task.dependencies.join(", ")})` : "";
54367
54666
  return {
54368
54667
  content: [{
@@ -54715,7 +55014,7 @@ ${lines.join("\n\n")}` }],
54715
55014
  }
54716
55015
  };
54717
55016
  }
54718
- function createDelegateTaskTool(agentStore, taskStore) {
55017
+ function createDelegateTaskTool(agentStore, taskStore, options) {
54719
55018
  return {
54720
55019
  name: "fn_delegate_task",
54721
55020
  label: "Delegate Task",
@@ -54735,13 +55034,13 @@ function createDelegateTaskTool(agentStore, taskStore) {
54735
55034
  details: {}
54736
55035
  };
54737
55036
  }
54738
- const task = await taskStore.createTask({
55037
+ const task = await createAgentTask(taskStore, {
54739
55038
  description: params.description,
54740
55039
  dependencies: params.dependencies,
54741
55040
  column: "todo",
54742
55041
  assignedAgentId: params.agent_id,
54743
55042
  source: { sourceType: "api" }
54744
- });
55043
+ }, options);
54745
55044
  const deps = task.dependencies.length ? ` (depends on: ${task.dependencies.join(", ")})` : "";
54746
55045
  return {
54747
55046
  content: [{
@@ -57568,7 +57867,7 @@ Write the PROMPT.md directly using the write tool, then call \`fn_review_spec()\
57568
57867
  // Agent delegation tools — discover and delegate work to other agents.
57569
57868
  ...this.options.agentStore ? [
57570
57869
  createListAgentsTool(this.options.agentStore),
57571
- createDelegateTaskTool(this.options.agentStore, this.store)
57870
+ createDelegateTaskTool(this.options.agentStore, this.store, { rootDir: this.rootDir })
57572
57871
  ] : [],
57573
57872
  this.createReviewSpecTool(
57574
57873
  task.id,
@@ -58107,7 +58406,7 @@ Remove or replace these ids and call fn_task_create again.`
58107
58406
  planLog.warn(`${options.parentTaskId}: failed to load parent task for fn_task_create inheritance: ${msg}`);
58108
58407
  parentTask = void 0;
58109
58408
  }
58110
- const newTask = await store.createTask({
58409
+ const newTask = await createAgentTask(store, {
58111
58410
  title: params.title,
58112
58411
  description: params.description,
58113
58412
  dependencies: validDeps,
@@ -58121,7 +58420,7 @@ Remove or replace these ids and call fn_task_create again.`
58121
58420
  sourceType: "agent_heartbeat",
58122
58421
  sourceParentTaskId: options.parentTaskId
58123
58422
  }
58124
- });
58423
+ }, { rootDir: this.rootDir });
58125
58424
  options.createdSubtasksRef.current.push(newTask.id);
58126
58425
  return {
58127
58426
  content: [
@@ -58549,7 +58848,7 @@ var init_run_audit = __esm({
58549
58848
  });
58550
58849
 
58551
58850
  // ../engine/src/merger.ts
58552
- import { execSync, exec as exec2, spawn as spawn2 } from "node:child_process";
58851
+ import { execSync, exec as exec2, spawn as spawn3 } from "node:child_process";
58553
58852
  import { promisify as promisify3 } from "node:util";
58554
58853
  import { existsSync as existsSync22 } from "node:fs";
58555
58854
  import { join as join29 } from "node:path";
@@ -58564,7 +58863,7 @@ async function execWithProcessGroup(command, options) {
58564
58863
  return;
58565
58864
  }
58566
58865
  const useProcessGroup = process.platform !== "win32";
58567
- const child = spawn2(command, {
58866
+ const child = spawn3(command, {
58568
58867
  cwd: options.cwd,
58569
58868
  shell: true,
58570
58869
  detached: useProcessGroup,
@@ -63300,10 +63599,10 @@ var init_step_session_executor = __esm({
63300
63599
  ] : [];
63301
63600
  const memoryTools = createMemoryTools(this.options.rootDir, settings);
63302
63601
  const taskLogTool = this.options.store ? [createTaskLogTool(this.options.store, taskDetail.id)] : [];
63303
- const taskCreateTool = this.options.store ? [createTaskCreateTool(this.options.store)] : [];
63602
+ const taskCreateTool = this.options.store ? [createTaskCreateTool(this.options.store, void 0, { rootDir: this.options.rootDir })] : [];
63304
63603
  const delegationTools = this.options.agentStore ? [
63305
63604
  createListAgentsTool(this.options.agentStore),
63306
- createDelegateTaskTool(this.options.agentStore, this.options.store)
63605
+ createDelegateTaskTool(this.options.agentStore, this.options.store, { rootDir: this.options.rootDir })
63307
63606
  ] : [];
63308
63607
  const messagingTools = this.options.messageStore && taskDetail.assignedAgentId ? [
63309
63608
  createSendMessageTool(this.options.messageStore, taskDetail.assignedAgentId),
@@ -63714,7 +64013,7 @@ var init_task_completion = __esm({
63714
64013
  });
63715
64014
 
63716
64015
  // ../engine/src/run-verification-tool.ts
63717
- import { spawn as spawn3 } from "node:child_process";
64016
+ import { spawn as spawn4 } from "node:child_process";
63718
64017
  import { existsSync as existsSync26 } from "node:fs";
63719
64018
  import { isAbsolute as isAbsolute10, join as join34 } from "node:path";
63720
64019
  import { Type as Type4 } from "@mariozechner/pi-ai";
@@ -63747,7 +64046,7 @@ async function runVerificationCommand2(opts) {
63747
64046
  const stdoutBuf = { head: "", tail: "", totalBytes: 0 };
63748
64047
  const stderrBuf = { head: "", tail: "", totalBytes: 0 };
63749
64048
  return new Promise((resolve19) => {
63750
- const child = spawn3(command, {
64049
+ const child = spawn4(command, {
63751
64050
  cwd,
63752
64051
  stdio: ["ignore", "pipe", "pipe"],
63753
64052
  env: { ...process.env },
@@ -66035,7 +66334,7 @@ The tool prevents your session from being killed by the inactivity watchdog duri
66035
66334
  // Agent delegation tools — discover and delegate work to other agents.
66036
66335
  ...this.options.agentStore ? [
66037
66336
  createListAgentsTool(this.options.agentStore),
66038
- createDelegateTaskTool(this.options.agentStore, this.store)
66337
+ createDelegateTaskTool(this.options.agentStore, this.store, { rootDir: this.rootDir })
66039
66338
  ] : [],
66040
66339
  // Messaging tools — allows executor agents to send and receive messages.
66041
66340
  ...this.options.messageStore && assignedAgentId ? [
@@ -66729,7 +67028,7 @@ The tool prevents your session from being killed by the inactivity watchdog duri
66729
67028
  return createTaskLogTool(this.store, taskId);
66730
67029
  }
66731
67030
  createTaskCreateTool() {
66732
- return createTaskCreateTool(this.store, { sourceType: "api" });
67031
+ return createTaskCreateTool(this.store, { sourceType: "api" }, { rootDir: this.rootDir });
66733
67032
  }
66734
67033
  createTaskDocumentWriteTool(taskId) {
66735
67034
  return createTaskDocumentWriteTool(this.store, taskId);
@@ -71664,7 +71963,7 @@ function isTickableState(state) {
71664
71963
  function isHeartbeatManaged(agent) {
71665
71964
  return !isEphemeralAgent(agent);
71666
71965
  }
71667
- var HEARTBEAT_SYSTEM_PROMPT, HEARTBEAT_NO_TASK_SYSTEM_PROMPT, HEARTBEAT_PROCEDURE, heartbeatDoneParams, HeartbeatMonitor, OVERDUE_FIRE_JITTER_MS, HeartbeatTriggerScheduler;
71966
+ var HEARTBEAT_SYSTEM_PROMPT, HEARTBEAT_NO_TASK_SYSTEM_PROMPT, HEARTBEAT_PROCEDURE, HEARTBEAT_NO_TASK_PROCEDURE, heartbeatDoneParams, HeartbeatMonitor, OVERDUE_FIRE_JITTER_MS, HeartbeatTriggerScheduler;
71668
71967
  var init_agent_heartbeat = __esm({
71669
71968
  "../engine/src/agent-heartbeat.ts"() {
71670
71969
  "use strict";
@@ -71854,6 +72153,32 @@ When sending messages:
71854
72153
  Critical: a heartbeat without observable progress (a log, a document write, a
71855
72154
  status change, a comment, a delegation, or an explicit "no-op with reason") is
71856
72155
  a bug. Do not loop on the same plan across heartbeats without recording why.`;
72156
+ HEARTBEAT_NO_TASK_PROCEDURE = `## Heartbeat Procedure (run every tick, in order)
72157
+
72158
+ 1. **Identity & context** \u2014 review the **Identity Snapshot** at the top of
72159
+ this prompt. Confirm your role, soul, instructions, and memory match what
72160
+ you expect, and surface any anomalies in your first text output before
72161
+ doing anything else. (If fn_identity is available in your runtime you may
72162
+ also call it for full structured detail; the snapshot above is the
72163
+ authoritative source.)
72164
+ 2. **Inbox** \u2014 when fn_read_messages is available, call it. Process any pending
72165
+ messages first; reply with reply_to_message_id when answering.
72166
+ 3. **Wake delta** \u2014 read the Wake Delta block above. The wake reason is the
72167
+ highest-priority change for this heartbeat. If you were woken by a comment
72168
+ or a message, acknowledge it before doing anything else.
72169
+ 4. **Ambient review** \u2014 since you have no assigned task, review board/project
72170
+ signals and recent memory context before acting.
72171
+ 5. **Pick the next concrete action** \u2014 exactly ONE useful action this heartbeat:
72172
+ create a focused task, delegate work, send/reply to a message, or append
72173
+ durable memory.
72174
+ 6. **Persist progress** \u2014 use available ambient tools only:
72175
+ fn_task_create, fn_delegate_task, fn_send_message, fn_memory_append.
72176
+ 7. **Exit** \u2014 call fn_heartbeat_done with a one-line summary of what changed
72177
+ this tick. If you took no action, say so and explain why.
72178
+
72179
+ Critical: a heartbeat without observable progress (a created task, delegation,
72180
+ message reply, memory append, or explicit "no-op with reason") is a bug. Do
72181
+ not loop on the same plan across heartbeats without recording why.`;
71857
72182
  heartbeatDoneParams = Type6.Object({
71858
72183
  summary: Type6.Optional(Type6.String({ description: "Summary of what was accomplished this heartbeat" }))
71859
72184
  });
@@ -71893,13 +72218,6 @@ a bug. Do not loop on the same plan across heartbeats without recording why.`;
71893
72218
  this.rootDir = options.rootDir;
71894
72219
  this.messageStore = options.messageStore;
71895
72220
  this.pluginRunner = options.pluginRunner;
71896
- this.onRecovered = options.onRecovered;
71897
- this.onTerminated = options.onTerminated;
71898
- this.onRunStarted = options.onRunStarted;
71899
- this.onRunCompleted = options.onRunCompleted;
71900
- this.taskStore = options.taskStore;
71901
- this.rootDir = options.rootDir;
71902
- this.messageStore = options.messageStore;
71903
72221
  }
71904
72222
  /**
71905
72223
  * Start the heartbeat monitoring loop.
@@ -72598,9 +72916,9 @@ a bug. Do not loop on the same plan across heartbeats without recording why.`;
72598
72916
  sourceType: "agent_heartbeat",
72599
72917
  sourceAgentId: agentId,
72600
72918
  sourceRunId: runContext?.runId
72601
- }));
72919
+ }, { rootDir: this.rootDir }));
72602
72920
  heartbeatTools.push(createListAgentsTool(this.store));
72603
- heartbeatTools.push(createDelegateTaskTool(this.store, taskStore));
72921
+ heartbeatTools.push(createDelegateTaskTool(this.store, taskStore, { rootDir: this.rootDir }));
72604
72922
  if (this.messageStore) {
72605
72923
  heartbeatTools.push(createSendMessageTool(this.messageStore, agentId));
72606
72924
  heartbeatTools.push(createReadMessagesTool(this.messageStore, agentId));
@@ -72623,22 +72941,27 @@ a bug. Do not loop on the same plan across heartbeats without recording why.`;
72623
72941
  heartbeatLog.warn(`Failed to configure heartbeat memory tools for ${agentId}: ${message}`);
72624
72942
  }
72625
72943
  const skillContext = buildSessionSkillContextSync2(agent, "heartbeat", rootDir);
72626
- let systemPrompt = isNoTaskRun ? HEARTBEAT_NO_TASK_SYSTEM_PROMPT : HEARTBEAT_SYSTEM_PROMPT;
72627
- const baseHeartbeatSystemPrompt = systemPrompt;
72944
+ const baseHeartbeatSystemPrompt = isNoTaskRun ? HEARTBEAT_NO_TASK_SYSTEM_PROMPT : HEARTBEAT_SYSTEM_PROMPT;
72628
72945
  let resolvedInstructionsForIdentity = "";
72629
72946
  try {
72630
- const agentInstructions = await resolveAgentInstructionsWithRatings(agent, rootDir, this.store);
72631
- resolvedInstructionsForIdentity = agentInstructions;
72632
- const memoryInstructions = memorySettings?.memoryEnabled === false ? "" : buildExecutionMemoryInstructions(rootDir, memorySettings);
72633
- systemPrompt = buildSystemPromptWithInstructions(
72634
- baseHeartbeatSystemPrompt,
72635
- [agentInstructions, memoryInstructions].filter((part) => part.trim()).join("\n\n")
72636
- );
72947
+ resolvedInstructionsForIdentity = await resolveAgentInstructionsWithRatings(agent, rootDir, this.store);
72637
72948
  } catch (instructionError) {
72638
- systemPrompt = baseHeartbeatSystemPrompt;
72639
72949
  const message = instructionError instanceof Error ? instructionError.message : String(instructionError);
72640
- heartbeatLog.warn(`Failed to enrich heartbeat system prompt for ${agentId}: ${message}`);
72950
+ heartbeatLog.warn(`Failed to resolve agent instructions for heartbeat ${agentId}: ${message}`);
72951
+ }
72952
+ let memoryInstructions = "";
72953
+ if (memorySettings?.memoryEnabled !== false) {
72954
+ try {
72955
+ memoryInstructions = buildExecutionMemoryInstructions(rootDir, memorySettings);
72956
+ } catch (memoryInstructionErr) {
72957
+ const message = memoryInstructionErr instanceof Error ? memoryInstructionErr.message : String(memoryInstructionErr);
72958
+ heartbeatLog.warn(`Failed to resolve project memory instructions for heartbeat ${agentId}: ${message}`);
72959
+ }
72641
72960
  }
72961
+ const systemPrompt = buildSystemPromptWithInstructions(
72962
+ baseHeartbeatSystemPrompt,
72963
+ [resolvedInstructionsForIdentity, memoryInstructions].filter((part) => part.trim()).join("\n\n")
72964
+ );
72642
72965
  heartbeatTools.push(createIdentityTool({ agent, resolvedInstructions: resolvedInstructionsForIdentity }));
72643
72966
  heartbeatTools.push(heartbeatDoneTool);
72644
72967
  if (isNoTaskRun) {
@@ -72699,7 +73022,7 @@ a bug. Do not loop on the same plan across heartbeats without recording why.`;
72699
73022
  };
72700
73023
  const wakeReason = deriveWakeReason();
72701
73024
  const customProcedure = await resolveAgentHeartbeatProcedure(agent, rootDir);
72702
- const heartbeatProcedureText = customProcedure ?? HEARTBEAT_PROCEDURE;
73025
+ const heartbeatProcedureText = customProcedure ?? (isNoTaskRun ? HEARTBEAT_NO_TASK_PROCEDURE : HEARTBEAT_PROCEDURE);
72703
73026
  if (isNoTaskRun) {
72704
73027
  if (this.messageStore) {
72705
73028
  try {
@@ -72732,6 +73055,8 @@ a bug. Do not loop on the same plan across heartbeats without recording why.`;
72732
73055
  `- pending messages: ${pendingMessages.length}`,
72733
73056
  "",
72734
73057
  "Treat this wake delta as the highest-priority change for this heartbeat.",
73058
+ "This is an autonomous heartbeat run (manual or automatic): re-anchor on",
73059
+ "identity, process wake context, then complete ONE concrete action.",
72735
73060
  "Run the Heartbeat Procedure (below) before doing anything else \u2014 even a",
72736
73061
  "timer-only wake should re-check messages, memory, and project state.",
72737
73062
  "",
@@ -72823,6 +73148,8 @@ a bug. Do not loop on the same plan across heartbeats without recording why.`;
72823
73148
  `- triggering comments: ${effectiveTriggeringCommentIds?.length ?? 0}`,
72824
73149
  "",
72825
73150
  "Treat this wake delta as the highest-priority change for this heartbeat.",
73151
+ "This is an autonomous heartbeat run (manual or automatic): re-anchor on",
73152
+ "identity, process wake context, then complete ONE concrete action.",
72826
73153
  "Before resuming prior task work, run the Heartbeat Procedure (below) and",
72827
73154
  "decide what action this delta requires. Your assigned task is one input",
72828
73155
  "to the procedure \u2014 not the only thing to consider.",
@@ -72982,7 +73309,7 @@ ${taskDetail.prompt}` : "No PROMPT.md available.",
72982
73309
  const baseCreateTool = createTaskCreateTool(taskStore, {
72983
73310
  sourceType: "agent_heartbeat",
72984
73311
  sourceAgentId: agentId
72985
- });
73312
+ }, { rootDir: this.rootDir });
72986
73313
  const trackedCreateTool = {
72987
73314
  ...baseCreateTool,
72988
73315
  execute: async (id, params, signal, onUpdate, ctx) => {
@@ -73009,7 +73336,7 @@ ${taskDetail.prompt}` : "No PROMPT.md available.",
73009
73336
  tools.push(createTaskDocumentWriteTool(taskStore, taskId));
73010
73337
  tools.push(createTaskDocumentReadTool(taskStore, taskId));
73011
73338
  tools.push(createListAgentsTool(this.store));
73012
- tools.push(createDelegateTaskTool(this.store, taskStore));
73339
+ tools.push(createDelegateTaskTool(this.store, taskStore, { rootDir: this.rootDir }));
73013
73340
  if (messageStore) {
73014
73341
  tools.push(createSendMessageTool(messageStore, agentId));
73015
73342
  tools.push(createReadMessagesTool(messageStore, agentId));
@@ -80711,7 +81038,7 @@ var init_provider_adapters = __esm({
80711
81038
 
80712
81039
  // ../engine/src/remote-access/tunnel-process-manager.ts
80713
81040
  import { EventEmitter as EventEmitter23 } from "node:events";
80714
- import { exec as exec9, execFile as execFile3, spawn as spawn4 } from "node:child_process";
81041
+ import { exec as exec9, execFile as execFile3, spawn as spawn5 } from "node:child_process";
80715
81042
  import { promisify as promisify9 } from "node:util";
80716
81043
  function nowIso() {
80717
81044
  return (/* @__PURE__ */ new Date()).toISOString();
@@ -80802,7 +81129,7 @@ var init_tunnel_process_manager = __esm({
80802
81129
  super();
80803
81130
  this.maxLogEntries = options.maxLogEntries ?? DEFAULT_MAX_LOG_ENTRIES;
80804
81131
  this.defaultStopTimeoutMs = options.stopTimeoutMs ?? DEFAULT_STOP_TIMEOUT_MS2;
80805
- this.spawnImpl = options.spawnImpl ?? spawn4;
81132
+ this.spawnImpl = options.spawnImpl ?? spawn5;
80806
81133
  }
80807
81134
  getStatus() {
80808
81135
  return { ...this.status, lastError: this.status.lastError ? { ...this.status.lastError } : null };
@@ -81176,6 +81503,7 @@ var execFileAsync2, MERGE_HANDOFF_GRACE_MS, isRemoteActive, ProjectEngine;
81176
81503
  var init_project_engine = __esm({
81177
81504
  "../engine/src/project-engine.ts"() {
81178
81505
  "use strict";
81506
+ init_src();
81179
81507
  init_in_process_runtime();
81180
81508
  init_pr_monitor();
81181
81509
  init_pr_comment_handler();
@@ -81870,6 +82198,48 @@ ${detail}`
81870
82198
  if (task.status === "failed") return false;
81871
82199
  return (task.mergeRetries ?? 0) < _ProjectEngine.MAX_AUTO_MERGE_RETRIES || this.hasAutoHealableVerificationBufferFailure(task) || this.isRetryCooldownElapsed(task);
81872
82200
  }
82201
+ /**
82202
+ * Remove and return the highest-priority taskId from the merge queue.
82203
+ * Ordering: priority (urgent→low), then createdAt ASC, then id ASC — matching
82204
+ * the triage and scheduler comparators. Manual merges (onMerge resolvers) are
82205
+ * preferred over auto-merges so awaited callers aren't starved by a flood of
82206
+ * higher-priority auto-enqueues. IDs whose tasks can't be loaded fall back to
82207
+ * FIFO order so they still drain.
82208
+ */
82209
+ async pickNextMergeTaskId(store) {
82210
+ if (this.mergeQueue.length === 0) return void 0;
82211
+ if (this.mergeQueue.length === 1) {
82212
+ return this.mergeQueue.shift();
82213
+ }
82214
+ const queueSnapshot = [...this.mergeQueue];
82215
+ const entries = [];
82216
+ for (let i = 0; i < queueSnapshot.length; i++) {
82217
+ const taskId = queueSnapshot[i];
82218
+ const task = await store.getTask(taskId).catch(() => void 0);
82219
+ entries.push({
82220
+ taskId,
82221
+ task,
82222
+ manual: this.manualMergeResolvers.has(taskId),
82223
+ order: i
82224
+ });
82225
+ }
82226
+ if (this.shuttingDown) return void 0;
82227
+ entries.sort((a, b) => {
82228
+ if (a.manual !== b.manual) return a.manual ? -1 : 1;
82229
+ if (a.task && b.task) return compareTasksByPriorityThenAgeAndId(a.task, b.task);
82230
+ if (a.task) return -1;
82231
+ if (b.task) return 1;
82232
+ return a.order - b.order;
82233
+ });
82234
+ for (const entry of entries) {
82235
+ const liveIndex = this.mergeQueue.indexOf(entry.taskId);
82236
+ if (liveIndex !== -1) {
82237
+ this.mergeQueue.splice(liveIndex, 1);
82238
+ return entry.taskId;
82239
+ }
82240
+ }
82241
+ return void 0;
82242
+ }
81873
82243
  internalEnqueueMerge(taskId) {
81874
82244
  if (this.shuttingDown) return;
81875
82245
  if (this.mergeActive.has(taskId)) return;
@@ -81877,6 +82247,23 @@ ${detail}`
81877
82247
  this.mergeQueue.push(taskId);
81878
82248
  void this.drainMergeQueue();
81879
82249
  }
82250
+ /**
82251
+ * Filter a sweep's listTasks() result to merge-eligible tasks, sort by
82252
+ * priority (urgent → low, then createdAt ASC, then id ASC), and enqueue.
82253
+ * Sorting before enqueue matters because each enqueue may immediately
82254
+ * trigger drainMergeQueue's single-item fast path, so the first task
82255
+ * pushed wins. listTasks returns createdAt ASC — without this sort an
82256
+ * older low-priority task would start before a later urgent one.
82257
+ */
82258
+ enqueueEligibleInReviewTasks(tasks) {
82259
+ const eligible = sortTasksByPriorityThenAgeAndId(
82260
+ tasks.filter((t) => !t.paused && this.canMergeTask(t))
82261
+ );
82262
+ for (const t of eligible) {
82263
+ this.internalEnqueueMerge(t.id);
82264
+ }
82265
+ return eligible.length;
82266
+ }
81880
82267
  async drainMergeQueue() {
81881
82268
  if (this.mergeRunning) return;
81882
82269
  this.mergeRunning = true;
@@ -81884,7 +82271,9 @@ ${detail}`
81884
82271
  const store = this.runtime.getTaskStore();
81885
82272
  const cwd = this.config.workingDirectory;
81886
82273
  while (this.mergeQueue.length > 0 && !this.shuttingDown) {
81887
- const taskId = this.mergeQueue.shift();
82274
+ const taskId = await this.pickNextMergeTaskId(store);
82275
+ if (!taskId) break;
82276
+ if (this.shuttingDown) break;
81888
82277
  const manualResolver = this.manualMergeResolvers.get(taskId);
81889
82278
  try {
81890
82279
  if (!manualResolver) {
@@ -82354,12 +82743,9 @@ ${detail}`
82354
82743
  }
82355
82744
  const settings = await store.getSettings();
82356
82745
  if (!settings.autoMerge) return;
82357
- const eligible = tasks.filter((t) => !t.paused && this.canMergeTask(t));
82358
- if (eligible.length > 0) {
82359
- runtimeLog.log(`Auto-merge startup sweep: enqueueing ${eligible.length} task(s)`);
82360
- for (const t of eligible) {
82361
- this.internalEnqueueMerge(t.id);
82362
- }
82746
+ const enqueued = this.enqueueEligibleInReviewTasks(tasks);
82747
+ if (enqueued > 0) {
82748
+ runtimeLog.log(`Auto-merge startup sweep: enqueueing ${enqueued} task(s)`);
82363
82749
  }
82364
82750
  } catch (err) {
82365
82751
  runtimeLog.warn(
@@ -82375,14 +82761,7 @@ ${detail}`
82375
82761
  const settings = await store.getSettings();
82376
82762
  if (!settings.globalPause && !settings.enginePaused && settings.autoMerge) {
82377
82763
  const tasks = await store.listTasks({ column: "in-review" });
82378
- for (const t of tasks) {
82379
- if (t.paused) {
82380
- continue;
82381
- }
82382
- if (this.canMergeTask(t)) {
82383
- this.internalEnqueueMerge(t.id);
82384
- }
82385
- }
82764
+ this.enqueueEligibleInReviewTasks(tasks);
82386
82765
  }
82387
82766
  } catch (err) {
82388
82767
  runtimeLog.warn(
@@ -82438,14 +82817,7 @@ ${detail}`
82438
82817
  if (s.autoMerge) {
82439
82818
  try {
82440
82819
  const tasks = await store.listTasks({ column: "in-review" });
82441
- for (const t of tasks) {
82442
- if (t.paused) {
82443
- continue;
82444
- }
82445
- if (this.canMergeTask(t)) {
82446
- this.internalEnqueueMerge(t.id);
82447
- }
82448
- }
82820
+ this.enqueueEligibleInReviewTasks(tasks);
82449
82821
  } catch (err) {
82450
82822
  runtimeLog.warn(
82451
82823
  `Global unpause: failed to scan in-review tasks for auto-merge: ${err instanceof Error ? err.message : String(err)}`
@@ -82475,14 +82847,7 @@ ${detail}`
82475
82847
  if (s.autoMerge) {
82476
82848
  try {
82477
82849
  const tasks = await store.listTasks({ column: "in-review" });
82478
- for (const t of tasks) {
82479
- if (t.paused) {
82480
- continue;
82481
- }
82482
- if (this.canMergeTask(t)) {
82483
- this.internalEnqueueMerge(t.id);
82484
- }
82485
- }
82850
+ this.enqueueEligibleInReviewTasks(tasks);
82486
82851
  } catch (err) {
82487
82852
  runtimeLog.warn(
82488
82853
  `Engine unpause: failed to scan in-review tasks for auto-merge: ${err instanceof Error ? err.message : String(err)}`
@@ -83075,7 +83440,7 @@ var init_peer_exchange_service = __esm({
83075
83440
  syncIntervalMs;
83076
83441
  interval = null;
83077
83442
  activeSync = null;
83078
- stopped = false;
83443
+ running = false;
83079
83444
  /** Whether settings sync is enabled. Default: false. */
83080
83445
  settingsSyncEnabled;
83081
83446
  /** Minimum interval between settings syncs with the same node in ms. Default: 5 minutes. */
@@ -83117,10 +83482,11 @@ var init_peer_exchange_service = __esm({
83117
83482
  * Begins periodic gossip with all online remote nodes.
83118
83483
  */
83119
83484
  start() {
83120
- if (this.stopped) {
83121
- peerExchangeLog.warn("Cannot start - service has been stopped");
83485
+ if (this.running) {
83486
+ peerExchangeLog.log("Peer exchange service already running");
83122
83487
  return;
83123
83488
  }
83489
+ this.running = true;
83124
83490
  this.centralCore.listNodes().then((nodes) => {
83125
83491
  const onlineRemoteCount = nodes.filter(
83126
83492
  (n) => n.type === "remote" && n.status === "online" && n.url
@@ -83130,6 +83496,7 @@ var init_peer_exchange_service = __esm({
83130
83496
  peerExchangeLog.warn(`Failed to get initial peer count: ${err}`);
83131
83497
  });
83132
83498
  this.interval = setInterval(() => {
83499
+ if (!this.running) return;
83133
83500
  void this.syncWithAllPeers();
83134
83501
  }, this.syncIntervalMs);
83135
83502
  }
@@ -83137,12 +83504,21 @@ var init_peer_exchange_service = __esm({
83137
83504
  * Stop the peer exchange service.
83138
83505
  * Clears the sync interval and prevents further syncs.
83139
83506
  */
83140
- stop() {
83507
+ async stop() {
83508
+ if (!this.running) {
83509
+ return;
83510
+ }
83511
+ this.running = false;
83141
83512
  if (this.interval) {
83142
83513
  clearInterval(this.interval);
83143
83514
  this.interval = null;
83144
83515
  }
83145
- this.stopped = true;
83516
+ if (this.activeSync) {
83517
+ try {
83518
+ await this.activeSync;
83519
+ } catch {
83520
+ }
83521
+ }
83146
83522
  peerExchangeLog.log("Stopped peer exchange service");
83147
83523
  }
83148
83524
  /**
@@ -83696,25 +84072,21 @@ async function ensureNtfyHelpersReady() {
83696
84072
  if (planningNtfyHelpers) {
83697
84073
  return;
83698
84074
  }
83699
- try {
83700
- const engine = await Promise.resolve().then(() => (init_src2(), src_exports2));
83701
- const hasNotificationService = "NotificationService" in engine && typeof engine.NotificationService === "function";
83702
- const hasAllHelpers = "isNtfyEventEnabled" in engine && "buildNtfyClickUrl" in engine && "sendNtfyNotification" in engine && typeof engine.isNtfyEventEnabled === "function" && typeof engine.buildNtfyClickUrl === "function" && typeof engine.sendNtfyNotification === "function";
83703
- if (!hasAllHelpers) {
83704
- return;
83705
- }
83706
- planningNtfyHelpers = {
83707
- isNtfyEventEnabled: engine.isNtfyEventEnabled,
83708
- buildNtfyClickUrl: engine.buildNtfyClickUrl,
83709
- sendNtfyNotification: engine.sendNtfyNotification
83710
- };
83711
- if (hasNotificationService) {
83712
- diagnostics.info(
83713
- "NotificationService abstraction detected in engine",
83714
- { operation: "notification-service-detection" }
83715
- );
83716
- }
83717
- } catch {
84075
+ const hasNotificationService = "NotificationService" in src_exports2 && typeof NotificationService === "function";
84076
+ const hasAllHelpers = "isNtfyEventEnabled" in src_exports2 && "buildNtfyClickUrl" in src_exports2 && "sendNtfyNotification" in src_exports2 && typeof isNtfyEventEnabled === "function" && typeof buildNtfyClickUrl === "function" && typeof sendNtfyNotification === "function";
84077
+ if (!hasAllHelpers) {
84078
+ return;
84079
+ }
84080
+ planningNtfyHelpers = {
84081
+ isNtfyEventEnabled,
84082
+ buildNtfyClickUrl,
84083
+ sendNtfyNotification
84084
+ };
84085
+ if (hasNotificationService) {
84086
+ diagnostics.info(
84087
+ "NotificationService abstraction detected in engine",
84088
+ { operation: "notification-service-detection" }
84089
+ );
83718
84090
  }
83719
84091
  }
83720
84092
  function safeParseJson(text, fallback, options) {
@@ -84504,6 +84876,7 @@ var init_planning = __esm({
84504
84876
  init_sse_buffer();
84505
84877
  init_ai_session_diagnostics();
84506
84878
  init_src2();
84879
+ init_src2();
84507
84880
  createFnAgent4 = createFnAgent2;
84508
84881
  diagnostics = createSessionDiagnostics("planning");
84509
84882
  PLANNING_SYSTEM_PROMPT = `You are a planning assistant for the fn task board system.
@@ -84565,7 +84938,7 @@ For completion:
84565
84938
  }`;
84566
84939
  SESSION_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
84567
84940
  CLEANUP_INTERVAL_MS2 = 5 * 60 * 1e3;
84568
- MAX_SESSIONS_PER_IP_PER_HOUR = 5;
84941
+ MAX_SESSIONS_PER_IP_PER_HOUR = 1e3;
84569
84942
  RATE_LIMIT_WINDOW_MS2 = 60 * 60 * 1e3;
84570
84943
  GENERATION_TIMEOUT_MS = 12e4;
84571
84944
  sessions = /* @__PURE__ */ new Map();
@@ -85188,7 +85561,7 @@ var init_src3 = __esm({
85188
85561
  });
85189
85562
 
85190
85563
  // ../../plugins/fusion-plugin-hermes-runtime/dist/cli-spawn.js
85191
- import { spawn as spawn5, spawnSync } from "node:child_process";
85564
+ import { spawn as spawn6, spawnSync } from "node:child_process";
85192
85565
  import os2 from "node:os";
85193
85566
  import path, { sep as PATH_SEP } from "node:path";
85194
85567
  function resolveBinaryForSpawn(binary) {
@@ -85298,7 +85671,7 @@ async function invokeHermesCli(prompt, settings, resumeSessionId, signal) {
85298
85671
  if (settings.profile) {
85299
85672
  spawnEnv.HERMES_HOME = hermesProfileHome(settings.profile);
85300
85673
  }
85301
- const child = spawn5(binary, args, {
85674
+ const child = spawn6(binary, args, {
85302
85675
  stdio: ["ignore", "pipe", "pipe"],
85303
85676
  env: spawnEnv
85304
85677
  });
@@ -85511,7 +85884,7 @@ var init_dist = __esm({
85511
85884
  });
85512
85885
 
85513
85886
  // ../../plugins/fusion-plugin-openclaw-runtime/dist/pi-module.js
85514
- import { spawn as spawn6 } from "node:child_process";
85887
+ import { spawn as spawn7 } from "node:child_process";
85515
85888
  import { randomUUID as randomUUID12 } from "node:crypto";
85516
85889
  function asString(v) {
85517
85890
  return typeof v === "string" && v.trim() !== "" ? v.trim() : void 0;
@@ -85588,7 +85961,7 @@ async function promptCli(session, message, config, callbacks, signal) {
85588
85961
  cb.onToolStart?.("openclaw.agent", { sessionId: session.sessionId });
85589
85962
  return new Promise((resolve19, reject) => {
85590
85963
  let settled = false;
85591
- const child = spawn6(config.binaryPath, args, {
85964
+ const child = spawn7(config.binaryPath, args, {
85592
85965
  stdio: ["ignore", "pipe", "pipe"]
85593
85966
  });
85594
85967
  const hardKill = setTimeout(() => {
@@ -85739,7 +86112,7 @@ var init_runtime_adapter2 = __esm({
85739
86112
  });
85740
86113
 
85741
86114
  // ../../plugins/fusion-plugin-openclaw-runtime/dist/probe.js
85742
- import { spawn as spawn7 } from "node:child_process";
86115
+ import { spawn as spawn8 } from "node:child_process";
85743
86116
  async function probeOpenClawBinary(opts = {}) {
85744
86117
  const startedAt = Date.now();
85745
86118
  const binary = opts.binaryPath ?? "openclaw";
@@ -85750,7 +86123,7 @@ async function probeOpenClawBinary(opts = {}) {
85750
86123
  resolvePromise({ ...partial, probeDurationMs: Date.now() - startedAt });
85751
86124
  };
85752
86125
  let settled = false;
85753
- const child = spawn7(resolvedPath ?? binary, ["--version"], {
86126
+ const child = spawn8(resolvedPath ?? binary, ["--version"], {
85754
86127
  stdio: ["ignore", "pipe", "pipe"]
85755
86128
  });
85756
86129
  const timer = setTimeout(() => {
@@ -85811,7 +86184,7 @@ async function probeOpenClawBinary(opts = {}) {
85811
86184
  async function tryResolveBinaryPath(binary) {
85812
86185
  return new Promise((resolvePromise) => {
85813
86186
  const which = process.platform === "win32" ? "where" : "which";
85814
- const child = spawn7(which, [binary], { stdio: ["ignore", "pipe", "ignore"] });
86187
+ const child = spawn8(which, [binary], { stdio: ["ignore", "pipe", "ignore"] });
85815
86188
  let out = "";
85816
86189
  child.stdout?.on("data", (chunk) => {
85817
86190
  out += chunk.toString("utf-8");
@@ -92400,7 +92773,7 @@ var init_register_git_github = __esm({
92400
92773
  });
92401
92774
 
92402
92775
  // ../dashboard/src/terminal.ts
92403
- import { spawn as spawn8 } from "node:child_process";
92776
+ import { spawn as spawn9 } from "node:child_process";
92404
92777
  import { randomUUID as randomUUID13 } from "node:crypto";
92405
92778
  import { EventEmitter as EventEmitter29 } from "node:events";
92406
92779
  function extractBaseCommand(command) {
@@ -92562,7 +92935,7 @@ var init_terminal = __esm({
92562
92935
  return { sessionId: "", error: validation.error };
92563
92936
  }
92564
92937
  const sessionId = randomUUID13();
92565
- const childProcess = spawn8(command, [], {
92938
+ const childProcess = spawn9(command, [], {
92566
92939
  cwd,
92567
92940
  shell: true,
92568
92941
  stdio: ["pipe", "pipe", "pipe"],
@@ -92836,6 +93209,7 @@ var init_register_agent_core_routes = __esm({
92836
93209
  "use strict";
92837
93210
  init_src();
92838
93211
  init_api_error();
93212
+ init_src2();
92839
93213
  }
92840
93214
  });
92841
93215
 
@@ -92848,10 +93222,13 @@ var init_register_agent_runtime_routes = __esm({
92848
93222
  });
92849
93223
 
92850
93224
  // ../dashboard/src/routes/register-agent-reflection-rating-routes.ts
93225
+ var AgentReflectionServiceBinding;
92851
93226
  var init_register_agent_reflection_rating_routes = __esm({
92852
93227
  "../dashboard/src/routes/register-agent-reflection-rating-routes.ts"() {
92853
93228
  "use strict";
92854
93229
  init_api_error();
93230
+ init_src2();
93231
+ AgentReflectionServiceBinding = "AgentReflectionService" in src_exports2 && typeof AgentReflectionService === "function" ? AgentReflectionService : void 0;
92855
93232
  }
92856
93233
  });
92857
93234
 
@@ -92994,12 +93371,20 @@ var init_claude_cli_probe = __esm({
92994
93371
  }
92995
93372
  });
92996
93373
 
93374
+ // ../dashboard/src/droid-cli-probe.ts
93375
+ var init_droid_cli_probe = __esm({
93376
+ "../dashboard/src/droid-cli-probe.ts"() {
93377
+ "use strict";
93378
+ }
93379
+ });
93380
+
92997
93381
  // ../dashboard/src/routes/register-auth-routes.ts
92998
93382
  var init_register_auth_routes = __esm({
92999
93383
  "../dashboard/src/routes/register-auth-routes.ts"() {
93000
93384
  "use strict";
93001
93385
  init_src();
93002
93386
  init_claude_cli_probe();
93387
+ init_droid_cli_probe();
93003
93388
  init_api_error();
93004
93389
  init_usage();
93005
93390
  init_project_store_resolver();
@@ -93274,7 +93659,7 @@ function remapSpawnError(err, bin) {
93274
93659
  return err instanceof Error ? err : new Error(String(err));
93275
93660
  }
93276
93661
  async function spawnPaperclipCliJson(args, opts) {
93277
- const { spawn: spawn11 } = await import("node:child_process");
93662
+ const { spawn: spawn12 } = await import("node:child_process");
93278
93663
  const bin = opts.cliBinaryPath ?? "paperclipai";
93279
93664
  const fullArgs = [...args, "--json"];
93280
93665
  if (opts.cliConfigPath) {
@@ -93285,7 +93670,7 @@ async function spawnPaperclipCliJson(args, opts) {
93285
93670
  return new Promise((resolve19, reject) => {
93286
93671
  let child;
93287
93672
  try {
93288
- child = spawn11(bin, fullArgs, { stdio: ["ignore", "pipe", "pipe"] });
93673
+ child = spawn12(bin, fullArgs, { stdio: ["ignore", "pipe", "pipe"] });
93289
93674
  } catch (err) {
93290
93675
  reject(remapSpawnError(err, bin));
93291
93676
  return;
@@ -93797,6 +94182,25 @@ var init_register_runtime_provider_routes = __esm({
93797
94182
  }
93798
94183
  });
93799
94184
 
94185
+ // ../dashboard/src/cli-package-version.ts
94186
+ var init_cli_package_version = __esm({
94187
+ "../dashboard/src/cli-package-version.ts"() {
94188
+ "use strict";
94189
+ }
94190
+ });
94191
+
94192
+ // ../dashboard/src/routes/register-fn-binary-routes.ts
94193
+ var MAX_OUTPUT_BYTES2;
94194
+ var init_register_fn_binary_routes = __esm({
94195
+ "../dashboard/src/routes/register-fn-binary-routes.ts"() {
94196
+ "use strict";
94197
+ init_src();
94198
+ init_api_error();
94199
+ init_cli_package_version();
94200
+ MAX_OUTPUT_BYTES2 = 64 * 1024;
94201
+ }
94202
+ });
94203
+
93800
94204
  // ../dashboard/src/update-check.ts
93801
94205
  var DAY_MS;
93802
94206
  var init_update_check = __esm({
@@ -93806,13 +94210,6 @@ var init_update_check = __esm({
93806
94210
  }
93807
94211
  });
93808
94212
 
93809
- // ../dashboard/src/cli-package-version.ts
93810
- var init_cli_package_version = __esm({
93811
- "../dashboard/src/cli-package-version.ts"() {
93812
- "use strict";
93813
- }
93814
- });
93815
-
93816
94213
  // ../dashboard/src/routes/register-update-check-routes.ts
93817
94214
  var init_register_update_check_routes = __esm({
93818
94215
  "../dashboard/src/routes/register-update-check-routes.ts"() {
@@ -93860,6 +94257,7 @@ var init_insights_routes = __esm({
93860
94257
  "../dashboard/src/insights-routes.ts"() {
93861
94258
  "use strict";
93862
94259
  init_api_error();
94260
+ init_src2();
93863
94261
  }
93864
94262
  });
93865
94263
 
@@ -93997,6 +94395,7 @@ var init_routes = __esm({
93997
94395
  init_register_usage_routes();
93998
94396
  init_register_auth_routes();
93999
94397
  init_register_runtime_provider_routes();
94398
+ init_register_fn_binary_routes();
94000
94399
  init_register_update_check_routes();
94001
94400
  init_register_integrated_routers();
94002
94401
  init_resolve_diff_base();
@@ -97725,6 +98124,7 @@ var init_terminal_websocket_diagnostics = __esm({
97725
98124
 
97726
98125
  // ../dashboard/src/chat.ts
97727
98126
  import { EventEmitter as EventEmitter30 } from "node:events";
98127
+ import { SessionManager as SessionManager3 } from "@mariozechner/pi-coding-agent";
97728
98128
  var defaultDiagnostics, _diagnostics, diagnostics7, RATE_LIMIT_WINDOW_MS6, MAX_REFERENCED_FILE_SIZE, ChatStreamManager, chatStreamManager;
97729
98129
  var init_chat = __esm({
97730
98130
  "../dashboard/src/chat.ts"() {
@@ -97732,6 +98132,7 @@ var init_chat = __esm({
97732
98132
  init_src();
97733
98133
  init_sse_buffer();
97734
98134
  init_src2();
98135
+ init_src2();
97735
98136
  defaultDiagnostics = {
97736
98137
  log(message, ...args) {
97737
98138
  console.log(`[chat] ${message}`, ...args);
@@ -99322,7 +99723,7 @@ async function runTaskPlan(initialPlanArg, yesFlag = false, projectName) {
99322
99723
  } catch (err) {
99323
99724
  clearThinking();
99324
99725
  if (err instanceof RateLimitError2) {
99325
- console.error("\n Rate limit exceeded. Maximum 5 planning sessions per hour.\n");
99726
+ console.error("\n Rate limit exceeded. Maximum 1000 planning sessions per hour.\n");
99326
99727
  process.exit(1);
99327
99728
  }
99328
99729
  console.error(`
@@ -99479,7 +99880,7 @@ __export(skills_exports, {
99479
99880
  runSkillsSearch: () => runSkillsSearch,
99480
99881
  searchSkills: () => searchSkills
99481
99882
  });
99482
- import { spawn as spawn9 } from "node:child_process";
99883
+ import { spawn as spawn10 } from "node:child_process";
99483
99884
  async function searchSkills(query, limit = 10) {
99484
99885
  const url = `${SKILLS_API_BASE}/api/search?q=${encodeURIComponent(query)}&limit=${limit}`;
99485
99886
  try {
@@ -99557,7 +99958,7 @@ async function runSkillsInstall(args, options) {
99557
99958
  npxArgs.push("--skill", options.skill);
99558
99959
  }
99559
99960
  npxArgs.push("-y", "-a", "pi");
99560
- const child = spawn9("npx", npxArgs, {
99961
+ const child = spawn10("npx", npxArgs, {
99561
99962
  cwd: process.cwd(),
99562
99963
  stdio: "inherit",
99563
99964
  shell: true
@@ -99594,7 +99995,7 @@ import { StringEnum } from "@mariozechner/pi-ai";
99594
99995
  import { resolve as resolve18, basename as basename11, extname as extname3, join as join41 } from "node:path";
99595
99996
  import { readFile as readFile18 } from "node:fs/promises";
99596
99997
  import { existsSync as existsSync31 } from "node:fs";
99597
- import { spawn as spawn10 } from "node:child_process";
99998
+ import { spawn as spawn11 } from "node:child_process";
99598
99999
  var MIME_TYPES2 = {
99599
100000
  ".png": "image/png",
99600
100001
  ".jpg": "image/jpeg",
@@ -101403,7 +101804,7 @@ Status: ${updated.status}`
101403
101804
  npxArgs.push("--skill", params.skill);
101404
101805
  }
101405
101806
  npxArgs.push("-y", "-a", "pi");
101406
- const child = spawn10("npx", npxArgs, {
101807
+ const child = spawn11("npx", npxArgs, {
101407
101808
  cwd: resolveProjectRoot(ctx.cwd),
101408
101809
  stdio: "pipe",
101409
101810
  shell: true
@@ -101485,7 +101886,7 @@ Status: ${updated.status}`
101485
101886
  return;
101486
101887
  }
101487
101888
  const port = trimmed ? parseInt(trimmed, 10) || 4040 : 4040;
101488
- const child = spawn10("fn", ["dashboard", "--port", String(port)], {
101889
+ const child = spawn11("fn", ["dashboard", "--port", String(port)], {
101489
101890
  cwd: resolveProjectRoot(ctx.cwd),
101490
101891
  stdio: ["ignore", "pipe", "pipe"],
101491
101892
  detached: false,