@runfusion/fusion 0.14.2 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.js +949 -138
- package/dist/client/assets/AgentDetailView-B1zViykq.js +18 -0
- package/dist/client/assets/{AgentsView-DkX0tzrN.js → AgentsView-Bl9JH5C8.js} +3 -3
- package/dist/client/assets/{ChatView-CEm2Hw6m.js → ChatView-liNErE53.js} +1 -1
- package/dist/client/assets/{DevServerView-Bumvo_ge.js → DevServerView-CV_PpbnZ.js} +1 -1
- package/dist/client/assets/{DirectoryPicker-CXN11cBp.js → DirectoryPicker-DPfkGnj5.js} +1 -1
- package/dist/client/assets/{DocumentsView-B71IqAxA.js → DocumentsView-CESb6RI7.js} +1 -1
- package/dist/client/assets/{InsightsView-Bs4Rldu6.js → InsightsView-BKhvyEyQ.js} +1 -1
- package/dist/client/assets/{MemoryView-Bs7b_L2Q.js → MemoryView-DB-l2miV.js} +1 -1
- package/dist/client/assets/{NodesView-BvAGTXbO.js → NodesView-DgTXO8mm.js} +1 -1
- package/dist/client/assets/{PiExtensionsManager-3Kcc4uhA.js → PiExtensionsManager-C4fTzemh.js} +1 -1
- package/dist/client/assets/{PluginManager-Ch-Xynlm.js → PluginManager-C2-dExUL.js} +1 -1
- package/dist/client/assets/{ResearchView-Bj6Saqf6.js → ResearchView-CkVwRDVA.js} +1 -1
- package/dist/client/assets/{RoadmapsView-9qT8Vwd0.js → RoadmapsView-Cu85_XrQ.js} +1 -1
- package/dist/client/assets/{SettingsModal-D4ERGQNQ.js → SettingsModal-BGnSAeqa.js} +1 -1
- package/dist/client/assets/SettingsModal-C0DokcId.js +31 -0
- package/dist/client/assets/{SetupWizardModal-Dv0rX2_o.js → SetupWizardModal-C_d9clJp.js} +1 -1
- package/dist/client/assets/{SkillMultiselect-CSkXQzdv.js → SkillMultiselect-DwGWYZi6.js} +1 -1
- package/dist/client/assets/{SkillsView-2srXMOzj.js → SkillsView-C096TB7i.js} +1 -1
- package/dist/client/assets/{TodoView-CxPPIvw2.js → TodoView-CUiAt2mR.js} +1 -1
- package/dist/client/assets/{folder-open-FA1PwpXV.js → folder-open-CKivQd8c.js} +1 -1
- package/dist/client/assets/index-B4StE1qN.js +662 -0
- package/dist/client/assets/index-DYJk0WDc.css +1 -0
- package/dist/client/assets/{list-checks-6EktkUso.js → list-checks-B3oufblU.js} +1 -1
- package/dist/client/assets/{star-B6Th07jw.js → star-damu_EYz.js} +1 -1
- package/dist/client/assets/{upload-BJwuErhV.js → upload-uH6CHlEw.js} +1 -1
- package/dist/client/assets/{users-BrnPTF8H.js → users-CUySbfji.js} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/client/version.json +1 -1
- package/dist/extension.js +722 -120
- package/dist/pi-claude-cli/index.ts +2 -2
- package/dist/pi-claude-cli/package.json +1 -1
- package/package.json +1 -1
- package/dist/client/assets/AgentDetailView-C2Iik3Qf.js +0 -18
- package/dist/client/assets/SettingsModal-Zo5qDGOq.js +0 -31
- package/dist/client/assets/index-CEavim6l.js +0 -662
- package/dist/client/assets/index-D1gTSlYB.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,
|
|
@@ -241,7 +242,8 @@ var init_settings_schema = __esm({
|
|
|
241
242
|
maxPostReviewFixes: 1,
|
|
242
243
|
maxSpawnedAgentsPerParent: 5,
|
|
243
244
|
maxSpawnedAgentsGlobal: 20,
|
|
244
|
-
|
|
245
|
+
// Run maintenance (including WAL checkpointing) every 5 minutes by default.
|
|
246
|
+
maintenanceIntervalMs: 3e5,
|
|
245
247
|
autoArchiveDoneTasksEnabled: true,
|
|
246
248
|
autoArchiveDoneAfterMs: 48 * 60 * 60 * 1e3,
|
|
247
249
|
archiveAgentLogMode: "compact",
|
|
@@ -2652,6 +2654,7 @@ var init_sqlite_adapter = __esm({
|
|
|
2652
2654
|
// ../core/src/db.ts
|
|
2653
2655
|
import { isAbsolute, join as join2 } from "node:path";
|
|
2654
2656
|
import { mkdirSync, existsSync } from "node:fs";
|
|
2657
|
+
import { spawnSync } from "node:child_process";
|
|
2655
2658
|
function toJson(value) {
|
|
2656
2659
|
if (value === void 0 || value === null) return "[]";
|
|
2657
2660
|
if (Array.isArray(value) && value.length === 0) return "[]";
|
|
@@ -3283,15 +3286,24 @@ CREATE INDEX IF NOT EXISTS idxTodoItemsSortOrder ON todo_items(listId, sortOrder
|
|
|
3283
3286
|
Database = class {
|
|
3284
3287
|
db;
|
|
3285
3288
|
dbPath;
|
|
3289
|
+
inMemory;
|
|
3290
|
+
corruptionDetected = false;
|
|
3286
3291
|
/** Tracks transaction nesting depth for savepoint-based nested transactions. */
|
|
3287
3292
|
transactionDepth = 0;
|
|
3288
3293
|
_fts5Available;
|
|
3289
3294
|
constructor(fusionDir, options) {
|
|
3290
3295
|
const inMemory = options?.inMemory === true;
|
|
3296
|
+
this.inMemory = inMemory;
|
|
3291
3297
|
this.dbPath = inMemory ? ":memory:" : join2(fusionDir, "fusion.db");
|
|
3292
3298
|
if (!inMemory && !isAbsolute(fusionDir)) {
|
|
3293
3299
|
throw new Error(`[fusion] Database constructor requires an absolute fusionDir path, got: ${fusionDir}`);
|
|
3294
3300
|
}
|
|
3301
|
+
if (!inMemory && /\.fusion[\\/]\.fusion(?:[\\/]|$)/.test(fusionDir)) {
|
|
3302
|
+
throw new Error(
|
|
3303
|
+
`[fusion] Refusing to open Database at nested .fusion/.fusion path: ${fusionDir}
|
|
3304
|
+
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.`
|
|
3305
|
+
);
|
|
3306
|
+
}
|
|
3295
3307
|
if (!inMemory && !existsSync(fusionDir)) {
|
|
3296
3308
|
mkdirSync(fusionDir, { recursive: true });
|
|
3297
3309
|
}
|
|
@@ -3303,8 +3315,13 @@ CREATE INDEX IF NOT EXISTS idxTodoItemsSortOrder ON todo_items(listId, sortOrder
|
|
|
3303
3315
|
}
|
|
3304
3316
|
if (!inMemory) {
|
|
3305
3317
|
this.db.exec("PRAGMA journal_mode = WAL");
|
|
3318
|
+
this.db.exec("PRAGMA busy_timeout = 5000");
|
|
3319
|
+
this.db.exec("PRAGMA synchronous = NORMAL");
|
|
3320
|
+
this.db.exec("PRAGMA wal_autocheckpoint = 100");
|
|
3321
|
+
this.db.exec("PRAGMA journal_size_limit = 4194304");
|
|
3322
|
+
} else {
|
|
3323
|
+
this.db.exec("PRAGMA busy_timeout = 5000");
|
|
3306
3324
|
}
|
|
3307
|
-
this.db.exec("PRAGMA busy_timeout = 5000");
|
|
3308
3325
|
this.db.exec("PRAGMA foreign_keys = ON");
|
|
3309
3326
|
this._fts5Available = probeFts5(this.db);
|
|
3310
3327
|
}
|
|
@@ -3385,6 +3402,35 @@ CREATE INDEX IF NOT EXISTS idxTodoItemsSortOrder ON todo_items(listId, sortOrder
|
|
|
3385
3402
|
return false;
|
|
3386
3403
|
}
|
|
3387
3404
|
}
|
|
3405
|
+
integrityCheck() {
|
|
3406
|
+
if (this.inMemory) {
|
|
3407
|
+
return { ok: true };
|
|
3408
|
+
}
|
|
3409
|
+
const rows = this.db.prepare("PRAGMA integrity_check(100)").all();
|
|
3410
|
+
const errors = rows.map((row) => row.integrity_check).filter((value) => typeof value === "string" && value !== "ok");
|
|
3411
|
+
if (errors.length > 0) {
|
|
3412
|
+
return { ok: false, errors };
|
|
3413
|
+
}
|
|
3414
|
+
return { ok: true };
|
|
3415
|
+
}
|
|
3416
|
+
recoverDatabase(outputPath) {
|
|
3417
|
+
if (this.inMemory) {
|
|
3418
|
+
return false;
|
|
3419
|
+
}
|
|
3420
|
+
const recoveredSql = spawnSync("sqlite3", ["-cmd", ".recover main", this.dbPath], {
|
|
3421
|
+
encoding: "utf-8",
|
|
3422
|
+
maxBuffer: 50 * 1024 * 1024
|
|
3423
|
+
});
|
|
3424
|
+
if (recoveredSql.status !== 0 || !recoveredSql.stdout) {
|
|
3425
|
+
return false;
|
|
3426
|
+
}
|
|
3427
|
+
const rebuilt = spawnSync("sqlite3", [outputPath], {
|
|
3428
|
+
input: recoveredSql.stdout,
|
|
3429
|
+
encoding: "utf-8",
|
|
3430
|
+
maxBuffer: 50 * 1024 * 1024
|
|
3431
|
+
});
|
|
3432
|
+
return rebuilt.status === 0;
|
|
3433
|
+
}
|
|
3388
3434
|
/**
|
|
3389
3435
|
* Initialize the database: create tables if they don't exist
|
|
3390
3436
|
* and seed meta values.
|
|
@@ -3403,6 +3449,11 @@ CREATE INDEX IF NOT EXISTS idxTodoItemsSortOrder ON todo_items(listId, sortOrder
|
|
|
3403
3449
|
this.db.exec(
|
|
3404
3450
|
`INSERT OR IGNORE INTO config (id, nextId, nextWorkflowStepId, settings, workflowSteps, updatedAt) VALUES (1, 1, 1, '${JSON.stringify(DEFAULT_PROJECT_SETTINGS)}', '[]', '${configNow}')`
|
|
3405
3451
|
);
|
|
3452
|
+
const integrity = this.integrityCheck();
|
|
3453
|
+
if (!integrity.ok) {
|
|
3454
|
+
this.corruptionDetected = true;
|
|
3455
|
+
console.warn("[fusion:db] Database integrity check FAILED \u2014 corruption detected");
|
|
3456
|
+
}
|
|
3406
3457
|
}
|
|
3407
3458
|
/**
|
|
3408
3459
|
* Run incremental schema migrations based on the stored schema version.
|
|
@@ -6018,6 +6069,7 @@ var init_agent_store = __esm({
|
|
|
6018
6069
|
};
|
|
6019
6070
|
const line = JSON.stringify(safeEntry) + "\n";
|
|
6020
6071
|
await appendFile(this.runLogPath(agentId, runId), line, "utf-8");
|
|
6072
|
+
this.emit("run:log", agentId, runId, safeEntry);
|
|
6021
6073
|
}
|
|
6022
6074
|
/**
|
|
6023
6075
|
* Read all log entries for a given run from its JSONL file.
|
|
@@ -19071,7 +19123,7 @@ __export(migration_exports, {
|
|
|
19071
19123
|
MigrationCoordinator: () => MigrationCoordinator,
|
|
19072
19124
|
ProjectRequiredError: () => ProjectRequiredError
|
|
19073
19125
|
});
|
|
19074
|
-
import { existsSync as existsSync9, readFileSync as readFileSync2 } from "node:fs";
|
|
19126
|
+
import { existsSync as existsSync9, readFileSync as readFileSync2, realpathSync as realpathSync2 } from "node:fs";
|
|
19075
19127
|
import { homedir as homedir2, tmpdir } from "node:os";
|
|
19076
19128
|
import { isAbsolute as isAbsolute3, join as join12, resolve as resolve5, basename as basename3, dirname as dirname4 } from "node:path";
|
|
19077
19129
|
function getHomeDir2() {
|
|
@@ -19172,12 +19224,30 @@ var init_migration = __esm({
|
|
|
19172
19224
|
const projects = [];
|
|
19173
19225
|
const visited = /* @__PURE__ */ new Set();
|
|
19174
19226
|
let current = resolve5(startDir);
|
|
19175
|
-
const home = getHomeDir2();
|
|
19227
|
+
const home = resolve5(getHomeDir2());
|
|
19176
19228
|
const root = dirname4(current) === current ? current : "/";
|
|
19177
19229
|
const systemTmp = resolve5(tmpdir());
|
|
19178
|
-
|
|
19230
|
+
const normalizePath2 = (path5) => {
|
|
19231
|
+
try {
|
|
19232
|
+
return realpathSync2(path5);
|
|
19233
|
+
} catch {
|
|
19234
|
+
return resolve5(path5);
|
|
19235
|
+
}
|
|
19236
|
+
};
|
|
19237
|
+
const normalizedHome = normalizePath2(home);
|
|
19238
|
+
const normalizedSystemTmp = normalizePath2(systemTmp);
|
|
19239
|
+
const normalizedStartDir = normalizePath2(current);
|
|
19240
|
+
while (true) {
|
|
19179
19241
|
if (visited.has(current)) break;
|
|
19180
19242
|
visited.add(current);
|
|
19243
|
+
const normalizedCurrent = normalizePath2(current);
|
|
19244
|
+
const isRootBoundary = current === root;
|
|
19245
|
+
const isHomeBoundary = normalizedCurrent === normalizedHome;
|
|
19246
|
+
const isTmpBoundary = normalizedCurrent === normalizedSystemTmp;
|
|
19247
|
+
const isStopBoundary = isRootBoundary || isHomeBoundary || isTmpBoundary;
|
|
19248
|
+
if (isRootBoundary || isTmpBoundary || isHomeBoundary && normalizedCurrent !== normalizedStartDir) {
|
|
19249
|
+
break;
|
|
19250
|
+
}
|
|
19181
19251
|
if (this.hasFusionProject(current)) {
|
|
19182
19252
|
const name = await this.generateProjectName(current);
|
|
19183
19253
|
projects.push({
|
|
@@ -19187,6 +19257,9 @@ var init_migration = __esm({
|
|
|
19187
19257
|
});
|
|
19188
19258
|
break;
|
|
19189
19259
|
}
|
|
19260
|
+
if (isStopBoundary) {
|
|
19261
|
+
break;
|
|
19262
|
+
}
|
|
19190
19263
|
const parent2 = dirname4(current);
|
|
19191
19264
|
if (parent2 === current) break;
|
|
19192
19265
|
current = parent2;
|
|
@@ -34495,6 +34568,39 @@ ${task.description}
|
|
|
34495
34568
|
this.db.bumpLastModified();
|
|
34496
34569
|
this.emit("agent:log", entry);
|
|
34497
34570
|
}
|
|
34571
|
+
async appendAgentLogBatch(entries) {
|
|
34572
|
+
if (entries.length === 0) {
|
|
34573
|
+
return;
|
|
34574
|
+
}
|
|
34575
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
34576
|
+
const stmt = this.db.prepare(`
|
|
34577
|
+
INSERT INTO agentLogEntries (taskId, timestamp, text, type, detail, agent)
|
|
34578
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
34579
|
+
`);
|
|
34580
|
+
this.db.transaction(() => {
|
|
34581
|
+
for (const entry of entries) {
|
|
34582
|
+
stmt.run(
|
|
34583
|
+
entry.taskId,
|
|
34584
|
+
timestamp,
|
|
34585
|
+
entry.text,
|
|
34586
|
+
entry.type,
|
|
34587
|
+
entry.detail ?? null,
|
|
34588
|
+
entry.agent ?? null
|
|
34589
|
+
);
|
|
34590
|
+
}
|
|
34591
|
+
});
|
|
34592
|
+
this.db.bumpLastModified();
|
|
34593
|
+
for (const entry of entries) {
|
|
34594
|
+
this.emit("agent:log", {
|
|
34595
|
+
timestamp,
|
|
34596
|
+
taskId: entry.taskId,
|
|
34597
|
+
text: entry.text,
|
|
34598
|
+
type: entry.type,
|
|
34599
|
+
...entry.detail !== void 0 && { detail: entry.detail },
|
|
34600
|
+
...entry.agent !== void 0 && { agent: entry.agent }
|
|
34601
|
+
});
|
|
34602
|
+
}
|
|
34603
|
+
}
|
|
34498
34604
|
mapAgentLogRow(row) {
|
|
34499
34605
|
return {
|
|
34500
34606
|
timestamp: row.timestamp,
|
|
@@ -36344,12 +36450,16 @@ var init_gh_cli = __esm({
|
|
|
36344
36450
|
|
|
36345
36451
|
// ../core/src/fn-binary.ts
|
|
36346
36452
|
import { spawn as spawn2 } from "node:child_process";
|
|
36347
|
-
import { platform as platform2 } from "node:os";
|
|
36453
|
+
import { platform as platform2, tmpdir as tmpdir2 } from "node:os";
|
|
36348
36454
|
function runProbe(command, args, timeoutMs) {
|
|
36349
36455
|
return new Promise((resolve42) => {
|
|
36350
36456
|
let stdout = "";
|
|
36351
36457
|
let stderr = "";
|
|
36352
|
-
const child = spawn2(command, args, {
|
|
36458
|
+
const child = spawn2(command, args, {
|
|
36459
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
36460
|
+
shell: false,
|
|
36461
|
+
cwd: tmpdir2()
|
|
36462
|
+
});
|
|
36353
36463
|
const timer = setTimeout(() => {
|
|
36354
36464
|
try {
|
|
36355
36465
|
child.kill("SIGKILL");
|
|
@@ -37480,6 +37590,20 @@ var init_plugin_loader = __esm({
|
|
|
37480
37590
|
}
|
|
37481
37591
|
return slots;
|
|
37482
37592
|
}
|
|
37593
|
+
/**
|
|
37594
|
+
* Get all top-level dashboard view definitions from loaded plugins.
|
|
37595
|
+
*/
|
|
37596
|
+
getPluginDashboardViews() {
|
|
37597
|
+
const views = [];
|
|
37598
|
+
for (const [pluginId, plugin4] of this.plugins) {
|
|
37599
|
+
if (plugin4.dashboardViews) {
|
|
37600
|
+
for (const view of plugin4.dashboardViews) {
|
|
37601
|
+
views.push({ pluginId, view });
|
|
37602
|
+
}
|
|
37603
|
+
}
|
|
37604
|
+
}
|
|
37605
|
+
return views;
|
|
37606
|
+
}
|
|
37483
37607
|
/**
|
|
37484
37608
|
* Get all runtime registrations from loaded plugins.
|
|
37485
37609
|
* Returns plugin ownership metadata along with the runtime registration.
|
|
@@ -37667,7 +37791,7 @@ async function syncBackupAutomation(automationStore, settings) {
|
|
|
37667
37791
|
if (!AutomationStore2.isValidCron(schedule)) {
|
|
37668
37792
|
throw new Error(`Invalid backup schedule: ${schedule}`);
|
|
37669
37793
|
}
|
|
37670
|
-
const command = "
|
|
37794
|
+
const command = "fn backup --create";
|
|
37671
37795
|
if (existingSchedule) {
|
|
37672
37796
|
return await automationStore.updateSchedule(existingSchedule.id, {
|
|
37673
37797
|
scheduleType: "custom",
|
|
@@ -37700,7 +37824,7 @@ async function syncBackupRoutine(routineStore, settings) {
|
|
|
37700
37824
|
if (!RoutineStore2.isValidCron(schedule)) {
|
|
37701
37825
|
throw new Error(`Invalid backup schedule: ${schedule}`);
|
|
37702
37826
|
}
|
|
37703
|
-
const command = "
|
|
37827
|
+
const command = "fn backup --create";
|
|
37704
37828
|
const input = {
|
|
37705
37829
|
name: BACKUP_SCHEDULE_NAME,
|
|
37706
37830
|
description: "Automatic database backup based on project settings",
|
|
@@ -49575,7 +49699,7 @@ var require_dist3 = __commonJS({
|
|
|
49575
49699
|
|
|
49576
49700
|
// ../core/src/agent-companies-parser.ts
|
|
49577
49701
|
import { existsSync as existsSync17, mkdtempSync, readdirSync as readdirSync2, readFileSync as readFileSync4, rmSync, statSync as statSync4 } from "node:fs";
|
|
49578
|
-
import { tmpdir as
|
|
49702
|
+
import { tmpdir as tmpdir3 } from "node:os";
|
|
49579
49703
|
import { join as join20, resolve as resolve9 } from "node:path";
|
|
49580
49704
|
function slugifyAgentReference(value) {
|
|
49581
49705
|
return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "");
|
|
@@ -49898,7 +50022,7 @@ async function extractTarArchive(archivePath, outputDir) {
|
|
|
49898
50022
|
}
|
|
49899
50023
|
async function parseCompanyArchive(archivePath) {
|
|
49900
50024
|
const resolvedArchivePath = resolve9(archivePath);
|
|
49901
|
-
const tempDir = mkdtempSync(join20(
|
|
50025
|
+
const tempDir = mkdtempSync(join20(tmpdir3(), "agent-companies-"));
|
|
49902
50026
|
try {
|
|
49903
50027
|
if (resolvedArchivePath.endsWith(".tar.gz") || resolvedArchivePath.endsWith(".tgz")) {
|
|
49904
50028
|
await extractTarArchive(resolvedArchivePath, tempDir);
|
|
@@ -51975,18 +52099,21 @@ function summarizeToolArgs(name, args) {
|
|
|
51975
52099
|
}
|
|
51976
52100
|
return void 0;
|
|
51977
52101
|
}
|
|
51978
|
-
var FLUSH_SIZE_BYTES, FLUSH_INTERVAL_MS, AgentLogger;
|
|
52102
|
+
var FLUSH_SIZE_BYTES, FLUSH_INTERVAL_MS, ENTRY_BATCH_SIZE, AgentLogger;
|
|
51979
52103
|
var init_agent_logger = __esm({
|
|
51980
52104
|
"../engine/src/agent-logger.ts"() {
|
|
51981
52105
|
"use strict";
|
|
51982
52106
|
init_logger2();
|
|
51983
52107
|
FLUSH_SIZE_BYTES = 1024;
|
|
51984
52108
|
FLUSH_INTERVAL_MS = 500;
|
|
52109
|
+
ENTRY_BATCH_SIZE = 50;
|
|
51985
52110
|
AgentLogger = class {
|
|
51986
52111
|
textBuffer = "";
|
|
51987
52112
|
thinkingBuffer = "";
|
|
51988
52113
|
flushTimer = null;
|
|
51989
52114
|
thinkingFlushTimer = null;
|
|
52115
|
+
entryFlushTimer = null;
|
|
52116
|
+
pendingEntries = [];
|
|
51990
52117
|
flushSizeBytes;
|
|
51991
52118
|
flushIntervalMs;
|
|
51992
52119
|
store;
|
|
@@ -52091,8 +52218,13 @@ var init_agent_logger = __esm({
|
|
|
52091
52218
|
clearTimeout(this.thinkingFlushTimer);
|
|
52092
52219
|
this.thinkingFlushTimer = null;
|
|
52093
52220
|
}
|
|
52221
|
+
if (this.entryFlushTimer) {
|
|
52222
|
+
clearTimeout(this.entryFlushTimer);
|
|
52223
|
+
this.entryFlushTimer = null;
|
|
52224
|
+
}
|
|
52094
52225
|
await this.flushTextBuffer();
|
|
52095
52226
|
await this.flushThinkingBuffer();
|
|
52227
|
+
await this.flushPendingEntries();
|
|
52096
52228
|
}
|
|
52097
52229
|
// ── Internal helpers ───────────────────────────────────────────────
|
|
52098
52230
|
/**
|
|
@@ -52101,7 +52233,7 @@ var init_agent_logger = __esm({
|
|
|
52101
52233
|
* When only `appendLogCb` is set (no store/taskId), only the callback is used.
|
|
52102
52234
|
* @param storeWarnMsg - Warning message prefix used when the task-store write fails.
|
|
52103
52235
|
*/
|
|
52104
|
-
writeEntry(text, type, detail,
|
|
52236
|
+
writeEntry(text, type, detail, _storeWarnMsg, immediate = false) {
|
|
52105
52237
|
const entry = {
|
|
52106
52238
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
52107
52239
|
taskId: this.taskId,
|
|
@@ -52110,72 +52242,38 @@ var init_agent_logger = __esm({
|
|
|
52110
52242
|
...detail !== void 0 && { detail },
|
|
52111
52243
|
...this.agent !== void 0 && { agent: this.agent }
|
|
52112
52244
|
};
|
|
52113
|
-
|
|
52114
|
-
|
|
52115
|
-
|
|
52116
|
-
|
|
52245
|
+
this.pendingEntries.push(entry);
|
|
52246
|
+
if (immediate || type !== "text" && type !== "thinking") {
|
|
52247
|
+
if (this.entryFlushTimer) {
|
|
52248
|
+
clearTimeout(this.entryFlushTimer);
|
|
52249
|
+
this.entryFlushTimer = null;
|
|
52250
|
+
}
|
|
52251
|
+
void this.flushPendingEntries();
|
|
52252
|
+
return;
|
|
52117
52253
|
}
|
|
52118
|
-
if (this.
|
|
52119
|
-
this.
|
|
52120
|
-
this.
|
|
52121
|
-
|
|
52254
|
+
if (this.pendingEntries.length >= ENTRY_BATCH_SIZE) {
|
|
52255
|
+
if (this.entryFlushTimer) {
|
|
52256
|
+
clearTimeout(this.entryFlushTimer);
|
|
52257
|
+
this.entryFlushTimer = null;
|
|
52258
|
+
}
|
|
52259
|
+
void this.flushPendingEntries();
|
|
52260
|
+
return;
|
|
52122
52261
|
}
|
|
52262
|
+
this.scheduleEntryFlush();
|
|
52123
52263
|
}
|
|
52124
52264
|
flushTextBuffer() {
|
|
52125
52265
|
if (this.textBuffer.length === 0) return Promise.resolve();
|
|
52126
52266
|
const chunk = this.textBuffer;
|
|
52127
52267
|
this.textBuffer = "";
|
|
52128
|
-
|
|
52129
|
-
|
|
52130
|
-
taskId: this.taskId,
|
|
52131
|
-
text: chunk,
|
|
52132
|
-
type: "text",
|
|
52133
|
-
...this.agent !== void 0 && { agent: this.agent }
|
|
52134
|
-
};
|
|
52135
|
-
const promises = [];
|
|
52136
|
-
if (this.store && this.taskId) {
|
|
52137
|
-
promises.push(
|
|
52138
|
-
this.store.appendAgentLog(this.taskId, chunk, "text", void 0, this.agent).catch((err) => {
|
|
52139
|
-
this.log.warn(`Failed to flush text buffer for ${this.taskId}: ${err instanceof Error ? err.message : String(err)}`);
|
|
52140
|
-
})
|
|
52141
|
-
);
|
|
52142
|
-
}
|
|
52143
|
-
if (this.appendLogCb) {
|
|
52144
|
-
promises.push(
|
|
52145
|
-
this.appendLogCb(entry).catch((err) => {
|
|
52146
|
-
this.log.warn(`appendLog callback failed for text flush: ${err instanceof Error ? err.message : String(err)}`);
|
|
52147
|
-
})
|
|
52148
|
-
);
|
|
52149
|
-
}
|
|
52150
|
-
return Promise.all(promises).then(() => void 0);
|
|
52268
|
+
this.writeEntry(chunk, "text", void 0, `Failed to flush text buffer for ${this.taskId}`, true);
|
|
52269
|
+
return this.flushPendingEntries();
|
|
52151
52270
|
}
|
|
52152
52271
|
flushThinkingBuffer() {
|
|
52153
52272
|
if (this.thinkingBuffer.length === 0) return Promise.resolve();
|
|
52154
52273
|
const chunk = this.thinkingBuffer;
|
|
52155
52274
|
this.thinkingBuffer = "";
|
|
52156
|
-
|
|
52157
|
-
|
|
52158
|
-
taskId: this.taskId,
|
|
52159
|
-
text: chunk,
|
|
52160
|
-
type: "thinking",
|
|
52161
|
-
...this.agent !== void 0 && { agent: this.agent }
|
|
52162
|
-
};
|
|
52163
|
-
const promises = [];
|
|
52164
|
-
if (this.store && this.taskId) {
|
|
52165
|
-
promises.push(
|
|
52166
|
-
this.store.appendAgentLog(this.taskId, chunk, "thinking", void 0, this.agent).catch((err) => {
|
|
52167
|
-
this.log.warn(`Failed to flush thinking buffer for ${this.taskId}: ${err instanceof Error ? err.message : String(err)}`);
|
|
52168
|
-
})
|
|
52169
|
-
);
|
|
52170
|
-
}
|
|
52171
|
-
if (this.appendLogCb) {
|
|
52172
|
-
promises.push(
|
|
52173
|
-
this.appendLogCb(entry).catch((err) => {
|
|
52174
|
-
this.log.warn(`appendLog callback failed for thinking flush: ${err instanceof Error ? err.message : String(err)}`);
|
|
52175
|
-
})
|
|
52176
|
-
);
|
|
52177
|
-
}
|
|
52178
|
-
return Promise.all(promises).then(() => void 0);
|
|
52275
|
+
this.writeEntry(chunk, "thinking", void 0, `Failed to flush thinking buffer for ${this.taskId}`, true);
|
|
52276
|
+
return this.flushPendingEntries();
|
|
52179
52277
|
}
|
|
52180
52278
|
scheduleFlush() {
|
|
52181
52279
|
if (this.flushTimer) return;
|
|
@@ -52191,6 +52289,52 @@ var init_agent_logger = __esm({
|
|
|
52191
52289
|
this.flushThinkingBuffer();
|
|
52192
52290
|
}, this.flushIntervalMs);
|
|
52193
52291
|
}
|
|
52292
|
+
scheduleEntryFlush() {
|
|
52293
|
+
if (this.entryFlushTimer) return;
|
|
52294
|
+
this.entryFlushTimer = setTimeout(() => {
|
|
52295
|
+
this.entryFlushTimer = null;
|
|
52296
|
+
void this.flushPendingEntries();
|
|
52297
|
+
}, this.flushIntervalMs);
|
|
52298
|
+
}
|
|
52299
|
+
async flushPendingEntries() {
|
|
52300
|
+
if (this.pendingEntries.length === 0) {
|
|
52301
|
+
return;
|
|
52302
|
+
}
|
|
52303
|
+
const entries = this.pendingEntries;
|
|
52304
|
+
this.pendingEntries = [];
|
|
52305
|
+
if (this.store && this.taskId) {
|
|
52306
|
+
if (typeof this.store.appendAgentLogBatch === "function") {
|
|
52307
|
+
await this.store.appendAgentLogBatch(
|
|
52308
|
+
entries.map((entry) => ({
|
|
52309
|
+
taskId: entry.taskId,
|
|
52310
|
+
text: entry.text,
|
|
52311
|
+
type: entry.type,
|
|
52312
|
+
detail: entry.detail,
|
|
52313
|
+
agent: entry.agent
|
|
52314
|
+
}))
|
|
52315
|
+
).catch((err) => {
|
|
52316
|
+
this.log.warn(`Failed to flush agent log batch for ${this.taskId}: ${err instanceof Error ? err.message : String(err)}`);
|
|
52317
|
+
});
|
|
52318
|
+
} else {
|
|
52319
|
+
await Promise.all(
|
|
52320
|
+
entries.map(
|
|
52321
|
+
(entry) => this.store.appendAgentLog(entry.taskId, entry.text, entry.type, entry.detail, entry.agent).catch((err) => {
|
|
52322
|
+
this.log.warn(`Failed to flush agent log entry for ${this.taskId}: ${err instanceof Error ? err.message : String(err)}`);
|
|
52323
|
+
})
|
|
52324
|
+
)
|
|
52325
|
+
);
|
|
52326
|
+
}
|
|
52327
|
+
}
|
|
52328
|
+
if (this.appendLogCb) {
|
|
52329
|
+
await Promise.all(
|
|
52330
|
+
entries.map(
|
|
52331
|
+
(entry) => this.appendLogCb(entry).catch((err) => {
|
|
52332
|
+
this.log.warn(`appendLog callback failed for entry (${entry.type}): ${err instanceof Error ? err.message : String(err)}`);
|
|
52333
|
+
})
|
|
52334
|
+
)
|
|
52335
|
+
);
|
|
52336
|
+
}
|
|
52337
|
+
}
|
|
52194
52338
|
};
|
|
52195
52339
|
}
|
|
52196
52340
|
});
|
|
@@ -65767,6 +65911,20 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
65767
65911
|
);
|
|
65768
65912
|
this.activeStepExecutors.delete(task.id);
|
|
65769
65913
|
}
|
|
65914
|
+
if (this.activeWorkflowStepSessions.has(task.id)) {
|
|
65915
|
+
executorLog.log(`${task.id} moved from in-progress to ${to} \u2014 terminating workflow step session`);
|
|
65916
|
+
this.pausedAborted.add(task.id);
|
|
65917
|
+
this.options.stuckTaskDetector?.untrackTask(task.id);
|
|
65918
|
+
const workflowSession = this.activeWorkflowStepSessions.get(task.id);
|
|
65919
|
+
const sessionWithAbort = workflowSession;
|
|
65920
|
+
if (typeof sessionWithAbort.abort === "function") {
|
|
65921
|
+
void sessionWithAbort.abort().catch((err) => {
|
|
65922
|
+
executorLog.warn(`Failed to abort workflow step session for ${task.id}: ${err}`);
|
|
65923
|
+
});
|
|
65924
|
+
}
|
|
65925
|
+
workflowSession.dispose();
|
|
65926
|
+
this.activeWorkflowStepSessions.delete(task.id);
|
|
65927
|
+
}
|
|
65770
65928
|
this.disposeSubagentsForTask(task.id, `parent moved from in-progress to ${to}`);
|
|
65771
65929
|
this.loopRecoveryState.delete(task.id);
|
|
65772
65930
|
this.spawnedAgents.delete(task.id);
|
|
@@ -65799,8 +65957,42 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
65799
65957
|
this.disposeSubagentsForTask(task.id, "task paused");
|
|
65800
65958
|
return;
|
|
65801
65959
|
}
|
|
65802
|
-
if (
|
|
65960
|
+
if (task.paused && this.activeWorkflowStepSessions.has(task.id)) {
|
|
65961
|
+
executorLog.log(`Pausing ${task.id} \u2014 terminating workflow step session`);
|
|
65962
|
+
this.pausedAborted.add(task.id);
|
|
65963
|
+
this.options.stuckTaskDetector?.untrackTask(task.id);
|
|
65964
|
+
const workflowSession = this.activeWorkflowStepSessions.get(task.id);
|
|
65965
|
+
const sessionWithAbort = workflowSession;
|
|
65966
|
+
if (typeof sessionWithAbort.abort === "function") {
|
|
65967
|
+
await sessionWithAbort.abort().catch(
|
|
65968
|
+
(err) => executorLog.warn(`Failed to abort workflow step session for pause ${task.id}: ${err}`)
|
|
65969
|
+
);
|
|
65970
|
+
}
|
|
65971
|
+
workflowSession.dispose();
|
|
65972
|
+
this.activeWorkflowStepSessions.delete(task.id);
|
|
65973
|
+
this.loopRecoveryState.delete(task.id);
|
|
65974
|
+
this.spawnedAgents.delete(task.id);
|
|
65975
|
+
this.stuckAborted.delete(task.id);
|
|
65976
|
+
this.disposeSubagentsForTask(task.id, "task paused");
|
|
65977
|
+
return;
|
|
65978
|
+
}
|
|
65979
|
+
if (!task.paused && task.column === "in-progress" && !this.activeSessions.has(task.id) && !this.activeStepExecutors.has(task.id) && !this.activeWorkflowStepSessions.has(task.id)) {
|
|
65803
65980
|
if (!this.executing.has(task.id) && !this.resumingUnpaused.has(task.id) && !this.recoveringCompleted.has(task.id)) {
|
|
65981
|
+
const pauseLabel = await this.getExecutionPauseLabel();
|
|
65982
|
+
if (pauseLabel) {
|
|
65983
|
+
executorLog.log(`Skipping unpause resume for ${task.id} \u2014 ${pauseLabel} active`);
|
|
65984
|
+
return;
|
|
65985
|
+
}
|
|
65986
|
+
if (this.isTaskWorkComplete(task) && !task.mergeDetails) {
|
|
65987
|
+
this.recoveringCompleted.add(task.id);
|
|
65988
|
+
executorLog.log(`${task.id} unpaused with completed work and no session \u2014 recovering directly to in-review`);
|
|
65989
|
+
void this.recoverCompletedTask(task).catch(
|
|
65990
|
+
(err) => executorLog.error(`Failed to recover completed unpaused task ${task.id}:`, err)
|
|
65991
|
+
).finally(() => {
|
|
65992
|
+
this.recoveringCompleted.delete(task.id);
|
|
65993
|
+
});
|
|
65994
|
+
return;
|
|
65995
|
+
}
|
|
65804
65996
|
this.resumingUnpaused.add(task.id);
|
|
65805
65997
|
executorLog.log(`Unpaused ${task.id} in-progress with no session \u2014 resuming execution`);
|
|
65806
65998
|
try {
|
|
@@ -65919,6 +66111,22 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
65919
66111
|
this.spawnedAgents.delete(taskId);
|
|
65920
66112
|
this.stuckAborted.delete(taskId);
|
|
65921
66113
|
}
|
|
66114
|
+
for (const [taskId, workflowSession] of this.activeWorkflowStepSessions) {
|
|
66115
|
+
executorLog.log(`Global pause \u2014 terminating workflow step session for ${taskId}`);
|
|
66116
|
+
this.pausedAborted.add(taskId);
|
|
66117
|
+
this.options.stuckTaskDetector?.untrackTask(taskId);
|
|
66118
|
+
const sessionWithAbort = workflowSession;
|
|
66119
|
+
if (typeof sessionWithAbort.abort === "function") {
|
|
66120
|
+
void sessionWithAbort.abort().catch((err) => {
|
|
66121
|
+
executorLog.warn(`Failed to abort workflow step session for ${taskId}: ${err}`);
|
|
66122
|
+
});
|
|
66123
|
+
}
|
|
66124
|
+
workflowSession.dispose();
|
|
66125
|
+
this.activeWorkflowStepSessions.delete(taskId);
|
|
66126
|
+
this.loopRecoveryState.delete(taskId);
|
|
66127
|
+
this.spawnedAgents.delete(taskId);
|
|
66128
|
+
this.stuckAborted.delete(taskId);
|
|
66129
|
+
}
|
|
65922
66130
|
}
|
|
65923
66131
|
});
|
|
65924
66132
|
}
|
|
@@ -65936,6 +66144,8 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
65936
66144
|
activeSessions = /* @__PURE__ */ new Map();
|
|
65937
66145
|
/** Active step-session executors per task (mutually exclusive with activeSessions). */
|
|
65938
66146
|
activeStepExecutors = /* @__PURE__ */ new Map();
|
|
66147
|
+
/** Active pre-merge workflow step sessions per task. */
|
|
66148
|
+
activeWorkflowStepSessions = /* @__PURE__ */ new Map();
|
|
65939
66149
|
/**
|
|
65940
66150
|
* Reviewer subagent sessions per task. Reviewers (`reviewer.ts`) create their
|
|
65941
66151
|
* own AgentSessions that aren't part of `activeSessions`/`activeStepExecutors`,
|
|
@@ -65982,6 +66192,69 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
65982
66192
|
await this.store.mergeTask(taskId);
|
|
65983
66193
|
return "merged";
|
|
65984
66194
|
}
|
|
66195
|
+
async getExecutionPauseLabel() {
|
|
66196
|
+
const settings = await this.store.getSettings();
|
|
66197
|
+
if (settings.globalPause) return "global pause";
|
|
66198
|
+
if (settings.enginePaused) return "engine pause";
|
|
66199
|
+
return null;
|
|
66200
|
+
}
|
|
66201
|
+
async shouldDeferCompletionForGlobalPause(taskId, context) {
|
|
66202
|
+
const settings = await this.store.getSettings();
|
|
66203
|
+
if (!settings.globalPause) {
|
|
66204
|
+
return false;
|
|
66205
|
+
}
|
|
66206
|
+
this.clearCompletedTaskWatchdog(taskId);
|
|
66207
|
+
executorLog.log(`${taskId}: completion handoff deferred \u2014 global pause active (${context})`);
|
|
66208
|
+
await this.store.logEntry(
|
|
66209
|
+
taskId,
|
|
66210
|
+
`Completion handoff deferred \u2014 global pause active (${context})`,
|
|
66211
|
+
void 0,
|
|
66212
|
+
this.currentRunContext
|
|
66213
|
+
).catch(() => void 0);
|
|
66214
|
+
return true;
|
|
66215
|
+
}
|
|
66216
|
+
async shouldDeferWorkflowStepCompletion(taskId, context) {
|
|
66217
|
+
let latestTask = null;
|
|
66218
|
+
try {
|
|
66219
|
+
latestTask = await this.store.getTask(taskId);
|
|
66220
|
+
} catch {
|
|
66221
|
+
latestTask = null;
|
|
66222
|
+
}
|
|
66223
|
+
if (latestTask?.paused || this.pausedAborted.has(taskId)) {
|
|
66224
|
+
this.clearCompletedTaskWatchdog(taskId);
|
|
66225
|
+
executorLog.log(`${taskId}: completion handoff deferred \u2014 task paused (${context})`);
|
|
66226
|
+
await this.store.logEntry(
|
|
66227
|
+
taskId,
|
|
66228
|
+
`Completion handoff deferred \u2014 task paused (${context})`,
|
|
66229
|
+
void 0,
|
|
66230
|
+
this.currentRunContext
|
|
66231
|
+
).catch(() => void 0);
|
|
66232
|
+
return true;
|
|
66233
|
+
}
|
|
66234
|
+
return this.shouldDeferCompletionForGlobalPause(taskId, context);
|
|
66235
|
+
}
|
|
66236
|
+
async parkTaskAfterWorkflowStepPause(taskId) {
|
|
66237
|
+
let latestTask = null;
|
|
66238
|
+
try {
|
|
66239
|
+
latestTask = await this.store.getTask(taskId);
|
|
66240
|
+
} catch {
|
|
66241
|
+
latestTask = null;
|
|
66242
|
+
}
|
|
66243
|
+
if (!latestTask?.paused) {
|
|
66244
|
+
return false;
|
|
66245
|
+
}
|
|
66246
|
+
executorLog.log(`${taskId}: workflow step interrupted by task pause \u2014 moving to todo`);
|
|
66247
|
+
await this.store.logEntry(
|
|
66248
|
+
taskId,
|
|
66249
|
+
"Execution paused during pre-merge workflow step \u2014 moved to todo",
|
|
66250
|
+
void 0,
|
|
66251
|
+
this.currentRunContext
|
|
66252
|
+
).catch(() => void 0);
|
|
66253
|
+
if (latestTask.column === "in-progress") {
|
|
66254
|
+
await this.store.moveTask(taskId, "todo", { preserveResumeState: true });
|
|
66255
|
+
}
|
|
66256
|
+
return true;
|
|
66257
|
+
}
|
|
65985
66258
|
/** Child agent sessions keyed by agent ID. Used for termination. */
|
|
65986
66259
|
childSessions = /* @__PURE__ */ new Map();
|
|
65987
66260
|
/** Total count of currently spawned agents (across all parents). */
|
|
@@ -66176,11 +66449,15 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
66176
66449
|
this.clearCompletedTaskWatchdog(taskId);
|
|
66177
66450
|
const handle = setTimeout(async () => {
|
|
66178
66451
|
this.completedTaskWatchdogs.delete(taskId);
|
|
66179
|
-
if (this.recoveringCompleted.has(taskId) || this.executing.has(taskId) || this.activeSessions.has(taskId) || this.activeStepExecutors.has(taskId) || this.resumingUnpaused.has(taskId)) {
|
|
66452
|
+
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)) {
|
|
66180
66453
|
return;
|
|
66181
66454
|
}
|
|
66182
66455
|
this.recoveringCompleted.add(taskId);
|
|
66183
66456
|
try {
|
|
66457
|
+
const pauseLabel = await this.getExecutionPauseLabel();
|
|
66458
|
+
if (pauseLabel) {
|
|
66459
|
+
return;
|
|
66460
|
+
}
|
|
66184
66461
|
let currentTask = null;
|
|
66185
66462
|
try {
|
|
66186
66463
|
currentTask = await this.store.getTask(taskId);
|
|
@@ -66226,6 +66503,11 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
66226
66503
|
* stuck.
|
|
66227
66504
|
*/
|
|
66228
66505
|
async performWorkflowRerunBounce(taskId, worktreePath, preserveResumeState = true) {
|
|
66506
|
+
const pauseLabel = await this.getExecutionPauseLabel();
|
|
66507
|
+
if (pauseLabel) {
|
|
66508
|
+
executorLog.log(`${taskId}: workflow rerun deferred \u2014 ${pauseLabel} active`);
|
|
66509
|
+
return "deferred-paused";
|
|
66510
|
+
}
|
|
66229
66511
|
if (this.workflowRerunPending.has(taskId)) {
|
|
66230
66512
|
executorLog.warn(`${taskId}: workflow rerun bounce already in flight \u2014 skipping re-entry`);
|
|
66231
66513
|
return "skipped-pending";
|
|
@@ -66236,6 +66518,10 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
66236
66518
|
if (!latestTask) {
|
|
66237
66519
|
throw new Error("task missing during workflow rerun bounce");
|
|
66238
66520
|
}
|
|
66521
|
+
if (latestTask.paused) {
|
|
66522
|
+
executorLog.log(`${taskId}: workflow rerun deferred \u2014 task is paused`);
|
|
66523
|
+
return "deferred-paused";
|
|
66524
|
+
}
|
|
66239
66525
|
if (latestTask.column === "in-progress") {
|
|
66240
66526
|
const originalExecutionStartedAt = latestTask.executionStartedAt;
|
|
66241
66527
|
if (preserveResumeState) {
|
|
@@ -66247,11 +66533,21 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
66247
66533
|
worktree: worktreePath,
|
|
66248
66534
|
executionStartedAt: originalExecutionStartedAt ?? null
|
|
66249
66535
|
});
|
|
66536
|
+
const pauseLabelAfterTodo = await this.getExecutionPauseLabel();
|
|
66537
|
+
if (pauseLabelAfterTodo) {
|
|
66538
|
+
executorLog.log(`${taskId}: workflow rerun parked in todo \u2014 ${pauseLabelAfterTodo} became active during bounce`);
|
|
66539
|
+
return "deferred-paused";
|
|
66540
|
+
}
|
|
66250
66541
|
await this.store.moveTask(taskId, "in-progress");
|
|
66251
66542
|
return "bounced";
|
|
66252
66543
|
}
|
|
66253
66544
|
if (latestTask.column === "todo") {
|
|
66254
66545
|
await this.store.updateTask(taskId, { worktree: worktreePath });
|
|
66546
|
+
const pauseLabelBeforeResume = await this.getExecutionPauseLabel();
|
|
66547
|
+
if (pauseLabelBeforeResume) {
|
|
66548
|
+
executorLog.log(`${taskId}: workflow rerun parked in todo \u2014 ${pauseLabelBeforeResume} became active before resume`);
|
|
66549
|
+
return "deferred-paused";
|
|
66550
|
+
}
|
|
66255
66551
|
await this.store.moveTask(taskId, "in-progress");
|
|
66256
66552
|
return "bounced";
|
|
66257
66553
|
}
|
|
@@ -66267,8 +66563,10 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
66267
66563
|
const outcome = await this.performWorkflowRerunBounce(taskId, worktreePath, preserveResumeState);
|
|
66268
66564
|
if (outcome === "bounced") {
|
|
66269
66565
|
executorLog.log(successMessage);
|
|
66270
|
-
} else {
|
|
66566
|
+
} else if (outcome === "skipped-pending") {
|
|
66271
66567
|
executorLog.warn(`${taskId}: rerun bounce skipped \u2014 another bounce already in flight`);
|
|
66568
|
+
} else {
|
|
66569
|
+
executorLog.log(`${taskId}: rerun bounce deferred while pause is active`);
|
|
66272
66570
|
}
|
|
66273
66571
|
} catch (err) {
|
|
66274
66572
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
@@ -66277,6 +66575,11 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
66277
66575
|
}, 0);
|
|
66278
66576
|
const watchdog = setTimeout(async () => {
|
|
66279
66577
|
this.workflowRerunWatchdogs.delete(taskId);
|
|
66578
|
+
const pauseLabel = await this.getExecutionPauseLabel();
|
|
66579
|
+
if (pauseLabel) {
|
|
66580
|
+
executorLog.log(`${taskId}: workflow rerun watchdog skipped \u2014 ${pauseLabel} active`);
|
|
66581
|
+
return;
|
|
66582
|
+
}
|
|
66280
66583
|
let currentTask = null;
|
|
66281
66584
|
try {
|
|
66282
66585
|
currentTask = await this.store.getTask(taskId);
|
|
@@ -66299,7 +66602,7 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
66299
66602
|
const outcome = await this.performWorkflowRerunBounce(taskId, worktreePath, preserveResumeState);
|
|
66300
66603
|
if (outcome === "bounced") {
|
|
66301
66604
|
executorLog.warn(`${taskId}: workflow rerun watchdog retry succeeded`);
|
|
66302
|
-
} else {
|
|
66605
|
+
} else if (outcome === "skipped-pending") {
|
|
66303
66606
|
executorLog.error(
|
|
66304
66607
|
`${taskId}: workflow rerun watchdog retry skipped \u2014 original bounce still in flight after ${WORKFLOW_RERUN_WATCHDOG_MS / 1e3}s; task may be stuck`
|
|
66305
66608
|
);
|
|
@@ -66307,6 +66610,8 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
66307
66610
|
taskId,
|
|
66308
66611
|
`Workflow rerun watchdog retry skipped \u2014 original bounce still in flight after ${WORKFLOW_RERUN_WATCHDOG_MS / 1e3}s; task may be stuck`
|
|
66309
66612
|
).catch(() => void 0);
|
|
66613
|
+
} else {
|
|
66614
|
+
executorLog.log(`${taskId}: workflow rerun watchdog retry deferred while pause is active`);
|
|
66310
66615
|
}
|
|
66311
66616
|
} catch (err) {
|
|
66312
66617
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
@@ -66429,11 +66734,17 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
66429
66734
|
*/
|
|
66430
66735
|
async recoverCompletedTask(task) {
|
|
66431
66736
|
try {
|
|
66432
|
-
if (this.executing.has(task.id) || this.activeSessions.has(task.id) || this.activeStepExecutors.has(task.id) || this.resumingUnpaused.has(task.id)) {
|
|
66737
|
+
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)) {
|
|
66433
66738
|
executorLog.log(`${task.id}: skipping recoverCompletedTask \u2014 task has active execution in flight`);
|
|
66434
66739
|
return false;
|
|
66435
66740
|
}
|
|
66436
66741
|
const settings = await this.store.getSettings();
|
|
66742
|
+
if (settings.globalPause || settings.enginePaused) {
|
|
66743
|
+
executorLog.log(
|
|
66744
|
+
`${task.id}: skipping recoverCompletedTask \u2014 ${settings.globalPause ? "global pause" : "engine pause"} active`
|
|
66745
|
+
);
|
|
66746
|
+
return false;
|
|
66747
|
+
}
|
|
66437
66748
|
if (task.worktree && existsSync27(task.worktree)) {
|
|
66438
66749
|
const modifiedFiles = await this.captureModifiedFiles(task.worktree, task.baseCommitSha);
|
|
66439
66750
|
if (modifiedFiles.length > 0) {
|
|
@@ -66441,7 +66752,16 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
66441
66752
|
executorLog.log(`${task.id}: recovered ${modifiedFiles.length} modified files`);
|
|
66442
66753
|
}
|
|
66443
66754
|
if (task.executionMode !== "fast") {
|
|
66755
|
+
if (await this.shouldDeferCompletionForGlobalPause(task.id, "before workflow steps during completed-task recovery")) {
|
|
66756
|
+
return false;
|
|
66757
|
+
}
|
|
66444
66758
|
const workflowResult = await this.runWorkflowSteps(task, task.worktree, settings);
|
|
66759
|
+
if (workflowResult === "deferred-paused") {
|
|
66760
|
+
if (this.pausedAborted.has(task.id)) {
|
|
66761
|
+
this.pausedAborted.delete(task.id);
|
|
66762
|
+
}
|
|
66763
|
+
return false;
|
|
66764
|
+
}
|
|
66445
66765
|
if (!workflowResult.allPassed) {
|
|
66446
66766
|
await this.sendTaskBackForFix(task, task.worktree, workflowResult.feedback, workflowResult.stepName || "Unknown", "Workflow step failed during recovery", false);
|
|
66447
66767
|
return true;
|
|
@@ -66450,6 +66770,9 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
66450
66770
|
executorLog.log(`${task.id}: fast mode \u2014 skipping workflow steps on auto-recovery`);
|
|
66451
66771
|
}
|
|
66452
66772
|
}
|
|
66773
|
+
if (await this.shouldDeferCompletionForGlobalPause(task.id, "before in-review transition during completed-task recovery")) {
|
|
66774
|
+
return false;
|
|
66775
|
+
}
|
|
66453
66776
|
await this.persistTokenUsage(task.id);
|
|
66454
66777
|
await this.store.moveTask(task.id, "in-review");
|
|
66455
66778
|
this.clearCompletedTaskWatchdog(task.id);
|
|
@@ -66515,6 +66838,13 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
66515
66838
|
* directly to in-review without spawning a new agent session.
|
|
66516
66839
|
*/
|
|
66517
66840
|
async resumeOrphaned() {
|
|
66841
|
+
const settings = await this.store.getSettings();
|
|
66842
|
+
if (settings.globalPause || settings.enginePaused) {
|
|
66843
|
+
executorLog.log(
|
|
66844
|
+
`resumeOrphaned skipped \u2014 ${settings.globalPause ? "global pause" : "engine pause"} is active`
|
|
66845
|
+
);
|
|
66846
|
+
return;
|
|
66847
|
+
}
|
|
66518
66848
|
const tasks = await this.store.listTasks({ slim: true, column: "in-progress" });
|
|
66519
66849
|
const inProgress = tasks.filter(
|
|
66520
66850
|
(t) => t.column === "in-progress" && !this.executing.has(t.id) && !t.paused
|
|
@@ -66953,8 +67283,21 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
66953
67283
|
await audit.filesystem({ type: "file:capture-modified", target: task.id, metadata: { files: modifiedFiles } });
|
|
66954
67284
|
}
|
|
66955
67285
|
this.scheduleCompletedTaskWatchdog(task.id, "step-session completion");
|
|
67286
|
+
if (await this.shouldDeferCompletionForGlobalPause(task.id, "before workflow steps after step-session completion")) {
|
|
67287
|
+
return;
|
|
67288
|
+
}
|
|
66956
67289
|
if (executionMode !== "fast") {
|
|
66957
67290
|
const workflowResult = await this.runWorkflowSteps(task, worktreePath, settings);
|
|
67291
|
+
if (workflowResult === "deferred-paused") {
|
|
67292
|
+
if (await this.parkTaskAfterWorkflowStepPause(task.id)) {
|
|
67293
|
+
this.pausedAborted.delete(task.id);
|
|
67294
|
+
return;
|
|
67295
|
+
}
|
|
67296
|
+
if (this.pausedAborted.has(task.id)) {
|
|
67297
|
+
this.pausedAborted.delete(task.id);
|
|
67298
|
+
}
|
|
67299
|
+
return;
|
|
67300
|
+
}
|
|
66958
67301
|
if (!workflowResult.allPassed) {
|
|
66959
67302
|
if (workflowResult.revisionRequested) {
|
|
66960
67303
|
await this.handleWorkflowRevisionRequest(task, worktreePath, workflowResult.feedback, workflowResult.stepName);
|
|
@@ -66972,6 +67315,9 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
66972
67315
|
await this.store.logEntry(task.id, "Fast mode \u2014 pre-merge workflow steps skipped", void 0, this.currentRunContext);
|
|
66973
67316
|
}
|
|
66974
67317
|
await this.store.updateTask(task.id, { workflowStepRetries: void 0, taskDoneRetryCount: null });
|
|
67318
|
+
if (await this.shouldDeferCompletionForGlobalPause(task.id, "before in-review transition after step-session completion")) {
|
|
67319
|
+
return;
|
|
67320
|
+
}
|
|
66975
67321
|
await this.store.moveTask(task.id, "in-review");
|
|
66976
67322
|
this.clearCompletedTaskWatchdog(task.id);
|
|
66977
67323
|
await audit.database({ type: "task:move", target: task.id, metadata: { to: "in-review" } });
|
|
@@ -67324,6 +67670,9 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
67324
67670
|
this.pausedAborted.delete(task.id);
|
|
67325
67671
|
wasPaused = true;
|
|
67326
67672
|
if (await this.shouldFinalizeCompletedTask(task.id, taskDone)) {
|
|
67673
|
+
if (await this.shouldDeferCompletionForGlobalPause(task.id, "paused after completion")) {
|
|
67674
|
+
return;
|
|
67675
|
+
}
|
|
67327
67676
|
executorLog.log(`${task.id} paused after completion (graceful session exit) \u2014 finalizing to in-review`);
|
|
67328
67677
|
await this.store.logEntry(task.id, "Execution paused after completion \u2014 finalizing to in-review");
|
|
67329
67678
|
await this.persistTokenUsage(task.id);
|
|
@@ -67360,8 +67709,23 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
67360
67709
|
executorLog.log(`${task.id}: captured ${modifiedFiles.length} modified files`);
|
|
67361
67710
|
}
|
|
67362
67711
|
this.scheduleCompletedTaskWatchdog(task.id, "task completion");
|
|
67712
|
+
if (await this.shouldDeferCompletionForGlobalPause(task.id, "before workflow steps after task completion")) {
|
|
67713
|
+
return;
|
|
67714
|
+
}
|
|
67363
67715
|
if (executionMode !== "fast") {
|
|
67364
67716
|
const workflowResult = await this.runWorkflowSteps(task, worktreePath, settings);
|
|
67717
|
+
if (workflowResult === "deferred-paused") {
|
|
67718
|
+
if (await this.parkTaskAfterWorkflowStepPause(task.id)) {
|
|
67719
|
+
this.pausedAborted.delete(task.id);
|
|
67720
|
+
wasPaused = true;
|
|
67721
|
+
return;
|
|
67722
|
+
}
|
|
67723
|
+
if (this.pausedAborted.has(task.id)) {
|
|
67724
|
+
this.pausedAborted.delete(task.id);
|
|
67725
|
+
wasPaused = true;
|
|
67726
|
+
}
|
|
67727
|
+
return;
|
|
67728
|
+
}
|
|
67365
67729
|
if (!workflowResult.allPassed) {
|
|
67366
67730
|
if (workflowResult.revisionRequested) {
|
|
67367
67731
|
await this.handleWorkflowRevisionRequest(task, worktreePath, workflowResult.feedback, workflowResult.stepName);
|
|
@@ -67379,6 +67743,9 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
67379
67743
|
await this.store.logEntry(task.id, "Fast mode \u2014 pre-merge workflow steps skipped", void 0, this.currentRunContext);
|
|
67380
67744
|
}
|
|
67381
67745
|
await this.store.updateTask(task.id, { workflowStepRetries: void 0, taskDoneRetryCount: null });
|
|
67746
|
+
if (await this.shouldDeferCompletionForGlobalPause(task.id, "before in-review transition after task completion")) {
|
|
67747
|
+
return;
|
|
67748
|
+
}
|
|
67382
67749
|
await this.persistTokenUsage(task.id);
|
|
67383
67750
|
await this.store.moveTask(task.id, "in-review");
|
|
67384
67751
|
this.clearCompletedTaskWatchdog(task.id);
|
|
@@ -67503,8 +67870,23 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
67503
67870
|
executorLog.log(`${task.id}: captured ${modifiedFiles.length} modified files`);
|
|
67504
67871
|
}
|
|
67505
67872
|
this.scheduleCompletedTaskWatchdog(task.id, "task completion retry");
|
|
67873
|
+
if (await this.shouldDeferCompletionForGlobalPause(task.id, "before workflow steps after task completion retry")) {
|
|
67874
|
+
return;
|
|
67875
|
+
}
|
|
67506
67876
|
if (executionMode !== "fast") {
|
|
67507
67877
|
const workflowResult = await this.runWorkflowSteps(task, worktreePath, settings);
|
|
67878
|
+
if (workflowResult === "deferred-paused") {
|
|
67879
|
+
if (await this.parkTaskAfterWorkflowStepPause(task.id)) {
|
|
67880
|
+
this.pausedAborted.delete(task.id);
|
|
67881
|
+
wasPaused = true;
|
|
67882
|
+
return;
|
|
67883
|
+
}
|
|
67884
|
+
if (this.pausedAborted.has(task.id)) {
|
|
67885
|
+
this.pausedAborted.delete(task.id);
|
|
67886
|
+
wasPaused = true;
|
|
67887
|
+
}
|
|
67888
|
+
return;
|
|
67889
|
+
}
|
|
67508
67890
|
if (!workflowResult.allPassed) {
|
|
67509
67891
|
if (workflowResult.revisionRequested) {
|
|
67510
67892
|
await this.handleWorkflowRevisionRequest(task, worktreePath, workflowResult.feedback, workflowResult.stepName);
|
|
@@ -67518,6 +67900,9 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
67518
67900
|
await this.store.logEntry(task.id, "Fast mode \u2014 pre-merge workflow steps skipped", void 0, this.currentRunContext);
|
|
67519
67901
|
}
|
|
67520
67902
|
await this.store.updateTask(task.id, { workflowStepRetries: void 0, taskDoneRetryCount: null });
|
|
67903
|
+
if (await this.shouldDeferCompletionForGlobalPause(task.id, "before in-review transition after task completion retry")) {
|
|
67904
|
+
return;
|
|
67905
|
+
}
|
|
67521
67906
|
await this.persistTokenUsage(task.id);
|
|
67522
67907
|
await this.store.moveTask(task.id, "in-review");
|
|
67523
67908
|
this.clearCompletedTaskWatchdog(task.id);
|
|
@@ -67610,6 +67995,9 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
67610
67995
|
} else if (this.pausedAborted.has(task.id)) {
|
|
67611
67996
|
this.pausedAborted.delete(task.id);
|
|
67612
67997
|
if (await this.shouldFinalizeCompletedTask(task.id, taskDone)) {
|
|
67998
|
+
if (await this.shouldDeferCompletionForGlobalPause(task.id, "paused after completion")) {
|
|
67999
|
+
return;
|
|
68000
|
+
}
|
|
67613
68001
|
executorLog.log(`${task.id} paused after completion \u2014 finalizing to in-review`);
|
|
67614
68002
|
await this.store.logEntry(task.id, "Execution paused after completion \u2014 finalizing to in-review", void 0, this.currentRunContext);
|
|
67615
68003
|
await this.persistTokenUsage(task.id);
|
|
@@ -67968,22 +68356,28 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
67968
68356
|
if (params.summary) {
|
|
67969
68357
|
await store.updateTask(taskId, { summary: params.summary });
|
|
67970
68358
|
}
|
|
67971
|
-
await store.
|
|
68359
|
+
const settings = await store.getSettings();
|
|
68360
|
+
const hardPauseActive = Boolean(task.paused || settings.globalPause);
|
|
68361
|
+
if (hardPauseActive) {
|
|
68362
|
+
await store.updateTask(taskId, { status: null });
|
|
68363
|
+
} else {
|
|
68364
|
+
await store.updateTask(taskId, { paused: false, status: null });
|
|
68365
|
+
}
|
|
67972
68366
|
await store.logEntry(taskId, "Task marked done by agent");
|
|
67973
68367
|
const latestTask = await store.getTask(taskId);
|
|
67974
68368
|
let latestColumn = latestTask.column;
|
|
67975
68369
|
if (latestColumn === "todo") {
|
|
67976
68370
|
await store.logEntry(
|
|
67977
68371
|
taskId,
|
|
67978
|
-
"fn_task_done called while task was in todo \u2014 promoting to in-progress before completion handoff"
|
|
68372
|
+
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"
|
|
67979
68373
|
);
|
|
67980
68374
|
await store.moveTask(taskId, "in-progress");
|
|
67981
68375
|
latestColumn = "in-progress";
|
|
67982
68376
|
}
|
|
67983
|
-
if (latestColumn === "in-progress") {
|
|
68377
|
+
if (latestColumn === "in-progress" && !hardPauseActive) {
|
|
67984
68378
|
this.scheduleCompletedTaskWatchdog(taskId, "fn_task_done");
|
|
67985
68379
|
}
|
|
67986
|
-
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.";
|
|
68380
|
+
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.";
|
|
67987
68381
|
return {
|
|
67988
68382
|
content: [{ type: "text", text: successMessage }],
|
|
67989
68383
|
details: {}
|
|
@@ -68558,6 +68952,9 @@ ${failureFeedback}
|
|
|
68558
68952
|
await this.store.updateTask(task.id, { workflowStepResults: results });
|
|
68559
68953
|
continue;
|
|
68560
68954
|
}
|
|
68955
|
+
if (await this.shouldDeferWorkflowStepCompletion(task.id, `before workflow step '${ws.name}'`)) {
|
|
68956
|
+
return "deferred-paused";
|
|
68957
|
+
}
|
|
68561
68958
|
await this.store.logEntry(task.id, `[pre-merge] Starting workflow step: ${ws.name} (${stepMode} mode)`);
|
|
68562
68959
|
executorLog.log(`${task.id} \u2014 [pre-merge] running workflow step: ${ws.name} (${stepMode} mode)`);
|
|
68563
68960
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -68572,6 +68969,9 @@ ${failureFeedback}
|
|
|
68572
68969
|
await this.store.updateTask(task.id, { workflowStepResults: results });
|
|
68573
68970
|
try {
|
|
68574
68971
|
const result = stepMode === "script" ? await this.executeScriptWorkflowStep(task, ws, worktreePath, settings) : await this.executeWorkflowStep(task, ws, worktreePath, settings);
|
|
68972
|
+
if (await this.shouldDeferWorkflowStepCompletion(task.id, `workflow step '${ws.name}'`)) {
|
|
68973
|
+
return "deferred-paused";
|
|
68974
|
+
}
|
|
68575
68975
|
const completedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
68576
68976
|
if (result.success) {
|
|
68577
68977
|
await this.store.logEntry(task.id, `[timing] Workflow step '${ws.name}' completed in ${Date.now() - stepStartedAtMs}ms`);
|
|
@@ -68637,6 +69037,9 @@ ${failureFeedback}
|
|
|
68637
69037
|
};
|
|
68638
69038
|
}
|
|
68639
69039
|
} catch (err) {
|
|
69040
|
+
if (await this.shouldDeferWorkflowStepCompletion(task.id, `workflow step '${ws.name}'`)) {
|
|
69041
|
+
return "deferred-paused";
|
|
69042
|
+
}
|
|
68640
69043
|
const { message: errorMessage, detail: errorDetail, stack: errorStack } = formatError(err);
|
|
68641
69044
|
const completedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
68642
69045
|
await this.store.logEntry(
|
|
@@ -68802,6 +69205,7 @@ and show an appropriate message to the user.\`
|
|
|
68802
69205
|
task.id,
|
|
68803
69206
|
`Workflow step '${workflowStep.name}' using model: ${describeModel(session)}${useOverride && attemptLabel === "primary" ? " (workflow step override)" : ""}${attemptLabel === "fallback" ? " (fallback after timeout)" : ""}`
|
|
68804
69207
|
);
|
|
69208
|
+
this.activeWorkflowStepSessions.set(task.id, session);
|
|
68805
69209
|
let output = "";
|
|
68806
69210
|
session.subscribe((event) => {
|
|
68807
69211
|
if (event.type === "message_update") {
|
|
@@ -68874,6 +69278,10 @@ Review the work done in this worktree and evaluate it against the criteria in yo
|
|
|
68874
69278
|
return { success: false, error: errorMessage };
|
|
68875
69279
|
} finally {
|
|
68876
69280
|
if (timeoutHandle) clearTimeout(timeoutHandle);
|
|
69281
|
+
const activeWorkflowStepSession = this.activeWorkflowStepSessions.get(task.id);
|
|
69282
|
+
if (activeWorkflowStepSession === session) {
|
|
69283
|
+
this.activeWorkflowStepSessions.delete(task.id);
|
|
69284
|
+
}
|
|
68877
69285
|
void timedOut;
|
|
68878
69286
|
}
|
|
68879
69287
|
};
|
|
@@ -70617,6 +71025,15 @@ var init_scheduler = __esm({
|
|
|
70617
71025
|
schedulerLog.log(`Task ${task.id} is paused \u2014 skipping dispatch`);
|
|
70618
71026
|
continue;
|
|
70619
71027
|
}
|
|
71028
|
+
const latestSettings = await this.store.getSettings();
|
|
71029
|
+
if (latestSettings.globalPause) {
|
|
71030
|
+
schedulerLog.log(`Task ${task.id} dispatch aborted \u2014 globalPause became active mid-pass`);
|
|
71031
|
+
continue;
|
|
71032
|
+
}
|
|
71033
|
+
if (latestSettings.enginePaused) {
|
|
71034
|
+
schedulerLog.log(`Task ${task.id} dispatch aborted \u2014 enginePaused became active mid-pass`);
|
|
71035
|
+
continue;
|
|
71036
|
+
}
|
|
70620
71037
|
let effectiveNode = resolveEffectiveNode(freshTask, settings);
|
|
70621
71038
|
schedulerLog.log(`Task ${task.id} routed to node=${effectiveNode.nodeId ?? "local"} (source=${effectiveNode.source})`);
|
|
70622
71039
|
if (effectiveNode.nodeId !== void 0 && this.options.nodeHealthMonitor) {
|
|
@@ -75754,6 +76171,36 @@ function execCommand(command, options) {
|
|
|
75754
76171
|
});
|
|
75755
76172
|
});
|
|
75756
76173
|
}
|
|
76174
|
+
function isInProcessBackupCommand(command) {
|
|
76175
|
+
if (!command) return false;
|
|
76176
|
+
const trimmed = command.trim();
|
|
76177
|
+
if (!trimmed) return false;
|
|
76178
|
+
if (SHELL_METACHARACTERS_REGEX.test(trimmed)) return false;
|
|
76179
|
+
const tokens = trimmed.split(/\s+/).map((tok) => tok.toLowerCase());
|
|
76180
|
+
let cursor = 0;
|
|
76181
|
+
if (tokens[cursor] === "npx") {
|
|
76182
|
+
cursor += 1;
|
|
76183
|
+
while (cursor < tokens.length) {
|
|
76184
|
+
const tok = tokens[cursor];
|
|
76185
|
+
if (tok === void 0 || !tok.startsWith("-")) break;
|
|
76186
|
+
const takesValue = (tok === "-p" || tok === "--package") && cursor + 1 < tokens.length && tokens[cursor + 1] !== void 0 && !tokens[cursor + 1].startsWith("-");
|
|
76187
|
+
cursor += takesValue ? 2 : 1;
|
|
76188
|
+
}
|
|
76189
|
+
}
|
|
76190
|
+
const binary = tokens[cursor];
|
|
76191
|
+
if (!binary || !FUSION_BINARY_TOKENS.has(binary)) return false;
|
|
76192
|
+
cursor += 1;
|
|
76193
|
+
if (tokens[cursor] !== "backup") return false;
|
|
76194
|
+
cursor += 1;
|
|
76195
|
+
if (tokens[cursor] !== "--create") return false;
|
|
76196
|
+
cursor += 1;
|
|
76197
|
+
for (; cursor < tokens.length; cursor += 1) {
|
|
76198
|
+
const tok = tokens[cursor];
|
|
76199
|
+
if (!tok) continue;
|
|
76200
|
+
if (!tok.startsWith("-")) return false;
|
|
76201
|
+
}
|
|
76202
|
+
return true;
|
|
76203
|
+
}
|
|
75757
76204
|
async function createAiPromptExecutor(cwd) {
|
|
75758
76205
|
const disposeLog = createLogger2("cron-runner");
|
|
75759
76206
|
return async (prompt, modelProvider, modelId) => {
|
|
@@ -75793,7 +76240,7 @@ function truncateOutput(stdout, stderr) {
|
|
|
75793
76240
|
}
|
|
75794
76241
|
return combined;
|
|
75795
76242
|
}
|
|
75796
|
-
var log14, DEFAULT_TIMEOUT_MS6, MAX_BUFFER, MAX_OUTPUT_LENGTH, DEFAULT_POLL_INTERVAL_MS, MIN_POLL_INTERVAL_MS, CronRunner, AI_AUTOMATION_SYSTEM_PROMPT;
|
|
76243
|
+
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;
|
|
75797
76244
|
var init_cron_runner = __esm({
|
|
75798
76245
|
"../engine/src/cron-runner.ts"() {
|
|
75799
76246
|
"use strict";
|
|
@@ -75802,6 +76249,14 @@ var init_cron_runner = __esm({
|
|
|
75802
76249
|
init_shell_utils();
|
|
75803
76250
|
init_pi();
|
|
75804
76251
|
log14 = createLogger2("cron-runner");
|
|
76252
|
+
FUSION_BINARY_TOKENS = /* @__PURE__ */ new Set([
|
|
76253
|
+
"fn",
|
|
76254
|
+
"fusion",
|
|
76255
|
+
"runfusion",
|
|
76256
|
+
"runfusion.ai",
|
|
76257
|
+
"@runfusion/fusion"
|
|
76258
|
+
]);
|
|
76259
|
+
SHELL_METACHARACTERS_REGEX = /[&|;<>`$()]/;
|
|
75805
76260
|
DEFAULT_TIMEOUT_MS6 = 5 * 60 * 1e3;
|
|
75806
76261
|
MAX_BUFFER = 1024 * 1024;
|
|
75807
76262
|
MAX_OUTPUT_LENGTH = 10 * 1024;
|
|
@@ -75942,6 +76397,9 @@ var init_cron_runner = __esm({
|
|
|
75942
76397
|
*/
|
|
75943
76398
|
async executeLegacyCommand(schedule, startedAt) {
|
|
75944
76399
|
log14.log(`Executing ${schedule.name} (${schedule.id}): ${schedule.command}`);
|
|
76400
|
+
if (isInProcessBackupCommand(schedule.command)) {
|
|
76401
|
+
return this.executeBackupInProcess(schedule, startedAt);
|
|
76402
|
+
}
|
|
75945
76403
|
try {
|
|
75946
76404
|
const timeoutMs = schedule.timeoutMs ?? DEFAULT_TIMEOUT_MS6;
|
|
75947
76405
|
const { stdout, stderr } = await execCommand(schedule.command, {
|
|
@@ -75972,6 +76430,47 @@ var init_cron_runner = __esm({
|
|
|
75972
76430
|
};
|
|
75973
76431
|
}
|
|
75974
76432
|
}
|
|
76433
|
+
/**
|
|
76434
|
+
* Run an auto-backup schedule in-process via the engine's open TaskStore,
|
|
76435
|
+
* bypassing the shell-out that would otherwise invoke an outdated fusion
|
|
76436
|
+
* binary on PATH. See `isInProcessBackupCommand` for the matching contract.
|
|
76437
|
+
*/
|
|
76438
|
+
async executeBackupInProcess(schedule, startedAt) {
|
|
76439
|
+
const action = await this.runBackupActionInProcess();
|
|
76440
|
+
if (action.success) {
|
|
76441
|
+
log14.log(`\u2713 ${schedule.name} completed in-process`);
|
|
76442
|
+
} else {
|
|
76443
|
+
log14.warn(`\u2717 ${schedule.name} in-process backup ${action.error ? `threw: ${action.error}` : `reported failure: ${action.output}`}`);
|
|
76444
|
+
}
|
|
76445
|
+
return {
|
|
76446
|
+
success: action.success,
|
|
76447
|
+
output: action.output,
|
|
76448
|
+
error: action.error,
|
|
76449
|
+
startedAt,
|
|
76450
|
+
completedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
76451
|
+
};
|
|
76452
|
+
}
|
|
76453
|
+
/**
|
|
76454
|
+
* Shared in-process backup execution used by both the legacy-command path
|
|
76455
|
+
* and the command-step path. Returns the success/output/error tuple in
|
|
76456
|
+
* a shape that callers can wrap into either a run or a step result.
|
|
76457
|
+
*/
|
|
76458
|
+
async runBackupActionInProcess() {
|
|
76459
|
+
try {
|
|
76460
|
+
const { runBackupCommand: runBackupCommand2 } = await Promise.resolve().then(() => (init_src(), src_exports));
|
|
76461
|
+
const fusionDir = this.store.getFusionDir();
|
|
76462
|
+
const settings = await this.store.getSettings();
|
|
76463
|
+
const result = await runBackupCommand2(fusionDir, settings);
|
|
76464
|
+
return {
|
|
76465
|
+
success: result.success,
|
|
76466
|
+
output: truncateOutput(result.output ?? "", ""),
|
|
76467
|
+
error: result.success ? void 0 : result.output
|
|
76468
|
+
};
|
|
76469
|
+
} catch (err) {
|
|
76470
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
76471
|
+
return { success: false, output: "", error: message };
|
|
76472
|
+
}
|
|
76473
|
+
}
|
|
75975
76474
|
/**
|
|
75976
76475
|
* Execute multiple steps sequentially.
|
|
75977
76476
|
* Aggregates per-step results into an overall AutomationRunResult.
|
|
@@ -76060,6 +76559,19 @@ var init_cron_runner = __esm({
|
|
|
76060
76559
|
completedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
76061
76560
|
};
|
|
76062
76561
|
}
|
|
76562
|
+
if (isInProcessBackupCommand(step.command)) {
|
|
76563
|
+
const action = await this.runBackupActionInProcess();
|
|
76564
|
+
return {
|
|
76565
|
+
stepId: step.id,
|
|
76566
|
+
stepName: step.name,
|
|
76567
|
+
stepIndex,
|
|
76568
|
+
success: action.success,
|
|
76569
|
+
output: action.output,
|
|
76570
|
+
error: action.error,
|
|
76571
|
+
startedAt,
|
|
76572
|
+
completedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
76573
|
+
};
|
|
76574
|
+
}
|
|
76063
76575
|
try {
|
|
76064
76576
|
const { stdout, stderr } = await execCommand(step.command, {
|
|
76065
76577
|
timeout: timeoutMs,
|
|
@@ -76251,6 +76763,7 @@ var init_routine_runner = __esm({
|
|
|
76251
76763
|
"../engine/src/routine-runner.ts"() {
|
|
76252
76764
|
"use strict";
|
|
76253
76765
|
import_cron_parser4 = __toESM(require_dist2(), 1);
|
|
76766
|
+
init_cron_runner();
|
|
76254
76767
|
init_logger2();
|
|
76255
76768
|
init_shell_utils();
|
|
76256
76769
|
log15 = createLogger2("routine-runner");
|
|
@@ -76408,6 +76921,30 @@ var init_routine_runner = __esm({
|
|
|
76408
76921
|
return this.executeCommand(routine.command ?? "", routine.timeoutMs, startedAt);
|
|
76409
76922
|
}
|
|
76410
76923
|
async executeCommand(command, timeoutMs, startedAt) {
|
|
76924
|
+
if (isInProcessBackupCommand(command) && this.options.taskStore) {
|
|
76925
|
+
try {
|
|
76926
|
+
const { runBackupCommand: runBackupCommand2 } = await Promise.resolve().then(() => (init_src(), src_exports));
|
|
76927
|
+
const fusionDir = this.options.taskStore.getFusionDir();
|
|
76928
|
+
const settings = await this.options.taskStore.getSettings();
|
|
76929
|
+
const result = await runBackupCommand2(fusionDir, settings);
|
|
76930
|
+
return {
|
|
76931
|
+
success: result.success,
|
|
76932
|
+
output: truncateOutput2(result.output ?? "", ""),
|
|
76933
|
+
error: result.success ? void 0 : result.output,
|
|
76934
|
+
startedAt,
|
|
76935
|
+
completedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
76936
|
+
};
|
|
76937
|
+
} catch (err) {
|
|
76938
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
76939
|
+
return {
|
|
76940
|
+
success: false,
|
|
76941
|
+
output: "",
|
|
76942
|
+
error: message,
|
|
76943
|
+
startedAt,
|
|
76944
|
+
completedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
76945
|
+
};
|
|
76946
|
+
}
|
|
76947
|
+
}
|
|
76411
76948
|
try {
|
|
76412
76949
|
const { stdout, stderr } = await execAsync6(command, {
|
|
76413
76950
|
timeout: timeoutMs ?? DEFAULT_TIMEOUT_MS7,
|
|
@@ -77199,6 +77736,13 @@ var init_self_healing = __esm({
|
|
|
77199
77736
|
* stale in-progress/planning tasks that no longer have a live worker.
|
|
77200
77737
|
*/
|
|
77201
77738
|
async runStartupRecovery() {
|
|
77739
|
+
const settings = await this.store.getSettings();
|
|
77740
|
+
if (settings.globalPause || settings.enginePaused) {
|
|
77741
|
+
log16.log(
|
|
77742
|
+
`Startup recovery skipped \u2014 ${settings.globalPause ? "global pause" : "engine pause"} is active`
|
|
77743
|
+
);
|
|
77744
|
+
return;
|
|
77745
|
+
}
|
|
77202
77746
|
const steps = [
|
|
77203
77747
|
{ name: "no-progress-no-task-done", fn: () => this.recoverNoProgressNoTaskDoneFailures().then(() => void 0) },
|
|
77204
77748
|
{ name: "completed-tasks", fn: () => this.recoverCompletedTasks().then(() => void 0) },
|
|
@@ -77549,27 +78093,34 @@ var init_self_healing = __esm({
|
|
|
77549
78093
|
log16.error(`Maintenance batch 1 step "${fn.name}" failed: ${stepErr instanceof Error ? stepErr.message : String(stepErr)}`);
|
|
77550
78094
|
}
|
|
77551
78095
|
}
|
|
77552
|
-
const
|
|
77553
|
-
|
|
77554
|
-
|
|
77555
|
-
|
|
77556
|
-
|
|
77557
|
-
|
|
77558
|
-
|
|
77559
|
-
|
|
77560
|
-
|
|
77561
|
-
|
|
77562
|
-
|
|
77563
|
-
|
|
77564
|
-
|
|
77565
|
-
|
|
77566
|
-
|
|
77567
|
-
|
|
77568
|
-
|
|
77569
|
-
|
|
77570
|
-
|
|
77571
|
-
|
|
77572
|
-
|
|
78096
|
+
const recoverySettings = await this.store.getSettings();
|
|
78097
|
+
if (recoverySettings.globalPause || recoverySettings.enginePaused) {
|
|
78098
|
+
log16.log(
|
|
78099
|
+
`Maintenance batch 2 skipped \u2014 ${recoverySettings.globalPause ? "global pause" : "engine pause"} is active`
|
|
78100
|
+
);
|
|
78101
|
+
} else {
|
|
78102
|
+
const batch2Fns = [
|
|
78103
|
+
{ name: "recover-completed-tasks", fn: () => this.recoverCompletedTasks() },
|
|
78104
|
+
{ name: "recover-stale-incomplete-review", fn: () => this.recoverStaleIncompleteReviewTasks() },
|
|
78105
|
+
{ name: "recover-failed-pre-merge-steps", fn: () => this.recoverReviewTasksWithFailedPreMergeSteps() },
|
|
78106
|
+
{ name: "recover-interrupted-merging", fn: () => this.recoverInterruptedMergingTasks() },
|
|
78107
|
+
{ name: "recover-mergeable-review", fn: () => this.recoverMergeableReviewTasks() },
|
|
78108
|
+
{ name: "recover-merged-review", fn: () => this.recoverMergedReviewTasks() },
|
|
78109
|
+
{ name: "recover-misclassified-failures", fn: () => this.recoverMisclassifiedFailures() },
|
|
78110
|
+
{ name: "recover-no-progress-no-task-done", fn: () => this.recoverNoProgressNoTaskDoneFailures() },
|
|
78111
|
+
{ name: "recover-partial-progress-no-task-done", fn: () => this.recoverPartialProgressNoTaskDoneFailures() },
|
|
78112
|
+
{ name: "recover-orphaned-executions", fn: () => this.recoverOrphanedExecutions() },
|
|
78113
|
+
{ name: "recover-approved-triage", fn: () => this.recoverApprovedTriageTasks() },
|
|
78114
|
+
{ name: "recover-orphaned-planning", fn: () => this.recoverOrphanedPlanningTasks() },
|
|
78115
|
+
{ name: "recover-ghost-review", fn: () => this.recoverGhostReviewTasks() }
|
|
78116
|
+
];
|
|
78117
|
+
for (const fn of batch2Fns) {
|
|
78118
|
+
try {
|
|
78119
|
+
await fn.fn();
|
|
78120
|
+
log16.log(`Maintenance batch 2 step "${fn.name}" succeeded`);
|
|
78121
|
+
} catch (stepErr) {
|
|
78122
|
+
log16.error(`Maintenance batch 2 step "${fn.name}" failed: ${stepErr instanceof Error ? stepErr.message : String(stepErr)}`);
|
|
78123
|
+
}
|
|
77573
78124
|
}
|
|
77574
78125
|
}
|
|
77575
78126
|
const batch3Fns = [
|
|
@@ -79302,6 +79853,10 @@ var init_in_process_runtime = __esm({
|
|
|
79302
79853
|
* before `start()` via `setMergeEnqueuer`.
|
|
79303
79854
|
*/
|
|
79304
79855
|
mergeEnqueuer;
|
|
79856
|
+
/** Tracks whether startup recovery was intentionally deferred due to pause state. */
|
|
79857
|
+
startupRecoveryDeferred = false;
|
|
79858
|
+
/** Prevent duplicate unpause recovery dispatches from racing each other. */
|
|
79859
|
+
resumeAfterUnpauseRunning = false;
|
|
79305
79860
|
/**
|
|
79306
79861
|
* Start the runtime and initialize all subsystems.
|
|
79307
79862
|
*
|
|
@@ -79335,7 +79890,7 @@ var init_in_process_runtime = __esm({
|
|
|
79335
79890
|
runtimeLog.log(`TaskStore initialized for project ${this.config.projectId}`);
|
|
79336
79891
|
}
|
|
79337
79892
|
this.messageStore = new MessageStoreClass(this.taskStore.getDatabase());
|
|
79338
|
-
this.pluginStore = new PluginStoreClass(this.
|
|
79893
|
+
this.pluginStore = new PluginStoreClass(this.config.workingDirectory);
|
|
79339
79894
|
await this.pluginStore.init();
|
|
79340
79895
|
this.pluginLoader = new PluginLoaderClass({
|
|
79341
79896
|
pluginStore: this.pluginStore,
|
|
@@ -79727,11 +80282,16 @@ var init_in_process_runtime = __esm({
|
|
|
79727
80282
|
this.selfHealingManager.start();
|
|
79728
80283
|
this.stuckTaskDetector.start();
|
|
79729
80284
|
this.setupEventForwarding();
|
|
79730
|
-
await this.
|
|
79731
|
-
|
|
79732
|
-
|
|
79733
|
-
runtimeLog.
|
|
79734
|
-
|
|
80285
|
+
const startupSettings = await this.taskStore.getSettings();
|
|
80286
|
+
if (startupSettings.globalPause || startupSettings.enginePaused) {
|
|
80287
|
+
this.startupRecoveryDeferred = true;
|
|
80288
|
+
runtimeLog.log(
|
|
80289
|
+
`Startup recovery deferred \u2014 ${startupSettings.globalPause ? "global pause" : "engine pause"} is active`
|
|
80290
|
+
);
|
|
80291
|
+
} else {
|
|
80292
|
+
this.startupRecoveryDeferred = false;
|
|
80293
|
+
await this.resumeStartupRecoverySequence();
|
|
80294
|
+
}
|
|
79735
80295
|
this.scheduler.start();
|
|
79736
80296
|
this.triageProcessor?.start();
|
|
79737
80297
|
this.missionExecutionLoop = missionExecutionLoop;
|
|
@@ -79897,6 +80457,45 @@ var init_in_process_runtime = __esm({
|
|
|
79897
80457
|
setMergeEnqueuer(enqueueMerge) {
|
|
79898
80458
|
this.mergeEnqueuer = enqueueMerge;
|
|
79899
80459
|
}
|
|
80460
|
+
/**
|
|
80461
|
+
* Resume executor/self-healing activity after an unpause transition.
|
|
80462
|
+
*
|
|
80463
|
+
* When startup recovery had been deferred, this replays the original startup
|
|
80464
|
+
* ordering so orphan resume and self-healing cannot race each other.
|
|
80465
|
+
*/
|
|
80466
|
+
async resumeAfterUnpause() {
|
|
80467
|
+
if (!this.taskStore || !this.executor || !this.selfHealingManager) {
|
|
80468
|
+
return;
|
|
80469
|
+
}
|
|
80470
|
+
if (this.resumeAfterUnpauseRunning) {
|
|
80471
|
+
return;
|
|
80472
|
+
}
|
|
80473
|
+
this.resumeAfterUnpauseRunning = true;
|
|
80474
|
+
try {
|
|
80475
|
+
const settings = await this.taskStore.getSettings();
|
|
80476
|
+
if (settings.globalPause || settings.enginePaused) {
|
|
80477
|
+
runtimeLog.log(
|
|
80478
|
+
`Unpause recovery still blocked \u2014 ${settings.globalPause ? "global pause" : "engine pause"} remains active`
|
|
80479
|
+
);
|
|
80480
|
+
return;
|
|
80481
|
+
}
|
|
80482
|
+
if (this.startupRecoveryDeferred) {
|
|
80483
|
+
await this.resumeStartupRecoverySequence();
|
|
80484
|
+
this.startupRecoveryDeferred = false;
|
|
80485
|
+
return;
|
|
80486
|
+
}
|
|
80487
|
+
await this.executor.resumeOrphaned();
|
|
80488
|
+
} finally {
|
|
80489
|
+
this.resumeAfterUnpauseRunning = false;
|
|
80490
|
+
}
|
|
80491
|
+
}
|
|
80492
|
+
async resumeStartupRecoverySequence() {
|
|
80493
|
+
await this.selfHealingManager.recoverNoProgressNoTaskDoneFailures();
|
|
80494
|
+
await this.executor.resumeOrphaned();
|
|
80495
|
+
void this.selfHealingManager.runStartupRecovery().catch((err) => {
|
|
80496
|
+
runtimeLog.error("Self-healing startup recovery failed:", err);
|
|
80497
|
+
});
|
|
80498
|
+
}
|
|
79900
80499
|
/**
|
|
79901
80500
|
* Get the project's TaskStore instance.
|
|
79902
80501
|
* @throws Error if runtime has not been started
|
|
@@ -83736,13 +84335,13 @@ ${detail}`
|
|
|
83736
84335
|
if (prev.globalPause && !s.globalPause) {
|
|
83737
84336
|
runtimeLog.log("Global unpause \u2014 resuming agentic activity");
|
|
83738
84337
|
try {
|
|
83739
|
-
const
|
|
83740
|
-
|
|
83741
|
-
(err) => runtimeLog.error("Failed to resume
|
|
84338
|
+
const runtime = this.runtime;
|
|
84339
|
+
runtime.resumeAfterUnpause?.().catch(
|
|
84340
|
+
(err) => runtimeLog.error("Failed to resume agentic activity on unpause:", err)
|
|
83742
84341
|
);
|
|
83743
84342
|
} catch (err) {
|
|
83744
84343
|
runtimeLog.warn(
|
|
83745
|
-
`Global unpause: failed to dispatch
|
|
84344
|
+
`Global unpause: failed to dispatch resumeAfterUnpause: ${err instanceof Error ? err.message : String(err)}`
|
|
83746
84345
|
);
|
|
83747
84346
|
}
|
|
83748
84347
|
if (s.autoMerge) {
|
|
@@ -83766,13 +84365,13 @@ ${detail}`
|
|
|
83766
84365
|
if (prev.enginePaused && !s.enginePaused) {
|
|
83767
84366
|
runtimeLog.log("Engine unpaused \u2014 resuming agentic activity");
|
|
83768
84367
|
try {
|
|
83769
|
-
const
|
|
83770
|
-
|
|
83771
|
-
(err) => runtimeLog.error("Failed to resume
|
|
84368
|
+
const runtime = this.runtime;
|
|
84369
|
+
runtime.resumeAfterUnpause?.().catch(
|
|
84370
|
+
(err) => runtimeLog.error("Failed to resume agentic activity on engine unpause:", err)
|
|
83772
84371
|
);
|
|
83773
84372
|
} catch (err) {
|
|
83774
84373
|
runtimeLog.warn(
|
|
83775
|
-
`Engine unpause: failed to dispatch
|
|
84374
|
+
`Engine unpause: failed to dispatch resumeAfterUnpause: ${err instanceof Error ? err.message : String(err)}`
|
|
83776
84375
|
);
|
|
83777
84376
|
}
|
|
83778
84377
|
if (s.autoMerge) {
|
|
@@ -88894,7 +89493,7 @@ var init_src3 = __esm({
|
|
|
88894
89493
|
});
|
|
88895
89494
|
|
|
88896
89495
|
// ../../plugins/fusion-plugin-hermes-runtime/dist/cli-spawn.js
|
|
88897
|
-
import { spawn as spawn6, spawnSync } from "node:child_process";
|
|
89496
|
+
import { spawn as spawn6, spawnSync as spawnSync2 } from "node:child_process";
|
|
88898
89497
|
import os2 from "node:os";
|
|
88899
89498
|
import path, { sep as PATH_SEP } from "node:path";
|
|
88900
89499
|
function resolveBinaryForSpawn(binary) {
|
|
@@ -88907,7 +89506,7 @@ function resolveBinaryForSpawn(binary) {
|
|
|
88907
89506
|
if (cached)
|
|
88908
89507
|
return cached;
|
|
88909
89508
|
try {
|
|
88910
|
-
const result =
|
|
89509
|
+
const result = spawnSync2("where", [binary], { encoding: "utf-8" });
|
|
88911
89510
|
if (result.status === 0) {
|
|
88912
89511
|
const first = (result.stdout ?? "").trim().split(/\r?\n/)[0];
|
|
88913
89512
|
if (first?.length) {
|
|
@@ -123889,7 +124488,7 @@ var require_commonjs4 = __commonJS({
|
|
|
123889
124488
|
var node_url_1 = __require("node:url");
|
|
123890
124489
|
var fs_1 = __require("fs");
|
|
123891
124490
|
var actualFS = __importStar(__require("node:fs"));
|
|
123892
|
-
var
|
|
124491
|
+
var realpathSync3 = fs_1.realpathSync.native;
|
|
123893
124492
|
var promises_1 = __require("node:fs/promises");
|
|
123894
124493
|
var minipass_1 = require_commonjs3();
|
|
123895
124494
|
var defaultFS = {
|
|
@@ -123897,7 +124496,7 @@ var require_commonjs4 = __commonJS({
|
|
|
123897
124496
|
readdir: fs_1.readdir,
|
|
123898
124497
|
readdirSync: fs_1.readdirSync,
|
|
123899
124498
|
readlinkSync: fs_1.readlinkSync,
|
|
123900
|
-
realpathSync:
|
|
124499
|
+
realpathSync: realpathSync3,
|
|
123901
124500
|
promises: {
|
|
123902
124501
|
lstat: promises_1.lstat,
|
|
123903
124502
|
readdir: promises_1.readdir,
|
|
@@ -136670,7 +137269,7 @@ Rules:
|
|
|
136670
137269
|
// ../dashboard/src/routes/register-agent-import-export-generation-routes.ts
|
|
136671
137270
|
import { createWriteStream } from "node:fs";
|
|
136672
137271
|
import * as fsPromises2 from "node:fs/promises";
|
|
136673
|
-
import { tmpdir as
|
|
137272
|
+
import { tmpdir as tmpdir4 } from "node:os";
|
|
136674
137273
|
import { join as join45, resolve as resolve22 } from "node:path";
|
|
136675
137274
|
import { Readable } from "node:stream";
|
|
136676
137275
|
import { pipeline as streamPipeline } from "node:stream/promises";
|
|
@@ -136712,7 +137311,7 @@ function registerAgentImportExportRoutes(ctx) {
|
|
|
136712
137311
|
} else if (typeof outputDir === "string") {
|
|
136713
137312
|
throw badRequest("outputDir cannot be empty");
|
|
136714
137313
|
} else {
|
|
136715
|
-
resolvedOutputDir = await mkdtemp(join45(
|
|
137314
|
+
resolvedOutputDir = await mkdtemp(join45(tmpdir4(), "fusion-agent-export-"));
|
|
136716
137315
|
}
|
|
136717
137316
|
const result = await exportAgentsToDirectory2(agentsToExport, resolvedOutputDir, {
|
|
136718
137317
|
companyName: typeof companyName === "string" ? companyName : void 0,
|
|
@@ -137027,7 +137626,7 @@ ${body}`;
|
|
|
137027
137626
|
const archiveUrl = `https://github.com/${repoOwner}/${repoName}/archive/refs/heads/main.tar.gz`;
|
|
137028
137627
|
let tempDir = null;
|
|
137029
137628
|
try {
|
|
137030
|
-
tempDir = await mkdtemp(join45(
|
|
137629
|
+
tempDir = await mkdtemp(join45(tmpdir4(), `fn-agent-import-${importCompanySlug}-`));
|
|
137031
137630
|
const archivePath = join45(tempDir, "archive.tar.gz");
|
|
137032
137631
|
const downloadController = new AbortController();
|
|
137033
137632
|
const downloadTimeout = setTimeout(() => downloadController.abort(), 3e4);
|
|
@@ -141554,6 +142153,21 @@ ${stderr}`;
|
|
|
141554
142153
|
});
|
|
141555
142154
|
});
|
|
141556
142155
|
}
|
|
142156
|
+
function buildSkippedStatusPayload(expectedVersion) {
|
|
142157
|
+
return {
|
|
142158
|
+
binary: {
|
|
142159
|
+
installed: false,
|
|
142160
|
+
invocation: FN_INSTALL_NPM
|
|
142161
|
+
},
|
|
142162
|
+
expectedVersion,
|
|
142163
|
+
state: "skipped",
|
|
142164
|
+
install: {
|
|
142165
|
+
npm: FN_INSTALL_NPM,
|
|
142166
|
+
curl: FN_INSTALL_CURL,
|
|
142167
|
+
package: FN_NPM_PACKAGE
|
|
142168
|
+
}
|
|
142169
|
+
};
|
|
142170
|
+
}
|
|
141557
142171
|
var INSTALL_TIMEOUT_MS, MAX_OUTPUT_BYTES2, registerFnBinaryRoutes;
|
|
141558
142172
|
var init_register_fn_binary_routes = __esm({
|
|
141559
142173
|
"../dashboard/src/routes/register-fn-binary-routes.ts"() {
|
|
@@ -141564,11 +142178,23 @@ var init_register_fn_binary_routes = __esm({
|
|
|
141564
142178
|
INSTALL_TIMEOUT_MS = 18e4;
|
|
141565
142179
|
MAX_OUTPUT_BYTES2 = 64 * 1024;
|
|
141566
142180
|
registerFnBinaryRoutes = (ctx) => {
|
|
141567
|
-
const { router, rethrowAsApiError: rethrowAsApiError8 } = ctx;
|
|
142181
|
+
const { router, rethrowAsApiError: rethrowAsApiError8, store } = ctx;
|
|
142182
|
+
async function isCheckEnabled() {
|
|
142183
|
+
try {
|
|
142184
|
+
const settings = await store.getSettings();
|
|
142185
|
+
return settings.fnBinaryCheckEnabled !== false;
|
|
142186
|
+
} catch {
|
|
142187
|
+
return true;
|
|
142188
|
+
}
|
|
142189
|
+
}
|
|
141568
142190
|
router.get("/system/fn-binary/status", async (_req, res) => {
|
|
141569
142191
|
try {
|
|
141570
|
-
const binary = await detectFnBinary();
|
|
141571
142192
|
const expectedVersion = getCliPackageVersion();
|
|
142193
|
+
if (!await isCheckEnabled()) {
|
|
142194
|
+
res.json(buildSkippedStatusPayload(expectedVersion));
|
|
142195
|
+
return;
|
|
142196
|
+
}
|
|
142197
|
+
const binary = await detectFnBinary();
|
|
141572
142198
|
res.json(buildStatusPayload(binary, expectedVersion));
|
|
141573
142199
|
} catch (err) {
|
|
141574
142200
|
if (err instanceof ApiError) throw err;
|
|
@@ -141577,6 +142203,13 @@ var init_register_fn_binary_routes = __esm({
|
|
|
141577
142203
|
});
|
|
141578
142204
|
router.post("/system/fn-binary/install", async (_req, res) => {
|
|
141579
142205
|
try {
|
|
142206
|
+
if (!await isCheckEnabled()) {
|
|
142207
|
+
throw new ApiError(
|
|
142208
|
+
409,
|
|
142209
|
+
"fn-binary checks are disabled in global settings (fnBinaryCheckEnabled=false). Re-enable them to install via the dashboard.",
|
|
142210
|
+
{ code: "FN_BINARY_CHECK_DISABLED" }
|
|
142211
|
+
);
|
|
142212
|
+
}
|
|
141580
142213
|
const installResult = await runNpmInstall();
|
|
141581
142214
|
const binary = await detectFnBinary();
|
|
141582
142215
|
const expectedVersion = getCliPackageVersion();
|
|
@@ -149386,6 +150019,10 @@ Description: ${step.description}`
|
|
|
149386
150019
|
const slots = options?.pluginLoader?.getPluginUiSlots() ?? [];
|
|
149387
150020
|
res.json(slots);
|
|
149388
150021
|
});
|
|
150022
|
+
router.get("/plugins/dashboard-views", async (_req, res) => {
|
|
150023
|
+
const views = options?.pluginLoader?.getPluginDashboardViews() ?? [];
|
|
150024
|
+
res.json(views);
|
|
150025
|
+
});
|
|
149389
150026
|
router.get("/plugins/runtimes", async (_req, res) => {
|
|
149390
150027
|
const runtimes2 = options?.pluginLoader?.getPluginRuntimes() ?? [];
|
|
149391
150028
|
const installed = runtimes2.map(({ pluginId, runtime }) => ({
|
|
@@ -155723,6 +156360,43 @@ data: ${JSON.stringify(entry)}
|
|
|
155723
156360
|
scopedStore.off("agent:log", onAgentLog);
|
|
155724
156361
|
});
|
|
155725
156362
|
});
|
|
156363
|
+
app.get("/api/agents/:id/runs/:runId/logs/stream", async (req, res) => {
|
|
156364
|
+
const agentId = req.params.id;
|
|
156365
|
+
const runId = req.params.runId;
|
|
156366
|
+
const projectId = typeof req.query.projectId === "string" ? req.query.projectId : void 0;
|
|
156367
|
+
res.setHeader("Content-Type", "text/event-stream");
|
|
156368
|
+
res.setHeader("Cache-Control", "no-cache");
|
|
156369
|
+
res.setHeader("Connection", "keep-alive");
|
|
156370
|
+
res.setHeader("X-Accel-Buffering", "no");
|
|
156371
|
+
res.flushHeaders();
|
|
156372
|
+
res.write(": connected\n\n");
|
|
156373
|
+
const engineManager = options?.engineManager;
|
|
156374
|
+
const engine2 = engineManager && projectId ? engineManager.getEngine(projectId) : options?.engine;
|
|
156375
|
+
const agentStore = engine2?.getAgentStore();
|
|
156376
|
+
if (!agentStore) {
|
|
156377
|
+
res.write(`event: error
|
|
156378
|
+
data: ${JSON.stringify({ message: "No active engine for project" })}
|
|
156379
|
+
|
|
156380
|
+
`);
|
|
156381
|
+
res.end();
|
|
156382
|
+
return;
|
|
156383
|
+
}
|
|
156384
|
+
const onRunLog = (eventAgentId, eventRunId, entry) => {
|
|
156385
|
+
if (eventAgentId !== agentId || eventRunId !== runId) return;
|
|
156386
|
+
res.write(`event: agent:log
|
|
156387
|
+
data: ${JSON.stringify(entry)}
|
|
156388
|
+
|
|
156389
|
+
`);
|
|
156390
|
+
};
|
|
156391
|
+
agentStore.on("run:log", onRunLog);
|
|
156392
|
+
const heartbeat = setInterval(() => {
|
|
156393
|
+
res.write(": heartbeat\n\n");
|
|
156394
|
+
}, 3e4);
|
|
156395
|
+
req.on("close", () => {
|
|
156396
|
+
clearInterval(heartbeat);
|
|
156397
|
+
agentStore.off("run:log", onRunLog);
|
|
156398
|
+
});
|
|
156399
|
+
});
|
|
155726
156400
|
app.get("/api/terminal/sessions/:id/stream", rateLimit(RATE_LIMITS.sse), (req, res) => {
|
|
155727
156401
|
const sessionId = Array.isArray(req.params.id) ? req.params.id[0] : req.params.id;
|
|
155728
156402
|
res.setHeader("Content-Type", "text/event-stream");
|
|
@@ -157556,7 +158230,8 @@ async function clearDefaultProject(globalDir) {
|
|
|
157556
158230
|
await globalStore.updateSettings(rest);
|
|
157557
158231
|
}
|
|
157558
158232
|
async function detectProjectFromCwd(cwd, central) {
|
|
157559
|
-
|
|
158233
|
+
const startDir = resolve27(cwd);
|
|
158234
|
+
let currentDir = startDir;
|
|
157560
158235
|
while (true) {
|
|
157561
158236
|
const kbPath = resolve27(currentDir, ".fusion", "fusion.db");
|
|
157562
158237
|
if (isValidSqliteDatabaseFile(kbPath)) {
|
|
@@ -157564,11 +158239,13 @@ async function detectProjectFromCwd(cwd, central) {
|
|
|
157564
158239
|
if (project) {
|
|
157565
158240
|
return project;
|
|
157566
158241
|
}
|
|
157567
|
-
|
|
157568
|
-
|
|
157569
|
-
|
|
157570
|
-
|
|
157571
|
-
|
|
158242
|
+
if (currentDir === startDir) {
|
|
158243
|
+
return {
|
|
158244
|
+
id: "",
|
|
158245
|
+
name: basename16(currentDir) || "current-project",
|
|
158246
|
+
path: currentDir
|
|
158247
|
+
};
|
|
158248
|
+
}
|
|
157572
158249
|
}
|
|
157573
158250
|
const parentDir = dirname21(currentDir);
|
|
157574
158251
|
if (parentDir === currentDir) {
|
|
@@ -158348,7 +159025,7 @@ var app_exports = {};
|
|
|
158348
159025
|
__export(app_exports, {
|
|
158349
159026
|
DashboardApp: () => DashboardApp
|
|
158350
159027
|
});
|
|
158351
|
-
import { useState as useState2, useSyncExternalStore, useCallback as useCallback2, useEffect as useEffect2 } from "react";
|
|
159028
|
+
import { useState as useState2, useSyncExternalStore, useCallback as useCallback2, useEffect as useEffect2, useRef } from "react";
|
|
158352
159029
|
import { Box, Text, useInput, useApp, useStdout } from "ink";
|
|
158353
159030
|
import Spinner from "ink-spinner";
|
|
158354
159031
|
import TextInput from "ink-text-input";
|
|
@@ -159130,7 +159807,8 @@ function formatLogTime(iso) {
|
|
|
159130
159807
|
function TaskDetailScreen({
|
|
159131
159808
|
task,
|
|
159132
159809
|
projectPath,
|
|
159133
|
-
interactiveData
|
|
159810
|
+
interactiveData,
|
|
159811
|
+
controller
|
|
159134
159812
|
}) {
|
|
159135
159813
|
const { stdout } = useStdout();
|
|
159136
159814
|
const cols = stdout?.columns ?? 80;
|
|
@@ -159195,6 +159873,27 @@ function TaskDetailScreen({
|
|
|
159195
159873
|
useEffect2(() => {
|
|
159196
159874
|
if (autoFollow) setLogScrollOffset(0);
|
|
159197
159875
|
}, [autoFollow, logCount]);
|
|
159876
|
+
const WHEEL_STEP = 3;
|
|
159877
|
+
const logCountRef = useRef(logCount);
|
|
159878
|
+
const logPaneRowsRef = useRef(logPaneRows);
|
|
159879
|
+
logCountRef.current = logCount;
|
|
159880
|
+
logPaneRowsRef.current = logPaneRows;
|
|
159881
|
+
useEffect2(() => {
|
|
159882
|
+
return controller.onWheel((dir2) => {
|
|
159883
|
+
const maxOffset = Math.max(0, logCountRef.current - logPaneRowsRef.current);
|
|
159884
|
+
if (maxOffset === 0) return;
|
|
159885
|
+
if (dir2 === "up") {
|
|
159886
|
+
setAutoFollow(false);
|
|
159887
|
+
setLogScrollOffset((o) => Math.min(maxOffset, o + WHEEL_STEP));
|
|
159888
|
+
} else {
|
|
159889
|
+
setLogScrollOffset((o) => {
|
|
159890
|
+
const next = Math.max(0, o - WHEEL_STEP);
|
|
159891
|
+
if (next === 0) setAutoFollow(true);
|
|
159892
|
+
return next;
|
|
159893
|
+
});
|
|
159894
|
+
}
|
|
159895
|
+
});
|
|
159896
|
+
}, [controller]);
|
|
159198
159897
|
useInput((input, key) => {
|
|
159199
159898
|
if (detail && detail !== "unavailable" && detail.recentLogs.length > 0) {
|
|
159200
159899
|
const maxOffset = Math.max(0, detail.recentLogs.length - logPaneRows);
|
|
@@ -159565,7 +160264,8 @@ function BoardView({ state, controller }) {
|
|
|
159565
160264
|
{
|
|
159566
160265
|
task: selectedTask,
|
|
159567
160266
|
projectPath: selectedProject?.path ?? null,
|
|
159568
|
-
interactiveData: state.interactiveData
|
|
160267
|
+
interactiveData: state.interactiveData,
|
|
160268
|
+
controller
|
|
159569
160269
|
}
|
|
159570
160270
|
) }) : tasksState.loading ? /* @__PURE__ */ jsxs(Box, { justifyContent: "center", alignItems: "center", flexGrow: 1, gap: 1, children: [
|
|
159571
160271
|
/* @__PURE__ */ jsx(Text, { color: "white", children: /* @__PURE__ */ jsx(Spinner, { type: "dots" }) }),
|
|
@@ -160387,7 +161087,7 @@ function PushModal({
|
|
|
160387
161087
|
}
|
|
160388
161088
|
);
|
|
160389
161089
|
}
|
|
160390
|
-
function GitView({ state }) {
|
|
161090
|
+
function GitView({ state, controller }) {
|
|
160391
161091
|
const { stdout } = useStdout();
|
|
160392
161092
|
const cols = stdout?.columns ?? 80;
|
|
160393
161093
|
const data = state.interactiveData;
|
|
@@ -160575,6 +161275,23 @@ function GitView({ state }) {
|
|
|
160575
161275
|
}
|
|
160576
161276
|
}
|
|
160577
161277
|
});
|
|
161278
|
+
const gitWheelRef = useRef({ activePane, commits, branches, worktrees });
|
|
161279
|
+
gitWheelRef.current = { activePane, commits, branches, worktrees };
|
|
161280
|
+
useEffect2(() => {
|
|
161281
|
+
if (state.interactiveView !== "git") return;
|
|
161282
|
+
return controller.onWheel((dir2) => {
|
|
161283
|
+
const { activePane: pane, commits: cs, branches: bs, worktrees: ws } = gitWheelRef.current;
|
|
161284
|
+
const STEP = 3;
|
|
161285
|
+
const delta = dir2 === "up" ? -STEP : STEP;
|
|
161286
|
+
if (pane === "commits") {
|
|
161287
|
+
setCommitIndex((i) => Math.max(0, Math.min(cs.length - 1, i + delta)));
|
|
161288
|
+
} else if (pane === "branches") {
|
|
161289
|
+
setBranchIndex((i) => Math.max(0, Math.min(bs.length - 1, i + delta)));
|
|
161290
|
+
} else if (pane === "worktrees") {
|
|
161291
|
+
setWorktreeIndex((i) => Math.max(0, Math.min(ws.length - 1, i + delta)));
|
|
161292
|
+
}
|
|
161293
|
+
});
|
|
161294
|
+
}, [controller, state.interactiveView]);
|
|
160578
161295
|
const isNarrow = cols < NARROW_THRESHOLD;
|
|
160579
161296
|
const leftWidth = Math.max(24, Math.floor(cols * 0.35));
|
|
160580
161297
|
const rightWidth = cols - leftWidth - 1;
|
|
@@ -160916,7 +161633,7 @@ function entriesToNodes(entries, depth) {
|
|
|
160916
161633
|
const files = filtered.filter((e) => !e.isDirectory).sort((a, b) => a.name.localeCompare(b.name));
|
|
160917
161634
|
return [...dirs, ...files].map((e) => ({ entry: e, depth, expanded: false, children: void 0 }));
|
|
160918
161635
|
}
|
|
160919
|
-
function FilesView({ state }) {
|
|
161636
|
+
function FilesView({ state, controller }) {
|
|
160920
161637
|
const { stdout } = useStdout();
|
|
160921
161638
|
const cols = stdout?.columns ?? 80;
|
|
160922
161639
|
const data = state.interactiveData;
|
|
@@ -161124,6 +161841,23 @@ function FilesView({ state }) {
|
|
|
161124
161841
|
}
|
|
161125
161842
|
}
|
|
161126
161843
|
}, { isActive: state.interactiveView === "files" });
|
|
161844
|
+
const filesWheelRef = useRef({ focusedPane, flatNodes, previewResult, previewHeight });
|
|
161845
|
+
filesWheelRef.current = { focusedPane, flatNodes, previewResult, previewHeight };
|
|
161846
|
+
useEffect2(() => {
|
|
161847
|
+
if (state.interactiveView !== "files") return;
|
|
161848
|
+
return controller.onWheel((dir2) => {
|
|
161849
|
+
const { focusedPane: pane, flatNodes: nodes, previewResult: pr, previewHeight: ph } = filesWheelRef.current;
|
|
161850
|
+
const STEP = 3;
|
|
161851
|
+
const delta = dir2 === "up" ? -STEP : STEP;
|
|
161852
|
+
if (pane === "tree") {
|
|
161853
|
+
setSelectedIndex((i) => Math.max(0, Math.min(nodes.length - 1, i + delta)));
|
|
161854
|
+
} else {
|
|
161855
|
+
const lineCount = pr?.lineCount ?? 0;
|
|
161856
|
+
const maxScroll = Math.max(0, lineCount - ph);
|
|
161857
|
+
setPreviewScroll((s) => Math.max(0, Math.min(maxScroll, s + delta)));
|
|
161858
|
+
}
|
|
161859
|
+
});
|
|
161860
|
+
}, [controller, state.interactiveView]);
|
|
161127
161861
|
const isNarrow = cols < NARROW_THRESHOLD;
|
|
161128
161862
|
const treeWidth = isNarrow ? Math.max(20, cols - 2) : Math.max(20, Math.floor(cols * 0.38));
|
|
161129
161863
|
const previewEntry = selectedNode && !selectedNode.entry.isDirectory ? selectedNode.entry : null;
|
|
@@ -161287,8 +162021,8 @@ function InteractiveMode({ state, controller }) {
|
|
|
161287
162021
|
state.interactiveView === "board" && /* @__PURE__ */ jsx(BoardView, { state, controller }),
|
|
161288
162022
|
state.interactiveView === "agents" && /* @__PURE__ */ jsx(AgentsView, { state }),
|
|
161289
162023
|
state.interactiveView === "settings" && /* @__PURE__ */ jsx(SettingsInteractiveView, { state, controller }),
|
|
161290
|
-
state.interactiveView === "git" && /* @__PURE__ */ jsx(GitView, { state }),
|
|
161291
|
-
state.interactiveView === "files" && /* @__PURE__ */ jsx(FilesView, { state })
|
|
162024
|
+
state.interactiveView === "git" && /* @__PURE__ */ jsx(GitView, { state, controller }),
|
|
162025
|
+
state.interactiveView === "files" && /* @__PURE__ */ jsx(FilesView, { state, controller })
|
|
161292
162026
|
] })
|
|
161293
162027
|
] });
|
|
161294
162028
|
}
|
|
@@ -161319,6 +162053,23 @@ function DashboardApp({ controller }) {
|
|
|
161319
162053
|
useCallback2((cb) => controller.subscribe(cb), [controller]),
|
|
161320
162054
|
useCallback2(() => controller.getSnapshot(), [controller])
|
|
161321
162055
|
);
|
|
162056
|
+
const wheelStateRef = useRef(state);
|
|
162057
|
+
wheelStateRef.current = state;
|
|
162058
|
+
useEffect2(() => {
|
|
162059
|
+
return controller.onWheel((dir2) => {
|
|
162060
|
+
const s = wheelStateRef.current;
|
|
162061
|
+
if (s.activeSection !== "logs") return;
|
|
162062
|
+
const filtered = controller.getFilteredLogEntries();
|
|
162063
|
+
if (filtered.length === 0) return;
|
|
162064
|
+
const WHEEL_STEP = 3;
|
|
162065
|
+
const cur = s.selectedLogIndex;
|
|
162066
|
+
if (dir2 === "up") {
|
|
162067
|
+
controller.setSelectedLogIndex(Math.max(0, cur - WHEEL_STEP));
|
|
162068
|
+
} else {
|
|
162069
|
+
controller.setSelectedLogIndex(Math.min(filtered.length - 1, cur + WHEEL_STEP));
|
|
162070
|
+
}
|
|
162071
|
+
});
|
|
162072
|
+
}, [controller]);
|
|
161322
162073
|
const [qrOverlay, setQrOverlay] = useState2(null);
|
|
161323
162074
|
useInput((input, key) => {
|
|
161324
162075
|
if ((input === "q" || input === "Q") && !key.ctrl || key.ctrl && input === "c") {
|
|
@@ -161701,6 +162452,15 @@ var init_controller = __esm({
|
|
|
161701
162452
|
// no remote API is wired up).
|
|
161702
162453
|
remoteStatus = null;
|
|
161703
162454
|
remoteStatusTimer = null;
|
|
162455
|
+
// Mouse-wheel handling. We enable xterm SGR mouse mode in start() so the
|
|
162456
|
+
// terminal sends button reports for wheel up/down (buttons 64/65). A
|
|
162457
|
+
// parallel `data` listener parses those reports and dispatches to wheel
|
|
162458
|
+
// handlers. Ink's own keypress parser ignores SGR mouse sequences so
|
|
162459
|
+
// long as the full sequence (including the leading ESC) arrives in one
|
|
162460
|
+
// chunk — which it does once raw mode is enabled before mouse mode is
|
|
162461
|
+
// requested. (See ink#222 / @zenobius/ink-mouse for prior art.)
|
|
162462
|
+
wheelHandlers = /* @__PURE__ */ new Set();
|
|
162463
|
+
mouseStdinListener = null;
|
|
161704
162464
|
constructor() {
|
|
161705
162465
|
this.logBuffer = new LogRingBuffer();
|
|
161706
162466
|
}
|
|
@@ -161709,6 +162469,15 @@ var init_controller = __esm({
|
|
|
161709
162469
|
this.subscribers.add(callback);
|
|
161710
162470
|
return () => this.subscribers.delete(callback);
|
|
161711
162471
|
}
|
|
162472
|
+
/**
|
|
162473
|
+
* Subscribe to mouse-wheel events. Direction is "up" (scroll back/older
|
|
162474
|
+
* content) or "down" (scroll forward/newer content). Only fires while the
|
|
162475
|
+
* dashboard is running and the terminal supports xterm mouse reporting.
|
|
162476
|
+
*/
|
|
162477
|
+
onWheel(handler) {
|
|
162478
|
+
this.wheelHandlers.add(handler);
|
|
162479
|
+
return () => this.wheelHandlers.delete(handler);
|
|
162480
|
+
}
|
|
161712
162481
|
getSnapshot() {
|
|
161713
162482
|
if (this.cachedSnapshot) return this.cachedSnapshot;
|
|
161714
162483
|
this.cachedSnapshot = {
|
|
@@ -162093,6 +162862,10 @@ var init_controller = __esm({
|
|
|
162093
162862
|
this.inkInstance = render(
|
|
162094
162863
|
createElement(DashboardApp2, { controller: this })
|
|
162095
162864
|
);
|
|
162865
|
+
if (process.stdin?.isTTY) {
|
|
162866
|
+
process.stdout.write("\x1B[?1000h\x1B[?1006h");
|
|
162867
|
+
this.installMouseListener();
|
|
162868
|
+
}
|
|
162096
162869
|
this.resizeListener = () => {
|
|
162097
162870
|
if (this.resizeDebounceTimer) clearTimeout(this.resizeDebounceTimer);
|
|
162098
162871
|
this.resizeDebounceTimer = setTimeout(() => {
|
|
@@ -162199,10 +162972,48 @@ var init_controller = __esm({
|
|
|
162199
162972
|
this.inkInstance = null;
|
|
162200
162973
|
}
|
|
162201
162974
|
if (process.stdout?.isTTY && typeof process.stdout.write === "function") {
|
|
162975
|
+
this.uninstallMouseListener();
|
|
162976
|
+
process.stdout.write("\x1B[?1006l\x1B[?1000l");
|
|
162202
162977
|
process.stdout.write("\x1B[?1049l");
|
|
162203
162978
|
}
|
|
162204
162979
|
}
|
|
162205
162980
|
// ── Private helpers ────────────────────────────────────────────────────────
|
|
162981
|
+
// Attach a parallel `data` listener that decodes xterm SGR mouse
|
|
162982
|
+
// sequences and dispatches wheel events. Ink's own listener is also
|
|
162983
|
+
// attached; SGR sequences arrive as a single chunk that Ink's keypress
|
|
162984
|
+
// parser silently ignores, so we don't need to (and shouldn't) strip
|
|
162985
|
+
// them from the stream.
|
|
162986
|
+
installMouseListener() {
|
|
162987
|
+
if (this.mouseStdinListener) return;
|
|
162988
|
+
const mouseRe = /\x1b\[<(\d+);\d+;\d+[Mm]/g;
|
|
162989
|
+
const listener = (chunk) => {
|
|
162990
|
+
const text = typeof chunk === "string" ? chunk : chunk.toString("utf8");
|
|
162991
|
+
if (text.indexOf("\x1B[<") === -1) return;
|
|
162992
|
+
mouseRe.lastIndex = 0;
|
|
162993
|
+
let m;
|
|
162994
|
+
while ((m = mouseRe.exec(text)) !== null) {
|
|
162995
|
+
const btn = Number.parseInt(m[1] ?? "", 10);
|
|
162996
|
+
if (btn === 64) this.dispatchWheel("up");
|
|
162997
|
+
else if (btn === 65) this.dispatchWheel("down");
|
|
162998
|
+
}
|
|
162999
|
+
};
|
|
163000
|
+
this.mouseStdinListener = listener;
|
|
163001
|
+
process.stdin.on("data", listener);
|
|
163002
|
+
}
|
|
163003
|
+
uninstallMouseListener() {
|
|
163004
|
+
if (!this.mouseStdinListener) return;
|
|
163005
|
+
process.stdin.off("data", this.mouseStdinListener);
|
|
163006
|
+
this.mouseStdinListener = null;
|
|
163007
|
+
}
|
|
163008
|
+
dispatchWheel(direction) {
|
|
163009
|
+
for (const handler of this.wheelHandlers) {
|
|
163010
|
+
try {
|
|
163011
|
+
handler(direction);
|
|
163012
|
+
} catch (err) {
|
|
163013
|
+
tuiDebug2("wheel-handler-error", { err: String(err) });
|
|
163014
|
+
}
|
|
163015
|
+
}
|
|
163016
|
+
}
|
|
162206
163017
|
clampSelectedLogIndex(entries) {
|
|
162207
163018
|
if (entries.length === 0) {
|
|
162208
163019
|
this.selectedLogIndex = 0;
|
|
@@ -170806,7 +171617,7 @@ __export(native_patch_exports, {
|
|
|
170806
171617
|
});
|
|
170807
171618
|
import { join as join68, basename as basename21, dirname as dirname28 } from "node:path";
|
|
170808
171619
|
import { existsSync as existsSync51, copyFileSync, mkdirSync as mkdirSync12, symlinkSync as symlinkSync2, rmSync as rmSync5, lstatSync as lstatSync3, readlinkSync as readlinkSync2 } from "node:fs";
|
|
170809
|
-
import { tmpdir as
|
|
171620
|
+
import { tmpdir as tmpdir5 } from "node:os";
|
|
170810
171621
|
function findStagedNativeDir2() {
|
|
170811
171622
|
const platform4 = process.platform === "darwin" ? "darwin" : process.platform === "linux" ? "linux" : process.platform === "win32" ? "win32" : "unknown";
|
|
170812
171623
|
const arch = process.arch === "arm64" ? "arm64" : process.arch === "x64" ? "x64" : "unknown";
|
|
@@ -170851,7 +171662,7 @@ function setupNativeResolution() {
|
|
|
170851
171662
|
process.env.NODE_PTY_SPAWN_HELPER_DIR = nativeDir;
|
|
170852
171663
|
}
|
|
170853
171664
|
process.env.FUSION_NATIVE_ASSETS_PATH = nativeDir;
|
|
170854
|
-
const tmpRoot = join68(
|
|
171665
|
+
const tmpRoot = join68(tmpdir5(), `fn-bunfs-${process.pid}`);
|
|
170855
171666
|
const fnDir = join68(tmpRoot, "fn");
|
|
170856
171667
|
const prebuildsDir = join68(fnDir, "prebuilds");
|
|
170857
171668
|
const platformDir = join68(prebuildsDir, basename21(nativeDir));
|
|
@@ -170939,7 +171750,7 @@ var init_native_patch = __esm({
|
|
|
170939
171750
|
import { existsSync as existsSync52, mkdtempSync as mkdtempSync2, readFileSync as readFileSync24, symlinkSync as symlinkSync3, writeFileSync as writeFileSync6 } from "node:fs";
|
|
170940
171751
|
import { createRequire as createRequire7 } from "node:module";
|
|
170941
171752
|
import { join as join69, dirname as dirname29, resolve as resolve41 } from "node:path";
|
|
170942
|
-
import { tmpdir as
|
|
171753
|
+
import { tmpdir as tmpdir6 } from "node:os";
|
|
170943
171754
|
import { performance as performance3 } from "node:perf_hooks";
|
|
170944
171755
|
import { fileURLToPath as fileURLToPath11 } from "node:url";
|
|
170945
171756
|
var isBunBinary3 = typeof Bun !== "undefined" && !!Bun.embeddedFiles;
|
|
@@ -170947,7 +171758,7 @@ function configurePiPackage() {
|
|
|
170947
171758
|
if (process.env.PI_PACKAGE_DIR) {
|
|
170948
171759
|
return;
|
|
170949
171760
|
}
|
|
170950
|
-
const tmp = mkdtempSync2(join69(
|
|
171761
|
+
const tmp = mkdtempSync2(join69(tmpdir6(), "fn-pkg-"));
|
|
170951
171762
|
let packageJson = {
|
|
170952
171763
|
name: "pi",
|
|
170953
171764
|
version: "0.1.0",
|