@runfusion/fusion 0.45.0 → 0.46.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 (116) hide show
  1. package/dist/bin.js +531 -99
  2. package/dist/client/.vite/manifest.json +244 -244
  3. package/dist/client/assets/{AgentDetailView-DJTi6hKC.js → AgentDetailView-D1cA9Bfj.js} +1 -1
  4. package/dist/client/assets/{AgentPermissionPolicyEditor-C5DR9dUS.js → AgentPermissionPolicyEditor-DE0wv8-L.js} +1 -1
  5. package/dist/client/assets/{AgentsView-BNd90PSx.js → AgentsView-Bm_FA_1w.js} +3 -3
  6. package/dist/client/assets/{ChatView-B7JaZCL_.js → ChatView-CHEkov6q.js} +1 -1
  7. package/dist/client/assets/CommandCenter-Cfgek-dC.js +71 -0
  8. package/dist/client/assets/{CommandCenter-Xs5Ftv8s.css → CommandCenter-DDjpYkPe.css} +1 -1
  9. package/dist/client/assets/DevServerView-DgDksVH2.css +1 -0
  10. package/dist/client/assets/DevServerView-_or2KLVw.js +1 -0
  11. package/dist/client/assets/{DirectoryPicker-DegxT8ia.js → DirectoryPicker-etVwa-6X.js} +1 -1
  12. package/dist/client/assets/{DocumentsView-kVVQYISc.js → DocumentsView-e8ZTdPIZ.js} +1 -1
  13. package/dist/client/assets/{EvalsView-C9-iTSat.js → EvalsView-CjDRQFMQ.js} +1 -1
  14. package/dist/client/assets/{ExperimentalAgentOnboardingModal-DjP_xNkc.js → ExperimentalAgentOnboardingModal-DXR0OAsR.js} +1 -1
  15. package/dist/client/assets/{GoalsView-VoIHJJ5l.js → GoalsView-WWDKVslN.js} +1 -1
  16. package/dist/client/assets/{InsightsView-BIZaUcpd.js → InsightsView-DxIfxOzx.js} +1 -1
  17. package/dist/client/assets/{MemoryView-7v7rv6ns.js → MemoryView-BVW2hkWf.js} +1 -1
  18. package/dist/client/assets/{PiExtensionsManager-CB89rV4U.js → PiExtensionsManager-CpSfMok2.js} +1 -1
  19. package/dist/client/assets/{PluginManager-BNSuW_R4.js → PluginManager-CpBCy656.js} +1 -1
  20. package/dist/client/assets/{PullRequestView-CI5jdKBL.js → PullRequestView-BMspX1ga.js} +1 -1
  21. package/dist/client/assets/{ResearchView-BT5dR_m8.js → ResearchView--YEtcv_s.js} +1 -1
  22. package/dist/client/assets/{SecretsView-WPYI58Uo.js → SecretsView-CC6QDMWN.js} +1 -1
  23. package/dist/client/assets/{SessionTerminal-C6gyOWLt.js → SessionTerminal-DJGtgIls.js} +1 -1
  24. package/dist/client/assets/{SettingsModal-Bq_3AbaB.js → SettingsModal-4d_p8D7W.js} +1 -1
  25. package/dist/client/assets/SettingsModal-ino68pI2.css +1 -0
  26. package/dist/client/assets/SettingsModal-wABa7xmZ.js +21 -0
  27. package/dist/client/assets/{SetupWizardModal-CrgbnR_d.js → SetupWizardModal-DHe2tXzx.js} +2 -2
  28. package/dist/client/assets/{ShadcnColorPicker-1icHuwhC.js → ShadcnColorPicker-HX3j1v66.js} +1 -1
  29. package/dist/client/assets/{SkillsView-byuSe3e0.js → SkillsView-mdFVTX-r.js} +1 -1
  30. package/dist/client/assets/{TodoView-CQ9GjYBg.js → TodoView-DQVugDpW.js} +1 -1
  31. package/dist/client/assets/{WorkflowNodeEditor-DW7sJZUX.js → WorkflowNodeEditor-CeERQHHu.js} +1 -1
  32. package/dist/client/assets/{agentSkills-CeytXLHF.js → agentSkills-C2v1wbxh.js} +1 -1
  33. package/dist/client/assets/{app-7-5DWLtg.js → app-BJGkp7Tt.js} +3 -3
  34. package/dist/client/assets/{app-CZ2LTFce.js → app-BKLUvvm8.js} +3 -3
  35. package/dist/client/assets/{app-Cv9xEnll.js → app-BaoNC9zg.js} +1 -1
  36. package/dist/client/assets/{app-x_yPcO5Y.js → app-CZQ_fFNf.js} +3 -3
  37. package/dist/client/assets/{app-CSvE5Noc.js → app-DAu3AzsS.js} +3 -3
  38. package/dist/client/assets/{app-3wbZ08YC.js → app-DgezOJub.js} +1 -1
  39. package/dist/client/assets/{architectureDiagram-3BPJPVTR-BRI0QwDf.js → architectureDiagram-3BPJPVTR-CGEhhgX_.js} +1 -1
  40. package/dist/client/assets/{blockDiagram-GPEHLZMM-BXkN8Hv_.js → blockDiagram-GPEHLZMM-D0DTo0HT.js} +1 -1
  41. package/dist/client/assets/{c4Diagram-AAUBKEIU-C_Kcokep.js → c4Diagram-AAUBKEIU-DNykNaRb.js} +1 -1
  42. package/dist/client/assets/channel-Dwf1FoGU.js +1 -0
  43. package/dist/client/assets/{chunk-2J33WTMH-BGeuCz9C.js → chunk-2J33WTMH-5oQfAhmr.js} +1 -1
  44. package/dist/client/assets/{chunk-4BX2VUAB-DcVdctr0.js → chunk-4BX2VUAB-BqLZXgFw.js} +1 -1
  45. package/dist/client/assets/{chunk-55IACEB6-Dg6gDigQ.js → chunk-55IACEB6-C1oIjX3T.js} +1 -1
  46. package/dist/client/assets/{chunk-727SXJPM-B9B-vlEb.js → chunk-727SXJPM-BaovQTgk.js} +1 -1
  47. package/dist/client/assets/{chunk-AQP2D5EJ-LrWQ-2yi.js → chunk-AQP2D5EJ-B4ikBK42.js} +1 -1
  48. package/dist/client/assets/{chunk-FMBD7UC4-Bnd6hAED.js → chunk-FMBD7UC4-5zgtNnG2.js} +1 -1
  49. package/dist/client/assets/{chunk-ND2GUHAM-nAw93UB5.js → chunk-ND2GUHAM-ByrypRna.js} +1 -1
  50. package/dist/client/assets/{chunk-QZHKN3VN-CgprKcZo.js → chunk-QZHKN3VN-B7D1exsu.js} +1 -1
  51. package/dist/client/assets/classDiagram-4FO5ZUOK-WqSUwcK_.js +1 -0
  52. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-WqSUwcK_.js +1 -0
  53. package/dist/client/assets/{cose-bilkent-S5V4N54A-Bhn0g_iK.js → cose-bilkent-S5V4N54A-CQmiNzGr.js} +1 -1
  54. package/dist/client/assets/{dagre-BM42HDAG-CP4kfwqd.js → dagre-BM42HDAG-BH8Uk0Up.js} +1 -1
  55. package/dist/client/assets/{dashboard-view-CM6Xin_H.js → dashboard-view-CWJ-b59n.js} +1 -1
  56. package/dist/client/assets/{dashboard-view-DV1nEhh9.js → dashboard-view-D3PWY2PH.js} +1 -1
  57. package/dist/client/assets/{diagram-2AECGRRQ-DPhzVEUf.js → diagram-2AECGRRQ-CDoHfkxY.js} +1 -1
  58. package/dist/client/assets/{diagram-5GNKFQAL-8QOC3K_L.js → diagram-5GNKFQAL-DARILCuD.js} +1 -1
  59. package/dist/client/assets/{diagram-KO2AKTUF-C1twgEch.js → diagram-KO2AKTUF-CNP-qi0y.js} +1 -1
  60. package/dist/client/assets/{diagram-LMA3HP47-3bEegSuf.js → diagram-LMA3HP47-SCiVmkcf.js} +1 -1
  61. package/dist/client/assets/{diagram-OG6HWLK6-BJ_x0rm6.js → diagram-OG6HWLK6-iTJLXH61.js} +1 -1
  62. package/dist/client/assets/{erDiagram-TEJ5UH35-DNiiS5q6.js → erDiagram-TEJ5UH35-ByMpiPIa.js} +1 -1
  63. package/dist/client/assets/{flowDiagram-I6XJVG4X-DoouPklt.js → flowDiagram-I6XJVG4X-DW3m3ZpM.js} +1 -1
  64. package/dist/client/assets/{folder-open-CuUs2W7f.js → folder-open-G-9vPAdv.js} +1 -1
  65. package/dist/client/assets/{ganttDiagram-6RSMTGT7-BrR6VnP5.js → ganttDiagram-6RSMTGT7-BJeYH7Ol.js} +1 -1
  66. package/dist/client/assets/{gitGraphDiagram-PVQCEYII-BV8_J6Ee.js → gitGraphDiagram-PVQCEYII-CK_Chj4m.js} +1 -1
  67. package/dist/client/assets/index-ccFe_1av.js +1459 -0
  68. package/dist/client/assets/{infoDiagram-5YYISTIA-DGwIIFkR.js → infoDiagram-5YYISTIA-D8Wg1IgN.js} +1 -1
  69. package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-D5dEH4RE.js → ishikawaDiagram-YF4QCWOH-DWROR2aX.js} +1 -1
  70. package/dist/client/assets/{journeyDiagram-JHISSGLW-BuuSWPw5.js → journeyDiagram-JHISSGLW-Bns9774_.js} +1 -1
  71. package/dist/client/assets/{kanban-definition-UN3LZRKU-DA34uUla.js → kanban-definition-UN3LZRKU-QtURfAoI.js} +1 -1
  72. package/dist/client/assets/{mermaid.core-Ch4HatqA.js → mermaid.core-DqgrgWWm.js} +4 -4
  73. package/dist/client/assets/{mindmap-definition-RKZ34NQL-DUCG-qD3.js → mindmap-definition-RKZ34NQL-jLaDb5_X.js} +1 -1
  74. package/dist/client/assets/{pieDiagram-4H26LBE5-DWvFGiLa.js → pieDiagram-4H26LBE5-CjbHuIje.js} +1 -1
  75. package/dist/client/assets/{quadrantDiagram-W4KKPZXB-OPTVW3WT.js → quadrantDiagram-W4KKPZXB-xgHbylQr.js} +1 -1
  76. package/dist/client/assets/{requirementDiagram-4Y6WPE33-D7feZvlU.js → requirementDiagram-4Y6WPE33-CHdD7W41.js} +1 -1
  77. package/dist/client/assets/{sankeyDiagram-5OEKKPKP-AtnSsEX1.js → sankeyDiagram-5OEKKPKP-C3me87g9.js} +1 -1
  78. package/dist/client/assets/{sequenceDiagram-3UESZ5HK-BZjHUeI5.js → sequenceDiagram-3UESZ5HK-DSHfs1kU.js} +1 -1
  79. package/dist/client/assets/{shield-alert-CzaF9ROV.js → shield-alert-DkGlhVjI.js} +1 -1
  80. package/dist/client/assets/{stateDiagram-AJRCARHV-C8j23Pvn.js → stateDiagram-AJRCARHV-C08n7sMP.js} +1 -1
  81. package/dist/client/assets/{stateDiagram-v2-BHNVJYJU-CiZR_S5-.js → stateDiagram-v2-BHNVJYJU-BosoAbfO.js} +1 -1
  82. package/dist/client/assets/{timeline-definition-PNZ67QCA-DvfNz25n.js → timeline-definition-PNZ67QCA-C21h9Zqh.js} +1 -1
  83. package/dist/client/assets/{upload-DTsYywhN.js → upload-B9B-tYt9.js} +1 -1
  84. package/dist/client/assets/{users-yAd09tKP.js → users-C_BA3C7E.js} +1 -1
  85. package/dist/client/assets/{vennDiagram-CIIHVFJN-CG2aMt3v.js → vennDiagram-CIIHVFJN-Bv3G0dwI.js} +1 -1
  86. package/dist/client/assets/{wardley-L42UT6IY-BnhBteen.js → wardley-L42UT6IY-WX0hHKcN.js} +1 -1
  87. package/dist/client/assets/{wardleyDiagram-YWT4CUSO-DwqTzM3T.js → wardleyDiagram-YWT4CUSO-CMwIPWm7.js} +1 -1
  88. package/dist/client/assets/{xychartDiagram-2RQKCTM6-5Cv2ZKMV.js → xychartDiagram-2RQKCTM6-CZvFdt6j.js} +1 -1
  89. package/dist/client/index.html +1 -1
  90. package/dist/client/version.json +1 -1
  91. package/dist/droid-cli/package.json +1 -1
  92. package/dist/extension.js +472 -94
  93. package/dist/pi-claude-cli/package.json +1 -1
  94. package/dist/plugin-sdk/index.d.ts +11 -0
  95. package/dist/plugins/fusion-plugin-acp-runtime/package.json +1 -1
  96. package/dist/plugins/fusion-plugin-cli-printing-press/package.json +1 -1
  97. package/dist/plugins/fusion-plugin-compound-engineering/package.json +1 -1
  98. package/dist/plugins/fusion-plugin-cursor-runtime/package.json +1 -1
  99. package/dist/plugins/fusion-plugin-dependency-graph/package.json +1 -1
  100. package/dist/plugins/fusion-plugin-droid-runtime/package.json +1 -1
  101. package/dist/plugins/fusion-plugin-hermes-runtime/package.json +1 -1
  102. package/dist/plugins/fusion-plugin-openclaw-runtime/package.json +1 -1
  103. package/dist/plugins/fusion-plugin-paperclip-runtime/package.json +1 -1
  104. package/dist/plugins/fusion-plugin-reports/package.json +1 -1
  105. package/dist/plugins/fusion-plugin-roadmap/package.json +1 -1
  106. package/dist/plugins/fusion-plugin-whatsapp-chat/package.json +1 -1
  107. package/package.json +1 -1
  108. package/dist/client/assets/CommandCenter-CHO21xEW.js +0 -74
  109. package/dist/client/assets/DevServerView-B_p91Mj_.css +0 -1
  110. package/dist/client/assets/DevServerView-H9OkT1ld.js +0 -1
  111. package/dist/client/assets/SettingsModal-BZokaNyx.css +0 -1
  112. package/dist/client/assets/SettingsModal-JHACoQJe.js +0 -21
  113. package/dist/client/assets/channel-Clzpzu32.js +0 -1
  114. package/dist/client/assets/classDiagram-4FO5ZUOK-CzYHP-af.js +0 -1
  115. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-CzYHP-af.js +0 -1
  116. package/dist/client/assets/index-CZcWjLAb.js +0 -1459
package/dist/bin.js CHANGED
@@ -435,6 +435,9 @@ var init_settings_schema = __esm({
435
435
  defaultProvider: void 0,
436
436
  defaultModelId: void 0,
437
437
  testMode: void 0,
438
+ modelPricingOverrides: void 0,
439
+ modelPricingFetchedAt: void 0,
440
+ modelPricingSource: void 0,
438
441
  modelRouterEnabled: void 0,
439
442
  modelRouterCheapProvider: void 0,
440
443
  modelRouterCheapModelId: void 0,
@@ -49378,6 +49381,9 @@ import { randomUUID as randomUUID15 } from "node:crypto";
49378
49381
  import { mkdir as mkdir7, readdir as readdir5, readFile as readFile8, stat as stat3, writeFile as writeFile6, rename as rename4, unlink as unlink3 } from "node:fs/promises";
49379
49382
  import { join as join20 } from "node:path";
49380
49383
  import { existsSync as existsSync16, watch } from "node:fs";
49384
+ function isWorkflowColumnsCompatibilityFlagEnabled(settings) {
49385
+ return settings?.experimentalFeatures?.workflowColumns === true;
49386
+ }
49381
49387
  function defineTaskColumn(column, serialize, sqlIdentifier = column) {
49382
49388
  return { column, sqlIdentifier, serialize };
49383
49389
  }
@@ -49692,7 +49698,6 @@ var init_store = __esm({
49692
49698
  init_moved_settings();
49693
49699
  init_workflow_ir();
49694
49700
  init_workflow_steps_to_ir();
49695
- init_workflow_columns_settings();
49696
49701
  init_workflow_transitions();
49697
49702
  init_plugin_gate_verdict();
49698
49703
  init_trait_registry();
@@ -50490,7 +50495,7 @@ var init_store = __esm({
50490
50495
  }
50491
50496
  try {
50492
50497
  const settings = await this.getSettingsFast();
50493
- if (isWorkflowColumnsEnabled(settings)) {
50498
+ if (isWorkflowColumnsCompatibilityFlagEnabled(settings)) {
50494
50499
  await this.runWorkflowColumnsIntegrityPass();
50495
50500
  await this.recoverStaleTransitionPending();
50496
50501
  } else {
@@ -52294,7 +52299,7 @@ ${TASK_UPSERT_SQL_ASSIGNMENTS}
52294
52299
  await this.writeConfig(config);
52295
52300
  const updatedMerged = { ...DEFAULT_SETTINGS, ...globalSettings, ...updatedProjectSettings };
52296
52301
  this.emit("settings:updated", { settings: updatedMerged, previous: previousMerged });
52297
- if (isWorkflowColumnsEnabled(previousMerged) && !isWorkflowColumnsEnabled(updatedMerged)) {
52302
+ if (isWorkflowColumnsCompatibilityFlagEnabled(previousMerged) && !isWorkflowColumnsCompatibilityFlagEnabled(updatedMerged)) {
52298
52303
  try {
52299
52304
  await this.evacuateCustomColumnsToLegacy("flag-toggled-off");
52300
52305
  } catch (err) {
@@ -52377,7 +52382,7 @@ ${TASK_UPSERT_SQL_ASSIGNMENTS}
52377
52382
  merged.secretsSyncPassphraseConfigured = false;
52378
52383
  }
52379
52384
  this.emit("settings:updated", { settings: merged, previous });
52380
- if (isWorkflowColumnsEnabled(previous) && !isWorkflowColumnsEnabled(merged)) {
52385
+ if (isWorkflowColumnsCompatibilityFlagEnabled(previous) && !isWorkflowColumnsCompatibilityFlagEnabled(merged)) {
52381
52386
  try {
52382
52387
  await this.evacuateCustomColumnsToLegacy("flag-toggled-off");
52383
52388
  } catch (err) {
@@ -54621,7 +54626,8 @@ ${newTask.description}
54621
54626
  return { kind: "engine" };
54622
54627
  }
54623
54628
  resolveWorkflowBypassGuards(moveSource, options) {
54624
- return options?.recoveryRehome === true || (options?.bypassGuards ?? (moveSource === "engine" || moveSource === "scheduler" || options?.skipMergeBlocker === true));
54629
+ void moveSource;
54630
+ return options?.recoveryRehome === true || (options?.bypassGuards ?? (options?.moveSource === "engine" || options?.moveSource === "scheduler" || options?.skipMergeBlocker === true));
54625
54631
  }
54626
54632
  shouldSkipWorkflowMovePolicies(params) {
54627
54633
  if (params.bypassGuards) return true;
@@ -54632,7 +54638,7 @@ ${newTask.description}
54632
54638
  const task = await this.readTaskForMove(id);
54633
54639
  const moveSource = options?.moveSource ?? "engine";
54634
54640
  const mergedSettingsForMove = await this.getSettingsFast();
54635
- if (!isWorkflowColumnsEnabled(mergedSettingsForMove)) return void 0;
54641
+ if (!isWorkflowColumnsCompatibilityFlagEnabled(mergedSettingsForMove)) return void 0;
54636
54642
  if (task.column === toColumn) return void 0;
54637
54643
  const workflowIr = this.resolveTaskWorkflowIrSync(id);
54638
54644
  const workflowSignature = serializeWorkflowIr(workflowIr);
@@ -54716,7 +54722,7 @@ ${newTask.description}
54716
54722
  const task = currentTask ?? await this.readTaskForMove(id);
54717
54723
  const moveSource = options?.moveSource ?? "engine";
54718
54724
  const mergedSettingsForMove = await this.getSettingsFast();
54719
- const useWorkflow = isWorkflowColumnsEnabled(mergedSettingsForMove);
54725
+ const useWorkflow = isWorkflowColumnsCompatibilityFlagEnabled(mergedSettingsForMove);
54720
54726
  const bypassGuards = this.resolveWorkflowBypassGuards(moveSource, options);
54721
54727
  const workflowIr = useWorkflow ? this.resolveTaskWorkflowIrSync(id) : void 0;
54722
54728
  if (task.column === toColumn) {
@@ -59343,12 +59349,32 @@ ${newTask.description}
59343
59349
  /**
59344
59350
  * FNXC:ArtifactRegistry 2026-06-19-22:04:
59345
59351
  * Cross-agent registry query path for filtering artifacts across tasks, authors, and media types. LEFT JOIN keeps task-less registry artifacts visible while excluding artifacts attached to soft-deleted tasks.
59352
+ *
59353
+ * FNXC:ArtifactRegistry 2026-06-23-12:48:
59354
+ * Agent execution can list artifacts frequently while large generated outputs are stored inline. The registry list is metadata-only, so avoid selecting artifact content here and require callers to use getArtifact for the full payload.
59346
59355
  */
59347
59356
  async listArtifacts(options) {
59348
59357
  const limit = Math.min(Math.max(1, options?.limit ?? 200), 1e3);
59349
59358
  const offset = Math.max(0, options?.offset ?? 0);
59350
59359
  let sql = `
59351
- SELECT a.*, t.title as taskTitle, t.description as taskDescription, t.column as taskColumn
59360
+ SELECT
59361
+ a.id,
59362
+ a.type,
59363
+ a.title,
59364
+ a.description,
59365
+ a.mimeType,
59366
+ a.sizeBytes,
59367
+ a.uri,
59368
+ NULL as content,
59369
+ a.authorId,
59370
+ a.authorType,
59371
+ a.taskId,
59372
+ a.metadata,
59373
+ a.createdAt,
59374
+ a.updatedAt,
59375
+ t.title as taskTitle,
59376
+ t.description as taskDescription,
59377
+ t.column as taskColumn
59352
59378
  FROM artifacts a
59353
59379
  LEFT JOIN tasks t ON a.taskId = t.id
59354
59380
  WHERE (a.taskId IS NULL OR t.${_TaskStore.ACTIVE_TASKS_WHERE})
@@ -61008,9 +61034,9 @@ ${stepsSection}`;
61008
61034
  // always route through `moveTask` with `moveSource: "engine"` + `bypassGuards`
61009
61035
  // (a recovery-class move, KTD-9) — never a raw column write — so capacity
61010
61036
  // (KTD-10) and the single transition authority (KTD-3) are honored.
61011
- /** True when the `workflowColumns` flag is ON (merged global + project). */
61037
+ /** True when the raw `workflowColumns` compatibility flag is ON (merged global + project). */
61012
61038
  async workflowColumnsFlagOn() {
61013
- return isWorkflowColumnsEnabled(await this.getSettingsFast());
61039
+ return isWorkflowColumnsCompatibilityFlagEnabled(await this.getSettingsFast());
61014
61040
  }
61015
61041
  /** The active (non-deleted) task ids currently selecting `workflowId`. A
61016
61042
  * built-in/default workflow additionally owns every task with NO selection
@@ -62972,18 +62998,70 @@ var init_agent_token_usage = __esm({
62972
62998
  function normalize4(s3) {
62973
62999
  return (s3 ?? "").trim().toLowerCase();
62974
63000
  }
62975
- function lookupPricing(ref) {
63001
+ function findBareModelPricing(model, entries) {
63002
+ for (const [key, entry] of Object.entries(entries)) {
63003
+ if (key.endsWith(`:${model}`)) return entry;
63004
+ }
63005
+ return void 0;
63006
+ }
63007
+ function lookupPricing(ref, overrides2) {
62976
63008
  const provider = normalize4(ref.provider);
62977
63009
  const model = normalize4(ref.model);
62978
63010
  if (!model) return void 0;
63011
+ if (provider) {
63012
+ const exactOverride = overrides2?.[`${provider}:${model}`];
63013
+ if (exactOverride) return exactOverride;
63014
+ }
63015
+ const bareOverride = overrides2 ? findBareModelPricing(model, overrides2) : void 0;
63016
+ if (bareOverride) return bareOverride;
62979
63017
  if (provider) {
62980
63018
  const exact = MODEL_PRICING[`${provider}:${model}`];
62981
63019
  if (exact) return exact;
62982
63020
  }
62983
- for (const [key, entry] of Object.entries(MODEL_PRICING)) {
62984
- if (key.endsWith(`:${model}`)) return entry;
62985
- }
62986
- return void 0;
63021
+ return findBareModelPricing(model, MODEL_PRICING);
63022
+ }
63023
+ function litellmProviderToFusionProvider(provider) {
63024
+ if (typeof provider !== "string") return null;
63025
+ const normalized = normalize4(provider);
63026
+ if (normalized === "openai") return "openai";
63027
+ if (normalized === "anthropic") return "anthropic";
63028
+ if (normalized === "gemini" || normalized.startsWith("gemini")) return "google";
63029
+ if (normalized === "vertex_ai" || normalized.startsWith("vertex_ai")) return "google";
63030
+ if (normalized === "vertex_ai-language-models") return "google";
63031
+ return null;
63032
+ }
63033
+ function numericField(entry, key) {
63034
+ const value = entry[key];
63035
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
63036
+ }
63037
+ function parseLiteLLMPricing(json) {
63038
+ const overrides2 = {};
63039
+ if (json === null || typeof json !== "object" || Array.isArray(json)) {
63040
+ return { overrides: overrides2, count: 0 };
63041
+ }
63042
+ for (const [modelId, value] of Object.entries(json)) {
63043
+ if (modelId === "sample_spec") continue;
63044
+ if (value === null || typeof value !== "object" || Array.isArray(value)) continue;
63045
+ const entry = value;
63046
+ if (entry.mode !== "chat") continue;
63047
+ const provider = litellmProviderToFusionProvider(entry.litellm_provider);
63048
+ if (!provider) continue;
63049
+ const inputCost = numericField(entry, "input_cost_per_token");
63050
+ const outputCost = numericField(entry, "output_cost_per_token");
63051
+ if (inputCost === void 0 || outputCost === void 0) continue;
63052
+ const inputPer1M = inputCost * 1e6;
63053
+ const outputPer1M = outputCost * 1e6;
63054
+ const cacheRead = numericField(entry, "cache_read_input_token_cost");
63055
+ const cacheWrite = numericField(entry, "cache_creation_input_token_cost");
63056
+ overrides2[`${provider}:${normalize4(modelId)}`] = {
63057
+ inputPer1M,
63058
+ outputPer1M,
63059
+ cacheReadPer1M: cacheRead === void 0 ? inputPer1M : cacheRead * 1e6,
63060
+ cacheWritePer1M: cacheWrite === void 0 ? inputPer1M : cacheWrite * 1e6,
63061
+ source: LITELLM_PRICING_SOURCE_LABEL
63062
+ };
63063
+ }
63064
+ return { overrides: overrides2, count: Object.keys(overrides2).length };
62987
63065
  }
62988
63066
  function isStale(now) {
62989
63067
  if (now === void 0) return false;
@@ -62991,21 +63069,23 @@ function isStale(now) {
62991
63069
  if (Number.isNaN(asOf)) return false;
62992
63070
  return now - asOf > PRICING_STALE_AFTER_MS;
62993
63071
  }
62994
- function costFor(usage, model, now) {
63072
+ function costFor(usage, model, now, overrides2) {
62995
63073
  const stale = isStale(now);
62996
- const pricing = lookupPricing(model);
63074
+ const pricing = lookupPricing(model, overrides2);
62997
63075
  if (!pricing) {
62998
63076
  return { usd: null, unavailable: true, stale };
62999
63077
  }
63000
63078
  const usd = (usage.inputTokens * pricing.inputPer1M + usage.outputTokens * pricing.outputPer1M + usage.cachedTokens * pricing.cacheReadPer1M + usage.cacheWriteTokens * pricing.cacheWritePer1M) / 1e6;
63001
63079
  return { usd, unavailable: false, stale };
63002
63080
  }
63003
- var pricingAsOf, PRICING_STALE_AFTER_MS, MODEL_PRICING;
63081
+ var pricingAsOf, PRICING_STALE_AFTER_MS, LITELLM_PRICING_SOURCE_URL, LITELLM_PRICING_SOURCE_LABEL, MODEL_PRICING;
63004
63082
  var init_model_pricing = __esm({
63005
63083
  "../core/src/model-pricing.ts"() {
63006
63084
  "use strict";
63007
63085
  pricingAsOf = "2026-06-21";
63008
63086
  PRICING_STALE_AFTER_MS = 180 * 24 * 60 * 60 * 1e3;
63087
+ LITELLM_PRICING_SOURCE_URL = "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json";
63088
+ LITELLM_PRICING_SOURCE_LABEL = "litellm/model_prices_and_context_window.json";
63009
63089
  MODEL_PRICING = {
63010
63090
  // ── Anthropic Claude ────────────────────────────────────────────────
63011
63091
  // input / output / cacheRead(0.1×) / cacheWrite(1.25×, 5-min TTL)
@@ -63252,7 +63332,7 @@ function groupKeyFor(row, groupBy2) {
63252
63332
  function emptyCostAccumulator() {
63253
63333
  return { usd: 0, anyPriced: false, anyUnavailable: false, anyStale: false };
63254
63334
  }
63255
- function addRowCost(acc, row, now) {
63335
+ function addRowCost(acc, row, now, pricingOverrides) {
63256
63336
  const result = costFor(
63257
63337
  {
63258
63338
  inputTokens: row.inputTokens ?? 0,
@@ -63264,7 +63344,8 @@ function addRowCost(acc, row, now) {
63264
63344
  provider: row.tokenUsageModelProvider ?? row.modelProvider,
63265
63345
  model: row.tokenUsageModelId ?? row.modelId
63266
63346
  },
63267
- now
63347
+ now,
63348
+ pricingOverrides
63268
63349
  );
63269
63350
  if (result.stale) acc.anyStale = true;
63270
63351
  if (result.unavailable || result.usd === null) {
@@ -63373,9 +63454,10 @@ function aggregateTokenAnalytics(db, query = {}) {
63373
63454
  const groupBy2 = query.groupBy;
63374
63455
  const granularity = query.granularity;
63375
63456
  const now = query.now;
63457
+ const pricingOverrides = query.pricingOverrides;
63376
63458
  for (const row of rows) {
63377
63459
  addRow(totals, row);
63378
- addRowCost(totalCost, row, now);
63460
+ addRowCost(totalCost, row, now, pricingOverrides);
63379
63461
  if (groupBy2) {
63380
63462
  const groupRows = groupBy2 === "model" || groupBy2 === "provider" ? parsePerModelRows(row) : [];
63381
63463
  const rowsForGroup = groupRows.length > 0 ? groupRows : [row];
@@ -63388,7 +63470,7 @@ function aggregateTokenAnalytics(db, query = {}) {
63388
63470
  groupCostMap.set(key, emptyCostAccumulator());
63389
63471
  }
63390
63472
  addRow(group, groupRow);
63391
- addRowCost(groupCostMap.get(key), groupRow, now);
63473
+ addRowCost(groupCostMap.get(key), groupRow, now, pricingOverrides);
63392
63474
  }
63393
63475
  }
63394
63476
  if (granularity) {
@@ -63400,7 +63482,7 @@ function aggregateTokenAnalytics(db, query = {}) {
63400
63482
  seriesCostMap.set(bucket, emptyCostAccumulator());
63401
63483
  }
63402
63484
  addRow(point, row);
63403
- addRowCost(seriesCostMap.get(bucket), row, now);
63485
+ addRowCost(seriesCostMap.get(bucket), row, now, pricingOverrides);
63404
63486
  }
63405
63487
  }
63406
63488
  for (const [key, group] of groupMap) {
@@ -64051,7 +64133,7 @@ function addTokenRow(totals, row) {
64051
64133
  totals.totalTokens += row.totalTokens ?? (row.inputTokens ?? 0) + (row.outputTokens ?? 0) + (row.cachedTokens ?? 0) + (row.cacheWriteTokens ?? 0);
64052
64134
  totals.nTasks += 1;
64053
64135
  }
64054
- function addRowCost2(acc, row, now) {
64136
+ function addRowCost2(acc, row, now, pricingOverrides) {
64055
64137
  const result = costFor(
64056
64138
  {
64057
64139
  inputTokens: row.inputTokens ?? 0,
@@ -64060,7 +64142,8 @@ function addRowCost2(acc, row, now) {
64060
64142
  cacheWriteTokens: row.cacheWriteTokens ?? 0
64061
64143
  },
64062
64144
  { provider: row.modelProvider, model: row.modelId },
64063
- now
64145
+ now,
64146
+ pricingOverrides
64064
64147
  );
64065
64148
  if (result.stale) acc.anyStale = true;
64066
64149
  if (result.unavailable || result.usd === null) {
@@ -64119,6 +64202,7 @@ function aggregateTeamAnalytics(db, query = {}) {
64119
64202
  const costAccumulators = /* @__PURE__ */ new Map();
64120
64203
  const totalTokens = emptyTokenTotals();
64121
64204
  const totalCost = emptyCostAccumulator2();
64205
+ const pricingOverrides = query.pricingOverrides;
64122
64206
  const agents = db.prepare(`SELECT id, name, role, state FROM agents ORDER BY id`).all();
64123
64207
  for (const agent of agents) {
64124
64208
  summaries.set(agent.id, makeSummary(agent.id, agent));
@@ -64154,8 +64238,8 @@ function aggregateTeamAnalytics(db, query = {}) {
64154
64238
  costAccumulators.set(row.agentId, agentCost);
64155
64239
  addTokenRow(summary.tokens, row);
64156
64240
  addTokenRow(totalTokens, row);
64157
- addRowCost2(agentCost, row, query.now);
64158
- addRowCost2(totalCost, row, query.now);
64241
+ addRowCost2(agentCost, row, query.now, pricingOverrides);
64242
+ addRowCost2(totalCost, row, query.now, pricingOverrides);
64159
64243
  }
64160
64244
  const completedClauses = ["assignedAgentId IS NOT NULL", `"column" = 'done'`, "columnMovedAt IS NOT NULL"];
64161
64245
  const completedParams = [];
@@ -86359,6 +86443,8 @@ __export(src_exports, {
86359
86443
  InvalidMergeQueueLeaseDurationError: () => InvalidMergeQueueLeaseDurationError,
86360
86444
  InvalidRehomeTargetError: () => InvalidRehomeTargetError,
86361
86445
  LEGACY_AGENT_PERMISSION_POLICY_ACTION_CATEGORY_ALIASES: () => LEGACY_AGENT_PERMISSION_POLICY_ACTION_CATEGORY_ALIASES,
86446
+ LITELLM_PRICING_SOURCE_LABEL: () => LITELLM_PRICING_SOURCE_LABEL,
86447
+ LITELLM_PRICING_SOURCE_URL: () => LITELLM_PRICING_SOURCE_URL,
86362
86448
  LinkedWorktreeBootstrapRefusedError: () => LinkedWorktreeBootstrapRefusedError,
86363
86449
  MANUAL_RETRY_RESET_COUNTER_KEYS: () => MANUAL_RETRY_RESET_COUNTER_KEYS,
86364
86450
  MASTER_KEY_FILENAME: () => MASTER_KEY_FILENAME,
@@ -86899,6 +86985,7 @@ __export(src_exports, {
86899
86985
  parseInsightExtractionResponse: () => parseInsightExtractionResponse,
86900
86986
  parseInstanceNodeId: () => parseInstanceNodeId,
86901
86987
  parseJsonSteps: () => parseJsonSteps,
86988
+ parseLiteLLMPricing: () => parseLiteLLMPricing,
86902
86989
  parseNoOpCompletionMarker: () => parseNoOpCompletionMarker,
86903
86990
  parseProjectManifest: () => parseProjectManifest,
86904
86991
  parseRepoFromRemote: () => parseRepoFromRemote,
@@ -88349,6 +88436,74 @@ var init_logger2 = __esm({
88349
88436
  });
88350
88437
 
88351
88438
  // ../engine/src/agent-logger.ts
88439
+ function truncateToolResultDetail(value) {
88440
+ if (value.length <= TOOL_RESULT_DETAIL_LIMIT) return value;
88441
+ return `${value.slice(0, TOOL_RESULT_DETAIL_LIMIT)}${TOOL_RESULT_DETAIL_TRUNCATION_NOTICE}`;
88442
+ }
88443
+ function summarizeToolResultValue(value, state) {
88444
+ if (state.remainingChars <= 0) return "[truncated]";
88445
+ if (value === null || value === void 0) return value;
88446
+ if (typeof value === "string") {
88447
+ const truncated = value.length > state.remainingChars ? `${value.slice(0, state.remainingChars)}${TOOL_RESULT_DETAIL_TRUNCATION_NOTICE}` : value;
88448
+ state.remainingChars -= Math.min(value.length, state.remainingChars);
88449
+ return truncated;
88450
+ }
88451
+ if (typeof value === "number" || typeof value === "boolean") return value;
88452
+ if (typeof value === "bigint") return value.toString();
88453
+ if (typeof value === "function" || typeof value === "symbol") return `[${typeof value}]`;
88454
+ if (value instanceof Error) {
88455
+ return {
88456
+ name: value.name,
88457
+ message: summarizeToolResultValue(value.message, state),
88458
+ ...value.stack ? { stack: summarizeToolResultValue(value.stack, state) } : {}
88459
+ };
88460
+ }
88461
+ if (typeof Buffer !== "undefined" && Buffer.isBuffer(value)) {
88462
+ return `<Buffer ${value.byteLength} bytes>`;
88463
+ }
88464
+ if (ArrayBuffer.isView(value)) {
88465
+ return `<${value.constructor.name} ${value.byteLength} bytes>`;
88466
+ }
88467
+ if (value instanceof ArrayBuffer) {
88468
+ return `<ArrayBuffer ${value.byteLength} bytes>`;
88469
+ }
88470
+ if (typeof value !== "object") return String(value);
88471
+ if (state.seen.has(value)) return "[Circular]";
88472
+ state.seen.add(value);
88473
+ if (Array.isArray(value)) {
88474
+ const summarized2 = value.slice(0, TOOL_RESULT_MAX_ARRAY_ITEMS).map((item) => summarizeToolResultValue(item, state));
88475
+ if (value.length > TOOL_RESULT_MAX_ARRAY_ITEMS) summarized2.push(`[${value.length - TOOL_RESULT_MAX_ARRAY_ITEMS} more items truncated]`);
88476
+ return summarized2;
88477
+ }
88478
+ const summarized = {};
88479
+ let count2 = 0;
88480
+ for (const key of Object.keys(value)) {
88481
+ if (count2 >= TOOL_RESULT_MAX_OBJECT_KEYS) {
88482
+ summarized.__truncatedKeys = true;
88483
+ break;
88484
+ }
88485
+ summarized[key] = summarizeToolResultValue(value[key], state);
88486
+ count2 += 1;
88487
+ if (state.remainingChars <= 0) {
88488
+ summarized.__truncated = true;
88489
+ break;
88490
+ }
88491
+ }
88492
+ return summarized;
88493
+ }
88494
+ function summarizeToolResultDetail(result) {
88495
+ if (result === void 0 || result === null) return void 0;
88496
+ if (typeof result === "string") return truncateToolResultDetail(result);
88497
+ try {
88498
+ const preview = summarizeToolResultValue(result, {
88499
+ remainingChars: TOOL_RESULT_DETAIL_LIMIT,
88500
+ seen: /* @__PURE__ */ new WeakSet()
88501
+ });
88502
+ return truncateToolResultDetail(JSON.stringify(preview));
88503
+ } catch {
88504
+ return truncateToolResultDetail(String(result));
88505
+ }
88506
+ }
88352
88507
  function summarizeToolArgs(name, args) {
88353
88508
  if (!args) return void 0;
88354
88509
  const lowerName = name.toLowerCase();
@@ -88365,7 +88520,7 @@ function summarizeToolArgs(name, args) {
88365
88520
  }
88366
88521
  return void 0;
88367
88522
  }
88368
- var FLUSH_SIZE_BYTES, FLUSH_INTERVAL_MS, ENTRY_BATCH_SIZE, AgentLogger;
88523
+ var FLUSH_SIZE_BYTES, FLUSH_INTERVAL_MS, ENTRY_BATCH_SIZE, TOOL_RESULT_DETAIL_LIMIT, TOOL_RESULT_DETAIL_TRUNCATION_NOTICE, TOOL_RESULT_MAX_ARRAY_ITEMS, TOOL_RESULT_MAX_OBJECT_KEYS, AgentLogger;
88369
88524
  var init_agent_logger = __esm({
88370
88525
  "../engine/src/agent-logger.ts"() {
88371
88526
  "use strict";
@@ -88374,6 +88529,10 @@ var init_agent_logger = __esm({
88374
88529
  FLUSH_SIZE_BYTES = 1024;
88375
88530
  FLUSH_INTERVAL_MS = 500;
88376
88531
  ENTRY_BATCH_SIZE = 50;
88532
+ TOOL_RESULT_DETAIL_LIMIT = 4096;
88533
+ TOOL_RESULT_DETAIL_TRUNCATION_NOTICE = "\n\n[tool output truncated to keep dashboard log views responsive]";
88534
+ TOOL_RESULT_MAX_ARRAY_ITEMS = 25;
88535
+ TOOL_RESULT_MAX_OBJECT_KEYS = 50;
88377
88536
  AgentLogger = class {
88378
88537
  textBuffer = "";
88379
88538
  thinkingBuffer = "";
@@ -88519,10 +88678,7 @@ var init_agent_logger = __esm({
88519
88678
  */
88520
88679
  onToolEnd(name, isError, result) {
88521
88680
  const type = isError ? "tool_error" : "tool_result";
88522
- let detail;
88523
- if (result !== void 0 && result !== null) {
88524
- detail = typeof result === "string" ? result : JSON.stringify(result);
88525
- }
88681
+ const detail = summarizeToolResultDetail(result);
88526
88682
  this.writeEntry(name, type, detail, `Failed to log tool end "${name}" (${type}) for ${this.taskId}`);
88527
88683
  const startedAt = this.toolStartedAt.get(name);
88528
88684
  if (startedAt !== void 0) this.toolStartedAt.delete(name);
@@ -89569,7 +89725,6 @@ function countCapacitySlot(allTasks, effectiveWorkflowIdByTask, targetColumn, wo
89569
89725
  async function runHoldReleaseSweep(store, deps) {
89570
89726
  const result = { released: [], held: [] };
89571
89727
  const settings = await store.getSettings();
89572
- if (!isWorkflowColumnsEnabled(settings)) return result;
89573
89728
  const allTasks = await store.listTasks({ includeArchived: false });
89574
89729
  const irCache = /* @__PURE__ */ new Map();
89575
89730
  const effectiveWorkflowIdByTask = /* @__PURE__ */ new Map();
@@ -108567,10 +108722,83 @@ var init_unlinked_missions_advisory_reporter = __esm({
108567
108722
  }
108568
108723
  });
108569
108724
 
108725
+ // ../engine/src/task-agent-sync.ts
108726
+ function hasFreshActiveHeartbeatRun(activeRun, now = Date.now(), freshRunMs = PARKED_AGENT_LINK_FRESH_RUN_MS) {
108727
+ const runStartedAt = activeRun?.startedAt;
108728
+ const runAgeMs = runStartedAt ? now - Date.parse(runStartedAt) : Number.POSITIVE_INFINITY;
108729
+ return {
108730
+ hasFreshRun: Boolean(activeRun) && Number.isFinite(runAgeMs) && runAgeMs <= freshRunMs,
108731
+ runAgeMs
108732
+ };
108733
+ }
108734
+ function isParkedTaskColumn(task) {
108735
+ return task?.column === "todo" || task?.column === "triage";
108736
+ }
108737
+ function evaluateParkedAgentTaskLink(options) {
108738
+ const { hasFreshRun, runAgeMs } = hasFreshActiveHeartbeatRun(options.activeRun, options.now);
108739
+ const hasActiveExecution = options.hasActiveAgentExecution?.(options.agent.id) === true;
108740
+ return {
108741
+ hasFreshRun,
108742
+ hasActiveExecution,
108743
+ shouldPreserveParkedLink: isParkedTaskColumn(options.linkedTask) && (hasFreshRun || hasActiveExecution),
108744
+ runAgeMs
108745
+ };
108746
+ }
108747
+ function attachAgentLinkSync(opts) {
108748
+ const logger3 = opts.logger ?? console;
108749
+ const handler = async ({ task, from, to: to2 }) => {
108750
+ if (!CLEAR_COLUMNS.has(to2)) {
108751
+ return;
108752
+ }
108753
+ try {
108754
+ const agents = await opts.agentStore.listAgents({ includeEphemeral: false });
108755
+ const linkedAgents = agents.filter((agent) => agent.taskId === task.id);
108756
+ for (const agent of linkedAgents) {
108757
+ if (to2 === "todo" || to2 === "triage") {
108758
+ const activeRun = await opts.agentStore.getActiveHeartbeatRun?.(agent.id);
108759
+ const proof = evaluateParkedAgentTaskLink({
108760
+ agent,
108761
+ linkedTask: { column: to2 },
108762
+ activeRun,
108763
+ hasActiveAgentExecution: opts.hasActiveAgentExecution
108764
+ });
108765
+ if (proof.shouldPreserveParkedLink) {
108766
+ continue;
108767
+ }
108768
+ }
108769
+ if (agent.state === "running") {
108770
+ await opts.agentStore.updateAgentState(agent.id, "active");
108771
+ }
108772
+ await opts.agentStore.syncExecutionTaskLink(agent.id, void 0);
108773
+ logger3.log(`taskAgentLinkSync: cleared agent ${agent.id} taskId from ${task.id} after move ${from} \u2192 ${to2}`);
108774
+ }
108775
+ } catch (error) {
108776
+ logger3.warn(
108777
+ `taskAgentLinkSync: failed to sync agents for task ${task.id} after move ${from} \u2192 ${to2}: ${error instanceof Error ? error.message : String(error)}`
108778
+ );
108779
+ }
108780
+ };
108781
+ opts.store.on("task:moved", handler);
108782
+ return () => {
108783
+ opts.store.off("task:moved", handler);
108784
+ };
108785
+ }
108786
+ var PARKED_AGENT_LINK_FRESH_RUN_MS, CLEAR_COLUMNS;
108787
+ var init_task_agent_sync = __esm({
108788
+ "../engine/src/task-agent-sync.ts"() {
108789
+ "use strict";
108790
+ PARKED_AGENT_LINK_FRESH_RUN_MS = 5 * 6e4;
108791
+ CLEAR_COLUMNS = /* @__PURE__ */ new Set(["done", "archived", "todo", "triage"]);
108792
+ }
108793
+ });
108794
+
108570
108795
  // ../engine/src/scheduler.ts
108571
108796
  import { existsSync as existsSync37 } from "node:fs";
108572
108797
  import { readFile as readFile20 } from "node:fs/promises";
108573
108798
  import { join as join42 } from "node:path";
108799
+ function shouldRunWorkflowColumnScheduler(_settings) {
108800
+ return true;
108801
+ }
108574
108802
  function pathsOverlap2(a, b2) {
108575
108803
  for (const pa of a) {
108576
108804
  const prefixA = pa.endsWith("/*") ? pa.slice(0, -1) : null;
@@ -108811,6 +109039,7 @@ var init_scheduler = __esm({
108811
109039
  init_run_audit();
108812
109040
  init_src();
108813
109041
  init_hold_release();
109042
+ init_task_agent_sync();
108814
109043
  COORDINATION_SAFE_SCOPE_EXACT = /* @__PURE__ */ new Set([
108815
109044
  // Literal glob scope entries from PROMPT.md are kept here; concrete
108816
109045
  // `.changeset/<name>.md` files are covered by COORDINATION_SAFE_SCOPE_PREFIXES.
@@ -109257,12 +109486,27 @@ var init_scheduler = __esm({
109257
109486
  async rollbackRunningAgentsForQueuedTodoTask(taskId) {
109258
109487
  const agentStore = this.options.agentStore;
109259
109488
  if (!agentStore) return;
109260
- const runningAgents = await agentStore.listAgents({ state: "running", includeEphemeral: true });
109489
+ const runningAgents = await agentStore.listAgents({ state: "running", includeEphemeral: false });
109261
109490
  const linkedAgents = runningAgents.filter((agent) => agent.taskId === taskId);
109262
109491
  for (const agent of linkedAgents) {
109492
+ const activeRun = await agentStore.getActiveHeartbeatRun?.(agent.id);
109493
+ const proof = evaluateParkedAgentTaskLink({
109494
+ agent,
109495
+ linkedTask: { column: "todo" },
109496
+ activeRun,
109497
+ hasActiveAgentExecution: this.options.hasActiveAgentExecution
109498
+ });
109499
+ if (proof.shouldPreserveParkedLink) {
109500
+ schedulerLog.log(
109501
+ `Preserved running agent ${agent.id} for queued ${taskId}; live proof freshRun=${proof.hasFreshRun} activeExecution=${proof.hasActiveExecution}`
109502
+ );
109503
+ continue;
109504
+ }
109263
109505
  await agentStore.updateAgentState(agent.id, "active");
109264
109506
  await agentStore.syncExecutionTaskLink(agent.id, void 0);
109265
- schedulerLog.log(`Rolled back running agent ${agent.id} after overlap requeue of ${taskId}`);
109507
+ schedulerLog.log(
109508
+ `Cleared stale running agent ${agent.id} after overlap requeue of ${taskId}; file-scope lease remains queued`
109509
+ );
109266
109510
  }
109267
109511
  }
109268
109512
  /**
@@ -109382,7 +109626,7 @@ var init_scheduler = __esm({
109382
109626
  schedulerLog.log("Engine pause cleared \u2014 scheduling resumed");
109383
109627
  }
109384
109628
  this.wasEnginePaused = false;
109385
- if (isWorkflowColumnsEnabled(settings)) {
109629
+ if (shouldRunWorkflowColumnScheduler(settings)) {
109386
109630
  await this.runHoldReleaseSweepPass(tasks, settings);
109387
109631
  tasks = await this.store.listTasks({ slim: true, includeArchived: false, startupMemo: false });
109388
109632
  settings = await this.store.getSettings();
@@ -110408,6 +110652,7 @@ var init_scheduler = __esm({
110408
110652
  blockedBy: null,
110409
110653
  overlapBlockedBy: overlappingTaskId
110410
110654
  });
110655
+ await this.rollbackRunningAgentsForQueuedTodoTask(task.id);
110411
110656
  await this.logDispatchQueuedReason(
110412
110657
  task.id,
110413
110658
  `queued \u2014 blocked by active file-scope lease ${overlappingTaskId} (column=${activeLeaseColumn})`
@@ -125336,6 +125581,7 @@ var init_self_healing = __esm({
125336
125581
  init_notifier();
125337
125582
  init_dependency_blocked_todo_reporter();
125338
125583
  init_scheduler();
125584
+ init_task_agent_sync();
125339
125585
  init_transient_merge_error_classifier();
125340
125586
  init_transient_merge_error_classifier();
125341
125587
  log22 = createLogger2("self-healing");
@@ -125402,7 +125648,7 @@ var init_self_healing = __esm({
125402
125648
  DURABLE_ERROR_RECOVERY_MAX_RETRIES = 5;
125403
125649
  DURABLE_ERROR_RECOVERY_BASE_COOLDOWN_MS = 3e4;
125404
125650
  DURABLE_ERROR_RECOVERY_MAX_COOLDOWN_MS = 15 * 6e4;
125405
- RUNNING_ON_INACTIVE_TASK_STALE_RUN_MS = 5 * 6e4;
125651
+ RUNNING_ON_INACTIVE_TASK_STALE_RUN_MS = PARKED_AGENT_LINK_FRESH_RUN_MS;
125406
125652
  SelfHealingManager = class _SelfHealingManager {
125407
125653
  constructor(store, options) {
125408
125654
  this.store = store;
@@ -132230,6 +132476,36 @@ var init_self_healing = __esm({
132230
132476
  const exponential = DURABLE_ERROR_RECOVERY_BASE_COOLDOWN_MS * Math.pow(2, clampedAttempts - 1);
132231
132477
  return Math.min(exponential, DURABLE_ERROR_RECOVERY_MAX_COOLDOWN_MS);
132232
132478
  }
132479
+ async emitStaleAgentAssignmentAudit(options) {
132480
+ try {
132481
+ await createRunAuditor(this.store, {
132482
+ runId: generateSyntheticRunId("self-healing-stale-agent-assignment", options.taskId),
132483
+ agentId: "self-healing",
132484
+ taskId: options.taskId,
132485
+ taskLineageId: options.linkedTask?.lineageId,
132486
+ phase: "reconcile-stale-agent-assignment"
132487
+ }).database({
132488
+ type: "task:reconcile-stale-agent-assignment",
132489
+ target: options.agent.id,
132490
+ metadata: {
132491
+ agentId: options.agent.id,
132492
+ taskId: options.taskId,
132493
+ taskColumn: options.linkedTask?.column ?? null,
132494
+ agentState: options.agent.state,
132495
+ status: options.linkedTask?.status ?? null,
132496
+ blockedBy: options.linkedTask?.blockedBy ?? null,
132497
+ overlapBlockedBy: options.linkedTask?.overlapBlockedBy ?? null,
132498
+ hadFreshRun: options.hadFreshRun,
132499
+ hadActiveExecution: options.hadActiveExecution,
132500
+ reason: options.reason
132501
+ }
132502
+ });
132503
+ } catch (error) {
132504
+ log22.warn(
132505
+ `Failed to emit stale agent assignment audit for ${options.agent.id}/${options.taskId}: ${error instanceof Error ? error.message : String(error)}`
132506
+ );
132507
+ }
132508
+ }
132233
132509
  async recoverAgentsRunningOnInactiveTasks() {
132234
132510
  const agentStore = this.options.agentStore;
132235
132511
  if (!agentStore) {
@@ -132247,16 +132523,30 @@ var init_self_healing = __esm({
132247
132523
  continue;
132248
132524
  }
132249
132525
  const activeRun = await agentStore.getActiveHeartbeatRun(agent.id);
132250
- const runStartedAt = activeRun?.startedAt;
132251
- const runAgeMs = runStartedAt ? now - Date.parse(runStartedAt) : Number.POSITIVE_INFINITY;
132252
- const hasFreshRun = Boolean(activeRun) && Number.isFinite(runAgeMs) && runAgeMs <= RUNNING_ON_INACTIVE_TASK_STALE_RUN_MS;
132253
- if (hasFreshRun || this.options.hasActiveAgentExecution?.(agent.id) === true) {
132526
+ const proof = evaluateParkedAgentTaskLink({
132527
+ agent,
132528
+ linkedTask: linkedTask ?? { column: "todo" },
132529
+ activeRun,
132530
+ hasActiveAgentExecution: this.options.hasActiveAgentExecution,
132531
+ now
132532
+ });
132533
+ if (proof.hasFreshRun || proof.hasActiveExecution) {
132254
132534
  continue;
132255
132535
  }
132536
+ const reason = linkedTask ? `running durable agent linked to inactive ${linkedTask.column} task without live execution proof` : "running durable agent linked to missing task without live execution proof";
132537
+ const staleAgentState = agent.state;
132256
132538
  await agentStore.updateAgentState(agent.id, "active");
132257
132539
  await agentStore.syncExecutionTaskLink(agent.id, void 0);
132540
+ await this.emitStaleAgentAssignmentAudit({
132541
+ agent: { id: agent.id, state: staleAgentState },
132542
+ taskId: agent.taskId,
132543
+ linkedTask,
132544
+ hadFreshRun: proof.hasFreshRun,
132545
+ hadActiveExecution: proof.hasActiveExecution,
132546
+ reason
132547
+ });
132258
132548
  recoveredAgentIds.add(agent.id);
132259
- log22.log(`Recovered running durable agent ${agent.id} on inactive task ${agent.taskId}`);
132549
+ log22.log(`Recovered running durable agent ${agent.id} on inactive task ${agent.taskId}; file-scope lease preserved when present`);
132260
132550
  }
132261
132551
  return recoveredAgentIds.size;
132262
132552
  }
@@ -132276,6 +132566,8 @@ var init_self_healing = __esm({
132276
132566
  const linkedTask = await this.store.getTask(linkedTaskId);
132277
132567
  let shouldClear = false;
132278
132568
  let reason = "";
132569
+ let hadFreshRun = false;
132570
+ let hadActiveExecution = false;
132279
132571
  if (!linkedTask) {
132280
132572
  shouldClear = true;
132281
132573
  reason = "linked task missing";
@@ -132287,21 +132579,38 @@ var init_self_healing = __esm({
132287
132579
  reason = `linked task assigned to ${linkedTask.assignedAgentId}`;
132288
132580
  } else if (linkedTask.column === "todo" || linkedTask.column === "triage") {
132289
132581
  const activeRun = await agentStore.getActiveHeartbeatRun(agent.id);
132290
- const runStartedAt = activeRun?.startedAt;
132291
- const runAgeMs = runStartedAt ? now - Date.parse(runStartedAt) : Number.POSITIVE_INFINITY;
132292
- const hasFreshRun = Boolean(activeRun) && Number.isFinite(runAgeMs) && runAgeMs <= RUNNING_ON_INACTIVE_TASK_STALE_RUN_MS;
132293
- const hasActiveExecution = this.options.hasActiveAgentExecution?.(agent.id) === true;
132294
- if (!hasFreshRun && !hasActiveExecution) {
132582
+ const proof = evaluateParkedAgentTaskLink({
132583
+ agent,
132584
+ linkedTask,
132585
+ activeRun,
132586
+ hasActiveAgentExecution: this.options.hasActiveAgentExecution,
132587
+ now
132588
+ });
132589
+ hadFreshRun = proof.hasFreshRun;
132590
+ hadActiveExecution = proof.hasActiveExecution;
132591
+ if (!proof.shouldPreserveParkedLink) {
132295
132592
  shouldClear = true;
132296
- reason = `linked task in queued column ${linkedTask.column} without fresh run`;
132593
+ reason = `linked task in queued column ${linkedTask.column} without fresh run or active execution`;
132297
132594
  }
132298
132595
  }
132299
132596
  if (!shouldClear) {
132300
132597
  continue;
132301
132598
  }
132599
+ const staleAgentState = agent.state;
132600
+ if (agent.state === "running") {
132601
+ await agentStore.updateAgentState(agent.id, "active");
132602
+ }
132302
132603
  await agentStore.syncExecutionTaskLink(agent.id, void 0);
132604
+ await this.emitStaleAgentAssignmentAudit({
132605
+ agent: { id: agent.id, state: staleAgentState },
132606
+ taskId: linkedTaskId,
132607
+ linkedTask,
132608
+ hadFreshRun,
132609
+ hadActiveExecution,
132610
+ reason
132611
+ });
132303
132612
  clearedAgentIds.add(agent.id);
132304
- log22.log(`Cleared drifted durable agent task link for ${agent.id} (${linkedTaskId}): ${reason}`);
132613
+ log22.log(`Cleared drifted durable agent task link for ${agent.id} (${linkedTaskId}): ${reason}; file-scope lease preserved when present`);
132305
132614
  }
132306
132615
  log22.log(`Recovered ${clearedAgentIds.size} drifted durable agent task link(s)`);
132307
132616
  return clearedAgentIds.size;
@@ -135456,6 +135765,10 @@ Remove or replace these ids and call fn_task_create again.`
135456
135765
  // Project-level validator fallback
135457
135766
  projectValidatorFallbackProvider: currentSettings.validatorFallbackProvider,
135458
135767
  projectValidatorFallbackModelId: currentSettings.validatorFallbackModelId,
135768
+ // FNXC:SpecReviewerFallback 2026-06-23-08:50:
135769
+ // Spec review must inherit global/default fallback reviewer model settings when no validator-specific fallback is configured, plus the project settings/prompt payload that reviewer sessions use for memory and custom prompt behavior.
135770
+ fallbackProvider: currentSettings.fallbackProvider,
135771
+ fallbackModelId: currentSettings.fallbackModelId,
135459
135772
  // Global validator lane
135460
135773
  globalValidatorProvider: currentSettings.validatorGlobalProvider,
135461
135774
  globalValidatorModelId: currentSettings.validatorGlobalModelId,
@@ -135467,8 +135780,10 @@ Remove or replace these ids and call fn_task_create again.`
135467
135780
  taskId,
135468
135781
  task: currentDetail,
135469
135782
  userComments: currentUserComments.length > 0 ? currentUserComments : void 0,
135783
+ agentPrompts: currentSettings.agentPrompts,
135470
135784
  agentStore: this.options.agentStore,
135471
135785
  rootDir,
135786
+ settings: currentSettings,
135472
135787
  // Track the spec reviewer's session under this task so it's
135473
135788
  // disposed alongside the main triage session on global pause.
135474
135789
  onSessionCreated: (s3) => this.registerSubagentSession(taskId, s3),
@@ -153878,7 +154193,14 @@ Review the work done in this worktree and evaluate it against the criteria in yo
153878
154193
  const errorMessage = err instanceof Error ? err.message : String(err);
153879
154194
  executorLog.warn(`Child agent ${agentId} failed: ${errorMessage}`);
153880
154195
  } finally {
153881
- this.childSessions.delete(agentId);
154196
+ if (this.childSessions.get(agentId) === childSession) {
154197
+ try {
154198
+ await childSession.dispose();
154199
+ } catch (disposeErr) {
154200
+ executorLog.warn(`Child agent ${agentId} session dispose failed: ${disposeErr instanceof Error ? disposeErr.message : String(disposeErr)}`);
154201
+ }
154202
+ this.childSessions.delete(agentId);
154203
+ }
153882
154204
  this.totalSpawnedCount = Math.max(0, this.totalSpawnedCount - 1);
153883
154205
  }
153884
154206
  }
@@ -158567,6 +158889,7 @@ var init_agent_heartbeat = __esm({
158567
158889
  init_heartbeat_prompt_trim();
158568
158890
  init_room_ambiguity();
158569
158891
  init_room_coordination();
158892
+ init_task_agent_sync();
158570
158893
  promptSizeLog = createLogger2("prompt-size");
158571
158894
  REPORTS_STALE_INTERVAL_MULTIPLIER = 1.5;
158572
158895
  MIN_HEARTBEAT_STALENESS_MS = 10 * 6e4;
@@ -159273,6 +159596,37 @@ Critical: a heartbeat without observable progress (or an explicit no-op reason)
159273
159596
  void this.checkMissedHeartbeats();
159274
159597
  }, this.pollIntervalMs);
159275
159598
  }
159599
+ async emitStaleAgentAssignmentAudit(options) {
159600
+ if (!this.taskStore) return;
159601
+ try {
159602
+ await createRunAuditor(this.taskStore, {
159603
+ runId: generateSyntheticRunId("heartbeat-stale-agent-assignment", options.taskId),
159604
+ agentId: "heartbeat-monitor",
159605
+ taskId: options.taskId,
159606
+ taskLineageId: options.linkedTask?.lineageId,
159607
+ phase: "reconcile-stale-agent-assignment"
159608
+ }).database({
159609
+ type: "task:reconcile-stale-agent-assignment",
159610
+ target: options.agent.id,
159611
+ metadata: {
159612
+ agentId: options.agent.id,
159613
+ taskId: options.taskId,
159614
+ taskColumn: options.linkedTask?.column ?? null,
159615
+ agentState: options.agent.state,
159616
+ status: options.linkedTask?.status ?? null,
159617
+ blockedBy: options.linkedTask?.blockedBy ?? null,
159618
+ overlapBlockedBy: options.linkedTask?.overlapBlockedBy ?? null,
159619
+ hadFreshRun: options.hadFreshRun,
159620
+ hadActiveExecution: options.hadActiveExecution,
159621
+ reason: options.reason
159622
+ }
159623
+ });
159624
+ } catch (error) {
159625
+ heartbeatLog.warn(
159626
+ `Failed to emit stale agent assignment audit for ${options.agent.id}/${options.taskId}: ${error instanceof Error ? error.message : String(error)}`
159627
+ );
159628
+ }
159629
+ }
159276
159630
  /**
159277
159631
  * Find agents in `state="running"` that are not actually running and flip
159278
159632
  * them to `"active"`. An agent is considered orphaned when either:
@@ -159290,8 +159644,8 @@ Critical: a heartbeat without observable progress (or an explicit no-op reason)
159290
159644
  * logged but do not block the caller.
159291
159645
  *
159292
159646
  * Complements SelfHealingManager.recoverAgentsRunningOnInactiveTasks():
159293
- * heartbeat reconciliation handles stale/no-run conditions, while self-healing
159294
- * handles task-column mismatches (for example running agents linked to todo tasks).
159647
+ * heartbeat reconciliation handles stale/no-run conditions and the prompt-critical
159648
+ * parked todo/triage assignment drift before Reports Health Check renders.
159295
159649
  */
159296
159650
  async reconcileOrphanedRunningAgents() {
159297
159651
  try {
@@ -159299,10 +159653,29 @@ Critical: a heartbeat without observable progress (or an explicit no-op reason)
159299
159653
  const now = Date.now();
159300
159654
  for (const agent of runningAgents) {
159301
159655
  let reason = null;
159656
+ let clearTaskLink = false;
159657
+ let taskIdToClear = null;
159658
+ let parkedProof = null;
159659
+ let linkedTask = null;
159302
159660
  const activeRun = await this.store.getActiveHeartbeatRun(agent.id);
159303
- if (!activeRun) {
159661
+ if (!isEphemeralAgent(agent) && agent.taskId && this.taskStore) {
159662
+ linkedTask = await this.taskStore.getTask(agent.taskId);
159663
+ parkedProof = evaluateParkedAgentTaskLink({
159664
+ agent,
159665
+ linkedTask,
159666
+ activeRun,
159667
+ hasActiveAgentExecution: (agentId) => this.trackedAgents.has(agentId),
159668
+ now
159669
+ });
159670
+ if (isParkedTaskColumn(linkedTask) && !parkedProof.shouldPreserveParkedLink) {
159671
+ reason = `parked ${linkedTask.column} task ${agent.taskId} without live execution proof`;
159672
+ clearTaskLink = true;
159673
+ taskIdToClear = agent.taskId;
159674
+ }
159675
+ }
159676
+ if (!reason && !activeRun) {
159304
159677
  reason = "no active run";
159305
- } else if (!this.trackedAgents.has(agent.id)) {
159678
+ } else if (!reason && activeRun && !this.trackedAgents.has(agent.id)) {
159306
159679
  const timeoutMs = this.resolveAgentConfig(agent.id).heartbeatTimeoutMs;
159307
159680
  const heartbeatAgeMs = getHeartbeatAgeMs(agent, now);
159308
159681
  if (!Number.isFinite(heartbeatAgeMs) || heartbeatAgeMs > timeoutMs * 3) {
@@ -159321,9 +159694,21 @@ Critical: a heartbeat without observable progress (or an explicit no-op reason)
159321
159694
  }
159322
159695
  if (!reason) continue;
159323
159696
  try {
159697
+ const staleAgentState = agent.state;
159324
159698
  await this.store.updateAgentState(agent.id, "active");
159699
+ if (clearTaskLink) {
159700
+ await this.store.syncExecutionTaskLink(agent.id, void 0);
159701
+ await this.emitStaleAgentAssignmentAudit({
159702
+ agent: { id: agent.id, state: staleAgentState },
159703
+ taskId: taskIdToClear,
159704
+ linkedTask,
159705
+ hadFreshRun: parkedProof?.hasFreshRun ?? false,
159706
+ hadActiveExecution: parkedProof?.hasActiveExecution ?? false,
159707
+ reason
159708
+ });
159709
+ }
159325
159710
  this.clearRunState(agent.id);
159326
- heartbeatLog.log(`Reconciled orphaned running agent ${agent.id} \u2192 active (${reason})`);
159711
+ heartbeatLog.log(`Reconciled orphaned running agent ${agent.id} \u2192 active (${reason})${clearTaskLink ? "; stale task link cleared" : ""}`);
159327
159712
  } catch (err) {
159328
159713
  heartbeatLog.warn(`Failed to reconcile orphaned running agent ${agent.id}: ${err instanceof Error ? err.message : String(err)}`);
159329
159714
  }
@@ -160876,8 +161261,35 @@ ${trimPromptMd(taskDetail.prompt, promptTemplate)}` : "No PROMPT.md available.",
160876
161261
  );
160877
161262
  const lastHeartbeatTs = report.lastHeartbeatAt ? Date.parse(report.lastHeartbeatAt) : NaN;
160878
161263
  const heartbeatAgeMs = Number.isFinite(lastHeartbeatTs) ? Math.max(0, now - lastHeartbeatTs) : Infinity;
161264
+ let renderedState = report.state;
161265
+ let renderedTask = report.taskId ?? "\u2014";
161266
+ let staleParkedAssignment = false;
161267
+ if (report.state === "running" && !isEphemeralAgent(report) && report.taskId && this.taskStore) {
161268
+ try {
161269
+ const linkedTask = await this.taskStore.getTask(report.taskId);
161270
+ if (isParkedTaskColumn(linkedTask)) {
161271
+ const activeRun = await agentStore.getActiveHeartbeatRun(report.id);
161272
+ const proof = evaluateParkedAgentTaskLink({
161273
+ agent: report,
161274
+ linkedTask,
161275
+ activeRun,
161276
+ hasActiveAgentExecution: (candidateId) => this.trackedAgents.has(candidateId),
161277
+ now
161278
+ });
161279
+ if (!proof.shouldPreserveParkedLink) {
161280
+ staleParkedAssignment = true;
161281
+ renderedState = "active";
161282
+ renderedTask = `${report.taskId} (queued/no live run)`;
161283
+ }
161284
+ }
161285
+ } catch (error) {
161286
+ heartbeatLog.warn(`[reports-health] failed to validate task link for ${report.id}/${report.taskId}: ${error instanceof Error ? error.message : String(error)}`);
161287
+ }
161288
+ }
160879
161289
  let health = "healthy";
160880
- if (report.state === "paused") {
161290
+ if (staleParkedAssignment) {
161291
+ health = "**stale** assignment";
161292
+ } else if (report.state === "paused") {
160881
161293
  health = report.pauseReason ? `paused (${report.pauseReason})` : "paused";
160882
161294
  } else if (report.state === "error") {
160883
161295
  health = "**stuck**";
@@ -160887,8 +161299,8 @@ ${trimPromptMd(taskDetail.prompt, promptTemplate)}` : "No PROMPT.md available.",
160887
161299
  health = "**stale**";
160888
161300
  heartbeatLog.log(`[reports-health] stale report ${report.id} intervalSource=${intervalSource} staleThresholdMs=${staleThresholdMs} heartbeatAgeMs=${heartbeatAgeMs}`);
160889
161301
  }
160890
- const task = report.taskId ?? "\u2014";
160891
- const state = report.state;
161302
+ const task = renderedTask;
161303
+ const state = renderedState;
160892
161304
  const heartbeat = formatRelativeTime(report.lastHeartbeatAt);
160893
161305
  return `| ${report.name} | ${state} | ${task} | ${heartbeat} | ${health} |`;
160894
161306
  }));
@@ -168969,42 +169381,6 @@ var init_node_dispatch_validation = __esm({
168969
169381
  }
168970
169382
  });
168971
169383
 
168972
- // ../engine/src/task-agent-sync.ts
168973
- function attachAgentLinkSync(opts) {
168974
- const logger3 = opts.logger ?? console;
168975
- const handler = async ({ task, from, to: to2 }) => {
168976
- if (!CLEAR_COLUMNS.has(to2)) {
168977
- return;
168978
- }
168979
- try {
168980
- const agents = await opts.agentStore.listAgents({ includeEphemeral: false });
168981
- const linkedAgents = agents.filter((agent) => agent.taskId === task.id);
168982
- for (const agent of linkedAgents) {
168983
- if ((to2 === "todo" || to2 === "triage") && opts.hasActiveAgentExecution?.(agent.id) === true) {
168984
- continue;
168985
- }
168986
- await opts.agentStore.syncExecutionTaskLink(agent.id, void 0);
168987
- logger3.log(`taskAgentLinkSync: cleared agent ${agent.id} taskId from ${task.id} after move ${from} \u2192 ${to2}`);
168988
- }
168989
- } catch (error) {
168990
- logger3.warn(
168991
- `taskAgentLinkSync: failed to sync agents for task ${task.id} after move ${from} \u2192 ${to2}: ${error instanceof Error ? error.message : String(error)}`
168992
- );
168993
- }
168994
- };
168995
- opts.store.on("task:moved", handler);
168996
- return () => {
168997
- opts.store.off("task:moved", handler);
168998
- };
168999
- }
169000
- var CLEAR_COLUMNS;
169001
- var init_task_agent_sync = __esm({
169002
- "../engine/src/task-agent-sync.ts"() {
169003
- "use strict";
169004
- CLEAR_COLUMNS = /* @__PURE__ */ new Set(["done", "archived", "todo", "triage"]);
169005
- }
169006
- });
169007
-
169008
169384
  // ../engine/src/runtimes/in-process-runtime.ts
169009
169385
  import { EventEmitter as EventEmitter22 } from "node:events";
169010
169386
  import { setImmediate as setImmediateCb4 } from "node:timers";
@@ -169259,6 +169635,7 @@ var init_in_process_runtime = __esm({
169259
169635
  maxWorktrees: this.config.maxWorktrees,
169260
169636
  semaphore: this.globalSemaphore,
169261
169637
  agentStore: this.agentStore,
169638
+ hasActiveAgentExecution: (agentId) => this.heartbeatMonitor?.getTrackedAgents().includes(agentId) ?? false,
169262
169639
  missionStore,
169263
169640
  missionAutopilot,
169264
169641
  missionExecutionLoop,
@@ -185806,7 +186183,7 @@ var init_sse = __esm({
185806
186183
  highWaterMark = 0;
185807
186184
  nextConnectionId = 1;
185808
186185
  SSE_CLIENT_ID_MAX_LENGTH = 128;
185809
- SSE_CLIENT_STALE_MS = 5e3;
186186
+ SSE_CLIENT_STALE_MS = 75e3;
185810
186187
  SSE_MAX_BUFFERED_BYTES = 4 * 1024 * 1024;
185811
186188
  managedConnections = /* @__PURE__ */ new Map();
185812
186189
  approvalSseListeners = /* @__PURE__ */ new Set();
@@ -255737,13 +256114,27 @@ function sendCsv(res, filename, table) {
255737
256114
  res.setHeader("Content-Disposition", `attachment; filename="${filename}"`);
255738
256115
  res.send(serializeCsv(table));
255739
256116
  }
255740
- var DEFAULT_WINDOW_DAYS, VALID_GROUP_BY, VALID_TOKEN_GRANULARITY, registerCommandCenterRoutes;
256117
+ async function fetchLatestLiteLLMPricing() {
256118
+ const response = await fetch(LITELLM_PRICING_SOURCE_URL, {
256119
+ signal: AbortSignal.timeout(PRICING_FETCH_TIMEOUT_MS)
256120
+ });
256121
+ if (!response.ok) {
256122
+ const body = await response.text().catch(() => "");
256123
+ throw new ApiError(
256124
+ 502,
256125
+ `Failed to fetch pricing source: ${response.status} ${response.statusText}${body ? `: ${body.slice(0, 200)}` : ""}`
256126
+ );
256127
+ }
256128
+ return response.json();
256129
+ }
256130
+ var DEFAULT_WINDOW_DAYS, VALID_GROUP_BY, VALID_TOKEN_GRANULARITY, PRICING_FETCH_TIMEOUT_MS, registerCommandCenterRoutes;
255741
256131
  var init_register_command_center_routes = __esm({
255742
256132
  "../dashboard/src/routes/register-command-center-routes.ts"() {
255743
256133
  "use strict";
255744
256134
  init_src();
255745
256135
  init_api_error();
255746
256136
  init_command_center_csv();
256137
+ init_project_store_resolver();
255747
256138
  DEFAULT_WINDOW_DAYS = 7;
255748
256139
  VALID_GROUP_BY = /* @__PURE__ */ new Set([
255749
256140
  "model",
@@ -255756,6 +256147,7 @@ var init_register_command_center_routes = __esm({
255756
256147
  "day",
255757
256148
  "week"
255758
256149
  ]);
256150
+ PRICING_FETCH_TIMEOUT_MS = 1e4;
255759
256151
  registerCommandCenterRoutes = (ctx) => {
255760
256152
  const { router, getScopedStore: getScopedStore2, rethrowAsApiError: rethrowAsApiError9 } = ctx;
255761
256153
  router.get("/command-center/tokens", async (req, res) => {
@@ -255764,12 +256156,14 @@ var init_register_command_center_routes = __esm({
255764
256156
  const range2 = resolveRange(req.query);
255765
256157
  const groupBy2 = resolveGroupBy(req.query);
255766
256158
  const granularity = resolveTokenGranularity(req.query);
256159
+ const settings = await store.getGlobalSettingsStore().getSettings();
255767
256160
  const result = aggregateTokenAnalytics(store.getDatabase(), {
255768
256161
  from: range2.from,
255769
256162
  to: range2.to,
255770
256163
  groupBy: groupBy2,
255771
256164
  granularity,
255772
- now: Date.now()
256165
+ now: Date.now(),
256166
+ pricingOverrides: settings.modelPricingOverrides
255773
256167
  });
255774
256168
  if (wantsCsv(req.query)) {
255775
256169
  sendCsv(res, "command-center-tokens.csv", tokenAnalyticsToTable(result));
@@ -255781,6 +256175,31 @@ var init_register_command_center_routes = __esm({
255781
256175
  rethrowAsApiError9(err, "Failed to aggregate token analytics");
255782
256176
  }
255783
256177
  });
256178
+ router.post("/command-center/pricing/fetch", async (req, res) => {
256179
+ try {
256180
+ const store = await getScopedStore2(req);
256181
+ const json = await fetchLatestLiteLLMPricing();
256182
+ const parsed = parseLiteLLMPricing(json);
256183
+ if (parsed.count === 0) {
256184
+ throw new ApiError(502, "No chat-mode pricing entries found in fetched LiteLLM data");
256185
+ }
256186
+ const settings = await store.getGlobalSettingsStore().getSettings();
256187
+ const fetchedAt = (/* @__PURE__ */ new Date()).toISOString();
256188
+ await store.updateGlobalSettings({
256189
+ modelPricingOverrides: {
256190
+ ...settings.modelPricingOverrides ?? {},
256191
+ ...parsed.overrides
256192
+ },
256193
+ modelPricingFetchedAt: fetchedAt,
256194
+ modelPricingSource: LITELLM_PRICING_SOURCE_URL
256195
+ });
256196
+ invalidateAllGlobalSettingsCaches();
256197
+ res.json({ count: parsed.count, fetchedAt, source: LITELLM_PRICING_SOURCE_URL });
256198
+ } catch (err) {
256199
+ if (err instanceof ApiError) throw err;
256200
+ rethrowAsApiError9(err, "Failed to fetch model pricing");
256201
+ }
256202
+ });
255784
256203
  router.get("/command-center/tools", async (req, res) => {
255785
256204
  try {
255786
256205
  const store = await getScopedStore2(req);
@@ -255855,10 +256274,12 @@ var init_register_command_center_routes = __esm({
255855
256274
  try {
255856
256275
  const store = await getScopedStore2(req);
255857
256276
  const range2 = resolveRange(req.query);
256277
+ const settings = await store.getGlobalSettingsStore().getSettings();
255858
256278
  const result = aggregateTeamAnalytics(store.getDatabase(), {
255859
256279
  from: range2.from,
255860
256280
  to: range2.to,
255861
- now: Date.now()
256281
+ now: Date.now(),
256282
+ pricingOverrides: settings.modelPricingOverrides
255862
256283
  });
255863
256284
  res.json(result);
255864
256285
  } catch (err) {
@@ -279068,6 +279489,13 @@ function tuiDebug(tag, data) {
279068
279489
  } catch {
279069
279490
  }
279070
279491
  }
279492
+ function drainTuiPerformanceTimeline() {
279493
+ const perf = globalThis.performance;
279494
+ if (!perf) return;
279495
+ perf.clearMeasures?.();
279496
+ perf.clearMarks?.();
279497
+ perf.clearResourceTimings?.();
279498
+ }
279071
279499
  function openInBrowser(url) {
279072
279500
  let cmd;
279073
279501
  let args;
@@ -282121,6 +282549,9 @@ function DashboardApp({ controller }) {
282121
282549
  const { t } = useTranslation("cli");
282122
282550
  const { exit } = useApp();
282123
282551
  const { stdout } = useStdout();
282552
+ useEffect2(() => {
282553
+ drainTuiPerformanceTimeline();
282554
+ });
282124
282555
  const [resizeTick, setResizeTick] = useState2(0);
282125
282556
  useEffect2(() => {
282126
282557
  if (!stdout) return;
@@ -298957,7 +299388,8 @@ configurePiPackage();
298957
299388
  setInterval(() => {
298958
299389
  performance3.clearMeasures();
298959
299390
  performance3.clearMarks();
298960
- }, 3e4).unref();
299391
+ performance3.clearResourceTimings();
299392
+ }, 1e3).unref();
298961
299393
  function loadEnvFile(path10) {
298962
299394
  if (!existsSync81(path10)) return;
298963
299395
  const contents = readFileSync39(path10, "utf-8");