@runfusion/fusion 0.75.1-beta.2 → 0.76.0-beta.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 (158) hide show
  1. package/dist/bin.js +2883 -1203
  2. package/dist/child-process-worker.js +2345 -1000
  3. package/dist/client/.vite/manifest.json +260 -260
  4. package/dist/client/assets/AgentDetailView-Czb9wfC3.css +1 -0
  5. package/dist/client/assets/AgentDetailView-nsriz_Mw.js +13 -0
  6. package/dist/client/assets/{AgentPermissionPolicyEditor-CC_rrbbG.js → AgentPermissionPolicyEditor-TKuT5ryJ.js} +1 -1
  7. package/dist/client/assets/AgentsView-BBLq03lO.js +24 -0
  8. package/dist/client/assets/{ChatView-mFKfoegS.js → ChatView-BEc5SVfU.js} +1 -1
  9. package/dist/client/assets/{CommandCenter-C2wvDq_N.js → CommandCenter-DBCxo_tI.js} +1 -1
  10. package/dist/client/assets/{DevServerView-0MWEJM7o.js → DevServerView-BaLUl9De.js} +1 -1
  11. package/dist/client/assets/{DirectoryPicker-BjMPhCdh.js → DirectoryPicker-CKj8CfUJ.js} +1 -1
  12. package/dist/client/assets/{DocumentsView-ckH3rjmd.js → DocumentsView-Cu8vlXJT.js} +1 -1
  13. package/dist/client/assets/{EvalsView-NyA644VF.js → EvalsView-CbdRWRjR.js} +1 -1
  14. package/dist/client/assets/{ExperimentalAgentOnboardingModal-D0ZfbBRN.js → ExperimentalAgentOnboardingModal-BVdW0xJ3.js} +1 -1
  15. package/dist/client/assets/{GoalsView-Ctt5P_yo.js → GoalsView-CCbeAvBm.js} +1 -1
  16. package/dist/client/assets/{InsightsView-NDp0bdNB.js → InsightsView-B46ZemSm.js} +1 -1
  17. package/dist/client/assets/{MemoryView-BKbA4Bm4.js → MemoryView-JM1fFU2D.js} +1 -1
  18. package/dist/client/assets/{PiExtensionsManager-V3AFWMHm.js → PiExtensionsManager-KHsw2af9.js} +1 -1
  19. package/dist/client/assets/{PluginManager-D88G_TZh.js → PluginManager-BeKLun0C.js} +1 -1
  20. package/dist/client/assets/{PullRequestView-DQQmFFYC.js → PullRequestView-CwQW2pe4.js} +1 -1
  21. package/dist/client/assets/{ReportModal-Clh1S8RF.js → ReportModal-BC9DhvX4.js} +1 -1
  22. package/dist/client/assets/{ResearchView-kxvq4zp5.js → ResearchView-CuB7IbEB.js} +1 -1
  23. package/dist/client/assets/{SecretsView-Dm0Vk8SA.js → SecretsView-DD8uA6G7.js} +1 -1
  24. package/dist/client/assets/{SessionTerminal-DJO4JTEX.js → SessionTerminal-B0S1e8qf.js} +1 -1
  25. package/dist/client/assets/SettingsModal-BHNEJWD4.js +21 -0
  26. package/dist/client/assets/{SettingsModal-BN5mL_ym.js → SettingsModal-BxrdR4-f.js} +1 -1
  27. package/dist/client/assets/{SettingsTextareaRow-CvKszqO-.js → SettingsTextareaRow-CpJOy-rv.js} +1 -1
  28. package/dist/client/assets/{SetupWizardModal-D5hVZh1G.js → SetupWizardModal-DviAPDlf.js} +2 -2
  29. package/dist/client/assets/{SkillsView-DQVz21w2.js → SkillsView-DeoEN_K0.js} +1 -1
  30. package/dist/client/assets/{WorkflowNodeEditor-CpKIi6Tb.js → WorkflowNodeEditor-CPk8plo1.js} +3 -3
  31. package/dist/client/assets/{agent-import-generation-DGUNF3nd.js → agent-import-generation-D74gC46R.js} +1 -1
  32. package/dist/client/assets/app-64JrFN7_.js +13 -0
  33. package/dist/client/assets/app-BxMYvSHk.js +13 -0
  34. package/dist/client/assets/app-CCgIvcFy.js +5 -0
  35. package/dist/client/assets/app-CVFYXkbM.js +13 -0
  36. package/dist/client/assets/app-Cl4NLAGd.js +13 -0
  37. package/dist/client/assets/app-_gHuSFBp.js +13 -0
  38. package/dist/client/assets/{architectureDiagram-3BPJPVTR-CV83wubP.js → architectureDiagram-3BPJPVTR-DUNXxw_k.js} +1 -1
  39. package/dist/client/assets/{blockDiagram-GPEHLZMM-CNQsA3dD.js → blockDiagram-GPEHLZMM-CTxWMFSZ.js} +1 -1
  40. package/dist/client/assets/{c4Diagram-AAUBKEIU-CXe7ii5H.js → c4Diagram-AAUBKEIU-DI15zO_G.js} +1 -1
  41. package/dist/client/assets/channel-DDHw4Vcu.js +1 -0
  42. package/dist/client/assets/{chunk-2J33WTMH-BS6W9bWv.js → chunk-2J33WTMH-D1X3sNvd.js} +1 -1
  43. package/dist/client/assets/{chunk-4BX2VUAB-DbehRhdW.js → chunk-4BX2VUAB-DYz990rf.js} +1 -1
  44. package/dist/client/assets/{chunk-55IACEB6-DMI5SR42.js → chunk-55IACEB6-BQshFTjv.js} +1 -1
  45. package/dist/client/assets/{chunk-727SXJPM-D1d-GBVj.js → chunk-727SXJPM-DiVH9Rd6.js} +1 -1
  46. package/dist/client/assets/{chunk-AQP2D5EJ-BzO6K3Uf.js → chunk-AQP2D5EJ-Bx8LBEvE.js} +1 -1
  47. package/dist/client/assets/{chunk-FMBD7UC4-BVRLV3lK.js → chunk-FMBD7UC4-7rsNdXy-.js} +1 -1
  48. package/dist/client/assets/{chunk-ND2GUHAM-DdWByQfn.js → chunk-ND2GUHAM-COOZ3ff5.js} +1 -1
  49. package/dist/client/assets/{chunk-QZHKN3VN-1ia12cRV.js → chunk-QZHKN3VN-oL-CU1gN.js} +1 -1
  50. package/dist/client/assets/classDiagram-4FO5ZUOK-C5tnFtxH.js +1 -0
  51. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-C5tnFtxH.js +1 -0
  52. package/dist/client/assets/{cose-bilkent-S5V4N54A-BPDT41HR.js → cose-bilkent-S5V4N54A-CHXhlVh-.js} +1 -1
  53. package/dist/client/assets/{dagre-BM42HDAG-Cktn26EH.js → dagre-BM42HDAG-BJVXidIK.js} +1 -1
  54. package/dist/client/assets/{dashboard-view-zd8GxC49.js → dashboard-view-B8OjndkD.js} +1 -1
  55. package/dist/client/assets/{dashboard-view-8o0EUcRX.js → dashboard-view-BOqEOmsR.js} +1 -1
  56. package/dist/client/assets/{dashboard-view-CkCerlVJ.js → dashboard-view-CCg2wkwd.js} +1 -1
  57. package/dist/client/assets/{dashboard-view-D_HC4lnr.js → dashboard-view-DOWuByTe.js} +1 -1
  58. package/dist/client/assets/{diagram-2AECGRRQ-But4Lvf-.js → diagram-2AECGRRQ-CBS8IBP_.js} +1 -1
  59. package/dist/client/assets/{diagram-5GNKFQAL-2bpQAGFc.js → diagram-5GNKFQAL-Dmyxol5P.js} +1 -1
  60. package/dist/client/assets/{diagram-KO2AKTUF-Ckn9Qd1u.js → diagram-KO2AKTUF-DVF_5fDu.js} +1 -1
  61. package/dist/client/assets/{diagram-LMA3HP47-Bjn8h84N.js → diagram-LMA3HP47-DqbOTb-t.js} +1 -1
  62. package/dist/client/assets/{diagram-OG6HWLK6-BI6541hx.js → diagram-OG6HWLK6-BIhwB3ji.js} +1 -1
  63. package/dist/client/assets/{erDiagram-TEJ5UH35-BqMiwHiM.js → erDiagram-TEJ5UH35-BcYtj6RH.js} +1 -1
  64. package/dist/client/assets/{flowDiagram-I6XJVG4X-Q_SAB10M.js → flowDiagram-I6XJVG4X-CBYrhc7v.js} +1 -1
  65. package/dist/client/assets/{folder-open-CHiAJbTN.js → folder-open-DBW3UhL6.js} +1 -1
  66. package/dist/client/assets/{ganttDiagram-6RSMTGT7-CqWFSfPF.js → ganttDiagram-6RSMTGT7-BtFaOg92.js} +1 -1
  67. package/dist/client/assets/{gitGraphDiagram-PVQCEYII-B1o4uEjO.js → gitGraphDiagram-PVQCEYII-BE5E5ctQ.js} +1 -1
  68. package/dist/client/assets/{index-kwRywd9M.js → index-D6J9uyUx.js} +306 -306
  69. package/dist/client/assets/index-DDdZ20Cz.css +1 -0
  70. package/dist/client/assets/{infoDiagram-5YYISTIA-BzaMa0c2.js → infoDiagram-5YYISTIA-BBpwj0Ir.js} +1 -1
  71. package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-I38yge00.js → ishikawaDiagram-YF4QCWOH-CwjmjYav.js} +1 -1
  72. package/dist/client/assets/{journeyDiagram-JHISSGLW-WVQSgx4P.js → journeyDiagram-JHISSGLW-ChnMZl1o.js} +1 -1
  73. package/dist/client/assets/{kanban-definition-UN3LZRKU-BWtPQ3AH.js → kanban-definition-UN3LZRKU-BA_bNZa3.js} +1 -1
  74. package/dist/client/assets/{mermaid.core-D1hnBtbi.js → mermaid.core-CNMqiN_t.js} +4 -4
  75. package/dist/client/assets/{mindmap-definition-RKZ34NQL-DqiSLkW7.js → mindmap-definition-RKZ34NQL-D-8_lEGp.js} +1 -1
  76. package/dist/client/assets/{pieDiagram-4H26LBE5-CcgwbdIN.js → pieDiagram-4H26LBE5-Cz_dYW8h.js} +1 -1
  77. package/dist/client/assets/{puzzle-BhXUaTQl.js → puzzle-Bbsv6mZh.js} +1 -1
  78. package/dist/client/assets/{quadrantDiagram-W4KKPZXB-MsqQQfDN.js → quadrantDiagram-W4KKPZXB-BtXzvuUT.js} +1 -1
  79. package/dist/client/assets/{requirementDiagram-4Y6WPE33-CILGjMMA.js → requirementDiagram-4Y6WPE33-CK29bQbQ.js} +1 -1
  80. package/dist/client/assets/{sankeyDiagram-5OEKKPKP-c14fWbtR.js → sankeyDiagram-5OEKKPKP-DGxLW8W6.js} +1 -1
  81. package/dist/client/assets/{sequenceDiagram-3UESZ5HK-BD5bxMom.js → sequenceDiagram-3UESZ5HK-CPOz7wfR.js} +1 -1
  82. package/dist/client/assets/{shield-alert-C9W2WywS.js → shield-alert-BnpS5qUu.js} +1 -1
  83. package/dist/client/assets/{standing-instructions-template-CJdbDSyg.js → standing-instructions-template-jeWwq3Zh.js} +1 -1
  84. package/dist/client/assets/{stateDiagram-AJRCARHV-CZ5YqeOn.js → stateDiagram-AJRCARHV-CxpQ3AE6.js} +1 -1
  85. package/dist/client/assets/{stateDiagram-v2-BHNVJYJU-mdMN-PNX.js → stateDiagram-v2-BHNVJYJU-BSImQHUw.js} +1 -1
  86. package/dist/client/assets/{timeline-definition-PNZ67QCA-DHKay9uj.js → timeline-definition-PNZ67QCA-jt74--XQ.js} +1 -1
  87. package/dist/client/assets/{upload-DxEwowRU.js → upload-D2j8N8vC.js} +1 -1
  88. package/dist/client/assets/{users-mNFgW_Zd.js → users-BtzATmoI.js} +1 -1
  89. package/dist/client/assets/{vennDiagram-CIIHVFJN-X29K9hWM.js → vennDiagram-CIIHVFJN-3vOGHb8-.js} +1 -1
  90. package/dist/client/assets/{wardley-L42UT6IY-yEoTrfmN.js → wardley-L42UT6IY-DNuWuyr8.js} +1 -1
  91. package/dist/client/assets/{wardleyDiagram-YWT4CUSO-ny4k3kG0.js → wardleyDiagram-YWT4CUSO-D-kFe8Uz.js} +1 -1
  92. package/dist/client/assets/{xychartDiagram-2RQKCTM6-DC8ODtRJ.js → xychartDiagram-2RQKCTM6-DvDQ30sY.js} +1 -1
  93. package/dist/client/index.html +2 -2
  94. package/dist/client/version.json +1 -1
  95. package/dist/droid-cli/package.json +1 -1
  96. package/dist/extension.js +2702 -1117
  97. package/dist/migrations/0045_fn_8764_multi_role_workflow_agents.sql +20 -0
  98. package/dist/migrations/0046_fn_8764_workflow_principal_fence.sql +49 -0
  99. package/dist/migrations/0047_fn_8829_task_recommendations.sql +3 -0
  100. package/dist/pi-claude-cli/package.json +1 -1
  101. package/dist/plugin-sdk/index.d.ts +43 -0
  102. package/dist/plugin-sdk/index.js +357 -25
  103. package/dist/plugins/fusion-plugin-acp-runtime/bundled.js +357 -25
  104. package/dist/plugins/fusion-plugin-acp-runtime/package.json +1 -1
  105. package/dist/plugins/fusion-plugin-claude-runtime/bundled.js +357 -25
  106. package/dist/plugins/fusion-plugin-claude-runtime/package.json +1 -1
  107. package/dist/plugins/fusion-plugin-cli-printing-press/bundled.js +357 -25
  108. package/dist/plugins/fusion-plugin-cli-printing-press/package.json +1 -1
  109. package/dist/plugins/fusion-plugin-compound-engineering/bundled.js +357 -25
  110. package/dist/plugins/fusion-plugin-compound-engineering/package.json +1 -1
  111. package/dist/plugins/fusion-plugin-cursor-runtime/bundled.js +357 -25
  112. package/dist/plugins/fusion-plugin-cursor-runtime/package.json +1 -1
  113. package/dist/plugins/fusion-plugin-dependency-graph/bundled.js +357 -25
  114. package/dist/plugins/fusion-plugin-dependency-graph/package.json +1 -1
  115. package/dist/plugins/fusion-plugin-droid-runtime/bundled.js +357 -25
  116. package/dist/plugins/fusion-plugin-droid-runtime/package.json +1 -1
  117. package/dist/plugins/fusion-plugin-grok-runtime/bundled.js +357 -25
  118. package/dist/plugins/fusion-plugin-grok-runtime/package.json +1 -1
  119. package/dist/plugins/fusion-plugin-hermes-runtime/bundled.js +357 -25
  120. package/dist/plugins/fusion-plugin-hermes-runtime/package.json +1 -1
  121. package/dist/plugins/fusion-plugin-linear-import/bundled.js +357 -25
  122. package/dist/plugins/fusion-plugin-linear-import/package.json +1 -1
  123. package/dist/plugins/fusion-plugin-omp-runtime/bundled.js +357 -25
  124. package/dist/plugins/fusion-plugin-omp-runtime/package.json +1 -1
  125. package/dist/plugins/fusion-plugin-openclaw-runtime/bundled.js +357 -25
  126. package/dist/plugins/fusion-plugin-openclaw-runtime/package.json +1 -1
  127. package/dist/plugins/fusion-plugin-paperclip-runtime/bundled.js +357 -25
  128. package/dist/plugins/fusion-plugin-paperclip-runtime/package.json +1 -1
  129. package/dist/plugins/fusion-plugin-quality/bundled.js +357 -25
  130. package/dist/plugins/fusion-plugin-quality/package.json +1 -1
  131. package/dist/plugins/fusion-plugin-reports/bundled.js +357 -25
  132. package/dist/plugins/fusion-plugin-reports/package.json +1 -1
  133. package/dist/plugins/fusion-plugin-roadmap/bundled.js +357 -25
  134. package/dist/plugins/fusion-plugin-roadmap/manifest.json +11 -1
  135. package/dist/plugins/fusion-plugin-roadmap/package.json +1 -1
  136. package/dist/plugins/fusion-plugin-todos/bundled.js +357 -25
  137. package/dist/plugins/fusion-plugin-todos/manifest.json +11 -1
  138. package/dist/plugins/fusion-plugin-todos/package.json +1 -1
  139. package/dist/plugins/fusion-plugin-whatsapp-chat/bundled.js +357 -25
  140. package/dist/plugins/fusion-plugin-whatsapp-chat/package.json +1 -1
  141. package/package.json +1 -1
  142. package/skill/fusion/SKILL.md +1 -1
  143. package/skill/fusion/references/extension-tools.md +16 -2
  144. package/skill/fusion/references/fusion-capabilities.md +1 -0
  145. package/dist/client/assets/AgentDetailView-C8-9_SOC.css +0 -1
  146. package/dist/client/assets/AgentDetailView-ltOG8f0-.js +0 -13
  147. package/dist/client/assets/AgentsView-DWUdi27N.js +0 -24
  148. package/dist/client/assets/SettingsModal-CTf5voEW.js +0 -21
  149. package/dist/client/assets/app-B4gkbXNS.js +0 -13
  150. package/dist/client/assets/app-BXLZYY_A.js +0 -13
  151. package/dist/client/assets/app-CNFmVt1q.js +0 -13
  152. package/dist/client/assets/app-DCERHstn.js +0 -13
  153. package/dist/client/assets/app-iKAA6v9i.js +0 -5
  154. package/dist/client/assets/app-p_Hq0vGS.js +0 -13
  155. package/dist/client/assets/channel-DoX862zl.js +0 -1
  156. package/dist/client/assets/classDiagram-4FO5ZUOK-mb2IeAGN.js +0 -1
  157. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-mb2IeAGN.js +0 -1
  158. package/dist/client/assets/index-Bdw5eNKV.css +0 -1
@@ -163063,6 +163063,7 @@ __export(project_exports, {
163063
163063
  unplannedExecutionBlocks: () => unplannedExecutionBlocks,
163064
163064
  usageEvents: () => usageEvents,
163065
163065
  verificationCache: () => verificationCache,
163066
+ workflowAgentCapacityLeases: () => workflowAgentCapacityLeases,
163066
163067
  workflowPromptOverrides: () => workflowPromptOverrides,
163067
163068
  workflowRunBranches: () => workflowRunBranches,
163068
163069
  workflowRunStepInstances: () => workflowRunStepInstances,
@@ -163171,6 +163172,8 @@ var tasks = projectSchema.table("tasks", {
163171
163172
  nextRecoveryAt: text("next_recovery_at"),
163172
163173
  error: text("error"),
163173
163174
  summary: text("summary"),
163175
+ // FNXC:TaskRecommendations 2026-08-08-05:02: structured completion suggestions remain project-scoped JSONB beside the source task.
163176
+ recommendations: jsonb("recommendations"),
163174
163177
  thinkingLevel: text("thinking_level"),
163175
163178
  // FNXC:Settings-ThinkingLevel 2026-07-13 (merge port): validator/planning reasoning-effort overrides.
163176
163179
  validatorThinkingLevel: text("validator_thinking_level"),
@@ -163751,7 +163754,9 @@ var agents = projectSchema.table("agents", {
163751
163754
  projectId: text("project_id").notNull().default(sql`current_setting('fusion.project_id', true)`),
163752
163755
  id: text("id").notNull(),
163753
163756
  name: text("name").notNull(),
163757
+ /** Deprecated singular compatibility column; canonical roles live in roles JSONB. */
163754
163758
  role: text("role").notNull(),
163759
+ roles: jsonb("roles").notNull().default([]),
163755
163760
  state: text("state").notNull().default("idle"),
163756
163761
  taskId: text("task_id"),
163757
163762
  createdAt: text("created_at").notNull(),
@@ -163875,6 +163880,17 @@ var completionHandoffMarkers = projectSchema.table("completion_handoff_markers",
163875
163880
  foreignKey({ columns: [t.projectId, t.taskId], foreignColumns: [tasks.projectId, tasks.id] }).onDelete("cascade"),
163876
163881
  index("idx_completion_handoff_markers_acceptedAt").on(t.acceptedAt)
163877
163882
  ]);
163883
+ var workflowAgentCapacityLeases = projectSchema.table("workflow_agent_capacity_leases", {
163884
+ projectId: text("project_id").notNull().default(sql`current_setting('fusion.project_id', true)`),
163885
+ attemptId: text("attempt_id").notNull(),
163886
+ agentId: text("agent_id").notNull(),
163887
+ createdAt: text("created_at").notNull(),
163888
+ // FNXC:WorkflowAgentRouting 2026-08-07-07:16: A crashed engine cannot run its finally cleanup, so durable workflow capacity leases expire and are reclaimed on the next admission.
163889
+ expiresAt: text("expires_at").notNull()
163890
+ }, (t) => [
163891
+ primaryKey({ columns: [t.projectId, t.attemptId] }),
163892
+ index("idx_workflow_agent_capacity_leases_agent").on(t.projectId, t.agentId)
163893
+ ]);
163878
163894
  var workflowWorkItems = projectSchema.table("workflow_work_items", {
163879
163895
  projectId: text("project_id").notNull().default(sql`current_setting('fusion.project_id', true)`),
163880
163896
  id: text("id").notNull(),
@@ -163895,6 +163911,13 @@ var workflowWorkItems = projectSchema.table("workflow_work_items", {
163895
163911
  sourceColumn: text("source_column"),
163896
163912
  targetColumn: text("target_column"),
163897
163913
  irHash: text("ir_hash"),
163914
+ // FNXC:WorkflowAgentRouting 2026-08-07-03:25:
163915
+ // A workflow claim fences its durable principal and exact template instance.
163916
+ // Session retries must reuse this identity rather than re-routing a task.
163917
+ principalAgentId: text("principal_agent_id"),
163918
+ workflowRole: text("workflow_role"),
163919
+ authorityKind: text("authority_kind"),
163920
+ nodeInstanceId: text("node_instance_id"),
163898
163921
  createdAt: text("created_at").notNull(),
163899
163922
  updatedAt: text("updated_at").notNull()
163900
163923
  }, (t) => [
@@ -165890,6 +165913,7 @@ var BLOCKING_TASK_STATUSES = /* @__PURE__ */ new Set([
165890
165913
  // ../core/src/tasks/in-review-stall.ts
165891
165914
  var DEFAULT_STALE_MERGING_MIN_AGE_MS = 5 * 6e4;
165892
165915
  var DEFAULT_MAX_AUTO_MERGE_RETRIES = 3;
165916
+ var CONSECUTIVE_TOOL_FAILURE_RETRY_THRESHOLD = 1;
165893
165917
 
165894
165918
  // ../core/src/config/settings-schema.ts
165895
165919
  var DEFAULT_GLOBAL_SETTINGS = {
@@ -166230,6 +166254,8 @@ var DEFAULT_GLOBAL_SETTINGS = {
166230
166254
  cliAgents: {}
166231
166255
  };
166232
166256
  var DEFAULT_PROJECT_SETTINGS = {
166257
+ // FNXC:TaskRecommendations 2026-08-08-05:02: completion follows-ups stay bounded by default; 0 disables writing them.
166258
+ maxRecommendationsPerTask: 3,
166233
166259
  globalPause: false,
166234
166260
  globalPauseReason: void 0,
166235
166261
  defaultWorkflowId: void 0,
@@ -166398,9 +166424,15 @@ var DEFAULT_PROJECT_SETTINGS = {
166398
166424
  * Project settings own the auto-merge conflict retry cap because existing engine/dashboard consumers already resolve project settings; the default imports core's stall-detection fallback to keep every surface on the historical value of 3.
166399
166425
  */
166400
166426
  maxAutoMergeRetries: DEFAULT_MAX_AUTO_MERGE_RETRIES,
166427
+ /*
166428
+ FNXC:ExecutorToolFailureRetry 2026-08-06-14:56:
166429
+ Fresh projects retry the existing bounded same-model continuation after one
166430
+ terminal tool error. Persisted project values are not migrated, so explicit
166431
+ operator thresholds remain authoritative.
166432
+ */
166401
166433
  executorToolFailureRetryCount: 2,
166402
166434
  executorToolFailureRetryBackoffMs: 2e3,
166403
- executorToolFailureThreshold: 3,
166435
+ executorToolFailureThreshold: CONSECUTIVE_TOOL_FAILURE_RETRY_THRESHOLD,
166404
166436
  executorModelEscalationEnabled: false,
166405
166437
  executorEscalationProvider: void 0,
166406
166438
  executorEscalationModelId: void 0,
@@ -166436,6 +166468,12 @@ var DEFAULT_PROJECT_SETTINGS = {
166436
166468
  // proportional to the change; the thin merge gate carries cross-cutting
166437
166469
  // coverage. Falls back to package/explicit command when no tests resolve.
166438
166470
  scopeVerificationToChangedFiles: true,
166471
+ /*
166472
+ FNXC:WorkflowAgentRouting 2026-08-07-08:45:
166473
+ Keep the legacy project setting defaulted and persistable for existing clients and
166474
+ configuration records. Workflow-stage routing must ignore its value; durable role
166475
+ principals own scheduler, executor, and mission-stage authority.
166476
+ */
166439
166477
  ephemeralAgentsEnabled: true,
166440
166478
  /*
166441
166479
  FNXC:EphemeralAgentTaskCreation 2026-07-01-00:00:
@@ -166737,6 +166775,23 @@ function isEphemeralAgent(agent) {
166737
166775
  }
166738
166776
 
166739
166777
  // ../core/src/types/agents/agents.ts
166778
+ var AGENT_CAPABILITIES = [
166779
+ "triage",
166780
+ "executor",
166781
+ "reviewer",
166782
+ "merger",
166783
+ "scheduler",
166784
+ "engineer",
166785
+ "custom"
166786
+ ];
166787
+ function normalizeAgentRoles(roles, legacyRole) {
166788
+ const values = roles?.length ? roles : legacyRole ? [legacyRole] : [];
166789
+ const unique2 = new Set(values);
166790
+ const normalized = AGENT_CAPABILITIES.filter((role) => unique2.has(role));
166791
+ if (normalized.length !== unique2.size) throw new Error("Agent roles contain an unknown capability");
166792
+ if (normalized.length === 0) throw new Error("Agent requires at least one role");
166793
+ return normalized;
166794
+ }
166740
166795
  var AGENT_PERMISSIONS = [
166741
166796
  "tasks:assign",
166742
166797
  // Assign tasks to agents
@@ -166858,6 +166913,7 @@ function getDefaultHeartbeatProcedurePath(agentId, agentName) {
166858
166913
  function agentToConfigSnapshot(agent) {
166859
166914
  return {
166860
166915
  name: agent.name,
166916
+ roles: [...agent.roles],
166861
166917
  role: agent.role,
166862
166918
  title: agent.title,
166863
166919
  icon: agent.icon,
@@ -167192,6 +167248,41 @@ function resolveColumnFlags(column) {
167192
167248
  return getTraitRegistry().resolveColumnFlags(column);
167193
167249
  }
167194
167250
 
167251
+ // ../core/src/workflows/workflow-ir-types.ts
167252
+ var WORKFLOW_AGENT_ROLES = ["triage", "executor", "reviewer", "merger"];
167253
+ function isWorkflowAgentRole(value) {
167254
+ return typeof value === "string" && WORKFLOW_AGENT_ROLES.includes(value);
167255
+ }
167256
+ function classifyWorkflowAgentNode(node) {
167257
+ if (node.kind === "step-review") return "reviewer";
167258
+ if (node.kind !== "prompt" && node.kind !== "script") return void 0;
167259
+ switch (node.config?.seam) {
167260
+ /*
167261
+ * FNXC:WorkflowAgentRouting 2026-08-07-05:52:
167262
+ * Planning, independent review, and promotion seams each launch work under
167263
+ * their permanent stage owner. Classify those production seams here rather
167264
+ * than treating their handoff worker as an unowned lifecycle operation.
167265
+ * Only pure handoff/scheduling seams remain principal-free.
167266
+ */
167267
+ case "planning":
167268
+ return "triage";
167269
+ case "execute":
167270
+ case "step-execute":
167271
+ return "executor";
167272
+ case "review":
167273
+ return "reviewer";
167274
+ case "merge":
167275
+ return "merger";
167276
+ case "review-handoff":
167277
+ case "schedule":
167278
+ return void 0;
167279
+ default: {
167280
+ const explicit = node.config?.workflowRole;
167281
+ return isWorkflowAgentRole(explicit) ? explicit : void 0;
167282
+ }
167283
+ }
167284
+ }
167285
+
167195
167286
  // ../core/src/workflows/workflow-extension-types.ts
167196
167287
  function workflowExtensionRegistryId(pluginId, extensionId) {
167197
167288
  return `plugin:${pluginId}:${extensionId}`;
@@ -168180,6 +168271,20 @@ function validateCredentialInstanceIdConfig(nodes2) {
168180
168271
  if (Array.isArray(templateNodes)) validateCredentialInstanceIdConfig(templateNodes);
168181
168272
  }
168182
168273
  }
168274
+ function validateReviewerAgentOverrides(nodes2) {
168275
+ for (const node of nodes2) {
168276
+ if (node.reviewerAgentId !== void 0) {
168277
+ if (typeof node.reviewerAgentId !== "string" || !node.reviewerAgentId.trim()) {
168278
+ throw new WorkflowIrError(`Workflow node '${node.id}' reviewerAgentId must be a non-empty string`);
168279
+ }
168280
+ if (classifyWorkflowAgentNode(node) !== "reviewer") {
168281
+ throw new WorkflowIrError(`Workflow node '${node.id}' reviewerAgentId is only legal on reviewer-session nodes`);
168282
+ }
168283
+ }
168284
+ const templateNodes = node.config?.template?.nodes;
168285
+ if (Array.isArray(templateNodes)) validateReviewerAgentOverrides(templateNodes);
168286
+ }
168287
+ }
168183
168288
  function validateThinkingLevelConfig(nodes2) {
168184
168289
  for (const node of nodes2) {
168185
168290
  const value = node.config?.thinkingLevel;
@@ -168795,6 +168900,7 @@ function validateV2(ir) {
168795
168900
  validateStepExecutePlacement(ir.nodes);
168796
168901
  validateThinkingLevelConfig(ir.nodes);
168797
168902
  validateCredentialInstanceIdConfig(ir.nodes);
168903
+ validateReviewerAgentOverrides(ir.nodes);
168798
168904
  for (const node of ir.nodes) {
168799
168905
  if (node.kind === "foreach") validateForeach(node, topLevelIds, columnIds);
168800
168906
  if (node.kind === "loop") validateLoop(node, topLevelIds, columnIds);
@@ -173028,7 +173134,8 @@ function normalizePermissions(raw) {
173028
173134
  return permissions;
173029
173135
  }
173030
173136
  function computeAccessState(agent) {
173031
- const roleDefaultPermissions = new Set(ROLE_DEFAULT_PERMISSIONS[agent.role] ?? []);
173137
+ const roles = agent.roles?.length ? agent.roles : [agent.role];
173138
+ const roleDefaultPermissions = new Set(roles.flatMap((role) => ROLE_DEFAULT_PERMISSIONS[role] ?? []));
173032
173139
  const explicitPermissions = normalizePermissions(agent.permissions ?? {});
173033
173140
  const resolvedPermissions = new Set(roleDefaultPermissions);
173034
173141
  for (const permission of explicitPermissions) {
@@ -173059,6 +173166,9 @@ var IMPLEMENTATION_TASK_COLUMNS = /* @__PURE__ */ new Set([
173059
173166
  "in-progress",
173060
173167
  "in-review"
173061
173168
  ]);
173169
+ function agentRoles(agent) {
173170
+ return agent.roles?.length ? agent.roles : agent.role ? [agent.role] : [];
173171
+ }
173062
173172
  function getAgentAssignmentPolicy(agent) {
173063
173173
  const raw = (agent.runtimeConfig ?? {})["assignmentPolicy"];
173064
173174
  return raw === "explicit-only" || raw === "none" ? raw : "auto";
@@ -173073,10 +173183,10 @@ function isImplementationTask(task) {
173073
173183
  return IMPLEMENTATION_TASK_COLUMNS.has(task.column);
173074
173184
  }
173075
173185
  function isExecutorRoleAgent(agent) {
173076
- return agent.role === "executor";
173186
+ return agentRoles(agent).includes("executor");
173077
173187
  }
173078
173188
  function isEngineerRoleAgent(agent) {
173079
- return agent.role === "engineer";
173189
+ return agentRoles(agent).includes("engineer");
173080
173190
  }
173081
173191
  function canAgentTakeImplementationTaskForExplicitRouting(agent, task) {
173082
173192
  if (!isImplementationTask(task)) return true;
@@ -173125,7 +173235,8 @@ function formatRoleMismatchReason(agent, task) {
173125
173235
  if (policy !== "auto") {
173126
173236
  return `Agent ${agent.id} has assignmentPolicy "${policy}"; implementation task ${task.id} cannot be routed to it${policy === "none" ? " by any path (no override supported)" : " automatically \u2014 explicit routing only"}.`;
173127
173237
  }
173128
- return `Agent ${agent.id} has role "${agent.role}"; implementation task ${task.id} requires an "executor"-role agent by default, with durable "engineer" supported only for explicit routing. Pass override=true to bypass.`;
173238
+ const roles = agentRoles(agent);
173239
+ return `Agent ${agent.id} has roles "${roles.join(", ") || "none"}"; implementation task ${task.id} requires an "executor"-role agent by default, with durable "engineer" supported only for explicit routing. Pass override=true to bypass.`;
173129
173240
  }
173130
173241
 
173131
173242
  // ../core/src/agents/agent-permission-policy.ts
@@ -173247,6 +173358,7 @@ var agentColumns = {
173247
173358
  id: project_exports.agents.id,
173248
173359
  name: project_exports.agents.name,
173249
173360
  role: project_exports.agents.role,
173361
+ roles: project_exports.agents.roles,
173250
173362
  state: project_exports.agents.state,
173251
173363
  taskId: project_exports.agents.taskId,
173252
173364
  createdAt: project_exports.agents.createdAt,
@@ -173277,6 +173389,7 @@ function agentToData(agent) {
173277
173389
  return {
173278
173390
  id: agent.id,
173279
173391
  name: agent.name,
173392
+ roles: agent.roles,
173280
173393
  role: agent.role,
173281
173394
  state: agent.state,
173282
173395
  taskId: agent.taskId,
@@ -173311,6 +173424,7 @@ async function writeAgent(handle, agent, projectId) {
173311
173424
  id: agent.id,
173312
173425
  name: agent.name,
173313
173426
  role: agent.role,
173427
+ roles: agent.roles,
173314
173428
  state: agent.state,
173315
173429
  taskId: agent.taskId ?? null,
173316
173430
  createdAt: agent.createdAt,
@@ -173323,6 +173437,7 @@ async function writeAgent(handle, agent, projectId) {
173323
173437
  set: {
173324
173438
  name: agent.name,
173325
173439
  role: agent.role,
173440
+ roles: agent.roles,
173326
173441
  state: agent.state,
173327
173442
  taskId: agent.taskId ?? null,
173328
173443
  updatedAt: agent.updatedAt,
@@ -173344,6 +173459,7 @@ function mergeAgentRow(row) {
173344
173459
  ...data,
173345
173460
  id: row.id,
173346
173461
  name: row.name,
173462
+ roles: row.roles?.length ? row.roles : [row.role],
173347
173463
  role: row.role,
173348
173464
  state: row.state,
173349
173465
  taskId: row.taskId ?? void 0,
@@ -173673,8 +173789,62 @@ var FsWatchPollController = class {
173673
173789
  }
173674
173790
  };
173675
173791
 
173792
+ // ../core/src/agents/workflow-role-agent-defaults.ts
173793
+ var BUILTIN_WORKFLOW_AGENT_BUNDLE_CONFIG = {
173794
+ mode: "managed",
173795
+ entryFile: "AGENTS.md",
173796
+ files: ["AGENTS.md", "soul.md"]
173797
+ };
173798
+ var BUILTIN_WORKFLOW_ROLE_AGENT_DEFAULTS = {
173799
+ triage: {
173800
+ role: "triage",
173801
+ name: "Workflow Planner",
173802
+ title: "Built-in workflow planning owner",
173803
+ instructionsText: "You are the Workflow Planner. Turn incoming work into clear, bounded, testable plans. Identify the user outcome, constraints, affected surfaces, and acceptance evidence before release. Keep planning distinct from implementation: do not claim code is complete, invent decisions, or bypass approval gates. Communicate concisely, make uncertainty explicit, and leave an executable plan that another agent can follow.",
173804
+ soul: "Calm, structured, and curious. You reduce ambiguity without adding ceremony, respect operator intent, and prefer concrete evidence over confident guesses."
173805
+ },
173806
+ executor: {
173807
+ role: "executor",
173808
+ name: "Workflow Executor",
173809
+ title: "Built-in workflow execution owner",
173810
+ instructionsText: "You are the Workflow Executor. Implement the approved scope with production-quality, maintainable changes. Read the task specification, preserve existing behavior outside scope, and verify the smallest relevant checks before reporting completion. Do not redesign workflow policy, broaden permissions, or conceal blockers; record concrete evidence and surface a genuine dependency when work cannot proceed. Communicate changes and verification precisely.",
173811
+ soul: "Methodical, practical, and accountable. You favor small safe steps, protect user work, and let tests and observable behavior guide your confidence."
173812
+ },
173813
+ reviewer: {
173814
+ role: "reviewer",
173815
+ name: "Workflow Reviewer",
173816
+ title: "Built-in workflow review owner",
173817
+ instructionsText: "You are the Workflow Reviewer. Independently assess whether the submitted work satisfies its specification, preserves system invariants, and has credible verification. Focus on correctness, regressions, security, maintainability, and missing evidence rather than rewriting the implementation. State actionable findings with severity and rationale; approve only when the evidence supports it. Do not perform implementation or override workflow gates.",
173818
+ soul: "Independent, exacting, and fair. You are skeptical of unsupported claims, respectful in feedback, and clear about what would make the work safe to accept."
173819
+ },
173820
+ merger: {
173821
+ role: "merger",
173822
+ name: "Workflow Merger",
173823
+ title: "Built-in workflow merge owner",
173824
+ instructionsText: "You are the Workflow Merger. Integrate only work that has met the project\u2019s configured completion and review requirements. Protect the target branch, inspect merge state and conflicts carefully, and preserve unrelated work. Do not substitute judgment for missing approval, bypass safeguards, or silently discard changes. Report the integration result, any conflicts, and the evidence used to make the decision.",
173825
+ soul: "Deliberate, conservative, and transparent. You optimize for repository integrity, treat uncertainty as a reason to stop, and communicate operational state plainly."
173826
+ }
173827
+ };
173828
+ var BUILTIN_WORKFLOW_ROLE_AGENT_DEFAULT_LIST = Object.values(BUILTIN_WORKFLOW_ROLE_AGENT_DEFAULTS);
173829
+
173676
173830
  // ../core/src/agents/agent-store.ts
173677
173831
  var agentStoreLog = createLogger("agent-store");
173832
+ function isCanonicalOrPartialBuiltinWorkflowBundle(config2) {
173833
+ if (config2?.mode !== "managed" || config2.entryFile !== "AGENTS.md") return false;
173834
+ return config2.files.every((file) => (file === "AGENTS.md" || file === "soul.md") && config2.files.indexOf(file) === config2.files.lastIndexOf(file));
173835
+ }
173836
+ function isCompleteBuiltinWorkflowBundle(config2) {
173837
+ return isCanonicalOrPartialBuiltinWorkflowBundle(config2) && config2.files.length === BUILTIN_WORKFLOW_AGENT_BUNDLE_CONFIG.files.length;
173838
+ }
173839
+ function compareBuiltinWorkflowOwnerAge(a, b) {
173840
+ const aTime = Date.parse(a.createdAt);
173841
+ const bTime = Date.parse(b.createdAt);
173842
+ const aValid = Number.isFinite(aTime);
173843
+ const bValid = Number.isFinite(bTime);
173844
+ if (aValid && bValid && aTime !== bTime) return aTime - bTime;
173845
+ if (aValid !== bValid) return aValid ? -1 : 1;
173846
+ return a.id.localeCompare(b.id);
173847
+ }
173678
173848
  var DEFAULT_AGENT_HEARTBEAT_INTERVAL_MS = 36e5;
173679
173849
  function normalizeStoredPermissions(raw) {
173680
173850
  if (!raw) return void 0;
@@ -173785,6 +173955,73 @@ var AgentStore = class _AgentStore extends EventEmitter {
173785
173955
  }
173786
173956
  return projectId;
173787
173957
  }
173958
+ /** The durable project partition used by workflow routing and capacity leases. */
173959
+ get workflowProjectId() {
173960
+ return this.asyncLayer?.projectId;
173961
+ }
173962
+ /**
173963
+ * FNXC:WorkflowAgentRouting 2026-08-07-05:52:
173964
+ * Workflow capacity is a PostgreSQL lease rather than a process-local count.
173965
+ * The project advisory lock serializes project then agent admission across
173966
+ * engine processes; model work starts only after this short transaction ends.
173967
+ */
173968
+ async acquireWorkflowSessionCapacity(input) {
173969
+ if (!this.asyncLayer) return "acquired";
173970
+ const projectId = this.backendProjectId;
173971
+ const now = /* @__PURE__ */ new Date();
173972
+ const expiresAt = new Date(now.getTime() + (input.leaseDurationMs ?? 10 * 6e4)).toISOString();
173973
+ return this.asyncLayer.transactionImmediate(async (tx) => {
173974
+ await tx.execute(sql`SELECT pg_advisory_xact_lock(hashtext(${projectId}), hashtext('workflow-agent-capacity'))`);
173975
+ await tx.delete(project_exports.workflowAgentCapacityLeases).where(and(
173976
+ eq(project_exports.workflowAgentCapacityLeases.projectId, projectId),
173977
+ lte(project_exports.workflowAgentCapacityLeases.expiresAt, now.toISOString())
173978
+ ));
173979
+ const existing = await tx.select({ attemptId: project_exports.workflowAgentCapacityLeases.attemptId }).from(project_exports.workflowAgentCapacityLeases).where(and(
173980
+ eq(project_exports.workflowAgentCapacityLeases.projectId, projectId),
173981
+ eq(project_exports.workflowAgentCapacityLeases.attemptId, input.attemptId)
173982
+ )).limit(1);
173983
+ if (existing[0]) return "acquired";
173984
+ const projectCount = await tx.select({ count: sql`count(*)::int` }).from(project_exports.workflowAgentCapacityLeases).where(eq(project_exports.workflowAgentCapacityLeases.projectId, projectId));
173985
+ if (input.maxProjectSessions !== void 0 && (projectCount[0]?.count ?? 0) >= input.maxProjectSessions) return "project-capacity";
173986
+ const agentCount = await tx.select({ count: sql`count(*)::int` }).from(project_exports.workflowAgentCapacityLeases).where(and(
173987
+ eq(project_exports.workflowAgentCapacityLeases.projectId, projectId),
173988
+ eq(project_exports.workflowAgentCapacityLeases.agentId, input.agentId)
173989
+ ));
173990
+ if (input.maxAgentSessions !== void 0 && (agentCount[0]?.count ?? 0) >= input.maxAgentSessions) return "agent-capacity";
173991
+ await tx.insert(project_exports.workflowAgentCapacityLeases).values({
173992
+ projectId,
173993
+ agentId: input.agentId,
173994
+ attemptId: input.attemptId,
173995
+ createdAt: now.toISOString(),
173996
+ expiresAt
173997
+ });
173998
+ return "acquired";
173999
+ });
174000
+ }
174001
+ /** Renew only the caller's project-scoped attempt; a reclaimed lease never resurrects. */
174002
+ async renewWorkflowSessionCapacity(attemptId, leaseDurationMs = 10 * 6e4) {
174003
+ if (!this.asyncLayer) return true;
174004
+ const now = /* @__PURE__ */ new Date();
174005
+ const result = await this.asyncLayer.db.update(project_exports.workflowAgentCapacityLeases).set({ expiresAt: new Date(now.getTime() + leaseDurationMs).toISOString() }).where(and(
174006
+ eq(project_exports.workflowAgentCapacityLeases.projectId, this.backendProjectId),
174007
+ eq(project_exports.workflowAgentCapacityLeases.attemptId, attemptId),
174008
+ /*
174009
+ * FNXC:WorkflowAgentRouting 2026-08-07-07:30:
174010
+ * Renew a currently live lease only. An expired row may already have
174011
+ * been reclaimed by another engine's admission transaction.
174012
+ */
174013
+ gt(project_exports.workflowAgentCapacityLeases.expiresAt, now.toISOString())
174014
+ )).returning({ attemptId: project_exports.workflowAgentCapacityLeases.attemptId });
174015
+ return result.length > 0;
174016
+ }
174017
+ /** Release is project-scoped and idempotent so cancellation and recovery may race safely. */
174018
+ async releaseWorkflowSessionCapacity(attemptId) {
174019
+ if (!this.asyncLayer) return;
174020
+ await this.asyncLayer.db.delete(project_exports.workflowAgentCapacityLeases).where(and(
174021
+ eq(project_exports.workflowAgentCapacityLeases.projectId, this.backendProjectId),
174022
+ eq(project_exports.workflowAgentCapacityLeases.attemptId, attemptId)
174023
+ ));
174024
+ }
173788
174025
  get db() {
173789
174026
  throw new Error("SQLite Database is not available in backend mode (asyncLayer injected)");
173790
174027
  }
@@ -173799,7 +174036,7 @@ var AgentStore = class _AgentStore extends EventEmitter {
173799
174036
  */
173800
174037
  async init() {
173801
174038
  await mkdir(this.agentsDir, { recursive: true });
173802
- return;
174039
+ await this.provisionBuiltinWorkflowRoleAgents();
173803
174040
  }
173804
174041
  /**
173805
174042
  * One-shot migration that re-points every non-ephemeral agent off the
@@ -173961,8 +174198,8 @@ var AgentStore = class _AgentStore extends EventEmitter {
173961
174198
  * @param name - Agent name to match exactly
173962
174199
  * @returns Matching non-ephemeral agent, or null when none exists
173963
174200
  */
173964
- async findAgentByName(name) {
173965
- const agents2 = await findAgentRowsByName(this.asyncLayer.db, name);
174201
+ async findAgentByName(name, executor) {
174202
+ const agents2 = await findAgentRowsByName(executor ?? this.asyncLayer.db, name);
173966
174203
  for (const agent of agents2) {
173967
174204
  if (!isEphemeralAgent(agent)) {
173968
174205
  return this.parseAgent(agent);
@@ -173997,18 +174234,16 @@ var AgentStore = class _AgentStore extends EventEmitter {
173997
174234
  * @returns The created agent
173998
174235
  * @throws Error if input is invalid or a duplicate non-ephemeral name exists
173999
174236
  */
174000
- async createAgent(input) {
174237
+ async createAgent(input, executor) {
174001
174238
  if (!input.name?.trim()) {
174002
174239
  throw new Error("Agent name is required");
174003
174240
  }
174004
- if (!input.role) {
174005
- throw new Error("Agent role is required");
174006
- }
174241
+ const roles = normalizeAgentRoles(input.roles, input.role);
174007
174242
  const normalizedName = input.name.trim();
174008
174243
  const metadata = input.metadata ?? {};
174009
- const ephemeral = isEphemeralAgent({ metadata, name: input.name, role: input.role, reportsTo: input.reportsTo });
174244
+ const ephemeral = isEphemeralAgent({ metadata, name: input.name, role: roles[0], reportsTo: input.reportsTo });
174010
174245
  if (!ephemeral) {
174011
- const existing = await this.findAgentByName(normalizedName);
174246
+ const existing = await this.findAgentByName(normalizedName, executor);
174012
174247
  if (existing) {
174013
174248
  throw new Error(`Agent with name "${normalizedName}" already exists (agentId: ${existing.id})`);
174014
174249
  }
@@ -174021,7 +174256,8 @@ var AgentStore = class _AgentStore extends EventEmitter {
174021
174256
  const agent = {
174022
174257
  id: agentId,
174023
174258
  name: normalizedName,
174024
- role: input.role,
174259
+ roles,
174260
+ role: roles[0],
174025
174261
  // Non-ephemeral agents start active so they immediately participate in
174026
174262
  // heartbeat scheduling; ephemeral/task-worker agents start idle and are
174027
174263
  // activated by the engine when work is assigned.
@@ -174043,7 +174279,7 @@ var AgentStore = class _AgentStore extends EventEmitter {
174043
174279
  ...input.bundleConfig && { bundleConfig: input.bundleConfig },
174044
174280
  ...resolvedHeartbeatProcedurePath && { heartbeatProcedurePath: resolvedHeartbeatProcedurePath }
174045
174281
  };
174046
- await this.writeAgent(agent);
174282
+ await this.writeAgent(agent, executor);
174047
174283
  this.emit("agent:created", agent);
174048
174284
  return agent;
174049
174285
  }
@@ -174386,10 +174622,12 @@ var AgentStore = class _AgentStore extends EventEmitter {
174386
174622
  const beforeSnapshot = agentToConfigSnapshot(agent);
174387
174623
  const updatedAt = (/* @__PURE__ */ new Date()).toISOString();
174388
174624
  const normalizedUpdatedPermissionPolicy = "permissionPolicy" in updates && updates.permissionPolicy !== void 0 ? normalizeAgentPermissionPolicy(updates.permissionPolicy) : updates.permissionPolicy;
174625
+ const roles = updates.roles !== void 0 || updates.role !== void 0 ? normalizeAgentRoles(updates.roles, updates.role) : agent.roles;
174389
174626
  const updated = {
174390
174627
  ...agent,
174391
174628
  name: nextName ?? agent.name,
174392
- role: updates.role ?? agent.role,
174629
+ roles,
174630
+ role: roles[0],
174393
174631
  metadata: updates.metadata !== void 0 ? updates.metadata : agent.metadata,
174394
174632
  updatedAt,
174395
174633
  ..."title" in updates && { title: updates.title },
@@ -174924,12 +175162,81 @@ var AgentStore = class _AgentStore extends EventEmitter {
174924
175162
  * @param filter - Optional filter criteria
174925
175163
  * @returns Array of agents
174926
175164
  */
174927
- async listAgents(filter) {
174928
- const agents2 = await listAgentRows(this.asyncLayer.db, {
174929
- state: filter?.state,
174930
- role: filter?.role
175165
+ async listAgents(filter, executor) {
175166
+ const agents2 = await listAgentRows(executor ?? this.asyncLayer.db, { state: filter?.state });
175167
+ return agents2.map((a) => this.parseAgent(a)).filter((agent) => !filter?.role || agent.roles.includes(filter.role)).filter((agent) => filter?.includeEphemeral === true || !isEphemeralAgent(agent));
175168
+ }
175169
+ /**
175170
+ * Idempotently seed the permanent owners that route built-in workflow stages.
175171
+ * Matching operator-created agents are intentionally not reused: provenance
175172
+ * makes upgrade repair deterministic while operators remain free to add pool
175173
+ * members with the same tag.
175174
+ */
175175
+ async provisionBuiltinWorkflowRoleAgents() {
175176
+ const provision = async (executor) => {
175177
+ const existing = await this.listAgents({ includeEphemeral: true }, executor);
175178
+ const supportedRoles = new Set(BUILTIN_WORKFLOW_ROLE_AGENT_DEFAULT_LIST.map(({ role }) => role));
175179
+ const groups = /* @__PURE__ */ new Map();
175180
+ for (const agent of existing) {
175181
+ const role = agent.metadata?.workflowRole;
175182
+ if (agent.metadata?.builtInWorkflowRole === true && typeof role === "string" && supportedRoles.has(role)) {
175183
+ const group = groups.get(role) ?? [];
175184
+ group.push(agent);
175185
+ groups.set(role, group);
175186
+ }
175187
+ }
175188
+ const winners = /* @__PURE__ */ new Map();
175189
+ for (const [role, group] of groups) {
175190
+ group.sort(compareBuiltinWorkflowOwnerAge);
175191
+ winners.set(role, group[0]);
175192
+ for (const loser of group.slice(1)) {
175193
+ const { builtInWorkflowRole: _builtIn, workflowRole: _role, ...metadata } = loser.metadata;
175194
+ await this.writeAgent({ ...loser, metadata, updatedAt: (/* @__PURE__ */ new Date()).toISOString() }, executor);
175195
+ }
175196
+ }
175197
+ const result = [];
175198
+ for (const definition of BUILTIN_WORKFLOW_ROLE_AGENT_DEFAULT_LIST) {
175199
+ let agent = winners.get(definition.role);
175200
+ if (!agent) {
175201
+ agent = await this.createAgent({
175202
+ name: definition.name,
175203
+ roles: [definition.role],
175204
+ title: definition.title,
175205
+ metadata: { builtInWorkflowRole: true, workflowRole: definition.role },
175206
+ runtimeConfig: { enabled: false },
175207
+ instructionsText: definition.instructionsText,
175208
+ soul: definition.soul,
175209
+ bundleConfig: { ...BUILTIN_WORKFLOW_AGENT_BUNDLE_CONFIG, files: [...BUILTIN_WORKFLOW_AGENT_BUNDLE_CONFIG.files] }
175210
+ }, executor);
175211
+ } else {
175212
+ const canonicalOrPartialBundle = isCanonicalOrPartialBuiltinWorkflowBundle(agent.bundleConfig);
175213
+ const customInstructions = Boolean(agent.instructionsPath?.trim()) || agent.bundleConfig?.mode === "external" || agent.bundleConfig !== void 0 && !canonicalOrPartialBundle || Boolean(agent.instructionsText?.trim()) && agent.instructionsText !== definition.instructionsText;
175214
+ const updates = {};
175215
+ if (!customInstructions && !agent.instructionsText?.trim()) updates.instructionsText = definition.instructionsText;
175216
+ if (!agent.soul?.trim()) updates.soul = definition.soul;
175217
+ if (!customInstructions && !isCompleteBuiltinWorkflowBundle(agent.bundleConfig)) {
175218
+ updates.bundleConfig = { ...BUILTIN_WORKFLOW_AGENT_BUNDLE_CONFIG, files: [...BUILTIN_WORKFLOW_AGENT_BUNDLE_CONFIG.files] };
175219
+ }
175220
+ if (Object.keys(updates).length > 0) {
175221
+ agent = { ...agent, ...updates, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
175222
+ await this.writeAgent(agent, executor);
175223
+ }
175224
+ }
175225
+ result.push(agent);
175226
+ }
175227
+ return result;
175228
+ };
175229
+ if (!this.asyncLayer) {
175230
+ const agents3 = await provision();
175231
+ await Promise.all(agents3.map((agent) => this.materializeBuiltinWorkflowRoleBundle(agent)));
175232
+ return agents3;
175233
+ }
175234
+ const agents2 = await this.asyncLayer.transactionImmediate(async (tx) => {
175235
+ await tx.execute(sql`SELECT pg_advisory_xact_lock(hashtext(${this.backendProjectId}), hashtext('builtin-workflow-role-provisioning'))`);
175236
+ return provision(tx);
174931
175237
  });
174932
- return agents2.map((a) => this.parseAgent(a)).filter((agent) => filter?.includeEphemeral === true || !isEphemeralAgent(agent));
175238
+ await Promise.all(agents2.map((agent) => this.materializeBuiltinWorkflowRoleBundle(agent)));
175239
+ return agents2;
174933
175240
  }
174934
175241
  /**
174935
175242
  * Create an API key for an agent.
@@ -175497,6 +175804,29 @@ var AgentStore = class _AgentStore extends EventEmitter {
175497
175804
  }
175498
175805
  return null;
175499
175806
  }
175807
+ /** Seed only missing or blank default-owned mirror files after the provisioning transaction commits. */
175808
+ async materializeBuiltinWorkflowRoleBundle(agent) {
175809
+ return this.withLock(agent.id, async () => {
175810
+ const role = agent.metadata?.workflowRole;
175811
+ const definition = BUILTIN_WORKFLOW_ROLE_AGENT_DEFAULT_LIST.find((candidate) => candidate.role === role);
175812
+ if (!definition || agent.metadata?.builtInWorkflowRole !== true) return;
175813
+ const config2 = agent.bundleConfig;
175814
+ if (!isCanonicalOrPartialBuiltinWorkflowBundle(config2)) return;
175815
+ if (agent.instructionsPath?.trim() || agent.instructionsText?.trim() && agent.instructionsText !== definition.instructionsText) return;
175816
+ const bundleDir = await this.resolveCompatibleBundleDir(agent.id, true);
175817
+ await mkdir(bundleDir, { recursive: true });
175818
+ for (const [file, content] of [["AGENTS.md", definition.instructionsText], ["soul.md", definition.soul]]) {
175819
+ const path = join(bundleDir, file);
175820
+ let current = "";
175821
+ try {
175822
+ current = await readFile(path, "utf-8");
175823
+ } catch (error) {
175824
+ if (error.code !== "ENOENT") throw error;
175825
+ }
175826
+ if (!current.trim()) await writeFile(path, content, "utf-8");
175827
+ }
175828
+ });
175829
+ }
175500
175830
  getCanonicalBundleDir(agent) {
175501
175831
  return join(this.agentsDir, getCanonicalAgentInstructionsBundleDirName(agent.name, agent.id));
175502
175832
  }
@@ -175619,6 +175949,7 @@ var AgentStore = class _AgentStore extends EventEmitter {
175619
175949
  ...data,
175620
175950
  id: row.id,
175621
175951
  name: row.name,
175952
+ roles: data.roles?.length ? data.roles : [row.role],
175622
175953
  role: row.role,
175623
175954
  state: row.state,
175624
175955
  taskId: row.taskId ?? void 0,
@@ -175652,7 +175983,8 @@ var AgentStore = class _AgentStore extends EventEmitter {
175652
175983
  return {
175653
175984
  id: data.id,
175654
175985
  name: data.name,
175655
- role: data.role,
175986
+ roles: normalizeAgentRoles(data.roles, data.role),
175987
+ role: normalizeAgentRoles(data.roles, data.role)[0],
175656
175988
  state: data.state,
175657
175989
  taskId: data.taskId,
175658
175990
  createdAt: data.createdAt,
@@ -175678,8 +176010,8 @@ var AgentStore = class _AgentStore extends EventEmitter {
175678
176010
  heartbeatProcedurePath: data.heartbeatProcedurePath
175679
176011
  };
175680
176012
  }
175681
- async writeAgent(agent) {
175682
- await writeAgent(this.asyncLayer.db, agent, this.asyncLayer.projectId);
176013
+ async writeAgent(agent, executor) {
176014
+ await writeAgent(executor ?? this.asyncLayer.db, agent, this.asyncLayer.projectId);
175683
176015
  return;
175684
176016
  }
175685
176017
  /**
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fusion-plugin-examples/whatsapp-chat",
3
- "version": "0.1.53-beta.2",
3
+ "version": "0.1.54-beta.0",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@runfusion/fusion",
3
- "version": "0.75.1-beta.2",
3
+ "version": "0.76.0-beta.0",
4
4
  "license": "MIT",
5
5
  "description": "Fusion CLI: HTTP API server, daemon, dashboard launcher, and task tooling for the Fusion AI coding agent.",
6
6
  "homepage": "https://github.com/Runfusion/Fusion#readme",