@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
@@ -5790,6 +5790,7 @@ __export(project_exports, {
5790
5790
  unplannedExecutionBlocks: () => unplannedExecutionBlocks,
5791
5791
  usageEvents: () => usageEvents,
5792
5792
  verificationCache: () => verificationCache,
5793
+ workflowAgentCapacityLeases: () => workflowAgentCapacityLeases,
5793
5794
  workflowPromptOverrides: () => workflowPromptOverrides,
5794
5795
  workflowRunBranches: () => workflowRunBranches,
5795
5796
  workflowRunStepInstances: () => workflowRunStepInstances,
@@ -5898,6 +5899,8 @@ var tasks = projectSchema.table("tasks", {
5898
5899
  nextRecoveryAt: text("next_recovery_at"),
5899
5900
  error: text("error"),
5900
5901
  summary: text("summary"),
5902
+ // FNXC:TaskRecommendations 2026-08-08-05:02: structured completion suggestions remain project-scoped JSONB beside the source task.
5903
+ recommendations: jsonb("recommendations"),
5901
5904
  thinkingLevel: text("thinking_level"),
5902
5905
  // FNXC:Settings-ThinkingLevel 2026-07-13 (merge port): validator/planning reasoning-effort overrides.
5903
5906
  validatorThinkingLevel: text("validator_thinking_level"),
@@ -6478,7 +6481,9 @@ var agents = projectSchema.table("agents", {
6478
6481
  projectId: text("project_id").notNull().default(sql`current_setting('fusion.project_id', true)`),
6479
6482
  id: text("id").notNull(),
6480
6483
  name: text("name").notNull(),
6484
+ /** Deprecated singular compatibility column; canonical roles live in roles JSONB. */
6481
6485
  role: text("role").notNull(),
6486
+ roles: jsonb("roles").notNull().default([]),
6482
6487
  state: text("state").notNull().default("idle"),
6483
6488
  taskId: text("task_id"),
6484
6489
  createdAt: text("created_at").notNull(),
@@ -6602,6 +6607,17 @@ var completionHandoffMarkers = projectSchema.table("completion_handoff_markers",
6602
6607
  foreignKey({ columns: [t.projectId, t.taskId], foreignColumns: [tasks.projectId, tasks.id] }).onDelete("cascade"),
6603
6608
  index("idx_completion_handoff_markers_acceptedAt").on(t.acceptedAt)
6604
6609
  ]);
6610
+ var workflowAgentCapacityLeases = projectSchema.table("workflow_agent_capacity_leases", {
6611
+ projectId: text("project_id").notNull().default(sql`current_setting('fusion.project_id', true)`),
6612
+ attemptId: text("attempt_id").notNull(),
6613
+ agentId: text("agent_id").notNull(),
6614
+ createdAt: text("created_at").notNull(),
6615
+ // 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.
6616
+ expiresAt: text("expires_at").notNull()
6617
+ }, (t) => [
6618
+ primaryKey({ columns: [t.projectId, t.attemptId] }),
6619
+ index("idx_workflow_agent_capacity_leases_agent").on(t.projectId, t.agentId)
6620
+ ]);
6605
6621
  var workflowWorkItems = projectSchema.table("workflow_work_items", {
6606
6622
  projectId: text("project_id").notNull().default(sql`current_setting('fusion.project_id', true)`),
6607
6623
  id: text("id").notNull(),
@@ -6622,6 +6638,13 @@ var workflowWorkItems = projectSchema.table("workflow_work_items", {
6622
6638
  sourceColumn: text("source_column"),
6623
6639
  targetColumn: text("target_column"),
6624
6640
  irHash: text("ir_hash"),
6641
+ // FNXC:WorkflowAgentRouting 2026-08-07-03:25:
6642
+ // A workflow claim fences its durable principal and exact template instance.
6643
+ // Session retries must reuse this identity rather than re-routing a task.
6644
+ principalAgentId: text("principal_agent_id"),
6645
+ workflowRole: text("workflow_role"),
6646
+ authorityKind: text("authority_kind"),
6647
+ nodeInstanceId: text("node_instance_id"),
6625
6648
  createdAt: text("created_at").notNull(),
6626
6649
  updatedAt: text("updated_at").notNull()
6627
6650
  }, (t) => [
@@ -8709,6 +8732,7 @@ var BLOCKING_TASK_STATUSES = /* @__PURE__ */ new Set([
8709
8732
  // ../core/src/tasks/in-review-stall.ts
8710
8733
  var DEFAULT_STALE_MERGING_MIN_AGE_MS = 5 * 6e4;
8711
8734
  var DEFAULT_MAX_AUTO_MERGE_RETRIES = 3;
8735
+ var CONSECUTIVE_TOOL_FAILURE_RETRY_THRESHOLD = 1;
8712
8736
 
8713
8737
  // ../core/src/config/settings-schema.ts
8714
8738
  var DEFAULT_GLOBAL_SETTINGS = {
@@ -9049,6 +9073,8 @@ var DEFAULT_GLOBAL_SETTINGS = {
9049
9073
  cliAgents: {}
9050
9074
  };
9051
9075
  var DEFAULT_PROJECT_SETTINGS = {
9076
+ // FNXC:TaskRecommendations 2026-08-08-05:02: completion follows-ups stay bounded by default; 0 disables writing them.
9077
+ maxRecommendationsPerTask: 3,
9052
9078
  globalPause: false,
9053
9079
  globalPauseReason: void 0,
9054
9080
  defaultWorkflowId: void 0,
@@ -9217,9 +9243,15 @@ var DEFAULT_PROJECT_SETTINGS = {
9217
9243
  * 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.
9218
9244
  */
9219
9245
  maxAutoMergeRetries: DEFAULT_MAX_AUTO_MERGE_RETRIES,
9246
+ /*
9247
+ FNXC:ExecutorToolFailureRetry 2026-08-06-14:56:
9248
+ Fresh projects retry the existing bounded same-model continuation after one
9249
+ terminal tool error. Persisted project values are not migrated, so explicit
9250
+ operator thresholds remain authoritative.
9251
+ */
9220
9252
  executorToolFailureRetryCount: 2,
9221
9253
  executorToolFailureRetryBackoffMs: 2e3,
9222
- executorToolFailureThreshold: 3,
9254
+ executorToolFailureThreshold: CONSECUTIVE_TOOL_FAILURE_RETRY_THRESHOLD,
9223
9255
  executorModelEscalationEnabled: false,
9224
9256
  executorEscalationProvider: void 0,
9225
9257
  executorEscalationModelId: void 0,
@@ -9255,6 +9287,12 @@ var DEFAULT_PROJECT_SETTINGS = {
9255
9287
  // proportional to the change; the thin merge gate carries cross-cutting
9256
9288
  // coverage. Falls back to package/explicit command when no tests resolve.
9257
9289
  scopeVerificationToChangedFiles: true,
9290
+ /*
9291
+ FNXC:WorkflowAgentRouting 2026-08-07-08:45:
9292
+ Keep the legacy project setting defaulted and persistable for existing clients and
9293
+ configuration records. Workflow-stage routing must ignore its value; durable role
9294
+ principals own scheduler, executor, and mission-stage authority.
9295
+ */
9258
9296
  ephemeralAgentsEnabled: true,
9259
9297
  /*
9260
9298
  FNXC:EphemeralAgentTaskCreation 2026-07-01-00:00:
@@ -9556,6 +9594,23 @@ function isEphemeralAgent(agent) {
9556
9594
  }
9557
9595
 
9558
9596
  // ../core/src/types/agents/agents.ts
9597
+ var AGENT_CAPABILITIES = [
9598
+ "triage",
9599
+ "executor",
9600
+ "reviewer",
9601
+ "merger",
9602
+ "scheduler",
9603
+ "engineer",
9604
+ "custom"
9605
+ ];
9606
+ function normalizeAgentRoles(roles, legacyRole) {
9607
+ const values = roles?.length ? roles : legacyRole ? [legacyRole] : [];
9608
+ const unique2 = new Set(values);
9609
+ const normalized = AGENT_CAPABILITIES.filter((role) => unique2.has(role));
9610
+ if (normalized.length !== unique2.size) throw new Error("Agent roles contain an unknown capability");
9611
+ if (normalized.length === 0) throw new Error("Agent requires at least one role");
9612
+ return normalized;
9613
+ }
9559
9614
  var AGENT_PERMISSIONS = [
9560
9615
  "tasks:assign",
9561
9616
  // Assign tasks to agents
@@ -9677,6 +9732,7 @@ function getDefaultHeartbeatProcedurePath(agentId, agentName) {
9677
9732
  function agentToConfigSnapshot(agent) {
9678
9733
  return {
9679
9734
  name: agent.name,
9735
+ roles: [...agent.roles],
9680
9736
  role: agent.role,
9681
9737
  title: agent.title,
9682
9738
  icon: agent.icon,
@@ -10011,6 +10067,41 @@ function resolveColumnFlags(column) {
10011
10067
  return getTraitRegistry().resolveColumnFlags(column);
10012
10068
  }
10013
10069
 
10070
+ // ../core/src/workflows/workflow-ir-types.ts
10071
+ var WORKFLOW_AGENT_ROLES = ["triage", "executor", "reviewer", "merger"];
10072
+ function isWorkflowAgentRole(value) {
10073
+ return typeof value === "string" && WORKFLOW_AGENT_ROLES.includes(value);
10074
+ }
10075
+ function classifyWorkflowAgentNode(node) {
10076
+ if (node.kind === "step-review") return "reviewer";
10077
+ if (node.kind !== "prompt" && node.kind !== "script") return void 0;
10078
+ switch (node.config?.seam) {
10079
+ /*
10080
+ * FNXC:WorkflowAgentRouting 2026-08-07-05:52:
10081
+ * Planning, independent review, and promotion seams each launch work under
10082
+ * their permanent stage owner. Classify those production seams here rather
10083
+ * than treating their handoff worker as an unowned lifecycle operation.
10084
+ * Only pure handoff/scheduling seams remain principal-free.
10085
+ */
10086
+ case "planning":
10087
+ return "triage";
10088
+ case "execute":
10089
+ case "step-execute":
10090
+ return "executor";
10091
+ case "review":
10092
+ return "reviewer";
10093
+ case "merge":
10094
+ return "merger";
10095
+ case "review-handoff":
10096
+ case "schedule":
10097
+ return void 0;
10098
+ default: {
10099
+ const explicit = node.config?.workflowRole;
10100
+ return isWorkflowAgentRole(explicit) ? explicit : void 0;
10101
+ }
10102
+ }
10103
+ }
10104
+
10014
10105
  // ../core/src/workflows/workflow-extension-types.ts
10015
10106
  function workflowExtensionRegistryId(pluginId, extensionId) {
10016
10107
  return `plugin:${pluginId}:${extensionId}`;
@@ -10999,6 +11090,20 @@ function validateCredentialInstanceIdConfig(nodes2) {
10999
11090
  if (Array.isArray(templateNodes)) validateCredentialInstanceIdConfig(templateNodes);
11000
11091
  }
11001
11092
  }
11093
+ function validateReviewerAgentOverrides(nodes2) {
11094
+ for (const node of nodes2) {
11095
+ if (node.reviewerAgentId !== void 0) {
11096
+ if (typeof node.reviewerAgentId !== "string" || !node.reviewerAgentId.trim()) {
11097
+ throw new WorkflowIrError(`Workflow node '${node.id}' reviewerAgentId must be a non-empty string`);
11098
+ }
11099
+ if (classifyWorkflowAgentNode(node) !== "reviewer") {
11100
+ throw new WorkflowIrError(`Workflow node '${node.id}' reviewerAgentId is only legal on reviewer-session nodes`);
11101
+ }
11102
+ }
11103
+ const templateNodes = node.config?.template?.nodes;
11104
+ if (Array.isArray(templateNodes)) validateReviewerAgentOverrides(templateNodes);
11105
+ }
11106
+ }
11002
11107
  function validateThinkingLevelConfig(nodes2) {
11003
11108
  for (const node of nodes2) {
11004
11109
  const value = node.config?.thinkingLevel;
@@ -11614,6 +11719,7 @@ function validateV2(ir) {
11614
11719
  validateStepExecutePlacement(ir.nodes);
11615
11720
  validateThinkingLevelConfig(ir.nodes);
11616
11721
  validateCredentialInstanceIdConfig(ir.nodes);
11722
+ validateReviewerAgentOverrides(ir.nodes);
11617
11723
  for (const node of ir.nodes) {
11618
11724
  if (node.kind === "foreach") validateForeach(node, topLevelIds, columnIds);
11619
11725
  if (node.kind === "loop") validateLoop(node, topLevelIds, columnIds);
@@ -15847,7 +15953,8 @@ function normalizePermissions(raw) {
15847
15953
  return permissions;
15848
15954
  }
15849
15955
  function computeAccessState(agent) {
15850
- const roleDefaultPermissions = new Set(ROLE_DEFAULT_PERMISSIONS[agent.role] ?? []);
15956
+ const roles = agent.roles?.length ? agent.roles : [agent.role];
15957
+ const roleDefaultPermissions = new Set(roles.flatMap((role) => ROLE_DEFAULT_PERMISSIONS[role] ?? []));
15851
15958
  const explicitPermissions = normalizePermissions(agent.permissions ?? {});
15852
15959
  const resolvedPermissions = new Set(roleDefaultPermissions);
15853
15960
  for (const permission of explicitPermissions) {
@@ -15878,6 +15985,9 @@ var IMPLEMENTATION_TASK_COLUMNS = /* @__PURE__ */ new Set([
15878
15985
  "in-progress",
15879
15986
  "in-review"
15880
15987
  ]);
15988
+ function agentRoles(agent) {
15989
+ return agent.roles?.length ? agent.roles : agent.role ? [agent.role] : [];
15990
+ }
15881
15991
  function getAgentAssignmentPolicy(agent) {
15882
15992
  const raw = (agent.runtimeConfig ?? {})["assignmentPolicy"];
15883
15993
  return raw === "explicit-only" || raw === "none" ? raw : "auto";
@@ -15892,10 +16002,10 @@ function isImplementationTask(task) {
15892
16002
  return IMPLEMENTATION_TASK_COLUMNS.has(task.column);
15893
16003
  }
15894
16004
  function isExecutorRoleAgent(agent) {
15895
- return agent.role === "executor";
16005
+ return agentRoles(agent).includes("executor");
15896
16006
  }
15897
16007
  function isEngineerRoleAgent(agent) {
15898
- return agent.role === "engineer";
16008
+ return agentRoles(agent).includes("engineer");
15899
16009
  }
15900
16010
  function canAgentTakeImplementationTaskForExplicitRouting(agent, task) {
15901
16011
  if (!isImplementationTask(task)) return true;
@@ -15944,7 +16054,8 @@ function formatRoleMismatchReason(agent, task) {
15944
16054
  if (policy !== "auto") {
15945
16055
  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"}.`;
15946
16056
  }
15947
- 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.`;
16057
+ const roles = agentRoles(agent);
16058
+ 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.`;
15948
16059
  }
15949
16060
 
15950
16061
  // ../core/src/agents/agent-permission-policy.ts
@@ -16066,6 +16177,7 @@ var agentColumns = {
16066
16177
  id: project_exports.agents.id,
16067
16178
  name: project_exports.agents.name,
16068
16179
  role: project_exports.agents.role,
16180
+ roles: project_exports.agents.roles,
16069
16181
  state: project_exports.agents.state,
16070
16182
  taskId: project_exports.agents.taskId,
16071
16183
  createdAt: project_exports.agents.createdAt,
@@ -16096,6 +16208,7 @@ function agentToData(agent) {
16096
16208
  return {
16097
16209
  id: agent.id,
16098
16210
  name: agent.name,
16211
+ roles: agent.roles,
16099
16212
  role: agent.role,
16100
16213
  state: agent.state,
16101
16214
  taskId: agent.taskId,
@@ -16130,6 +16243,7 @@ async function writeAgent(handle, agent, projectId) {
16130
16243
  id: agent.id,
16131
16244
  name: agent.name,
16132
16245
  role: agent.role,
16246
+ roles: agent.roles,
16133
16247
  state: agent.state,
16134
16248
  taskId: agent.taskId ?? null,
16135
16249
  createdAt: agent.createdAt,
@@ -16142,6 +16256,7 @@ async function writeAgent(handle, agent, projectId) {
16142
16256
  set: {
16143
16257
  name: agent.name,
16144
16258
  role: agent.role,
16259
+ roles: agent.roles,
16145
16260
  state: agent.state,
16146
16261
  taskId: agent.taskId ?? null,
16147
16262
  updatedAt: agent.updatedAt,
@@ -16163,6 +16278,7 @@ function mergeAgentRow(row) {
16163
16278
  ...data,
16164
16279
  id: row.id,
16165
16280
  name: row.name,
16281
+ roles: row.roles?.length ? row.roles : [row.role],
16166
16282
  role: row.role,
16167
16283
  state: row.state,
16168
16284
  taskId: row.taskId ?? void 0,
@@ -16492,8 +16608,62 @@ var FsWatchPollController = class {
16492
16608
  }
16493
16609
  };
16494
16610
 
16611
+ // ../core/src/agents/workflow-role-agent-defaults.ts
16612
+ var BUILTIN_WORKFLOW_AGENT_BUNDLE_CONFIG = {
16613
+ mode: "managed",
16614
+ entryFile: "AGENTS.md",
16615
+ files: ["AGENTS.md", "soul.md"]
16616
+ };
16617
+ var BUILTIN_WORKFLOW_ROLE_AGENT_DEFAULTS = {
16618
+ triage: {
16619
+ role: "triage",
16620
+ name: "Workflow Planner",
16621
+ title: "Built-in workflow planning owner",
16622
+ 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.",
16623
+ soul: "Calm, structured, and curious. You reduce ambiguity without adding ceremony, respect operator intent, and prefer concrete evidence over confident guesses."
16624
+ },
16625
+ executor: {
16626
+ role: "executor",
16627
+ name: "Workflow Executor",
16628
+ title: "Built-in workflow execution owner",
16629
+ 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.",
16630
+ soul: "Methodical, practical, and accountable. You favor small safe steps, protect user work, and let tests and observable behavior guide your confidence."
16631
+ },
16632
+ reviewer: {
16633
+ role: "reviewer",
16634
+ name: "Workflow Reviewer",
16635
+ title: "Built-in workflow review owner",
16636
+ 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.",
16637
+ 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."
16638
+ },
16639
+ merger: {
16640
+ role: "merger",
16641
+ name: "Workflow Merger",
16642
+ title: "Built-in workflow merge owner",
16643
+ 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.",
16644
+ soul: "Deliberate, conservative, and transparent. You optimize for repository integrity, treat uncertainty as a reason to stop, and communicate operational state plainly."
16645
+ }
16646
+ };
16647
+ var BUILTIN_WORKFLOW_ROLE_AGENT_DEFAULT_LIST = Object.values(BUILTIN_WORKFLOW_ROLE_AGENT_DEFAULTS);
16648
+
16495
16649
  // ../core/src/agents/agent-store.ts
16496
16650
  var agentStoreLog = createLogger("agent-store");
16651
+ function isCanonicalOrPartialBuiltinWorkflowBundle(config2) {
16652
+ if (config2?.mode !== "managed" || config2.entryFile !== "AGENTS.md") return false;
16653
+ return config2.files.every((file) => (file === "AGENTS.md" || file === "soul.md") && config2.files.indexOf(file) === config2.files.lastIndexOf(file));
16654
+ }
16655
+ function isCompleteBuiltinWorkflowBundle(config2) {
16656
+ return isCanonicalOrPartialBuiltinWorkflowBundle(config2) && config2.files.length === BUILTIN_WORKFLOW_AGENT_BUNDLE_CONFIG.files.length;
16657
+ }
16658
+ function compareBuiltinWorkflowOwnerAge(a, b) {
16659
+ const aTime = Date.parse(a.createdAt);
16660
+ const bTime = Date.parse(b.createdAt);
16661
+ const aValid = Number.isFinite(aTime);
16662
+ const bValid = Number.isFinite(bTime);
16663
+ if (aValid && bValid && aTime !== bTime) return aTime - bTime;
16664
+ if (aValid !== bValid) return aValid ? -1 : 1;
16665
+ return a.id.localeCompare(b.id);
16666
+ }
16497
16667
  var DEFAULT_AGENT_HEARTBEAT_INTERVAL_MS = 36e5;
16498
16668
  function normalizeStoredPermissions(raw) {
16499
16669
  if (!raw) return void 0;
@@ -16604,6 +16774,73 @@ var AgentStore = class _AgentStore extends EventEmitter {
16604
16774
  }
16605
16775
  return projectId;
16606
16776
  }
16777
+ /** The durable project partition used by workflow routing and capacity leases. */
16778
+ get workflowProjectId() {
16779
+ return this.asyncLayer?.projectId;
16780
+ }
16781
+ /**
16782
+ * FNXC:WorkflowAgentRouting 2026-08-07-05:52:
16783
+ * Workflow capacity is a PostgreSQL lease rather than a process-local count.
16784
+ * The project advisory lock serializes project then agent admission across
16785
+ * engine processes; model work starts only after this short transaction ends.
16786
+ */
16787
+ async acquireWorkflowSessionCapacity(input) {
16788
+ if (!this.asyncLayer) return "acquired";
16789
+ const projectId = this.backendProjectId;
16790
+ const now = /* @__PURE__ */ new Date();
16791
+ const expiresAt = new Date(now.getTime() + (input.leaseDurationMs ?? 10 * 6e4)).toISOString();
16792
+ return this.asyncLayer.transactionImmediate(async (tx) => {
16793
+ await tx.execute(sql`SELECT pg_advisory_xact_lock(hashtext(${projectId}), hashtext('workflow-agent-capacity'))`);
16794
+ await tx.delete(project_exports.workflowAgentCapacityLeases).where(and(
16795
+ eq(project_exports.workflowAgentCapacityLeases.projectId, projectId),
16796
+ lte(project_exports.workflowAgentCapacityLeases.expiresAt, now.toISOString())
16797
+ ));
16798
+ const existing = await tx.select({ attemptId: project_exports.workflowAgentCapacityLeases.attemptId }).from(project_exports.workflowAgentCapacityLeases).where(and(
16799
+ eq(project_exports.workflowAgentCapacityLeases.projectId, projectId),
16800
+ eq(project_exports.workflowAgentCapacityLeases.attemptId, input.attemptId)
16801
+ )).limit(1);
16802
+ if (existing[0]) return "acquired";
16803
+ const projectCount = await tx.select({ count: sql`count(*)::int` }).from(project_exports.workflowAgentCapacityLeases).where(eq(project_exports.workflowAgentCapacityLeases.projectId, projectId));
16804
+ if (input.maxProjectSessions !== void 0 && (projectCount[0]?.count ?? 0) >= input.maxProjectSessions) return "project-capacity";
16805
+ const agentCount = await tx.select({ count: sql`count(*)::int` }).from(project_exports.workflowAgentCapacityLeases).where(and(
16806
+ eq(project_exports.workflowAgentCapacityLeases.projectId, projectId),
16807
+ eq(project_exports.workflowAgentCapacityLeases.agentId, input.agentId)
16808
+ ));
16809
+ if (input.maxAgentSessions !== void 0 && (agentCount[0]?.count ?? 0) >= input.maxAgentSessions) return "agent-capacity";
16810
+ await tx.insert(project_exports.workflowAgentCapacityLeases).values({
16811
+ projectId,
16812
+ agentId: input.agentId,
16813
+ attemptId: input.attemptId,
16814
+ createdAt: now.toISOString(),
16815
+ expiresAt
16816
+ });
16817
+ return "acquired";
16818
+ });
16819
+ }
16820
+ /** Renew only the caller's project-scoped attempt; a reclaimed lease never resurrects. */
16821
+ async renewWorkflowSessionCapacity(attemptId, leaseDurationMs = 10 * 6e4) {
16822
+ if (!this.asyncLayer) return true;
16823
+ const now = /* @__PURE__ */ new Date();
16824
+ const result = await this.asyncLayer.db.update(project_exports.workflowAgentCapacityLeases).set({ expiresAt: new Date(now.getTime() + leaseDurationMs).toISOString() }).where(and(
16825
+ eq(project_exports.workflowAgentCapacityLeases.projectId, this.backendProjectId),
16826
+ eq(project_exports.workflowAgentCapacityLeases.attemptId, attemptId),
16827
+ /*
16828
+ * FNXC:WorkflowAgentRouting 2026-08-07-07:30:
16829
+ * Renew a currently live lease only. An expired row may already have
16830
+ * been reclaimed by another engine's admission transaction.
16831
+ */
16832
+ gt(project_exports.workflowAgentCapacityLeases.expiresAt, now.toISOString())
16833
+ )).returning({ attemptId: project_exports.workflowAgentCapacityLeases.attemptId });
16834
+ return result.length > 0;
16835
+ }
16836
+ /** Release is project-scoped and idempotent so cancellation and recovery may race safely. */
16837
+ async releaseWorkflowSessionCapacity(attemptId) {
16838
+ if (!this.asyncLayer) return;
16839
+ await this.asyncLayer.db.delete(project_exports.workflowAgentCapacityLeases).where(and(
16840
+ eq(project_exports.workflowAgentCapacityLeases.projectId, this.backendProjectId),
16841
+ eq(project_exports.workflowAgentCapacityLeases.attemptId, attemptId)
16842
+ ));
16843
+ }
16607
16844
  get db() {
16608
16845
  throw new Error("SQLite Database is not available in backend mode (asyncLayer injected)");
16609
16846
  }
@@ -16618,7 +16855,7 @@ var AgentStore = class _AgentStore extends EventEmitter {
16618
16855
  */
16619
16856
  async init() {
16620
16857
  await mkdir(this.agentsDir, { recursive: true });
16621
- return;
16858
+ await this.provisionBuiltinWorkflowRoleAgents();
16622
16859
  }
16623
16860
  /**
16624
16861
  * One-shot migration that re-points every non-ephemeral agent off the
@@ -16780,8 +17017,8 @@ var AgentStore = class _AgentStore extends EventEmitter {
16780
17017
  * @param name - Agent name to match exactly
16781
17018
  * @returns Matching non-ephemeral agent, or null when none exists
16782
17019
  */
16783
- async findAgentByName(name) {
16784
- const agents2 = await findAgentRowsByName(this.asyncLayer.db, name);
17020
+ async findAgentByName(name, executor) {
17021
+ const agents2 = await findAgentRowsByName(executor ?? this.asyncLayer.db, name);
16785
17022
  for (const agent of agents2) {
16786
17023
  if (!isEphemeralAgent(agent)) {
16787
17024
  return this.parseAgent(agent);
@@ -16816,18 +17053,16 @@ var AgentStore = class _AgentStore extends EventEmitter {
16816
17053
  * @returns The created agent
16817
17054
  * @throws Error if input is invalid or a duplicate non-ephemeral name exists
16818
17055
  */
16819
- async createAgent(input) {
17056
+ async createAgent(input, executor) {
16820
17057
  if (!input.name?.trim()) {
16821
17058
  throw new Error("Agent name is required");
16822
17059
  }
16823
- if (!input.role) {
16824
- throw new Error("Agent role is required");
16825
- }
17060
+ const roles = normalizeAgentRoles(input.roles, input.role);
16826
17061
  const normalizedName = input.name.trim();
16827
17062
  const metadata = input.metadata ?? {};
16828
- const ephemeral = isEphemeralAgent({ metadata, name: input.name, role: input.role, reportsTo: input.reportsTo });
17063
+ const ephemeral = isEphemeralAgent({ metadata, name: input.name, role: roles[0], reportsTo: input.reportsTo });
16829
17064
  if (!ephemeral) {
16830
- const existing = await this.findAgentByName(normalizedName);
17065
+ const existing = await this.findAgentByName(normalizedName, executor);
16831
17066
  if (existing) {
16832
17067
  throw new Error(`Agent with name "${normalizedName}" already exists (agentId: ${existing.id})`);
16833
17068
  }
@@ -16840,7 +17075,8 @@ var AgentStore = class _AgentStore extends EventEmitter {
16840
17075
  const agent = {
16841
17076
  id: agentId,
16842
17077
  name: normalizedName,
16843
- role: input.role,
17078
+ roles,
17079
+ role: roles[0],
16844
17080
  // Non-ephemeral agents start active so they immediately participate in
16845
17081
  // heartbeat scheduling; ephemeral/task-worker agents start idle and are
16846
17082
  // activated by the engine when work is assigned.
@@ -16862,7 +17098,7 @@ var AgentStore = class _AgentStore extends EventEmitter {
16862
17098
  ...input.bundleConfig && { bundleConfig: input.bundleConfig },
16863
17099
  ...resolvedHeartbeatProcedurePath && { heartbeatProcedurePath: resolvedHeartbeatProcedurePath }
16864
17100
  };
16865
- await this.writeAgent(agent);
17101
+ await this.writeAgent(agent, executor);
16866
17102
  this.emit("agent:created", agent);
16867
17103
  return agent;
16868
17104
  }
@@ -17205,10 +17441,12 @@ var AgentStore = class _AgentStore extends EventEmitter {
17205
17441
  const beforeSnapshot = agentToConfigSnapshot(agent);
17206
17442
  const updatedAt = (/* @__PURE__ */ new Date()).toISOString();
17207
17443
  const normalizedUpdatedPermissionPolicy = "permissionPolicy" in updates && updates.permissionPolicy !== void 0 ? normalizeAgentPermissionPolicy(updates.permissionPolicy) : updates.permissionPolicy;
17444
+ const roles = updates.roles !== void 0 || updates.role !== void 0 ? normalizeAgentRoles(updates.roles, updates.role) : agent.roles;
17208
17445
  const updated = {
17209
17446
  ...agent,
17210
17447
  name: nextName ?? agent.name,
17211
- role: updates.role ?? agent.role,
17448
+ roles,
17449
+ role: roles[0],
17212
17450
  metadata: updates.metadata !== void 0 ? updates.metadata : agent.metadata,
17213
17451
  updatedAt,
17214
17452
  ..."title" in updates && { title: updates.title },
@@ -17743,12 +17981,81 @@ var AgentStore = class _AgentStore extends EventEmitter {
17743
17981
  * @param filter - Optional filter criteria
17744
17982
  * @returns Array of agents
17745
17983
  */
17746
- async listAgents(filter) {
17747
- const agents2 = await listAgentRows(this.asyncLayer.db, {
17748
- state: filter?.state,
17749
- role: filter?.role
17984
+ async listAgents(filter, executor) {
17985
+ const agents2 = await listAgentRows(executor ?? this.asyncLayer.db, { state: filter?.state });
17986
+ return agents2.map((a) => this.parseAgent(a)).filter((agent) => !filter?.role || agent.roles.includes(filter.role)).filter((agent) => filter?.includeEphemeral === true || !isEphemeralAgent(agent));
17987
+ }
17988
+ /**
17989
+ * Idempotently seed the permanent owners that route built-in workflow stages.
17990
+ * Matching operator-created agents are intentionally not reused: provenance
17991
+ * makes upgrade repair deterministic while operators remain free to add pool
17992
+ * members with the same tag.
17993
+ */
17994
+ async provisionBuiltinWorkflowRoleAgents() {
17995
+ const provision = async (executor) => {
17996
+ const existing = await this.listAgents({ includeEphemeral: true }, executor);
17997
+ const supportedRoles = new Set(BUILTIN_WORKFLOW_ROLE_AGENT_DEFAULT_LIST.map(({ role }) => role));
17998
+ const groups = /* @__PURE__ */ new Map();
17999
+ for (const agent of existing) {
18000
+ const role = agent.metadata?.workflowRole;
18001
+ if (agent.metadata?.builtInWorkflowRole === true && typeof role === "string" && supportedRoles.has(role)) {
18002
+ const group = groups.get(role) ?? [];
18003
+ group.push(agent);
18004
+ groups.set(role, group);
18005
+ }
18006
+ }
18007
+ const winners = /* @__PURE__ */ new Map();
18008
+ for (const [role, group] of groups) {
18009
+ group.sort(compareBuiltinWorkflowOwnerAge);
18010
+ winners.set(role, group[0]);
18011
+ for (const loser of group.slice(1)) {
18012
+ const { builtInWorkflowRole: _builtIn, workflowRole: _role, ...metadata } = loser.metadata;
18013
+ await this.writeAgent({ ...loser, metadata, updatedAt: (/* @__PURE__ */ new Date()).toISOString() }, executor);
18014
+ }
18015
+ }
18016
+ const result = [];
18017
+ for (const definition of BUILTIN_WORKFLOW_ROLE_AGENT_DEFAULT_LIST) {
18018
+ let agent = winners.get(definition.role);
18019
+ if (!agent) {
18020
+ agent = await this.createAgent({
18021
+ name: definition.name,
18022
+ roles: [definition.role],
18023
+ title: definition.title,
18024
+ metadata: { builtInWorkflowRole: true, workflowRole: definition.role },
18025
+ runtimeConfig: { enabled: false },
18026
+ instructionsText: definition.instructionsText,
18027
+ soul: definition.soul,
18028
+ bundleConfig: { ...BUILTIN_WORKFLOW_AGENT_BUNDLE_CONFIG, files: [...BUILTIN_WORKFLOW_AGENT_BUNDLE_CONFIG.files] }
18029
+ }, executor);
18030
+ } else {
18031
+ const canonicalOrPartialBundle = isCanonicalOrPartialBuiltinWorkflowBundle(agent.bundleConfig);
18032
+ const customInstructions = Boolean(agent.instructionsPath?.trim()) || agent.bundleConfig?.mode === "external" || agent.bundleConfig !== void 0 && !canonicalOrPartialBundle || Boolean(agent.instructionsText?.trim()) && agent.instructionsText !== definition.instructionsText;
18033
+ const updates = {};
18034
+ if (!customInstructions && !agent.instructionsText?.trim()) updates.instructionsText = definition.instructionsText;
18035
+ if (!agent.soul?.trim()) updates.soul = definition.soul;
18036
+ if (!customInstructions && !isCompleteBuiltinWorkflowBundle(agent.bundleConfig)) {
18037
+ updates.bundleConfig = { ...BUILTIN_WORKFLOW_AGENT_BUNDLE_CONFIG, files: [...BUILTIN_WORKFLOW_AGENT_BUNDLE_CONFIG.files] };
18038
+ }
18039
+ if (Object.keys(updates).length > 0) {
18040
+ agent = { ...agent, ...updates, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
18041
+ await this.writeAgent(agent, executor);
18042
+ }
18043
+ }
18044
+ result.push(agent);
18045
+ }
18046
+ return result;
18047
+ };
18048
+ if (!this.asyncLayer) {
18049
+ const agents3 = await provision();
18050
+ await Promise.all(agents3.map((agent) => this.materializeBuiltinWorkflowRoleBundle(agent)));
18051
+ return agents3;
18052
+ }
18053
+ const agents2 = await this.asyncLayer.transactionImmediate(async (tx) => {
18054
+ await tx.execute(sql`SELECT pg_advisory_xact_lock(hashtext(${this.backendProjectId}), hashtext('builtin-workflow-role-provisioning'))`);
18055
+ return provision(tx);
17750
18056
  });
17751
- return agents2.map((a) => this.parseAgent(a)).filter((agent) => filter?.includeEphemeral === true || !isEphemeralAgent(agent));
18057
+ await Promise.all(agents2.map((agent) => this.materializeBuiltinWorkflowRoleBundle(agent)));
18058
+ return agents2;
17752
18059
  }
17753
18060
  /**
17754
18061
  * Create an API key for an agent.
@@ -18316,6 +18623,29 @@ var AgentStore = class _AgentStore extends EventEmitter {
18316
18623
  }
18317
18624
  return null;
18318
18625
  }
18626
+ /** Seed only missing or blank default-owned mirror files after the provisioning transaction commits. */
18627
+ async materializeBuiltinWorkflowRoleBundle(agent) {
18628
+ return this.withLock(agent.id, async () => {
18629
+ const role = agent.metadata?.workflowRole;
18630
+ const definition = BUILTIN_WORKFLOW_ROLE_AGENT_DEFAULT_LIST.find((candidate) => candidate.role === role);
18631
+ if (!definition || agent.metadata?.builtInWorkflowRole !== true) return;
18632
+ const config2 = agent.bundleConfig;
18633
+ if (!isCanonicalOrPartialBuiltinWorkflowBundle(config2)) return;
18634
+ if (agent.instructionsPath?.trim() || agent.instructionsText?.trim() && agent.instructionsText !== definition.instructionsText) return;
18635
+ const bundleDir = await this.resolveCompatibleBundleDir(agent.id, true);
18636
+ await mkdir(bundleDir, { recursive: true });
18637
+ for (const [file, content] of [["AGENTS.md", definition.instructionsText], ["soul.md", definition.soul]]) {
18638
+ const path = join(bundleDir, file);
18639
+ let current = "";
18640
+ try {
18641
+ current = await readFile(path, "utf-8");
18642
+ } catch (error) {
18643
+ if (error.code !== "ENOENT") throw error;
18644
+ }
18645
+ if (!current.trim()) await writeFile(path, content, "utf-8");
18646
+ }
18647
+ });
18648
+ }
18319
18649
  getCanonicalBundleDir(agent) {
18320
18650
  return join(this.agentsDir, getCanonicalAgentInstructionsBundleDirName(agent.name, agent.id));
18321
18651
  }
@@ -18438,6 +18768,7 @@ var AgentStore = class _AgentStore extends EventEmitter {
18438
18768
  ...data,
18439
18769
  id: row.id,
18440
18770
  name: row.name,
18771
+ roles: data.roles?.length ? data.roles : [row.role],
18441
18772
  role: row.role,
18442
18773
  state: row.state,
18443
18774
  taskId: row.taskId ?? void 0,
@@ -18471,7 +18802,8 @@ var AgentStore = class _AgentStore extends EventEmitter {
18471
18802
  return {
18472
18803
  id: data.id,
18473
18804
  name: data.name,
18474
- role: data.role,
18805
+ roles: normalizeAgentRoles(data.roles, data.role),
18806
+ role: normalizeAgentRoles(data.roles, data.role)[0],
18475
18807
  state: data.state,
18476
18808
  taskId: data.taskId,
18477
18809
  createdAt: data.createdAt,
@@ -18497,8 +18829,8 @@ var AgentStore = class _AgentStore extends EventEmitter {
18497
18829
  heartbeatProcedurePath: data.heartbeatProcedurePath
18498
18830
  };
18499
18831
  }
18500
- async writeAgent(agent) {
18501
- await writeAgent(this.asyncLayer.db, agent, this.asyncLayer.projectId);
18832
+ async writeAgent(agent, executor) {
18833
+ await writeAgent(executor ?? this.asyncLayer.db, agent, this.asyncLayer.projectId);
18502
18834
  return;
18503
18835
  }
18504
18836
  /**
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fusion-plugin-examples/cli-printing-press",
3
- "version": "0.1.51-beta.2",
3
+ "version": "0.1.52-beta.0",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {