@runfusion/fusion 0.50.0 → 0.51.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.
- package/dist/bin.js +815 -352
- package/dist/client/.vite/manifest.json +242 -241
- package/dist/client/assets/{AgentDetailView-D2oYUApl.js → AgentDetailView-CjwOlcMI.js} +1 -1
- package/dist/client/assets/{AgentPermissionPolicyEditor-DGgx46f3.js → AgentPermissionPolicyEditor-CD8znNHS.js} +1 -1
- package/dist/client/assets/{AgentsView-Dhf5Vq_T.js → AgentsView-CTjNaKu-.js} +3 -3
- package/dist/client/assets/ChatView-DCWypaw3.js +19 -0
- package/dist/client/assets/{CommandCenter-DzQhGq_J.js → CommandCenter-C4v0FMxx.js} +9 -9
- package/dist/client/assets/{DevServerView-DjJYp6Jj.js → DevServerView-BE_o7v9j.js} +1 -1
- package/dist/client/assets/{DirectoryPicker-CYPYp9T4.js → DirectoryPicker-BpcPo65A.js} +1 -1
- package/dist/client/assets/{DocumentsView-CeQVRVdp.js → DocumentsView-Cpma23hu.js} +1 -1
- package/dist/client/assets/{EvalsView-D250mVR5.js → EvalsView-DYSobhb7.js} +1 -1
- package/dist/client/assets/{ExperimentalAgentOnboardingModal-B1EEY_bl.js → ExperimentalAgentOnboardingModal-D_iQqD-n.js} +1 -1
- package/dist/client/assets/{GoalsView-CUh8JBnJ.css → GoalsView-4-eEOeqw.css} +1 -1
- package/dist/client/assets/{GoalsView-MftVwE9g.js → GoalsView-B6Wfxk59.js} +1 -1
- package/dist/client/assets/{InsightsView-CbWItYe8.js → InsightsView-DZyMvYQ8.js} +1 -1
- package/dist/client/assets/{MemoryView-Ct0nOTES.js → MemoryView-B9Z6cRma.js} +1 -1
- package/dist/client/assets/{PiExtensionsManager-DSQwngX-.js → PiExtensionsManager-BQt3JU60.js} +1 -1
- package/dist/client/assets/{PluginManager-DqLxCiNg.js → PluginManager-DEsUstVo.js} +1 -1
- package/dist/client/assets/PullRequestView-B7k8ogy4.js +6 -0
- package/dist/client/assets/PullRequestView-NkeImY1n.css +1 -0
- package/dist/client/assets/{ResearchView-CJQL9XKC.js → ResearchView-BseF58_R.js} +1 -1
- package/dist/client/assets/{SecretsView-ChN9aTbi.js → SecretsView-bkNS5VBC.js} +1 -1
- package/dist/client/assets/{SessionTerminal-BVon9kbr.js → SessionTerminal-BSllyqgF.js} +1 -1
- package/dist/client/assets/{SettingsModal-CIC5jhgo.js → SettingsModal-DAH7LuLw.js} +1 -1
- package/dist/client/assets/SettingsModal-Dso0K76J.js +21 -0
- package/dist/client/assets/{SetupWizardModal-C6EGjmbd.js → SetupWizardModal-Bm6bG31V.js} +2 -2
- package/dist/client/assets/{ShadcnColorPicker-4e_aXoGX.js → ShadcnColorPicker-CLiOvo7W.js} +1 -1
- package/dist/client/assets/{SkillsView-Dz8dDwfP.js → SkillsView-D_UEJY_f.js} +1 -1
- package/dist/client/assets/{TodoView-DvJFzoOv.js → TodoView-CMCoPep1.js} +1 -1
- package/dist/client/assets/{WorkflowNodeEditor-DrdTCstS.js → WorkflowNodeEditor-BeQcn0Wa.js} +1 -1
- package/dist/client/assets/{agentSkills-B2dD5Nm5.js → agentSkills-BiipFw1z.js} +1 -1
- package/dist/client/assets/{app-D27ewSC0.js → app-A1yCJR4Q.js} +3 -3
- package/dist/client/assets/{app-BYq4S7Cb.js → app-Bfiafek7.js} +3 -3
- package/dist/client/assets/{app-OPrfgjjn.js → app-Bh-Xj32t.js} +3 -3
- package/dist/client/assets/{app-k1K9zAjp.js → app-DxJHfzxz.js} +3 -3
- package/dist/client/assets/{app-dATBczl3.js → app-T5iMjZpX.js} +2 -2
- package/dist/client/assets/{app-DFLDkeOE.js → app-zR_n4GL2.js} +3 -3
- package/dist/client/assets/{architectureDiagram-3BPJPVTR-ClO0MnzR.js → architectureDiagram-3BPJPVTR-glwWkaDt.js} +1 -1
- package/dist/client/assets/{blockDiagram-GPEHLZMM-C4cyy4WE.js → blockDiagram-GPEHLZMM-C8DhxwG4.js} +1 -1
- package/dist/client/assets/{c4Diagram-AAUBKEIU-NHPC9TJo.js → c4Diagram-AAUBKEIU-CQXf6PzI.js} +1 -1
- package/dist/client/assets/channel-BY6zFSV1.js +1 -0
- package/dist/client/assets/{chunk-2J33WTMH-CGYIKTVR.js → chunk-2J33WTMH-kak65s83.js} +1 -1
- package/dist/client/assets/{chunk-4BX2VUAB-BkCCD0tE.js → chunk-4BX2VUAB-B_yEpoxU.js} +1 -1
- package/dist/client/assets/{chunk-55IACEB6-Be3yOIDe.js → chunk-55IACEB6-C9lQVeH5.js} +1 -1
- package/dist/client/assets/{chunk-727SXJPM-DxjyMaQQ.js → chunk-727SXJPM-D4SVzqNs.js} +1 -1
- package/dist/client/assets/{chunk-AQP2D5EJ-BC0PjN0W.js → chunk-AQP2D5EJ-DBHuj4BH.js} +1 -1
- package/dist/client/assets/{chunk-FMBD7UC4-DoWxmM8k.js → chunk-FMBD7UC4-BJ3ghjau.js} +1 -1
- package/dist/client/assets/{chunk-ND2GUHAM-EOpuc9pP.js → chunk-ND2GUHAM-CE4tkh6e.js} +1 -1
- package/dist/client/assets/{chunk-QZHKN3VN-DiRyx9QZ.js → chunk-QZHKN3VN-DmVw_cEu.js} +1 -1
- package/dist/client/assets/classDiagram-4FO5ZUOK-CeX_WCiC.js +1 -0
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2-CeX_WCiC.js +1 -0
- package/dist/client/assets/{cose-bilkent-S5V4N54A-CfVoUUxT.js → cose-bilkent-S5V4N54A-DI_-BOYE.js} +1 -1
- package/dist/client/assets/{dagre-BM42HDAG-BfILvxLb.js → dagre-BM42HDAG-ywUbHZxt.js} +1 -1
- package/dist/client/assets/{dashboard-view-wFC1pe4d.js → dashboard-view-DJvdban3.js} +1 -1
- package/dist/client/assets/{dashboard-view-C9YIcx-O.js → dashboard-view-r4NTTh_X.js} +1633 -1633
- package/dist/client/assets/{diagram-2AECGRRQ-6mdmbH-5.js → diagram-2AECGRRQ-CAGucHCe.js} +1 -1
- package/dist/client/assets/{diagram-5GNKFQAL-CY7MMwO1.js → diagram-5GNKFQAL-D_1uzVtW.js} +1 -1
- package/dist/client/assets/{diagram-KO2AKTUF-D05XnQqd.js → diagram-KO2AKTUF-D7zY4Nlm.js} +1 -1
- package/dist/client/assets/{diagram-LMA3HP47-B18FTOJG.js → diagram-LMA3HP47-C1T9eOzA.js} +1 -1
- package/dist/client/assets/{diagram-OG6HWLK6-xmXlYf5m.js → diagram-OG6HWLK6-sWxhPvzU.js} +1 -1
- package/dist/client/assets/{erDiagram-TEJ5UH35-4XlgVLCs.js → erDiagram-TEJ5UH35-DfHtnaNg.js} +1 -1
- package/dist/client/assets/{flowDiagram-I6XJVG4X-BYl3ps7f.js → flowDiagram-I6XJVG4X-Brbb_Jlh.js} +1 -1
- package/dist/client/assets/{folder-open-BH6FwmO7.js → folder-open-BLwukrE0.js} +1 -1
- package/dist/client/assets/{ganttDiagram-6RSMTGT7-4vxuH9Dx.js → ganttDiagram-6RSMTGT7-CLvZ7891.js} +1 -1
- package/dist/client/assets/{gitGraphDiagram-PVQCEYII-BESmFgXT.js → gitGraphDiagram-PVQCEYII-DD56Jsa3.js} +1 -1
- package/dist/client/assets/index-DAv17W73.js +1317 -0
- package/dist/client/assets/index-DhBnj8YN.css +1 -0
- package/dist/client/assets/{infoDiagram-5YYISTIA-DvjdjtwB.js → infoDiagram-5YYISTIA-uXai801q.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-Cis5IoEm.js → ishikawaDiagram-YF4QCWOH-C_bWlUP_.js} +1 -1
- package/dist/client/assets/{journeyDiagram-JHISSGLW-C20pB1W8.js → journeyDiagram-JHISSGLW-B2kB9niE.js} +1 -1
- package/dist/client/assets/{kanban-definition-UN3LZRKU-CiVfCeU3.js → kanban-definition-UN3LZRKU-BJwlpR9z.js} +1 -1
- package/dist/client/assets/{mermaid.core-CW0vCXA6.js → mermaid.core-0hUjZF5S.js} +4 -4
- package/dist/client/assets/{mindmap-definition-RKZ34NQL-E5rPHUdw.js → mindmap-definition-RKZ34NQL-DsTgTpX-.js} +1 -1
- package/dist/client/assets/{pieDiagram-4H26LBE5-DVIFbPjH.js → pieDiagram-4H26LBE5-BUQsNWHN.js} +1 -1
- package/dist/client/assets/{quadrantDiagram-W4KKPZXB-C8qPRe_x.js → quadrantDiagram-W4KKPZXB-DOYWgfAz.js} +1 -1
- package/dist/client/assets/{requirementDiagram-4Y6WPE33-DQ-pwXh3.js → requirementDiagram-4Y6WPE33-DUnPKaMd.js} +1 -1
- package/dist/client/assets/{sankeyDiagram-5OEKKPKP-BnghLuUc.js → sankeyDiagram-5OEKKPKP-Btl4NRo-.js} +1 -1
- package/dist/client/assets/{sequenceDiagram-3UESZ5HK-ChlGxGVk.js → sequenceDiagram-3UESZ5HK-b0hyJ_Wr.js} +1 -1
- package/dist/client/assets/{shield-alert-BfOnDO6O.js → shield-alert-yWvC7t2v.js} +1 -1
- package/dist/client/assets/{stateDiagram-AJRCARHV-ByUvNEch.js → stateDiagram-AJRCARHV-orREjkd9.js} +1 -1
- package/dist/client/assets/{stateDiagram-v2-BHNVJYJU-Df38ITSP.js → stateDiagram-v2-BHNVJYJU-DYJRRNh-.js} +1 -1
- package/dist/client/assets/{timeline-definition-PNZ67QCA-bsAGc83D.js → timeline-definition-PNZ67QCA-DZga7lqO.js} +1 -1
- package/dist/client/assets/{upload-BK1crTqn.js → upload-BsYRkl_8.js} +1 -1
- package/dist/client/assets/{users-DW8jC2Z9.js → users-dyoJEvrM.js} +1 -1
- package/dist/client/assets/{vennDiagram-CIIHVFJN-BBIj6LFN.js → vennDiagram-CIIHVFJN-C2P26V71.js} +1 -1
- package/dist/client/assets/{wardley-L42UT6IY-De6JFroy.js → wardley-L42UT6IY-ckctH9bL.js} +1 -1
- package/dist/client/assets/{wardleyDiagram-YWT4CUSO-P1LOLx0_.js → wardleyDiagram-YWT4CUSO-9It3QAYs.js} +1 -1
- package/dist/client/assets/{xychartDiagram-2RQKCTM6-v1G4ki6U.js → xychartDiagram-2RQKCTM6-Dj3sSBqg.js} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/client/version.json +1 -1
- package/dist/droid-cli/package.json +1 -1
- package/dist/extension.js +584 -296
- package/dist/pi-claude-cli/package.json +1 -1
- package/dist/plugin-sdk/index.d.ts +6 -0
- package/dist/plugins/fusion-plugin-acp-runtime/package.json +1 -1
- package/dist/plugins/fusion-plugin-cli-printing-press/package.json +1 -1
- package/dist/plugins/fusion-plugin-compound-engineering/bundled.js +1 -1
- package/dist/plugins/fusion-plugin-compound-engineering/manifest.json +1 -1
- package/dist/plugins/fusion-plugin-compound-engineering/package.json +1 -1
- package/dist/plugins/fusion-plugin-cursor-runtime/package.json +1 -1
- package/dist/plugins/fusion-plugin-dependency-graph/package.json +1 -1
- package/dist/plugins/fusion-plugin-droid-runtime/package.json +1 -1
- package/dist/plugins/fusion-plugin-hermes-runtime/package.json +1 -1
- package/dist/plugins/fusion-plugin-openclaw-runtime/package.json +1 -1
- package/dist/plugins/fusion-plugin-paperclip-runtime/package.json +1 -1
- package/dist/plugins/fusion-plugin-reports/package.json +1 -1
- package/dist/plugins/fusion-plugin-roadmap/package.json +1 -1
- package/dist/plugins/fusion-plugin-whatsapp-chat/package.json +1 -1
- package/package.json +1 -1
- package/skill/fusion/references/best-practices.md +3 -3
- package/skill/fusion/references/extension-tools.md +1 -1
- package/skill/fusion/references/fusion-capabilities.md +1 -1
- package/skill/fusion/workflows/task-management.md +1 -1
- package/dist/client/assets/ChatView-DpgpxHva.js +0 -19
- package/dist/client/assets/PullRequestView-CpdE3Yo_.css +0 -1
- package/dist/client/assets/PullRequestView-D0WgwdAi.js +0 -6
- package/dist/client/assets/SettingsModal-B4WANQnu.js +0 -21
- package/dist/client/assets/channel-Q2WKocEP.js +0 -1
- package/dist/client/assets/classDiagram-4FO5ZUOK-CPFl2Aqd.js +0 -1
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2-CPFl2Aqd.js +0 -1
- package/dist/client/assets/index-B9jtBwFy.css +0 -1
- package/dist/client/assets/index-DFeBJ3GM.js +0 -1302
package/dist/extension.js
CHANGED
|
@@ -564,6 +564,20 @@ var init_mcp_config = __esm({
|
|
|
564
564
|
}
|
|
565
565
|
});
|
|
566
566
|
|
|
567
|
+
// ../core/src/task-helpers.ts
|
|
568
|
+
function getPrimaryPrInfo(task) {
|
|
569
|
+
return task.prInfos?.[0] ?? task.prInfo;
|
|
570
|
+
}
|
|
571
|
+
function taskHasManualOpenPullRequest(task) {
|
|
572
|
+
const prs = task.prInfos ?? (task.prInfo ? [task.prInfo] : []);
|
|
573
|
+
return prs.some((pr2) => pr2.manual === true && (pr2.status === "open" || pr2.status === "draft"));
|
|
574
|
+
}
|
|
575
|
+
var init_task_helpers = __esm({
|
|
576
|
+
"../core/src/task-helpers.ts"() {
|
|
577
|
+
"use strict";
|
|
578
|
+
}
|
|
579
|
+
});
|
|
580
|
+
|
|
567
581
|
// ../core/src/task-merge.ts
|
|
568
582
|
function isFusionSiblingBranch(branch) {
|
|
569
583
|
return FUSION_SIBLING_BRANCH_RE.test(branch);
|
|
@@ -572,7 +586,7 @@ function resolveEffectiveAutoMerge(task, settings) {
|
|
|
572
586
|
return task.autoMerge ?? settings.autoMerge;
|
|
573
587
|
}
|
|
574
588
|
function allowsAutoMergeProcessing(task, settings) {
|
|
575
|
-
return settings.autoMerge !== false || task.autoMerge === true;
|
|
589
|
+
return (settings.autoMerge !== false || task.autoMerge === true) && !taskHasManualOpenPullRequest(task);
|
|
576
590
|
}
|
|
577
591
|
function resolveEffectiveGroupAutoMerge(group, settings) {
|
|
578
592
|
return group.autoMerge ?? settings.autoMerge;
|
|
@@ -688,6 +702,7 @@ var FUSION_SIBLING_BRANCH_RE, HARD_BLOCKING_TASK_STATUSES, SCHEDULER_TRANSIENT_S
|
|
|
688
702
|
var init_task_merge = __esm({
|
|
689
703
|
"../core/src/task-merge.ts"() {
|
|
690
704
|
"use strict";
|
|
705
|
+
init_task_helpers();
|
|
691
706
|
FUSION_SIBLING_BRANCH_RE = /^fusion\/fn-/i;
|
|
692
707
|
HARD_BLOCKING_TASK_STATUSES = /* @__PURE__ */ new Set([
|
|
693
708
|
"failed",
|
|
@@ -1244,6 +1259,8 @@ var init_settings_schema = __esm({
|
|
|
1244
1259
|
testCommand: void 0,
|
|
1245
1260
|
buildCommand: void 0,
|
|
1246
1261
|
recycleWorktrees: false,
|
|
1262
|
+
showWorktreeGrouping: false,
|
|
1263
|
+
openTasksInRightSidebar: false,
|
|
1247
1264
|
executorAllowSiblingBranchRename: false,
|
|
1248
1265
|
worktreeNaming: "random",
|
|
1249
1266
|
worktrunk: {
|
|
@@ -1411,6 +1428,8 @@ var init_settings_schema = __esm({
|
|
|
1411
1428
|
titleSummarizerModelId: void 0,
|
|
1412
1429
|
titleSummarizerFallbackProvider: void 0,
|
|
1413
1430
|
titleSummarizerFallbackModelId: void 0,
|
|
1431
|
+
prTitlePromptInstructions: void 0,
|
|
1432
|
+
prDescriptionPromptInstructions: void 0,
|
|
1414
1433
|
scripts: void 0,
|
|
1415
1434
|
setupScript: void 0,
|
|
1416
1435
|
insightExtractionEnabled: false,
|
|
@@ -4611,19 +4630,21 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
4611
4630
|
- Run **workspace-scoped non-test gates** (\`pnpm lint\`, \`pnpm build\`, and typecheck commands from root) when required for completion, but keep test verification targeted unless explicit task/workflow instructions require a full run.
|
|
4612
4631
|
- If you need to run \`pnpm install\` (e.g. you added a new package), use \`fn_run_verification\` with \`scope: "workspace"\` and \`timeoutSec: 600\`.
|
|
4613
4632
|
- If a verification command times out, do NOT blindly retry \u2014 investigate. Check for hung subprocesses, infinite test loops, or tests waiting on missing dependencies. Use \`node_modules/.modules.yaml\` presence to confirm bootstrap.`;
|
|
4614
|
-
FAST_TRIAGE_PROMPT_TEXT = `You are a task specification agent for "fn"
|
|
4615
|
-
|
|
4616
|
-
## Your Role
|
|
4617
|
-
You are a fast-path spec writer. Keep output lean but executable, with enough precision that an executor can run immediately.
|
|
4633
|
+
FAST_TRIAGE_PROMPT_TEXT = `You are a task specification agent for "fn". This task is running in **fast mode**.
|
|
4618
4634
|
|
|
4619
|
-
|
|
4635
|
+
Write a lean, executable PROMPT.md quickly. Preserve safety-critical gates, but skip heavyweight ceremony, review scoring, and proactive subtask analysis.
|
|
4620
4636
|
|
|
4621
|
-
##
|
|
4622
|
-
|
|
4637
|
+
## Fast-mode priorities
|
|
4638
|
+
- Read only the source/docs needed to make the spec precise.
|
|
4639
|
+
- Keep prose brief; use concrete file paths, commands, and expected outcomes.
|
|
4640
|
+
- Do not expand scope. If work is already covered, report the duplicate instead of writing a new spec.
|
|
4641
|
+
- Keep the selected workflow unchanged unless the user explicitly requested a workflow for this task.
|
|
4623
4642
|
|
|
4624
|
-
##
|
|
4643
|
+
## Duplicate check
|
|
4644
|
+
Before writing a spec, call \`fn_task_list\` for active work, then call \`fn_task_search\` with 2-4 targeted keyword phrases from the title/description, such as file paths, symptoms, and symbols. For any likely match in \`done\` or \`archived\`, call \`fn_task_show\` and inspect it before deciding. If an existing task covers the same work, do not write PROMPT.md; write exactly \`DUPLICATE: {existing-task-id}\`.
|
|
4625
4645
|
|
|
4626
|
-
|
|
4646
|
+
## Required PROMPT.md shape
|
|
4647
|
+
Write a real PROMPT.md to the requested path using the write tool:
|
|
4627
4648
|
|
|
4628
4649
|
\`\`\`markdown
|
|
4629
4650
|
# Task: {ID} - {Name}
|
|
@@ -4632,205 +4653,69 @@ Follow this structure exactly:
|
|
|
4632
4653
|
**Size:** {S | M}
|
|
4633
4654
|
|
|
4634
4655
|
## Mission
|
|
4635
|
-
|
|
4636
|
-
{One paragraph: what to build and why it matters}
|
|
4637
|
-
|
|
4638
|
-
## Surface Enumeration
|
|
4639
|
-
|
|
4640
|
-
{Required for bug-fix tasks and UI-affordance add/remove tasks (adding, removing, or restructuring icons, buttons, chevrons/arrows, toggles, badges, menu entries, click targets): a checklist enumerating every surface the fixed invariant must hold across. Include every provider/bridge for streaming and agent paths; desktop AND mobile breakpoints; empty/undefined/duplicate/populated data states; and every hook/component/module that shares the affected logic. For UI-affordance add/remove tasks, enumerate every component that renders the affordance by searching the codebase for the icon/class/testid \u2014 not just the component the user pointed at. Explicitly check for leftover shells after removal (empty buttons, orphaned click targets, now-unused wrappers, dangling aria-labels) across both desktop and mobile breakpoints. Use the canonical checklist in docs/testing.md as the starting point.}
|
|
4641
|
-
|
|
4642
|
-
## Symptom Verification
|
|
4643
|
-
|
|
4644
|
-
{Required for bug-class/bug-fix tasks only; feature/docs/non-bug tasks do not need this section. Use the exact heading \`## Symptom Verification\` and include: (1) **Original symptom** \u2014 what the user/issue reported was broken; (2) **Exact reproduction** \u2014 the precise steps, inputs, fixture, or automated repro that triggered the failure; (3) **Assertion it is gone** \u2014 the executor's final verification must reproduce that original failure condition and assert it no longer occurs via a real automated test. Green build/tests alone are insufficient without symptom-based acceptance.}
|
|
4656
|
+
{One short paragraph describing the outcome and why it matters.}
|
|
4645
4657
|
|
|
4646
4658
|
## Dependencies
|
|
4647
|
-
|
|
4648
4659
|
- **None**
|
|
4649
|
-
{
|
|
4650
|
-
- **Task:** {ID} ({what must be complete first})
|
|
4660
|
+
{or list concrete task dependencies after inspecting them with fn_task_show.}
|
|
4651
4661
|
|
|
4652
4662
|
## Context to Read First
|
|
4653
|
-
|
|
4654
|
-
{List the minimal, specific files needed for implementation}
|
|
4663
|
+
- \`specific/file-or-doc.md\` \u2014 why it matters
|
|
4655
4664
|
|
|
4656
4665
|
## File Scope
|
|
4657
|
-
|
|
4658
|
-
{List exact files/directories expected to change}
|
|
4659
|
-
|
|
4660
|
-
- \`path/to/file.ext\`
|
|
4661
|
-
- \`path/to/directory/*\`
|
|
4666
|
+
- \`expected/file/or/directory\`
|
|
4662
4667
|
|
|
4663
4668
|
## Steps
|
|
4664
|
-
|
|
4665
|
-
> Optional: a step heading may carry a \`(depends: N,M)\` annotation listing the 1-indexed
|
|
4666
|
-
> step numbers it depends on \u2014 e.g. \`### Step 3 (depends: 1): Title\`. Annotate ONLY steps
|
|
4667
|
-
> that are genuinely independent of their immediate predecessor; an unannotated step is
|
|
4668
|
-
> assumed to depend on the one before it (fully sequential). Be conservative \u2014 only mark a
|
|
4669
|
-
> step independent when it truly does not read or modify the prior step's output.
|
|
4670
|
-
|
|
4671
4669
|
### Step 0: Preflight
|
|
4670
|
+
- [ ] Confirm required context, dependencies, and paths.
|
|
4672
4671
|
|
|
4673
|
-
|
|
4674
|
-
- [ ]
|
|
4672
|
+
### Step 1: {Outcome-oriented implementation step}
|
|
4673
|
+
- [ ] {Specific verifiable outcome.}
|
|
4674
|
+
- [ ] {Specific verifiable outcome.}
|
|
4675
4675
|
|
|
4676
|
-
### Step 1:
|
|
4676
|
+
### Step N-1: Testing & Verification
|
|
4677
|
+
- [ ] Run targeted real automated tests in the project test runner; typecheck/build/manual checks do not replace tests.
|
|
4678
|
+
- [ ] Run lint/typecheck/build commands required by the task or repo.
|
|
4677
4679
|
|
|
4678
|
-
|
|
4679
|
-
- [ ]
|
|
4680
|
-
- [ ]
|
|
4681
|
-
|
|
4682
|
-
For bug-fix and UI-affordance add/remove tasks, paste and fill in this checklist in the \`## Surface Enumeration\` section:
|
|
4683
|
-
- [ ] Providers / bridges / execution paths touched by the invariant
|
|
4684
|
-
- [ ] Desktop + mobile breakpoints / platforms that exercise the behavior
|
|
4685
|
-
- [ ] Empty / undefined / duplicate / populated data states
|
|
4686
|
-
- [ ] Shared hooks / components / modules / helpers reusing the logic
|
|
4687
|
-
- [ ] Every component that renders the affordance (search the codebase for the icon/class/testid, not just the one the user pointed at)
|
|
4688
|
-
- [ ] Leftover shells after removal \u2014 empty buttons, orphaned click targets, now-unused wrappers, dangling aria-labels \u2014 are explicitly checked and fixed/hidden
|
|
4689
|
-
|
|
4690
|
-
For bug-class/bug-fix tasks, add and fill in the exact \`## Symptom Verification\` section:
|
|
4691
|
-
- [ ] **Original symptom** \u2014 what the user/issue reported was broken
|
|
4692
|
-
- [ ] **Exact reproduction** \u2014 the precise steps, inputs, fixture, or automated repro that triggered the failure
|
|
4693
|
-
- [ ] **Assertion it is gone** \u2014 final verification reproduces the original failure condition and asserts it no longer occurs via a real automated test; green build/tests alone are insufficient
|
|
4694
|
-
|
|
4695
|
-
**Artifacts:**
|
|
4696
|
-
- \`path/to/file\` (new | modified)
|
|
4697
|
-
|
|
4698
|
-
### Step {N-1}: Testing & Verification
|
|
4699
|
-
|
|
4700
|
-
> ZERO failures allowed for checks required by this task's quality gates. Run impacted/package-scoped verification first. Do NOT run the full workspace test suite (\`pnpm test:full\`, \`pnpm verify:workspace\`, or whole-package \`pnpm --filter <pkg> test\`) as routine or final-integration verification \u2014 a full run is allowed ONLY when the task or workflow explicitly requires it.
|
|
4701
|
-
> If keeping lint/tests/build/typecheck green requires edits outside the initial File Scope, make those fixes as part of this task.
|
|
4702
|
-
|
|
4703
|
-
- [ ] Run lint check (\`pnpm lint\`)
|
|
4704
|
-
- [ ] Run impacted tests
|
|
4705
|
-
- [ ] Run project typecheck if available
|
|
4706
|
-
- [ ] Build passes
|
|
4707
|
-
|
|
4708
|
-
### Step {N}: Documentation & Delivery
|
|
4709
|
-
|
|
4710
|
-
- [ ] Update relevant documentation
|
|
4711
|
-
- [ ] Save documentation deliverables as task documents via \`fn_task_document_write\` (key="docs", content=...)
|
|
4712
|
-
- [ ] Create out-of-scope follow-up tasks via \`fn_task_create\` when needed
|
|
4680
|
+
### Step N: Documentation & Delivery
|
|
4681
|
+
- [ ] Update required docs/changesets/task documents.
|
|
4682
|
+
- [ ] Create follow-up tasks for out-of-scope findings.
|
|
4713
4683
|
|
|
4714
4684
|
## Documentation Requirements
|
|
4715
|
-
|
|
4716
4685
|
**Must Update:**
|
|
4717
|
-
- \`path/to/doc.md\` \u2014
|
|
4686
|
+
- \`path/to/doc-or-changeset.md\` \u2014 required update, or **None**
|
|
4718
4687
|
|
|
4719
4688
|
**Check If Affected:**
|
|
4720
|
-
- \`path/to/doc.md\` \u2014
|
|
4689
|
+
- \`path/to/doc.md\` \u2014 update if impacted
|
|
4721
4690
|
|
|
4722
4691
|
## Completion Criteria
|
|
4723
|
-
|
|
4724
|
-
- [ ]
|
|
4725
|
-
- [ ]
|
|
4726
|
-
- [ ] All tests passing
|
|
4727
|
-
- [ ] Typecheck passing (if available)
|
|
4728
|
-
- [ ] Documentation updated
|
|
4692
|
+
- [ ] Implementation outcomes complete
|
|
4693
|
+
- [ ] Required automated tests, lint/typecheck/build pass
|
|
4694
|
+
- [ ] Documentation/delivery complete
|
|
4729
4695
|
|
|
4730
4696
|
## Git Commit Convention
|
|
4731
|
-
|
|
4732
|
-
Commits at step boundaries. All commits include the task ID:
|
|
4733
|
-
|
|
4734
|
-
- **Step completion:** \`feat({ID}): complete Step N \u2014 <short summary>\` (the \`<short summary>\` is required \u2014 use a concrete 5\u201310 word description)
|
|
4735
|
-
- **Bug fixes:** \`fix({ID}): description\` (short, concrete summary required)
|
|
4736
|
-
- **Tests:** \`test({ID}): description\` (short, concrete summary required)
|
|
4737
|
-
|
|
4738
|
-
Good examples:
|
|
4739
|
-
- \`feat(FN-1234): complete Step 2 \u2014 add retry guard for workflow step timeouts\`
|
|
4740
|
-
- \`test(FN-1234): add regression tests for paused-session cleanup\`
|
|
4741
|
-
|
|
4742
|
-
Bad example:
|
|
4743
|
-
- \`feat(FN-1234): complete Step 2\`
|
|
4697
|
+
- Use task ID-prefixed conventional commits, e.g. \`feat({ID}): complete Step 1 \u2014 concrete summary\`.
|
|
4744
4698
|
|
|
4745
4699
|
## Do NOT
|
|
4746
|
-
|
|
4747
|
-
- Expand task scope
|
|
4748
|
-
- Skip tests
|
|
4749
|
-
- Refuse necessary fixes just because they touch files outside the initial File Scope
|
|
4750
|
-
- Commit without the task ID prefix
|
|
4751
|
-
- Remove, delete, or gut modules, settings, interfaces, exports, or test files outside the File Scope
|
|
4752
|
-
- Remove features as "cleanup" \u2014 if something seems unused, create a task via \`fn_task_create\`
|
|
4753
|
-
|
|
4754
|
-
## Changeset Requirements
|
|
4755
|
-
|
|
4756
|
-
If this task REMOVES existing functionality (deleting modules, settings, API endpoints, or exports), a changeset file is REQUIRED:
|
|
4757
|
-
- Create \`.changeset/{task-id}-removal.md\` explaining what was removed and why
|
|
4758
|
-
- This is mandatory for any net-negative change (more deletions than additions to existing files)
|
|
4700
|
+
- Expand scope, skip tests, weaken acceptance, or delete/gut unrelated modules/features.
|
|
4759
4701
|
\`\`\`
|
|
4760
4702
|
|
|
4761
|
-
##
|
|
4762
|
-
-
|
|
4763
|
-
-
|
|
4764
|
-
- For bug fixes and UI-affordance add/remove tasks, the spec MUST include a \`## Surface Enumeration\` section. During self-review via \`fn_review_spec()\`, treat a missing section on a bug-fix or UI-affordance add/remove spec as a blocking REVISE.
|
|
4765
|
-
- For bug fixes and UI-affordance add/remove tasks, populate \`## Surface Enumeration\` with this checklist from \`docs/testing.md\`: providers/bridges/execution paths; desktop + mobile breakpoints/platforms; empty/undefined/duplicate/populated data states; shared hooks/components/modules/helpers; every component that renders the affordance; leftover shells after removal.
|
|
4766
|
-
- For bug fixes and UI-affordance add/remove tasks, regression tests must assert the invariant across all known surfaces \u2014 enumerate every provider/bridge, desktop + mobile breakpoints, empty/undefined/populated data states, and for UI-affordance changes every component rendering the affordance plus leftover shells after removal \u2014 not just the reported repro (see FN-5787/FN-5789/FN-5803, FN-5751, and FN-6115/FN-6118/FN-6123)
|
|
4767
|
-
- For bug-class/bug-fix tasks, the spec MUST include a \`## Symptom Verification\` section with **Original symptom**, **Exact reproduction**, and **Assertion it is gone**. The final verification step must perform symptom-based acceptance: reproduce the original failure and prove it is gone with a real automated test. Green build/tests alone are insufficient. Feature/docs/non-bug tasks are not required to carry \`## Symptom Verification\`.
|
|
4768
|
-
- Include targeted tests in implementation steps and bounded/changed-scoped quality-gate runs in final verification; never run the full workspace test suite unless the task explicitly requires it
|
|
4769
|
-
|
|
4770
|
-
## Duplicate check
|
|
4771
|
-
Before writing a spec, call \`fn_task_list\` to find existing active tasks, then call \`fn_task_search\` with 2-4 distinct keyword phrases from the task title and description (for example file paths, error symptoms, and symbol names).
|
|
4772
|
-
For any likely match in \`done\` or \`archived\`, call \`fn_task_show\` to inspect details before deciding.
|
|
4773
|
-
If an existing task already covers the same work, do NOT write a PROMPT.md. Instead write exactly:
|
|
4774
|
-
\`DUPLICATE: {existing-task-id}\`
|
|
4775
|
-
|
|
4776
|
-
## Dependency awareness
|
|
4777
|
-
When adding a dependency in \`## Dependencies\`, first call \`fn_task_show\` for that task and read its PROMPT.md.
|
|
4778
|
-
Use that context to align file paths, APIs, assumptions, and completion expectations. If the dependency has no PROMPT.md yet, note that explicitly.
|
|
4779
|
-
|
|
4780
|
-
## Decision-only task flag (noCommitsExpected)
|
|
4781
|
-
When ALL of the following are true, include this metadata line in the header block after Size:
|
|
4782
|
-
|
|
4783
|
-
- Add this exact line: **No commits expected:** true
|
|
4784
|
-
|
|
4785
|
-
Set it only when all of these conditions hold:
|
|
4786
|
-
- Title/mission starts with decision verbs like "Decide", "Evaluate", "Verify", "Confirm", "Audit", "Review whether", or "Investigate and report", OR is an operational routing/coordination task whose only outcome is assigning/routing existing work or recording an intentional no-route/no-owner decision
|
|
4787
|
-
- Acceptance criteria are strictly observational (record findings, routing evidence, no-route/no-owner state, log a decision, update task log/docs) with no required code/config/file mutations
|
|
4788
|
-
- Task description explicitly says things like "no code changes expected", "no source files expected", "no product-source changes", or "the deliverable is the recorded decision"
|
|
4789
|
-
|
|
4790
|
-
Anti-heuristics (bias to false-negative when ambiguous):
|
|
4791
|
-
- SET: Decide whether FN-XYZ needs a fix
|
|
4792
|
-
- SET: Assign ready implementation task to active owner, or record no-route state (no source files expected)
|
|
4793
|
-
- LEAVE UNSET: Investigate FN-XYZ
|
|
4794
|
-
- LEAVE UNSET: Investigate FN-XYZ and fix if needed
|
|
4795
|
-
- LEAVE UNSET: Investigate and fix routing if needed
|
|
4796
|
-
|
|
4797
|
-
If an executor later proves an ordinary implementation task is already satisfied on HEAD, it may close without fabricating a commit by calling \`fn_task_done\` with a leading verified no-op/duplicate sentinel summary: \`PREMISE STALE:\`, \`NO-OP:\`, \`NOOP:\`, \`DUPLICATE: FN-NNNN ...\`, or \`REDUNDANT:\`. This does not weaken ordinary tasks: zero-commit completions without one of these leading sentinels still fail the no-commits invariant.
|
|
4798
|
-
|
|
4799
|
-
## Guidelines
|
|
4800
|
-
- Read relevant source files before writing the spec
|
|
4801
|
-
- Be specific: reference concrete files, modules, and commands from this repo
|
|
4802
|
-
- Keep steps outcome-focused with 2\u20134 checkboxes per step
|
|
4803
|
-
- Keep file scope realistic: include tests and integration touchpoints likely required for green quality gates
|
|
4804
|
-
- Always include Testing & Verification and Documentation & Delivery steps
|
|
4805
|
-
- Keep fast-mode scope lean and executable; do not add heavyweight review scoring or subtask-analysis sections
|
|
4806
|
-
|
|
4807
|
-
## Project commands
|
|
4808
|
-
When the user prompt includes explicit test/build commands, use those exact commands in the generated spec.
|
|
4809
|
-
|
|
4810
|
-
<!--
|
|
4811
|
-
FNXC:WorkflowRouting 2026-06-22-17:24:
|
|
4812
|
-
Fast triage must keep tasks on the project default workflow unless the user explicitly asked for a specific workflow or the agent created the task. The no-commits header remains a PROMPT.md marker only; it is not permission to select a lightweight workflow automatically.
|
|
4813
|
-
-->
|
|
4814
|
-
## Workflow Routing
|
|
4815
|
-
Keep the project default workflow (\`builtin:coding\`) unless the user explicitly requested a specific workflow for this task or subtask, or you created that task yourself. Do NOT call \`fn_workflow_select\` or pass \`workflow_id\` to \`fn_task_create\` just because a task looks like investigation, audit, research, coordination, decision-only work, or coding work. If the user explicitly asks for a workflow, call \`fn_workflow_list\` to discover valid IDs, then use \`fn_workflow_select\` for the current task or pass \`workflow_id\` to \`fn_task_create\` for the requested subtask. When you create a task via \`fn_task_create\` or \`fn_delegate_task\`, you may select that created task's workflow with \`workflow_id\` at create time or \`fn_workflow_select\` afterward; do not move a task you did not create unless the user asked. For investigation/audit/research, operational routing/coordination, or decision-only tasks that meet the no-commits criteria above, still include \`**No commits expected:** true\` in the PROMPT.md header when appropriate; that header marker does not change the workflow.
|
|
4816
|
-
|
|
4817
|
-
## Task Artifact Location for Forensic / Reconciliation Tasks
|
|
4818
|
-
|
|
4819
|
-
For audit/forensic/historical reconciliation tasks that target a different task ID, explicitly state in generated PROMPT.md context/scope that authoritative artifacts and DB state are at project root, not the worktree.
|
|
4820
|
-
- Target-task files live at \`<rootDir>/.fusion/tasks/{TARGET_ID}/\` (\`task.json\`, \`PROMPT.md\`, \`attachments/\`, logs).
|
|
4821
|
-
- Task DB truth lives at \`<rootDir>/.fusion/fusion.db\` (SQLite/WAL) and should be accessed via \`TaskStore\`/task tools, not direct SQL edits.
|
|
4822
|
-
- \`.fusion/\` is gitignored: fresh worktrees from \`main\` do not contain other tasks' \`.fusion/tasks/{TARGET_ID}/\` or \`.fusion/fusion.db\`; worktree-local \`.fusion/\` is running-task scratch/session state only.
|
|
4823
|
-
|
|
4824
|
-
## Spec Review
|
|
4825
|
-
|
|
4826
|
-
After writing the PROMPT.md, call \`fn_review_spec()\` to confirm the spec.
|
|
4703
|
+
## Bug and UI-affordance gates
|
|
4704
|
+
- For bug-fix or UI-affordance add/remove tasks, include \`## Surface Enumeration\` and tersely enumerate affected surfaces: providers/bridges, desktop/mobile, empty/undefined/duplicate/populated states, shared hooks/components/modules, all affordance renderers, and leftover shells after removal.
|
|
4705
|
+
- For bug-class tasks, include \`## Symptom Verification\` with **Original symptom**, **Exact reproduction**, and **Assertion it is gone**; final verification must reproduce the original failure and assert it no longer occurs with a real automated test.
|
|
4827
4706
|
|
|
4828
|
-
|
|
4707
|
+
## Testing requirements
|
|
4708
|
+
- Every implementation spec needs a Testing & Verification step with real automated tests and bounded commands.
|
|
4709
|
+
- Prefer changed-file/package-scoped tests; do not require full workspace suites unless the user/task explicitly requires them.
|
|
4710
|
+
- If the user supplied exact test/build commands, include those exact commands.
|
|
4829
4711
|
|
|
4830
|
-
|
|
4712
|
+
## Safety and workflow rules
|
|
4713
|
+
- Never kill port 4040; use a random/free port for test servers.
|
|
4714
|
+
- Do not call workflow-selection tools unless the user explicitly requested a workflow for this task, or you are selecting a workflow for a task you created.
|
|
4715
|
+
- Do not cite task-local \`.fusion/tasks/<id>/<file>\` paths unless the file exists, is PROMPT.md/task.json/attachments for a sibling task, or the spec creates it. Save scratch notes with \`fn_task_document_write\`.
|
|
4831
4716
|
|
|
4832
4717
|
## Output
|
|
4833
|
-
Write
|
|
4718
|
+
Write PROMPT.md directly, then call \`fn_review_spec()\`. Fast-mode specs auto-approve, but the call is still required.`;
|
|
4834
4719
|
TRIAGE_PROMPT_TEXT = `You are a task specification agent for "fn", an AI-orchestrated task board.
|
|
4835
4720
|
|
|
4836
4721
|
## Your Role
|
|
@@ -10723,6 +10608,18 @@ function defaultColumnForLinearNode(node) {
|
|
|
10723
10608
|
if (seam === "merge") return "in-review";
|
|
10724
10609
|
return "todo";
|
|
10725
10610
|
}
|
|
10611
|
+
function canonicalBuiltinWorkflowColumns() {
|
|
10612
|
+
if (BUILTIN_CODING_WORKFLOW_IR.version !== "v2") {
|
|
10613
|
+
throw new Error("builtin coding workflow must be v2 to provide canonical columns");
|
|
10614
|
+
}
|
|
10615
|
+
return BUILTIN_CODING_WORKFLOW_IR.columns.map((column) => ({
|
|
10616
|
+
...column,
|
|
10617
|
+
traits: column.traits.map((trait) => ({
|
|
10618
|
+
...trait,
|
|
10619
|
+
config: trait.config ? { ...trait.config } : void 0
|
|
10620
|
+
}))
|
|
10621
|
+
}));
|
|
10622
|
+
}
|
|
10726
10623
|
function linear(spec) {
|
|
10727
10624
|
const nodes = [
|
|
10728
10625
|
{ id: "start", kind: "start" },
|
|
@@ -10742,14 +10639,16 @@ function linear(spec) {
|
|
|
10742
10639
|
nodes.forEach((node, i) => {
|
|
10743
10640
|
layout[node.id] = { x: 60 + i * 170, y: 160 };
|
|
10744
10641
|
});
|
|
10745
|
-
const
|
|
10746
|
-
const ir2 = hasV2OnlyNode ? parseWorkflowIr({
|
|
10642
|
+
const ir2 = parseWorkflowIr({
|
|
10747
10643
|
version: "v2",
|
|
10748
10644
|
name: spec.name,
|
|
10749
|
-
columns:
|
|
10645
|
+
columns: canonicalBuiltinWorkflowColumns(),
|
|
10750
10646
|
nodes: nodes.map((node) => node.column ? node : { ...node, column: defaultColumnForLinearNode(node) }),
|
|
10751
10647
|
edges
|
|
10752
|
-
})
|
|
10648
|
+
});
|
|
10649
|
+
if (ir2.version !== "v2" || !ir2.columns.find((column) => column.id === "todo")?.traits.some((trait) => trait.trait === "hold")) {
|
|
10650
|
+
throw new Error(`linear built-in workflow '${spec.id}' must synthesize a hold-capacity todo column`);
|
|
10651
|
+
}
|
|
10753
10652
|
if (ir2.version === "v2") {
|
|
10754
10653
|
ir2.settings = BUILTIN_WORKFLOW_SETTINGS;
|
|
10755
10654
|
}
|
|
@@ -10769,7 +10668,7 @@ function linear(spec) {
|
|
|
10769
10668
|
function getBuiltinWorkflow(id) {
|
|
10770
10669
|
return BUILTIN_BY_ID.get(id);
|
|
10771
10670
|
}
|
|
10772
|
-
var BUILTIN_WORKFLOW_ID_PREFIX, PLUGIN_GATED_BUILTIN_WORKFLOWS, BUILTIN_TS,
|
|
10671
|
+
var BUILTIN_WORKFLOW_ID_PREFIX, PLUGIN_GATED_BUILTIN_WORKFLOWS, BUILTIN_TS, BUILTIN_WORKFLOWS, BUILTIN_BY_ID;
|
|
10773
10672
|
var init_builtin_workflows = __esm({
|
|
10774
10673
|
"../core/src/builtin-workflows.ts"() {
|
|
10775
10674
|
"use strict";
|
|
@@ -10786,7 +10685,6 @@ var init_builtin_workflows = __esm({
|
|
|
10786
10685
|
["builtin:compound-engineering", "fusion-plugin-compound-engineering"]
|
|
10787
10686
|
]);
|
|
10788
10687
|
BUILTIN_TS = "2026-01-01T00:00:00.000Z";
|
|
10789
|
-
V1_LINEAR_NODE_KINDS = /* @__PURE__ */ new Set(["start", "prompt", "script", "gate", "end"]);
|
|
10790
10688
|
BUILTIN_WORKFLOWS = [
|
|
10791
10689
|
{
|
|
10792
10690
|
id: "builtin:coding",
|
|
@@ -11300,6 +11198,21 @@ function setRunningAgentCountSource(fn2) {
|
|
|
11300
11198
|
function getRunningAgentCountSource() {
|
|
11301
11199
|
return runningAgentCountSource;
|
|
11302
11200
|
}
|
|
11201
|
+
function isRunningAgentTask(task) {
|
|
11202
|
+
if (task.column === "in-progress") {
|
|
11203
|
+
return true;
|
|
11204
|
+
}
|
|
11205
|
+
if (task.column === "triage") {
|
|
11206
|
+
return task.status === "planning" && !task.paused;
|
|
11207
|
+
}
|
|
11208
|
+
if (task.column === "in-review") {
|
|
11209
|
+
return ACTIVE_IN_REVIEW_AGENT_STATUSES.has(String(task.status ?? "")) && !task.paused;
|
|
11210
|
+
}
|
|
11211
|
+
return false;
|
|
11212
|
+
}
|
|
11213
|
+
function countRunningAgentTasks(tasks) {
|
|
11214
|
+
return tasks.filter(isRunningAgentTask).length;
|
|
11215
|
+
}
|
|
11303
11216
|
function deriveRunningAgentCounts(perProject) {
|
|
11304
11217
|
const projectsActive = {};
|
|
11305
11218
|
let currentlyActive = 0;
|
|
@@ -11312,10 +11225,11 @@ function deriveRunningAgentCounts(perProject) {
|
|
|
11312
11225
|
}
|
|
11313
11226
|
return { currentlyActive, projectsActive };
|
|
11314
11227
|
}
|
|
11315
|
-
var runningAgentCountSource;
|
|
11228
|
+
var ACTIVE_IN_REVIEW_AGENT_STATUSES, runningAgentCountSource;
|
|
11316
11229
|
var init_live_agent_count = __esm({
|
|
11317
11230
|
"../core/src/live-agent-count.ts"() {
|
|
11318
11231
|
"use strict";
|
|
11232
|
+
ACTIVE_IN_REVIEW_AGENT_STATUSES = /* @__PURE__ */ new Set(["merging", "merging-pr", "merging-fix", "reviewing", "fixing"]);
|
|
11319
11233
|
}
|
|
11320
11234
|
});
|
|
11321
11235
|
|
|
@@ -47778,6 +47692,35 @@ var init_automation_store = __esm({
|
|
|
47778
47692
|
return schedule;
|
|
47779
47693
|
});
|
|
47780
47694
|
}
|
|
47695
|
+
/**
|
|
47696
|
+
* Atomically claim one due schedule occurrence before execution.
|
|
47697
|
+
*
|
|
47698
|
+
* FNXC:Automations 2026-06-27-00:00:
|
|
47699
|
+
* Claiming advances nextRunAt before executing the schedule so concurrent CronRunner pollers, overlapping scopes, and separate engine processes sharing one SQLite DB cannot double-fire the same due window. The conditional UPDATE is the cross-process claim boundary; losers observe zero changed rows and skip execution.
|
|
47700
|
+
*/
|
|
47701
|
+
async claimDueSchedule(id, expectedNextRunAt) {
|
|
47702
|
+
return this.withScheduleLock(id, async () => {
|
|
47703
|
+
const row = this.db.prepare(
|
|
47704
|
+
"SELECT id, cronExpression, enabled, nextRunAt FROM automations WHERE id = ?"
|
|
47705
|
+
).get(id);
|
|
47706
|
+
if (!row || row.enabled !== 1 || !row.nextRunAt) {
|
|
47707
|
+
return false;
|
|
47708
|
+
}
|
|
47709
|
+
const nextRunAt = this.computeNextRun(row.cronExpression);
|
|
47710
|
+
const updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
47711
|
+
const result = this.db.prepare(`
|
|
47712
|
+
UPDATE automations
|
|
47713
|
+
SET nextRunAt = ?, updatedAt = ?
|
|
47714
|
+
WHERE id = ? AND enabled = 1 AND nextRunAt = ?
|
|
47715
|
+
`).run(nextRunAt, updatedAt, id, expectedNextRunAt);
|
|
47716
|
+
const changes = typeof result.changes === "bigint" ? Number(result.changes) : result.changes;
|
|
47717
|
+
if (changes === 1) {
|
|
47718
|
+
this.db.bumpLastModified();
|
|
47719
|
+
return true;
|
|
47720
|
+
}
|
|
47721
|
+
return false;
|
|
47722
|
+
});
|
|
47723
|
+
}
|
|
47781
47724
|
/**
|
|
47782
47725
|
* Record a run result for a schedule. Updates lastRunAt, lastRunResult,
|
|
47783
47726
|
* nextRunAt, runCount, and appends to runHistory.
|
|
@@ -54600,20 +54543,10 @@ ${TASK_UPSERT_SQL_ASSIGNMENTS}
|
|
|
54600
54543
|
throw new Error("Feedback is required and cannot be empty");
|
|
54601
54544
|
}
|
|
54602
54545
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
54603
|
-
|
|
54604
|
-
|
|
54605
|
-
sourceLabel = sourceTask.title.trim();
|
|
54606
|
-
} else {
|
|
54607
|
-
const firstLine = sourceTask.description.split("\n").map((line) => line.trim()).find((line) => line.length > 0);
|
|
54608
|
-
sourceLabel = firstLine ? firstLine.replace(/\s+/g, " ") : sourceTask.id;
|
|
54609
|
-
}
|
|
54546
|
+
const normalizedFeedback = feedback.trim().replace(/\s+/g, " ");
|
|
54547
|
+
const refinementTitle = `${id}: ${normalizedFeedback}`.slice(0, MAX_TITLE_LENGTH).trim();
|
|
54610
54548
|
return this.createTaskWithDistributedReservation({ description: feedback.trim() }, {
|
|
54611
54549
|
createTaskWithId: async (newId, reservationCommit) => {
|
|
54612
|
-
const normalizedTitle = normalizeTitleForTaskId(`Refinement: ${sourceLabel}`, newId);
|
|
54613
|
-
if (normalizedTitle.changed) {
|
|
54614
|
-
const removed = extractTaskIdTokens(`Refinement: ${sourceLabel}`).filter((token) => token !== newId.toUpperCase());
|
|
54615
|
-
storeLog.log(`[title-id-drift] normalized title for ${newId}: removed=[${removed.join(",")}]`);
|
|
54616
|
-
}
|
|
54617
54550
|
const sourceGithubLinked = sourceTask.githubTracking?.enabled === true || Boolean(sourceTask.githubTracking?.issue);
|
|
54618
54551
|
const refinementGithubTracking = sourceGithubLinked ? {
|
|
54619
54552
|
enabled: true,
|
|
@@ -54622,7 +54555,7 @@ ${TASK_UPSERT_SQL_ASSIGNMENTS}
|
|
|
54622
54555
|
const newTask = {
|
|
54623
54556
|
id: newId,
|
|
54624
54557
|
lineageId: generateTaskLineageId(),
|
|
54625
|
-
title:
|
|
54558
|
+
title: refinementTitle,
|
|
54626
54559
|
description: `${feedback.trim()}
|
|
54627
54560
|
|
|
54628
54561
|
Refines: ${id}`,
|
|
@@ -60870,6 +60803,9 @@ ${newTask.description}
|
|
|
60870
60803
|
/**
|
|
60871
60804
|
* FNXC:ArtifactRegistry 2026-06-19-22:04:
|
|
60872
60805
|
* Register multi-type agent/user/system artifacts in SQLite while writing binary payloads to disk. Task-scoped binaries use `.fusion/tasks/{taskId}/artifacts/`; task-less binaries use `.fusion/artifacts/`, and both store only a relative `artifacts/<file>` uri in the row.
|
|
60806
|
+
*
|
|
60807
|
+
* FNXC:ArtifactRegistry 2026-06-27-00:00:
|
|
60808
|
+
* Successful registry writes emit `artifact:registered` as the authoritative live-update signal. Dashboard inbox notifications remain best-effort discovery messages, so already-open artifact lists must not depend on message delivery to invalidate their SWR cache.
|
|
60873
60809
|
*/
|
|
60874
60810
|
async registerArtifact(input) {
|
|
60875
60811
|
const id = randomUUID15();
|
|
@@ -60897,7 +60833,9 @@ ${newTask.description}
|
|
|
60897
60833
|
throw error2;
|
|
60898
60834
|
}
|
|
60899
60835
|
};
|
|
60900
|
-
|
|
60836
|
+
const artifact = input.taskId ? await this.withTaskLock(input.taskId, register) : await register();
|
|
60837
|
+
this.emit("artifact:registered", artifact);
|
|
60838
|
+
return artifact;
|
|
60901
60839
|
}
|
|
60902
60840
|
/**
|
|
60903
60841
|
* FNXC:ArtifactRegistry 2026-06-19-22:04:
|
|
@@ -64154,6 +64092,51 @@ var init_retry_storm_error = __esm({
|
|
|
64154
64092
|
});
|
|
64155
64093
|
|
|
64156
64094
|
// ../core/src/agent-token-usage.ts
|
|
64095
|
+
function aggregateTaskTokenTotalsByAgentLink(db) {
|
|
64096
|
+
const rows = db.prepare(`
|
|
64097
|
+
SELECT
|
|
64098
|
+
id AS taskId,
|
|
64099
|
+
assignedAgentId,
|
|
64100
|
+
sourceAgentId,
|
|
64101
|
+
checkedOutBy,
|
|
64102
|
+
tokenUsageInputTokens AS inputTokens,
|
|
64103
|
+
tokenUsageCachedTokens AS cachedTokens,
|
|
64104
|
+
tokenUsageCacheWriteTokens AS cacheWriteTokens,
|
|
64105
|
+
tokenUsageOutputTokens AS outputTokens,
|
|
64106
|
+
tokenUsageTotalTokens AS totalTokens
|
|
64107
|
+
FROM tasks
|
|
64108
|
+
WHERE tokenUsageInputTokens IS NOT NULL
|
|
64109
|
+
OR tokenUsageCachedTokens IS NOT NULL
|
|
64110
|
+
OR tokenUsageCacheWriteTokens IS NOT NULL
|
|
64111
|
+
OR tokenUsageOutputTokens IS NOT NULL
|
|
64112
|
+
OR tokenUsageTotalTokens IS NOT NULL
|
|
64113
|
+
`).all();
|
|
64114
|
+
const totalsByAgentId = /* @__PURE__ */ new Map();
|
|
64115
|
+
for (const row of rows) {
|
|
64116
|
+
const agentIds = new Set([row.assignedAgentId, row.sourceAgentId, row.checkedOutBy].filter((value) => Boolean(value)));
|
|
64117
|
+
for (const agentId of agentIds) {
|
|
64118
|
+
const existing = totalsByAgentId.get(agentId) ?? createTaskTokenTotals();
|
|
64119
|
+
existing.inputTokens += row.inputTokens ?? 0;
|
|
64120
|
+
existing.cachedTokens += row.cachedTokens ?? 0;
|
|
64121
|
+
existing.cacheWriteTokens += row.cacheWriteTokens ?? 0;
|
|
64122
|
+
existing.outputTokens += row.outputTokens ?? 0;
|
|
64123
|
+
existing.totalTokens += row.totalTokens ?? (row.inputTokens ?? 0) + (row.cachedTokens ?? 0) + (row.cacheWriteTokens ?? 0) + (row.outputTokens ?? 0);
|
|
64124
|
+
existing.nTasks += 1;
|
|
64125
|
+
totalsByAgentId.set(agentId, existing);
|
|
64126
|
+
}
|
|
64127
|
+
}
|
|
64128
|
+
return totalsByAgentId;
|
|
64129
|
+
}
|
|
64130
|
+
function createTaskTokenTotals() {
|
|
64131
|
+
return {
|
|
64132
|
+
inputTokens: 0,
|
|
64133
|
+
cachedTokens: 0,
|
|
64134
|
+
cacheWriteTokens: 0,
|
|
64135
|
+
outputTokens: 0,
|
|
64136
|
+
totalTokens: 0,
|
|
64137
|
+
nTasks: 0
|
|
64138
|
+
};
|
|
64139
|
+
}
|
|
64157
64140
|
async function aggregateAgentTokenUsage({
|
|
64158
64141
|
taskStore,
|
|
64159
64142
|
agentStore,
|
|
@@ -64161,7 +64144,7 @@ async function aggregateAgentTokenUsage({
|
|
|
64161
64144
|
now = /* @__PURE__ */ new Date()
|
|
64162
64145
|
}) {
|
|
64163
64146
|
const agent = await agentStore.getAgent(agentId);
|
|
64164
|
-
if (!agent
|
|
64147
|
+
if (!agent) {
|
|
64165
64148
|
return null;
|
|
64166
64149
|
}
|
|
64167
64150
|
const tasks = await taskStore.listTasks({ slim: true, includeArchived: true });
|
|
@@ -64221,7 +64204,6 @@ function finalizeWindowSummary(summary) {
|
|
|
64221
64204
|
var init_agent_token_usage = __esm({
|
|
64222
64205
|
"../core/src/agent-token-usage.ts"() {
|
|
64223
64206
|
"use strict";
|
|
64224
|
-
init_types();
|
|
64225
64207
|
}
|
|
64226
64208
|
});
|
|
64227
64209
|
|
|
@@ -66135,16 +66117,6 @@ var init_otel_metrics = __esm({
|
|
|
66135
66117
|
}
|
|
66136
66118
|
});
|
|
66137
66119
|
|
|
66138
|
-
// ../core/src/task-helpers.ts
|
|
66139
|
-
function getPrimaryPrInfo(task) {
|
|
66140
|
-
return task.prInfos?.[0] ?? task.prInfo;
|
|
66141
|
-
}
|
|
66142
|
-
var init_task_helpers = __esm({
|
|
66143
|
-
"../core/src/task-helpers.ts"() {
|
|
66144
|
-
"use strict";
|
|
66145
|
-
}
|
|
66146
|
-
});
|
|
66147
|
-
|
|
66148
66120
|
// ../core/src/manual-retry-reset.ts
|
|
66149
66121
|
function buildAutoPauseClearPatch(task) {
|
|
66150
66122
|
if (task.paused === true && task.userPaused !== true && task.pausedReason === IN_REVIEW_STALL_DEADLOCK_PAUSE_REASON) {
|
|
@@ -88160,6 +88132,7 @@ __export(src_exports, {
|
|
|
88160
88132
|
aggregatePluginActivations: () => aggregatePluginActivations,
|
|
88161
88133
|
aggregateProductivityAnalytics: () => aggregateProductivityAnalytics,
|
|
88162
88134
|
aggregateSignalsAnalytics: () => aggregateSignalsAnalytics,
|
|
88135
|
+
aggregateTaskTokenTotalsByAgentLink: () => aggregateTaskTokenTotalsByAgentLink,
|
|
88163
88136
|
aggregateTeamAnalytics: () => aggregateTeamAnalytics,
|
|
88164
88137
|
aggregateTokenAnalytics: () => aggregateTokenAnalytics,
|
|
88165
88138
|
aggregateToolAnalytics: () => aggregateToolAnalytics,
|
|
@@ -88250,6 +88223,7 @@ __export(src_exports, {
|
|
|
88250
88223
|
countDualAcceptDisagreements: () => countDualAcceptDisagreements,
|
|
88251
88224
|
countInterventions: () => countInterventions,
|
|
88252
88225
|
countRecentIdenticalStallEntries: () => countRecentIdenticalStallEntries,
|
|
88226
|
+
countRunningAgentTasks: () => countRunningAgentTasks,
|
|
88253
88227
|
countUsageEventsBy: () => countUsageEventsBy,
|
|
88254
88228
|
createActivityLogSnapshot: () => createActivityLogSnapshot,
|
|
88255
88229
|
createAgentRunSnapshot: () => createAgentRunSnapshot,
|
|
@@ -88452,6 +88426,7 @@ __export(src_exports, {
|
|
|
88452
88426
|
isResearchExperimentalEnabled: () => isResearchExperimentalEnabled,
|
|
88453
88427
|
isRetryableMeshWriteFailure: () => isRetryableMeshWriteFailure,
|
|
88454
88428
|
isRunRecord: () => isRunRecord,
|
|
88429
|
+
isRunningAgentTask: () => isRunningAgentTask,
|
|
88455
88430
|
isSandboxExperimentalEnabled: () => isSandboxExperimentalEnabled,
|
|
88456
88431
|
isSecretAccessPolicy: () => isSecretAccessPolicy,
|
|
88457
88432
|
isSecretScope: () => isSecretScope,
|
|
@@ -88713,6 +88688,7 @@ __export(src_exports, {
|
|
|
88713
88688
|
syncMemoryBackupRoutine: () => syncMemoryBackupRoutine,
|
|
88714
88689
|
syncMemoryDreamsAutomation: () => syncMemoryDreamsAutomation,
|
|
88715
88690
|
syncScheduledEvalBatchAutomation: () => syncScheduledEvalBatchAutomation,
|
|
88691
|
+
taskHasManualOpenPullRequest: () => taskHasManualOpenPullRequest,
|
|
88716
88692
|
taskMatchesReplicatedCreate: () => taskMatchesReplicatedCreate,
|
|
88717
88693
|
toJson: () => toJson,
|
|
88718
88694
|
toJsonNullable: () => toJsonNullable,
|
|
@@ -88873,7 +88849,6 @@ var init_src = __esm({
|
|
|
88873
88849
|
init_stalled_review_detector();
|
|
88874
88850
|
init_task_id_integrity();
|
|
88875
88851
|
init_task_title_id_drift();
|
|
88876
|
-
init_task_helpers();
|
|
88877
88852
|
init_manual_retry_reset();
|
|
88878
88853
|
init_task_lineage();
|
|
88879
88854
|
init_distributed_task_id();
|
|
@@ -88893,6 +88868,7 @@ var init_src = __esm({
|
|
|
88893
88868
|
init_blocker_fanout();
|
|
88894
88869
|
init_capacity();
|
|
88895
88870
|
init_dependency_blocked_todo_report();
|
|
88871
|
+
init_task_helpers();
|
|
88896
88872
|
init_task_merge();
|
|
88897
88873
|
init_branch_group_completion();
|
|
88898
88874
|
init_pr_entity();
|
|
@@ -89629,6 +89605,11 @@ var init_gating_classifications = __esm({
|
|
|
89629
89605
|
"fn_task_done",
|
|
89630
89606
|
"fn_heartbeat_done",
|
|
89631
89607
|
"fn_memory_append",
|
|
89608
|
+
/**
|
|
89609
|
+
* FNXC:ToolGovernance 2026-06-28-00:00:
|
|
89610
|
+
* FN-7191 requires permanent agents to call fn_ask_question directly without an approval gate. The tool only posts a structured question to the user's inbox, so it has the same trust level as fn_send_message and must be positively recognized in both gate paths.
|
|
89611
|
+
*/
|
|
89612
|
+
"fn_ask_question",
|
|
89632
89613
|
"fn_send_message",
|
|
89633
89614
|
"fn_read_messages",
|
|
89634
89615
|
"fn_post_room_message",
|
|
@@ -89670,6 +89651,11 @@ var init_gating_classifications = __esm({
|
|
|
89670
89651
|
"fn_agent_show",
|
|
89671
89652
|
"fn_agent_org_chart",
|
|
89672
89653
|
"fn_workflow_list",
|
|
89654
|
+
/**
|
|
89655
|
+
* FNXC:ToolGovernance 2026-06-28-00:00:
|
|
89656
|
+
* FN-7191 requires fn_ask_question to bypass permanent-agent approval gates like other user-messaging coordination tools; membership here makes the action gate classify it as exempt/allow even under locked-down policies.
|
|
89657
|
+
*/
|
|
89658
|
+
"fn_ask_question",
|
|
89673
89659
|
"fn_send_message",
|
|
89674
89660
|
"fn_post_room_message",
|
|
89675
89661
|
"fn_memory_append",
|
|
@@ -90557,7 +90543,7 @@ var init_hold_release = __esm({
|
|
|
90557
90543
|
|
|
90558
90544
|
// ../engine/src/concurrency.ts
|
|
90559
90545
|
function persistedTopLevelAgentSlots(tasks) {
|
|
90560
|
-
return tasks
|
|
90546
|
+
return countRunningAgentTasks(tasks);
|
|
90561
90547
|
}
|
|
90562
90548
|
function recoverIdleSemaphoreLeakCandidate(params) {
|
|
90563
90549
|
const {
|
|
@@ -90584,10 +90570,11 @@ function recoverIdleSemaphoreLeakCandidate(params) {
|
|
|
90584
90570
|
reconciliation: semaphore.reconcileActiveCount(0)
|
|
90585
90571
|
};
|
|
90586
90572
|
}
|
|
90587
|
-
var concurrencyLog, PRIORITY_MERGE, PRIORITY_EXECUTE, PRIORITY_SPECIFY, IDLE_SEMAPHORE_LEAK_REPAIR_MS, AgentSemaphore;
|
|
90573
|
+
var concurrencyLog, PRIORITY_MERGE, PRIORITY_EXECUTE, PRIORITY_SPECIFY, IDLE_SEMAPHORE_LEAK_REPAIR_MS, AgentSemaphore, ScopedAgentSemaphore;
|
|
90588
90574
|
var init_concurrency = __esm({
|
|
90589
90575
|
"../engine/src/concurrency.ts"() {
|
|
90590
90576
|
"use strict";
|
|
90577
|
+
init_src();
|
|
90591
90578
|
init_logger2();
|
|
90592
90579
|
concurrencyLog = createLogger2("concurrency");
|
|
90593
90580
|
PRIORITY_MERGE = 2;
|
|
@@ -90738,13 +90725,21 @@ var init_concurrency = __esm({
|
|
|
90738
90725
|
* deadlock that would occur if both parent and child needed a queued slot.
|
|
90739
90726
|
*/
|
|
90740
90727
|
async runNested(fn2) {
|
|
90741
|
-
this.
|
|
90728
|
+
this.acquireNestedSlot();
|
|
90742
90729
|
try {
|
|
90743
90730
|
return await fn2();
|
|
90744
90731
|
} finally {
|
|
90745
|
-
this.
|
|
90732
|
+
this.releaseNestedSlot();
|
|
90746
90733
|
}
|
|
90747
90734
|
}
|
|
90735
|
+
/** Reserve a nested helper-agent slot without queueing. */
|
|
90736
|
+
acquireNestedSlot() {
|
|
90737
|
+
this._active++;
|
|
90738
|
+
}
|
|
90739
|
+
/** Return a nested helper-agent slot. */
|
|
90740
|
+
releaseNestedSlot() {
|
|
90741
|
+
this.returnSlot("runNested");
|
|
90742
|
+
}
|
|
90748
90743
|
/**
|
|
90749
90744
|
* FNXC:Scheduler-Concurrency 2026-06-13-19:58:
|
|
90750
90745
|
* FN-6423 requires excess slot returns to remain observable without corrupting scheduler capacity accounting. Clamp the active slot count at zero and warn once so a release leak cannot surface as negative `activeCount` or a negative `semaphore used=` diagnostic.
|
|
@@ -90793,6 +90788,95 @@ var init_concurrency = __esm({
|
|
|
90793
90788
|
return bestIdx;
|
|
90794
90789
|
}
|
|
90795
90790
|
};
|
|
90791
|
+
ScopedAgentSemaphore = class extends AgentSemaphore {
|
|
90792
|
+
delegate;
|
|
90793
|
+
_held = 0;
|
|
90794
|
+
constructor(delegate) {
|
|
90795
|
+
super(() => delegate.limit);
|
|
90796
|
+
this.delegate = delegate;
|
|
90797
|
+
}
|
|
90798
|
+
/** Number of slots this scope currently owns in the delegated semaphore. */
|
|
90799
|
+
get heldCount() {
|
|
90800
|
+
return this._held;
|
|
90801
|
+
}
|
|
90802
|
+
/** Shared-pool active count, preserving scheduler/metrics semantics. */
|
|
90803
|
+
get activeCount() {
|
|
90804
|
+
return this.delegate.activeCount;
|
|
90805
|
+
}
|
|
90806
|
+
get waitingCount() {
|
|
90807
|
+
return this.delegate.waitingCount;
|
|
90808
|
+
}
|
|
90809
|
+
get availableCount() {
|
|
90810
|
+
return this.delegate.availableCount;
|
|
90811
|
+
}
|
|
90812
|
+
get limit() {
|
|
90813
|
+
return this.delegate.limit;
|
|
90814
|
+
}
|
|
90815
|
+
snapshot() {
|
|
90816
|
+
return this.delegate.snapshot();
|
|
90817
|
+
}
|
|
90818
|
+
reconcileActiveCount(maxActive) {
|
|
90819
|
+
const bounded = Math.max(0, Math.floor(maxActive));
|
|
90820
|
+
const before = this._held;
|
|
90821
|
+
if (before > bounded) {
|
|
90822
|
+
const returned = before - bounded;
|
|
90823
|
+
this._held = bounded;
|
|
90824
|
+
for (let i = 0; i < returned; i++) {
|
|
90825
|
+
this.delegate.release();
|
|
90826
|
+
}
|
|
90827
|
+
}
|
|
90828
|
+
return { before, after: this._held, changed: before !== this._held };
|
|
90829
|
+
}
|
|
90830
|
+
async acquire(priority = 0) {
|
|
90831
|
+
await this.delegate.acquire(priority);
|
|
90832
|
+
this._held++;
|
|
90833
|
+
}
|
|
90834
|
+
tryAcquire() {
|
|
90835
|
+
const acquired = this.delegate.tryAcquire();
|
|
90836
|
+
if (acquired) this._held++;
|
|
90837
|
+
return acquired;
|
|
90838
|
+
}
|
|
90839
|
+
release() {
|
|
90840
|
+
if (this._held <= 0) return;
|
|
90841
|
+
this._held--;
|
|
90842
|
+
this.delegate.release();
|
|
90843
|
+
}
|
|
90844
|
+
async run(fn2, priority = 0) {
|
|
90845
|
+
await this.acquire(priority);
|
|
90846
|
+
try {
|
|
90847
|
+
return await fn2();
|
|
90848
|
+
} finally {
|
|
90849
|
+
this.release();
|
|
90850
|
+
}
|
|
90851
|
+
}
|
|
90852
|
+
async runNested(fn2) {
|
|
90853
|
+
this.delegate.acquireNestedSlot();
|
|
90854
|
+
this._held++;
|
|
90855
|
+
try {
|
|
90856
|
+
return await fn2();
|
|
90857
|
+
} finally {
|
|
90858
|
+
if (this._held > 0) {
|
|
90859
|
+
this._held--;
|
|
90860
|
+
this.delegate.releaseNestedSlot();
|
|
90861
|
+
}
|
|
90862
|
+
}
|
|
90863
|
+
}
|
|
90864
|
+
/**
|
|
90865
|
+
* Return every slot still attributed to this scope.
|
|
90866
|
+
*
|
|
90867
|
+
* Late normal releases from already-aborted agents become no-ops because the
|
|
90868
|
+
* scope's held count is zeroed before returning residual slots to the pool.
|
|
90869
|
+
*/
|
|
90870
|
+
returnAllHeldSlots() {
|
|
90871
|
+
const residual = this._held;
|
|
90872
|
+
if (residual <= 0) return 0;
|
|
90873
|
+
this._held = 0;
|
|
90874
|
+
for (let i = 0; i < residual; i++) {
|
|
90875
|
+
this.delegate.release();
|
|
90876
|
+
}
|
|
90877
|
+
return residual;
|
|
90878
|
+
}
|
|
90879
|
+
};
|
|
90796
90880
|
}
|
|
90797
90881
|
});
|
|
90798
90882
|
|
|
@@ -161599,7 +161683,7 @@ ${isRevision ? "1. Review the existing specification and user feedback carefully
|
|
|
161599
161683
|
|
|
161600
161684
|
Use the write tool to write the specification file.${commandsSection}${completionDocumentationSection}${memorySection}${attachmentsSection}${userCommentsSection}`;
|
|
161601
161685
|
}
|
|
161602
|
-
var TriageProcessor, IMAGE_MIME_TYPES, TEXT_INLINE_LIMIT;
|
|
161686
|
+
var TRIAGE_STUCK_RESUME_LOG_ACTION, TRIAGE_STUCK_RESUME_FEEDBACK, TriageProcessor, IMAGE_MIME_TYPES, TEXT_INLINE_LIMIT;
|
|
161603
161687
|
var init_triage = __esm({
|
|
161604
161688
|
"../engine/src/triage.ts"() {
|
|
161605
161689
|
"use strict";
|
|
@@ -161631,6 +161715,8 @@ var init_triage = __esm({
|
|
|
161631
161715
|
init_run_audit();
|
|
161632
161716
|
init_goal_injection_diagnostics();
|
|
161633
161717
|
init_session_token_usage();
|
|
161718
|
+
TRIAGE_STUCK_RESUME_LOG_ACTION = "Triage stuck re-queue will resume existing planning draft";
|
|
161719
|
+
TRIAGE_STUCK_RESUME_FEEDBACK = "The previous triage session was killed by the stuck-task detector after writing a non-empty planning draft. Resume from the existing draft below: preserve useful structure and decisions, fill gaps, and continue toward review instead of restarting planning from scratch.";
|
|
161634
161720
|
TriageProcessor = class _TriageProcessor {
|
|
161635
161721
|
/**
|
|
161636
161722
|
* @param store — Task store instance (also used to listen for `settings:updated` events)
|
|
@@ -161941,6 +162027,106 @@ var init_triage = __esm({
|
|
|
161941
162027
|
});
|
|
161942
162028
|
return true;
|
|
161943
162029
|
}
|
|
162030
|
+
async readNonEmptyPromptDraft(taskId, context) {
|
|
162031
|
+
const promptPath = join49(this.rootDir, ".fusion", "tasks", taskId, "PROMPT.md");
|
|
162032
|
+
const written = await readFile22(promptPath, "utf-8").catch((err) => {
|
|
162033
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
162034
|
+
planLog.warn(`${taskId}: failed to read PROMPT.md during ${context} (${promptPath}): ${msg}`);
|
|
162035
|
+
return "";
|
|
162036
|
+
});
|
|
162037
|
+
return written.trim().length > 0 ? written : void 0;
|
|
162038
|
+
}
|
|
162039
|
+
async readNonEmptyPlanDocument(taskId, context) {
|
|
162040
|
+
const readTaskDocument = this.store.getTaskDocument;
|
|
162041
|
+
if (typeof readTaskDocument !== "function") {
|
|
162042
|
+
return void 0;
|
|
162043
|
+
}
|
|
162044
|
+
const document2 = await readTaskDocument.call(this.store, taskId, "plan").catch((err) => {
|
|
162045
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
162046
|
+
planLog.warn(`${taskId}: failed to read plan task document during ${context}: ${msg}`);
|
|
162047
|
+
return null;
|
|
162048
|
+
});
|
|
162049
|
+
const content = typeof document2?.content === "string" ? document2.content : "";
|
|
162050
|
+
return content.trim().length > 0 ? content : void 0;
|
|
162051
|
+
}
|
|
162052
|
+
async readNonEmptyPlanningDraft(taskId, context) {
|
|
162053
|
+
const promptDraft = await this.readNonEmptyPromptDraft(taskId, context);
|
|
162054
|
+
if (promptDraft) {
|
|
162055
|
+
return { content: promptDraft, source: "prompt" };
|
|
162056
|
+
}
|
|
162057
|
+
const planDocument = await this.readNonEmptyPlanDocument(taskId, context);
|
|
162058
|
+
return planDocument ? { content: planDocument, source: "plan-document" } : void 0;
|
|
162059
|
+
}
|
|
162060
|
+
async handleStuckAbortRequeue(task, context) {
|
|
162061
|
+
const freshTask = await this.store.getTask(task.id).catch((err) => {
|
|
162062
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
162063
|
+
planLog.warn(`${task.id}: failed to refresh task during stuck-detector ${context} cleanup: ${msg}`);
|
|
162064
|
+
return task;
|
|
162065
|
+
});
|
|
162066
|
+
if (hasLatestSpecReviewApproval2(freshTask)) {
|
|
162067
|
+
const recovered = await this.recoverApprovedTask(freshTask).catch((err) => {
|
|
162068
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
162069
|
+
planLog.warn(`${task.id}: approved-spec recovery failed during stuck-detector ${context} cleanup: ${msg}`);
|
|
162070
|
+
return false;
|
|
162071
|
+
});
|
|
162072
|
+
if (recovered) {
|
|
162073
|
+
return;
|
|
162074
|
+
}
|
|
162075
|
+
}
|
|
162076
|
+
const maxStuckSettings = await this.store.getSettings().catch((err) => {
|
|
162077
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
162078
|
+
planLog.warn(`${task.id}: failed to read maxStuckKills during stuck-detector ${context} cleanup, using default 6: ${msg}`);
|
|
162079
|
+
return {};
|
|
162080
|
+
});
|
|
162081
|
+
const maxKills = Math.max(1, maxStuckSettings.maxStuckKills ?? 6);
|
|
162082
|
+
const nextStuckKillCount = (freshTask.stuckKillCount ?? task.stuckKillCount ?? 0) + 1;
|
|
162083
|
+
const draft = await this.readNonEmptyPlanningDraft(task.id, `stuck-detector ${context} cleanup`);
|
|
162084
|
+
if (nextStuckKillCount >= maxKills) {
|
|
162085
|
+
const exhaustedError = `STUCK_LOOP_EXHAUSTED: triage stuck detector killed ${task.id} ${nextStuckKillCount}/${maxKills} times without planning completion; task paused for manual intervention.`;
|
|
162086
|
+
planLog.error(exhaustedError);
|
|
162087
|
+
await this.store.logEntry(task.id, exhaustedError).catch((err) => {
|
|
162088
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
162089
|
+
planLog.warn(`${task.id}: failed to log stuck-loop exhaustion: ${msg}`);
|
|
162090
|
+
});
|
|
162091
|
+
await this.store.updateTask(task.id, {
|
|
162092
|
+
stuckKillCount: nextStuckKillCount,
|
|
162093
|
+
status: "failed",
|
|
162094
|
+
error: exhaustedError,
|
|
162095
|
+
paused: true,
|
|
162096
|
+
pausedReason: "stuck-loop-exhausted-manual-intervention-required",
|
|
162097
|
+
pausedByAgentId: "triage"
|
|
162098
|
+
}).catch((err) => {
|
|
162099
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
162100
|
+
planLog.warn(`${task.id}: failed to persist stuck-loop exhaustion during stuck-detector ${context} cleanup: ${msg}`);
|
|
162101
|
+
});
|
|
162102
|
+
return;
|
|
162103
|
+
}
|
|
162104
|
+
if (draft) {
|
|
162105
|
+
const sourceLabel = draft.source === "prompt" ? "PROMPT.md draft" : "plan task document";
|
|
162106
|
+
planLog.log(`${task.id} killed by stuck detector \u2014 requeueing to resume existing ${sourceLabel} (${nextStuckKillCount}/${maxKills})`);
|
|
162107
|
+
await this.store.logEntry(task.id, TRIAGE_STUCK_RESUME_LOG_ACTION, TRIAGE_STUCK_RESUME_FEEDBACK).catch((err) => {
|
|
162108
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
162109
|
+
planLog.warn(`${task.id}: failed to log stuck-resume feedback: ${msg}`);
|
|
162110
|
+
});
|
|
162111
|
+
await this.store.updateTask(task.id, {
|
|
162112
|
+
status: "needs-replan",
|
|
162113
|
+
stuckKillCount: nextStuckKillCount
|
|
162114
|
+
}).catch((err) => {
|
|
162115
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
162116
|
+
planLog.warn(`${task.id}: failed to restore status to 'needs-replan' during stuck-detector ${context} cleanup: ${msg}`);
|
|
162117
|
+
});
|
|
162118
|
+
return;
|
|
162119
|
+
}
|
|
162120
|
+
planLog.log(`${task.id} killed by stuck detector \u2014 clearing status for cold retry (${nextStuckKillCount}/${maxKills})`);
|
|
162121
|
+
const restoreStatus = (freshTask.status ?? task.status) === "needs-replan" ? "needs-replan" : null;
|
|
162122
|
+
await this.store.updateTask(task.id, {
|
|
162123
|
+
status: restoreStatus,
|
|
162124
|
+
stuckKillCount: nextStuckKillCount
|
|
162125
|
+
}).catch((err) => {
|
|
162126
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
162127
|
+
planLog.warn(`${task.id}: failed to restore status to '${restoreStatus}' during stuck-detector ${context} cleanup: ${msg}`);
|
|
162128
|
+
});
|
|
162129
|
+
}
|
|
161944
162130
|
/**
|
|
161945
162131
|
* If `newIntervalMs` differs from the currently active timer, restart
|
|
161946
162132
|
* the `setInterval` so the new cadence takes effect immediately.
|
|
@@ -162299,9 +162485,16 @@ You are ${assignedAgent.name}${assignedAgent.title?.trim() ? `, ${assignedAgent.
|
|
|
162299
162485
|
let feedback;
|
|
162300
162486
|
if (isReplan) {
|
|
162301
162487
|
const feedbackLogEntry = [...task.log].reverse().find(
|
|
162302
|
-
(entry) => entry.action === "User comment requested re-specification of planned task" || entry.action === "User comment invalidated spec approval \u2014 task needs re-specification" || entry.action === "AI spec revision requested"
|
|
162488
|
+
(entry) => entry.action === "User comment requested re-specification of planned task" || entry.action === "User comment invalidated spec approval \u2014 task needs re-specification" || entry.action === "AI spec revision requested" || entry.action === TRIAGE_STUCK_RESUME_LOG_ACTION
|
|
162303
162489
|
);
|
|
162304
162490
|
feedback = feedbackLogEntry?.outcome;
|
|
162491
|
+
if (feedbackLogEntry?.action === TRIAGE_STUCK_RESUME_LOG_ACTION) {
|
|
162492
|
+
const planningDraft = await this.readNonEmptyPlanningDraft(task.id, "stuck-resume replan seed");
|
|
162493
|
+
existingPrompt = planningDraft?.content;
|
|
162494
|
+
if (!existingPrompt) {
|
|
162495
|
+
feedback = void 0;
|
|
162496
|
+
}
|
|
162497
|
+
}
|
|
162305
162498
|
if (!feedback) {
|
|
162306
162499
|
const latestUserComment = [...detail.comments || []].reverse().find((comment) => comment.author === "user");
|
|
162307
162500
|
feedback = latestUserComment?.text;
|
|
@@ -162336,12 +162529,7 @@ You are ${assignedAgent.name}${assignedAgent.title?.trim() ? `, ${assignedAgent.
|
|
|
162336
162529
|
}
|
|
162337
162530
|
if (this.stuckAborted.has(task.id)) {
|
|
162338
162531
|
this.stuckAborted.delete(task.id);
|
|
162339
|
-
|
|
162340
|
-
const restoreStatus = task.status === "needs-replan" ? "needs-replan" : null;
|
|
162341
|
-
await this.store.updateTask(task.id, { status: restoreStatus }).catch((err) => {
|
|
162342
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
162343
|
-
planLog.warn(`${task.id}: failed to restore status to '${restoreStatus}' during stuck-detector abort cleanup: ${msg}`);
|
|
162344
|
-
});
|
|
162532
|
+
await this.handleStuckAbortRequeue(task, "in-loop");
|
|
162345
162533
|
return;
|
|
162346
162534
|
}
|
|
162347
162535
|
if (createdSubtasksRef.current.length > 0) {
|
|
@@ -162590,12 +162778,7 @@ You are ${assignedAgent.name}${assignedAgent.title?.trim() ? `, ${assignedAgent.
|
|
|
162590
162778
|
});
|
|
162591
162779
|
} else if (this.stuckAborted.has(task.id)) {
|
|
162592
162780
|
this.stuckAborted.delete(task.id);
|
|
162593
|
-
|
|
162594
|
-
const restoreStatus = task.status === "needs-replan" ? "needs-replan" : null;
|
|
162595
|
-
await this.store.updateTask(task.id, { status: restoreStatus }).catch((err2) => {
|
|
162596
|
-
const msg = err2 instanceof Error ? err2.message : String(err2);
|
|
162597
|
-
planLog.warn(`${task.id}: failed to restore status to '${restoreStatus}' during stuck-detector error cleanup: ${msg}`);
|
|
162598
|
-
});
|
|
162781
|
+
await this.handleStuckAbortRequeue(task, "catch");
|
|
162599
162782
|
} else {
|
|
162600
162783
|
if (this.options.usageLimitPauser && isUsageLimitError(errorMessage2)) {
|
|
162601
162784
|
await this.options.usageLimitPauser.onUsageLimitHit(
|
|
@@ -175847,9 +176030,7 @@ ${summary}`,
|
|
|
175847
176030
|
} else {
|
|
175848
176031
|
const settings2 = await this.store.getSettings();
|
|
175849
176032
|
const preserveProgress = settings2.preserveProgressOnStuckRequeue !== false;
|
|
175850
|
-
|
|
175851
|
-
await this.resetStepsIfWorkLost(latestTask);
|
|
175852
|
-
}
|
|
176033
|
+
await this.resetStepsIfWorkLost(latestTask);
|
|
175853
176034
|
if (worktreePath && existsSync47(worktreePath)) {
|
|
175854
176035
|
try {
|
|
175855
176036
|
await removeWorktree({
|
|
@@ -177197,9 +177378,7 @@ ${summary}`,
|
|
|
177197
177378
|
} else {
|
|
177198
177379
|
const settings2 = await this.store.getSettings();
|
|
177199
177380
|
const preserveProgress = settings2.preserveProgressOnStuckRequeue !== false;
|
|
177200
|
-
|
|
177201
|
-
await this.resetStepsIfWorkLost(latestTask);
|
|
177202
|
-
}
|
|
177381
|
+
await this.resetStepsIfWorkLost(latestTask);
|
|
177203
177382
|
if (worktreePath && existsSync47(worktreePath)) {
|
|
177204
177383
|
try {
|
|
177205
177384
|
await removeWorktree({
|
|
@@ -178544,28 +178723,48 @@ Take a different approach. Do NOT repeat the rejected strategy. Re-read the step
|
|
|
178544
178723
|
await this.store.logEntry(taskId, "Execution stopped \u2014 work discarded, moved to triage for re-planning");
|
|
178545
178724
|
}
|
|
178546
178725
|
/**
|
|
178547
|
-
* Re-open the
|
|
178548
|
-
*
|
|
178549
|
-
*
|
|
178550
|
-
* was nothing to re-open.
|
|
178726
|
+
* Re-open the implementation-bearing slice of work for a revision/failure
|
|
178727
|
+
* handler. Returns the earliest reopened step and all reopened indexes, or
|
|
178728
|
+
* null when there was nothing to re-open.
|
|
178551
178729
|
*/
|
|
178552
178730
|
async reopenLastStepForRevision(taskId, task) {
|
|
178553
178731
|
const steps = task.steps;
|
|
178554
178732
|
if (steps.length === 0) return null;
|
|
178555
|
-
let
|
|
178733
|
+
let lastNonPendingIndex = -1;
|
|
178556
178734
|
for (let i = steps.length - 1; i >= 0; i--) {
|
|
178557
178735
|
if (steps[i].status !== "pending") {
|
|
178558
|
-
|
|
178736
|
+
lastNonPendingIndex = i;
|
|
178559
178737
|
break;
|
|
178560
178738
|
}
|
|
178561
178739
|
}
|
|
178562
|
-
if (
|
|
178740
|
+
if (lastNonPendingIndex === -1) {
|
|
178563
178741
|
await this.store.updateTask(taskId, { currentStep: 0 });
|
|
178564
178742
|
return null;
|
|
178565
178743
|
}
|
|
178566
|
-
|
|
178567
|
-
|
|
178568
|
-
|
|
178744
|
+
const isTerminalVerificationOrDeliveryStep = (name) => /(^|[^a-z])(testing|verification|documentation|delivery)([^a-z]|$)/i.test(name);
|
|
178745
|
+
const resetIndexes = /* @__PURE__ */ new Set([lastNonPendingIndex]);
|
|
178746
|
+
if (isTerminalVerificationOrDeliveryStep(steps[lastNonPendingIndex].name)) {
|
|
178747
|
+
let cursor = lastNonPendingIndex;
|
|
178748
|
+
while (cursor >= 0 && isTerminalVerificationOrDeliveryStep(steps[cursor].name)) {
|
|
178749
|
+
resetIndexes.add(cursor);
|
|
178750
|
+
cursor--;
|
|
178751
|
+
}
|
|
178752
|
+
while (cursor >= 0 && steps[cursor].status === "pending") {
|
|
178753
|
+
cursor--;
|
|
178754
|
+
}
|
|
178755
|
+
if (cursor >= 0) {
|
|
178756
|
+
resetIndexes.add(cursor);
|
|
178757
|
+
}
|
|
178758
|
+
}
|
|
178759
|
+
const indexes = [...resetIndexes].sort((a, b2) => a - b2);
|
|
178760
|
+
for (const index of indexes) {
|
|
178761
|
+
if (steps[index].status !== "pending") {
|
|
178762
|
+
await this.store.updateStep(taskId, index, "pending");
|
|
178763
|
+
}
|
|
178764
|
+
}
|
|
178765
|
+
const currentStep = indexes[0] ?? lastNonPendingIndex;
|
|
178766
|
+
await this.store.updateTask(taskId, { currentStep });
|
|
178767
|
+
return { index: currentStep, name: steps[currentStep].name, indexes };
|
|
178569
178768
|
}
|
|
178570
178769
|
/**
|
|
178571
178770
|
* Run deterministic verification (test + build commands) in the task's worktree.
|
|
@@ -181129,39 +181328,45 @@ Review the work done in this worktree and evaluate it against the criteria in yo
|
|
|
181129
181328
|
const mergeBase = mergeBaseStdout.trim();
|
|
181130
181329
|
const branchHead = branchHeadStdout.trim();
|
|
181131
181330
|
if (mergeBase === branchHead) {
|
|
181132
|
-
|
|
181133
|
-
|
|
181331
|
+
await this.resetLostWorkStepProgress(task, completedSteps.length, "branch had no commits");
|
|
181332
|
+
}
|
|
181333
|
+
} catch (err) {
|
|
181334
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
181335
|
+
executorLog.warn(
|
|
181336
|
+
`${task.id}: unable to prove surviving branch commits before worktree removal \u2014 resetting ${completedSteps.length} step(s) to pending: ${msg}`
|
|
181337
|
+
);
|
|
181338
|
+
await this.resetLostWorkStepProgress(task, completedSteps.length, `git proof failed: ${msg}`);
|
|
181339
|
+
}
|
|
181340
|
+
}
|
|
181341
|
+
async resetLostWorkStepProgress(task, completedStepCount, reason) {
|
|
181342
|
+
executorLog.warn(
|
|
181343
|
+
`${task.id} ${reason} \u2014 resetting ${completedStepCount} step(s) to pending`
|
|
181344
|
+
);
|
|
181345
|
+
for (let i = 0; i < task.steps.length; i++) {
|
|
181346
|
+
if (task.steps[i].status === "done" || task.steps[i].status === "in-progress") {
|
|
181347
|
+
await this.store.updateStep(task.id, i, "pending");
|
|
181348
|
+
}
|
|
181349
|
+
}
|
|
181350
|
+
const refreshedTask = await this.store.getTask(task.id);
|
|
181351
|
+
const prevCurrentStep = refreshedTask.currentStep;
|
|
181352
|
+
if (refreshedTask.steps.length > 0) {
|
|
181353
|
+
const firstPendingStep = refreshedTask.steps.findIndex((s3) => s3.status === "pending");
|
|
181354
|
+
const newCurrentStep = firstPendingStep >= 0 ? firstPendingStep : 0;
|
|
181355
|
+
if (newCurrentStep !== prevCurrentStep) {
|
|
181356
|
+
await this.store.updateTask(task.id, { currentStep: newCurrentStep });
|
|
181357
|
+
executorLog.log(
|
|
181358
|
+
`${task.id}: reset currentStep to ${newCurrentStep} after lost-work reset (was ${prevCurrentStep})`
|
|
181134
181359
|
);
|
|
181135
|
-
for (let i = 0; i < task.steps.length; i++) {
|
|
181136
|
-
if (task.steps[i].status === "done" || task.steps[i].status === "in-progress") {
|
|
181137
|
-
await this.store.updateStep(task.id, i, "pending");
|
|
181138
|
-
}
|
|
181139
|
-
}
|
|
181140
|
-
const refreshedTask = await this.store.getTask(task.id);
|
|
181141
|
-
const prevCurrentStep = refreshedTask.currentStep;
|
|
181142
|
-
if (refreshedTask.steps.length > 0) {
|
|
181143
|
-
const firstPendingStep = refreshedTask.steps.findIndex((s3) => s3.status === "pending");
|
|
181144
|
-
const newCurrentStep = firstPendingStep >= 0 ? firstPendingStep : 0;
|
|
181145
|
-
if (newCurrentStep !== prevCurrentStep) {
|
|
181146
|
-
await this.store.updateTask(task.id, { currentStep: newCurrentStep });
|
|
181147
|
-
executorLog.log(
|
|
181148
|
-
`${task.id}: reset currentStep to ${newCurrentStep} after lost-work reset (was ${prevCurrentStep})`
|
|
181149
|
-
);
|
|
181150
|
-
await this.store.logEntry(
|
|
181151
|
-
task.id,
|
|
181152
|
-
`Reset currentStep to ${newCurrentStep} after lost-work step reset (was ${prevCurrentStep})`
|
|
181153
|
-
);
|
|
181154
|
-
}
|
|
181155
|
-
}
|
|
181156
181360
|
await this.store.logEntry(
|
|
181157
181361
|
task.id,
|
|
181158
|
-
`Reset ${
|
|
181362
|
+
`Reset currentStep to ${newCurrentStep} after lost-work step reset (was ${prevCurrentStep})`
|
|
181159
181363
|
);
|
|
181160
181364
|
}
|
|
181161
|
-
} catch (err) {
|
|
181162
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
181163
|
-
executorLog.warn(`${task.id}: step-reset-on-work-lost failed (non-fatal, steps keep current status): ${msg}`);
|
|
181164
181365
|
}
|
|
181366
|
+
await this.store.logEntry(
|
|
181367
|
+
task.id,
|
|
181368
|
+
`Reset ${completedStepCount} step(s) to pending \u2014 ${reason} (uncommitted work lost with worktree)`
|
|
181369
|
+
);
|
|
181165
181370
|
}
|
|
181166
181371
|
/**
|
|
181167
181372
|
* Mark a task as stuck-aborted so the executor's error handling
|
|
@@ -181224,9 +181429,7 @@ Review the work done in this worktree and evaluate it against the criteria in yo
|
|
|
181224
181429
|
});
|
|
181225
181430
|
await this.awaitAbortInFlightTaskWork(taskId, "force-requeue after stuck-kill unwind timeout");
|
|
181226
181431
|
this.clearPausedAborted(taskId);
|
|
181227
|
-
|
|
181228
|
-
await this.resetStepsIfWorkLost(latestTask);
|
|
181229
|
-
}
|
|
181432
|
+
await this.resetStepsIfWorkLost(latestTask);
|
|
181230
181433
|
let cleanupFailed = false;
|
|
181231
181434
|
if (worktreePath && existsSync47(worktreePath)) {
|
|
181232
181435
|
try {
|
|
@@ -186515,6 +186718,7 @@ var init_agent_reflection = __esm({
|
|
|
186515
186718
|
init_logger2();
|
|
186516
186719
|
init_pi();
|
|
186517
186720
|
init_mcp_resolution();
|
|
186721
|
+
init_run_audit();
|
|
186518
186722
|
reflectionLog = createLogger2("reflection");
|
|
186519
186723
|
REFLECTION_SYSTEM_PROMPT = `You are an autonomous performance analyst reviewing an AI agent's recent execution history.
|
|
186520
186724
|
|
|
@@ -186553,11 +186757,22 @@ Rules:
|
|
|
186553
186757
|
this.modelId = options.modelId;
|
|
186554
186758
|
}
|
|
186555
186759
|
async generateReflection(agentId, trigger, options = {}) {
|
|
186760
|
+
const runContext = {
|
|
186761
|
+
runId: generateSyntheticRunId("reflection", agentId),
|
|
186762
|
+
agentId,
|
|
186763
|
+
...options.taskId ? { taskId: options.taskId } : {},
|
|
186764
|
+
phase: "reflection",
|
|
186765
|
+
source: trigger
|
|
186766
|
+
};
|
|
186767
|
+
const auditor = createRunAuditor(this.taskStore, runContext);
|
|
186556
186768
|
try {
|
|
186557
186769
|
const context = await this.buildReflectionContext(agentId);
|
|
186558
186770
|
const recentRuns = await this.agentStore.getRecentRuns(agentId, DEFAULT_OUTCOME_LIMIT);
|
|
186559
186771
|
if (context.recentOutcomes.length === 0 && recentRuns.length === 0) {
|
|
186560
186772
|
reflectionLog.log(`Skipping reflection for ${agentId}: no recent tasks or heartbeat runs`);
|
|
186773
|
+
await this.emitReflectionAudit(auditor, "reflection:skipped", agentId, trigger, options, {
|
|
186774
|
+
reason: "no-history"
|
|
186775
|
+
});
|
|
186561
186776
|
return null;
|
|
186562
186777
|
}
|
|
186563
186778
|
let responseText = "";
|
|
@@ -186587,7 +186802,7 @@ Rules:
|
|
|
186587
186802
|
}
|
|
186588
186803
|
const parsed = this.parseReflectionResponse(responseText);
|
|
186589
186804
|
const metrics = this.buildReflectionMetrics(context.recentOutcomes, context.performanceSummary, recentRuns);
|
|
186590
|
-
|
|
186805
|
+
const reflection = await this.reflectionStore.createReflection({
|
|
186591
186806
|
agentId,
|
|
186592
186807
|
trigger,
|
|
186593
186808
|
triggerDetail: options.triggerDetail,
|
|
@@ -186597,11 +186812,42 @@ Rules:
|
|
|
186597
186812
|
suggestedImprovements: parsed.suggestedImprovements,
|
|
186598
186813
|
summary: parsed.summary
|
|
186599
186814
|
});
|
|
186815
|
+
await this.emitReflectionAudit(auditor, "reflection:generated", agentId, trigger, options, {
|
|
186816
|
+
reflectionId: reflection.id,
|
|
186817
|
+
...metrics.tasksCompleted !== void 0 ? { tasksCompleted: metrics.tasksCompleted } : {},
|
|
186818
|
+
...metrics.tasksFailed !== void 0 ? { tasksFailed: metrics.tasksFailed } : {},
|
|
186819
|
+
...metrics.avgDurationMs !== void 0 ? { avgDurationMs: metrics.avgDurationMs } : {},
|
|
186820
|
+
commonErrorCount: metrics.commonErrors?.length ?? 0,
|
|
186821
|
+
insightCount: parsed.insights.length,
|
|
186822
|
+
suggestedImprovementCount: parsed.suggestedImprovements.length
|
|
186823
|
+
});
|
|
186824
|
+
return reflection;
|
|
186600
186825
|
} catch (error2) {
|
|
186826
|
+
await this.emitReflectionAudit(auditor, "reflection:failed", agentId, trigger, options, {
|
|
186827
|
+
errorClass: error2 instanceof Error ? error2.name : typeof error2
|
|
186828
|
+
});
|
|
186601
186829
|
reflectionLog.error(`Failed to generate reflection for ${agentId}: ${error2.message}`);
|
|
186602
186830
|
return null;
|
|
186603
186831
|
}
|
|
186604
186832
|
}
|
|
186833
|
+
async emitReflectionAudit(auditor, type, agentId, trigger, options, metadata) {
|
|
186834
|
+
try {
|
|
186835
|
+
await auditor.database({
|
|
186836
|
+
type,
|
|
186837
|
+
target: agentId,
|
|
186838
|
+
metadata: {
|
|
186839
|
+
agentId,
|
|
186840
|
+
trigger,
|
|
186841
|
+
...options.taskId ? { taskId: options.taskId } : {},
|
|
186842
|
+
...metadata
|
|
186843
|
+
}
|
|
186844
|
+
});
|
|
186845
|
+
} catch (auditError) {
|
|
186846
|
+
reflectionLog.warn(
|
|
186847
|
+
`Failed to record reflection telemetry for ${agentId}: ${auditError instanceof Error ? auditError.message : String(auditError)}`
|
|
186848
|
+
);
|
|
186849
|
+
}
|
|
186850
|
+
}
|
|
186605
186851
|
async buildReflectionContext(agentId) {
|
|
186606
186852
|
const [agentRecord, recentOutcomes, performanceSummaryRaw, latestReflection] = await Promise.all([
|
|
186607
186853
|
this.agentStore.getAgent(agentId),
|
|
@@ -187703,6 +187949,8 @@ Your job:
|
|
|
187703
187949
|
- Surface the blocker concretely with fn_task_log.
|
|
187704
187950
|
- Chase the dependency: comment on the blocking task, send a message to the responsible agent, or ping an owner.
|
|
187705
187951
|
- Look for unblocking work you can spawn or delegate right now.
|
|
187952
|
+
- Do NOT call fn_task_pause to handle a failed or blocked task. Pausing is reserved for explicit user requests for manual control.
|
|
187953
|
+
- If the task needs recovery, create/delegate focused follow-up work with fn_task_create or fn_delegate_task, log the needed operator action, or let the task surface as failed.
|
|
187706
187954
|
- Pivot to other relevant coordination work if the blocker cannot be immediately resolved.
|
|
187707
187955
|
|
|
187708
187956
|
**If your bound task is not blocked:**
|
|
@@ -187739,8 +187987,8 @@ Prefer fn_delegate_task when immediate ownership by a specific agent materially
|
|
|
187739
187987
|
|
|
187740
187988
|
## Common Patterns
|
|
187741
187989
|
|
|
187742
|
-
- **Blocked task:** log the concrete blocker, chase the dependency via fn_send_message, create a narrowly scoped unblocker task if needed.
|
|
187743
|
-
- **Stuck task with no blockedBy:** log the observation and create a follow-up task to investigate the root cause.
|
|
187990
|
+
- **Blocked task:** log the concrete blocker, chase the dependency via fn_send_message, create a narrowly scoped unblocker task if needed; do not pause it unless the user explicitly requested manual control.
|
|
187991
|
+
- **Stuck task with no blockedBy:** log the observation and create a follow-up task to investigate the root cause; do not use fn_task_pause as failure handling.
|
|
187744
187992
|
- **Completed task with follow-up risk:** create explicit follow-up task(s) for residual risk instead of burying notes in a long log.
|
|
187745
187993
|
- **New user/agent comments:** summarize what changed, identify required action, and route via task creation/delegation.
|
|
187746
187994
|
- **Dependency drift:** log the mismatch and create reconciliation tasks with clear dependencies.
|
|
@@ -187832,6 +188080,7 @@ If unsure who should do the work, prefer fn_task_create and let scheduler routin
|
|
|
187832
188080
|
|
|
187833
188081
|
## Common Patterns
|
|
187834
188082
|
|
|
188083
|
+
- **Failed or blocked task:** do NOT call fn_task_pause to handle the failure or blocker. Pausing is reserved for explicit user requests for manual control; instead surface the blocker through available task or message context, create/delegate follow-up work, or let the task surface as failed.
|
|
187835
188084
|
- **Unowned risk discovered:** create one focused task with concrete acceptance language.
|
|
187836
188085
|
- **Known specialist needed:** list agents, then delegate to matching role/capability.
|
|
187837
188086
|
- **Repeated confusion across runs:** append a concise memory entry so future agents avoid the same mistake.
|
|
@@ -193360,12 +193609,21 @@ var init_cron_runner = __esm({
|
|
|
193360
193609
|
log24.log(`Skipping ${schedule.name} (${schedule.id}) \u2014 belongs to ${scheduleScope} scope, not polling`);
|
|
193361
193610
|
continue;
|
|
193362
193611
|
}
|
|
193363
|
-
log24.log(`Executing ${schedule.name} (${schedule.id}) [scope: ${scheduleScope}]`);
|
|
193364
193612
|
const currentSettings = await this.store.getSettings();
|
|
193365
193613
|
if (currentSettings.globalPause || currentSettings.enginePaused) {
|
|
193366
193614
|
log24.log("Pause detected mid-tick \u2014 stopping schedule execution");
|
|
193367
193615
|
break;
|
|
193368
193616
|
}
|
|
193617
|
+
if (!schedule.nextRunAt) {
|
|
193618
|
+
log24.warn(`Skipping ${schedule.name} (${schedule.id}) \u2014 missing nextRunAt for due claim`);
|
|
193619
|
+
continue;
|
|
193620
|
+
}
|
|
193621
|
+
const claimed = await this.automationStore.claimDueSchedule(schedule.id, schedule.nextRunAt);
|
|
193622
|
+
if (!claimed) {
|
|
193623
|
+
log24.log(`Skipping ${schedule.name} (${schedule.id}) \u2014 claim lost to another poller`);
|
|
193624
|
+
continue;
|
|
193625
|
+
}
|
|
193626
|
+
log24.log(`Executing ${schedule.name} (${schedule.id}) [scope: ${scheduleScope}]`);
|
|
193369
193627
|
await this.executeSchedule(schedule);
|
|
193370
193628
|
}
|
|
193371
193629
|
} catch (err) {
|
|
@@ -198355,6 +198613,7 @@ var init_in_process_runtime = __esm({
|
|
|
198355
198613
|
executor;
|
|
198356
198614
|
worktreePool;
|
|
198357
198615
|
globalSemaphore;
|
|
198616
|
+
projectSemaphore;
|
|
198358
198617
|
stuckTaskDetector;
|
|
198359
198618
|
/**
|
|
198360
198619
|
* Per-project CLI Agent Executor runtime bundle (PTY manager + telemetry hub +
|
|
@@ -198502,6 +198761,7 @@ var init_in_process_runtime = __esm({
|
|
|
198502
198761
|
this.centralCore.on("concurrency:changed", this.concurrencyChangedListener);
|
|
198503
198762
|
}
|
|
198504
198763
|
}
|
|
198764
|
+
this.projectSemaphore = new ScopedAgentSemaphore(this.globalSemaphore);
|
|
198505
198765
|
await yieldEventLoop4();
|
|
198506
198766
|
let agentStoreForReflection;
|
|
198507
198767
|
try {
|
|
@@ -198561,7 +198821,7 @@ var init_in_process_runtime = __esm({
|
|
|
198561
198821
|
this.scheduler = new Scheduler(this.taskStore, {
|
|
198562
198822
|
maxConcurrent: this.config.maxConcurrent,
|
|
198563
198823
|
maxWorktrees: this.config.maxWorktrees,
|
|
198564
|
-
semaphore: this.
|
|
198824
|
+
semaphore: this.projectSemaphore,
|
|
198565
198825
|
agentStore: this.agentStore,
|
|
198566
198826
|
hasActiveAgentExecution: (agentId) => this.heartbeatMonitor?.getTrackedAgents().includes(agentId) ?? false,
|
|
198567
198827
|
missionStore,
|
|
@@ -198658,7 +198918,7 @@ var init_in_process_runtime = __esm({
|
|
|
198658
198918
|
const prNodeGithubOps = this.config.prNodeGithubOps;
|
|
198659
198919
|
const workflowAuthoritativeDriverRef = {};
|
|
198660
198920
|
const executorOptions = {
|
|
198661
|
-
semaphore: this.
|
|
198921
|
+
semaphore: this.projectSemaphore,
|
|
198662
198922
|
pool: this.worktreePool,
|
|
198663
198923
|
usageLimitPauser: this.usageLimitPauser,
|
|
198664
198924
|
stuckTaskDetector: this.stuckTaskDetector,
|
|
@@ -198856,7 +199116,7 @@ var init_in_process_runtime = __esm({
|
|
|
198856
199116
|
this.taskStore,
|
|
198857
199117
|
this.config.workingDirectory,
|
|
198858
199118
|
{
|
|
198859
|
-
semaphore: this.
|
|
199119
|
+
semaphore: this.projectSemaphore,
|
|
198860
199120
|
stuckTaskDetector: this.stuckTaskDetector,
|
|
198861
199121
|
agentStore: this.agentStore,
|
|
198862
199122
|
pluginRunner: this.pluginRunner,
|
|
@@ -199155,6 +199415,12 @@ var init_in_process_runtime = __esm({
|
|
|
199155
199415
|
`post-abort drain timeout: shutdown reached with ${finalMetrics.inFlightTasks} tasks still in-flight`
|
|
199156
199416
|
);
|
|
199157
199417
|
}
|
|
199418
|
+
const returnedResidualSlots = this.projectSemaphore?.returnAllHeldSlots() ?? 0;
|
|
199419
|
+
if (returnedResidualSlots > 0) {
|
|
199420
|
+
runtimeLog.warn(
|
|
199421
|
+
`Returned ${returnedResidualSlots} residual global concurrency slot(s) after project stop drain`
|
|
199422
|
+
);
|
|
199423
|
+
}
|
|
199158
199424
|
if (this.pluginRunner) {
|
|
199159
199425
|
await this.pluginRunner.shutdown();
|
|
199160
199426
|
runtimeLog.log("PluginRunner shutdown complete");
|
|
@@ -203690,7 +203956,7 @@ ${detail}`
|
|
|
203690
203956
|
}
|
|
203691
203957
|
} else {
|
|
203692
203958
|
runtimeLog.log(`${hasManualResolver ? "Manual" : "Auto"}-merge merging ${taskId}...`);
|
|
203693
|
-
const semaphore = this.runtime.globalSemaphore;
|
|
203959
|
+
const semaphore = this.runtime.projectSemaphore ?? this.runtime.globalSemaphore;
|
|
203694
203960
|
const pool = this.runtime.worktreePool;
|
|
203695
203961
|
const agentStore = this.runtime.agentStore;
|
|
203696
203962
|
const usageLimitPauser = this.runtime.usageLimitPauser;
|
|
@@ -210701,6 +210967,7 @@ var init_sse2 = __esm({
|
|
|
210701
210967
|
var init_project_store_resolver = __esm({
|
|
210702
210968
|
"../dashboard/src/project-store-resolver.ts"() {
|
|
210703
210969
|
"use strict";
|
|
210970
|
+
init_src();
|
|
210704
210971
|
}
|
|
210705
210972
|
});
|
|
210706
210973
|
|
|
@@ -215816,6 +216083,15 @@ async function generatePrMetadata(input) {
|
|
|
215816
216083
|
const templateExists = await raceWithAbort(access9(templatePath).then(() => true).catch(() => false), combinedSignal);
|
|
215817
216084
|
const template = templateExists ? await raceWithAbort(readFile26(templatePath, "utf8"), combinedSignal) : "";
|
|
215818
216085
|
const model = resolveTitleSummarizerSettingsModel(settings);
|
|
216086
|
+
const systemPrompt = [PR_METADATA_SYSTEM_PROMPT];
|
|
216087
|
+
const titleGuidance = settings.prTitlePromptInstructions?.trim();
|
|
216088
|
+
const descriptionGuidance = settings.prDescriptionPromptInstructions?.trim();
|
|
216089
|
+
if (titleGuidance) {
|
|
216090
|
+
systemPrompt.push(`Title guidance: ${titleGuidance}`);
|
|
216091
|
+
}
|
|
216092
|
+
if (descriptionGuidance) {
|
|
216093
|
+
systemPrompt.push(`Description guidance: ${descriptionGuidance}`);
|
|
216094
|
+
}
|
|
215819
216095
|
const mcpServers = (await raceWithAbort(resolveMcpServersForStore(store ?? {}), combinedSignal)).servers;
|
|
215820
216096
|
let aiText = "";
|
|
215821
216097
|
const { session } = await raceWithAbort(createFnAgent2({
|
|
@@ -215828,26 +216104,27 @@ async function generatePrMetadata(input) {
|
|
|
215828
216104
|
mcpServers,
|
|
215829
216105
|
defaultProvider: model.provider,
|
|
215830
216106
|
defaultModelId: model.modelId,
|
|
215831
|
-
systemPrompt:
|
|
215832
|
-
"Generate GitHub PR metadata.",
|
|
215833
|
-
"Respond with strict JSON only.",
|
|
215834
|
-
"Schema: {title, summary, changes, testing, linkedTask}"
|
|
215835
|
-
].join("\n"),
|
|
216107
|
+
systemPrompt: systemPrompt.join("\n"),
|
|
215836
216108
|
onText: (delta) => {
|
|
215837
216109
|
aiText += delta;
|
|
215838
216110
|
}
|
|
215839
216111
|
}), combinedSignal);
|
|
215840
216112
|
try {
|
|
215841
216113
|
const contextPrompt = [
|
|
216114
|
+
"Source-of-truth guidance:",
|
|
216115
|
+
"- Ground the PR title, summary, changes, and testing in the commit log and diff stat below.",
|
|
216116
|
+
"- Use task title, task description, and task prompt only to clarify intent when the git evidence supports it.",
|
|
216117
|
+
"- Omit speculation; when evidence for testing or a change is absent, say so instead of inventing details.",
|
|
216118
|
+
"- Return only strict JSON matching the required schema.",
|
|
215842
216119
|
`Task ID: ${task.id}`,
|
|
215843
216120
|
`Task title: ${task.title}`,
|
|
215844
216121
|
`Task description: ${task.description ?? ""}`,
|
|
215845
216122
|
`Base branch: ${baseBranch}`,
|
|
215846
|
-
"Commit log:",
|
|
216123
|
+
"Commit log (source of truth):",
|
|
215847
216124
|
logOut || "(none)",
|
|
215848
|
-
"Diff stat:",
|
|
216125
|
+
"Diff stat (source of truth):",
|
|
215849
216126
|
diffStatOut || "(none)",
|
|
215850
|
-
"Task prompt:",
|
|
216127
|
+
"Task prompt (supporting context):",
|
|
215851
216128
|
promptContent || "(none)"
|
|
215852
216129
|
].join("\n\n");
|
|
215853
216130
|
throwIfAborted3(combinedSignal);
|
|
@@ -215880,7 +216157,7 @@ async function generatePrMetadata(input) {
|
|
|
215880
216157
|
}
|
|
215881
216158
|
}
|
|
215882
216159
|
}
|
|
215883
|
-
var execAsync36, PR_METADATA_TIMEOUT_MS;
|
|
216160
|
+
var execAsync36, PR_METADATA_TIMEOUT_MS, PR_METADATA_SYSTEM_PROMPT;
|
|
215884
216161
|
var init_pr_metadata_generator = __esm({
|
|
215885
216162
|
"../dashboard/src/pr-metadata-generator.ts"() {
|
|
215886
216163
|
"use strict";
|
|
@@ -215888,6 +216165,17 @@ var init_pr_metadata_generator = __esm({
|
|
|
215888
216165
|
init_src4();
|
|
215889
216166
|
execAsync36 = promisify55(execCb);
|
|
215890
216167
|
PR_METADATA_TIMEOUT_MS = 6e4;
|
|
216168
|
+
PR_METADATA_SYSTEM_PROMPT = [
|
|
216169
|
+
"You generate GitHub pull request metadata for Fusion tasks.",
|
|
216170
|
+
"Respond with STRICT JSON ONLY: no markdown fences, no commentary, no prose outside the JSON object.",
|
|
216171
|
+
'Use exactly this schema: {"title": string, "summary": string, "changes": string, "testing": string, "linkedTask": string}.',
|
|
216172
|
+
"Title: concise imperative conventional-commit style, at most 72 characters, no trailing period, with a scope when the change clearly suggests one.",
|
|
216173
|
+
"Summary: 1-3 short plain-sentence paragraphs explaining what changed and why; be specific and avoid marketing fluff.",
|
|
216174
|
+
"Changes: a tight markdown bullet list of concrete, verifiable changes supported by the commit log or diff stat; do not speculate.",
|
|
216175
|
+
"Testing: markdown bullets naming actual verification shown in the evidence; if no testing evidence exists, say '- Not provided.' or '- Not run (not provided).'.",
|
|
216176
|
+
"LinkedTask: set to the task ID exactly as provided.",
|
|
216177
|
+
"Ground every field in the commit log and diff stat first, then use the task title, task description, and task prompt only as supporting context."
|
|
216178
|
+
].join("\n");
|
|
215891
216179
|
}
|
|
215892
216180
|
});
|
|
215893
216181
|
|
|
@@ -227842,8 +228130,8 @@ Path: .fusion/tasks/${params.id}/attachments/${attachment.filename}`
|
|
|
227842
228130
|
pi2.registerTool({
|
|
227843
228131
|
name: "fn_task_pause",
|
|
227844
228132
|
label: "fn: Pause Task",
|
|
227845
|
-
description: "Pause a task \u2014 stops all automated agent and scheduler interaction
|
|
227846
|
-
promptSnippet: "Pause a Fusion task
|
|
228133
|
+
description: "Pause a task for explicit user-requested manual control \u2014 stops all automated agent and scheduler interaction. Agents should not pause tasks to handle failures or blockers; use retry, create/delegate follow-up work, or let the task surface as failed instead.",
|
|
228134
|
+
promptSnippet: "Pause a Fusion task only for explicit user-requested manual control; do not use for failure/blocker handling",
|
|
227847
228135
|
parameters: Type9.Object({
|
|
227848
228136
|
id: Type9.String({ description: "Task ID (e.g. FN-001)" })
|
|
227849
228137
|
}),
|