@runfusion/fusion 0.14.1 → 0.14.3
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 +934 -166
- package/dist/client/assets/AgentDetailView-BBCnqhqI.js +18 -0
- package/dist/client/assets/{AgentsView-DoXb_amw.js → AgentsView-BY-Yq-Te.js} +3 -3
- package/dist/client/assets/ChatView-DkoJNxFW.js +1 -0
- package/dist/client/assets/{DevServerView-DbgM4tlT.js → DevServerView-qvs6pp6c.js} +1 -1
- package/dist/client/assets/{DirectoryPicker-DfmtfMiu.js → DirectoryPicker-BkAIXNrP.js} +1 -1
- package/dist/client/assets/{DocumentsView-_-Efkx_W.js → DocumentsView-BcaUGgaL.js} +1 -1
- package/dist/client/assets/{InsightsView-DUjcfW53.js → InsightsView-Dz9Ivclw.js} +1 -1
- package/dist/client/assets/{MemoryView-DxMPBb0q.js → MemoryView-BsweARBT.js} +1 -1
- package/dist/client/assets/{NodesView-BEBTI15s.js → NodesView-bAU-v4bJ.js} +1 -1
- package/dist/client/assets/{PiExtensionsManager-BpMYhHH_.js → PiExtensionsManager-C_U2g7y3.js} +2 -2
- package/dist/client/assets/{PluginManager-CPv7yQd3.js → PluginManager-pIDsTk5v.js} +1 -1
- package/dist/client/assets/{ResearchView-BrFvdyXT.js → ResearchView-D4Eib_uR.js} +1 -1
- package/dist/client/assets/{RoadmapsView-BDjLrtcj.js → RoadmapsView-BaGwsUGS.js} +1 -1
- package/dist/client/assets/{SettingsModal-CxDxiTRy.js → SettingsModal-BiZVi3cI.js} +1 -1
- package/dist/client/assets/SettingsModal-CRyg643t.js +31 -0
- package/dist/client/assets/{SetupWizardModal-DFUA4X3z.js → SetupWizardModal-BcIGBBpA.js} +1 -1
- package/dist/client/assets/{SkillMultiselect-BUWe5ujb.js → SkillMultiselect-DPARHJeQ.js} +1 -1
- package/dist/client/assets/{SkillsView-RAkqGX3y.js → SkillsView-Da_d_HPu.js} +1 -1
- package/dist/client/assets/{TodoView-Ceb0wrg1.js → TodoView-5rAeqYtV.js} +1 -1
- package/dist/client/assets/{folder-open-DcM-Vd6r.js → folder-open-CgjcFqww.js} +1 -1
- package/dist/client/assets/index-D1gTSlYB.css +1 -0
- package/dist/client/assets/{index-DH3aprf6.js → index-DoQ5ALYY.js} +150 -149
- package/dist/client/assets/{list-checks-ByGHVQpZ.js → list-checks-C9YWtF7h.js} +1 -1
- package/dist/client/assets/{star-DlEYI8GL.js → star-4nUh67-U.js} +1 -1
- package/dist/client/assets/{upload-DKshabz-.js → upload-CEt5-Bnq.js} +1 -1
- package/dist/client/assets/{users-X6tYPPBV.js → users-4I0JDmgO.js} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/client/version.json +1 -1
- package/dist/extension.js +709 -146
- package/dist/pi-claude-cli/index.ts +2 -2
- package/dist/pi-claude-cli/package.json +1 -1
- package/dist/pi-claude-cli/src/__tests__/event-bridge.test.ts +107 -0
- package/dist/pi-claude-cli/src/event-bridge.ts +48 -4
- package/package.json +1 -1
- package/skill/fusion/references/engine-tools.md +0 -1
- package/dist/client/assets/AgentDetailView-B3KAsP2O.js +0 -18
- package/dist/client/assets/ChatView-BJ2c7wvd.js +0 -1
- package/dist/client/assets/SettingsModal-Cd-QGB0C.js +0 -31
- package/dist/client/assets/index-C1prPuSl.css +0 -1
package/dist/bin.js
CHANGED
|
@@ -77,6 +77,7 @@ var init_settings_schema = __esm({
|
|
|
77
77
|
favoriteModels: void 0,
|
|
78
78
|
openrouterModelSync: true,
|
|
79
79
|
updateCheckEnabled: true,
|
|
80
|
+
fnBinaryCheckEnabled: true,
|
|
80
81
|
updateCheckFrequency: "daily",
|
|
81
82
|
showGitHubStarButton: true,
|
|
82
83
|
modelOnboardingComplete: void 0,
|
|
@@ -3292,6 +3293,12 @@ CREATE INDEX IF NOT EXISTS idxTodoItemsSortOrder ON todo_items(listId, sortOrder
|
|
|
3292
3293
|
if (!inMemory && !isAbsolute(fusionDir)) {
|
|
3293
3294
|
throw new Error(`[fusion] Database constructor requires an absolute fusionDir path, got: ${fusionDir}`);
|
|
3294
3295
|
}
|
|
3296
|
+
if (!inMemory && /\.fusion[\\/]\.fusion(?:[\\/]|$)/.test(fusionDir)) {
|
|
3297
|
+
throw new Error(
|
|
3298
|
+
`[fusion] Refusing to open Database at nested .fusion/.fusion path: ${fusionDir}
|
|
3299
|
+
This means a caller passed a .fusion directory where a project root was expected. Audit the call site for an extra \`join(rootDir, '.fusion')\` step.`
|
|
3300
|
+
);
|
|
3301
|
+
}
|
|
3295
3302
|
if (!inMemory && !existsSync(fusionDir)) {
|
|
3296
3303
|
mkdirSync(fusionDir, { recursive: true });
|
|
3297
3304
|
}
|
|
@@ -6018,6 +6025,7 @@ var init_agent_store = __esm({
|
|
|
6018
6025
|
};
|
|
6019
6026
|
const line = JSON.stringify(safeEntry) + "\n";
|
|
6020
6027
|
await appendFile(this.runLogPath(agentId, runId), line, "utf-8");
|
|
6028
|
+
this.emit("run:log", agentId, runId, safeEntry);
|
|
6021
6029
|
}
|
|
6022
6030
|
/**
|
|
6023
6031
|
* Read all log entries for a given run from its JSONL file.
|
|
@@ -36344,12 +36352,16 @@ var init_gh_cli = __esm({
|
|
|
36344
36352
|
|
|
36345
36353
|
// ../core/src/fn-binary.ts
|
|
36346
36354
|
import { spawn as spawn2 } from "node:child_process";
|
|
36347
|
-
import { platform as platform2 } from "node:os";
|
|
36355
|
+
import { platform as platform2, tmpdir as tmpdir2 } from "node:os";
|
|
36348
36356
|
function runProbe(command, args, timeoutMs) {
|
|
36349
36357
|
return new Promise((resolve42) => {
|
|
36350
36358
|
let stdout = "";
|
|
36351
36359
|
let stderr = "";
|
|
36352
|
-
const child = spawn2(command, args, {
|
|
36360
|
+
const child = spawn2(command, args, {
|
|
36361
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
36362
|
+
shell: false,
|
|
36363
|
+
cwd: tmpdir2()
|
|
36364
|
+
});
|
|
36353
36365
|
const timer = setTimeout(() => {
|
|
36354
36366
|
try {
|
|
36355
36367
|
child.kill("SIGKILL");
|
|
@@ -37493,6 +37505,60 @@ var init_plugin_loader = __esm({
|
|
|
37493
37505
|
}
|
|
37494
37506
|
return runtimes2;
|
|
37495
37507
|
}
|
|
37508
|
+
/**
|
|
37509
|
+
* Get all skill contributions from loaded plugins.
|
|
37510
|
+
*/
|
|
37511
|
+
getPluginSkills() {
|
|
37512
|
+
const skills = [];
|
|
37513
|
+
for (const [pluginId, plugin4] of this.plugins) {
|
|
37514
|
+
if (plugin4.skills) {
|
|
37515
|
+
for (const skill of plugin4.skills) {
|
|
37516
|
+
skills.push({ pluginId, skill });
|
|
37517
|
+
}
|
|
37518
|
+
}
|
|
37519
|
+
}
|
|
37520
|
+
return skills;
|
|
37521
|
+
}
|
|
37522
|
+
/**
|
|
37523
|
+
* Get all workflow step contributions from loaded plugins.
|
|
37524
|
+
*/
|
|
37525
|
+
getPluginWorkflowSteps() {
|
|
37526
|
+
const steps = [];
|
|
37527
|
+
for (const [pluginId, plugin4] of this.plugins) {
|
|
37528
|
+
if (plugin4.workflowSteps) {
|
|
37529
|
+
for (const step of plugin4.workflowSteps) {
|
|
37530
|
+
steps.push({ pluginId, step });
|
|
37531
|
+
}
|
|
37532
|
+
}
|
|
37533
|
+
}
|
|
37534
|
+
return steps;
|
|
37535
|
+
}
|
|
37536
|
+
/**
|
|
37537
|
+
* Get all prompt contributions from loaded plugins.
|
|
37538
|
+
*/
|
|
37539
|
+
getPluginPromptContributions() {
|
|
37540
|
+
const contributions = [];
|
|
37541
|
+
for (const [pluginId, plugin4] of this.plugins) {
|
|
37542
|
+
if (plugin4.promptContributions) {
|
|
37543
|
+
for (const contribution of plugin4.promptContributions.contributions) {
|
|
37544
|
+
contributions.push({ pluginId, contribution, config: plugin4.promptContributions });
|
|
37545
|
+
}
|
|
37546
|
+
}
|
|
37547
|
+
}
|
|
37548
|
+
return contributions;
|
|
37549
|
+
}
|
|
37550
|
+
/**
|
|
37551
|
+
* Get all setup metadata and hooks from loaded plugins.
|
|
37552
|
+
*/
|
|
37553
|
+
getPluginSetupInfo() {
|
|
37554
|
+
const setups = [];
|
|
37555
|
+
for (const [pluginId, plugin4] of this.plugins) {
|
|
37556
|
+
if (plugin4.setup) {
|
|
37557
|
+
setups.push({ pluginId, manifest: plugin4.setup.manifest, hooks: plugin4.setup.hooks });
|
|
37558
|
+
}
|
|
37559
|
+
}
|
|
37560
|
+
return setups;
|
|
37561
|
+
}
|
|
37496
37562
|
/**
|
|
37497
37563
|
* Get all loaded plugin instances.
|
|
37498
37564
|
*/
|
|
@@ -37613,7 +37679,7 @@ async function syncBackupAutomation(automationStore, settings) {
|
|
|
37613
37679
|
if (!AutomationStore2.isValidCron(schedule)) {
|
|
37614
37680
|
throw new Error(`Invalid backup schedule: ${schedule}`);
|
|
37615
37681
|
}
|
|
37616
|
-
const command = "
|
|
37682
|
+
const command = "fn backup --create";
|
|
37617
37683
|
if (existingSchedule) {
|
|
37618
37684
|
return await automationStore.updateSchedule(existingSchedule.id, {
|
|
37619
37685
|
scheduleType: "custom",
|
|
@@ -37646,7 +37712,7 @@ async function syncBackupRoutine(routineStore, settings) {
|
|
|
37646
37712
|
if (!RoutineStore2.isValidCron(schedule)) {
|
|
37647
37713
|
throw new Error(`Invalid backup schedule: ${schedule}`);
|
|
37648
37714
|
}
|
|
37649
|
-
const command = "
|
|
37715
|
+
const command = "fn backup --create";
|
|
37650
37716
|
const input = {
|
|
37651
37717
|
name: BACKUP_SCHEDULE_NAME,
|
|
37652
37718
|
description: "Automatic database backup based on project settings",
|
|
@@ -49521,7 +49587,7 @@ var require_dist3 = __commonJS({
|
|
|
49521
49587
|
|
|
49522
49588
|
// ../core/src/agent-companies-parser.ts
|
|
49523
49589
|
import { existsSync as existsSync17, mkdtempSync, readdirSync as readdirSync2, readFileSync as readFileSync4, rmSync, statSync as statSync4 } from "node:fs";
|
|
49524
|
-
import { tmpdir as
|
|
49590
|
+
import { tmpdir as tmpdir3 } from "node:os";
|
|
49525
49591
|
import { join as join20, resolve as resolve9 } from "node:path";
|
|
49526
49592
|
function slugifyAgentReference(value) {
|
|
49527
49593
|
return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "");
|
|
@@ -49844,7 +49910,7 @@ async function extractTarArchive(archivePath, outputDir) {
|
|
|
49844
49910
|
}
|
|
49845
49911
|
async function parseCompanyArchive(archivePath) {
|
|
49846
49912
|
const resolvedArchivePath = resolve9(archivePath);
|
|
49847
|
-
const tempDir = mkdtempSync(join20(
|
|
49913
|
+
const tempDir = mkdtempSync(join20(tmpdir3(), "agent-companies-"));
|
|
49848
49914
|
try {
|
|
49849
49915
|
if (resolvedArchivePath.endsWith(".tar.gz") || resolvedArchivePath.endsWith(".tgz")) {
|
|
49850
49916
|
await extractTarArchive(resolvedArchivePath, tempDir);
|
|
@@ -56178,65 +56244,6 @@ ${lines.join("\n")}`
|
|
|
56178
56244
|
}
|
|
56179
56245
|
};
|
|
56180
56246
|
}
|
|
56181
|
-
function createIdentityTool({ agent, resolvedInstructions }) {
|
|
56182
|
-
const identityParams = Type.Object({});
|
|
56183
|
-
return {
|
|
56184
|
-
name: "fn_identity",
|
|
56185
|
-
label: "Identity Check",
|
|
56186
|
-
description: "Return a structured summary of which soul, instructions, and memory are loaded for this heartbeat tick. Call this FIRST before any other tool.",
|
|
56187
|
-
parameters: identityParams,
|
|
56188
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
56189
|
-
execute: async (_id, _params, _signal, _onUpdate, _ctx) => {
|
|
56190
|
-
const PREVIEW_CHARS = 500;
|
|
56191
|
-
const INSTRUCTIONS_PREVIEW_CHARS = 1e3;
|
|
56192
|
-
const MEMORY_PREVIEW_CHARS = 1e3;
|
|
56193
|
-
const soulPresent = typeof agent.soul === "string" && agent.soul.trim().length > 0;
|
|
56194
|
-
const instructionsPresent = resolvedInstructions.trim().length > 0;
|
|
56195
|
-
const memoryPresent = typeof agent.memory === "string" && agent.memory.trim().length > 0;
|
|
56196
|
-
const soulPreview = soulPresent ? agent.soul.slice(0, PREVIEW_CHARS) : "";
|
|
56197
|
-
const instructionsPreview = instructionsPresent ? resolvedInstructions.slice(0, INSTRUCTIONS_PREVIEW_CHARS) : "";
|
|
56198
|
-
const memoryPreview = memoryPresent ? agent.memory.slice(0, MEMORY_PREVIEW_CHARS) : "";
|
|
56199
|
-
const result = {
|
|
56200
|
-
agentId: agent.id,
|
|
56201
|
-
name: agent.name,
|
|
56202
|
-
role: agent.role,
|
|
56203
|
-
soulPresent,
|
|
56204
|
-
instructionsPresent,
|
|
56205
|
-
memoryPresent,
|
|
56206
|
-
soulPreview,
|
|
56207
|
-
instructionsPreview,
|
|
56208
|
-
memoryPreview
|
|
56209
|
-
};
|
|
56210
|
-
const lines = [
|
|
56211
|
-
`agentId: ${result.agentId}`,
|
|
56212
|
-
`name: ${result.name}`,
|
|
56213
|
-
`role: ${result.role}`,
|
|
56214
|
-
`soul: ${result.soulPresent ? "loaded" : "absent"}`,
|
|
56215
|
-
`instructions: ${result.instructionsPresent ? "loaded" : "absent"}`,
|
|
56216
|
-
`memory: ${result.memoryPresent ? "loaded" : "absent"}`
|
|
56217
|
-
];
|
|
56218
|
-
if (result.soulPresent && result.soulPreview) {
|
|
56219
|
-
lines.push(`
|
|
56220
|
-
Soul preview (first ${PREVIEW_CHARS} chars):
|
|
56221
|
-
${result.soulPreview}`);
|
|
56222
|
-
}
|
|
56223
|
-
if (result.instructionsPresent && result.instructionsPreview) {
|
|
56224
|
-
lines.push(`
|
|
56225
|
-
Instructions preview (first ${INSTRUCTIONS_PREVIEW_CHARS} chars):
|
|
56226
|
-
${result.instructionsPreview}`);
|
|
56227
|
-
}
|
|
56228
|
-
if (result.memoryPresent && result.memoryPreview) {
|
|
56229
|
-
lines.push(`
|
|
56230
|
-
Memory preview (first ${MEMORY_PREVIEW_CHARS} chars):
|
|
56231
|
-
${result.memoryPreview}`);
|
|
56232
|
-
}
|
|
56233
|
-
return {
|
|
56234
|
-
content: [{ type: "text", text: lines.join("\n") }],
|
|
56235
|
-
details: result
|
|
56236
|
-
};
|
|
56237
|
-
}
|
|
56238
|
-
};
|
|
56239
|
-
}
|
|
56240
56247
|
var taskCreateParams, taskLogParams, taskDocumentWriteParams, taskDocumentReadParams, reflectOnPerformanceParams, listAgentsParams, delegateTaskParams, sendMessageParams, readMessagesParams, memorySearchParams, memoryGetParams, researchRunParams, researchListParams, researchGetParams, researchCancelParams, memoryAppendParams, log10, AGENT_MEMORY_ROOT2, AGENT_MEMORY_FILENAME2, AGENT_DREAMS_FILENAME2, agentQmdRefreshState, AGENT_QMD_REFRESH_INTERVAL_MS, DAILY_AGENT_MEMORY_RE2;
|
|
56241
56248
|
var init_agent_tools = __esm({
|
|
56242
56249
|
"../engine/src/agent-tools.ts"() {
|
|
@@ -65772,6 +65779,20 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
65772
65779
|
);
|
|
65773
65780
|
this.activeStepExecutors.delete(task.id);
|
|
65774
65781
|
}
|
|
65782
|
+
if (this.activeWorkflowStepSessions.has(task.id)) {
|
|
65783
|
+
executorLog.log(`${task.id} moved from in-progress to ${to} \u2014 terminating workflow step session`);
|
|
65784
|
+
this.pausedAborted.add(task.id);
|
|
65785
|
+
this.options.stuckTaskDetector?.untrackTask(task.id);
|
|
65786
|
+
const workflowSession = this.activeWorkflowStepSessions.get(task.id);
|
|
65787
|
+
const sessionWithAbort = workflowSession;
|
|
65788
|
+
if (typeof sessionWithAbort.abort === "function") {
|
|
65789
|
+
void sessionWithAbort.abort().catch((err) => {
|
|
65790
|
+
executorLog.warn(`Failed to abort workflow step session for ${task.id}: ${err}`);
|
|
65791
|
+
});
|
|
65792
|
+
}
|
|
65793
|
+
workflowSession.dispose();
|
|
65794
|
+
this.activeWorkflowStepSessions.delete(task.id);
|
|
65795
|
+
}
|
|
65775
65796
|
this.disposeSubagentsForTask(task.id, `parent moved from in-progress to ${to}`);
|
|
65776
65797
|
this.loopRecoveryState.delete(task.id);
|
|
65777
65798
|
this.spawnedAgents.delete(task.id);
|
|
@@ -65804,8 +65825,42 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
65804
65825
|
this.disposeSubagentsForTask(task.id, "task paused");
|
|
65805
65826
|
return;
|
|
65806
65827
|
}
|
|
65807
|
-
if (
|
|
65828
|
+
if (task.paused && this.activeWorkflowStepSessions.has(task.id)) {
|
|
65829
|
+
executorLog.log(`Pausing ${task.id} \u2014 terminating workflow step session`);
|
|
65830
|
+
this.pausedAborted.add(task.id);
|
|
65831
|
+
this.options.stuckTaskDetector?.untrackTask(task.id);
|
|
65832
|
+
const workflowSession = this.activeWorkflowStepSessions.get(task.id);
|
|
65833
|
+
const sessionWithAbort = workflowSession;
|
|
65834
|
+
if (typeof sessionWithAbort.abort === "function") {
|
|
65835
|
+
await sessionWithAbort.abort().catch(
|
|
65836
|
+
(err) => executorLog.warn(`Failed to abort workflow step session for pause ${task.id}: ${err}`)
|
|
65837
|
+
);
|
|
65838
|
+
}
|
|
65839
|
+
workflowSession.dispose();
|
|
65840
|
+
this.activeWorkflowStepSessions.delete(task.id);
|
|
65841
|
+
this.loopRecoveryState.delete(task.id);
|
|
65842
|
+
this.spawnedAgents.delete(task.id);
|
|
65843
|
+
this.stuckAborted.delete(task.id);
|
|
65844
|
+
this.disposeSubagentsForTask(task.id, "task paused");
|
|
65845
|
+
return;
|
|
65846
|
+
}
|
|
65847
|
+
if (!task.paused && task.column === "in-progress" && !this.activeSessions.has(task.id) && !this.activeStepExecutors.has(task.id) && !this.activeWorkflowStepSessions.has(task.id)) {
|
|
65808
65848
|
if (!this.executing.has(task.id) && !this.resumingUnpaused.has(task.id) && !this.recoveringCompleted.has(task.id)) {
|
|
65849
|
+
const pauseLabel = await this.getExecutionPauseLabel();
|
|
65850
|
+
if (pauseLabel) {
|
|
65851
|
+
executorLog.log(`Skipping unpause resume for ${task.id} \u2014 ${pauseLabel} active`);
|
|
65852
|
+
return;
|
|
65853
|
+
}
|
|
65854
|
+
if (this.isTaskWorkComplete(task) && !task.mergeDetails) {
|
|
65855
|
+
this.recoveringCompleted.add(task.id);
|
|
65856
|
+
executorLog.log(`${task.id} unpaused with completed work and no session \u2014 recovering directly to in-review`);
|
|
65857
|
+
void this.recoverCompletedTask(task).catch(
|
|
65858
|
+
(err) => executorLog.error(`Failed to recover completed unpaused task ${task.id}:`, err)
|
|
65859
|
+
).finally(() => {
|
|
65860
|
+
this.recoveringCompleted.delete(task.id);
|
|
65861
|
+
});
|
|
65862
|
+
return;
|
|
65863
|
+
}
|
|
65809
65864
|
this.resumingUnpaused.add(task.id);
|
|
65810
65865
|
executorLog.log(`Unpaused ${task.id} in-progress with no session \u2014 resuming execution`);
|
|
65811
65866
|
try {
|
|
@@ -65924,6 +65979,22 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
65924
65979
|
this.spawnedAgents.delete(taskId);
|
|
65925
65980
|
this.stuckAborted.delete(taskId);
|
|
65926
65981
|
}
|
|
65982
|
+
for (const [taskId, workflowSession] of this.activeWorkflowStepSessions) {
|
|
65983
|
+
executorLog.log(`Global pause \u2014 terminating workflow step session for ${taskId}`);
|
|
65984
|
+
this.pausedAborted.add(taskId);
|
|
65985
|
+
this.options.stuckTaskDetector?.untrackTask(taskId);
|
|
65986
|
+
const sessionWithAbort = workflowSession;
|
|
65987
|
+
if (typeof sessionWithAbort.abort === "function") {
|
|
65988
|
+
void sessionWithAbort.abort().catch((err) => {
|
|
65989
|
+
executorLog.warn(`Failed to abort workflow step session for ${taskId}: ${err}`);
|
|
65990
|
+
});
|
|
65991
|
+
}
|
|
65992
|
+
workflowSession.dispose();
|
|
65993
|
+
this.activeWorkflowStepSessions.delete(taskId);
|
|
65994
|
+
this.loopRecoveryState.delete(taskId);
|
|
65995
|
+
this.spawnedAgents.delete(taskId);
|
|
65996
|
+
this.stuckAborted.delete(taskId);
|
|
65997
|
+
}
|
|
65927
65998
|
}
|
|
65928
65999
|
});
|
|
65929
66000
|
}
|
|
@@ -65941,6 +66012,8 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
65941
66012
|
activeSessions = /* @__PURE__ */ new Map();
|
|
65942
66013
|
/** Active step-session executors per task (mutually exclusive with activeSessions). */
|
|
65943
66014
|
activeStepExecutors = /* @__PURE__ */ new Map();
|
|
66015
|
+
/** Active pre-merge workflow step sessions per task. */
|
|
66016
|
+
activeWorkflowStepSessions = /* @__PURE__ */ new Map();
|
|
65944
66017
|
/**
|
|
65945
66018
|
* Reviewer subagent sessions per task. Reviewers (`reviewer.ts`) create their
|
|
65946
66019
|
* own AgentSessions that aren't part of `activeSessions`/`activeStepExecutors`,
|
|
@@ -65987,6 +66060,69 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
65987
66060
|
await this.store.mergeTask(taskId);
|
|
65988
66061
|
return "merged";
|
|
65989
66062
|
}
|
|
66063
|
+
async getExecutionPauseLabel() {
|
|
66064
|
+
const settings = await this.store.getSettings();
|
|
66065
|
+
if (settings.globalPause) return "global pause";
|
|
66066
|
+
if (settings.enginePaused) return "engine pause";
|
|
66067
|
+
return null;
|
|
66068
|
+
}
|
|
66069
|
+
async shouldDeferCompletionForGlobalPause(taskId, context) {
|
|
66070
|
+
const settings = await this.store.getSettings();
|
|
66071
|
+
if (!settings.globalPause) {
|
|
66072
|
+
return false;
|
|
66073
|
+
}
|
|
66074
|
+
this.clearCompletedTaskWatchdog(taskId);
|
|
66075
|
+
executorLog.log(`${taskId}: completion handoff deferred \u2014 global pause active (${context})`);
|
|
66076
|
+
await this.store.logEntry(
|
|
66077
|
+
taskId,
|
|
66078
|
+
`Completion handoff deferred \u2014 global pause active (${context})`,
|
|
66079
|
+
void 0,
|
|
66080
|
+
this.currentRunContext
|
|
66081
|
+
).catch(() => void 0);
|
|
66082
|
+
return true;
|
|
66083
|
+
}
|
|
66084
|
+
async shouldDeferWorkflowStepCompletion(taskId, context) {
|
|
66085
|
+
let latestTask = null;
|
|
66086
|
+
try {
|
|
66087
|
+
latestTask = await this.store.getTask(taskId);
|
|
66088
|
+
} catch {
|
|
66089
|
+
latestTask = null;
|
|
66090
|
+
}
|
|
66091
|
+
if (latestTask?.paused || this.pausedAborted.has(taskId)) {
|
|
66092
|
+
this.clearCompletedTaskWatchdog(taskId);
|
|
66093
|
+
executorLog.log(`${taskId}: completion handoff deferred \u2014 task paused (${context})`);
|
|
66094
|
+
await this.store.logEntry(
|
|
66095
|
+
taskId,
|
|
66096
|
+
`Completion handoff deferred \u2014 task paused (${context})`,
|
|
66097
|
+
void 0,
|
|
66098
|
+
this.currentRunContext
|
|
66099
|
+
).catch(() => void 0);
|
|
66100
|
+
return true;
|
|
66101
|
+
}
|
|
66102
|
+
return this.shouldDeferCompletionForGlobalPause(taskId, context);
|
|
66103
|
+
}
|
|
66104
|
+
async parkTaskAfterWorkflowStepPause(taskId) {
|
|
66105
|
+
let latestTask = null;
|
|
66106
|
+
try {
|
|
66107
|
+
latestTask = await this.store.getTask(taskId);
|
|
66108
|
+
} catch {
|
|
66109
|
+
latestTask = null;
|
|
66110
|
+
}
|
|
66111
|
+
if (!latestTask?.paused) {
|
|
66112
|
+
return false;
|
|
66113
|
+
}
|
|
66114
|
+
executorLog.log(`${taskId}: workflow step interrupted by task pause \u2014 moving to todo`);
|
|
66115
|
+
await this.store.logEntry(
|
|
66116
|
+
taskId,
|
|
66117
|
+
"Execution paused during pre-merge workflow step \u2014 moved to todo",
|
|
66118
|
+
void 0,
|
|
66119
|
+
this.currentRunContext
|
|
66120
|
+
).catch(() => void 0);
|
|
66121
|
+
if (latestTask.column === "in-progress") {
|
|
66122
|
+
await this.store.moveTask(taskId, "todo", { preserveResumeState: true });
|
|
66123
|
+
}
|
|
66124
|
+
return true;
|
|
66125
|
+
}
|
|
65990
66126
|
/** Child agent sessions keyed by agent ID. Used for termination. */
|
|
65991
66127
|
childSessions = /* @__PURE__ */ new Map();
|
|
65992
66128
|
/** Total count of currently spawned agents (across all parents). */
|
|
@@ -66181,11 +66317,15 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
66181
66317
|
this.clearCompletedTaskWatchdog(taskId);
|
|
66182
66318
|
const handle = setTimeout(async () => {
|
|
66183
66319
|
this.completedTaskWatchdogs.delete(taskId);
|
|
66184
|
-
if (this.recoveringCompleted.has(taskId) || this.executing.has(taskId) || this.activeSessions.has(taskId) || this.activeStepExecutors.has(taskId) || this.resumingUnpaused.has(taskId)) {
|
|
66320
|
+
if (this.recoveringCompleted.has(taskId) || this.executing.has(taskId) || this.activeSessions.has(taskId) || this.activeStepExecutors.has(taskId) || this.activeWorkflowStepSessions.has(taskId) || this.resumingUnpaused.has(taskId)) {
|
|
66185
66321
|
return;
|
|
66186
66322
|
}
|
|
66187
66323
|
this.recoveringCompleted.add(taskId);
|
|
66188
66324
|
try {
|
|
66325
|
+
const pauseLabel = await this.getExecutionPauseLabel();
|
|
66326
|
+
if (pauseLabel) {
|
|
66327
|
+
return;
|
|
66328
|
+
}
|
|
66189
66329
|
let currentTask = null;
|
|
66190
66330
|
try {
|
|
66191
66331
|
currentTask = await this.store.getTask(taskId);
|
|
@@ -66231,6 +66371,11 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
66231
66371
|
* stuck.
|
|
66232
66372
|
*/
|
|
66233
66373
|
async performWorkflowRerunBounce(taskId, worktreePath, preserveResumeState = true) {
|
|
66374
|
+
const pauseLabel = await this.getExecutionPauseLabel();
|
|
66375
|
+
if (pauseLabel) {
|
|
66376
|
+
executorLog.log(`${taskId}: workflow rerun deferred \u2014 ${pauseLabel} active`);
|
|
66377
|
+
return "deferred-paused";
|
|
66378
|
+
}
|
|
66234
66379
|
if (this.workflowRerunPending.has(taskId)) {
|
|
66235
66380
|
executorLog.warn(`${taskId}: workflow rerun bounce already in flight \u2014 skipping re-entry`);
|
|
66236
66381
|
return "skipped-pending";
|
|
@@ -66241,6 +66386,10 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
66241
66386
|
if (!latestTask) {
|
|
66242
66387
|
throw new Error("task missing during workflow rerun bounce");
|
|
66243
66388
|
}
|
|
66389
|
+
if (latestTask.paused) {
|
|
66390
|
+
executorLog.log(`${taskId}: workflow rerun deferred \u2014 task is paused`);
|
|
66391
|
+
return "deferred-paused";
|
|
66392
|
+
}
|
|
66244
66393
|
if (latestTask.column === "in-progress") {
|
|
66245
66394
|
const originalExecutionStartedAt = latestTask.executionStartedAt;
|
|
66246
66395
|
if (preserveResumeState) {
|
|
@@ -66252,11 +66401,21 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
66252
66401
|
worktree: worktreePath,
|
|
66253
66402
|
executionStartedAt: originalExecutionStartedAt ?? null
|
|
66254
66403
|
});
|
|
66404
|
+
const pauseLabelAfterTodo = await this.getExecutionPauseLabel();
|
|
66405
|
+
if (pauseLabelAfterTodo) {
|
|
66406
|
+
executorLog.log(`${taskId}: workflow rerun parked in todo \u2014 ${pauseLabelAfterTodo} became active during bounce`);
|
|
66407
|
+
return "deferred-paused";
|
|
66408
|
+
}
|
|
66255
66409
|
await this.store.moveTask(taskId, "in-progress");
|
|
66256
66410
|
return "bounced";
|
|
66257
66411
|
}
|
|
66258
66412
|
if (latestTask.column === "todo") {
|
|
66259
66413
|
await this.store.updateTask(taskId, { worktree: worktreePath });
|
|
66414
|
+
const pauseLabelBeforeResume = await this.getExecutionPauseLabel();
|
|
66415
|
+
if (pauseLabelBeforeResume) {
|
|
66416
|
+
executorLog.log(`${taskId}: workflow rerun parked in todo \u2014 ${pauseLabelBeforeResume} became active before resume`);
|
|
66417
|
+
return "deferred-paused";
|
|
66418
|
+
}
|
|
66260
66419
|
await this.store.moveTask(taskId, "in-progress");
|
|
66261
66420
|
return "bounced";
|
|
66262
66421
|
}
|
|
@@ -66272,8 +66431,10 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
66272
66431
|
const outcome = await this.performWorkflowRerunBounce(taskId, worktreePath, preserveResumeState);
|
|
66273
66432
|
if (outcome === "bounced") {
|
|
66274
66433
|
executorLog.log(successMessage);
|
|
66275
|
-
} else {
|
|
66434
|
+
} else if (outcome === "skipped-pending") {
|
|
66276
66435
|
executorLog.warn(`${taskId}: rerun bounce skipped \u2014 another bounce already in flight`);
|
|
66436
|
+
} else {
|
|
66437
|
+
executorLog.log(`${taskId}: rerun bounce deferred while pause is active`);
|
|
66277
66438
|
}
|
|
66278
66439
|
} catch (err) {
|
|
66279
66440
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
@@ -66282,6 +66443,11 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
66282
66443
|
}, 0);
|
|
66283
66444
|
const watchdog = setTimeout(async () => {
|
|
66284
66445
|
this.workflowRerunWatchdogs.delete(taskId);
|
|
66446
|
+
const pauseLabel = await this.getExecutionPauseLabel();
|
|
66447
|
+
if (pauseLabel) {
|
|
66448
|
+
executorLog.log(`${taskId}: workflow rerun watchdog skipped \u2014 ${pauseLabel} active`);
|
|
66449
|
+
return;
|
|
66450
|
+
}
|
|
66285
66451
|
let currentTask = null;
|
|
66286
66452
|
try {
|
|
66287
66453
|
currentTask = await this.store.getTask(taskId);
|
|
@@ -66304,7 +66470,7 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
66304
66470
|
const outcome = await this.performWorkflowRerunBounce(taskId, worktreePath, preserveResumeState);
|
|
66305
66471
|
if (outcome === "bounced") {
|
|
66306
66472
|
executorLog.warn(`${taskId}: workflow rerun watchdog retry succeeded`);
|
|
66307
|
-
} else {
|
|
66473
|
+
} else if (outcome === "skipped-pending") {
|
|
66308
66474
|
executorLog.error(
|
|
66309
66475
|
`${taskId}: workflow rerun watchdog retry skipped \u2014 original bounce still in flight after ${WORKFLOW_RERUN_WATCHDOG_MS / 1e3}s; task may be stuck`
|
|
66310
66476
|
);
|
|
@@ -66312,6 +66478,8 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
66312
66478
|
taskId,
|
|
66313
66479
|
`Workflow rerun watchdog retry skipped \u2014 original bounce still in flight after ${WORKFLOW_RERUN_WATCHDOG_MS / 1e3}s; task may be stuck`
|
|
66314
66480
|
).catch(() => void 0);
|
|
66481
|
+
} else {
|
|
66482
|
+
executorLog.log(`${taskId}: workflow rerun watchdog retry deferred while pause is active`);
|
|
66315
66483
|
}
|
|
66316
66484
|
} catch (err) {
|
|
66317
66485
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
@@ -66434,11 +66602,17 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
66434
66602
|
*/
|
|
66435
66603
|
async recoverCompletedTask(task) {
|
|
66436
66604
|
try {
|
|
66437
|
-
if (this.executing.has(task.id) || this.activeSessions.has(task.id) || this.activeStepExecutors.has(task.id) || this.resumingUnpaused.has(task.id)) {
|
|
66605
|
+
if (this.executing.has(task.id) || this.activeSessions.has(task.id) || this.activeStepExecutors.has(task.id) || this.activeWorkflowStepSessions.has(task.id) || this.resumingUnpaused.has(task.id)) {
|
|
66438
66606
|
executorLog.log(`${task.id}: skipping recoverCompletedTask \u2014 task has active execution in flight`);
|
|
66439
66607
|
return false;
|
|
66440
66608
|
}
|
|
66441
66609
|
const settings = await this.store.getSettings();
|
|
66610
|
+
if (settings.globalPause || settings.enginePaused) {
|
|
66611
|
+
executorLog.log(
|
|
66612
|
+
`${task.id}: skipping recoverCompletedTask \u2014 ${settings.globalPause ? "global pause" : "engine pause"} active`
|
|
66613
|
+
);
|
|
66614
|
+
return false;
|
|
66615
|
+
}
|
|
66442
66616
|
if (task.worktree && existsSync27(task.worktree)) {
|
|
66443
66617
|
const modifiedFiles = await this.captureModifiedFiles(task.worktree, task.baseCommitSha);
|
|
66444
66618
|
if (modifiedFiles.length > 0) {
|
|
@@ -66446,7 +66620,16 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
66446
66620
|
executorLog.log(`${task.id}: recovered ${modifiedFiles.length} modified files`);
|
|
66447
66621
|
}
|
|
66448
66622
|
if (task.executionMode !== "fast") {
|
|
66623
|
+
if (await this.shouldDeferCompletionForGlobalPause(task.id, "before workflow steps during completed-task recovery")) {
|
|
66624
|
+
return false;
|
|
66625
|
+
}
|
|
66449
66626
|
const workflowResult = await this.runWorkflowSteps(task, task.worktree, settings);
|
|
66627
|
+
if (workflowResult === "deferred-paused") {
|
|
66628
|
+
if (this.pausedAborted.has(task.id)) {
|
|
66629
|
+
this.pausedAborted.delete(task.id);
|
|
66630
|
+
}
|
|
66631
|
+
return false;
|
|
66632
|
+
}
|
|
66450
66633
|
if (!workflowResult.allPassed) {
|
|
66451
66634
|
await this.sendTaskBackForFix(task, task.worktree, workflowResult.feedback, workflowResult.stepName || "Unknown", "Workflow step failed during recovery", false);
|
|
66452
66635
|
return true;
|
|
@@ -66455,6 +66638,9 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
66455
66638
|
executorLog.log(`${task.id}: fast mode \u2014 skipping workflow steps on auto-recovery`);
|
|
66456
66639
|
}
|
|
66457
66640
|
}
|
|
66641
|
+
if (await this.shouldDeferCompletionForGlobalPause(task.id, "before in-review transition during completed-task recovery")) {
|
|
66642
|
+
return false;
|
|
66643
|
+
}
|
|
66458
66644
|
await this.persistTokenUsage(task.id);
|
|
66459
66645
|
await this.store.moveTask(task.id, "in-review");
|
|
66460
66646
|
this.clearCompletedTaskWatchdog(task.id);
|
|
@@ -66520,6 +66706,13 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
66520
66706
|
* directly to in-review without spawning a new agent session.
|
|
66521
66707
|
*/
|
|
66522
66708
|
async resumeOrphaned() {
|
|
66709
|
+
const settings = await this.store.getSettings();
|
|
66710
|
+
if (settings.globalPause || settings.enginePaused) {
|
|
66711
|
+
executorLog.log(
|
|
66712
|
+
`resumeOrphaned skipped \u2014 ${settings.globalPause ? "global pause" : "engine pause"} is active`
|
|
66713
|
+
);
|
|
66714
|
+
return;
|
|
66715
|
+
}
|
|
66523
66716
|
const tasks = await this.store.listTasks({ slim: true, column: "in-progress" });
|
|
66524
66717
|
const inProgress = tasks.filter(
|
|
66525
66718
|
(t) => t.column === "in-progress" && !this.executing.has(t.id) && !t.paused
|
|
@@ -66958,8 +67151,21 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
66958
67151
|
await audit.filesystem({ type: "file:capture-modified", target: task.id, metadata: { files: modifiedFiles } });
|
|
66959
67152
|
}
|
|
66960
67153
|
this.scheduleCompletedTaskWatchdog(task.id, "step-session completion");
|
|
67154
|
+
if (await this.shouldDeferCompletionForGlobalPause(task.id, "before workflow steps after step-session completion")) {
|
|
67155
|
+
return;
|
|
67156
|
+
}
|
|
66961
67157
|
if (executionMode !== "fast") {
|
|
66962
67158
|
const workflowResult = await this.runWorkflowSteps(task, worktreePath, settings);
|
|
67159
|
+
if (workflowResult === "deferred-paused") {
|
|
67160
|
+
if (await this.parkTaskAfterWorkflowStepPause(task.id)) {
|
|
67161
|
+
this.pausedAborted.delete(task.id);
|
|
67162
|
+
return;
|
|
67163
|
+
}
|
|
67164
|
+
if (this.pausedAborted.has(task.id)) {
|
|
67165
|
+
this.pausedAborted.delete(task.id);
|
|
67166
|
+
}
|
|
67167
|
+
return;
|
|
67168
|
+
}
|
|
66963
67169
|
if (!workflowResult.allPassed) {
|
|
66964
67170
|
if (workflowResult.revisionRequested) {
|
|
66965
67171
|
await this.handleWorkflowRevisionRequest(task, worktreePath, workflowResult.feedback, workflowResult.stepName);
|
|
@@ -66977,6 +67183,9 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
66977
67183
|
await this.store.logEntry(task.id, "Fast mode \u2014 pre-merge workflow steps skipped", void 0, this.currentRunContext);
|
|
66978
67184
|
}
|
|
66979
67185
|
await this.store.updateTask(task.id, { workflowStepRetries: void 0, taskDoneRetryCount: null });
|
|
67186
|
+
if (await this.shouldDeferCompletionForGlobalPause(task.id, "before in-review transition after step-session completion")) {
|
|
67187
|
+
return;
|
|
67188
|
+
}
|
|
66980
67189
|
await this.store.moveTask(task.id, "in-review");
|
|
66981
67190
|
this.clearCompletedTaskWatchdog(task.id);
|
|
66982
67191
|
await audit.database({ type: "task:move", target: task.id, metadata: { to: "in-review" } });
|
|
@@ -67329,6 +67538,9 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
67329
67538
|
this.pausedAborted.delete(task.id);
|
|
67330
67539
|
wasPaused = true;
|
|
67331
67540
|
if (await this.shouldFinalizeCompletedTask(task.id, taskDone)) {
|
|
67541
|
+
if (await this.shouldDeferCompletionForGlobalPause(task.id, "paused after completion")) {
|
|
67542
|
+
return;
|
|
67543
|
+
}
|
|
67332
67544
|
executorLog.log(`${task.id} paused after completion (graceful session exit) \u2014 finalizing to in-review`);
|
|
67333
67545
|
await this.store.logEntry(task.id, "Execution paused after completion \u2014 finalizing to in-review");
|
|
67334
67546
|
await this.persistTokenUsage(task.id);
|
|
@@ -67365,8 +67577,23 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
67365
67577
|
executorLog.log(`${task.id}: captured ${modifiedFiles.length} modified files`);
|
|
67366
67578
|
}
|
|
67367
67579
|
this.scheduleCompletedTaskWatchdog(task.id, "task completion");
|
|
67580
|
+
if (await this.shouldDeferCompletionForGlobalPause(task.id, "before workflow steps after task completion")) {
|
|
67581
|
+
return;
|
|
67582
|
+
}
|
|
67368
67583
|
if (executionMode !== "fast") {
|
|
67369
67584
|
const workflowResult = await this.runWorkflowSteps(task, worktreePath, settings);
|
|
67585
|
+
if (workflowResult === "deferred-paused") {
|
|
67586
|
+
if (await this.parkTaskAfterWorkflowStepPause(task.id)) {
|
|
67587
|
+
this.pausedAborted.delete(task.id);
|
|
67588
|
+
wasPaused = true;
|
|
67589
|
+
return;
|
|
67590
|
+
}
|
|
67591
|
+
if (this.pausedAborted.has(task.id)) {
|
|
67592
|
+
this.pausedAborted.delete(task.id);
|
|
67593
|
+
wasPaused = true;
|
|
67594
|
+
}
|
|
67595
|
+
return;
|
|
67596
|
+
}
|
|
67370
67597
|
if (!workflowResult.allPassed) {
|
|
67371
67598
|
if (workflowResult.revisionRequested) {
|
|
67372
67599
|
await this.handleWorkflowRevisionRequest(task, worktreePath, workflowResult.feedback, workflowResult.stepName);
|
|
@@ -67384,6 +67611,9 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
67384
67611
|
await this.store.logEntry(task.id, "Fast mode \u2014 pre-merge workflow steps skipped", void 0, this.currentRunContext);
|
|
67385
67612
|
}
|
|
67386
67613
|
await this.store.updateTask(task.id, { workflowStepRetries: void 0, taskDoneRetryCount: null });
|
|
67614
|
+
if (await this.shouldDeferCompletionForGlobalPause(task.id, "before in-review transition after task completion")) {
|
|
67615
|
+
return;
|
|
67616
|
+
}
|
|
67387
67617
|
await this.persistTokenUsage(task.id);
|
|
67388
67618
|
await this.store.moveTask(task.id, "in-review");
|
|
67389
67619
|
this.clearCompletedTaskWatchdog(task.id);
|
|
@@ -67508,8 +67738,23 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
67508
67738
|
executorLog.log(`${task.id}: captured ${modifiedFiles.length} modified files`);
|
|
67509
67739
|
}
|
|
67510
67740
|
this.scheduleCompletedTaskWatchdog(task.id, "task completion retry");
|
|
67741
|
+
if (await this.shouldDeferCompletionForGlobalPause(task.id, "before workflow steps after task completion retry")) {
|
|
67742
|
+
return;
|
|
67743
|
+
}
|
|
67511
67744
|
if (executionMode !== "fast") {
|
|
67512
67745
|
const workflowResult = await this.runWorkflowSteps(task, worktreePath, settings);
|
|
67746
|
+
if (workflowResult === "deferred-paused") {
|
|
67747
|
+
if (await this.parkTaskAfterWorkflowStepPause(task.id)) {
|
|
67748
|
+
this.pausedAborted.delete(task.id);
|
|
67749
|
+
wasPaused = true;
|
|
67750
|
+
return;
|
|
67751
|
+
}
|
|
67752
|
+
if (this.pausedAborted.has(task.id)) {
|
|
67753
|
+
this.pausedAborted.delete(task.id);
|
|
67754
|
+
wasPaused = true;
|
|
67755
|
+
}
|
|
67756
|
+
return;
|
|
67757
|
+
}
|
|
67513
67758
|
if (!workflowResult.allPassed) {
|
|
67514
67759
|
if (workflowResult.revisionRequested) {
|
|
67515
67760
|
await this.handleWorkflowRevisionRequest(task, worktreePath, workflowResult.feedback, workflowResult.stepName);
|
|
@@ -67523,6 +67768,9 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
67523
67768
|
await this.store.logEntry(task.id, "Fast mode \u2014 pre-merge workflow steps skipped", void 0, this.currentRunContext);
|
|
67524
67769
|
}
|
|
67525
67770
|
await this.store.updateTask(task.id, { workflowStepRetries: void 0, taskDoneRetryCount: null });
|
|
67771
|
+
if (await this.shouldDeferCompletionForGlobalPause(task.id, "before in-review transition after task completion retry")) {
|
|
67772
|
+
return;
|
|
67773
|
+
}
|
|
67526
67774
|
await this.persistTokenUsage(task.id);
|
|
67527
67775
|
await this.store.moveTask(task.id, "in-review");
|
|
67528
67776
|
this.clearCompletedTaskWatchdog(task.id);
|
|
@@ -67615,6 +67863,9 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
67615
67863
|
} else if (this.pausedAborted.has(task.id)) {
|
|
67616
67864
|
this.pausedAborted.delete(task.id);
|
|
67617
67865
|
if (await this.shouldFinalizeCompletedTask(task.id, taskDone)) {
|
|
67866
|
+
if (await this.shouldDeferCompletionForGlobalPause(task.id, "paused after completion")) {
|
|
67867
|
+
return;
|
|
67868
|
+
}
|
|
67618
67869
|
executorLog.log(`${task.id} paused after completion \u2014 finalizing to in-review`);
|
|
67619
67870
|
await this.store.logEntry(task.id, "Execution paused after completion \u2014 finalizing to in-review", void 0, this.currentRunContext);
|
|
67620
67871
|
await this.persistTokenUsage(task.id);
|
|
@@ -67973,22 +68224,28 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
67973
68224
|
if (params.summary) {
|
|
67974
68225
|
await store.updateTask(taskId, { summary: params.summary });
|
|
67975
68226
|
}
|
|
67976
|
-
await store.
|
|
68227
|
+
const settings = await store.getSettings();
|
|
68228
|
+
const hardPauseActive = Boolean(task.paused || settings.globalPause);
|
|
68229
|
+
if (hardPauseActive) {
|
|
68230
|
+
await store.updateTask(taskId, { status: null });
|
|
68231
|
+
} else {
|
|
68232
|
+
await store.updateTask(taskId, { paused: false, status: null });
|
|
68233
|
+
}
|
|
67977
68234
|
await store.logEntry(taskId, "Task marked done by agent");
|
|
67978
68235
|
const latestTask = await store.getTask(taskId);
|
|
67979
68236
|
let latestColumn = latestTask.column;
|
|
67980
68237
|
if (latestColumn === "todo") {
|
|
67981
68238
|
await store.logEntry(
|
|
67982
68239
|
taskId,
|
|
67983
|
-
"fn_task_done called while task was in todo \u2014 promoting to in-progress before completion handoff"
|
|
68240
|
+
hardPauseActive ? "fn_task_done called while task was in todo during pause \u2014 promoting to in-progress for deferred completion handoff" : "fn_task_done called while task was in todo \u2014 promoting to in-progress before completion handoff"
|
|
67984
68241
|
);
|
|
67985
68242
|
await store.moveTask(taskId, "in-progress");
|
|
67986
68243
|
latestColumn = "in-progress";
|
|
67987
68244
|
}
|
|
67988
|
-
if (latestColumn === "in-progress") {
|
|
68245
|
+
if (latestColumn === "in-progress" && !hardPauseActive) {
|
|
67989
68246
|
this.scheduleCompletedTaskWatchdog(taskId, "fn_task_done");
|
|
67990
68247
|
}
|
|
67991
|
-
const successMessage = params.summary ? "Task marked complete with summary. All steps done. Moving to in-review." : "Task marked complete. All steps done. Moving to in-review.";
|
|
68248
|
+
const successMessage = hardPauseActive ? "Task marked complete. Completion handoff deferred until pause is cleared." : params.summary ? "Task marked complete with summary. All steps done. Moving to in-review." : "Task marked complete. All steps done. Moving to in-review.";
|
|
67992
68249
|
return {
|
|
67993
68250
|
content: [{ type: "text", text: successMessage }],
|
|
67994
68251
|
details: {}
|
|
@@ -68563,6 +68820,9 @@ ${failureFeedback}
|
|
|
68563
68820
|
await this.store.updateTask(task.id, { workflowStepResults: results });
|
|
68564
68821
|
continue;
|
|
68565
68822
|
}
|
|
68823
|
+
if (await this.shouldDeferWorkflowStepCompletion(task.id, `before workflow step '${ws.name}'`)) {
|
|
68824
|
+
return "deferred-paused";
|
|
68825
|
+
}
|
|
68566
68826
|
await this.store.logEntry(task.id, `[pre-merge] Starting workflow step: ${ws.name} (${stepMode} mode)`);
|
|
68567
68827
|
executorLog.log(`${task.id} \u2014 [pre-merge] running workflow step: ${ws.name} (${stepMode} mode)`);
|
|
68568
68828
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -68577,6 +68837,9 @@ ${failureFeedback}
|
|
|
68577
68837
|
await this.store.updateTask(task.id, { workflowStepResults: results });
|
|
68578
68838
|
try {
|
|
68579
68839
|
const result = stepMode === "script" ? await this.executeScriptWorkflowStep(task, ws, worktreePath, settings) : await this.executeWorkflowStep(task, ws, worktreePath, settings);
|
|
68840
|
+
if (await this.shouldDeferWorkflowStepCompletion(task.id, `workflow step '${ws.name}'`)) {
|
|
68841
|
+
return "deferred-paused";
|
|
68842
|
+
}
|
|
68580
68843
|
const completedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
68581
68844
|
if (result.success) {
|
|
68582
68845
|
await this.store.logEntry(task.id, `[timing] Workflow step '${ws.name}' completed in ${Date.now() - stepStartedAtMs}ms`);
|
|
@@ -68642,6 +68905,9 @@ ${failureFeedback}
|
|
|
68642
68905
|
};
|
|
68643
68906
|
}
|
|
68644
68907
|
} catch (err) {
|
|
68908
|
+
if (await this.shouldDeferWorkflowStepCompletion(task.id, `workflow step '${ws.name}'`)) {
|
|
68909
|
+
return "deferred-paused";
|
|
68910
|
+
}
|
|
68645
68911
|
const { message: errorMessage, detail: errorDetail, stack: errorStack } = formatError(err);
|
|
68646
68912
|
const completedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
68647
68913
|
await this.store.logEntry(
|
|
@@ -68807,6 +69073,7 @@ and show an appropriate message to the user.\`
|
|
|
68807
69073
|
task.id,
|
|
68808
69074
|
`Workflow step '${workflowStep.name}' using model: ${describeModel(session)}${useOverride && attemptLabel === "primary" ? " (workflow step override)" : ""}${attemptLabel === "fallback" ? " (fallback after timeout)" : ""}`
|
|
68809
69075
|
);
|
|
69076
|
+
this.activeWorkflowStepSessions.set(task.id, session);
|
|
68810
69077
|
let output = "";
|
|
68811
69078
|
session.subscribe((event) => {
|
|
68812
69079
|
if (event.type === "message_update") {
|
|
@@ -68879,6 +69146,10 @@ Review the work done in this worktree and evaluate it against the criteria in yo
|
|
|
68879
69146
|
return { success: false, error: errorMessage };
|
|
68880
69147
|
} finally {
|
|
68881
69148
|
if (timeoutHandle) clearTimeout(timeoutHandle);
|
|
69149
|
+
const activeWorkflowStepSession = this.activeWorkflowStepSessions.get(task.id);
|
|
69150
|
+
if (activeWorkflowStepSession === session) {
|
|
69151
|
+
this.activeWorkflowStepSessions.delete(task.id);
|
|
69152
|
+
}
|
|
68882
69153
|
void timedOut;
|
|
68883
69154
|
}
|
|
68884
69155
|
};
|
|
@@ -70622,6 +70893,15 @@ var init_scheduler = __esm({
|
|
|
70622
70893
|
schedulerLog.log(`Task ${task.id} is paused \u2014 skipping dispatch`);
|
|
70623
70894
|
continue;
|
|
70624
70895
|
}
|
|
70896
|
+
const latestSettings = await this.store.getSettings();
|
|
70897
|
+
if (latestSettings.globalPause) {
|
|
70898
|
+
schedulerLog.log(`Task ${task.id} dispatch aborted \u2014 globalPause became active mid-pass`);
|
|
70899
|
+
continue;
|
|
70900
|
+
}
|
|
70901
|
+
if (latestSettings.enginePaused) {
|
|
70902
|
+
schedulerLog.log(`Task ${task.id} dispatch aborted \u2014 enginePaused became active mid-pass`);
|
|
70903
|
+
continue;
|
|
70904
|
+
}
|
|
70625
70905
|
let effectiveNode = resolveEffectiveNode(freshTask, settings);
|
|
70626
70906
|
schedulerLog.log(`Task ${task.id} routed to node=${effectiveNode.nodeId ?? "local"} (source=${effectiveNode.source})`);
|
|
70627
70907
|
if (effectiveNode.nodeId !== void 0 && this.options.nodeHealthMonitor) {
|
|
@@ -72742,6 +73022,7 @@ Rules:
|
|
|
72742
73022
|
|
|
72743
73023
|
// ../engine/src/agent-heartbeat.ts
|
|
72744
73024
|
import { Type as Type6 } from "@mariozechner/pi-ai";
|
|
73025
|
+
import { createHash as createHash5 } from "node:crypto";
|
|
72745
73026
|
function isBlockedStateDuplicate(current, previous) {
|
|
72746
73027
|
return current.blockedBy === previous.blockedBy && current.contextHash === previous.contextHash;
|
|
72747
73028
|
}
|
|
@@ -72751,39 +73032,30 @@ function truncatePrompt(text, maxChars) {
|
|
|
72751
73032
|
|
|
72752
73033
|
... (truncated, ${text.length} chars)`;
|
|
72753
73034
|
}
|
|
73035
|
+
function shortContentHash(value) {
|
|
73036
|
+
return createHash5("sha256").update(value).digest("hex").slice(0, 8);
|
|
73037
|
+
}
|
|
72754
73038
|
function buildIdentitySnapshot(args) {
|
|
72755
73039
|
const { agent, resolvedInstructions } = args;
|
|
72756
|
-
const
|
|
72757
|
-
const
|
|
72758
|
-
const
|
|
72759
|
-
const
|
|
72760
|
-
|
|
72761
|
-
|
|
72762
|
-
|
|
73040
|
+
const soulTrimmed = typeof agent.soul === "string" ? agent.soul.trim() : "";
|
|
73041
|
+
const instrTrimmed = resolvedInstructions.trim();
|
|
73042
|
+
const memTrimmed = typeof agent.memory === "string" ? agent.memory.trim() : "";
|
|
73043
|
+
const formatField = (trimmed) => {
|
|
73044
|
+
if (!trimmed) return "absent";
|
|
73045
|
+
return `loaded (${trimmed.length} chars, sha256:${shortContentHash(trimmed)})`;
|
|
73046
|
+
};
|
|
73047
|
+
return [
|
|
72763
73048
|
"## Identity Snapshot",
|
|
72764
73049
|
"",
|
|
72765
|
-
"
|
|
73050
|
+
"Full content is in the Custom Instructions section of your system prompt. Surface anomalies in your first text output before acting.",
|
|
72766
73051
|
"",
|
|
72767
73052
|
`- agentId: ${agent.id}`,
|
|
72768
73053
|
`- name: ${agent.name}`,
|
|
72769
73054
|
`- role: ${agent.role}`,
|
|
72770
|
-
`- soul: ${
|
|
72771
|
-
`- instructions: ${
|
|
72772
|
-
`- memory: ${
|
|
72773
|
-
];
|
|
72774
|
-
if (soulPresent) {
|
|
72775
|
-
const preview = agent.soul.trim().slice(0, SOUL_PREVIEW);
|
|
72776
|
-
lines.push("", `### Soul (first ${SOUL_PREVIEW} chars)`, preview);
|
|
72777
|
-
}
|
|
72778
|
-
if (instrPresent) {
|
|
72779
|
-
const preview = resolvedInstructions.trim().slice(0, INSTR_PREVIEW);
|
|
72780
|
-
lines.push("", `### Instructions (first ${INSTR_PREVIEW} chars)`, preview);
|
|
72781
|
-
}
|
|
72782
|
-
if (memPresent) {
|
|
72783
|
-
const preview = agent.memory.trim().slice(0, MEM_PREVIEW);
|
|
72784
|
-
lines.push("", `### Memory (first ${MEM_PREVIEW} chars)`, preview);
|
|
72785
|
-
}
|
|
72786
|
-
return lines.join("\n");
|
|
73055
|
+
`- soul: ${formatField(soulTrimmed)}`,
|
|
73056
|
+
`- instructions: ${formatField(instrTrimmed)}`,
|
|
73057
|
+
`- memory: ${formatField(memTrimmed)}`
|
|
73058
|
+
].join("\n");
|
|
72787
73059
|
}
|
|
72788
73060
|
async function getHeartbeatMemorySettings(taskStore) {
|
|
72789
73061
|
const maybeGetSettings = taskStore.getSettings;
|
|
@@ -72965,9 +73237,8 @@ When sending messages:
|
|
|
72965
73237
|
1. **Identity & context** \u2014 review the **Identity Snapshot** at the top of
|
|
72966
73238
|
this prompt. Confirm your role, soul, instructions, and memory match what
|
|
72967
73239
|
you expect, and surface any anomalies in your first text output before
|
|
72968
|
-
doing anything else.
|
|
72969
|
-
|
|
72970
|
-
authoritative source.)
|
|
73240
|
+
doing anything else. The full content is in the Custom Instructions
|
|
73241
|
+
section of your system prompt.
|
|
72971
73242
|
2. **Inbox** \u2014 when fn_read_messages is available, call it. Process any pending
|
|
72972
73243
|
messages first; reply with reply_to_message_id when answering.
|
|
72973
73244
|
3. **Wake delta** \u2014 read the Wake Delta block above. The wake reason is the
|
|
@@ -72993,9 +73264,8 @@ a bug. Do not loop on the same plan across heartbeats without recording why.`;
|
|
|
72993
73264
|
1. **Identity & context** \u2014 review the **Identity Snapshot** at the top of
|
|
72994
73265
|
this prompt. Confirm your role, soul, instructions, and memory match what
|
|
72995
73266
|
you expect, and surface any anomalies in your first text output before
|
|
72996
|
-
doing anything else.
|
|
72997
|
-
|
|
72998
|
-
authoritative source.)
|
|
73267
|
+
doing anything else. The full content is in the Custom Instructions
|
|
73268
|
+
section of your system prompt.
|
|
72999
73269
|
2. **Inbox** \u2014 when fn_read_messages is available, call it. Process any pending
|
|
73000
73270
|
messages first; reply with reply_to_message_id when answering.
|
|
73001
73271
|
3. **Wake delta** \u2014 read the Wake Delta block above. The wake reason is the
|
|
@@ -73797,7 +74067,6 @@ not loop on the same plan across heartbeats without recording why.`;
|
|
|
73797
74067
|
baseHeartbeatSystemPrompt,
|
|
73798
74068
|
[resolvedInstructionsForIdentity, memoryInstructions].filter((part) => part.trim()).join("\n\n")
|
|
73799
74069
|
);
|
|
73800
|
-
heartbeatTools.push(createIdentityTool({ agent, resolvedInstructions: resolvedInstructionsForIdentity }));
|
|
73801
74070
|
heartbeatTools.push(heartbeatDoneTool);
|
|
73802
74071
|
if (isNoTaskRun) {
|
|
73803
74072
|
agentLogger = new AgentLogger({
|
|
@@ -75770,6 +76039,36 @@ function execCommand(command, options) {
|
|
|
75770
76039
|
});
|
|
75771
76040
|
});
|
|
75772
76041
|
}
|
|
76042
|
+
function isInProcessBackupCommand(command) {
|
|
76043
|
+
if (!command) return false;
|
|
76044
|
+
const trimmed = command.trim();
|
|
76045
|
+
if (!trimmed) return false;
|
|
76046
|
+
if (SHELL_METACHARACTERS_REGEX.test(trimmed)) return false;
|
|
76047
|
+
const tokens = trimmed.split(/\s+/).map((tok) => tok.toLowerCase());
|
|
76048
|
+
let cursor = 0;
|
|
76049
|
+
if (tokens[cursor] === "npx") {
|
|
76050
|
+
cursor += 1;
|
|
76051
|
+
while (cursor < tokens.length) {
|
|
76052
|
+
const tok = tokens[cursor];
|
|
76053
|
+
if (tok === void 0 || !tok.startsWith("-")) break;
|
|
76054
|
+
const takesValue = (tok === "-p" || tok === "--package") && cursor + 1 < tokens.length && tokens[cursor + 1] !== void 0 && !tokens[cursor + 1].startsWith("-");
|
|
76055
|
+
cursor += takesValue ? 2 : 1;
|
|
76056
|
+
}
|
|
76057
|
+
}
|
|
76058
|
+
const binary = tokens[cursor];
|
|
76059
|
+
if (!binary || !FUSION_BINARY_TOKENS.has(binary)) return false;
|
|
76060
|
+
cursor += 1;
|
|
76061
|
+
if (tokens[cursor] !== "backup") return false;
|
|
76062
|
+
cursor += 1;
|
|
76063
|
+
if (tokens[cursor] !== "--create") return false;
|
|
76064
|
+
cursor += 1;
|
|
76065
|
+
for (; cursor < tokens.length; cursor += 1) {
|
|
76066
|
+
const tok = tokens[cursor];
|
|
76067
|
+
if (!tok) continue;
|
|
76068
|
+
if (!tok.startsWith("-")) return false;
|
|
76069
|
+
}
|
|
76070
|
+
return true;
|
|
76071
|
+
}
|
|
75773
76072
|
async function createAiPromptExecutor(cwd) {
|
|
75774
76073
|
const disposeLog = createLogger2("cron-runner");
|
|
75775
76074
|
return async (prompt, modelProvider, modelId) => {
|
|
@@ -75809,7 +76108,7 @@ function truncateOutput(stdout, stderr) {
|
|
|
75809
76108
|
}
|
|
75810
76109
|
return combined;
|
|
75811
76110
|
}
|
|
75812
|
-
var log14, DEFAULT_TIMEOUT_MS6, MAX_BUFFER, MAX_OUTPUT_LENGTH, DEFAULT_POLL_INTERVAL_MS, MIN_POLL_INTERVAL_MS, CronRunner, AI_AUTOMATION_SYSTEM_PROMPT;
|
|
76111
|
+
var log14, FUSION_BINARY_TOKENS, SHELL_METACHARACTERS_REGEX, DEFAULT_TIMEOUT_MS6, MAX_BUFFER, MAX_OUTPUT_LENGTH, DEFAULT_POLL_INTERVAL_MS, MIN_POLL_INTERVAL_MS, CronRunner, AI_AUTOMATION_SYSTEM_PROMPT;
|
|
75813
76112
|
var init_cron_runner = __esm({
|
|
75814
76113
|
"../engine/src/cron-runner.ts"() {
|
|
75815
76114
|
"use strict";
|
|
@@ -75818,6 +76117,14 @@ var init_cron_runner = __esm({
|
|
|
75818
76117
|
init_shell_utils();
|
|
75819
76118
|
init_pi();
|
|
75820
76119
|
log14 = createLogger2("cron-runner");
|
|
76120
|
+
FUSION_BINARY_TOKENS = /* @__PURE__ */ new Set([
|
|
76121
|
+
"fn",
|
|
76122
|
+
"fusion",
|
|
76123
|
+
"runfusion",
|
|
76124
|
+
"runfusion.ai",
|
|
76125
|
+
"@runfusion/fusion"
|
|
76126
|
+
]);
|
|
76127
|
+
SHELL_METACHARACTERS_REGEX = /[&|;<>`$()]/;
|
|
75821
76128
|
DEFAULT_TIMEOUT_MS6 = 5 * 60 * 1e3;
|
|
75822
76129
|
MAX_BUFFER = 1024 * 1024;
|
|
75823
76130
|
MAX_OUTPUT_LENGTH = 10 * 1024;
|
|
@@ -75958,6 +76265,9 @@ var init_cron_runner = __esm({
|
|
|
75958
76265
|
*/
|
|
75959
76266
|
async executeLegacyCommand(schedule, startedAt) {
|
|
75960
76267
|
log14.log(`Executing ${schedule.name} (${schedule.id}): ${schedule.command}`);
|
|
76268
|
+
if (isInProcessBackupCommand(schedule.command)) {
|
|
76269
|
+
return this.executeBackupInProcess(schedule, startedAt);
|
|
76270
|
+
}
|
|
75961
76271
|
try {
|
|
75962
76272
|
const timeoutMs = schedule.timeoutMs ?? DEFAULT_TIMEOUT_MS6;
|
|
75963
76273
|
const { stdout, stderr } = await execCommand(schedule.command, {
|
|
@@ -75988,6 +76298,47 @@ var init_cron_runner = __esm({
|
|
|
75988
76298
|
};
|
|
75989
76299
|
}
|
|
75990
76300
|
}
|
|
76301
|
+
/**
|
|
76302
|
+
* Run an auto-backup schedule in-process via the engine's open TaskStore,
|
|
76303
|
+
* bypassing the shell-out that would otherwise invoke an outdated fusion
|
|
76304
|
+
* binary on PATH. See `isInProcessBackupCommand` for the matching contract.
|
|
76305
|
+
*/
|
|
76306
|
+
async executeBackupInProcess(schedule, startedAt) {
|
|
76307
|
+
const action = await this.runBackupActionInProcess();
|
|
76308
|
+
if (action.success) {
|
|
76309
|
+
log14.log(`\u2713 ${schedule.name} completed in-process`);
|
|
76310
|
+
} else {
|
|
76311
|
+
log14.warn(`\u2717 ${schedule.name} in-process backup ${action.error ? `threw: ${action.error}` : `reported failure: ${action.output}`}`);
|
|
76312
|
+
}
|
|
76313
|
+
return {
|
|
76314
|
+
success: action.success,
|
|
76315
|
+
output: action.output,
|
|
76316
|
+
error: action.error,
|
|
76317
|
+
startedAt,
|
|
76318
|
+
completedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
76319
|
+
};
|
|
76320
|
+
}
|
|
76321
|
+
/**
|
|
76322
|
+
* Shared in-process backup execution used by both the legacy-command path
|
|
76323
|
+
* and the command-step path. Returns the success/output/error tuple in
|
|
76324
|
+
* a shape that callers can wrap into either a run or a step result.
|
|
76325
|
+
*/
|
|
76326
|
+
async runBackupActionInProcess() {
|
|
76327
|
+
try {
|
|
76328
|
+
const { runBackupCommand: runBackupCommand2 } = await Promise.resolve().then(() => (init_src(), src_exports));
|
|
76329
|
+
const fusionDir = this.store.getFusionDir();
|
|
76330
|
+
const settings = await this.store.getSettings();
|
|
76331
|
+
const result = await runBackupCommand2(fusionDir, settings);
|
|
76332
|
+
return {
|
|
76333
|
+
success: result.success,
|
|
76334
|
+
output: truncateOutput(result.output ?? "", ""),
|
|
76335
|
+
error: result.success ? void 0 : result.output
|
|
76336
|
+
};
|
|
76337
|
+
} catch (err) {
|
|
76338
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
76339
|
+
return { success: false, output: "", error: message };
|
|
76340
|
+
}
|
|
76341
|
+
}
|
|
75991
76342
|
/**
|
|
75992
76343
|
* Execute multiple steps sequentially.
|
|
75993
76344
|
* Aggregates per-step results into an overall AutomationRunResult.
|
|
@@ -76076,6 +76427,19 @@ var init_cron_runner = __esm({
|
|
|
76076
76427
|
completedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
76077
76428
|
};
|
|
76078
76429
|
}
|
|
76430
|
+
if (isInProcessBackupCommand(step.command)) {
|
|
76431
|
+
const action = await this.runBackupActionInProcess();
|
|
76432
|
+
return {
|
|
76433
|
+
stepId: step.id,
|
|
76434
|
+
stepName: step.name,
|
|
76435
|
+
stepIndex,
|
|
76436
|
+
success: action.success,
|
|
76437
|
+
output: action.output,
|
|
76438
|
+
error: action.error,
|
|
76439
|
+
startedAt,
|
|
76440
|
+
completedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
76441
|
+
};
|
|
76442
|
+
}
|
|
76079
76443
|
try {
|
|
76080
76444
|
const { stdout, stderr } = await execCommand(step.command, {
|
|
76081
76445
|
timeout: timeoutMs,
|
|
@@ -76267,6 +76631,7 @@ var init_routine_runner = __esm({
|
|
|
76267
76631
|
"../engine/src/routine-runner.ts"() {
|
|
76268
76632
|
"use strict";
|
|
76269
76633
|
import_cron_parser4 = __toESM(require_dist2(), 1);
|
|
76634
|
+
init_cron_runner();
|
|
76270
76635
|
init_logger2();
|
|
76271
76636
|
init_shell_utils();
|
|
76272
76637
|
log15 = createLogger2("routine-runner");
|
|
@@ -76424,6 +76789,30 @@ var init_routine_runner = __esm({
|
|
|
76424
76789
|
return this.executeCommand(routine.command ?? "", routine.timeoutMs, startedAt);
|
|
76425
76790
|
}
|
|
76426
76791
|
async executeCommand(command, timeoutMs, startedAt) {
|
|
76792
|
+
if (isInProcessBackupCommand(command) && this.options.taskStore) {
|
|
76793
|
+
try {
|
|
76794
|
+
const { runBackupCommand: runBackupCommand2 } = await Promise.resolve().then(() => (init_src(), src_exports));
|
|
76795
|
+
const fusionDir = this.options.taskStore.getFusionDir();
|
|
76796
|
+
const settings = await this.options.taskStore.getSettings();
|
|
76797
|
+
const result = await runBackupCommand2(fusionDir, settings);
|
|
76798
|
+
return {
|
|
76799
|
+
success: result.success,
|
|
76800
|
+
output: truncateOutput2(result.output ?? "", ""),
|
|
76801
|
+
error: result.success ? void 0 : result.output,
|
|
76802
|
+
startedAt,
|
|
76803
|
+
completedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
76804
|
+
};
|
|
76805
|
+
} catch (err) {
|
|
76806
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
76807
|
+
return {
|
|
76808
|
+
success: false,
|
|
76809
|
+
output: "",
|
|
76810
|
+
error: message,
|
|
76811
|
+
startedAt,
|
|
76812
|
+
completedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
76813
|
+
};
|
|
76814
|
+
}
|
|
76815
|
+
}
|
|
76427
76816
|
try {
|
|
76428
76817
|
const { stdout, stderr } = await execAsync6(command, {
|
|
76429
76818
|
timeout: timeoutMs ?? DEFAULT_TIMEOUT_MS7,
|
|
@@ -77215,6 +77604,13 @@ var init_self_healing = __esm({
|
|
|
77215
77604
|
* stale in-progress/planning tasks that no longer have a live worker.
|
|
77216
77605
|
*/
|
|
77217
77606
|
async runStartupRecovery() {
|
|
77607
|
+
const settings = await this.store.getSettings();
|
|
77608
|
+
if (settings.globalPause || settings.enginePaused) {
|
|
77609
|
+
log16.log(
|
|
77610
|
+
`Startup recovery skipped \u2014 ${settings.globalPause ? "global pause" : "engine pause"} is active`
|
|
77611
|
+
);
|
|
77612
|
+
return;
|
|
77613
|
+
}
|
|
77218
77614
|
const steps = [
|
|
77219
77615
|
{ name: "no-progress-no-task-done", fn: () => this.recoverNoProgressNoTaskDoneFailures().then(() => void 0) },
|
|
77220
77616
|
{ name: "completed-tasks", fn: () => this.recoverCompletedTasks().then(() => void 0) },
|
|
@@ -77565,27 +77961,34 @@ var init_self_healing = __esm({
|
|
|
77565
77961
|
log16.error(`Maintenance batch 1 step "${fn.name}" failed: ${stepErr instanceof Error ? stepErr.message : String(stepErr)}`);
|
|
77566
77962
|
}
|
|
77567
77963
|
}
|
|
77568
|
-
const
|
|
77569
|
-
|
|
77570
|
-
|
|
77571
|
-
|
|
77572
|
-
|
|
77573
|
-
|
|
77574
|
-
|
|
77575
|
-
|
|
77576
|
-
|
|
77577
|
-
|
|
77578
|
-
|
|
77579
|
-
|
|
77580
|
-
|
|
77581
|
-
|
|
77582
|
-
|
|
77583
|
-
|
|
77584
|
-
|
|
77585
|
-
|
|
77586
|
-
|
|
77587
|
-
|
|
77588
|
-
|
|
77964
|
+
const recoverySettings = await this.store.getSettings();
|
|
77965
|
+
if (recoverySettings.globalPause || recoverySettings.enginePaused) {
|
|
77966
|
+
log16.log(
|
|
77967
|
+
`Maintenance batch 2 skipped \u2014 ${recoverySettings.globalPause ? "global pause" : "engine pause"} is active`
|
|
77968
|
+
);
|
|
77969
|
+
} else {
|
|
77970
|
+
const batch2Fns = [
|
|
77971
|
+
{ name: "recover-completed-tasks", fn: () => this.recoverCompletedTasks() },
|
|
77972
|
+
{ name: "recover-stale-incomplete-review", fn: () => this.recoverStaleIncompleteReviewTasks() },
|
|
77973
|
+
{ name: "recover-failed-pre-merge-steps", fn: () => this.recoverReviewTasksWithFailedPreMergeSteps() },
|
|
77974
|
+
{ name: "recover-interrupted-merging", fn: () => this.recoverInterruptedMergingTasks() },
|
|
77975
|
+
{ name: "recover-mergeable-review", fn: () => this.recoverMergeableReviewTasks() },
|
|
77976
|
+
{ name: "recover-merged-review", fn: () => this.recoverMergedReviewTasks() },
|
|
77977
|
+
{ name: "recover-misclassified-failures", fn: () => this.recoverMisclassifiedFailures() },
|
|
77978
|
+
{ name: "recover-no-progress-no-task-done", fn: () => this.recoverNoProgressNoTaskDoneFailures() },
|
|
77979
|
+
{ name: "recover-partial-progress-no-task-done", fn: () => this.recoverPartialProgressNoTaskDoneFailures() },
|
|
77980
|
+
{ name: "recover-orphaned-executions", fn: () => this.recoverOrphanedExecutions() },
|
|
77981
|
+
{ name: "recover-approved-triage", fn: () => this.recoverApprovedTriageTasks() },
|
|
77982
|
+
{ name: "recover-orphaned-planning", fn: () => this.recoverOrphanedPlanningTasks() },
|
|
77983
|
+
{ name: "recover-ghost-review", fn: () => this.recoverGhostReviewTasks() }
|
|
77984
|
+
];
|
|
77985
|
+
for (const fn of batch2Fns) {
|
|
77986
|
+
try {
|
|
77987
|
+
await fn.fn();
|
|
77988
|
+
log16.log(`Maintenance batch 2 step "${fn.name}" succeeded`);
|
|
77989
|
+
} catch (stepErr) {
|
|
77990
|
+
log16.error(`Maintenance batch 2 step "${fn.name}" failed: ${stepErr instanceof Error ? stepErr.message : String(stepErr)}`);
|
|
77991
|
+
}
|
|
77589
77992
|
}
|
|
77590
77993
|
}
|
|
77591
77994
|
const batch3Fns = [
|
|
@@ -78651,10 +79054,18 @@ var init_plugin_runner = __esm({
|
|
|
78651
79054
|
cachedRoutes = null;
|
|
78652
79055
|
cachedUiSlots = null;
|
|
78653
79056
|
cachedRuntimes = null;
|
|
79057
|
+
cachedSkills = null;
|
|
79058
|
+
cachedWorkflowSteps = null;
|
|
79059
|
+
cachedPromptContributions = null;
|
|
79060
|
+
cachedSetupInfo = null;
|
|
78654
79061
|
toolsCacheVersion = 0;
|
|
78655
79062
|
routesCacheVersion = 0;
|
|
78656
79063
|
uiSlotsCacheVersion = 0;
|
|
78657
79064
|
runtimesCacheVersion = 0;
|
|
79065
|
+
skillsCacheVersion = 0;
|
|
79066
|
+
workflowStepsCacheVersion = 0;
|
|
79067
|
+
promptContributionsCacheVersion = 0;
|
|
79068
|
+
setupCacheVersion = 0;
|
|
78658
79069
|
hookTimeoutMs;
|
|
78659
79070
|
// Event handler references for cleanup
|
|
78660
79071
|
handlePluginEnabled;
|
|
@@ -78686,6 +79097,10 @@ var init_plugin_runner = __esm({
|
|
|
78686
79097
|
this.invalidateRoutesCache();
|
|
78687
79098
|
this.invalidateUiSlotsCache();
|
|
78688
79099
|
this.invalidateRuntimesCache();
|
|
79100
|
+
this.invalidateSkillsCache();
|
|
79101
|
+
this.invalidateWorkflowStepsCache();
|
|
79102
|
+
this.invalidatePromptContributionsCache();
|
|
79103
|
+
this.invalidateSetupCache();
|
|
78689
79104
|
}
|
|
78690
79105
|
/**
|
|
78691
79106
|
* Shutdown the plugin runner.
|
|
@@ -78766,6 +79181,54 @@ var init_plugin_runner = __esm({
|
|
|
78766
79181
|
}
|
|
78767
79182
|
return this.cachedRuntimes.runtimes;
|
|
78768
79183
|
}
|
|
79184
|
+
getPluginSkills() {
|
|
79185
|
+
if (!this.cachedSkills || this.cachedSkills.version !== this.skillsCacheVersion) {
|
|
79186
|
+
this.cachedSkills = {
|
|
79187
|
+
skills: this.options.pluginLoader.getPluginSkills(),
|
|
79188
|
+
version: this.skillsCacheVersion
|
|
79189
|
+
};
|
|
79190
|
+
}
|
|
79191
|
+
return this.cachedSkills.skills;
|
|
79192
|
+
}
|
|
79193
|
+
getPluginWorkflowSteps() {
|
|
79194
|
+
if (!this.cachedWorkflowSteps || this.cachedWorkflowSteps.version !== this.workflowStepsCacheVersion) {
|
|
79195
|
+
this.cachedWorkflowSteps = {
|
|
79196
|
+
steps: this.options.pluginLoader.getPluginWorkflowSteps(),
|
|
79197
|
+
version: this.workflowStepsCacheVersion
|
|
79198
|
+
};
|
|
79199
|
+
}
|
|
79200
|
+
return this.cachedWorkflowSteps.steps;
|
|
79201
|
+
}
|
|
79202
|
+
getPluginPromptContributions() {
|
|
79203
|
+
if (!this.cachedPromptContributions || this.cachedPromptContributions.version !== this.promptContributionsCacheVersion) {
|
|
79204
|
+
this.cachedPromptContributions = {
|
|
79205
|
+
contributions: this.options.pluginLoader.getPluginPromptContributions(),
|
|
79206
|
+
version: this.promptContributionsCacheVersion
|
|
79207
|
+
};
|
|
79208
|
+
}
|
|
79209
|
+
return this.cachedPromptContributions.contributions;
|
|
79210
|
+
}
|
|
79211
|
+
getPluginSetupInfo() {
|
|
79212
|
+
if (!this.cachedSetupInfo || this.cachedSetupInfo.version !== this.setupCacheVersion) {
|
|
79213
|
+
this.cachedSetupInfo = {
|
|
79214
|
+
setups: this.options.pluginLoader.getPluginSetupInfo(),
|
|
79215
|
+
version: this.setupCacheVersion
|
|
79216
|
+
};
|
|
79217
|
+
}
|
|
79218
|
+
return this.cachedSetupInfo.setups;
|
|
79219
|
+
}
|
|
79220
|
+
getPromptContributionsForSurface(surface) {
|
|
79221
|
+
return this.getPluginPromptContributions().filter(({ pluginId, contribution, config }) => {
|
|
79222
|
+
const plugin4 = this.options.pluginLoader.getPlugin(pluginId);
|
|
79223
|
+
if (!plugin4 || plugin4.state !== "started") {
|
|
79224
|
+
return false;
|
|
79225
|
+
}
|
|
79226
|
+
if (contribution.surface !== surface) {
|
|
79227
|
+
return false;
|
|
79228
|
+
}
|
|
79229
|
+
return config.enabledByDefault !== false;
|
|
79230
|
+
});
|
|
79231
|
+
}
|
|
78769
79232
|
/**
|
|
78770
79233
|
* Get a specific runtime registration by its runtimeId.
|
|
78771
79234
|
*
|
|
@@ -78799,6 +79262,10 @@ var init_plugin_runner = __esm({
|
|
|
78799
79262
|
this.invalidateRoutesCache();
|
|
78800
79263
|
this.invalidateUiSlotsCache();
|
|
78801
79264
|
this.invalidateRuntimesCache();
|
|
79265
|
+
this.invalidateSkillsCache();
|
|
79266
|
+
this.invalidateWorkflowStepsCache();
|
|
79267
|
+
this.invalidatePromptContributionsCache();
|
|
79268
|
+
this.invalidateSetupCache();
|
|
78802
79269
|
executorLog.log(`Plugin ${pluginId} reloaded`);
|
|
78803
79270
|
}
|
|
78804
79271
|
// ── Event Handlers for Hot-Load/Unload ─────────────────────────
|
|
@@ -78810,6 +79277,10 @@ var init_plugin_runner = __esm({
|
|
|
78810
79277
|
this.invalidateRoutesCache();
|
|
78811
79278
|
this.invalidateUiSlotsCache();
|
|
78812
79279
|
this.invalidateRuntimesCache();
|
|
79280
|
+
this.invalidateSkillsCache();
|
|
79281
|
+
this.invalidateWorkflowStepsCache();
|
|
79282
|
+
this.invalidatePromptContributionsCache();
|
|
79283
|
+
this.invalidateSetupCache();
|
|
78813
79284
|
try {
|
|
78814
79285
|
executorLog.log(`Auto-loading enabled plugin: ${plugin4.id}`);
|
|
78815
79286
|
await this.options.pluginLoader.loadPlugin(plugin4.id);
|
|
@@ -78825,6 +79296,10 @@ var init_plugin_runner = __esm({
|
|
|
78825
79296
|
this.invalidateRoutesCache();
|
|
78826
79297
|
this.invalidateUiSlotsCache();
|
|
78827
79298
|
this.invalidateRuntimesCache();
|
|
79299
|
+
this.invalidateSkillsCache();
|
|
79300
|
+
this.invalidateWorkflowStepsCache();
|
|
79301
|
+
this.invalidatePromptContributionsCache();
|
|
79302
|
+
this.invalidateSetupCache();
|
|
78828
79303
|
try {
|
|
78829
79304
|
executorLog.log(`Auto-stopping disabled plugin: ${plugin4.id}`);
|
|
78830
79305
|
await this.options.pluginLoader.stopPlugin(plugin4.id);
|
|
@@ -78840,6 +79315,10 @@ var init_plugin_runner = __esm({
|
|
|
78840
79315
|
this.invalidateRoutesCache();
|
|
78841
79316
|
this.invalidateUiSlotsCache();
|
|
78842
79317
|
this.invalidateRuntimesCache();
|
|
79318
|
+
this.invalidateSkillsCache();
|
|
79319
|
+
this.invalidateWorkflowStepsCache();
|
|
79320
|
+
this.invalidatePromptContributionsCache();
|
|
79321
|
+
this.invalidateSetupCache();
|
|
78843
79322
|
try {
|
|
78844
79323
|
executorLog.log(`Stopping unregistered plugin: ${plugin4.id}`);
|
|
78845
79324
|
await this.options.pluginLoader.stopPlugin(plugin4.id);
|
|
@@ -78856,6 +79335,10 @@ var init_plugin_runner = __esm({
|
|
|
78856
79335
|
this.invalidateRoutesCache();
|
|
78857
79336
|
this.invalidateUiSlotsCache();
|
|
78858
79337
|
this.invalidateRuntimesCache();
|
|
79338
|
+
this.invalidateSkillsCache();
|
|
79339
|
+
this.invalidateWorkflowStepsCache();
|
|
79340
|
+
this.invalidatePromptContributionsCache();
|
|
79341
|
+
this.invalidateSetupCache();
|
|
78859
79342
|
}
|
|
78860
79343
|
/**
|
|
78861
79344
|
* Handle plugin updates - invalidate caches.
|
|
@@ -78865,6 +79348,10 @@ var init_plugin_runner = __esm({
|
|
|
78865
79348
|
this.invalidateRoutesCache();
|
|
78866
79349
|
this.invalidateUiSlotsCache();
|
|
78867
79350
|
this.invalidateRuntimesCache();
|
|
79351
|
+
this.invalidateSkillsCache();
|
|
79352
|
+
this.invalidateWorkflowStepsCache();
|
|
79353
|
+
this.invalidatePromptContributionsCache();
|
|
79354
|
+
this.invalidateSetupCache();
|
|
78868
79355
|
}
|
|
78869
79356
|
/**
|
|
78870
79357
|
* Handle plugin:loaded event from loader - invalidate caches.
|
|
@@ -78874,6 +79361,10 @@ var init_plugin_runner = __esm({
|
|
|
78874
79361
|
this.invalidateRoutesCache();
|
|
78875
79362
|
this.invalidateUiSlotsCache();
|
|
78876
79363
|
this.invalidateRuntimesCache();
|
|
79364
|
+
this.invalidateSkillsCache();
|
|
79365
|
+
this.invalidateWorkflowStepsCache();
|
|
79366
|
+
this.invalidatePromptContributionsCache();
|
|
79367
|
+
this.invalidateSetupCache();
|
|
78877
79368
|
}
|
|
78878
79369
|
/**
|
|
78879
79370
|
* Handle plugin:unloaded event from loader - invalidate caches.
|
|
@@ -78883,6 +79374,10 @@ var init_plugin_runner = __esm({
|
|
|
78883
79374
|
this.invalidateRoutesCache();
|
|
78884
79375
|
this.invalidateUiSlotsCache();
|
|
78885
79376
|
this.invalidateRuntimesCache();
|
|
79377
|
+
this.invalidateSkillsCache();
|
|
79378
|
+
this.invalidateWorkflowStepsCache();
|
|
79379
|
+
this.invalidatePromptContributionsCache();
|
|
79380
|
+
this.invalidateSetupCache();
|
|
78886
79381
|
}
|
|
78887
79382
|
/**
|
|
78888
79383
|
* Handle plugin:reloaded event from loader - invalidate caches.
|
|
@@ -78892,6 +79387,10 @@ var init_plugin_runner = __esm({
|
|
|
78892
79387
|
this.invalidateRoutesCache();
|
|
78893
79388
|
this.invalidateUiSlotsCache();
|
|
78894
79389
|
this.invalidateRuntimesCache();
|
|
79390
|
+
this.invalidateSkillsCache();
|
|
79391
|
+
this.invalidateWorkflowStepsCache();
|
|
79392
|
+
this.invalidatePromptContributionsCache();
|
|
79393
|
+
this.invalidateSetupCache();
|
|
78895
79394
|
}
|
|
78896
79395
|
// ── Tool Conversion ───────────────────────────────────────────────
|
|
78897
79396
|
/**
|
|
@@ -79063,6 +79562,22 @@ var init_plugin_runner = __esm({
|
|
|
79063
79562
|
this.runtimesCacheVersion++;
|
|
79064
79563
|
this.log.log(`Runtimes cache invalidated (version: ${this.runtimesCacheVersion})`);
|
|
79065
79564
|
}
|
|
79565
|
+
invalidateSkillsCache() {
|
|
79566
|
+
this.skillsCacheVersion++;
|
|
79567
|
+
this.log.log(`Skills cache invalidated (version: ${this.skillsCacheVersion})`);
|
|
79568
|
+
}
|
|
79569
|
+
invalidateWorkflowStepsCache() {
|
|
79570
|
+
this.workflowStepsCacheVersion++;
|
|
79571
|
+
this.log.log(`Workflow steps cache invalidated (version: ${this.workflowStepsCacheVersion})`);
|
|
79572
|
+
}
|
|
79573
|
+
invalidatePromptContributionsCache() {
|
|
79574
|
+
this.promptContributionsCacheVersion++;
|
|
79575
|
+
this.log.log(`Prompt contributions cache invalidated (version: ${this.promptContributionsCacheVersion})`);
|
|
79576
|
+
}
|
|
79577
|
+
invalidateSetupCache() {
|
|
79578
|
+
this.setupCacheVersion++;
|
|
79579
|
+
this.log.log(`Setup cache invalidated (version: ${this.setupCacheVersion})`);
|
|
79580
|
+
}
|
|
79066
79581
|
// ── Store Event Subscriptions ────────────────────────────────────
|
|
79067
79582
|
/**
|
|
79068
79583
|
* Subscribe to TaskStore events for task lifecycle hooks.
|
|
@@ -79206,6 +79721,10 @@ var init_in_process_runtime = __esm({
|
|
|
79206
79721
|
* before `start()` via `setMergeEnqueuer`.
|
|
79207
79722
|
*/
|
|
79208
79723
|
mergeEnqueuer;
|
|
79724
|
+
/** Tracks whether startup recovery was intentionally deferred due to pause state. */
|
|
79725
|
+
startupRecoveryDeferred = false;
|
|
79726
|
+
/** Prevent duplicate unpause recovery dispatches from racing each other. */
|
|
79727
|
+
resumeAfterUnpauseRunning = false;
|
|
79209
79728
|
/**
|
|
79210
79729
|
* Start the runtime and initialize all subsystems.
|
|
79211
79730
|
*
|
|
@@ -79239,7 +79758,7 @@ var init_in_process_runtime = __esm({
|
|
|
79239
79758
|
runtimeLog.log(`TaskStore initialized for project ${this.config.projectId}`);
|
|
79240
79759
|
}
|
|
79241
79760
|
this.messageStore = new MessageStoreClass(this.taskStore.getDatabase());
|
|
79242
|
-
this.pluginStore = new PluginStoreClass(this.
|
|
79761
|
+
this.pluginStore = new PluginStoreClass(this.config.workingDirectory);
|
|
79243
79762
|
await this.pluginStore.init();
|
|
79244
79763
|
this.pluginLoader = new PluginLoaderClass({
|
|
79245
79764
|
pluginStore: this.pluginStore,
|
|
@@ -79631,11 +80150,16 @@ var init_in_process_runtime = __esm({
|
|
|
79631
80150
|
this.selfHealingManager.start();
|
|
79632
80151
|
this.stuckTaskDetector.start();
|
|
79633
80152
|
this.setupEventForwarding();
|
|
79634
|
-
await this.
|
|
79635
|
-
|
|
79636
|
-
|
|
79637
|
-
runtimeLog.
|
|
79638
|
-
|
|
80153
|
+
const startupSettings = await this.taskStore.getSettings();
|
|
80154
|
+
if (startupSettings.globalPause || startupSettings.enginePaused) {
|
|
80155
|
+
this.startupRecoveryDeferred = true;
|
|
80156
|
+
runtimeLog.log(
|
|
80157
|
+
`Startup recovery deferred \u2014 ${startupSettings.globalPause ? "global pause" : "engine pause"} is active`
|
|
80158
|
+
);
|
|
80159
|
+
} else {
|
|
80160
|
+
this.startupRecoveryDeferred = false;
|
|
80161
|
+
await this.resumeStartupRecoverySequence();
|
|
80162
|
+
}
|
|
79639
80163
|
this.scheduler.start();
|
|
79640
80164
|
this.triageProcessor?.start();
|
|
79641
80165
|
this.missionExecutionLoop = missionExecutionLoop;
|
|
@@ -79801,6 +80325,45 @@ var init_in_process_runtime = __esm({
|
|
|
79801
80325
|
setMergeEnqueuer(enqueueMerge) {
|
|
79802
80326
|
this.mergeEnqueuer = enqueueMerge;
|
|
79803
80327
|
}
|
|
80328
|
+
/**
|
|
80329
|
+
* Resume executor/self-healing activity after an unpause transition.
|
|
80330
|
+
*
|
|
80331
|
+
* When startup recovery had been deferred, this replays the original startup
|
|
80332
|
+
* ordering so orphan resume and self-healing cannot race each other.
|
|
80333
|
+
*/
|
|
80334
|
+
async resumeAfterUnpause() {
|
|
80335
|
+
if (!this.taskStore || !this.executor || !this.selfHealingManager) {
|
|
80336
|
+
return;
|
|
80337
|
+
}
|
|
80338
|
+
if (this.resumeAfterUnpauseRunning) {
|
|
80339
|
+
return;
|
|
80340
|
+
}
|
|
80341
|
+
this.resumeAfterUnpauseRunning = true;
|
|
80342
|
+
try {
|
|
80343
|
+
const settings = await this.taskStore.getSettings();
|
|
80344
|
+
if (settings.globalPause || settings.enginePaused) {
|
|
80345
|
+
runtimeLog.log(
|
|
80346
|
+
`Unpause recovery still blocked \u2014 ${settings.globalPause ? "global pause" : "engine pause"} remains active`
|
|
80347
|
+
);
|
|
80348
|
+
return;
|
|
80349
|
+
}
|
|
80350
|
+
if (this.startupRecoveryDeferred) {
|
|
80351
|
+
await this.resumeStartupRecoverySequence();
|
|
80352
|
+
this.startupRecoveryDeferred = false;
|
|
80353
|
+
return;
|
|
80354
|
+
}
|
|
80355
|
+
await this.executor.resumeOrphaned();
|
|
80356
|
+
} finally {
|
|
80357
|
+
this.resumeAfterUnpauseRunning = false;
|
|
80358
|
+
}
|
|
80359
|
+
}
|
|
80360
|
+
async resumeStartupRecoverySequence() {
|
|
80361
|
+
await this.selfHealingManager.recoverNoProgressNoTaskDoneFailures();
|
|
80362
|
+
await this.executor.resumeOrphaned();
|
|
80363
|
+
void this.selfHealingManager.runStartupRecovery().catch((err) => {
|
|
80364
|
+
runtimeLog.error("Self-healing startup recovery failed:", err);
|
|
80365
|
+
});
|
|
80366
|
+
}
|
|
79804
80367
|
/**
|
|
79805
80368
|
* Get the project's TaskStore instance.
|
|
79806
80369
|
* @throws Error if runtime has not been started
|
|
@@ -83640,13 +84203,13 @@ ${detail}`
|
|
|
83640
84203
|
if (prev.globalPause && !s.globalPause) {
|
|
83641
84204
|
runtimeLog.log("Global unpause \u2014 resuming agentic activity");
|
|
83642
84205
|
try {
|
|
83643
|
-
const
|
|
83644
|
-
|
|
83645
|
-
(err) => runtimeLog.error("Failed to resume
|
|
84206
|
+
const runtime = this.runtime;
|
|
84207
|
+
runtime.resumeAfterUnpause?.().catch(
|
|
84208
|
+
(err) => runtimeLog.error("Failed to resume agentic activity on unpause:", err)
|
|
83646
84209
|
);
|
|
83647
84210
|
} catch (err) {
|
|
83648
84211
|
runtimeLog.warn(
|
|
83649
|
-
`Global unpause: failed to dispatch
|
|
84212
|
+
`Global unpause: failed to dispatch resumeAfterUnpause: ${err instanceof Error ? err.message : String(err)}`
|
|
83650
84213
|
);
|
|
83651
84214
|
}
|
|
83652
84215
|
if (s.autoMerge) {
|
|
@@ -83670,13 +84233,13 @@ ${detail}`
|
|
|
83670
84233
|
if (prev.enginePaused && !s.enginePaused) {
|
|
83671
84234
|
runtimeLog.log("Engine unpaused \u2014 resuming agentic activity");
|
|
83672
84235
|
try {
|
|
83673
|
-
const
|
|
83674
|
-
|
|
83675
|
-
(err) => runtimeLog.error("Failed to resume
|
|
84236
|
+
const runtime = this.runtime;
|
|
84237
|
+
runtime.resumeAfterUnpause?.().catch(
|
|
84238
|
+
(err) => runtimeLog.error("Failed to resume agentic activity on engine unpause:", err)
|
|
83676
84239
|
);
|
|
83677
84240
|
} catch (err) {
|
|
83678
84241
|
runtimeLog.warn(
|
|
83679
|
-
`Engine unpause: failed to dispatch
|
|
84242
|
+
`Engine unpause: failed to dispatch resumeAfterUnpause: ${err instanceof Error ? err.message : String(err)}`
|
|
83680
84243
|
);
|
|
83681
84244
|
}
|
|
83682
84245
|
if (s.autoMerge) {
|
|
@@ -133416,8 +133979,8 @@ var init_register_settings_sync_routes = __esm({
|
|
|
133416
133979
|
exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
133417
133980
|
version: 1
|
|
133418
133981
|
};
|
|
133419
|
-
const { createHash:
|
|
133420
|
-
const checksum =
|
|
133982
|
+
const { createHash: createHash6 } = await import("node:crypto");
|
|
133983
|
+
const checksum = createHash6("sha256").update(JSON.stringify(payload)).digest("hex");
|
|
133421
133984
|
await fetchFromRemoteNode(node, "/api/settings/sync-receive", {
|
|
133422
133985
|
method: "POST",
|
|
133423
133986
|
body: { ...payload, checksum }
|
|
@@ -133476,7 +134039,7 @@ var init_register_settings_sync_routes = __esm({
|
|
|
133476
134039
|
});
|
|
133477
134040
|
return;
|
|
133478
134041
|
}
|
|
133479
|
-
const { createHash:
|
|
134042
|
+
const { createHash: createHash6 } = await import("node:crypto");
|
|
133480
134043
|
const exportedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
133481
134044
|
const payloadWithoutChecksum = {
|
|
133482
134045
|
global: remoteSettings.global,
|
|
@@ -133484,7 +134047,7 @@ var init_register_settings_sync_routes = __esm({
|
|
|
133484
134047
|
exportedAt,
|
|
133485
134048
|
version: 1
|
|
133486
134049
|
};
|
|
133487
|
-
const checksum =
|
|
134050
|
+
const checksum = createHash6("sha256").update(JSON.stringify(payloadWithoutChecksum)).digest("hex");
|
|
133488
134051
|
const result = await central.applyRemoteSettings({
|
|
133489
134052
|
...payloadWithoutChecksum,
|
|
133490
134053
|
checksum
|
|
@@ -136574,7 +137137,7 @@ Rules:
|
|
|
136574
137137
|
// ../dashboard/src/routes/register-agent-import-export-generation-routes.ts
|
|
136575
137138
|
import { createWriteStream } from "node:fs";
|
|
136576
137139
|
import * as fsPromises2 from "node:fs/promises";
|
|
136577
|
-
import { tmpdir as
|
|
137140
|
+
import { tmpdir as tmpdir4 } from "node:os";
|
|
136578
137141
|
import { join as join45, resolve as resolve22 } from "node:path";
|
|
136579
137142
|
import { Readable } from "node:stream";
|
|
136580
137143
|
import { pipeline as streamPipeline } from "node:stream/promises";
|
|
@@ -136616,7 +137179,7 @@ function registerAgentImportExportRoutes(ctx) {
|
|
|
136616
137179
|
} else if (typeof outputDir === "string") {
|
|
136617
137180
|
throw badRequest("outputDir cannot be empty");
|
|
136618
137181
|
} else {
|
|
136619
|
-
resolvedOutputDir = await mkdtemp(join45(
|
|
137182
|
+
resolvedOutputDir = await mkdtemp(join45(tmpdir4(), "fusion-agent-export-"));
|
|
136620
137183
|
}
|
|
136621
137184
|
const result = await exportAgentsToDirectory2(agentsToExport, resolvedOutputDir, {
|
|
136622
137185
|
companyName: typeof companyName === "string" ? companyName : void 0,
|
|
@@ -136931,7 +137494,7 @@ ${body}`;
|
|
|
136931
137494
|
const archiveUrl = `https://github.com/${repoOwner}/${repoName}/archive/refs/heads/main.tar.gz`;
|
|
136932
137495
|
let tempDir = null;
|
|
136933
137496
|
try {
|
|
136934
|
-
tempDir = await mkdtemp(join45(
|
|
137497
|
+
tempDir = await mkdtemp(join45(tmpdir4(), `fn-agent-import-${importCompanySlug}-`));
|
|
136935
137498
|
const archivePath = join45(tempDir, "archive.tar.gz");
|
|
136936
137499
|
const downloadController = new AbortController();
|
|
136937
137500
|
const downloadTimeout = setTimeout(() => downloadController.abort(), 3e4);
|
|
@@ -141458,6 +142021,21 @@ ${stderr}`;
|
|
|
141458
142021
|
});
|
|
141459
142022
|
});
|
|
141460
142023
|
}
|
|
142024
|
+
function buildSkippedStatusPayload(expectedVersion) {
|
|
142025
|
+
return {
|
|
142026
|
+
binary: {
|
|
142027
|
+
installed: false,
|
|
142028
|
+
invocation: FN_INSTALL_NPM
|
|
142029
|
+
},
|
|
142030
|
+
expectedVersion,
|
|
142031
|
+
state: "skipped",
|
|
142032
|
+
install: {
|
|
142033
|
+
npm: FN_INSTALL_NPM,
|
|
142034
|
+
curl: FN_INSTALL_CURL,
|
|
142035
|
+
package: FN_NPM_PACKAGE
|
|
142036
|
+
}
|
|
142037
|
+
};
|
|
142038
|
+
}
|
|
141461
142039
|
var INSTALL_TIMEOUT_MS, MAX_OUTPUT_BYTES2, registerFnBinaryRoutes;
|
|
141462
142040
|
var init_register_fn_binary_routes = __esm({
|
|
141463
142041
|
"../dashboard/src/routes/register-fn-binary-routes.ts"() {
|
|
@@ -141468,11 +142046,23 @@ var init_register_fn_binary_routes = __esm({
|
|
|
141468
142046
|
INSTALL_TIMEOUT_MS = 18e4;
|
|
141469
142047
|
MAX_OUTPUT_BYTES2 = 64 * 1024;
|
|
141470
142048
|
registerFnBinaryRoutes = (ctx) => {
|
|
141471
|
-
const { router, rethrowAsApiError: rethrowAsApiError8 } = ctx;
|
|
142049
|
+
const { router, rethrowAsApiError: rethrowAsApiError8, store } = ctx;
|
|
142050
|
+
async function isCheckEnabled() {
|
|
142051
|
+
try {
|
|
142052
|
+
const settings = await store.getSettings();
|
|
142053
|
+
return settings.fnBinaryCheckEnabled !== false;
|
|
142054
|
+
} catch {
|
|
142055
|
+
return true;
|
|
142056
|
+
}
|
|
142057
|
+
}
|
|
141472
142058
|
router.get("/system/fn-binary/status", async (_req, res) => {
|
|
141473
142059
|
try {
|
|
141474
|
-
const binary = await detectFnBinary();
|
|
141475
142060
|
const expectedVersion = getCliPackageVersion();
|
|
142061
|
+
if (!await isCheckEnabled()) {
|
|
142062
|
+
res.json(buildSkippedStatusPayload(expectedVersion));
|
|
142063
|
+
return;
|
|
142064
|
+
}
|
|
142065
|
+
const binary = await detectFnBinary();
|
|
141476
142066
|
res.json(buildStatusPayload(binary, expectedVersion));
|
|
141477
142067
|
} catch (err) {
|
|
141478
142068
|
if (err instanceof ApiError) throw err;
|
|
@@ -141481,6 +142071,13 @@ var init_register_fn_binary_routes = __esm({
|
|
|
141481
142071
|
});
|
|
141482
142072
|
router.post("/system/fn-binary/install", async (_req, res) => {
|
|
141483
142073
|
try {
|
|
142074
|
+
if (!await isCheckEnabled()) {
|
|
142075
|
+
throw new ApiError(
|
|
142076
|
+
409,
|
|
142077
|
+
"fn-binary checks are disabled in global settings (fnBinaryCheckEnabled=false). Re-enable them to install via the dashboard.",
|
|
142078
|
+
{ code: "FN_BINARY_CHECK_DISABLED" }
|
|
142079
|
+
);
|
|
142080
|
+
}
|
|
141484
142081
|
const installResult = await runNpmInstall();
|
|
141485
142082
|
const binary = await detectFnBinary();
|
|
141486
142083
|
const expectedVersion = getCliPackageVersion();
|
|
@@ -153257,7 +153854,7 @@ var require_websocket = __commonJS({
|
|
|
153257
153854
|
var http = __require("http");
|
|
153258
153855
|
var net = __require("net");
|
|
153259
153856
|
var tls = __require("tls");
|
|
153260
|
-
var { randomBytes: randomBytes4, createHash:
|
|
153857
|
+
var { randomBytes: randomBytes4, createHash: createHash6 } = __require("crypto");
|
|
153261
153858
|
var { Duplex, Readable: Readable2 } = __require("stream");
|
|
153262
153859
|
var { URL: URL2 } = __require("url");
|
|
153263
153860
|
var PerMessageDeflate2 = require_permessage_deflate();
|
|
@@ -153917,7 +154514,7 @@ var require_websocket = __commonJS({
|
|
|
153917
154514
|
abortHandshake(websocket, socket, "Invalid Upgrade header");
|
|
153918
154515
|
return;
|
|
153919
154516
|
}
|
|
153920
|
-
const digest =
|
|
154517
|
+
const digest = createHash6("sha1").update(key + GUID).digest("base64");
|
|
153921
154518
|
if (res.headers["sec-websocket-accept"] !== digest) {
|
|
153922
154519
|
abortHandshake(websocket, socket, "Invalid Sec-WebSocket-Accept header");
|
|
153923
154520
|
return;
|
|
@@ -154284,7 +154881,7 @@ var require_websocket_server = __commonJS({
|
|
|
154284
154881
|
var EventEmitter37 = __require("events");
|
|
154285
154882
|
var http = __require("http");
|
|
154286
154883
|
var { Duplex } = __require("stream");
|
|
154287
|
-
var { createHash:
|
|
154884
|
+
var { createHash: createHash6 } = __require("crypto");
|
|
154288
154885
|
var extension2 = require_extension();
|
|
154289
154886
|
var PerMessageDeflate2 = require_permessage_deflate();
|
|
154290
154887
|
var subprotocol2 = require_subprotocol();
|
|
@@ -154585,7 +155182,7 @@ var require_websocket_server = __commonJS({
|
|
|
154585
155182
|
);
|
|
154586
155183
|
}
|
|
154587
155184
|
if (this._state > RUNNING) return abortHandshake(socket, 503);
|
|
154588
|
-
const digest =
|
|
155185
|
+
const digest = createHash6("sha1").update(key + GUID).digest("base64");
|
|
154589
155186
|
const headers = [
|
|
154590
155187
|
"HTTP/1.1 101 Switching Protocols",
|
|
154591
155188
|
"Upgrade: websocket",
|
|
@@ -155627,6 +156224,43 @@ data: ${JSON.stringify(entry)}
|
|
|
155627
156224
|
scopedStore.off("agent:log", onAgentLog);
|
|
155628
156225
|
});
|
|
155629
156226
|
});
|
|
156227
|
+
app.get("/api/agents/:id/runs/:runId/logs/stream", async (req, res) => {
|
|
156228
|
+
const agentId = req.params.id;
|
|
156229
|
+
const runId = req.params.runId;
|
|
156230
|
+
const projectId = typeof req.query.projectId === "string" ? req.query.projectId : void 0;
|
|
156231
|
+
res.setHeader("Content-Type", "text/event-stream");
|
|
156232
|
+
res.setHeader("Cache-Control", "no-cache");
|
|
156233
|
+
res.setHeader("Connection", "keep-alive");
|
|
156234
|
+
res.setHeader("X-Accel-Buffering", "no");
|
|
156235
|
+
res.flushHeaders();
|
|
156236
|
+
res.write(": connected\n\n");
|
|
156237
|
+
const engineManager = options?.engineManager;
|
|
156238
|
+
const engine2 = engineManager && projectId ? engineManager.getEngine(projectId) : options?.engine;
|
|
156239
|
+
const agentStore = engine2?.getAgentStore();
|
|
156240
|
+
if (!agentStore) {
|
|
156241
|
+
res.write(`event: error
|
|
156242
|
+
data: ${JSON.stringify({ message: "No active engine for project" })}
|
|
156243
|
+
|
|
156244
|
+
`);
|
|
156245
|
+
res.end();
|
|
156246
|
+
return;
|
|
156247
|
+
}
|
|
156248
|
+
const onRunLog = (eventAgentId, eventRunId, entry) => {
|
|
156249
|
+
if (eventAgentId !== agentId || eventRunId !== runId) return;
|
|
156250
|
+
res.write(`event: agent:log
|
|
156251
|
+
data: ${JSON.stringify(entry)}
|
|
156252
|
+
|
|
156253
|
+
`);
|
|
156254
|
+
};
|
|
156255
|
+
agentStore.on("run:log", onRunLog);
|
|
156256
|
+
const heartbeat = setInterval(() => {
|
|
156257
|
+
res.write(": heartbeat\n\n");
|
|
156258
|
+
}, 3e4);
|
|
156259
|
+
req.on("close", () => {
|
|
156260
|
+
clearInterval(heartbeat);
|
|
156261
|
+
agentStore.off("run:log", onRunLog);
|
|
156262
|
+
});
|
|
156263
|
+
});
|
|
155630
156264
|
app.get("/api/terminal/sessions/:id/stream", rateLimit(RATE_LIMITS.sse), (req, res) => {
|
|
155631
156265
|
const sessionId = Array.isArray(req.params.id) ? req.params.id[0] : req.params.id;
|
|
155632
156266
|
res.setHeader("Content-Type", "text/event-stream");
|
|
@@ -158252,7 +158886,7 @@ var app_exports = {};
|
|
|
158252
158886
|
__export(app_exports, {
|
|
158253
158887
|
DashboardApp: () => DashboardApp
|
|
158254
158888
|
});
|
|
158255
|
-
import { useState as useState2, useSyncExternalStore, useCallback as useCallback2, useEffect as useEffect2 } from "react";
|
|
158889
|
+
import { useState as useState2, useSyncExternalStore, useCallback as useCallback2, useEffect as useEffect2, useRef } from "react";
|
|
158256
158890
|
import { Box, Text, useInput, useApp, useStdout } from "ink";
|
|
158257
158891
|
import Spinner from "ink-spinner";
|
|
158258
158892
|
import TextInput from "ink-text-input";
|
|
@@ -159034,7 +159668,8 @@ function formatLogTime(iso) {
|
|
|
159034
159668
|
function TaskDetailScreen({
|
|
159035
159669
|
task,
|
|
159036
159670
|
projectPath,
|
|
159037
|
-
interactiveData
|
|
159671
|
+
interactiveData,
|
|
159672
|
+
controller
|
|
159038
159673
|
}) {
|
|
159039
159674
|
const { stdout } = useStdout();
|
|
159040
159675
|
const cols = stdout?.columns ?? 80;
|
|
@@ -159099,6 +159734,27 @@ function TaskDetailScreen({
|
|
|
159099
159734
|
useEffect2(() => {
|
|
159100
159735
|
if (autoFollow) setLogScrollOffset(0);
|
|
159101
159736
|
}, [autoFollow, logCount]);
|
|
159737
|
+
const WHEEL_STEP = 3;
|
|
159738
|
+
const logCountRef = useRef(logCount);
|
|
159739
|
+
const logPaneRowsRef = useRef(logPaneRows);
|
|
159740
|
+
logCountRef.current = logCount;
|
|
159741
|
+
logPaneRowsRef.current = logPaneRows;
|
|
159742
|
+
useEffect2(() => {
|
|
159743
|
+
return controller.onWheel((dir2) => {
|
|
159744
|
+
const maxOffset = Math.max(0, logCountRef.current - logPaneRowsRef.current);
|
|
159745
|
+
if (maxOffset === 0) return;
|
|
159746
|
+
if (dir2 === "up") {
|
|
159747
|
+
setAutoFollow(false);
|
|
159748
|
+
setLogScrollOffset((o) => Math.min(maxOffset, o + WHEEL_STEP));
|
|
159749
|
+
} else {
|
|
159750
|
+
setLogScrollOffset((o) => {
|
|
159751
|
+
const next = Math.max(0, o - WHEEL_STEP);
|
|
159752
|
+
if (next === 0) setAutoFollow(true);
|
|
159753
|
+
return next;
|
|
159754
|
+
});
|
|
159755
|
+
}
|
|
159756
|
+
});
|
|
159757
|
+
}, [controller]);
|
|
159102
159758
|
useInput((input, key) => {
|
|
159103
159759
|
if (detail && detail !== "unavailable" && detail.recentLogs.length > 0) {
|
|
159104
159760
|
const maxOffset = Math.max(0, detail.recentLogs.length - logPaneRows);
|
|
@@ -159469,7 +160125,8 @@ function BoardView({ state, controller }) {
|
|
|
159469
160125
|
{
|
|
159470
160126
|
task: selectedTask,
|
|
159471
160127
|
projectPath: selectedProject?.path ?? null,
|
|
159472
|
-
interactiveData: state.interactiveData
|
|
160128
|
+
interactiveData: state.interactiveData,
|
|
160129
|
+
controller
|
|
159473
160130
|
}
|
|
159474
160131
|
) }) : tasksState.loading ? /* @__PURE__ */ jsxs(Box, { justifyContent: "center", alignItems: "center", flexGrow: 1, gap: 1, children: [
|
|
159475
160132
|
/* @__PURE__ */ jsx(Text, { color: "white", children: /* @__PURE__ */ jsx(Spinner, { type: "dots" }) }),
|
|
@@ -160291,7 +160948,7 @@ function PushModal({
|
|
|
160291
160948
|
}
|
|
160292
160949
|
);
|
|
160293
160950
|
}
|
|
160294
|
-
function GitView({ state }) {
|
|
160951
|
+
function GitView({ state, controller }) {
|
|
160295
160952
|
const { stdout } = useStdout();
|
|
160296
160953
|
const cols = stdout?.columns ?? 80;
|
|
160297
160954
|
const data = state.interactiveData;
|
|
@@ -160479,6 +161136,23 @@ function GitView({ state }) {
|
|
|
160479
161136
|
}
|
|
160480
161137
|
}
|
|
160481
161138
|
});
|
|
161139
|
+
const gitWheelRef = useRef({ activePane, commits, branches, worktrees });
|
|
161140
|
+
gitWheelRef.current = { activePane, commits, branches, worktrees };
|
|
161141
|
+
useEffect2(() => {
|
|
161142
|
+
if (state.interactiveView !== "git") return;
|
|
161143
|
+
return controller.onWheel((dir2) => {
|
|
161144
|
+
const { activePane: pane, commits: cs, branches: bs, worktrees: ws } = gitWheelRef.current;
|
|
161145
|
+
const STEP = 3;
|
|
161146
|
+
const delta = dir2 === "up" ? -STEP : STEP;
|
|
161147
|
+
if (pane === "commits") {
|
|
161148
|
+
setCommitIndex((i) => Math.max(0, Math.min(cs.length - 1, i + delta)));
|
|
161149
|
+
} else if (pane === "branches") {
|
|
161150
|
+
setBranchIndex((i) => Math.max(0, Math.min(bs.length - 1, i + delta)));
|
|
161151
|
+
} else if (pane === "worktrees") {
|
|
161152
|
+
setWorktreeIndex((i) => Math.max(0, Math.min(ws.length - 1, i + delta)));
|
|
161153
|
+
}
|
|
161154
|
+
});
|
|
161155
|
+
}, [controller, state.interactiveView]);
|
|
160482
161156
|
const isNarrow = cols < NARROW_THRESHOLD;
|
|
160483
161157
|
const leftWidth = Math.max(24, Math.floor(cols * 0.35));
|
|
160484
161158
|
const rightWidth = cols - leftWidth - 1;
|
|
@@ -160820,7 +161494,7 @@ function entriesToNodes(entries, depth) {
|
|
|
160820
161494
|
const files = filtered.filter((e) => !e.isDirectory).sort((a, b) => a.name.localeCompare(b.name));
|
|
160821
161495
|
return [...dirs, ...files].map((e) => ({ entry: e, depth, expanded: false, children: void 0 }));
|
|
160822
161496
|
}
|
|
160823
|
-
function FilesView({ state }) {
|
|
161497
|
+
function FilesView({ state, controller }) {
|
|
160824
161498
|
const { stdout } = useStdout();
|
|
160825
161499
|
const cols = stdout?.columns ?? 80;
|
|
160826
161500
|
const data = state.interactiveData;
|
|
@@ -161028,6 +161702,23 @@ function FilesView({ state }) {
|
|
|
161028
161702
|
}
|
|
161029
161703
|
}
|
|
161030
161704
|
}, { isActive: state.interactiveView === "files" });
|
|
161705
|
+
const filesWheelRef = useRef({ focusedPane, flatNodes, previewResult, previewHeight });
|
|
161706
|
+
filesWheelRef.current = { focusedPane, flatNodes, previewResult, previewHeight };
|
|
161707
|
+
useEffect2(() => {
|
|
161708
|
+
if (state.interactiveView !== "files") return;
|
|
161709
|
+
return controller.onWheel((dir2) => {
|
|
161710
|
+
const { focusedPane: pane, flatNodes: nodes, previewResult: pr, previewHeight: ph } = filesWheelRef.current;
|
|
161711
|
+
const STEP = 3;
|
|
161712
|
+
const delta = dir2 === "up" ? -STEP : STEP;
|
|
161713
|
+
if (pane === "tree") {
|
|
161714
|
+
setSelectedIndex((i) => Math.max(0, Math.min(nodes.length - 1, i + delta)));
|
|
161715
|
+
} else {
|
|
161716
|
+
const lineCount = pr?.lineCount ?? 0;
|
|
161717
|
+
const maxScroll = Math.max(0, lineCount - ph);
|
|
161718
|
+
setPreviewScroll((s) => Math.max(0, Math.min(maxScroll, s + delta)));
|
|
161719
|
+
}
|
|
161720
|
+
});
|
|
161721
|
+
}, [controller, state.interactiveView]);
|
|
161031
161722
|
const isNarrow = cols < NARROW_THRESHOLD;
|
|
161032
161723
|
const treeWidth = isNarrow ? Math.max(20, cols - 2) : Math.max(20, Math.floor(cols * 0.38));
|
|
161033
161724
|
const previewEntry = selectedNode && !selectedNode.entry.isDirectory ? selectedNode.entry : null;
|
|
@@ -161191,8 +161882,8 @@ function InteractiveMode({ state, controller }) {
|
|
|
161191
161882
|
state.interactiveView === "board" && /* @__PURE__ */ jsx(BoardView, { state, controller }),
|
|
161192
161883
|
state.interactiveView === "agents" && /* @__PURE__ */ jsx(AgentsView, { state }),
|
|
161193
161884
|
state.interactiveView === "settings" && /* @__PURE__ */ jsx(SettingsInteractiveView, { state, controller }),
|
|
161194
|
-
state.interactiveView === "git" && /* @__PURE__ */ jsx(GitView, { state }),
|
|
161195
|
-
state.interactiveView === "files" && /* @__PURE__ */ jsx(FilesView, { state })
|
|
161885
|
+
state.interactiveView === "git" && /* @__PURE__ */ jsx(GitView, { state, controller }),
|
|
161886
|
+
state.interactiveView === "files" && /* @__PURE__ */ jsx(FilesView, { state, controller })
|
|
161196
161887
|
] })
|
|
161197
161888
|
] });
|
|
161198
161889
|
}
|
|
@@ -161223,6 +161914,23 @@ function DashboardApp({ controller }) {
|
|
|
161223
161914
|
useCallback2((cb) => controller.subscribe(cb), [controller]),
|
|
161224
161915
|
useCallback2(() => controller.getSnapshot(), [controller])
|
|
161225
161916
|
);
|
|
161917
|
+
const wheelStateRef = useRef(state);
|
|
161918
|
+
wheelStateRef.current = state;
|
|
161919
|
+
useEffect2(() => {
|
|
161920
|
+
return controller.onWheel((dir2) => {
|
|
161921
|
+
const s = wheelStateRef.current;
|
|
161922
|
+
if (s.activeSection !== "logs") return;
|
|
161923
|
+
const filtered = controller.getFilteredLogEntries();
|
|
161924
|
+
if (filtered.length === 0) return;
|
|
161925
|
+
const WHEEL_STEP = 3;
|
|
161926
|
+
const cur = s.selectedLogIndex;
|
|
161927
|
+
if (dir2 === "up") {
|
|
161928
|
+
controller.setSelectedLogIndex(Math.max(0, cur - WHEEL_STEP));
|
|
161929
|
+
} else {
|
|
161930
|
+
controller.setSelectedLogIndex(Math.min(filtered.length - 1, cur + WHEEL_STEP));
|
|
161931
|
+
}
|
|
161932
|
+
});
|
|
161933
|
+
}, [controller]);
|
|
161226
161934
|
const [qrOverlay, setQrOverlay] = useState2(null);
|
|
161227
161935
|
useInput((input, key) => {
|
|
161228
161936
|
if ((input === "q" || input === "Q") && !key.ctrl || key.ctrl && input === "c") {
|
|
@@ -161605,6 +162313,15 @@ var init_controller = __esm({
|
|
|
161605
162313
|
// no remote API is wired up).
|
|
161606
162314
|
remoteStatus = null;
|
|
161607
162315
|
remoteStatusTimer = null;
|
|
162316
|
+
// Mouse-wheel handling. We enable xterm SGR mouse mode in start() so the
|
|
162317
|
+
// terminal sends button reports for wheel up/down (buttons 64/65). A
|
|
162318
|
+
// parallel `data` listener parses those reports and dispatches to wheel
|
|
162319
|
+
// handlers. Ink's own keypress parser ignores SGR mouse sequences so
|
|
162320
|
+
// long as the full sequence (including the leading ESC) arrives in one
|
|
162321
|
+
// chunk — which it does once raw mode is enabled before mouse mode is
|
|
162322
|
+
// requested. (See ink#222 / @zenobius/ink-mouse for prior art.)
|
|
162323
|
+
wheelHandlers = /* @__PURE__ */ new Set();
|
|
162324
|
+
mouseStdinListener = null;
|
|
161608
162325
|
constructor() {
|
|
161609
162326
|
this.logBuffer = new LogRingBuffer();
|
|
161610
162327
|
}
|
|
@@ -161613,6 +162330,15 @@ var init_controller = __esm({
|
|
|
161613
162330
|
this.subscribers.add(callback);
|
|
161614
162331
|
return () => this.subscribers.delete(callback);
|
|
161615
162332
|
}
|
|
162333
|
+
/**
|
|
162334
|
+
* Subscribe to mouse-wheel events. Direction is "up" (scroll back/older
|
|
162335
|
+
* content) or "down" (scroll forward/newer content). Only fires while the
|
|
162336
|
+
* dashboard is running and the terminal supports xterm mouse reporting.
|
|
162337
|
+
*/
|
|
162338
|
+
onWheel(handler) {
|
|
162339
|
+
this.wheelHandlers.add(handler);
|
|
162340
|
+
return () => this.wheelHandlers.delete(handler);
|
|
162341
|
+
}
|
|
161616
162342
|
getSnapshot() {
|
|
161617
162343
|
if (this.cachedSnapshot) return this.cachedSnapshot;
|
|
161618
162344
|
this.cachedSnapshot = {
|
|
@@ -161997,6 +162723,10 @@ var init_controller = __esm({
|
|
|
161997
162723
|
this.inkInstance = render(
|
|
161998
162724
|
createElement(DashboardApp2, { controller: this })
|
|
161999
162725
|
);
|
|
162726
|
+
if (process.stdin?.isTTY) {
|
|
162727
|
+
process.stdout.write("\x1B[?1000h\x1B[?1006h");
|
|
162728
|
+
this.installMouseListener();
|
|
162729
|
+
}
|
|
162000
162730
|
this.resizeListener = () => {
|
|
162001
162731
|
if (this.resizeDebounceTimer) clearTimeout(this.resizeDebounceTimer);
|
|
162002
162732
|
this.resizeDebounceTimer = setTimeout(() => {
|
|
@@ -162103,10 +162833,48 @@ var init_controller = __esm({
|
|
|
162103
162833
|
this.inkInstance = null;
|
|
162104
162834
|
}
|
|
162105
162835
|
if (process.stdout?.isTTY && typeof process.stdout.write === "function") {
|
|
162836
|
+
this.uninstallMouseListener();
|
|
162837
|
+
process.stdout.write("\x1B[?1006l\x1B[?1000l");
|
|
162106
162838
|
process.stdout.write("\x1B[?1049l");
|
|
162107
162839
|
}
|
|
162108
162840
|
}
|
|
162109
162841
|
// ── Private helpers ────────────────────────────────────────────────────────
|
|
162842
|
+
// Attach a parallel `data` listener that decodes xterm SGR mouse
|
|
162843
|
+
// sequences and dispatches wheel events. Ink's own listener is also
|
|
162844
|
+
// attached; SGR sequences arrive as a single chunk that Ink's keypress
|
|
162845
|
+
// parser silently ignores, so we don't need to (and shouldn't) strip
|
|
162846
|
+
// them from the stream.
|
|
162847
|
+
installMouseListener() {
|
|
162848
|
+
if (this.mouseStdinListener) return;
|
|
162849
|
+
const mouseRe = /\x1b\[<(\d+);\d+;\d+[Mm]/g;
|
|
162850
|
+
const listener = (chunk) => {
|
|
162851
|
+
const text = typeof chunk === "string" ? chunk : chunk.toString("utf8");
|
|
162852
|
+
if (text.indexOf("\x1B[<") === -1) return;
|
|
162853
|
+
mouseRe.lastIndex = 0;
|
|
162854
|
+
let m;
|
|
162855
|
+
while ((m = mouseRe.exec(text)) !== null) {
|
|
162856
|
+
const btn = Number.parseInt(m[1] ?? "", 10);
|
|
162857
|
+
if (btn === 64) this.dispatchWheel("up");
|
|
162858
|
+
else if (btn === 65) this.dispatchWheel("down");
|
|
162859
|
+
}
|
|
162860
|
+
};
|
|
162861
|
+
this.mouseStdinListener = listener;
|
|
162862
|
+
process.stdin.on("data", listener);
|
|
162863
|
+
}
|
|
162864
|
+
uninstallMouseListener() {
|
|
162865
|
+
if (!this.mouseStdinListener) return;
|
|
162866
|
+
process.stdin.off("data", this.mouseStdinListener);
|
|
162867
|
+
this.mouseStdinListener = null;
|
|
162868
|
+
}
|
|
162869
|
+
dispatchWheel(direction) {
|
|
162870
|
+
for (const handler of this.wheelHandlers) {
|
|
162871
|
+
try {
|
|
162872
|
+
handler(direction);
|
|
162873
|
+
} catch (err) {
|
|
162874
|
+
tuiDebug2("wheel-handler-error", { err: String(err) });
|
|
162875
|
+
}
|
|
162876
|
+
}
|
|
162877
|
+
}
|
|
162110
162878
|
clampSelectedLogIndex(entries) {
|
|
162111
162879
|
if (entries.length === 0) {
|
|
162112
162880
|
this.selectedLogIndex = 0;
|
|
@@ -170710,7 +171478,7 @@ __export(native_patch_exports, {
|
|
|
170710
171478
|
});
|
|
170711
171479
|
import { join as join68, basename as basename21, dirname as dirname28 } from "node:path";
|
|
170712
171480
|
import { existsSync as existsSync51, copyFileSync, mkdirSync as mkdirSync12, symlinkSync as symlinkSync2, rmSync as rmSync5, lstatSync as lstatSync3, readlinkSync as readlinkSync2 } from "node:fs";
|
|
170713
|
-
import { tmpdir as
|
|
171481
|
+
import { tmpdir as tmpdir5 } from "node:os";
|
|
170714
171482
|
function findStagedNativeDir2() {
|
|
170715
171483
|
const platform4 = process.platform === "darwin" ? "darwin" : process.platform === "linux" ? "linux" : process.platform === "win32" ? "win32" : "unknown";
|
|
170716
171484
|
const arch = process.arch === "arm64" ? "arm64" : process.arch === "x64" ? "x64" : "unknown";
|
|
@@ -170755,7 +171523,7 @@ function setupNativeResolution() {
|
|
|
170755
171523
|
process.env.NODE_PTY_SPAWN_HELPER_DIR = nativeDir;
|
|
170756
171524
|
}
|
|
170757
171525
|
process.env.FUSION_NATIVE_ASSETS_PATH = nativeDir;
|
|
170758
|
-
const tmpRoot = join68(
|
|
171526
|
+
const tmpRoot = join68(tmpdir5(), `fn-bunfs-${process.pid}`);
|
|
170759
171527
|
const fnDir = join68(tmpRoot, "fn");
|
|
170760
171528
|
const prebuildsDir = join68(fnDir, "prebuilds");
|
|
170761
171529
|
const platformDir = join68(prebuildsDir, basename21(nativeDir));
|
|
@@ -170843,7 +171611,7 @@ var init_native_patch = __esm({
|
|
|
170843
171611
|
import { existsSync as existsSync52, mkdtempSync as mkdtempSync2, readFileSync as readFileSync24, symlinkSync as symlinkSync3, writeFileSync as writeFileSync6 } from "node:fs";
|
|
170844
171612
|
import { createRequire as createRequire7 } from "node:module";
|
|
170845
171613
|
import { join as join69, dirname as dirname29, resolve as resolve41 } from "node:path";
|
|
170846
|
-
import { tmpdir as
|
|
171614
|
+
import { tmpdir as tmpdir6 } from "node:os";
|
|
170847
171615
|
import { performance as performance3 } from "node:perf_hooks";
|
|
170848
171616
|
import { fileURLToPath as fileURLToPath11 } from "node:url";
|
|
170849
171617
|
var isBunBinary3 = typeof Bun !== "undefined" && !!Bun.embeddedFiles;
|
|
@@ -170851,7 +171619,7 @@ function configurePiPackage() {
|
|
|
170851
171619
|
if (process.env.PI_PACKAGE_DIR) {
|
|
170852
171620
|
return;
|
|
170853
171621
|
}
|
|
170854
|
-
const tmp = mkdtempSync2(join69(
|
|
171622
|
+
const tmp = mkdtempSync2(join69(tmpdir6(), "fn-pkg-"));
|
|
170855
171623
|
let packageJson = {
|
|
170856
171624
|
name: "pi",
|
|
170857
171625
|
version: "0.1.0",
|