@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
@@ -5762,6 +5762,7 @@ __export(project_exports, {
5762
5762
  unplannedExecutionBlocks: () => unplannedExecutionBlocks,
5763
5763
  usageEvents: () => usageEvents,
5764
5764
  verificationCache: () => verificationCache,
5765
+ workflowAgentCapacityLeases: () => workflowAgentCapacityLeases,
5765
5766
  workflowPromptOverrides: () => workflowPromptOverrides,
5766
5767
  workflowRunBranches: () => workflowRunBranches,
5767
5768
  workflowRunStepInstances: () => workflowRunStepInstances,
@@ -5870,6 +5871,8 @@ var tasks = projectSchema.table("tasks", {
5870
5871
  nextRecoveryAt: text("next_recovery_at"),
5871
5872
  error: text("error"),
5872
5873
  summary: text("summary"),
5874
+ // FNXC:TaskRecommendations 2026-08-08-05:02: structured completion suggestions remain project-scoped JSONB beside the source task.
5875
+ recommendations: jsonb("recommendations"),
5873
5876
  thinkingLevel: text("thinking_level"),
5874
5877
  // FNXC:Settings-ThinkingLevel 2026-07-13 (merge port): validator/planning reasoning-effort overrides.
5875
5878
  validatorThinkingLevel: text("validator_thinking_level"),
@@ -6450,7 +6453,9 @@ var agents = projectSchema.table("agents", {
6450
6453
  projectId: text("project_id").notNull().default(sql`current_setting('fusion.project_id', true)`),
6451
6454
  id: text("id").notNull(),
6452
6455
  name: text("name").notNull(),
6456
+ /** Deprecated singular compatibility column; canonical roles live in roles JSONB. */
6453
6457
  role: text("role").notNull(),
6458
+ roles: jsonb("roles").notNull().default([]),
6454
6459
  state: text("state").notNull().default("idle"),
6455
6460
  taskId: text("task_id"),
6456
6461
  createdAt: text("created_at").notNull(),
@@ -6574,6 +6579,17 @@ var completionHandoffMarkers = projectSchema.table("completion_handoff_markers",
6574
6579
  foreignKey({ columns: [t.projectId, t.taskId], foreignColumns: [tasks.projectId, tasks.id] }).onDelete("cascade"),
6575
6580
  index("idx_completion_handoff_markers_acceptedAt").on(t.acceptedAt)
6576
6581
  ]);
6582
+ var workflowAgentCapacityLeases = projectSchema.table("workflow_agent_capacity_leases", {
6583
+ projectId: text("project_id").notNull().default(sql`current_setting('fusion.project_id', true)`),
6584
+ attemptId: text("attempt_id").notNull(),
6585
+ agentId: text("agent_id").notNull(),
6586
+ createdAt: text("created_at").notNull(),
6587
+ // 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.
6588
+ expiresAt: text("expires_at").notNull()
6589
+ }, (t) => [
6590
+ primaryKey({ columns: [t.projectId, t.attemptId] }),
6591
+ index("idx_workflow_agent_capacity_leases_agent").on(t.projectId, t.agentId)
6592
+ ]);
6577
6593
  var workflowWorkItems = projectSchema.table("workflow_work_items", {
6578
6594
  projectId: text("project_id").notNull().default(sql`current_setting('fusion.project_id', true)`),
6579
6595
  id: text("id").notNull(),
@@ -6594,6 +6610,13 @@ var workflowWorkItems = projectSchema.table("workflow_work_items", {
6594
6610
  sourceColumn: text("source_column"),
6595
6611
  targetColumn: text("target_column"),
6596
6612
  irHash: text("ir_hash"),
6613
+ // FNXC:WorkflowAgentRouting 2026-08-07-03:25:
6614
+ // A workflow claim fences its durable principal and exact template instance.
6615
+ // Session retries must reuse this identity rather than re-routing a task.
6616
+ principalAgentId: text("principal_agent_id"),
6617
+ workflowRole: text("workflow_role"),
6618
+ authorityKind: text("authority_kind"),
6619
+ nodeInstanceId: text("node_instance_id"),
6597
6620
  createdAt: text("created_at").notNull(),
6598
6621
  updatedAt: text("updated_at").notNull()
6599
6622
  }, (t) => [
@@ -8589,6 +8612,7 @@ var BLOCKING_TASK_STATUSES = /* @__PURE__ */ new Set([
8589
8612
  // ../core/src/tasks/in-review-stall.ts
8590
8613
  var DEFAULT_STALE_MERGING_MIN_AGE_MS = 5 * 6e4;
8591
8614
  var DEFAULT_MAX_AUTO_MERGE_RETRIES = 3;
8615
+ var CONSECUTIVE_TOOL_FAILURE_RETRY_THRESHOLD = 1;
8592
8616
 
8593
8617
  // ../core/src/config/settings-schema.ts
8594
8618
  var DEFAULT_GLOBAL_SETTINGS = {
@@ -8929,6 +8953,8 @@ var DEFAULT_GLOBAL_SETTINGS = {
8929
8953
  cliAgents: {}
8930
8954
  };
8931
8955
  var DEFAULT_PROJECT_SETTINGS = {
8956
+ // FNXC:TaskRecommendations 2026-08-08-05:02: completion follows-ups stay bounded by default; 0 disables writing them.
8957
+ maxRecommendationsPerTask: 3,
8932
8958
  globalPause: false,
8933
8959
  globalPauseReason: void 0,
8934
8960
  defaultWorkflowId: void 0,
@@ -9097,9 +9123,15 @@ var DEFAULT_PROJECT_SETTINGS = {
9097
9123
  * 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.
9098
9124
  */
9099
9125
  maxAutoMergeRetries: DEFAULT_MAX_AUTO_MERGE_RETRIES,
9126
+ /*
9127
+ FNXC:ExecutorToolFailureRetry 2026-08-06-14:56:
9128
+ Fresh projects retry the existing bounded same-model continuation after one
9129
+ terminal tool error. Persisted project values are not migrated, so explicit
9130
+ operator thresholds remain authoritative.
9131
+ */
9100
9132
  executorToolFailureRetryCount: 2,
9101
9133
  executorToolFailureRetryBackoffMs: 2e3,
9102
- executorToolFailureThreshold: 3,
9134
+ executorToolFailureThreshold: CONSECUTIVE_TOOL_FAILURE_RETRY_THRESHOLD,
9103
9135
  executorModelEscalationEnabled: false,
9104
9136
  executorEscalationProvider: void 0,
9105
9137
  executorEscalationModelId: void 0,
@@ -9135,6 +9167,12 @@ var DEFAULT_PROJECT_SETTINGS = {
9135
9167
  // proportional to the change; the thin merge gate carries cross-cutting
9136
9168
  // coverage. Falls back to package/explicit command when no tests resolve.
9137
9169
  scopeVerificationToChangedFiles: true,
9170
+ /*
9171
+ FNXC:WorkflowAgentRouting 2026-08-07-08:45:
9172
+ Keep the legacy project setting defaulted and persistable for existing clients and
9173
+ configuration records. Workflow-stage routing must ignore its value; durable role
9174
+ principals own scheduler, executor, and mission-stage authority.
9175
+ */
9138
9176
  ephemeralAgentsEnabled: true,
9139
9177
  /*
9140
9178
  FNXC:EphemeralAgentTaskCreation 2026-07-01-00:00:
@@ -9436,6 +9474,23 @@ function isEphemeralAgent(agent) {
9436
9474
  }
9437
9475
 
9438
9476
  // ../core/src/types/agents/agents.ts
9477
+ var AGENT_CAPABILITIES = [
9478
+ "triage",
9479
+ "executor",
9480
+ "reviewer",
9481
+ "merger",
9482
+ "scheduler",
9483
+ "engineer",
9484
+ "custom"
9485
+ ];
9486
+ function normalizeAgentRoles(roles, legacyRole) {
9487
+ const values = roles?.length ? roles : legacyRole ? [legacyRole] : [];
9488
+ const unique2 = new Set(values);
9489
+ const normalized = AGENT_CAPABILITIES.filter((role) => unique2.has(role));
9490
+ if (normalized.length !== unique2.size) throw new Error("Agent roles contain an unknown capability");
9491
+ if (normalized.length === 0) throw new Error("Agent requires at least one role");
9492
+ return normalized;
9493
+ }
9439
9494
  var AGENT_PERMISSIONS = [
9440
9495
  "tasks:assign",
9441
9496
  // Assign tasks to agents
@@ -9557,6 +9612,7 @@ function getDefaultHeartbeatProcedurePath(agentId, agentName) {
9557
9612
  function agentToConfigSnapshot(agent) {
9558
9613
  return {
9559
9614
  name: agent.name,
9615
+ roles: [...agent.roles],
9560
9616
  role: agent.role,
9561
9617
  title: agent.title,
9562
9618
  icon: agent.icon,
@@ -9891,6 +9947,41 @@ function resolveColumnFlags(column) {
9891
9947
  return getTraitRegistry().resolveColumnFlags(column);
9892
9948
  }
9893
9949
 
9950
+ // ../core/src/workflows/workflow-ir-types.ts
9951
+ var WORKFLOW_AGENT_ROLES = ["triage", "executor", "reviewer", "merger"];
9952
+ function isWorkflowAgentRole(value) {
9953
+ return typeof value === "string" && WORKFLOW_AGENT_ROLES.includes(value);
9954
+ }
9955
+ function classifyWorkflowAgentNode(node) {
9956
+ if (node.kind === "step-review") return "reviewer";
9957
+ if (node.kind !== "prompt" && node.kind !== "script") return void 0;
9958
+ switch (node.config?.seam) {
9959
+ /*
9960
+ * FNXC:WorkflowAgentRouting 2026-08-07-05:52:
9961
+ * Planning, independent review, and promotion seams each launch work under
9962
+ * their permanent stage owner. Classify those production seams here rather
9963
+ * than treating their handoff worker as an unowned lifecycle operation.
9964
+ * Only pure handoff/scheduling seams remain principal-free.
9965
+ */
9966
+ case "planning":
9967
+ return "triage";
9968
+ case "execute":
9969
+ case "step-execute":
9970
+ return "executor";
9971
+ case "review":
9972
+ return "reviewer";
9973
+ case "merge":
9974
+ return "merger";
9975
+ case "review-handoff":
9976
+ case "schedule":
9977
+ return void 0;
9978
+ default: {
9979
+ const explicit = node.config?.workflowRole;
9980
+ return isWorkflowAgentRole(explicit) ? explicit : void 0;
9981
+ }
9982
+ }
9983
+ }
9984
+
9894
9985
  // ../core/src/workflows/workflow-extension-types.ts
9895
9986
  function workflowExtensionRegistryId(pluginId, extensionId) {
9896
9987
  return `plugin:${pluginId}:${extensionId}`;
@@ -10879,6 +10970,20 @@ function validateCredentialInstanceIdConfig(nodes2) {
10879
10970
  if (Array.isArray(templateNodes)) validateCredentialInstanceIdConfig(templateNodes);
10880
10971
  }
10881
10972
  }
10973
+ function validateReviewerAgentOverrides(nodes2) {
10974
+ for (const node of nodes2) {
10975
+ if (node.reviewerAgentId !== void 0) {
10976
+ if (typeof node.reviewerAgentId !== "string" || !node.reviewerAgentId.trim()) {
10977
+ throw new WorkflowIrError(`Workflow node '${node.id}' reviewerAgentId must be a non-empty string`);
10978
+ }
10979
+ if (classifyWorkflowAgentNode(node) !== "reviewer") {
10980
+ throw new WorkflowIrError(`Workflow node '${node.id}' reviewerAgentId is only legal on reviewer-session nodes`);
10981
+ }
10982
+ }
10983
+ const templateNodes = node.config?.template?.nodes;
10984
+ if (Array.isArray(templateNodes)) validateReviewerAgentOverrides(templateNodes);
10985
+ }
10986
+ }
10882
10987
  function validateThinkingLevelConfig(nodes2) {
10883
10988
  for (const node of nodes2) {
10884
10989
  const value = node.config?.thinkingLevel;
@@ -11494,6 +11599,7 @@ function validateV2(ir) {
11494
11599
  validateStepExecutePlacement(ir.nodes);
11495
11600
  validateThinkingLevelConfig(ir.nodes);
11496
11601
  validateCredentialInstanceIdConfig(ir.nodes);
11602
+ validateReviewerAgentOverrides(ir.nodes);
11497
11603
  for (const node of ir.nodes) {
11498
11604
  if (node.kind === "foreach") validateForeach(node, topLevelIds, columnIds);
11499
11605
  if (node.kind === "loop") validateLoop(node, topLevelIds, columnIds);
@@ -15727,7 +15833,8 @@ function normalizePermissions(raw) {
15727
15833
  return permissions;
15728
15834
  }
15729
15835
  function computeAccessState(agent) {
15730
- const roleDefaultPermissions = new Set(ROLE_DEFAULT_PERMISSIONS[agent.role] ?? []);
15836
+ const roles = agent.roles?.length ? agent.roles : [agent.role];
15837
+ const roleDefaultPermissions = new Set(roles.flatMap((role) => ROLE_DEFAULT_PERMISSIONS[role] ?? []));
15731
15838
  const explicitPermissions = normalizePermissions(agent.permissions ?? {});
15732
15839
  const resolvedPermissions = new Set(roleDefaultPermissions);
15733
15840
  for (const permission of explicitPermissions) {
@@ -15758,6 +15865,9 @@ var IMPLEMENTATION_TASK_COLUMNS = /* @__PURE__ */ new Set([
15758
15865
  "in-progress",
15759
15866
  "in-review"
15760
15867
  ]);
15868
+ function agentRoles(agent) {
15869
+ return agent.roles?.length ? agent.roles : agent.role ? [agent.role] : [];
15870
+ }
15761
15871
  function getAgentAssignmentPolicy(agent) {
15762
15872
  const raw = (agent.runtimeConfig ?? {})["assignmentPolicy"];
15763
15873
  return raw === "explicit-only" || raw === "none" ? raw : "auto";
@@ -15772,10 +15882,10 @@ function isImplementationTask(task) {
15772
15882
  return IMPLEMENTATION_TASK_COLUMNS.has(task.column);
15773
15883
  }
15774
15884
  function isExecutorRoleAgent(agent) {
15775
- return agent.role === "executor";
15885
+ return agentRoles(agent).includes("executor");
15776
15886
  }
15777
15887
  function isEngineerRoleAgent(agent) {
15778
- return agent.role === "engineer";
15888
+ return agentRoles(agent).includes("engineer");
15779
15889
  }
15780
15890
  function canAgentTakeImplementationTaskForExplicitRouting(agent, task) {
15781
15891
  if (!isImplementationTask(task)) return true;
@@ -15824,7 +15934,8 @@ function formatRoleMismatchReason(agent, task) {
15824
15934
  if (policy !== "auto") {
15825
15935
  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"}.`;
15826
15936
  }
15827
- 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.`;
15937
+ const roles = agentRoles(agent);
15938
+ 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.`;
15828
15939
  }
15829
15940
 
15830
15941
  // ../core/src/agents/agent-permission-policy.ts
@@ -15946,6 +16057,7 @@ var agentColumns = {
15946
16057
  id: project_exports.agents.id,
15947
16058
  name: project_exports.agents.name,
15948
16059
  role: project_exports.agents.role,
16060
+ roles: project_exports.agents.roles,
15949
16061
  state: project_exports.agents.state,
15950
16062
  taskId: project_exports.agents.taskId,
15951
16063
  createdAt: project_exports.agents.createdAt,
@@ -15976,6 +16088,7 @@ function agentToData(agent) {
15976
16088
  return {
15977
16089
  id: agent.id,
15978
16090
  name: agent.name,
16091
+ roles: agent.roles,
15979
16092
  role: agent.role,
15980
16093
  state: agent.state,
15981
16094
  taskId: agent.taskId,
@@ -16010,6 +16123,7 @@ async function writeAgent(handle, agent, projectId) {
16010
16123
  id: agent.id,
16011
16124
  name: agent.name,
16012
16125
  role: agent.role,
16126
+ roles: agent.roles,
16013
16127
  state: agent.state,
16014
16128
  taskId: agent.taskId ?? null,
16015
16129
  createdAt: agent.createdAt,
@@ -16022,6 +16136,7 @@ async function writeAgent(handle, agent, projectId) {
16022
16136
  set: {
16023
16137
  name: agent.name,
16024
16138
  role: agent.role,
16139
+ roles: agent.roles,
16025
16140
  state: agent.state,
16026
16141
  taskId: agent.taskId ?? null,
16027
16142
  updatedAt: agent.updatedAt,
@@ -16043,6 +16158,7 @@ function mergeAgentRow(row) {
16043
16158
  ...data,
16044
16159
  id: row.id,
16045
16160
  name: row.name,
16161
+ roles: row.roles?.length ? row.roles : [row.role],
16046
16162
  role: row.role,
16047
16163
  state: row.state,
16048
16164
  taskId: row.taskId ?? void 0,
@@ -16372,8 +16488,62 @@ var FsWatchPollController = class {
16372
16488
  }
16373
16489
  };
16374
16490
 
16491
+ // ../core/src/agents/workflow-role-agent-defaults.ts
16492
+ var BUILTIN_WORKFLOW_AGENT_BUNDLE_CONFIG = {
16493
+ mode: "managed",
16494
+ entryFile: "AGENTS.md",
16495
+ files: ["AGENTS.md", "soul.md"]
16496
+ };
16497
+ var BUILTIN_WORKFLOW_ROLE_AGENT_DEFAULTS = {
16498
+ triage: {
16499
+ role: "triage",
16500
+ name: "Workflow Planner",
16501
+ title: "Built-in workflow planning owner",
16502
+ 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.",
16503
+ soul: "Calm, structured, and curious. You reduce ambiguity without adding ceremony, respect operator intent, and prefer concrete evidence over confident guesses."
16504
+ },
16505
+ executor: {
16506
+ role: "executor",
16507
+ name: "Workflow Executor",
16508
+ title: "Built-in workflow execution owner",
16509
+ 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.",
16510
+ soul: "Methodical, practical, and accountable. You favor small safe steps, protect user work, and let tests and observable behavior guide your confidence."
16511
+ },
16512
+ reviewer: {
16513
+ role: "reviewer",
16514
+ name: "Workflow Reviewer",
16515
+ title: "Built-in workflow review owner",
16516
+ 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.",
16517
+ 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."
16518
+ },
16519
+ merger: {
16520
+ role: "merger",
16521
+ name: "Workflow Merger",
16522
+ title: "Built-in workflow merge owner",
16523
+ 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.",
16524
+ soul: "Deliberate, conservative, and transparent. You optimize for repository integrity, treat uncertainty as a reason to stop, and communicate operational state plainly."
16525
+ }
16526
+ };
16527
+ var BUILTIN_WORKFLOW_ROLE_AGENT_DEFAULT_LIST = Object.values(BUILTIN_WORKFLOW_ROLE_AGENT_DEFAULTS);
16528
+
16375
16529
  // ../core/src/agents/agent-store.ts
16376
16530
  var agentStoreLog = createLogger("agent-store");
16531
+ function isCanonicalOrPartialBuiltinWorkflowBundle(config2) {
16532
+ if (config2?.mode !== "managed" || config2.entryFile !== "AGENTS.md") return false;
16533
+ return config2.files.every((file) => (file === "AGENTS.md" || file === "soul.md") && config2.files.indexOf(file) === config2.files.lastIndexOf(file));
16534
+ }
16535
+ function isCompleteBuiltinWorkflowBundle(config2) {
16536
+ return isCanonicalOrPartialBuiltinWorkflowBundle(config2) && config2.files.length === BUILTIN_WORKFLOW_AGENT_BUNDLE_CONFIG.files.length;
16537
+ }
16538
+ function compareBuiltinWorkflowOwnerAge(a, b) {
16539
+ const aTime = Date.parse(a.createdAt);
16540
+ const bTime = Date.parse(b.createdAt);
16541
+ const aValid = Number.isFinite(aTime);
16542
+ const bValid = Number.isFinite(bTime);
16543
+ if (aValid && bValid && aTime !== bTime) return aTime - bTime;
16544
+ if (aValid !== bValid) return aValid ? -1 : 1;
16545
+ return a.id.localeCompare(b.id);
16546
+ }
16377
16547
  var DEFAULT_AGENT_HEARTBEAT_INTERVAL_MS = 36e5;
16378
16548
  function normalizeStoredPermissions(raw) {
16379
16549
  if (!raw) return void 0;
@@ -16484,6 +16654,73 @@ var AgentStore = class _AgentStore extends EventEmitter {
16484
16654
  }
16485
16655
  return projectId;
16486
16656
  }
16657
+ /** The durable project partition used by workflow routing and capacity leases. */
16658
+ get workflowProjectId() {
16659
+ return this.asyncLayer?.projectId;
16660
+ }
16661
+ /**
16662
+ * FNXC:WorkflowAgentRouting 2026-08-07-05:52:
16663
+ * Workflow capacity is a PostgreSQL lease rather than a process-local count.
16664
+ * The project advisory lock serializes project then agent admission across
16665
+ * engine processes; model work starts only after this short transaction ends.
16666
+ */
16667
+ async acquireWorkflowSessionCapacity(input) {
16668
+ if (!this.asyncLayer) return "acquired";
16669
+ const projectId = this.backendProjectId;
16670
+ const now = /* @__PURE__ */ new Date();
16671
+ const expiresAt = new Date(now.getTime() + (input.leaseDurationMs ?? 10 * 6e4)).toISOString();
16672
+ return this.asyncLayer.transactionImmediate(async (tx) => {
16673
+ await tx.execute(sql`SELECT pg_advisory_xact_lock(hashtext(${projectId}), hashtext('workflow-agent-capacity'))`);
16674
+ await tx.delete(project_exports.workflowAgentCapacityLeases).where(and(
16675
+ eq(project_exports.workflowAgentCapacityLeases.projectId, projectId),
16676
+ lte(project_exports.workflowAgentCapacityLeases.expiresAt, now.toISOString())
16677
+ ));
16678
+ const existing = await tx.select({ attemptId: project_exports.workflowAgentCapacityLeases.attemptId }).from(project_exports.workflowAgentCapacityLeases).where(and(
16679
+ eq(project_exports.workflowAgentCapacityLeases.projectId, projectId),
16680
+ eq(project_exports.workflowAgentCapacityLeases.attemptId, input.attemptId)
16681
+ )).limit(1);
16682
+ if (existing[0]) return "acquired";
16683
+ const projectCount = await tx.select({ count: sql`count(*)::int` }).from(project_exports.workflowAgentCapacityLeases).where(eq(project_exports.workflowAgentCapacityLeases.projectId, projectId));
16684
+ if (input.maxProjectSessions !== void 0 && (projectCount[0]?.count ?? 0) >= input.maxProjectSessions) return "project-capacity";
16685
+ const agentCount = await tx.select({ count: sql`count(*)::int` }).from(project_exports.workflowAgentCapacityLeases).where(and(
16686
+ eq(project_exports.workflowAgentCapacityLeases.projectId, projectId),
16687
+ eq(project_exports.workflowAgentCapacityLeases.agentId, input.agentId)
16688
+ ));
16689
+ if (input.maxAgentSessions !== void 0 && (agentCount[0]?.count ?? 0) >= input.maxAgentSessions) return "agent-capacity";
16690
+ await tx.insert(project_exports.workflowAgentCapacityLeases).values({
16691
+ projectId,
16692
+ agentId: input.agentId,
16693
+ attemptId: input.attemptId,
16694
+ createdAt: now.toISOString(),
16695
+ expiresAt
16696
+ });
16697
+ return "acquired";
16698
+ });
16699
+ }
16700
+ /** Renew only the caller's project-scoped attempt; a reclaimed lease never resurrects. */
16701
+ async renewWorkflowSessionCapacity(attemptId, leaseDurationMs = 10 * 6e4) {
16702
+ if (!this.asyncLayer) return true;
16703
+ const now = /* @__PURE__ */ new Date();
16704
+ const result = await this.asyncLayer.db.update(project_exports.workflowAgentCapacityLeases).set({ expiresAt: new Date(now.getTime() + leaseDurationMs).toISOString() }).where(and(
16705
+ eq(project_exports.workflowAgentCapacityLeases.projectId, this.backendProjectId),
16706
+ eq(project_exports.workflowAgentCapacityLeases.attemptId, attemptId),
16707
+ /*
16708
+ * FNXC:WorkflowAgentRouting 2026-08-07-07:30:
16709
+ * Renew a currently live lease only. An expired row may already have
16710
+ * been reclaimed by another engine's admission transaction.
16711
+ */
16712
+ gt(project_exports.workflowAgentCapacityLeases.expiresAt, now.toISOString())
16713
+ )).returning({ attemptId: project_exports.workflowAgentCapacityLeases.attemptId });
16714
+ return result.length > 0;
16715
+ }
16716
+ /** Release is project-scoped and idempotent so cancellation and recovery may race safely. */
16717
+ async releaseWorkflowSessionCapacity(attemptId) {
16718
+ if (!this.asyncLayer) return;
16719
+ await this.asyncLayer.db.delete(project_exports.workflowAgentCapacityLeases).where(and(
16720
+ eq(project_exports.workflowAgentCapacityLeases.projectId, this.backendProjectId),
16721
+ eq(project_exports.workflowAgentCapacityLeases.attemptId, attemptId)
16722
+ ));
16723
+ }
16487
16724
  get db() {
16488
16725
  throw new Error("SQLite Database is not available in backend mode (asyncLayer injected)");
16489
16726
  }
@@ -16498,7 +16735,7 @@ var AgentStore = class _AgentStore extends EventEmitter {
16498
16735
  */
16499
16736
  async init() {
16500
16737
  await mkdir(this.agentsDir, { recursive: true });
16501
- return;
16738
+ await this.provisionBuiltinWorkflowRoleAgents();
16502
16739
  }
16503
16740
  /**
16504
16741
  * One-shot migration that re-points every non-ephemeral agent off the
@@ -16660,8 +16897,8 @@ var AgentStore = class _AgentStore extends EventEmitter {
16660
16897
  * @param name - Agent name to match exactly
16661
16898
  * @returns Matching non-ephemeral agent, or null when none exists
16662
16899
  */
16663
- async findAgentByName(name) {
16664
- const agents2 = await findAgentRowsByName(this.asyncLayer.db, name);
16900
+ async findAgentByName(name, executor) {
16901
+ const agents2 = await findAgentRowsByName(executor ?? this.asyncLayer.db, name);
16665
16902
  for (const agent of agents2) {
16666
16903
  if (!isEphemeralAgent(agent)) {
16667
16904
  return this.parseAgent(agent);
@@ -16696,18 +16933,16 @@ var AgentStore = class _AgentStore extends EventEmitter {
16696
16933
  * @returns The created agent
16697
16934
  * @throws Error if input is invalid or a duplicate non-ephemeral name exists
16698
16935
  */
16699
- async createAgent(input) {
16936
+ async createAgent(input, executor) {
16700
16937
  if (!input.name?.trim()) {
16701
16938
  throw new Error("Agent name is required");
16702
16939
  }
16703
- if (!input.role) {
16704
- throw new Error("Agent role is required");
16705
- }
16940
+ const roles = normalizeAgentRoles(input.roles, input.role);
16706
16941
  const normalizedName = input.name.trim();
16707
16942
  const metadata = input.metadata ?? {};
16708
- const ephemeral = isEphemeralAgent({ metadata, name: input.name, role: input.role, reportsTo: input.reportsTo });
16943
+ const ephemeral = isEphemeralAgent({ metadata, name: input.name, role: roles[0], reportsTo: input.reportsTo });
16709
16944
  if (!ephemeral) {
16710
- const existing = await this.findAgentByName(normalizedName);
16945
+ const existing = await this.findAgentByName(normalizedName, executor);
16711
16946
  if (existing) {
16712
16947
  throw new Error(`Agent with name "${normalizedName}" already exists (agentId: ${existing.id})`);
16713
16948
  }
@@ -16720,7 +16955,8 @@ var AgentStore = class _AgentStore extends EventEmitter {
16720
16955
  const agent = {
16721
16956
  id: agentId,
16722
16957
  name: normalizedName,
16723
- role: input.role,
16958
+ roles,
16959
+ role: roles[0],
16724
16960
  // Non-ephemeral agents start active so they immediately participate in
16725
16961
  // heartbeat scheduling; ephemeral/task-worker agents start idle and are
16726
16962
  // activated by the engine when work is assigned.
@@ -16742,7 +16978,7 @@ var AgentStore = class _AgentStore extends EventEmitter {
16742
16978
  ...input.bundleConfig && { bundleConfig: input.bundleConfig },
16743
16979
  ...resolvedHeartbeatProcedurePath && { heartbeatProcedurePath: resolvedHeartbeatProcedurePath }
16744
16980
  };
16745
- await this.writeAgent(agent);
16981
+ await this.writeAgent(agent, executor);
16746
16982
  this.emit("agent:created", agent);
16747
16983
  return agent;
16748
16984
  }
@@ -17085,10 +17321,12 @@ var AgentStore = class _AgentStore extends EventEmitter {
17085
17321
  const beforeSnapshot = agentToConfigSnapshot(agent);
17086
17322
  const updatedAt = (/* @__PURE__ */ new Date()).toISOString();
17087
17323
  const normalizedUpdatedPermissionPolicy = "permissionPolicy" in updates && updates.permissionPolicy !== void 0 ? normalizeAgentPermissionPolicy(updates.permissionPolicy) : updates.permissionPolicy;
17324
+ const roles = updates.roles !== void 0 || updates.role !== void 0 ? normalizeAgentRoles(updates.roles, updates.role) : agent.roles;
17088
17325
  const updated = {
17089
17326
  ...agent,
17090
17327
  name: nextName ?? agent.name,
17091
- role: updates.role ?? agent.role,
17328
+ roles,
17329
+ role: roles[0],
17092
17330
  metadata: updates.metadata !== void 0 ? updates.metadata : agent.metadata,
17093
17331
  updatedAt,
17094
17332
  ..."title" in updates && { title: updates.title },
@@ -17623,12 +17861,81 @@ var AgentStore = class _AgentStore extends EventEmitter {
17623
17861
  * @param filter - Optional filter criteria
17624
17862
  * @returns Array of agents
17625
17863
  */
17626
- async listAgents(filter) {
17627
- const agents2 = await listAgentRows(this.asyncLayer.db, {
17628
- state: filter?.state,
17629
- role: filter?.role
17864
+ async listAgents(filter, executor) {
17865
+ const agents2 = await listAgentRows(executor ?? this.asyncLayer.db, { state: filter?.state });
17866
+ return agents2.map((a) => this.parseAgent(a)).filter((agent) => !filter?.role || agent.roles.includes(filter.role)).filter((agent) => filter?.includeEphemeral === true || !isEphemeralAgent(agent));
17867
+ }
17868
+ /**
17869
+ * Idempotently seed the permanent owners that route built-in workflow stages.
17870
+ * Matching operator-created agents are intentionally not reused: provenance
17871
+ * makes upgrade repair deterministic while operators remain free to add pool
17872
+ * members with the same tag.
17873
+ */
17874
+ async provisionBuiltinWorkflowRoleAgents() {
17875
+ const provision = async (executor) => {
17876
+ const existing = await this.listAgents({ includeEphemeral: true }, executor);
17877
+ const supportedRoles = new Set(BUILTIN_WORKFLOW_ROLE_AGENT_DEFAULT_LIST.map(({ role }) => role));
17878
+ const groups = /* @__PURE__ */ new Map();
17879
+ for (const agent of existing) {
17880
+ const role = agent.metadata?.workflowRole;
17881
+ if (agent.metadata?.builtInWorkflowRole === true && typeof role === "string" && supportedRoles.has(role)) {
17882
+ const group = groups.get(role) ?? [];
17883
+ group.push(agent);
17884
+ groups.set(role, group);
17885
+ }
17886
+ }
17887
+ const winners = /* @__PURE__ */ new Map();
17888
+ for (const [role, group] of groups) {
17889
+ group.sort(compareBuiltinWorkflowOwnerAge);
17890
+ winners.set(role, group[0]);
17891
+ for (const loser of group.slice(1)) {
17892
+ const { builtInWorkflowRole: _builtIn, workflowRole: _role, ...metadata } = loser.metadata;
17893
+ await this.writeAgent({ ...loser, metadata, updatedAt: (/* @__PURE__ */ new Date()).toISOString() }, executor);
17894
+ }
17895
+ }
17896
+ const result = [];
17897
+ for (const definition of BUILTIN_WORKFLOW_ROLE_AGENT_DEFAULT_LIST) {
17898
+ let agent = winners.get(definition.role);
17899
+ if (!agent) {
17900
+ agent = await this.createAgent({
17901
+ name: definition.name,
17902
+ roles: [definition.role],
17903
+ title: definition.title,
17904
+ metadata: { builtInWorkflowRole: true, workflowRole: definition.role },
17905
+ runtimeConfig: { enabled: false },
17906
+ instructionsText: definition.instructionsText,
17907
+ soul: definition.soul,
17908
+ bundleConfig: { ...BUILTIN_WORKFLOW_AGENT_BUNDLE_CONFIG, files: [...BUILTIN_WORKFLOW_AGENT_BUNDLE_CONFIG.files] }
17909
+ }, executor);
17910
+ } else {
17911
+ const canonicalOrPartialBundle = isCanonicalOrPartialBuiltinWorkflowBundle(agent.bundleConfig);
17912
+ const customInstructions = Boolean(agent.instructionsPath?.trim()) || agent.bundleConfig?.mode === "external" || agent.bundleConfig !== void 0 && !canonicalOrPartialBundle || Boolean(agent.instructionsText?.trim()) && agent.instructionsText !== definition.instructionsText;
17913
+ const updates = {};
17914
+ if (!customInstructions && !agent.instructionsText?.trim()) updates.instructionsText = definition.instructionsText;
17915
+ if (!agent.soul?.trim()) updates.soul = definition.soul;
17916
+ if (!customInstructions && !isCompleteBuiltinWorkflowBundle(agent.bundleConfig)) {
17917
+ updates.bundleConfig = { ...BUILTIN_WORKFLOW_AGENT_BUNDLE_CONFIG, files: [...BUILTIN_WORKFLOW_AGENT_BUNDLE_CONFIG.files] };
17918
+ }
17919
+ if (Object.keys(updates).length > 0) {
17920
+ agent = { ...agent, ...updates, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
17921
+ await this.writeAgent(agent, executor);
17922
+ }
17923
+ }
17924
+ result.push(agent);
17925
+ }
17926
+ return result;
17927
+ };
17928
+ if (!this.asyncLayer) {
17929
+ const agents3 = await provision();
17930
+ await Promise.all(agents3.map((agent) => this.materializeBuiltinWorkflowRoleBundle(agent)));
17931
+ return agents3;
17932
+ }
17933
+ const agents2 = await this.asyncLayer.transactionImmediate(async (tx) => {
17934
+ await tx.execute(sql`SELECT pg_advisory_xact_lock(hashtext(${this.backendProjectId}), hashtext('builtin-workflow-role-provisioning'))`);
17935
+ return provision(tx);
17630
17936
  });
17631
- return agents2.map((a) => this.parseAgent(a)).filter((agent) => filter?.includeEphemeral === true || !isEphemeralAgent(agent));
17937
+ await Promise.all(agents2.map((agent) => this.materializeBuiltinWorkflowRoleBundle(agent)));
17938
+ return agents2;
17632
17939
  }
17633
17940
  /**
17634
17941
  * Create an API key for an agent.
@@ -18196,6 +18503,29 @@ var AgentStore = class _AgentStore extends EventEmitter {
18196
18503
  }
18197
18504
  return null;
18198
18505
  }
18506
+ /** Seed only missing or blank default-owned mirror files after the provisioning transaction commits. */
18507
+ async materializeBuiltinWorkflowRoleBundle(agent) {
18508
+ return this.withLock(agent.id, async () => {
18509
+ const role = agent.metadata?.workflowRole;
18510
+ const definition = BUILTIN_WORKFLOW_ROLE_AGENT_DEFAULT_LIST.find((candidate) => candidate.role === role);
18511
+ if (!definition || agent.metadata?.builtInWorkflowRole !== true) return;
18512
+ const config2 = agent.bundleConfig;
18513
+ if (!isCanonicalOrPartialBuiltinWorkflowBundle(config2)) return;
18514
+ if (agent.instructionsPath?.trim() || agent.instructionsText?.trim() && agent.instructionsText !== definition.instructionsText) return;
18515
+ const bundleDir = await this.resolveCompatibleBundleDir(agent.id, true);
18516
+ await mkdir(bundleDir, { recursive: true });
18517
+ for (const [file, content] of [["AGENTS.md", definition.instructionsText], ["soul.md", definition.soul]]) {
18518
+ const path = join(bundleDir, file);
18519
+ let current = "";
18520
+ try {
18521
+ current = await readFile(path, "utf-8");
18522
+ } catch (error) {
18523
+ if (error.code !== "ENOENT") throw error;
18524
+ }
18525
+ if (!current.trim()) await writeFile(path, content, "utf-8");
18526
+ }
18527
+ });
18528
+ }
18199
18529
  getCanonicalBundleDir(agent) {
18200
18530
  return join(this.agentsDir, getCanonicalAgentInstructionsBundleDirName(agent.name, agent.id));
18201
18531
  }
@@ -18318,6 +18648,7 @@ var AgentStore = class _AgentStore extends EventEmitter {
18318
18648
  ...data,
18319
18649
  id: row.id,
18320
18650
  name: row.name,
18651
+ roles: data.roles?.length ? data.roles : [row.role],
18321
18652
  role: row.role,
18322
18653
  state: row.state,
18323
18654
  taskId: row.taskId ?? void 0,
@@ -18351,7 +18682,8 @@ var AgentStore = class _AgentStore extends EventEmitter {
18351
18682
  return {
18352
18683
  id: data.id,
18353
18684
  name: data.name,
18354
- role: data.role,
18685
+ roles: normalizeAgentRoles(data.roles, data.role),
18686
+ role: normalizeAgentRoles(data.roles, data.role)[0],
18355
18687
  state: data.state,
18356
18688
  taskId: data.taskId,
18357
18689
  createdAt: data.createdAt,
@@ -18377,8 +18709,8 @@ var AgentStore = class _AgentStore extends EventEmitter {
18377
18709
  heartbeatProcedurePath: data.heartbeatProcedurePath
18378
18710
  };
18379
18711
  }
18380
- async writeAgent(agent) {
18381
- await writeAgent(this.asyncLayer.db, agent, this.asyncLayer.projectId);
18712
+ async writeAgent(agent, executor) {
18713
+ await writeAgent(executor ?? this.asyncLayer.db, agent, this.asyncLayer.projectId);
18382
18714
  return;
18383
18715
  }
18384
18716
  /**
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fusion-plugin-examples/droid-runtime",
3
- "version": "0.1.60-beta.2",
3
+ "version": "0.1.61-beta.0",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {