@runfusion/fusion 0.9.3 → 0.9.4
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 +2876 -915
- package/dist/client/assets/{AgentDetailView-D9UWpTYr.js → AgentDetailView-5W1q48YS.js} +3 -3
- package/dist/client/assets/{AgentsView-DeCfRupM.js → AgentsView-DcEnemu0.js} +3 -3
- package/dist/client/assets/{ChatView-ChlqnJfu.js → ChatView-CTc6mP8y.js} +1 -1
- package/dist/client/assets/{DevServerView-B7EjWlgc.js → DevServerView-LOrDrAYm.js} +1 -1
- package/dist/client/assets/{DirectoryPicker-crtmkC00.js → DirectoryPicker-Bgp6PCbu.js} +1 -1
- package/dist/client/assets/{DocumentsView-BLxVoopL.js → DocumentsView-CNbnZ7Q3.js} +1 -1
- package/dist/client/assets/{InsightsView-CcdTychV.js → InsightsView-CmJwV-ZC.js} +1 -1
- package/dist/client/assets/{MemoryView-rSwx9Md8.js → MemoryView-Bwi5p79s.js} +1 -1
- package/dist/client/assets/{NodesView-Bwz0cHKV.js → NodesView-1pZii99I.js} +3 -3
- package/dist/client/assets/{PiExtensionsManager-Uo3E8Ae7.js → PiExtensionsManager-CokhM-MB.js} +3 -3
- package/dist/client/assets/{PluginManager-HtW8xVY8.js → PluginManager-cHaGKMgY.js} +1 -1
- package/dist/client/assets/ResearchView-BVJFgfat.css +1 -0
- package/dist/client/assets/ResearchView-CQDI2y7Q.js +1 -0
- package/dist/client/assets/{RoadmapsView-C2j64cbz.js → RoadmapsView-BTo3BT0I.js} +2 -2
- package/dist/client/assets/{SettingsModal-YjpwLH2V.css → SettingsModal-9HS8MnmW.css} +1 -1
- package/dist/client/assets/{SettingsModal-CVd9kNk7.js → SettingsModal-D5slUUsC.js} +1 -1
- package/dist/client/assets/SettingsModal-EEQwF0Ql.js +31 -0
- package/dist/client/assets/{SetupWizardModal-Dy-vQpTm.js → SetupWizardModal-1qSn8Yl0.js} +1 -1
- package/dist/client/assets/{SkillsView-BQwTyjxc.js → SkillsView-CY3I5OYc.js} +1 -1
- package/dist/client/assets/{TodoView-Dce4DrzU.js → TodoView-B1GDwwhR.js} +2 -2
- package/dist/client/assets/{folder-open-DWUflP4Q.js → folder-open-DPESt6bg.js} +1 -1
- package/dist/client/assets/{index-C3-q81dV.css → index-2_pvFDiN.css} +1 -1
- package/dist/client/assets/index-DNIrnlpO.js +656 -0
- package/dist/client/assets/{list-checks-CusZ_RMn.js → list-checks-D7D9kx7Y.js} +1 -1
- package/dist/client/assets/{star-C-NXZn1F.js → star-C59_6aNu.js} +1 -1
- package/dist/client/assets/{upload-CXJ5L6L4.js → upload-1I0eQddJ.js} +1 -1
- package/dist/client/assets/{users-YaA3x5mt.js → users-DH50eBCX.js} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/client/version.json +1 -1
- package/dist/extension.js +2200 -380
- package/dist/pi-claude-cli/package.json +1 -1
- package/package.json +1 -1
- package/dist/client/assets/ResearchView-BV-iy9g8.js +0 -1
- package/dist/client/assets/ResearchView-aQkoD9QR.css +0 -1
- package/dist/client/assets/SettingsModal-ClnT1Lcv.js +0 -31
- package/dist/client/assets/index-Bs3RZu5I.js +0 -656
package/dist/extension.js
CHANGED
|
@@ -101,7 +101,23 @@ var init_settings_schema = __esm({
|
|
|
101
101
|
dashboardCurrentProjectIdByNode: void 0,
|
|
102
102
|
// Dashboard TUI memory guard
|
|
103
103
|
vitestAutoKillEnabled: true,
|
|
104
|
-
vitestKillThresholdPct: 90
|
|
104
|
+
vitestKillThresholdPct: 90,
|
|
105
|
+
researchGlobalEnabled: true,
|
|
106
|
+
researchGlobalMaxConcurrentRuns: 3,
|
|
107
|
+
researchGlobalDefaultTimeout: 3e5,
|
|
108
|
+
researchGlobalMaxSourcesPerRun: 20,
|
|
109
|
+
researchGlobalMaxSynthesisRounds: 2,
|
|
110
|
+
researchWebSearchProvider: "none",
|
|
111
|
+
researchSearxngUrl: void 0,
|
|
112
|
+
researchBraveApiKey: void 0,
|
|
113
|
+
researchGoogleSearchApiKey: void 0,
|
|
114
|
+
researchGoogleSearchCx: void 0,
|
|
115
|
+
researchTavilyApiKey: void 0,
|
|
116
|
+
researchGitHubEnabled: false,
|
|
117
|
+
researchLocalDocsEnabled: true,
|
|
118
|
+
researchMaxSearchResults: 10,
|
|
119
|
+
researchFetchTimeoutMs: 3e4,
|
|
120
|
+
researchUserAgent: "FusionResearchBot/1.0"
|
|
105
121
|
};
|
|
106
122
|
DEFAULT_PROJECT_SETTINGS = {
|
|
107
123
|
globalPause: false,
|
|
@@ -184,7 +200,7 @@ var init_settings_schema = __esm({
|
|
|
184
200
|
autoBackupRetention: 7,
|
|
185
201
|
autoBackupDir: ".fusion/backups",
|
|
186
202
|
autoSummarizeTitles: false,
|
|
187
|
-
useAiMergeCommitSummary:
|
|
203
|
+
useAiMergeCommitSummary: true,
|
|
188
204
|
titleSummarizerProvider: void 0,
|
|
189
205
|
titleSummarizerModelId: void 0,
|
|
190
206
|
titleSummarizerFallbackProvider: void 0,
|
|
@@ -248,6 +264,11 @@ var init_settings_schema = __esm({
|
|
|
248
264
|
reflectionAfterTask: true,
|
|
249
265
|
reviewHandoffPolicy: "disabled",
|
|
250
266
|
showQuickChatFAB: false,
|
|
267
|
+
researchEnabled: true,
|
|
268
|
+
researchMaxConcurrentRuns: 3,
|
|
269
|
+
researchDefaultTimeout: 3e5,
|
|
270
|
+
researchMaxSourcesPerRun: 20,
|
|
271
|
+
researchMaxSynthesisRounds: 2,
|
|
251
272
|
experimentalFeatures: {}
|
|
252
273
|
};
|
|
253
274
|
DEFAULT_SETTINGS = {
|
|
@@ -6754,9 +6775,9 @@ var init_global_settings = __esm({
|
|
|
6754
6775
|
* Serialize operations via promise chain to prevent lost-update races.
|
|
6755
6776
|
*/
|
|
6756
6777
|
withLock(fn) {
|
|
6757
|
-
let
|
|
6778
|
+
let resolve19;
|
|
6758
6779
|
const next = new Promise((r) => {
|
|
6759
|
-
|
|
6780
|
+
resolve19 = r;
|
|
6760
6781
|
});
|
|
6761
6782
|
const prev = this.lock;
|
|
6762
6783
|
this.lock = next;
|
|
@@ -6764,7 +6785,7 @@ var init_global_settings = __esm({
|
|
|
6764
6785
|
try {
|
|
6765
6786
|
return await fn();
|
|
6766
6787
|
} finally {
|
|
6767
|
-
|
|
6788
|
+
resolve19();
|
|
6768
6789
|
}
|
|
6769
6790
|
});
|
|
6770
6791
|
}
|
|
@@ -11989,7 +12010,7 @@ var init_research_store = __esm({
|
|
|
11989
12010
|
}
|
|
11990
12011
|
return deleted;
|
|
11991
12012
|
}
|
|
11992
|
-
|
|
12013
|
+
addEvent(runId, event) {
|
|
11993
12014
|
const run = this.getRun(runId);
|
|
11994
12015
|
if (!run) throw new Error(`Research run not found: ${runId}`);
|
|
11995
12016
|
const created = {
|
|
@@ -12000,13 +12021,18 @@ var init_research_store = __esm({
|
|
|
12000
12021
|
metadata: event.metadata
|
|
12001
12022
|
};
|
|
12002
12023
|
this.updateRun(runId, { events: [...run.events, created] });
|
|
12024
|
+
this.emit("event:added", { runId, event: created });
|
|
12003
12025
|
return created;
|
|
12004
12026
|
}
|
|
12027
|
+
appendEvent(runId, event) {
|
|
12028
|
+
return this.addEvent(runId, event);
|
|
12029
|
+
}
|
|
12005
12030
|
addSource(runId, source) {
|
|
12006
12031
|
const run = this.getRun(runId);
|
|
12007
12032
|
if (!run) throw new Error(`Research run not found: ${runId}`);
|
|
12008
12033
|
const created = { ...source, id: generateId("RSRC") };
|
|
12009
12034
|
this.updateRun(runId, { sources: [...run.sources, created] });
|
|
12035
|
+
this.emit("source:added", { runId, source: created });
|
|
12010
12036
|
return created;
|
|
12011
12037
|
}
|
|
12012
12038
|
updateSource(runId, sourceId, updates) {
|
|
@@ -28419,9 +28445,9 @@ var init_automation_store = __esm({
|
|
|
28419
28445
|
*/
|
|
28420
28446
|
withScheduleLock(id, fn) {
|
|
28421
28447
|
const prev = this.scheduleLocks.get(id) ?? Promise.resolve();
|
|
28422
|
-
let
|
|
28448
|
+
let resolve19;
|
|
28423
28449
|
const next = new Promise((r) => {
|
|
28424
|
-
|
|
28450
|
+
resolve19 = r;
|
|
28425
28451
|
});
|
|
28426
28452
|
this.scheduleLocks.set(id, next);
|
|
28427
28453
|
return prev.then(async () => {
|
|
@@ -28431,7 +28457,7 @@ var init_automation_store = __esm({
|
|
|
28431
28457
|
if (this.scheduleLocks.get(id) === next) {
|
|
28432
28458
|
this.scheduleLocks.delete(id);
|
|
28433
28459
|
}
|
|
28434
|
-
|
|
28460
|
+
resolve19();
|
|
28435
28461
|
}
|
|
28436
28462
|
});
|
|
28437
28463
|
}
|
|
@@ -30220,7 +30246,7 @@ var init_project_memory = __esm({
|
|
|
30220
30246
|
// ../core/src/run-command.ts
|
|
30221
30247
|
import { spawn } from "node:child_process";
|
|
30222
30248
|
function runCommandAsync(command, options = {}) {
|
|
30223
|
-
return new Promise((
|
|
30249
|
+
return new Promise((resolve19) => {
|
|
30224
30250
|
const maxBuffer = options.maxBuffer ?? DEFAULT_MAX_BUFFER;
|
|
30225
30251
|
let stdout = "";
|
|
30226
30252
|
let stderr = "";
|
|
@@ -30279,7 +30305,7 @@ function runCommandAsync(command, options = {}) {
|
|
|
30279
30305
|
clearTimeout(forceKillTimer);
|
|
30280
30306
|
forceKillTimer = null;
|
|
30281
30307
|
}
|
|
30282
|
-
|
|
30308
|
+
resolve19({
|
|
30283
30309
|
stdout,
|
|
30284
30310
|
stderr,
|
|
30285
30311
|
exitCode: null,
|
|
@@ -30297,7 +30323,7 @@ function runCommandAsync(command, options = {}) {
|
|
|
30297
30323
|
}
|
|
30298
30324
|
signalProcessGroup("SIGTERM");
|
|
30299
30325
|
scheduleForceKill(NORMAL_CLEANUP_FORCE_KILL_DELAY_MS);
|
|
30300
|
-
|
|
30326
|
+
resolve19({
|
|
30301
30327
|
stdout,
|
|
30302
30328
|
stderr,
|
|
30303
30329
|
exitCode: code,
|
|
@@ -31035,10 +31061,10 @@ ${recentText}` : void 0
|
|
|
31035
31061
|
* report the same total-execution figure that the task detail Stats panel
|
|
31036
31062
|
* computes from the full log.
|
|
31037
31063
|
*/
|
|
31038
|
-
computeTimedExecutionMs(
|
|
31039
|
-
if (!
|
|
31064
|
+
computeTimedExecutionMs(log17) {
|
|
31065
|
+
if (!log17 || log17.length === 0) return 0;
|
|
31040
31066
|
let total = 0;
|
|
31041
|
-
for (const entry of
|
|
31067
|
+
for (const entry of log17) {
|
|
31042
31068
|
const action = typeof entry.action === "string" ? entry.action : "";
|
|
31043
31069
|
const outcome = typeof entry.outcome === "string" ? entry.outcome : "";
|
|
31044
31070
|
if (!action.includes("[timing]") && !outcome.includes("[timing]")) continue;
|
|
@@ -31512,9 +31538,9 @@ ${outcome}`;
|
|
|
31512
31538
|
* lost-update races on the nextId counter.
|
|
31513
31539
|
*/
|
|
31514
31540
|
withConfigLock(fn) {
|
|
31515
|
-
let
|
|
31541
|
+
let resolve19;
|
|
31516
31542
|
const next = new Promise((r) => {
|
|
31517
|
-
|
|
31543
|
+
resolve19 = r;
|
|
31518
31544
|
});
|
|
31519
31545
|
const prev = this.configLock;
|
|
31520
31546
|
this.configLock = next;
|
|
@@ -31522,7 +31548,7 @@ ${outcome}`;
|
|
|
31522
31548
|
try {
|
|
31523
31549
|
return await fn();
|
|
31524
31550
|
} finally {
|
|
31525
|
-
|
|
31551
|
+
resolve19();
|
|
31526
31552
|
}
|
|
31527
31553
|
});
|
|
31528
31554
|
}
|
|
@@ -31532,9 +31558,9 @@ ${outcome}`;
|
|
|
31532
31558
|
*/
|
|
31533
31559
|
withTaskLock(id, fn) {
|
|
31534
31560
|
const prev = this.taskLocks.get(id) ?? Promise.resolve();
|
|
31535
|
-
let
|
|
31561
|
+
let resolve19;
|
|
31536
31562
|
const next = new Promise((r) => {
|
|
31537
|
-
|
|
31563
|
+
resolve19 = r;
|
|
31538
31564
|
});
|
|
31539
31565
|
this.taskLocks.set(id, next);
|
|
31540
31566
|
return prev.then(async () => {
|
|
@@ -31544,7 +31570,7 @@ ${outcome}`;
|
|
|
31544
31570
|
if (this.taskLocks.get(id) === next) {
|
|
31545
31571
|
this.taskLocks.delete(id);
|
|
31546
31572
|
}
|
|
31547
|
-
|
|
31573
|
+
resolve19();
|
|
31548
31574
|
}
|
|
31549
31575
|
});
|
|
31550
31576
|
}
|
|
@@ -33025,13 +33051,13 @@ ${task.description}
|
|
|
33025
33051
|
if (row.column === "archived") {
|
|
33026
33052
|
throw new Error(`Task ${id} is archived \u2014 logging is read-only`);
|
|
33027
33053
|
}
|
|
33028
|
-
const
|
|
33029
|
-
|
|
33030
|
-
if (
|
|
33031
|
-
|
|
33054
|
+
const log17 = fromJson(row.log) || [];
|
|
33055
|
+
log17.push(entry);
|
|
33056
|
+
if (log17.length > TASK_ACTIVITY_LOG_ENTRY_LIMIT) {
|
|
33057
|
+
log17.splice(0, log17.length - TASK_ACTIVITY_LOG_ENTRY_LIMIT);
|
|
33032
33058
|
}
|
|
33033
33059
|
const updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
33034
|
-
this.db.prepare("UPDATE tasks SET log = ?, updatedAt = ? WHERE id = ?").run(toJson(
|
|
33060
|
+
this.db.prepare("UPDATE tasks SET log = ?, updatedAt = ? WHERE id = ?").run(toJson(log17), updatedAt, id);
|
|
33035
33061
|
this.db.bumpLastModified();
|
|
33036
33062
|
const current = this.readTaskFromDb(id);
|
|
33037
33063
|
if (current) {
|
|
@@ -33042,7 +33068,7 @@ ${task.description}
|
|
|
33042
33068
|
this.emit("task:updated", current);
|
|
33043
33069
|
return current;
|
|
33044
33070
|
}
|
|
33045
|
-
const emittedTask = { id, log:
|
|
33071
|
+
const emittedTask = { id, log: log17, updatedAt };
|
|
33046
33072
|
this.emit("task:updated", emittedTask);
|
|
33047
33073
|
return emittedTask;
|
|
33048
33074
|
});
|
|
@@ -33796,7 +33822,7 @@ ${task.description}
|
|
|
33796
33822
|
}
|
|
33797
33823
|
}
|
|
33798
33824
|
}
|
|
33799
|
-
await new Promise((
|
|
33825
|
+
await new Promise((resolve19) => setImmediate(resolve19));
|
|
33800
33826
|
const selectClause = this.getTaskSelectClause(true);
|
|
33801
33827
|
const changedRows = this.lastPollTime ? this.db.prepare(`SELECT ${selectClause} FROM tasks WHERE updatedAt > ? OR columnMovedAt > ?`).all(this.lastPollTime, this.lastPollTime) : this.db.prepare(`SELECT ${selectClause} FROM tasks`).all();
|
|
33802
33828
|
this.lastPollTime = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -33816,7 +33842,7 @@ ${task.description}
|
|
|
33816
33842
|
this.emit("task:updated", task);
|
|
33817
33843
|
}
|
|
33818
33844
|
if (i > 0 && i % 50 === 0) {
|
|
33819
|
-
await new Promise((
|
|
33845
|
+
await new Promise((resolve19) => setImmediate(resolve19));
|
|
33820
33846
|
}
|
|
33821
33847
|
}
|
|
33822
33848
|
const elapsed = Date.now() - startTime;
|
|
@@ -35672,7 +35698,7 @@ function runGh(args, cwd) {
|
|
|
35672
35698
|
}
|
|
35673
35699
|
function runGhAsync(args, cwdOrOptions) {
|
|
35674
35700
|
const { cwd, signal: externalSignal, timeoutMs = DEFAULT_GH_TIMEOUT_MS } = normalizeRunGhOptions(cwdOrOptions);
|
|
35675
|
-
return new Promise((
|
|
35701
|
+
return new Promise((resolve19, reject) => {
|
|
35676
35702
|
if (externalSignal?.aborted) {
|
|
35677
35703
|
reject(makeGhError(`gh command aborted: ${describeAbortReason(externalSignal.reason)}`, "ABORT_ERR"));
|
|
35678
35704
|
return;
|
|
@@ -35723,7 +35749,7 @@ function runGhAsync(args, cwdOrOptions) {
|
|
|
35723
35749
|
ghError.stderr = stderr ?? "";
|
|
35724
35750
|
reject(ghError);
|
|
35725
35751
|
} else {
|
|
35726
|
-
|
|
35752
|
+
resolve19(stdout ?? "");
|
|
35727
35753
|
}
|
|
35728
35754
|
}
|
|
35729
35755
|
);
|
|
@@ -36011,9 +36037,9 @@ var init_routine_store = __esm({
|
|
|
36011
36037
|
*/
|
|
36012
36038
|
withRoutineLock(id, fn) {
|
|
36013
36039
|
const prev = this.routineLocks.get(id) ?? Promise.resolve();
|
|
36014
|
-
let
|
|
36040
|
+
let resolve19;
|
|
36015
36041
|
const next = new Promise((r) => {
|
|
36016
|
-
|
|
36042
|
+
resolve19 = r;
|
|
36017
36043
|
});
|
|
36018
36044
|
this.routineLocks.set(id, next);
|
|
36019
36045
|
return prev.then(async () => {
|
|
@@ -36023,7 +36049,7 @@ var init_routine_store = __esm({
|
|
|
36023
36049
|
if (this.routineLocks.get(id) === next) {
|
|
36024
36050
|
this.routineLocks.delete(id);
|
|
36025
36051
|
}
|
|
36026
|
-
|
|
36052
|
+
resolve19();
|
|
36027
36053
|
}
|
|
36028
36054
|
});
|
|
36029
36055
|
}
|
|
@@ -36622,13 +36648,13 @@ var init_plugin_loader = __esm({
|
|
|
36622
36648
|
* Execute a promise with a timeout.
|
|
36623
36649
|
*/
|
|
36624
36650
|
withTimeout(promise, ms, timeoutMessage) {
|
|
36625
|
-
return new Promise((
|
|
36651
|
+
return new Promise((resolve19, reject) => {
|
|
36626
36652
|
const timer = setTimeout(() => {
|
|
36627
36653
|
reject(new Error(timeoutMessage));
|
|
36628
36654
|
}, ms);
|
|
36629
36655
|
promise.then((result) => {
|
|
36630
36656
|
clearTimeout(timer);
|
|
36631
|
-
|
|
36657
|
+
resolve19(result);
|
|
36632
36658
|
}).catch((err) => {
|
|
36633
36659
|
clearTimeout(timer);
|
|
36634
36660
|
reject(err);
|
|
@@ -39051,7 +39077,7 @@ var init_memory_insights = __esm({
|
|
|
39051
39077
|
});
|
|
39052
39078
|
|
|
39053
39079
|
// ../core/src/research-types.ts
|
|
39054
|
-
var RESEARCH_RUN_STATUSES, RESEARCH_SOURCE_STATUSES, RESEARCH_EXPORT_FORMATS, RESEARCH_SOURCE_TYPES, RESEARCH_EVENT_TYPES;
|
|
39080
|
+
var RESEARCH_RUN_STATUSES, RESEARCH_SOURCE_STATUSES, RESEARCH_EXPORT_FORMATS, RESEARCH_SOURCE_TYPES, RESEARCH_EVENT_TYPES, RESEARCH_ORCHESTRATION_PHASES, RESEARCH_ORCHESTRATION_STEP_STATUSES;
|
|
39055
39081
|
var init_research_types = __esm({
|
|
39056
39082
|
"../core/src/research-types.ts"() {
|
|
39057
39083
|
"use strict";
|
|
@@ -39078,6 +39104,17 @@ var init_research_types = __esm({
|
|
|
39078
39104
|
"result_updated",
|
|
39079
39105
|
"progress"
|
|
39080
39106
|
];
|
|
39107
|
+
RESEARCH_ORCHESTRATION_PHASES = [
|
|
39108
|
+
"planning",
|
|
39109
|
+
"searching",
|
|
39110
|
+
"fetching",
|
|
39111
|
+
"synthesizing",
|
|
39112
|
+
"finalizing",
|
|
39113
|
+
"completed",
|
|
39114
|
+
"failed",
|
|
39115
|
+
"cancelled"
|
|
39116
|
+
];
|
|
39117
|
+
RESEARCH_ORCHESTRATION_STEP_STATUSES = ["pending", "running", "completed", "failed", "skipped"];
|
|
39081
39118
|
}
|
|
39082
39119
|
});
|
|
39083
39120
|
|
|
@@ -39669,7 +39706,7 @@ var require_node = __commonJS({
|
|
|
39669
39706
|
var tty = __require("tty");
|
|
39670
39707
|
var util = __require("util");
|
|
39671
39708
|
exports.init = init;
|
|
39672
|
-
exports.log =
|
|
39709
|
+
exports.log = log17;
|
|
39673
39710
|
exports.formatArgs = formatArgs;
|
|
39674
39711
|
exports.save = save;
|
|
39675
39712
|
exports.load = load;
|
|
@@ -39804,7 +39841,7 @@ var require_node = __commonJS({
|
|
|
39804
39841
|
}
|
|
39805
39842
|
return (/* @__PURE__ */ new Date()).toISOString() + " ";
|
|
39806
39843
|
}
|
|
39807
|
-
function
|
|
39844
|
+
function log17(...args) {
|
|
39808
39845
|
return process.stderr.write(util.formatWithOptions(exports.inspectOpts, ...args) + "\n");
|
|
39809
39846
|
}
|
|
39810
39847
|
function save(namespaces) {
|
|
@@ -40015,9 +40052,9 @@ var require_pump = __commonJS({
|
|
|
40015
40052
|
"use strict";
|
|
40016
40053
|
var once = require_once();
|
|
40017
40054
|
var eos = require_end_of_stream();
|
|
40018
|
-
var
|
|
40055
|
+
var fs2;
|
|
40019
40056
|
try {
|
|
40020
|
-
|
|
40057
|
+
fs2 = __require("fs");
|
|
40021
40058
|
} catch (e) {
|
|
40022
40059
|
}
|
|
40023
40060
|
var noop = function() {
|
|
@@ -40028,8 +40065,8 @@ var require_pump = __commonJS({
|
|
|
40028
40065
|
};
|
|
40029
40066
|
var isFS = function(stream) {
|
|
40030
40067
|
if (!ancient) return false;
|
|
40031
|
-
if (!
|
|
40032
|
-
return (stream instanceof (
|
|
40068
|
+
if (!fs2) return false;
|
|
40069
|
+
return (stream instanceof (fs2.ReadStream || noop) || stream instanceof (fs2.WriteStream || noop)) && isFn(stream.close);
|
|
40033
40070
|
};
|
|
40034
40071
|
var isRequest = function(stream) {
|
|
40035
40072
|
return stream.setHeader && isFn(stream.abort);
|
|
@@ -40153,7 +40190,7 @@ var require_get_stream = __commonJS({
|
|
|
40153
40190
|
};
|
|
40154
40191
|
const { maxBuffer } = options;
|
|
40155
40192
|
let stream;
|
|
40156
|
-
await new Promise((
|
|
40193
|
+
await new Promise((resolve19, reject) => {
|
|
40157
40194
|
const rejectPromise = (error) => {
|
|
40158
40195
|
if (error && stream.getBufferedLength() <= BufferConstants.MAX_LENGTH) {
|
|
40159
40196
|
error.bufferedData = stream.getBufferedValue();
|
|
@@ -40165,7 +40202,7 @@ var require_get_stream = __commonJS({
|
|
|
40165
40202
|
rejectPromise(error);
|
|
40166
40203
|
return;
|
|
40167
40204
|
}
|
|
40168
|
-
|
|
40205
|
+
resolve19();
|
|
40169
40206
|
});
|
|
40170
40207
|
stream.on("data", () => {
|
|
40171
40208
|
if (stream.getBufferedLength() > maxBuffer) {
|
|
@@ -40243,7 +40280,7 @@ var require_pend = __commonJS({
|
|
|
40243
40280
|
var require_fd_slicer = __commonJS({
|
|
40244
40281
|
"../../node_modules/.pnpm/fd-slicer@1.1.0/node_modules/fd-slicer/index.js"(exports) {
|
|
40245
40282
|
"use strict";
|
|
40246
|
-
var
|
|
40283
|
+
var fs2 = __require("fs");
|
|
40247
40284
|
var util = __require("util");
|
|
40248
40285
|
var stream = __require("stream");
|
|
40249
40286
|
var Readable = stream.Readable;
|
|
@@ -40268,7 +40305,7 @@ var require_fd_slicer = __commonJS({
|
|
|
40268
40305
|
FdSlicer.prototype.read = function(buffer, offset, length, position, callback) {
|
|
40269
40306
|
var self = this;
|
|
40270
40307
|
self.pend.go(function(cb) {
|
|
40271
|
-
|
|
40308
|
+
fs2.read(self.fd, buffer, offset, length, position, function(err, bytesRead, buffer2) {
|
|
40272
40309
|
cb();
|
|
40273
40310
|
callback(err, bytesRead, buffer2);
|
|
40274
40311
|
});
|
|
@@ -40277,7 +40314,7 @@ var require_fd_slicer = __commonJS({
|
|
|
40277
40314
|
FdSlicer.prototype.write = function(buffer, offset, length, position, callback) {
|
|
40278
40315
|
var self = this;
|
|
40279
40316
|
self.pend.go(function(cb) {
|
|
40280
|
-
|
|
40317
|
+
fs2.write(self.fd, buffer, offset, length, position, function(err, written, buffer2) {
|
|
40281
40318
|
cb();
|
|
40282
40319
|
callback(err, written, buffer2);
|
|
40283
40320
|
});
|
|
@@ -40298,7 +40335,7 @@ var require_fd_slicer = __commonJS({
|
|
|
40298
40335
|
if (self.refCount > 0) return;
|
|
40299
40336
|
if (self.refCount < 0) throw new Error("invalid unref");
|
|
40300
40337
|
if (self.autoClose) {
|
|
40301
|
-
|
|
40338
|
+
fs2.close(self.fd, onCloseDone);
|
|
40302
40339
|
}
|
|
40303
40340
|
function onCloseDone(err) {
|
|
40304
40341
|
if (err) {
|
|
@@ -40335,7 +40372,7 @@ var require_fd_slicer = __commonJS({
|
|
|
40335
40372
|
self.context.pend.go(function(cb) {
|
|
40336
40373
|
if (self.destroyed) return cb();
|
|
40337
40374
|
var buffer = new Buffer(toRead);
|
|
40338
|
-
|
|
40375
|
+
fs2.read(self.context.fd, buffer, 0, toRead, self.pos, function(err, bytesRead) {
|
|
40339
40376
|
if (err) {
|
|
40340
40377
|
self.destroy(err);
|
|
40341
40378
|
} else if (bytesRead === 0) {
|
|
@@ -40382,7 +40419,7 @@ var require_fd_slicer = __commonJS({
|
|
|
40382
40419
|
}
|
|
40383
40420
|
self.context.pend.go(function(cb) {
|
|
40384
40421
|
if (self.destroyed) return cb();
|
|
40385
|
-
|
|
40422
|
+
fs2.write(self.context.fd, buffer, 0, buffer.length, self.pos, function(err2, bytes) {
|
|
40386
40423
|
if (err2) {
|
|
40387
40424
|
self.destroy();
|
|
40388
40425
|
cb();
|
|
@@ -40811,7 +40848,7 @@ var require_buffer_crc32 = __commonJS({
|
|
|
40811
40848
|
var require_yauzl = __commonJS({
|
|
40812
40849
|
"../../node_modules/.pnpm/yauzl@2.10.0/node_modules/yauzl/index.js"(exports) {
|
|
40813
40850
|
"use strict";
|
|
40814
|
-
var
|
|
40851
|
+
var fs2 = __require("fs");
|
|
40815
40852
|
var zlib = __require("zlib");
|
|
40816
40853
|
var fd_slicer = require_fd_slicer();
|
|
40817
40854
|
var crc32 = require_buffer_crc32();
|
|
@@ -40841,10 +40878,10 @@ var require_yauzl = __commonJS({
|
|
|
40841
40878
|
if (options.validateEntrySizes == null) options.validateEntrySizes = true;
|
|
40842
40879
|
if (options.strictFileNames == null) options.strictFileNames = false;
|
|
40843
40880
|
if (callback == null) callback = defaultCallback;
|
|
40844
|
-
|
|
40881
|
+
fs2.open(path2, "r", function(err, fd) {
|
|
40845
40882
|
if (err) return callback(err);
|
|
40846
40883
|
fromFd(fd, options, function(err2, zipfile) {
|
|
40847
|
-
if (err2)
|
|
40884
|
+
if (err2) fs2.close(fd, defaultCallback);
|
|
40848
40885
|
callback(err2, zipfile);
|
|
40849
40886
|
});
|
|
40850
40887
|
});
|
|
@@ -40861,7 +40898,7 @@ var require_yauzl = __commonJS({
|
|
|
40861
40898
|
if (options.validateEntrySizes == null) options.validateEntrySizes = true;
|
|
40862
40899
|
if (options.strictFileNames == null) options.strictFileNames = false;
|
|
40863
40900
|
if (callback == null) callback = defaultCallback;
|
|
40864
|
-
|
|
40901
|
+
fs2.fstat(fd, function(err, stats) {
|
|
40865
40902
|
if (err) return callback(err);
|
|
40866
40903
|
var reader = fd_slicer.createFromFd(fd, { autoClose: true });
|
|
40867
40904
|
fromRandomAccessReader(reader, stats.size, options, callback);
|
|
@@ -41442,7 +41479,7 @@ var require_extract_zip = __commonJS({
|
|
|
41442
41479
|
"../../node_modules/.pnpm/extract-zip@2.0.1/node_modules/extract-zip/index.js"(exports, module) {
|
|
41443
41480
|
"use strict";
|
|
41444
41481
|
var debug = require_src()("extract-zip");
|
|
41445
|
-
var { createWriteStream, promises:
|
|
41482
|
+
var { createWriteStream, promises: fs2 } = __require("fs");
|
|
41446
41483
|
var getStream = require_get_stream();
|
|
41447
41484
|
var path2 = __require("path");
|
|
41448
41485
|
var { promisify: promisify13 } = __require("util");
|
|
@@ -41459,7 +41496,7 @@ var require_extract_zip = __commonJS({
|
|
|
41459
41496
|
debug("opening", this.zipPath, "with opts", this.opts);
|
|
41460
41497
|
this.zipfile = await openZip(this.zipPath, { lazyEntries: true });
|
|
41461
41498
|
this.canceled = false;
|
|
41462
|
-
return new Promise((
|
|
41499
|
+
return new Promise((resolve19, reject) => {
|
|
41463
41500
|
this.zipfile.on("error", (err) => {
|
|
41464
41501
|
this.canceled = true;
|
|
41465
41502
|
reject(err);
|
|
@@ -41468,7 +41505,7 @@ var require_extract_zip = __commonJS({
|
|
|
41468
41505
|
this.zipfile.on("close", () => {
|
|
41469
41506
|
if (!this.canceled) {
|
|
41470
41507
|
debug("zip extraction complete");
|
|
41471
|
-
|
|
41508
|
+
resolve19();
|
|
41472
41509
|
}
|
|
41473
41510
|
});
|
|
41474
41511
|
this.zipfile.on("entry", async (entry) => {
|
|
@@ -41483,8 +41520,8 @@ var require_extract_zip = __commonJS({
|
|
|
41483
41520
|
}
|
|
41484
41521
|
const destDir = path2.dirname(path2.join(this.opts.dir, entry.fileName));
|
|
41485
41522
|
try {
|
|
41486
|
-
await
|
|
41487
|
-
const canonicalDestDir = await
|
|
41523
|
+
await fs2.mkdir(destDir, { recursive: true });
|
|
41524
|
+
const canonicalDestDir = await fs2.realpath(destDir);
|
|
41488
41525
|
const relativeDestDir = path2.relative(this.opts.dir, canonicalDestDir);
|
|
41489
41526
|
if (relativeDestDir.split(path2.sep).includes("..")) {
|
|
41490
41527
|
throw new Error(`Out of bound path "${canonicalDestDir}" found while processing file ${entry.fileName}`);
|
|
@@ -41528,14 +41565,14 @@ var require_extract_zip = __commonJS({
|
|
|
41528
41565
|
mkdirOptions.mode = procMode;
|
|
41529
41566
|
}
|
|
41530
41567
|
debug("mkdir", { dir: destDir, ...mkdirOptions });
|
|
41531
|
-
await
|
|
41568
|
+
await fs2.mkdir(destDir, mkdirOptions);
|
|
41532
41569
|
if (isDir) return;
|
|
41533
41570
|
debug("opening read stream", dest);
|
|
41534
41571
|
const readStream = await promisify13(this.zipfile.openReadStream.bind(this.zipfile))(entry);
|
|
41535
41572
|
if (symlink) {
|
|
41536
41573
|
const link = await getStream(readStream);
|
|
41537
41574
|
debug("creating symlink", link, dest);
|
|
41538
|
-
await
|
|
41575
|
+
await fs2.symlink(link, dest);
|
|
41539
41576
|
} else {
|
|
41540
41577
|
await pipeline(readStream, createWriteStream(dest, { mode: procMode }));
|
|
41541
41578
|
}
|
|
@@ -41567,8 +41604,8 @@ var require_extract_zip = __commonJS({
|
|
|
41567
41604
|
if (!path2.isAbsolute(opts.dir)) {
|
|
41568
41605
|
throw new Error("Target directory is expected to be absolute");
|
|
41569
41606
|
}
|
|
41570
|
-
await
|
|
41571
|
-
opts.dir = await
|
|
41607
|
+
await fs2.mkdir(opts.dir, { recursive: true });
|
|
41608
|
+
opts.dir = await fs2.realpath(opts.dir);
|
|
41572
41609
|
return new Extractor(zipPath, opts).extract();
|
|
41573
41610
|
};
|
|
41574
41611
|
}
|
|
@@ -43289,7 +43326,7 @@ var require_merge = __commonJS({
|
|
|
43289
43326
|
var require_addPairToJSMap = __commonJS({
|
|
43290
43327
|
"../../node_modules/.pnpm/yaml@2.8.3/node_modules/yaml/dist/nodes/addPairToJSMap.js"(exports) {
|
|
43291
43328
|
"use strict";
|
|
43292
|
-
var
|
|
43329
|
+
var log17 = require_log();
|
|
43293
43330
|
var merge = require_merge();
|
|
43294
43331
|
var stringify = require_stringify();
|
|
43295
43332
|
var identity = require_identity();
|
|
@@ -43338,7 +43375,7 @@ var require_addPairToJSMap = __commonJS({
|
|
|
43338
43375
|
let jsonStr = JSON.stringify(strKey);
|
|
43339
43376
|
if (jsonStr.length > 40)
|
|
43340
43377
|
jsonStr = jsonStr.substring(0, 36) + '..."';
|
|
43341
|
-
|
|
43378
|
+
log17.warn(ctx.doc.options.logLevel, `Keys with collection values will be stringified due to JS Object restrictions: ${jsonStr}. Set mapAsMap: true to use object keys.`);
|
|
43342
43379
|
ctx.mapKeyWarned = true;
|
|
43343
43380
|
}
|
|
43344
43381
|
return strKey;
|
|
@@ -48427,14 +48464,14 @@ var require_parser = __commonJS({
|
|
|
48427
48464
|
case "scalar":
|
|
48428
48465
|
case "single-quoted-scalar":
|
|
48429
48466
|
case "double-quoted-scalar": {
|
|
48430
|
-
const
|
|
48467
|
+
const fs2 = this.flowScalar(this.type);
|
|
48431
48468
|
if (atNextItem || it.value) {
|
|
48432
|
-
map.items.push({ start, key:
|
|
48469
|
+
map.items.push({ start, key: fs2, sep: [] });
|
|
48433
48470
|
this.onKeyLine = true;
|
|
48434
48471
|
} else if (it.sep) {
|
|
48435
|
-
this.stack.push(
|
|
48472
|
+
this.stack.push(fs2);
|
|
48436
48473
|
} else {
|
|
48437
|
-
Object.assign(it, { key:
|
|
48474
|
+
Object.assign(it, { key: fs2, sep: [] });
|
|
48438
48475
|
this.onKeyLine = true;
|
|
48439
48476
|
}
|
|
48440
48477
|
return;
|
|
@@ -48562,13 +48599,13 @@ var require_parser = __commonJS({
|
|
|
48562
48599
|
case "scalar":
|
|
48563
48600
|
case "single-quoted-scalar":
|
|
48564
48601
|
case "double-quoted-scalar": {
|
|
48565
|
-
const
|
|
48602
|
+
const fs2 = this.flowScalar(this.type);
|
|
48566
48603
|
if (!it || it.value)
|
|
48567
|
-
fc.items.push({ start: [], key:
|
|
48604
|
+
fc.items.push({ start: [], key: fs2, sep: [] });
|
|
48568
48605
|
else if (it.sep)
|
|
48569
|
-
this.stack.push(
|
|
48606
|
+
this.stack.push(fs2);
|
|
48570
48607
|
else
|
|
48571
|
-
Object.assign(it, { key:
|
|
48608
|
+
Object.assign(it, { key: fs2, sep: [] });
|
|
48572
48609
|
return;
|
|
48573
48610
|
}
|
|
48574
48611
|
case "flow-map-end":
|
|
@@ -48734,7 +48771,7 @@ var require_public_api = __commonJS({
|
|
|
48734
48771
|
var composer = require_composer();
|
|
48735
48772
|
var Document = require_Document();
|
|
48736
48773
|
var errors = require_errors();
|
|
48737
|
-
var
|
|
48774
|
+
var log17 = require_log();
|
|
48738
48775
|
var identity = require_identity();
|
|
48739
48776
|
var lineCounter = require_line_counter();
|
|
48740
48777
|
var parser = require_parser();
|
|
@@ -48786,7 +48823,7 @@ var require_public_api = __commonJS({
|
|
|
48786
48823
|
const doc = parseDocument(src, options);
|
|
48787
48824
|
if (!doc)
|
|
48788
48825
|
return null;
|
|
48789
|
-
doc.warnings.forEach((warning) =>
|
|
48826
|
+
doc.warnings.forEach((warning) => log17.warn(doc.options.logLevel, warning));
|
|
48790
48827
|
if (doc.errors.length > 0) {
|
|
48791
48828
|
if (doc.options.logLevel !== "silent")
|
|
48792
48829
|
throw doc.errors[0];
|
|
@@ -50098,6 +50135,8 @@ __export(src_exports, {
|
|
|
50098
50135
|
QmdMemoryBackend: () => QmdMemoryBackend,
|
|
50099
50136
|
RESEARCH_EVENT_TYPES: () => RESEARCH_EVENT_TYPES,
|
|
50100
50137
|
RESEARCH_EXPORT_FORMATS: () => RESEARCH_EXPORT_FORMATS,
|
|
50138
|
+
RESEARCH_ORCHESTRATION_PHASES: () => RESEARCH_ORCHESTRATION_PHASES,
|
|
50139
|
+
RESEARCH_ORCHESTRATION_STEP_STATUSES: () => RESEARCH_ORCHESTRATION_STEP_STATUSES,
|
|
50101
50140
|
RESEARCH_RUN_STATUSES: () => RESEARCH_RUN_STATUSES,
|
|
50102
50141
|
RESEARCH_SOURCE_STATUSES: () => RESEARCH_SOURCE_STATUSES,
|
|
50103
50142
|
RESEARCH_SOURCE_TYPES: () => RESEARCH_SOURCE_TYPES,
|
|
@@ -51568,12 +51607,12 @@ var init_concurrency = __esm({
|
|
|
51568
51607
|
this._active++;
|
|
51569
51608
|
return Promise.resolve();
|
|
51570
51609
|
}
|
|
51571
|
-
return new Promise((
|
|
51610
|
+
return new Promise((resolve19) => {
|
|
51572
51611
|
this._waiters.push({
|
|
51573
51612
|
priority,
|
|
51574
51613
|
resolve: () => {
|
|
51575
51614
|
this._active++;
|
|
51576
|
-
|
|
51615
|
+
resolve19();
|
|
51577
51616
|
}
|
|
51578
51617
|
});
|
|
51579
51618
|
});
|
|
@@ -54195,20 +54234,20 @@ async function withRateLimitRetry(fn, options = {}) {
|
|
|
54195
54234
|
throw lastError ?? new Error("withRateLimitRetry: unexpected state");
|
|
54196
54235
|
}
|
|
54197
54236
|
function sleep(ms, signal) {
|
|
54198
|
-
return new Promise((
|
|
54237
|
+
return new Promise((resolve19, reject) => {
|
|
54199
54238
|
if (signal?.aborted) {
|
|
54200
54239
|
reject(signal.reason ?? new Error("Aborted"));
|
|
54201
54240
|
return;
|
|
54202
54241
|
}
|
|
54203
|
-
const timer = setTimeout(
|
|
54242
|
+
const timer = setTimeout(resolve19, ms);
|
|
54204
54243
|
if (signal) {
|
|
54205
54244
|
const onAbort = () => {
|
|
54206
54245
|
clearTimeout(timer);
|
|
54207
54246
|
reject(signal.reason ?? new Error("Aborted"));
|
|
54208
54247
|
};
|
|
54209
54248
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
54210
|
-
const origResolve =
|
|
54211
|
-
|
|
54249
|
+
const origResolve = resolve19;
|
|
54250
|
+
resolve19 = () => {
|
|
54212
54251
|
signal.removeEventListener("abort", onAbort);
|
|
54213
54252
|
origResolve();
|
|
54214
54253
|
};
|
|
@@ -54288,9 +54327,9 @@ async function readAttachmentContents(rootDir, taskId, attachments) {
|
|
|
54288
54327
|
return { attachmentContents, imageContents };
|
|
54289
54328
|
}
|
|
54290
54329
|
const { readFile: readFile19 } = await import("node:fs/promises");
|
|
54291
|
-
const { join:
|
|
54330
|
+
const { join: join41 } = await import("node:path");
|
|
54292
54331
|
for (const att of attachments) {
|
|
54293
|
-
const filePath =
|
|
54332
|
+
const filePath = join41(
|
|
54294
54333
|
rootDir,
|
|
54295
54334
|
".fusion",
|
|
54296
54335
|
"tasks",
|
|
@@ -55828,9 +55867,9 @@ Remove or replace these ids and call fn_task_create again.`
|
|
|
55828
55867
|
}
|
|
55829
55868
|
try {
|
|
55830
55869
|
const { readFile: readFile19 } = await import("node:fs/promises");
|
|
55831
|
-
const { join:
|
|
55870
|
+
const { join: join41 } = await import("node:path");
|
|
55832
55871
|
const promptContent = await readFile19(
|
|
55833
|
-
|
|
55872
|
+
join41(rootDir, promptPath),
|
|
55834
55873
|
"utf-8"
|
|
55835
55874
|
).catch((err) => {
|
|
55836
55875
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -56201,7 +56240,7 @@ import { existsSync as existsSync22 } from "node:fs";
|
|
|
56201
56240
|
import { join as join28 } from "node:path";
|
|
56202
56241
|
import { Type as Type3 } from "typebox";
|
|
56203
56242
|
async function execWithProcessGroup(command, options) {
|
|
56204
|
-
return new Promise((
|
|
56243
|
+
return new Promise((resolve19, reject) => {
|
|
56205
56244
|
if (options.signal?.aborted) {
|
|
56206
56245
|
reject(Object.assign(
|
|
56207
56246
|
new Error(`Command aborted before start: ${command}`),
|
|
@@ -56294,7 +56333,7 @@ async function execWithProcessGroup(command, options) {
|
|
|
56294
56333
|
return;
|
|
56295
56334
|
}
|
|
56296
56335
|
if (code === 0) {
|
|
56297
|
-
|
|
56336
|
+
resolve19({ stdout, stderr, bufferOverflow: stdoutOverflow || stderrOverflow });
|
|
56298
56337
|
return;
|
|
56299
56338
|
}
|
|
56300
56339
|
reject(Object.assign(
|
|
@@ -56885,10 +56924,27 @@ async function generateAiMergeSummary(commitLog, diffStat, settings, rootDir) {
|
|
|
56885
56924
|
return null;
|
|
56886
56925
|
}
|
|
56887
56926
|
}
|
|
56927
|
+
async function generateAiMergeSubject(commitLog, diffStat, settings, rootDir, branch, taskId, signal) {
|
|
56928
|
+
try {
|
|
56929
|
+
const resolved = resolveTitleSummarizerSettingsModel(settings);
|
|
56930
|
+
return await summarizeCommitSubject(
|
|
56931
|
+
diffStat,
|
|
56932
|
+
rootDir,
|
|
56933
|
+
resolved.provider,
|
|
56934
|
+
resolved.modelId,
|
|
56935
|
+
{ branch, taskId, commitLog, signal }
|
|
56936
|
+
);
|
|
56937
|
+
} catch (err) {
|
|
56938
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
56939
|
+
mergerLog.warn(`AI merge subject failed; using deterministic fallback (${message})`);
|
|
56940
|
+
return null;
|
|
56941
|
+
}
|
|
56942
|
+
}
|
|
56888
56943
|
async function buildDeterministicMergeMessage(params) {
|
|
56889
|
-
const { taskId, branch, commitLog, diffStat, includeTaskId, aiSummary } = params;
|
|
56944
|
+
const { taskId, branch, commitLog, diffStat, includeTaskId, aiSummary, aiSubject } = params;
|
|
56890
56945
|
const prefix = includeTaskId ? `feat(${taskId})` : "feat";
|
|
56891
|
-
const
|
|
56946
|
+
const subjectSummary = aiSubject?.trim().length ? aiSubject.trim() : `merge ${branch}`;
|
|
56947
|
+
const subject = `${prefix}: ${subjectSummary}`;
|
|
56892
56948
|
const trimmedCommitLog = commitLog?.trim() ?? "";
|
|
56893
56949
|
const trimmedDiffStat = diffStat?.trim() ?? "";
|
|
56894
56950
|
const commitsSection = trimmedCommitLog.length > 0 ? trimmedCommitLog : `- merge ${branch}`;
|
|
@@ -56904,7 +56960,7 @@ ${trimmedDiffStat}` : ""
|
|
|
56904
56960
|
bodyArg: `-m "${escape(body)}"`
|
|
56905
56961
|
};
|
|
56906
56962
|
}
|
|
56907
|
-
async function commitOrAmendMergeWithFixes(rootDir, taskId, branch, commitLog, includeTaskId, preAttemptHeadSha, authorArg, diffStat, settings, signal, aiSummary) {
|
|
56963
|
+
async function commitOrAmendMergeWithFixes(rootDir, taskId, branch, commitLog, includeTaskId, preAttemptHeadSha, authorArg, diffStat, settings, signal, aiSummary, aiSubject) {
|
|
56908
56964
|
try {
|
|
56909
56965
|
const { stdout: unstagedFiles } = await execAsync2("git diff --name-only", {
|
|
56910
56966
|
cwd: rootDir,
|
|
@@ -56959,7 +57015,8 @@ async function commitOrAmendMergeWithFixes(rootDir, taskId, branch, commitLog, i
|
|
|
56959
57015
|
commitLog: messageCommitLog,
|
|
56960
57016
|
diffStat: messageDiffStat,
|
|
56961
57017
|
includeTaskId,
|
|
56962
|
-
aiSummary
|
|
57018
|
+
aiSummary,
|
|
57019
|
+
aiSubject
|
|
56963
57020
|
});
|
|
56964
57021
|
const trailerArg = buildTaskIdTrailerArg(taskId);
|
|
56965
57022
|
if (!headMoved) {
|
|
@@ -58200,6 +58257,7 @@ async function aiMergeTask(store, rootDir, taskId, options = {}) {
|
|
|
58200
58257
|
diffStat = "(unable to read diff)";
|
|
58201
58258
|
}
|
|
58202
58259
|
const aiMergeSummary = settings.useAiMergeCommitSummary ? await generateAiMergeSummary(commitLog, diffStat, settings, rootDir) : null;
|
|
58260
|
+
const aiMergeSubject = settings.useAiMergeCommitSummary ? await generateAiMergeSubject(commitLog, diffStat, settings, rootDir, branch, taskId, options.signal) : null;
|
|
58203
58261
|
try {
|
|
58204
58262
|
const scopeResult = await validateDiffScope(store, taskId, diffStat, settings.strictScopeEnforcement);
|
|
58205
58263
|
for (const warning of scopeResult.warnings) {
|
|
@@ -58261,6 +58319,7 @@ async function aiMergeTask(store, rootDir, taskId, options = {}) {
|
|
|
58261
58319
|
commitLog,
|
|
58262
58320
|
diffStat,
|
|
58263
58321
|
aiSummary: aiMergeSummary,
|
|
58322
|
+
aiSubject: aiMergeSubject,
|
|
58264
58323
|
includeTaskId,
|
|
58265
58324
|
sourceIssueRef,
|
|
58266
58325
|
smartConflictResolution,
|
|
@@ -58383,7 +58442,8 @@ async function aiMergeTask(store, rootDir, taskId, options = {}) {
|
|
|
58383
58442
|
diffStat,
|
|
58384
58443
|
settings,
|
|
58385
58444
|
options.signal,
|
|
58386
|
-
aiMergeSummary
|
|
58445
|
+
aiMergeSummary,
|
|
58446
|
+
aiMergeSubject
|
|
58387
58447
|
);
|
|
58388
58448
|
if (!finalized) {
|
|
58389
58449
|
resetMergeWithWarn(rootDir, taskId, "verification-fix finalize");
|
|
@@ -58478,7 +58538,8 @@ async function aiMergeTask(store, rootDir, taskId, options = {}) {
|
|
|
58478
58538
|
diffStat,
|
|
58479
58539
|
settings,
|
|
58480
58540
|
options.signal,
|
|
58481
|
-
aiMergeSummary
|
|
58541
|
+
aiMergeSummary,
|
|
58542
|
+
aiMergeSubject
|
|
58482
58543
|
);
|
|
58483
58544
|
if (!finalized) {
|
|
58484
58545
|
resetMergeWithWarn(rootDir, taskId, "build-verification fix finalize");
|
|
@@ -58857,6 +58918,7 @@ async function executeMergeAttempt(params, aiTracker) {
|
|
|
58857
58918
|
commitLog,
|
|
58858
58919
|
diffStat,
|
|
58859
58920
|
aiSummary,
|
|
58921
|
+
aiSubject,
|
|
58860
58922
|
includeTaskId,
|
|
58861
58923
|
sourceIssueRef,
|
|
58862
58924
|
smartConflictResolution,
|
|
@@ -59102,7 +59164,8 @@ async function executeMergeAttempt(params, aiTracker) {
|
|
|
59102
59164
|
commitLog: actualContext.commitLog || commitLog,
|
|
59103
59165
|
diffStat: actualContext.diffStat || diffStat,
|
|
59104
59166
|
includeTaskId,
|
|
59105
|
-
aiSummary
|
|
59167
|
+
aiSummary,
|
|
59168
|
+
aiSubject
|
|
59106
59169
|
});
|
|
59107
59170
|
const trailerArg = buildTaskIdTrailerArg(taskId);
|
|
59108
59171
|
await execAsync2(
|
|
@@ -60684,7 +60747,7 @@ function resolveExecutorModelPair(taskModelProvider, taskModelId, settings) {
|
|
|
60684
60747
|
return { provider: void 0, modelId: void 0 };
|
|
60685
60748
|
}
|
|
60686
60749
|
function sleep2(ms) {
|
|
60687
|
-
return new Promise((
|
|
60750
|
+
return new Promise((resolve19) => setTimeout(resolve19, ms));
|
|
60688
60751
|
}
|
|
60689
60752
|
var execAsync4, stepExecLog, MAX_STEP_RETRIES, RETRY_DELAYS_MS, NOOP_TASK_STORE, StepSessionExecutor;
|
|
60690
60753
|
var init_step_session_executor = __esm({
|
|
@@ -64909,7 +64972,7 @@ Review the work done in this worktree and evaluate it against the criteria in yo
|
|
|
64909
64972
|
);
|
|
64910
64973
|
}
|
|
64911
64974
|
const delay2 = this.WORKTREE_RETRY_DELAYS[attempt] || 1e3;
|
|
64912
|
-
await new Promise((
|
|
64975
|
+
await new Promise((resolve19) => setTimeout(resolve19, delay2));
|
|
64913
64976
|
}
|
|
64914
64977
|
}
|
|
64915
64978
|
throw new Error("Unexpected exit from worktree creation retry loop");
|
|
@@ -65552,8 +65615,8 @@ Review the work done in this worktree and evaluate it against the criteria in yo
|
|
|
65552
65615
|
executorLog.warn(`${taskId}: recoverApprovedStepsOnResume getTask failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
65553
65616
|
return;
|
|
65554
65617
|
}
|
|
65555
|
-
const
|
|
65556
|
-
if (
|
|
65618
|
+
const log17 = detail.log ?? [];
|
|
65619
|
+
if (log17.length === 0) return;
|
|
65557
65620
|
let recovered = 0;
|
|
65558
65621
|
for (let i = 0; i < detail.steps.length; i++) {
|
|
65559
65622
|
if (detail.steps[i].status !== "in-progress") continue;
|
|
@@ -65562,8 +65625,8 @@ Review the work done in this worktree and evaluate it against the criteria in yo
|
|
|
65562
65625
|
const stepName = detail.steps[i].name;
|
|
65563
65626
|
const transitionPrefix = `Step ${i} (${stepName}) \u2192 `;
|
|
65564
65627
|
const approvePrefix = `code review Step ${i}:`;
|
|
65565
|
-
for (let j = 0; j <
|
|
65566
|
-
const action =
|
|
65628
|
+
for (let j = 0; j < log17.length; j++) {
|
|
65629
|
+
const action = log17[j].action || "";
|
|
65567
65630
|
if (action.startsWith(transitionPrefix)) {
|
|
65568
65631
|
const status = action.slice(transitionPrefix.length).trim();
|
|
65569
65632
|
if (status === "pending") lastPendingAt = j;
|
|
@@ -70492,6 +70555,1651 @@ ${taskDetail.prompt}` : "No PROMPT.md available.",
|
|
|
70492
70555
|
}
|
|
70493
70556
|
});
|
|
70494
70557
|
|
|
70558
|
+
// ../engine/src/research-orchestrator.ts
|
|
70559
|
+
var log6, ResearchOrchestrator;
|
|
70560
|
+
var init_research_orchestrator = __esm({
|
|
70561
|
+
"../engine/src/research-orchestrator.ts"() {
|
|
70562
|
+
"use strict";
|
|
70563
|
+
init_concurrency();
|
|
70564
|
+
init_logger2();
|
|
70565
|
+
log6 = createLogger2("research-orchestrator");
|
|
70566
|
+
ResearchOrchestrator = class {
|
|
70567
|
+
store;
|
|
70568
|
+
stepRunner;
|
|
70569
|
+
semaphore;
|
|
70570
|
+
activeRuns = /* @__PURE__ */ new Map();
|
|
70571
|
+
cancellation = /* @__PURE__ */ new Map();
|
|
70572
|
+
constructor(options) {
|
|
70573
|
+
this.store = options.store;
|
|
70574
|
+
this.stepRunner = options.stepRunner;
|
|
70575
|
+
this.semaphore = new AgentSemaphore(options.maxConcurrentRuns ?? 3);
|
|
70576
|
+
}
|
|
70577
|
+
createRun(config) {
|
|
70578
|
+
const run = this.store.createRun({
|
|
70579
|
+
query: "",
|
|
70580
|
+
providerConfig: config,
|
|
70581
|
+
metadata: {
|
|
70582
|
+
orchestration: {
|
|
70583
|
+
phase: "planning",
|
|
70584
|
+
stepIndex: 0,
|
|
70585
|
+
totalSteps: this.computeTotalSteps(config)
|
|
70586
|
+
}
|
|
70587
|
+
}
|
|
70588
|
+
});
|
|
70589
|
+
return run.id;
|
|
70590
|
+
}
|
|
70591
|
+
async startRun(runId, query, options = {}) {
|
|
70592
|
+
const run = this.store.getRun(runId);
|
|
70593
|
+
if (!run) throw new Error(`Research run not found: ${runId}`);
|
|
70594
|
+
const config = run.providerConfig ?? {};
|
|
70595
|
+
const controller = new AbortController();
|
|
70596
|
+
if (options.abortSignal) {
|
|
70597
|
+
options.abortSignal.addEventListener("abort", () => controller.abort(options.abortSignal?.reason), { once: true });
|
|
70598
|
+
}
|
|
70599
|
+
const totalSteps = this.computeTotalSteps(config);
|
|
70600
|
+
this.activeRuns.set(runId, {
|
|
70601
|
+
controller,
|
|
70602
|
+
phase: "planning",
|
|
70603
|
+
stepIndex: 0,
|
|
70604
|
+
totalSteps,
|
|
70605
|
+
config
|
|
70606
|
+
});
|
|
70607
|
+
await this.semaphore.run(async () => {
|
|
70608
|
+
this.store.updateRun(runId, { query, status: "running", startedAt: (/* @__PURE__ */ new Date()).toISOString(), error: null });
|
|
70609
|
+
await this.runPhases(runId, query, config, controller.signal);
|
|
70610
|
+
});
|
|
70611
|
+
const updated = this.store.getRun(runId);
|
|
70612
|
+
if (!updated) throw new Error(`Research run not found after start: ${runId}`);
|
|
70613
|
+
return updated;
|
|
70614
|
+
}
|
|
70615
|
+
cancelRun(runId) {
|
|
70616
|
+
const active = this.activeRuns.get(runId);
|
|
70617
|
+
if (!active) return false;
|
|
70618
|
+
const state = {
|
|
70619
|
+
runId,
|
|
70620
|
+
controller: active.controller,
|
|
70621
|
+
requestedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
70622
|
+
gracefulShutdown: true,
|
|
70623
|
+
reason: "Cancelled by user"
|
|
70624
|
+
};
|
|
70625
|
+
this.cancellation.set(runId, state);
|
|
70626
|
+
active.controller.abort(new Error("Research run cancelled"));
|
|
70627
|
+
return true;
|
|
70628
|
+
}
|
|
70629
|
+
retryRun(runId) {
|
|
70630
|
+
const run = this.store.getRun(runId);
|
|
70631
|
+
if (!run) throw new Error(`Research run not found: ${runId}`);
|
|
70632
|
+
if (run.status !== "failed" && run.status !== "cancelled") {
|
|
70633
|
+
throw new Error(`Research run ${runId} is not retryable (status=${run.status})`);
|
|
70634
|
+
}
|
|
70635
|
+
const next = this.store.createRun({
|
|
70636
|
+
query: run.query,
|
|
70637
|
+
topic: run.topic,
|
|
70638
|
+
providerConfig: run.providerConfig,
|
|
70639
|
+
tags: [...run.tags],
|
|
70640
|
+
metadata: {
|
|
70641
|
+
...run.metadata ?? {},
|
|
70642
|
+
retryOfRunId: run.id
|
|
70643
|
+
}
|
|
70644
|
+
});
|
|
70645
|
+
this.store.addEvent(next.id, {
|
|
70646
|
+
type: "info",
|
|
70647
|
+
message: `Retry run created from ${run.id}`,
|
|
70648
|
+
metadata: { retryOfRunId: run.id }
|
|
70649
|
+
});
|
|
70650
|
+
return next.id;
|
|
70651
|
+
}
|
|
70652
|
+
getRunStatus(runId) {
|
|
70653
|
+
const run = this.store.getRun(runId);
|
|
70654
|
+
if (!run) throw new Error(`Research run not found: ${runId}`);
|
|
70655
|
+
const active = this.activeRuns.get(runId);
|
|
70656
|
+
const metadata = run.metadata?.orchestration ?? {};
|
|
70657
|
+
const phase = active?.phase ?? metadata.phase ?? this.statusToPhase(run.status);
|
|
70658
|
+
const stepIndex = active?.stepIndex ?? Number(metadata.stepIndex ?? 0);
|
|
70659
|
+
const totalSteps = active?.totalSteps ?? Number(metadata.totalSteps ?? 0);
|
|
70660
|
+
return {
|
|
70661
|
+
runId,
|
|
70662
|
+
status: run.status,
|
|
70663
|
+
phase,
|
|
70664
|
+
stepIndex,
|
|
70665
|
+
totalSteps,
|
|
70666
|
+
progress: totalSteps > 0 ? Math.min(1, stepIndex / totalSteps) : 0,
|
|
70667
|
+
active: this.activeRuns.has(runId)
|
|
70668
|
+
};
|
|
70669
|
+
}
|
|
70670
|
+
async runPhases(runId, query, config, signal) {
|
|
70671
|
+
try {
|
|
70672
|
+
await this.runPlanning(runId, query, config, signal);
|
|
70673
|
+
const sources = await this.runSearching(runId, query, config, signal);
|
|
70674
|
+
const fetchedSources = await this.runFetching(runId, sources, config, signal);
|
|
70675
|
+
const synthesis = await this.runSynthesis(runId, query, fetchedSources, config, signal);
|
|
70676
|
+
await this.runFinalizing(runId, synthesis.output, synthesis.citations, synthesis.confidence, signal);
|
|
70677
|
+
this.store.updateStatus(runId, "completed");
|
|
70678
|
+
this.transitionPhase(runId, "completed", "Research run completed");
|
|
70679
|
+
} catch (err) {
|
|
70680
|
+
if (signal.aborted) {
|
|
70681
|
+
this.onCancelled(runId);
|
|
70682
|
+
} else {
|
|
70683
|
+
const { message, detail } = formatError(err);
|
|
70684
|
+
this.store.addEvent(runId, {
|
|
70685
|
+
type: "error",
|
|
70686
|
+
message: `Research run failed: ${message}`,
|
|
70687
|
+
metadata: { detail }
|
|
70688
|
+
});
|
|
70689
|
+
this.store.updateStatus(runId, "failed", { error: message });
|
|
70690
|
+
this.transitionPhase(runId, "failed", "Research run failed", { error: message });
|
|
70691
|
+
}
|
|
70692
|
+
} finally {
|
|
70693
|
+
this.activeRuns.delete(runId);
|
|
70694
|
+
this.cancellation.delete(runId);
|
|
70695
|
+
}
|
|
70696
|
+
}
|
|
70697
|
+
async runPlanning(runId, query, config, _signal) {
|
|
70698
|
+
this.transitionPhase(runId, "planning", "Planning research execution");
|
|
70699
|
+
this.stepStarted(runId, {
|
|
70700
|
+
id: `${runId}-planning`,
|
|
70701
|
+
type: "synthesis-pass",
|
|
70702
|
+
phase: "planning",
|
|
70703
|
+
status: "running",
|
|
70704
|
+
order: 0,
|
|
70705
|
+
name: "Create plan",
|
|
70706
|
+
input: { query, providerCount: config.providers.length },
|
|
70707
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
70708
|
+
});
|
|
70709
|
+
this.stepCompleted(runId, `${runId}-planning`, { query });
|
|
70710
|
+
}
|
|
70711
|
+
async runSearching(runId, query, config, signal) {
|
|
70712
|
+
this.throwIfAborted(signal);
|
|
70713
|
+
this.transitionPhase(runId, "searching", "Searching sources");
|
|
70714
|
+
const allSources = [];
|
|
70715
|
+
for (const provider of config.providers) {
|
|
70716
|
+
this.throwIfAborted(signal);
|
|
70717
|
+
const step = this.createStep(runId, "source-query", "searching", `Search with ${provider.type}`, {
|
|
70718
|
+
query,
|
|
70719
|
+
provider: provider.type
|
|
70720
|
+
});
|
|
70721
|
+
this.stepStarted(runId, step);
|
|
70722
|
+
const result = await this.stepRunner.runSourceQuery(query, provider.type, provider.config, signal);
|
|
70723
|
+
if (!result.ok || !result.data) {
|
|
70724
|
+
this.stepFailed(runId, step.id, result.error?.message ?? `Provider ${provider.type} returned no data`, result.error);
|
|
70725
|
+
continue;
|
|
70726
|
+
}
|
|
70727
|
+
for (const source of result.data.slice(0, Math.max(0, config.maxSources - allSources.length))) {
|
|
70728
|
+
const saved = this.store.addSource(runId, source);
|
|
70729
|
+
allSources.push(saved);
|
|
70730
|
+
this.store.addEvent(runId, {
|
|
70731
|
+
type: "source_added",
|
|
70732
|
+
message: `Source found: ${saved.reference}`,
|
|
70733
|
+
metadata: { sourceId: saved.id, provider: provider.type }
|
|
70734
|
+
});
|
|
70735
|
+
}
|
|
70736
|
+
this.stepCompleted(runId, step.id, { sourceCount: result.data.length });
|
|
70737
|
+
if (allSources.length >= config.maxSources) break;
|
|
70738
|
+
}
|
|
70739
|
+
if (allSources.length === 0) {
|
|
70740
|
+
throw new Error("No sources discovered during search phase");
|
|
70741
|
+
}
|
|
70742
|
+
return allSources;
|
|
70743
|
+
}
|
|
70744
|
+
async runFetching(runId, sources, config, signal) {
|
|
70745
|
+
this.throwIfAborted(signal);
|
|
70746
|
+
this.transitionPhase(runId, "fetching", "Fetching source content");
|
|
70747
|
+
const fetched = [];
|
|
70748
|
+
const provider = config.providers[0];
|
|
70749
|
+
for (const source of sources.slice(0, config.maxSources)) {
|
|
70750
|
+
this.throwIfAborted(signal);
|
|
70751
|
+
const step = this.createStep(runId, "content-fetch", "fetching", `Fetch ${source.reference}`, {
|
|
70752
|
+
sourceId: source.id
|
|
70753
|
+
});
|
|
70754
|
+
this.stepStarted(runId, step);
|
|
70755
|
+
const result = await this.stepRunner.runContentFetch(source.reference, provider?.config, signal);
|
|
70756
|
+
if (!result.ok || !result.data) {
|
|
70757
|
+
this.stepFailed(runId, step.id, result.error?.message ?? "Failed to fetch source content", result.error);
|
|
70758
|
+
continue;
|
|
70759
|
+
}
|
|
70760
|
+
const updated = {
|
|
70761
|
+
...source,
|
|
70762
|
+
content: result.data.content,
|
|
70763
|
+
metadata: {
|
|
70764
|
+
...source.metadata ?? {},
|
|
70765
|
+
...result.data.metadata ?? {}
|
|
70766
|
+
},
|
|
70767
|
+
status: "completed",
|
|
70768
|
+
fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
70769
|
+
};
|
|
70770
|
+
this.store.updateSource(runId, source.id, updated);
|
|
70771
|
+
fetched.push(updated);
|
|
70772
|
+
this.stepCompleted(runId, step.id, { fetched: true });
|
|
70773
|
+
}
|
|
70774
|
+
if (fetched.length === 0) {
|
|
70775
|
+
throw new Error("No source content fetched");
|
|
70776
|
+
}
|
|
70777
|
+
return fetched;
|
|
70778
|
+
}
|
|
70779
|
+
async runSynthesis(runId, query, sources, config, signal) {
|
|
70780
|
+
this.throwIfAborted(signal);
|
|
70781
|
+
this.transitionPhase(runId, "synthesizing", "Synthesizing findings");
|
|
70782
|
+
let final;
|
|
70783
|
+
for (let round = 1; round <= Math.max(1, config.maxSynthesisRounds); round++) {
|
|
70784
|
+
this.throwIfAborted(signal);
|
|
70785
|
+
const step = this.createStep(runId, "synthesis-pass", "synthesizing", `Synthesis round ${round}`, {
|
|
70786
|
+
round
|
|
70787
|
+
});
|
|
70788
|
+
this.stepStarted(runId, step);
|
|
70789
|
+
const request2 = {
|
|
70790
|
+
query,
|
|
70791
|
+
sources,
|
|
70792
|
+
round,
|
|
70793
|
+
desiredFormat: "markdown"
|
|
70794
|
+
};
|
|
70795
|
+
const result = await this.stepRunner.runSynthesis(request2, config.synthesisModel, signal);
|
|
70796
|
+
if (!result.ok || !result.data) {
|
|
70797
|
+
this.stepFailed(runId, step.id, result.error?.message ?? "Synthesis failed", result.error);
|
|
70798
|
+
continue;
|
|
70799
|
+
}
|
|
70800
|
+
final = result.data;
|
|
70801
|
+
this.store.addEvent(runId, {
|
|
70802
|
+
type: "progress",
|
|
70803
|
+
message: `Synthesis round ${round} completed`,
|
|
70804
|
+
metadata: { round, confidence: result.data.confidence }
|
|
70805
|
+
});
|
|
70806
|
+
this.stepCompleted(runId, step.id, { round, citations: result.data.citations.length });
|
|
70807
|
+
}
|
|
70808
|
+
if (!final) {
|
|
70809
|
+
throw new Error("All synthesis rounds failed");
|
|
70810
|
+
}
|
|
70811
|
+
return final;
|
|
70812
|
+
}
|
|
70813
|
+
async runFinalizing(runId, output, citations, confidence, signal) {
|
|
70814
|
+
this.throwIfAborted(signal);
|
|
70815
|
+
this.transitionPhase(runId, "finalizing", "Finalizing research results");
|
|
70816
|
+
this.store.setResults(runId, {
|
|
70817
|
+
summary: output,
|
|
70818
|
+
findings: [
|
|
70819
|
+
{
|
|
70820
|
+
heading: "Synthesis",
|
|
70821
|
+
content: output,
|
|
70822
|
+
sources: citations,
|
|
70823
|
+
confidence
|
|
70824
|
+
}
|
|
70825
|
+
],
|
|
70826
|
+
citations,
|
|
70827
|
+
synthesizedOutput: output
|
|
70828
|
+
});
|
|
70829
|
+
}
|
|
70830
|
+
onCancelled(runId) {
|
|
70831
|
+
const cancellation = this.cancellation.get(runId);
|
|
70832
|
+
this.store.addEvent(runId, {
|
|
70833
|
+
type: "warning",
|
|
70834
|
+
message: "Research run cancelled",
|
|
70835
|
+
metadata: {
|
|
70836
|
+
requestedAt: cancellation?.requestedAt,
|
|
70837
|
+
reason: cancellation?.reason
|
|
70838
|
+
}
|
|
70839
|
+
});
|
|
70840
|
+
this.store.updateStatus(runId, "cancelled", {
|
|
70841
|
+
cancelledAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
70842
|
+
error: cancellation?.reason
|
|
70843
|
+
});
|
|
70844
|
+
this.transitionPhase(runId, "cancelled", "Research run cancelled");
|
|
70845
|
+
}
|
|
70846
|
+
transitionPhase(runId, phase, message, metadata) {
|
|
70847
|
+
const active = this.activeRuns.get(runId);
|
|
70848
|
+
if (active) {
|
|
70849
|
+
active.phase = phase;
|
|
70850
|
+
}
|
|
70851
|
+
this.store.updateRun(runId, {
|
|
70852
|
+
metadata: {
|
|
70853
|
+
orchestration: {
|
|
70854
|
+
phase,
|
|
70855
|
+
stepIndex: active?.stepIndex ?? 0,
|
|
70856
|
+
totalSteps: active?.totalSteps ?? 0
|
|
70857
|
+
}
|
|
70858
|
+
}
|
|
70859
|
+
});
|
|
70860
|
+
this.store.addEvent(runId, {
|
|
70861
|
+
type: "progress",
|
|
70862
|
+
message,
|
|
70863
|
+
metadata: {
|
|
70864
|
+
orchestrationEventType: "phase-changed",
|
|
70865
|
+
phase,
|
|
70866
|
+
...metadata ?? {}
|
|
70867
|
+
}
|
|
70868
|
+
});
|
|
70869
|
+
log6.log(`${runId}: phase changed -> ${phase}`);
|
|
70870
|
+
}
|
|
70871
|
+
stepStarted(runId, step) {
|
|
70872
|
+
this.bumpStep(runId, step.order);
|
|
70873
|
+
this.store.addEvent(runId, {
|
|
70874
|
+
type: "progress",
|
|
70875
|
+
message: `${step.name} started`,
|
|
70876
|
+
metadata: {
|
|
70877
|
+
orchestrationEventType: "step-started",
|
|
70878
|
+
step
|
|
70879
|
+
}
|
|
70880
|
+
});
|
|
70881
|
+
}
|
|
70882
|
+
stepCompleted(runId, stepId, output) {
|
|
70883
|
+
this.store.addEvent(runId, {
|
|
70884
|
+
type: "progress",
|
|
70885
|
+
message: `${stepId} completed`,
|
|
70886
|
+
metadata: {
|
|
70887
|
+
orchestrationEventType: "step-completed",
|
|
70888
|
+
stepId,
|
|
70889
|
+
output
|
|
70890
|
+
}
|
|
70891
|
+
});
|
|
70892
|
+
}
|
|
70893
|
+
stepFailed(runId, stepId, errorMessage, errorMeta) {
|
|
70894
|
+
this.store.addEvent(runId, {
|
|
70895
|
+
type: "error",
|
|
70896
|
+
message: `${stepId} failed: ${errorMessage}`,
|
|
70897
|
+
metadata: {
|
|
70898
|
+
orchestrationEventType: "step-failed",
|
|
70899
|
+
stepId,
|
|
70900
|
+
...errorMeta ?? {}
|
|
70901
|
+
}
|
|
70902
|
+
});
|
|
70903
|
+
}
|
|
70904
|
+
bumpStep(runId, stepIndex) {
|
|
70905
|
+
const active = this.activeRuns.get(runId);
|
|
70906
|
+
if (!active) return;
|
|
70907
|
+
active.stepIndex = stepIndex;
|
|
70908
|
+
this.store.updateRun(runId, {
|
|
70909
|
+
metadata: {
|
|
70910
|
+
orchestration: {
|
|
70911
|
+
phase: active.phase,
|
|
70912
|
+
stepIndex: active.stepIndex,
|
|
70913
|
+
totalSteps: active.totalSteps
|
|
70914
|
+
}
|
|
70915
|
+
}
|
|
70916
|
+
});
|
|
70917
|
+
}
|
|
70918
|
+
createStep(runId, type, phase, name, input) {
|
|
70919
|
+
const active = this.activeRuns.get(runId);
|
|
70920
|
+
const order = (active?.stepIndex ?? 0) + 1;
|
|
70921
|
+
return {
|
|
70922
|
+
id: `${runId}-${phase}-${order}`,
|
|
70923
|
+
type,
|
|
70924
|
+
phase,
|
|
70925
|
+
status: "running",
|
|
70926
|
+
order,
|
|
70927
|
+
name,
|
|
70928
|
+
input,
|
|
70929
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
70930
|
+
};
|
|
70931
|
+
}
|
|
70932
|
+
computeTotalSteps(config) {
|
|
70933
|
+
const providers = Math.max(1, config.providers.length);
|
|
70934
|
+
return 1 + providers + Math.max(1, config.maxSources) + Math.max(1, config.maxSynthesisRounds) + 1;
|
|
70935
|
+
}
|
|
70936
|
+
statusToPhase(status) {
|
|
70937
|
+
if (status === "completed") return "completed";
|
|
70938
|
+
if (status === "failed") return "failed";
|
|
70939
|
+
if (status === "cancelled") return "cancelled";
|
|
70940
|
+
return "planning";
|
|
70941
|
+
}
|
|
70942
|
+
throwIfAborted(signal) {
|
|
70943
|
+
if (signal.aborted) {
|
|
70944
|
+
throw signal.reason ?? new Error("Research run aborted");
|
|
70945
|
+
}
|
|
70946
|
+
}
|
|
70947
|
+
};
|
|
70948
|
+
}
|
|
70949
|
+
});
|
|
70950
|
+
|
|
70951
|
+
// ../engine/src/research-step-runner.ts
|
|
70952
|
+
var log7, DEFAULT_QUERY_TIMEOUT_MS, DEFAULT_FETCH_TIMEOUT_MS, DEFAULT_SYNTHESIS_TIMEOUT_MS, ResearchStepTimeoutError, ResearchStepAbortError, ResearchStepProviderError, ResearchStepRunner;
|
|
70953
|
+
var init_research_step_runner = __esm({
|
|
70954
|
+
"../engine/src/research-step-runner.ts"() {
|
|
70955
|
+
"use strict";
|
|
70956
|
+
init_logger2();
|
|
70957
|
+
log7 = createLogger2("research-step-runner");
|
|
70958
|
+
DEFAULT_QUERY_TIMEOUT_MS = 3e4;
|
|
70959
|
+
DEFAULT_FETCH_TIMEOUT_MS = 6e4;
|
|
70960
|
+
DEFAULT_SYNTHESIS_TIMEOUT_MS = 12e4;
|
|
70961
|
+
ResearchStepTimeoutError = class extends Error {
|
|
70962
|
+
constructor(step, timeoutMs) {
|
|
70963
|
+
super(`${step} timed out after ${timeoutMs}ms`);
|
|
70964
|
+
this.name = "ResearchStepTimeoutError";
|
|
70965
|
+
}
|
|
70966
|
+
};
|
|
70967
|
+
ResearchStepAbortError = class extends Error {
|
|
70968
|
+
constructor(step) {
|
|
70969
|
+
super(`${step} aborted`);
|
|
70970
|
+
this.name = "ResearchStepAbortError";
|
|
70971
|
+
}
|
|
70972
|
+
};
|
|
70973
|
+
ResearchStepProviderError = class extends Error {
|
|
70974
|
+
constructor(step, message) {
|
|
70975
|
+
super(`${step} provider error: ${message}`);
|
|
70976
|
+
this.name = "ResearchStepProviderError";
|
|
70977
|
+
}
|
|
70978
|
+
};
|
|
70979
|
+
ResearchStepRunner = class {
|
|
70980
|
+
providers;
|
|
70981
|
+
synthesisRunner;
|
|
70982
|
+
constructor(options = {}) {
|
|
70983
|
+
this.providers = new Map((options.providers ?? []).map((provider) => [provider.type, provider]));
|
|
70984
|
+
this.synthesisRunner = options.synthesisRunner;
|
|
70985
|
+
}
|
|
70986
|
+
async runSourceQuery(query, providerType, config = {}, signal) {
|
|
70987
|
+
const provider = this.providers.get(providerType);
|
|
70988
|
+
if (!provider || !provider.isConfigured()) {
|
|
70989
|
+
return this.unconfigured(`provider ${providerType} is not configured`);
|
|
70990
|
+
}
|
|
70991
|
+
try {
|
|
70992
|
+
const data = await this.withTimeout(
|
|
70993
|
+
`source-query:${providerType}`,
|
|
70994
|
+
provider.search(query, config, signal),
|
|
70995
|
+
config.timeoutMs ?? DEFAULT_QUERY_TIMEOUT_MS,
|
|
70996
|
+
signal
|
|
70997
|
+
);
|
|
70998
|
+
return { ok: true, data };
|
|
70999
|
+
} catch (error) {
|
|
71000
|
+
return this.classifyError("source-query", error);
|
|
71001
|
+
}
|
|
71002
|
+
}
|
|
71003
|
+
async runContentFetch(url, config = {}, signal) {
|
|
71004
|
+
const provider = this.findFirstConfiguredProvider();
|
|
71005
|
+
if (!provider) {
|
|
71006
|
+
return this.unconfigured("no configured provider available for content fetch");
|
|
71007
|
+
}
|
|
71008
|
+
try {
|
|
71009
|
+
const data = await this.withTimeout(
|
|
71010
|
+
`content-fetch:${provider.type}`,
|
|
71011
|
+
provider.fetchContent(url, config, signal),
|
|
71012
|
+
config.timeoutMs ?? DEFAULT_FETCH_TIMEOUT_MS,
|
|
71013
|
+
signal
|
|
71014
|
+
);
|
|
71015
|
+
return { ok: true, data };
|
|
71016
|
+
} catch (error) {
|
|
71017
|
+
return this.classifyError("content-fetch", error);
|
|
71018
|
+
}
|
|
71019
|
+
}
|
|
71020
|
+
async runSynthesis(request2, modelSettings = {}, signal) {
|
|
71021
|
+
if (!this.synthesisRunner) {
|
|
71022
|
+
return this.unconfigured("synthesis provider is not configured");
|
|
71023
|
+
}
|
|
71024
|
+
try {
|
|
71025
|
+
const timeoutMs = modelSettings.timeoutMs ?? DEFAULT_SYNTHESIS_TIMEOUT_MS;
|
|
71026
|
+
const data = await this.withTimeout(
|
|
71027
|
+
"synthesis",
|
|
71028
|
+
this.synthesisRunner(request2, modelSettings, signal),
|
|
71029
|
+
timeoutMs,
|
|
71030
|
+
signal
|
|
71031
|
+
);
|
|
71032
|
+
return { ok: true, data };
|
|
71033
|
+
} catch (error) {
|
|
71034
|
+
return this.classifyError("synthesis", error);
|
|
71035
|
+
}
|
|
71036
|
+
}
|
|
71037
|
+
findFirstConfiguredProvider() {
|
|
71038
|
+
for (const provider of this.providers.values()) {
|
|
71039
|
+
if (provider.isConfigured()) return provider;
|
|
71040
|
+
}
|
|
71041
|
+
return void 0;
|
|
71042
|
+
}
|
|
71043
|
+
classifyError(step, error) {
|
|
71044
|
+
if (error instanceof ResearchStepTimeoutError) {
|
|
71045
|
+
return { ok: false, error: { code: "timeout", message: error.message, retryable: true } };
|
|
71046
|
+
}
|
|
71047
|
+
if (error instanceof ResearchStepAbortError) {
|
|
71048
|
+
return { ok: false, error: { code: "aborted", message: error.message, retryable: false } };
|
|
71049
|
+
}
|
|
71050
|
+
const { message, detail } = formatError(error);
|
|
71051
|
+
log7.warn(`${step} failed`, detail);
|
|
71052
|
+
return {
|
|
71053
|
+
ok: false,
|
|
71054
|
+
error: {
|
|
71055
|
+
code: "provider_error",
|
|
71056
|
+
message,
|
|
71057
|
+
retryable: true
|
|
71058
|
+
}
|
|
71059
|
+
};
|
|
71060
|
+
}
|
|
71061
|
+
unconfigured(message) {
|
|
71062
|
+
return {
|
|
71063
|
+
ok: false,
|
|
71064
|
+
error: {
|
|
71065
|
+
code: "provider_not_configured",
|
|
71066
|
+
message,
|
|
71067
|
+
retryable: false
|
|
71068
|
+
}
|
|
71069
|
+
};
|
|
71070
|
+
}
|
|
71071
|
+
async withTimeout(step, promise, timeoutMs, signal) {
|
|
71072
|
+
if (signal?.aborted) {
|
|
71073
|
+
throw new ResearchStepAbortError(step);
|
|
71074
|
+
}
|
|
71075
|
+
let timeoutId;
|
|
71076
|
+
let abortListener;
|
|
71077
|
+
const abortPromise = new Promise((_, reject) => {
|
|
71078
|
+
if (!signal) return;
|
|
71079
|
+
abortListener = () => reject(new ResearchStepAbortError(step));
|
|
71080
|
+
signal.addEventListener("abort", abortListener, { once: true });
|
|
71081
|
+
});
|
|
71082
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
71083
|
+
timeoutId = setTimeout(() => reject(new ResearchStepTimeoutError(step, timeoutMs)), timeoutMs);
|
|
71084
|
+
});
|
|
71085
|
+
try {
|
|
71086
|
+
return await Promise.race([promise, timeoutPromise, abortPromise]);
|
|
71087
|
+
} finally {
|
|
71088
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
71089
|
+
if (signal && abortListener) {
|
|
71090
|
+
signal.removeEventListener("abort", abortListener);
|
|
71091
|
+
}
|
|
71092
|
+
}
|
|
71093
|
+
}
|
|
71094
|
+
};
|
|
71095
|
+
}
|
|
71096
|
+
});
|
|
71097
|
+
|
|
71098
|
+
// ../engine/src/research/types.ts
|
|
71099
|
+
var ResearchProviderError;
|
|
71100
|
+
var init_types2 = __esm({
|
|
71101
|
+
"../engine/src/research/types.ts"() {
|
|
71102
|
+
"use strict";
|
|
71103
|
+
ResearchProviderError = class extends Error {
|
|
71104
|
+
providerType;
|
|
71105
|
+
code;
|
|
71106
|
+
retryable;
|
|
71107
|
+
constructor(options) {
|
|
71108
|
+
super(options.message, options.cause ? { cause: options.cause } : void 0);
|
|
71109
|
+
this.name = "ResearchProviderError";
|
|
71110
|
+
this.providerType = options.providerType;
|
|
71111
|
+
this.code = options.code;
|
|
71112
|
+
this.retryable = options.retryable ?? false;
|
|
71113
|
+
}
|
|
71114
|
+
};
|
|
71115
|
+
}
|
|
71116
|
+
});
|
|
71117
|
+
|
|
71118
|
+
// ../engine/src/research/providers/github-provider.ts
|
|
71119
|
+
function isGhError(value) {
|
|
71120
|
+
return value instanceof Error && "stderr" in value && "stdout" in value && "code" in value;
|
|
71121
|
+
}
|
|
71122
|
+
function parseGitHubUrl(url) {
|
|
71123
|
+
let parsedUrl;
|
|
71124
|
+
try {
|
|
71125
|
+
parsedUrl = new URL(url);
|
|
71126
|
+
} catch {
|
|
71127
|
+
return null;
|
|
71128
|
+
}
|
|
71129
|
+
if (!/(^|\.)github\.com$/i.test(parsedUrl.hostname)) {
|
|
71130
|
+
return null;
|
|
71131
|
+
}
|
|
71132
|
+
const segments = parsedUrl.pathname.split("/").filter(Boolean);
|
|
71133
|
+
if (segments.length < 2) return null;
|
|
71134
|
+
const [owner, repo, section, ...rest] = segments;
|
|
71135
|
+
if (!section) {
|
|
71136
|
+
return { owner, repo, kind: "repo" };
|
|
71137
|
+
}
|
|
71138
|
+
if (section === "issues" && rest[0]) {
|
|
71139
|
+
return { owner, repo, kind: "issue", number: rest[0] };
|
|
71140
|
+
}
|
|
71141
|
+
if (section === "pull" && rest[0]) {
|
|
71142
|
+
return { owner, repo, kind: "pr", number: rest[0] };
|
|
71143
|
+
}
|
|
71144
|
+
if (section === "blob" && rest.length >= 2) {
|
|
71145
|
+
return { owner, repo, kind: "file", ref: rest[0], filePath: rest.slice(1).join("/") };
|
|
71146
|
+
}
|
|
71147
|
+
return null;
|
|
71148
|
+
}
|
|
71149
|
+
var log8, DEFAULT_TIMEOUT_MS, GitHubProvider;
|
|
71150
|
+
var init_github_provider = __esm({
|
|
71151
|
+
"../engine/src/research/providers/github-provider.ts"() {
|
|
71152
|
+
"use strict";
|
|
71153
|
+
init_src();
|
|
71154
|
+
init_logger2();
|
|
71155
|
+
init_types2();
|
|
71156
|
+
log8 = createLogger2("research:github");
|
|
71157
|
+
DEFAULT_TIMEOUT_MS = 3e4;
|
|
71158
|
+
GitHubProvider = class {
|
|
71159
|
+
type = "github";
|
|
71160
|
+
async search(query, config = {}, signal) {
|
|
71161
|
+
const timeoutMs = Number(config.timeoutMs ?? DEFAULT_TIMEOUT_MS);
|
|
71162
|
+
const maxResults = Number(config.maxResults ?? 10);
|
|
71163
|
+
const searchType = config.metadata?.searchType ?? "both";
|
|
71164
|
+
const sources = [];
|
|
71165
|
+
try {
|
|
71166
|
+
if (searchType === "repos" || searchType === "both") {
|
|
71167
|
+
const repos = await runGhJsonAsync(
|
|
71168
|
+
[
|
|
71169
|
+
"search",
|
|
71170
|
+
"repos",
|
|
71171
|
+
query,
|
|
71172
|
+
"--json",
|
|
71173
|
+
"fullName,description,htmlUrl,stargazersCount,language,updatedAt",
|
|
71174
|
+
"--limit",
|
|
71175
|
+
String(maxResults)
|
|
71176
|
+
],
|
|
71177
|
+
{ signal, timeoutMs }
|
|
71178
|
+
);
|
|
71179
|
+
sources.push(
|
|
71180
|
+
...repos.slice(0, maxResults).map((repo, idx) => ({
|
|
71181
|
+
id: `github-repo-${idx}-${repo.htmlUrl}`,
|
|
71182
|
+
type: "github",
|
|
71183
|
+
reference: repo.htmlUrl,
|
|
71184
|
+
title: repo.fullName,
|
|
71185
|
+
excerpt: repo.description ?? "",
|
|
71186
|
+
status: "completed",
|
|
71187
|
+
metadata: {
|
|
71188
|
+
resultType: "repo",
|
|
71189
|
+
stars: repo.stargazersCount ?? 0,
|
|
71190
|
+
language: repo.language,
|
|
71191
|
+
updatedAt: repo.updatedAt,
|
|
71192
|
+
rank: idx + 1
|
|
71193
|
+
}
|
|
71194
|
+
}))
|
|
71195
|
+
);
|
|
71196
|
+
}
|
|
71197
|
+
if (searchType === "issues" || searchType === "both") {
|
|
71198
|
+
const issues = await runGhJsonAsync(
|
|
71199
|
+
[
|
|
71200
|
+
"search",
|
|
71201
|
+
"issues",
|
|
71202
|
+
query,
|
|
71203
|
+
"--json",
|
|
71204
|
+
"title,body,htmlUrl,state,labels",
|
|
71205
|
+
"--limit",
|
|
71206
|
+
String(maxResults)
|
|
71207
|
+
],
|
|
71208
|
+
{ signal, timeoutMs }
|
|
71209
|
+
);
|
|
71210
|
+
sources.push(
|
|
71211
|
+
...issues.slice(0, maxResults).map((issue, idx) => ({
|
|
71212
|
+
id: `github-issue-${idx}-${issue.htmlUrl}`,
|
|
71213
|
+
type: "github",
|
|
71214
|
+
reference: issue.htmlUrl,
|
|
71215
|
+
title: issue.title,
|
|
71216
|
+
excerpt: issue.body?.slice(0, 280) ?? "",
|
|
71217
|
+
status: "completed",
|
|
71218
|
+
metadata: {
|
|
71219
|
+
resultType: "issue",
|
|
71220
|
+
state: issue.state,
|
|
71221
|
+
labels: (issue.labels ?? []).map((label) => label.name).filter(Boolean),
|
|
71222
|
+
rank: idx + 1
|
|
71223
|
+
}
|
|
71224
|
+
}))
|
|
71225
|
+
);
|
|
71226
|
+
}
|
|
71227
|
+
return sources;
|
|
71228
|
+
} catch (error) {
|
|
71229
|
+
throw this.mapGhError(error);
|
|
71230
|
+
}
|
|
71231
|
+
}
|
|
71232
|
+
async fetchContent(url, config = {}, signal) {
|
|
71233
|
+
const parsed = parseGitHubUrl(url);
|
|
71234
|
+
if (!parsed) {
|
|
71235
|
+
throw new ResearchProviderError({
|
|
71236
|
+
providerType: "github",
|
|
71237
|
+
code: "provider-unavailable",
|
|
71238
|
+
message: "Unsupported GitHub URL"
|
|
71239
|
+
});
|
|
71240
|
+
}
|
|
71241
|
+
const timeoutMs = Number(config.timeoutMs ?? DEFAULT_TIMEOUT_MS);
|
|
71242
|
+
try {
|
|
71243
|
+
if (parsed.kind === "repo") {
|
|
71244
|
+
const readme = await runGhJsonAsync(
|
|
71245
|
+
["api", `repos/${parsed.owner}/${parsed.repo}/readme`],
|
|
71246
|
+
{ signal, timeoutMs }
|
|
71247
|
+
);
|
|
71248
|
+
if (readme.encoding !== "base64" || !readme.content) {
|
|
71249
|
+
throw new ResearchProviderError({
|
|
71250
|
+
providerType: "github",
|
|
71251
|
+
code: "provider-unavailable",
|
|
71252
|
+
message: "Unsupported README encoding"
|
|
71253
|
+
});
|
|
71254
|
+
}
|
|
71255
|
+
const content2 = Buffer.from(readme.content.replace(/\n/g, ""), "base64").toString("utf-8");
|
|
71256
|
+
return {
|
|
71257
|
+
content: content2,
|
|
71258
|
+
metadata: {
|
|
71259
|
+
url,
|
|
71260
|
+
owner: parsed.owner,
|
|
71261
|
+
repo: parsed.repo,
|
|
71262
|
+
kind: "repo-readme",
|
|
71263
|
+
name: readme.name
|
|
71264
|
+
},
|
|
71265
|
+
mimeType: "text/markdown"
|
|
71266
|
+
};
|
|
71267
|
+
}
|
|
71268
|
+
if (parsed.kind === "issue") {
|
|
71269
|
+
const issue = await runGhAsync(["issue", "view", parsed.number ?? "", "--repo", `${parsed.owner}/${parsed.repo}`, "--comments"], {
|
|
71270
|
+
signal,
|
|
71271
|
+
timeoutMs
|
|
71272
|
+
});
|
|
71273
|
+
return {
|
|
71274
|
+
content: issue,
|
|
71275
|
+
metadata: { url, owner: parsed.owner, repo: parsed.repo, kind: "issue", number: parsed.number },
|
|
71276
|
+
mimeType: "text/plain"
|
|
71277
|
+
};
|
|
71278
|
+
}
|
|
71279
|
+
if (parsed.kind === "pr") {
|
|
71280
|
+
const pr = await runGhAsync(["pr", "view", parsed.number ?? "", "--repo", `${parsed.owner}/${parsed.repo}`, "--comments"], {
|
|
71281
|
+
signal,
|
|
71282
|
+
timeoutMs
|
|
71283
|
+
});
|
|
71284
|
+
return {
|
|
71285
|
+
content: pr,
|
|
71286
|
+
metadata: { url, owner: parsed.owner, repo: parsed.repo, kind: "pr", number: parsed.number },
|
|
71287
|
+
mimeType: "text/plain"
|
|
71288
|
+
};
|
|
71289
|
+
}
|
|
71290
|
+
const apiPath = `repos/${parsed.owner}/${parsed.repo}/contents/${parsed.filePath ?? ""}${parsed.ref ? `?ref=${encodeURIComponent(parsed.ref)}` : ""}`;
|
|
71291
|
+
const file = await runGhJsonAsync(["api", apiPath], {
|
|
71292
|
+
signal,
|
|
71293
|
+
timeoutMs
|
|
71294
|
+
});
|
|
71295
|
+
const content = file.encoding === "base64" && file.content ? Buffer.from(file.content.replace(/\n/g, ""), "base64").toString("utf-8") : file.content ?? "";
|
|
71296
|
+
return {
|
|
71297
|
+
content,
|
|
71298
|
+
metadata: {
|
|
71299
|
+
url,
|
|
71300
|
+
owner: parsed.owner,
|
|
71301
|
+
repo: parsed.repo,
|
|
71302
|
+
kind: "file",
|
|
71303
|
+
path: parsed.filePath,
|
|
71304
|
+
ref: parsed.ref,
|
|
71305
|
+
name: file.name
|
|
71306
|
+
},
|
|
71307
|
+
mimeType: "text/plain"
|
|
71308
|
+
};
|
|
71309
|
+
} catch (error) {
|
|
71310
|
+
throw this.mapGhError(error);
|
|
71311
|
+
}
|
|
71312
|
+
}
|
|
71313
|
+
isConfigured() {
|
|
71314
|
+
return isGhAvailable() && isGhAuthenticated();
|
|
71315
|
+
}
|
|
71316
|
+
mapGhError(error) {
|
|
71317
|
+
if (error instanceof ResearchProviderError) return error;
|
|
71318
|
+
if (isGhError(error)) {
|
|
71319
|
+
const message = `${error.message}${error.stderr ? `: ${error.stderr}` : ""}`;
|
|
71320
|
+
const lowered = message.toLowerCase();
|
|
71321
|
+
if (error.code === "ABORT_ERR" || lowered.includes("aborted")) {
|
|
71322
|
+
return new ResearchProviderError({ providerType: "github", code: "abort", message, cause: error });
|
|
71323
|
+
}
|
|
71324
|
+
if (lowered.includes("timed out")) {
|
|
71325
|
+
return new ResearchProviderError({
|
|
71326
|
+
providerType: "github",
|
|
71327
|
+
code: "timeout",
|
|
71328
|
+
message,
|
|
71329
|
+
retryable: true,
|
|
71330
|
+
cause: error
|
|
71331
|
+
});
|
|
71332
|
+
}
|
|
71333
|
+
if (error.code === 403 || lowered.includes("rate limit")) {
|
|
71334
|
+
return new ResearchProviderError({
|
|
71335
|
+
providerType: "github",
|
|
71336
|
+
code: "rate-limited",
|
|
71337
|
+
message,
|
|
71338
|
+
retryable: true,
|
|
71339
|
+
cause: error
|
|
71340
|
+
});
|
|
71341
|
+
}
|
|
71342
|
+
if (error.code === 401 || lowered.includes("not logged") || lowered.includes("authentication")) {
|
|
71343
|
+
return new ResearchProviderError({
|
|
71344
|
+
providerType: "github",
|
|
71345
|
+
code: "auth-failed",
|
|
71346
|
+
message,
|
|
71347
|
+
cause: error
|
|
71348
|
+
});
|
|
71349
|
+
}
|
|
71350
|
+
if (error.code === 404 || lowered.includes("not found")) {
|
|
71351
|
+
return new ResearchProviderError({
|
|
71352
|
+
providerType: "github",
|
|
71353
|
+
code: "provider-unavailable",
|
|
71354
|
+
message,
|
|
71355
|
+
cause: error
|
|
71356
|
+
});
|
|
71357
|
+
}
|
|
71358
|
+
return new ResearchProviderError({
|
|
71359
|
+
providerType: "github",
|
|
71360
|
+
code: "network-error",
|
|
71361
|
+
message,
|
|
71362
|
+
retryable: true,
|
|
71363
|
+
cause: error
|
|
71364
|
+
});
|
|
71365
|
+
}
|
|
71366
|
+
log8.warn("github provider error", { error });
|
|
71367
|
+
return new ResearchProviderError({
|
|
71368
|
+
providerType: "github",
|
|
71369
|
+
code: "network-error",
|
|
71370
|
+
message: error instanceof Error ? error.message : "GitHub provider failed",
|
|
71371
|
+
retryable: true,
|
|
71372
|
+
cause: error
|
|
71373
|
+
});
|
|
71374
|
+
}
|
|
71375
|
+
};
|
|
71376
|
+
}
|
|
71377
|
+
});
|
|
71378
|
+
|
|
71379
|
+
// ../engine/src/research/providers/llm-synthesis-provider.ts
|
|
71380
|
+
function isLargeModel(modelId) {
|
|
71381
|
+
const id = modelId?.toLowerCase() ?? "";
|
|
71382
|
+
return id.includes("gpt-5") || id.includes("claude-opus") || id.includes("gemini-2.5-pro") || id.includes("sonnet");
|
|
71383
|
+
}
|
|
71384
|
+
function scoreSource(source) {
|
|
71385
|
+
const confidence = typeof source.metadata?.confidence === "number" ? source.metadata.confidence : 0;
|
|
71386
|
+
const hasContent = source.content ? 1 : 0;
|
|
71387
|
+
return confidence * 10 + hasContent;
|
|
71388
|
+
}
|
|
71389
|
+
function buildSynthesisPrompt(request2, sources) {
|
|
71390
|
+
const format = request2.desiredFormat ?? "markdown";
|
|
71391
|
+
const renderedSources = sources.map(
|
|
71392
|
+
(source, index) => `Source [${index + 1}]
|
|
71393
|
+
Title: ${source.title ?? source.reference}
|
|
71394
|
+
Reference: ${source.reference}
|
|
71395
|
+
Excerpt: ${source.excerpt ?? ""}
|
|
71396
|
+
Content: ${source.content ?? ""}`
|
|
71397
|
+
).join("\n\n");
|
|
71398
|
+
return [
|
|
71399
|
+
"You are a research synthesis assistant.",
|
|
71400
|
+
`Query: ${request2.query}`,
|
|
71401
|
+
`Round: ${request2.round}`,
|
|
71402
|
+
`Desired format: ${format}`,
|
|
71403
|
+
"Analyze the provided sources and produce: summary, key findings, contradictions, confidence, and follow-up queries.",
|
|
71404
|
+
"Cite source references inline using [n] notation where n is source number.",
|
|
71405
|
+
request2.instructions ? `Additional instructions: ${request2.instructions}` : "",
|
|
71406
|
+
"Sources:",
|
|
71407
|
+
renderedSources,
|
|
71408
|
+
'Return valid JSON: {"summary": string, "findings": [{"statement": string, "citations": string[]}], "confidence": number, "followUps": string[]}'
|
|
71409
|
+
].filter(Boolean).join("\n\n");
|
|
71410
|
+
}
|
|
71411
|
+
function extractAssistantText(session) {
|
|
71412
|
+
const messages = session.state?.messages;
|
|
71413
|
+
if (!Array.isArray(messages)) return void 0;
|
|
71414
|
+
for (let i = messages.length - 1; i >= 0; i -= 1) {
|
|
71415
|
+
const msg = messages[i];
|
|
71416
|
+
if (msg.role !== "assistant") continue;
|
|
71417
|
+
if (typeof msg.content === "string" && msg.content.trim()) return msg.content;
|
|
71418
|
+
if (Array.isArray(msg.content)) {
|
|
71419
|
+
for (const part of msg.content) {
|
|
71420
|
+
if (typeof part === "object" && part && "text" in part && typeof part.text === "string") {
|
|
71421
|
+
return part.text;
|
|
71422
|
+
}
|
|
71423
|
+
}
|
|
71424
|
+
}
|
|
71425
|
+
}
|
|
71426
|
+
return void 0;
|
|
71427
|
+
}
|
|
71428
|
+
function extractCitations(text, sources) {
|
|
71429
|
+
const matches = text.match(/\[(\d+)\]/g) ?? [];
|
|
71430
|
+
const refs = /* @__PURE__ */ new Set();
|
|
71431
|
+
for (const match of matches) {
|
|
71432
|
+
const idx = Number.parseInt(match.replace(/\D/g, ""), 10) - 1;
|
|
71433
|
+
if (Number.isFinite(idx) && idx >= 0 && idx < sources.length) {
|
|
71434
|
+
refs.add(sources[idx].reference);
|
|
71435
|
+
}
|
|
71436
|
+
}
|
|
71437
|
+
return [...refs];
|
|
71438
|
+
}
|
|
71439
|
+
function extractConfidence(text) {
|
|
71440
|
+
const jsonBlock = text.match(/```(?:json)?\s*([\s\S]*?)```/i)?.[1] ?? text;
|
|
71441
|
+
try {
|
|
71442
|
+
const parsed = JSON.parse(jsonBlock);
|
|
71443
|
+
if (typeof parsed.confidence === "number") return parsed.confidence;
|
|
71444
|
+
} catch {
|
|
71445
|
+
const match = text.match(/"confidence"\s*:\s*([0-9]*\.?[0-9]+)/i);
|
|
71446
|
+
if (match) {
|
|
71447
|
+
const parsed = Number.parseFloat(match[1]);
|
|
71448
|
+
if (Number.isFinite(parsed)) return parsed;
|
|
71449
|
+
}
|
|
71450
|
+
}
|
|
71451
|
+
return void 0;
|
|
71452
|
+
}
|
|
71453
|
+
var log9, DEFAULT_TIMEOUT_MS2, LARGE_MODEL_CONTEXT_CHARS, SMALL_MODEL_CONTEXT_CHARS, LLMSynthesisProvider;
|
|
71454
|
+
var init_llm_synthesis_provider = __esm({
|
|
71455
|
+
"../engine/src/research/providers/llm-synthesis-provider.ts"() {
|
|
71456
|
+
"use strict";
|
|
71457
|
+
init_logger2();
|
|
71458
|
+
init_pi();
|
|
71459
|
+
init_types2();
|
|
71460
|
+
log9 = createLogger2("research:llm-synthesis");
|
|
71461
|
+
DEFAULT_TIMEOUT_MS2 = 12e4;
|
|
71462
|
+
LARGE_MODEL_CONTEXT_CHARS = 1e5;
|
|
71463
|
+
SMALL_MODEL_CONTEXT_CHARS = 3e4;
|
|
71464
|
+
LLMSynthesisProvider = class {
|
|
71465
|
+
constructor(options) {
|
|
71466
|
+
this.options = options;
|
|
71467
|
+
}
|
|
71468
|
+
type = "llm-synthesis";
|
|
71469
|
+
isConfigured() {
|
|
71470
|
+
return true;
|
|
71471
|
+
}
|
|
71472
|
+
async search(_query, _options = {}, _signal) {
|
|
71473
|
+
return [];
|
|
71474
|
+
}
|
|
71475
|
+
async fetchContent(_url, _options = {}, _signal) {
|
|
71476
|
+
return { content: "", metadata: {} };
|
|
71477
|
+
}
|
|
71478
|
+
async synthesize(request2, modelSelection, signal) {
|
|
71479
|
+
if (!modelSelection?.provider || !modelSelection?.modelId) {
|
|
71480
|
+
throw new ResearchProviderError({ providerType: "llm-synthesis", code: "provider-unavailable", message: "Synthesis model is not configured" });
|
|
71481
|
+
}
|
|
71482
|
+
const timeoutSignal = AbortSignal.timeout(this.options.timeoutMs ?? DEFAULT_TIMEOUT_MS2);
|
|
71483
|
+
const requestSignal = signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal;
|
|
71484
|
+
try {
|
|
71485
|
+
const cappedSources = this.applySourceBudget(request2.sources, modelSelection);
|
|
71486
|
+
const prompt = buildSynthesisPrompt(request2, cappedSources);
|
|
71487
|
+
const { session } = await createFnAgent2({
|
|
71488
|
+
cwd: this.options.projectRoot,
|
|
71489
|
+
tools: "readonly",
|
|
71490
|
+
systemPrompt: "You synthesize research findings into concise, cited outputs.",
|
|
71491
|
+
defaultProvider: modelSelection.provider,
|
|
71492
|
+
defaultModelId: modelSelection.modelId
|
|
71493
|
+
});
|
|
71494
|
+
try {
|
|
71495
|
+
await Promise.race([
|
|
71496
|
+
promptWithFallback(session, prompt),
|
|
71497
|
+
new Promise((_, reject) => {
|
|
71498
|
+
requestSignal.addEventListener(
|
|
71499
|
+
"abort",
|
|
71500
|
+
() => reject(new ResearchProviderError({
|
|
71501
|
+
providerType: "llm-synthesis",
|
|
71502
|
+
code: signal?.aborted ? "abort" : "timeout",
|
|
71503
|
+
message: signal?.aborted ? "Synthesis aborted" : "Synthesis timed out",
|
|
71504
|
+
retryable: !signal?.aborted
|
|
71505
|
+
})),
|
|
71506
|
+
{ once: true }
|
|
71507
|
+
);
|
|
71508
|
+
})
|
|
71509
|
+
]);
|
|
71510
|
+
if (requestSignal.aborted) {
|
|
71511
|
+
throw new ResearchProviderError({ providerType: "llm-synthesis", code: signal?.aborted ? "abort" : "timeout", message: signal?.aborted ? "Synthesis aborted" : "Synthesis timed out", retryable: !signal?.aborted });
|
|
71512
|
+
}
|
|
71513
|
+
const responseText = extractAssistantText(session);
|
|
71514
|
+
if (!responseText) {
|
|
71515
|
+
throw new ResearchProviderError({ providerType: "llm-synthesis", code: "provider-unavailable", message: "No synthesis response received" });
|
|
71516
|
+
}
|
|
71517
|
+
return {
|
|
71518
|
+
output: responseText,
|
|
71519
|
+
citations: extractCitations(responseText, cappedSources),
|
|
71520
|
+
confidence: extractConfidence(responseText),
|
|
71521
|
+
metadata: {
|
|
71522
|
+
sourceCount: cappedSources.length,
|
|
71523
|
+
truncated: cappedSources.length < request2.sources.length
|
|
71524
|
+
}
|
|
71525
|
+
};
|
|
71526
|
+
} finally {
|
|
71527
|
+
session.dispose();
|
|
71528
|
+
}
|
|
71529
|
+
} catch (error) {
|
|
71530
|
+
if (error instanceof ResearchProviderError) throw error;
|
|
71531
|
+
if (error instanceof DOMException && error.name === "AbortError") {
|
|
71532
|
+
throw new ResearchProviderError({ providerType: "llm-synthesis", code: "abort", message: "Synthesis aborted", cause: error });
|
|
71533
|
+
}
|
|
71534
|
+
log9.warn("llm synthesis failed", { error });
|
|
71535
|
+
throw new ResearchProviderError({
|
|
71536
|
+
providerType: "llm-synthesis",
|
|
71537
|
+
code: "provider-unavailable",
|
|
71538
|
+
message: error instanceof Error ? error.message : "Synthesis failed",
|
|
71539
|
+
retryable: true,
|
|
71540
|
+
cause: error
|
|
71541
|
+
});
|
|
71542
|
+
}
|
|
71543
|
+
}
|
|
71544
|
+
applySourceBudget(sources, modelSelection) {
|
|
71545
|
+
const contextChars = isLargeModel(modelSelection.modelId) ? LARGE_MODEL_CONTEXT_CHARS : SMALL_MODEL_CONTEXT_CHARS;
|
|
71546
|
+
const budget = Math.floor(contextChars * 0.8);
|
|
71547
|
+
const ordered = [...sources].sort((a, b) => scoreSource(b) - scoreSource(a));
|
|
71548
|
+
let total = 0;
|
|
71549
|
+
const kept = [];
|
|
71550
|
+
for (const source of ordered) {
|
|
71551
|
+
const chunk = `${source.title ?? ""}
|
|
71552
|
+
${source.excerpt ?? ""}
|
|
71553
|
+
${source.content ?? ""}`;
|
|
71554
|
+
if (total + chunk.length > budget) continue;
|
|
71555
|
+
total += chunk.length;
|
|
71556
|
+
kept.push(source);
|
|
71557
|
+
}
|
|
71558
|
+
return kept.length > 0 ? kept : ordered.slice(0, 1);
|
|
71559
|
+
}
|
|
71560
|
+
};
|
|
71561
|
+
}
|
|
71562
|
+
});
|
|
71563
|
+
|
|
71564
|
+
// ../engine/src/research/providers/local-docs-provider.ts
|
|
71565
|
+
import { promises as fs } from "node:fs";
|
|
71566
|
+
import { extname as extname2, join as join35, relative as relative7, resolve as resolve15 } from "node:path";
|
|
71567
|
+
function buildExcerpt(content, terms) {
|
|
71568
|
+
const lower = content.toLowerCase();
|
|
71569
|
+
const first = terms.find((term) => lower.includes(term));
|
|
71570
|
+
if (!first) return content.slice(0, 220).replace(/\s+/g, " ").trim();
|
|
71571
|
+
const idx = lower.indexOf(first);
|
|
71572
|
+
const start = Math.max(0, idx - 80);
|
|
71573
|
+
const end = Math.min(content.length, idx + 140);
|
|
71574
|
+
return content.slice(start, end).replace(/\s+/g, " ").trim();
|
|
71575
|
+
}
|
|
71576
|
+
function matchesGitignore(relPath, patterns) {
|
|
71577
|
+
return patterns.some((pattern) => {
|
|
71578
|
+
if (pattern.endsWith("/")) return relPath.startsWith(pattern.slice(0, -1));
|
|
71579
|
+
if (pattern.includes("*")) {
|
|
71580
|
+
const regex = new RegExp(`^${pattern.split("*").map(escapeRegex2).join(".*")}$`);
|
|
71581
|
+
return regex.test(relPath);
|
|
71582
|
+
}
|
|
71583
|
+
return relPath === pattern || relPath.startsWith(`${pattern}/`);
|
|
71584
|
+
});
|
|
71585
|
+
}
|
|
71586
|
+
function escapeRegex2(value) {
|
|
71587
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
71588
|
+
}
|
|
71589
|
+
var log10, DEFAULT_MAX_RESULTS, DEFAULT_TIMEOUT_MS3, MAX_FILE_SIZE_BYTES, BINARY_SNIFF_BYTES, LocalDocsProvider;
|
|
71590
|
+
var init_local_docs_provider = __esm({
|
|
71591
|
+
"../engine/src/research/providers/local-docs-provider.ts"() {
|
|
71592
|
+
"use strict";
|
|
71593
|
+
init_logger2();
|
|
71594
|
+
init_types2();
|
|
71595
|
+
log10 = createLogger2("research:local-docs");
|
|
71596
|
+
DEFAULT_MAX_RESULTS = 10;
|
|
71597
|
+
DEFAULT_TIMEOUT_MS3 = 3e4;
|
|
71598
|
+
MAX_FILE_SIZE_BYTES = 1024 * 1024;
|
|
71599
|
+
BINARY_SNIFF_BYTES = 8 * 1024;
|
|
71600
|
+
LocalDocsProvider = class {
|
|
71601
|
+
constructor(options) {
|
|
71602
|
+
this.options = options;
|
|
71603
|
+
this.projectRoot = resolve15(options.projectRoot);
|
|
71604
|
+
this.scanPaths = options.scanPaths ?? ["docs", "README.md", "AGENTS.md", ".fusion/memory"];
|
|
71605
|
+
}
|
|
71606
|
+
type = "local-docs";
|
|
71607
|
+
projectRoot;
|
|
71608
|
+
scanPaths;
|
|
71609
|
+
isConfigured() {
|
|
71610
|
+
return true;
|
|
71611
|
+
}
|
|
71612
|
+
async search(query, config = {}, signal) {
|
|
71613
|
+
const timeoutMs = Number(config.timeoutMs ?? this.options.timeoutMs ?? DEFAULT_TIMEOUT_MS3);
|
|
71614
|
+
const maxResults = Number(config.maxResults ?? this.options.maxResults ?? DEFAULT_MAX_RESULTS);
|
|
71615
|
+
const terms = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
71616
|
+
const files = await this.withTimeout(this.collectCandidateFiles(signal), timeoutMs, signal);
|
|
71617
|
+
const results = [];
|
|
71618
|
+
for (const file of files) {
|
|
71619
|
+
this.throwIfAborted(signal);
|
|
71620
|
+
const content = await this.safeReadText(file, signal);
|
|
71621
|
+
if (!content) continue;
|
|
71622
|
+
const lower = content.toLowerCase();
|
|
71623
|
+
let score = 0;
|
|
71624
|
+
for (const term of terms) {
|
|
71625
|
+
const matches = lower.match(new RegExp(escapeRegex2(term), "g"));
|
|
71626
|
+
score += matches?.length ?? 0;
|
|
71627
|
+
}
|
|
71628
|
+
if (score <= 0) continue;
|
|
71629
|
+
const relPath = relative7(this.projectRoot, file);
|
|
71630
|
+
results.push({
|
|
71631
|
+
score,
|
|
71632
|
+
source: {
|
|
71633
|
+
id: `local-docs-${relPath}`,
|
|
71634
|
+
type: "local",
|
|
71635
|
+
reference: relPath,
|
|
71636
|
+
title: relPath,
|
|
71637
|
+
excerpt: buildExcerpt(content, terms),
|
|
71638
|
+
status: "completed",
|
|
71639
|
+
metadata: { score, path: relPath }
|
|
71640
|
+
}
|
|
71641
|
+
});
|
|
71642
|
+
}
|
|
71643
|
+
return results.sort((a, b) => b.score - a.score).slice(0, maxResults).map((item) => item.source);
|
|
71644
|
+
}
|
|
71645
|
+
async fetchContent(filePath, config = {}, signal) {
|
|
71646
|
+
const timeoutMs = Number(config.timeoutMs ?? this.options.timeoutMs ?? DEFAULT_TIMEOUT_MS3);
|
|
71647
|
+
const resolvedPath = resolve15(this.projectRoot, filePath);
|
|
71648
|
+
if (!resolvedPath.startsWith(this.projectRoot)) {
|
|
71649
|
+
throw new ResearchProviderError({
|
|
71650
|
+
providerType: "local-docs",
|
|
71651
|
+
code: "provider-unavailable",
|
|
71652
|
+
message: "Path traversal is not allowed"
|
|
71653
|
+
});
|
|
71654
|
+
}
|
|
71655
|
+
const stat8 = await this.withTimeout(fs.stat(resolvedPath), timeoutMs, signal);
|
|
71656
|
+
if (!stat8.isFile()) {
|
|
71657
|
+
throw new ResearchProviderError({ providerType: "local-docs", code: "provider-unavailable", message: "Path is not a file" });
|
|
71658
|
+
}
|
|
71659
|
+
const content = await this.withTimeout(fs.readFile(resolvedPath), timeoutMs, signal);
|
|
71660
|
+
const sniff = content.subarray(0, BINARY_SNIFF_BYTES);
|
|
71661
|
+
if (sniff.includes(0)) {
|
|
71662
|
+
throw new ResearchProviderError({ providerType: "local-docs", code: "provider-unavailable", message: "Binary file is not supported" });
|
|
71663
|
+
}
|
|
71664
|
+
const text = content.toString("utf-8");
|
|
71665
|
+
return {
|
|
71666
|
+
content: text.length > MAX_FILE_SIZE_BYTES ? text.slice(0, MAX_FILE_SIZE_BYTES) : text,
|
|
71667
|
+
metadata: {
|
|
71668
|
+
path: relative7(this.projectRoot, resolvedPath),
|
|
71669
|
+
size: stat8.size,
|
|
71670
|
+
modifiedAt: stat8.mtime.toISOString(),
|
|
71671
|
+
extension: extname2(resolvedPath)
|
|
71672
|
+
},
|
|
71673
|
+
mimeType: "text/plain"
|
|
71674
|
+
};
|
|
71675
|
+
}
|
|
71676
|
+
async collectCandidateFiles(signal) {
|
|
71677
|
+
const ignorePatterns = await this.readGitignore();
|
|
71678
|
+
const files = [];
|
|
71679
|
+
for (const pathEntry of this.scanPaths) {
|
|
71680
|
+
const target = resolve15(this.projectRoot, pathEntry);
|
|
71681
|
+
if (!target.startsWith(this.projectRoot)) continue;
|
|
71682
|
+
try {
|
|
71683
|
+
const stat8 = await fs.stat(target);
|
|
71684
|
+
if (stat8.isDirectory()) {
|
|
71685
|
+
await this.walk(target, files, ignorePatterns, signal);
|
|
71686
|
+
} else if (stat8.isFile()) {
|
|
71687
|
+
files.push(target);
|
|
71688
|
+
}
|
|
71689
|
+
} catch {
|
|
71690
|
+
}
|
|
71691
|
+
}
|
|
71692
|
+
const rootEntries = await fs.readdir(this.projectRoot, { withFileTypes: true });
|
|
71693
|
+
for (const entry of rootEntries) {
|
|
71694
|
+
if (entry.isFile() && entry.name.toLowerCase().endsWith(".md")) {
|
|
71695
|
+
files.push(join35(this.projectRoot, entry.name));
|
|
71696
|
+
}
|
|
71697
|
+
}
|
|
71698
|
+
return [...new Set(files)];
|
|
71699
|
+
}
|
|
71700
|
+
async walk(dir, out, ignorePatterns, signal) {
|
|
71701
|
+
this.throwIfAborted(signal);
|
|
71702
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
71703
|
+
for (const entry of entries) {
|
|
71704
|
+
this.throwIfAborted(signal);
|
|
71705
|
+
const fullPath = join35(dir, entry.name);
|
|
71706
|
+
const relPath = relative7(this.projectRoot, fullPath).replace(/\\/g, "/");
|
|
71707
|
+
if (matchesGitignore(relPath, ignorePatterns)) continue;
|
|
71708
|
+
if (entry.isDirectory()) {
|
|
71709
|
+
await this.walk(fullPath, out, ignorePatterns, signal);
|
|
71710
|
+
} else if (entry.isFile()) {
|
|
71711
|
+
out.push(fullPath);
|
|
71712
|
+
}
|
|
71713
|
+
}
|
|
71714
|
+
}
|
|
71715
|
+
async safeReadText(filePath, signal) {
|
|
71716
|
+
try {
|
|
71717
|
+
const stat8 = await fs.stat(filePath);
|
|
71718
|
+
if (stat8.size > MAX_FILE_SIZE_BYTES) return void 0;
|
|
71719
|
+
const content = await fs.readFile(filePath);
|
|
71720
|
+
if (content.subarray(0, BINARY_SNIFF_BYTES).includes(0)) return void 0;
|
|
71721
|
+
this.throwIfAborted(signal);
|
|
71722
|
+
return content.toString("utf-8");
|
|
71723
|
+
} catch (error) {
|
|
71724
|
+
log10.warn("failed to read local docs file", { filePath, error });
|
|
71725
|
+
return void 0;
|
|
71726
|
+
}
|
|
71727
|
+
}
|
|
71728
|
+
async readGitignore() {
|
|
71729
|
+
try {
|
|
71730
|
+
const content = await fs.readFile(join35(this.projectRoot, ".gitignore"), "utf-8");
|
|
71731
|
+
return content.split(/\r?\n/).map((line) => line.trim()).filter((line) => line && !line.startsWith("#"));
|
|
71732
|
+
} catch {
|
|
71733
|
+
return [];
|
|
71734
|
+
}
|
|
71735
|
+
}
|
|
71736
|
+
throwIfAborted(signal) {
|
|
71737
|
+
if (signal?.aborted) {
|
|
71738
|
+
throw new ResearchProviderError({ providerType: "local-docs", code: "abort", message: "Local docs scan aborted" });
|
|
71739
|
+
}
|
|
71740
|
+
}
|
|
71741
|
+
async withTimeout(promise, timeoutMs, signal) {
|
|
71742
|
+
const timeoutSignal = AbortSignal.timeout(timeoutMs);
|
|
71743
|
+
const combined = signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal;
|
|
71744
|
+
return await Promise.race([
|
|
71745
|
+
promise,
|
|
71746
|
+
new Promise((_, reject) => {
|
|
71747
|
+
combined.addEventListener(
|
|
71748
|
+
"abort",
|
|
71749
|
+
() => {
|
|
71750
|
+
reject(
|
|
71751
|
+
new ResearchProviderError({
|
|
71752
|
+
providerType: "local-docs",
|
|
71753
|
+
code: signal?.aborted ? "abort" : "timeout",
|
|
71754
|
+
message: signal?.aborted ? "Local docs operation aborted" : `Local docs operation timed out after ${timeoutMs}ms`,
|
|
71755
|
+
retryable: !signal?.aborted
|
|
71756
|
+
})
|
|
71757
|
+
);
|
|
71758
|
+
},
|
|
71759
|
+
{ once: true }
|
|
71760
|
+
);
|
|
71761
|
+
})
|
|
71762
|
+
]);
|
|
71763
|
+
}
|
|
71764
|
+
};
|
|
71765
|
+
}
|
|
71766
|
+
});
|
|
71767
|
+
|
|
71768
|
+
// ../engine/src/research/providers/page-fetch-provider.ts
|
|
71769
|
+
function extractHtml(html) {
|
|
71770
|
+
const title = html.match(/<title[^>]*>([\s\S]*?)<\/title>/i)?.[1]?.trim();
|
|
71771
|
+
const description = html.match(/<meta[^>]*name=["']description["'][^>]*content=["']([^"']+)["'][^>]*>/i)?.[1]?.trim();
|
|
71772
|
+
const stripped = html.replace(/<script[\s\S]*?<\/script>/gi, " ").replace(/<style[\s\S]*?<\/style>/gi, " ").replace(/<(nav|footer|header)[\s\S]*?<\/\1>/gi, " ");
|
|
71773
|
+
const main = stripped.match(/<(main|article)[^>]*>([\s\S]*?)<\/\1>/i)?.[2] ?? stripped.match(/<body[^>]*>([\s\S]*?)<\/body>/i)?.[1] ?? stripped;
|
|
71774
|
+
const text = main.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
|
|
71775
|
+
return { title, description, content: text };
|
|
71776
|
+
}
|
|
71777
|
+
function truncate(value) {
|
|
71778
|
+
return value.length > MAX_CONTENT_CHARS ? value.slice(0, MAX_CONTENT_CHARS) : value;
|
|
71779
|
+
}
|
|
71780
|
+
function looksLikeJson(value) {
|
|
71781
|
+
const trimmed = value.trim();
|
|
71782
|
+
return trimmed.startsWith("{") || trimmed.startsWith("[");
|
|
71783
|
+
}
|
|
71784
|
+
var log11, DEFAULT_TIMEOUT_MS4, DEFAULT_USER_AGENT, MAX_CONTENT_CHARS, PageFetchProvider;
|
|
71785
|
+
var init_page_fetch_provider = __esm({
|
|
71786
|
+
"../engine/src/research/providers/page-fetch-provider.ts"() {
|
|
71787
|
+
"use strict";
|
|
71788
|
+
init_logger2();
|
|
71789
|
+
init_types2();
|
|
71790
|
+
log11 = createLogger2("research:page-fetch");
|
|
71791
|
+
DEFAULT_TIMEOUT_MS4 = 3e4;
|
|
71792
|
+
DEFAULT_USER_AGENT = "FusionResearchBot/1.0";
|
|
71793
|
+
MAX_CONTENT_CHARS = 500 * 1024;
|
|
71794
|
+
PageFetchProvider = class {
|
|
71795
|
+
constructor(options = {}) {
|
|
71796
|
+
this.options = options;
|
|
71797
|
+
}
|
|
71798
|
+
type = "page-fetch";
|
|
71799
|
+
isConfigured() {
|
|
71800
|
+
return true;
|
|
71801
|
+
}
|
|
71802
|
+
async search(_query, _config = {}, _signal) {
|
|
71803
|
+
return [];
|
|
71804
|
+
}
|
|
71805
|
+
async fetchContent(url, config = {}, signal) {
|
|
71806
|
+
const timeoutMs = Number(config.timeoutMs ?? this.options.timeoutMs ?? DEFAULT_TIMEOUT_MS4);
|
|
71807
|
+
const timeoutSignal = AbortSignal.timeout(timeoutMs);
|
|
71808
|
+
const requestSignal = signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal;
|
|
71809
|
+
try {
|
|
71810
|
+
const response = await fetch(url, {
|
|
71811
|
+
method: "GET",
|
|
71812
|
+
redirect: "follow",
|
|
71813
|
+
headers: {
|
|
71814
|
+
"User-Agent": config.metadata?.userAgent ?? this.options.userAgent ?? DEFAULT_USER_AGENT
|
|
71815
|
+
},
|
|
71816
|
+
signal: requestSignal
|
|
71817
|
+
});
|
|
71818
|
+
if (!response.ok) {
|
|
71819
|
+
throw new ResearchProviderError({
|
|
71820
|
+
providerType: "page-fetch",
|
|
71821
|
+
code: response.status >= 500 ? "provider-unavailable" : "network-error",
|
|
71822
|
+
message: `fetch failed with status ${response.status}`,
|
|
71823
|
+
retryable: response.status >= 500
|
|
71824
|
+
});
|
|
71825
|
+
}
|
|
71826
|
+
const contentType = response.headers.get("content-type") ?? "application/octet-stream";
|
|
71827
|
+
const mimeType = contentType.split(";")[0].trim().toLowerCase();
|
|
71828
|
+
const raw = await response.text();
|
|
71829
|
+
const metadata = {
|
|
71830
|
+
url,
|
|
71831
|
+
contentType,
|
|
71832
|
+
contentLength: raw.length
|
|
71833
|
+
};
|
|
71834
|
+
if (mimeType.includes("text/html")) {
|
|
71835
|
+
const extracted = extractHtml(raw);
|
|
71836
|
+
metadata.title = extracted.title;
|
|
71837
|
+
metadata.description = extracted.description;
|
|
71838
|
+
metadata.contentLength = extracted.content.length;
|
|
71839
|
+
return { content: truncate(extracted.content), metadata, mimeType };
|
|
71840
|
+
}
|
|
71841
|
+
if (mimeType.includes("application/json") || looksLikeJson(raw)) {
|
|
71842
|
+
const pretty = JSON.stringify(JSON.parse(raw), null, 2);
|
|
71843
|
+
return { content: truncate(pretty), metadata, mimeType };
|
|
71844
|
+
}
|
|
71845
|
+
if (mimeType.includes("text/") || mimeType.includes("markdown")) {
|
|
71846
|
+
return { content: truncate(raw), metadata, mimeType };
|
|
71847
|
+
}
|
|
71848
|
+
throw new ResearchProviderError({
|
|
71849
|
+
providerType: "page-fetch",
|
|
71850
|
+
code: "provider-unavailable",
|
|
71851
|
+
message: `unsupported mime type: ${mimeType}`
|
|
71852
|
+
});
|
|
71853
|
+
} catch (error) {
|
|
71854
|
+
if (error instanceof ResearchProviderError) throw error;
|
|
71855
|
+
if (error instanceof DOMException && error.name === "AbortError") {
|
|
71856
|
+
throw new ResearchProviderError({ providerType: "page-fetch", code: "abort", message: "Fetch aborted", cause: error });
|
|
71857
|
+
}
|
|
71858
|
+
if (error instanceof Error && error.name === "TimeoutError") {
|
|
71859
|
+
throw new ResearchProviderError({ providerType: "page-fetch", code: "timeout", message: error.message, retryable: true, cause: error });
|
|
71860
|
+
}
|
|
71861
|
+
log11.warn("page fetch failed", { error });
|
|
71862
|
+
throw new ResearchProviderError({
|
|
71863
|
+
providerType: "page-fetch",
|
|
71864
|
+
code: "network-error",
|
|
71865
|
+
message: error instanceof Error ? error.message : "fetch failed",
|
|
71866
|
+
retryable: true,
|
|
71867
|
+
cause: error
|
|
71868
|
+
});
|
|
71869
|
+
}
|
|
71870
|
+
}
|
|
71871
|
+
};
|
|
71872
|
+
}
|
|
71873
|
+
});
|
|
71874
|
+
|
|
71875
|
+
// ../engine/src/research/providers/web-search-provider.ts
|
|
71876
|
+
async function sleep3(ms, signal) {
|
|
71877
|
+
await new Promise((resolve19, reject) => {
|
|
71878
|
+
const timer = setTimeout(resolve19, ms);
|
|
71879
|
+
const onAbort = () => {
|
|
71880
|
+
clearTimeout(timer);
|
|
71881
|
+
reject(new ResearchProviderError({ providerType: "web-search", code: "abort", message: "Search aborted" }));
|
|
71882
|
+
};
|
|
71883
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
71884
|
+
});
|
|
71885
|
+
}
|
|
71886
|
+
var log12, DEFAULT_MAX_RESULTS2, DEFAULT_TIMEOUT_MS5, RETRY_BASE_DELAY_MS2, RETRY_MAX_DELAY_MS, RETRY_MAX_ATTEMPTS, WebSearchProvider;
|
|
71887
|
+
var init_web_search_provider = __esm({
|
|
71888
|
+
"../engine/src/research/providers/web-search-provider.ts"() {
|
|
71889
|
+
"use strict";
|
|
71890
|
+
init_logger2();
|
|
71891
|
+
init_types2();
|
|
71892
|
+
log12 = createLogger2("research:web-search");
|
|
71893
|
+
DEFAULT_MAX_RESULTS2 = 10;
|
|
71894
|
+
DEFAULT_TIMEOUT_MS5 = 3e4;
|
|
71895
|
+
RETRY_BASE_DELAY_MS2 = 1e3;
|
|
71896
|
+
RETRY_MAX_DELAY_MS = 1e4;
|
|
71897
|
+
RETRY_MAX_ATTEMPTS = 3;
|
|
71898
|
+
WebSearchProvider = class {
|
|
71899
|
+
constructor(options = {}) {
|
|
71900
|
+
this.options = options;
|
|
71901
|
+
}
|
|
71902
|
+
type = "web-search";
|
|
71903
|
+
isConfigured() {
|
|
71904
|
+
const backend = this.options.backend ?? "none";
|
|
71905
|
+
if (backend === "none") return false;
|
|
71906
|
+
if (backend === "searxng") return Boolean(this.options.searxngUrl);
|
|
71907
|
+
if (backend === "brave") return Boolean(this.options.braveApiKey);
|
|
71908
|
+
if (backend === "google") return Boolean(this.options.googleApiKey && this.options.googleCx);
|
|
71909
|
+
if (backend === "tavily") return Boolean(this.options.tavilyApiKey);
|
|
71910
|
+
return false;
|
|
71911
|
+
}
|
|
71912
|
+
async search(query, config = {}, signal) {
|
|
71913
|
+
const backend = this.options.backend ?? "none";
|
|
71914
|
+
if (!this.isConfigured()) return [];
|
|
71915
|
+
const maxResults = Number(config.maxResults ?? this.options.maxResults ?? DEFAULT_MAX_RESULTS2);
|
|
71916
|
+
const timeoutMs = Number(config.timeoutMs ?? this.options.timeoutMs ?? DEFAULT_TIMEOUT_MS5);
|
|
71917
|
+
const timeoutSignal = AbortSignal.timeout(timeoutMs);
|
|
71918
|
+
const requestSignal = signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal;
|
|
71919
|
+
const results = await this.withHttpRetry(async () => {
|
|
71920
|
+
if (backend === "searxng") return this.searchSearxng(query, maxResults, requestSignal);
|
|
71921
|
+
if (backend === "brave") return this.searchBrave(query, maxResults, requestSignal);
|
|
71922
|
+
if (backend === "google") return this.searchGoogle(query, maxResults, requestSignal);
|
|
71923
|
+
if (backend === "tavily") return this.searchTavily(query, maxResults, requestSignal);
|
|
71924
|
+
return [];
|
|
71925
|
+
}, requestSignal, backend);
|
|
71926
|
+
return results.slice(0, maxResults).map((result, index) => ({
|
|
71927
|
+
id: `${backend}-${index}-${result.url}`,
|
|
71928
|
+
type: "web",
|
|
71929
|
+
reference: result.url,
|
|
71930
|
+
title: result.title,
|
|
71931
|
+
excerpt: result.snippet,
|
|
71932
|
+
status: "completed",
|
|
71933
|
+
metadata: {
|
|
71934
|
+
backend,
|
|
71935
|
+
rank: index + 1
|
|
71936
|
+
}
|
|
71937
|
+
}));
|
|
71938
|
+
}
|
|
71939
|
+
async fetchContent(_url, _options = {}, _signal) {
|
|
71940
|
+
return { content: "", metadata: {} };
|
|
71941
|
+
}
|
|
71942
|
+
async searchSearxng(query, maxResults, signal) {
|
|
71943
|
+
const base = this.options.searxngUrl?.replace(/\/$/, "") ?? "";
|
|
71944
|
+
const url = new URL(`${base}/search`);
|
|
71945
|
+
url.searchParams.set("q", query);
|
|
71946
|
+
url.searchParams.set("format", "json");
|
|
71947
|
+
const response = await this.requestJson(
|
|
71948
|
+
url.toString(),
|
|
71949
|
+
{
|
|
71950
|
+
method: "GET",
|
|
71951
|
+
signal
|
|
71952
|
+
},
|
|
71953
|
+
"searxng"
|
|
71954
|
+
);
|
|
71955
|
+
return (response.results ?? []).slice(0, maxResults).map((item) => ({
|
|
71956
|
+
url: item.url,
|
|
71957
|
+
title: item.title ?? item.url,
|
|
71958
|
+
snippet: item.content ?? ""
|
|
71959
|
+
}));
|
|
71960
|
+
}
|
|
71961
|
+
async searchBrave(query, maxResults, signal) {
|
|
71962
|
+
const url = new URL("https://api.search.brave.com/res/v1/web/search");
|
|
71963
|
+
url.searchParams.set("q", query);
|
|
71964
|
+
url.searchParams.set("count", String(maxResults));
|
|
71965
|
+
const response = await this.requestJson(
|
|
71966
|
+
url.toString(),
|
|
71967
|
+
{
|
|
71968
|
+
method: "GET",
|
|
71969
|
+
headers: {
|
|
71970
|
+
"X-Subscription-Token": this.options.braveApiKey ?? "",
|
|
71971
|
+
"User-Agent": this.options.userAgent ?? "FusionResearchBot/1.0"
|
|
71972
|
+
},
|
|
71973
|
+
signal
|
|
71974
|
+
},
|
|
71975
|
+
"brave"
|
|
71976
|
+
);
|
|
71977
|
+
return (response.web?.results ?? []).slice(0, maxResults).map((item) => ({
|
|
71978
|
+
url: item.url,
|
|
71979
|
+
title: item.title ?? item.url,
|
|
71980
|
+
snippet: item.description ?? ""
|
|
71981
|
+
}));
|
|
71982
|
+
}
|
|
71983
|
+
async searchGoogle(query, maxResults, signal) {
|
|
71984
|
+
const url = new URL("https://www.googleapis.com/customsearch/v1");
|
|
71985
|
+
url.searchParams.set("q", query);
|
|
71986
|
+
url.searchParams.set("num", String(maxResults));
|
|
71987
|
+
url.searchParams.set("key", this.options.googleApiKey ?? "");
|
|
71988
|
+
url.searchParams.set("cx", this.options.googleCx ?? "");
|
|
71989
|
+
const response = await this.requestJson(
|
|
71990
|
+
url.toString(),
|
|
71991
|
+
{
|
|
71992
|
+
method: "GET",
|
|
71993
|
+
signal
|
|
71994
|
+
},
|
|
71995
|
+
"google"
|
|
71996
|
+
);
|
|
71997
|
+
return (response.items ?? []).slice(0, maxResults).map((item) => ({
|
|
71998
|
+
url: item.link,
|
|
71999
|
+
title: item.title ?? item.link,
|
|
72000
|
+
snippet: item.snippet ?? ""
|
|
72001
|
+
}));
|
|
72002
|
+
}
|
|
72003
|
+
async searchTavily(query, maxResults, signal) {
|
|
72004
|
+
const response = await this.requestJson(
|
|
72005
|
+
"https://api.tavily.com/search",
|
|
72006
|
+
{
|
|
72007
|
+
method: "POST",
|
|
72008
|
+
headers: {
|
|
72009
|
+
"Content-Type": "application/json",
|
|
72010
|
+
"User-Agent": this.options.userAgent ?? "FusionResearchBot/1.0"
|
|
72011
|
+
},
|
|
72012
|
+
body: JSON.stringify({
|
|
72013
|
+
api_key: this.options.tavilyApiKey,
|
|
72014
|
+
query,
|
|
72015
|
+
max_results: maxResults
|
|
72016
|
+
}),
|
|
72017
|
+
signal
|
|
72018
|
+
},
|
|
72019
|
+
"tavily"
|
|
72020
|
+
);
|
|
72021
|
+
return (response.results ?? []).slice(0, maxResults).map((item) => ({
|
|
72022
|
+
url: item.url,
|
|
72023
|
+
title: item.title ?? item.url,
|
|
72024
|
+
snippet: item.content ?? ""
|
|
72025
|
+
}));
|
|
72026
|
+
}
|
|
72027
|
+
async withHttpRetry(operation, signal, backend) {
|
|
72028
|
+
let attempt = 0;
|
|
72029
|
+
let lastError;
|
|
72030
|
+
while (attempt < RETRY_MAX_ATTEMPTS) {
|
|
72031
|
+
if (signal.aborted) {
|
|
72032
|
+
throw new ResearchProviderError({ providerType: "web-search", code: "abort", message: "Search aborted" });
|
|
72033
|
+
}
|
|
72034
|
+
try {
|
|
72035
|
+
return await operation();
|
|
72036
|
+
} catch (error) {
|
|
72037
|
+
lastError = error;
|
|
72038
|
+
const isRetryableHttp = error instanceof ResearchProviderError && (error.code === "rate-limited" || error.code === "network-error" || error.code === "provider-unavailable") && error.retryable;
|
|
72039
|
+
attempt += 1;
|
|
72040
|
+
if (!isRetryableHttp || attempt >= RETRY_MAX_ATTEMPTS) {
|
|
72041
|
+
break;
|
|
72042
|
+
}
|
|
72043
|
+
const delay2 = Math.min(RETRY_BASE_DELAY_MS2 * 2 ** (attempt - 1), RETRY_MAX_DELAY_MS);
|
|
72044
|
+
const jitter = delay2 * (Math.random() * 0.2 - 0.1);
|
|
72045
|
+
const totalDelay = Math.max(0, delay2 + jitter);
|
|
72046
|
+
log12.warn(`retrying ${backend} search`, { attempt, totalDelay });
|
|
72047
|
+
await sleep3(totalDelay, signal);
|
|
72048
|
+
}
|
|
72049
|
+
}
|
|
72050
|
+
throw lastError;
|
|
72051
|
+
}
|
|
72052
|
+
async requestJson(url, init, backend) {
|
|
72053
|
+
try {
|
|
72054
|
+
const response = await fetch(url, init);
|
|
72055
|
+
if (!response.ok) {
|
|
72056
|
+
const message = `${backend} request failed with status ${response.status}`;
|
|
72057
|
+
if (response.status === 401 || response.status === 403) {
|
|
72058
|
+
throw new ResearchProviderError({ providerType: "web-search", code: "auth-failed", message });
|
|
72059
|
+
}
|
|
72060
|
+
if (response.status === 429) {
|
|
72061
|
+
throw new ResearchProviderError({
|
|
72062
|
+
providerType: "web-search",
|
|
72063
|
+
code: "rate-limited",
|
|
72064
|
+
message,
|
|
72065
|
+
retryable: true
|
|
72066
|
+
});
|
|
72067
|
+
}
|
|
72068
|
+
if (response.status >= 500) {
|
|
72069
|
+
throw new ResearchProviderError({
|
|
72070
|
+
providerType: "web-search",
|
|
72071
|
+
code: "provider-unavailable",
|
|
72072
|
+
message,
|
|
72073
|
+
retryable: true
|
|
72074
|
+
});
|
|
72075
|
+
}
|
|
72076
|
+
throw new ResearchProviderError({ providerType: "web-search", code: "network-error", message });
|
|
72077
|
+
}
|
|
72078
|
+
return await response.json();
|
|
72079
|
+
} catch (error) {
|
|
72080
|
+
if (error instanceof ResearchProviderError) throw error;
|
|
72081
|
+
if (error instanceof DOMException && error.name === "AbortError") {
|
|
72082
|
+
throw new ResearchProviderError({ providerType: "web-search", code: "abort", message: "Search aborted", cause: error });
|
|
72083
|
+
}
|
|
72084
|
+
if (error instanceof Error && error.name === "TimeoutError") {
|
|
72085
|
+
throw new ResearchProviderError({ providerType: "web-search", code: "timeout", message: error.message, retryable: true, cause: error });
|
|
72086
|
+
}
|
|
72087
|
+
throw new ResearchProviderError({
|
|
72088
|
+
providerType: "web-search",
|
|
72089
|
+
code: "network-error",
|
|
72090
|
+
message: error instanceof Error ? error.message : "Unexpected network error",
|
|
72091
|
+
retryable: true,
|
|
72092
|
+
cause: error
|
|
72093
|
+
});
|
|
72094
|
+
}
|
|
72095
|
+
}
|
|
72096
|
+
};
|
|
72097
|
+
}
|
|
72098
|
+
});
|
|
72099
|
+
|
|
72100
|
+
// ../engine/src/research/provider-registry.ts
|
|
72101
|
+
var log13, ResearchProviderRegistry, DisabledProvider;
|
|
72102
|
+
var init_provider_registry = __esm({
|
|
72103
|
+
"../engine/src/research/provider-registry.ts"() {
|
|
72104
|
+
"use strict";
|
|
72105
|
+
init_logger2();
|
|
72106
|
+
init_github_provider();
|
|
72107
|
+
init_llm_synthesis_provider();
|
|
72108
|
+
init_local_docs_provider();
|
|
72109
|
+
init_page_fetch_provider();
|
|
72110
|
+
init_web_search_provider();
|
|
72111
|
+
log13 = createLogger2("research:provider-registry");
|
|
72112
|
+
ResearchProviderRegistry = class {
|
|
72113
|
+
constructor(settings, projectRoot) {
|
|
72114
|
+
this.settings = settings;
|
|
72115
|
+
this.projectRoot = projectRoot;
|
|
72116
|
+
this.instantiateProviders();
|
|
72117
|
+
}
|
|
72118
|
+
providers = /* @__PURE__ */ new Map();
|
|
72119
|
+
getProvider(type) {
|
|
72120
|
+
return this.providers.get(type);
|
|
72121
|
+
}
|
|
72122
|
+
getAvailableProviders() {
|
|
72123
|
+
return [...this.providers.entries()].filter(([, provider]) => provider.isConfigured()).map(([type]) => type);
|
|
72124
|
+
}
|
|
72125
|
+
isProviderAvailable(type) {
|
|
72126
|
+
const provider = this.providers.get(type);
|
|
72127
|
+
return Boolean(provider?.isConfigured());
|
|
72128
|
+
}
|
|
72129
|
+
refreshSettings(settings) {
|
|
72130
|
+
this.settings = settings;
|
|
72131
|
+
this.instantiateProviders();
|
|
72132
|
+
}
|
|
72133
|
+
instantiateProviders() {
|
|
72134
|
+
const backend = this.resolveSearchBackend();
|
|
72135
|
+
const maxResults = Number(this.settings.researchMaxSearchResults ?? 10);
|
|
72136
|
+
const fetchTimeoutMs = Number(this.settings.researchFetchTimeoutMs ?? 3e4);
|
|
72137
|
+
const userAgent = this.settings.researchUserAgent ?? "FusionResearchBot/1.0";
|
|
72138
|
+
this.providers = /* @__PURE__ */ new Map([
|
|
72139
|
+
[
|
|
72140
|
+
"web-search",
|
|
72141
|
+
new WebSearchProvider({
|
|
72142
|
+
backend,
|
|
72143
|
+
searxngUrl: this.settings.researchSearxngUrl,
|
|
72144
|
+
braveApiKey: this.settings.researchBraveApiKey,
|
|
72145
|
+
googleApiKey: this.settings.researchGoogleSearchApiKey,
|
|
72146
|
+
googleCx: this.settings.researchGoogleSearchCx,
|
|
72147
|
+
tavilyApiKey: this.settings.researchTavilyApiKey,
|
|
72148
|
+
maxResults,
|
|
72149
|
+
timeoutMs: fetchTimeoutMs,
|
|
72150
|
+
userAgent
|
|
72151
|
+
})
|
|
72152
|
+
],
|
|
72153
|
+
["page-fetch", new PageFetchProvider({ timeoutMs: fetchTimeoutMs, userAgent })],
|
|
72154
|
+
["github", this.settings.researchGitHubEnabled ? new GitHubProvider() : new DisabledProvider("github")],
|
|
72155
|
+
[
|
|
72156
|
+
"local-docs",
|
|
72157
|
+
this.settings.researchLocalDocsEnabled === false ? new DisabledProvider("local-docs") : new LocalDocsProvider({ projectRoot: this.projectRoot, timeoutMs: fetchTimeoutMs, maxResults })
|
|
72158
|
+
],
|
|
72159
|
+
["llm-synthesis", new LLMSynthesisProvider({ projectRoot: this.projectRoot })]
|
|
72160
|
+
]);
|
|
72161
|
+
log13.log("providers refreshed", { available: this.getAvailableProviders(), backend });
|
|
72162
|
+
}
|
|
72163
|
+
resolveSearchBackend() {
|
|
72164
|
+
const explicit = this.settings.researchWebSearchProvider;
|
|
72165
|
+
if (explicit && explicit !== "none") return explicit;
|
|
72166
|
+
if (this.settings.researchSearxngUrl) return "searxng";
|
|
72167
|
+
if (this.settings.researchTavilyApiKey) return "tavily";
|
|
72168
|
+
if (this.settings.researchBraveApiKey) return "brave";
|
|
72169
|
+
if (this.settings.researchGoogleSearchApiKey && this.settings.researchGoogleSearchCx) return "google";
|
|
72170
|
+
return "none";
|
|
72171
|
+
}
|
|
72172
|
+
};
|
|
72173
|
+
DisabledProvider = class {
|
|
72174
|
+
type;
|
|
72175
|
+
constructor(type) {
|
|
72176
|
+
this.type = type;
|
|
72177
|
+
}
|
|
72178
|
+
isConfigured() {
|
|
72179
|
+
return false;
|
|
72180
|
+
}
|
|
72181
|
+
async search() {
|
|
72182
|
+
return [];
|
|
72183
|
+
}
|
|
72184
|
+
async fetchContent() {
|
|
72185
|
+
return { content: "", metadata: {} };
|
|
72186
|
+
}
|
|
72187
|
+
};
|
|
72188
|
+
}
|
|
72189
|
+
});
|
|
72190
|
+
|
|
72191
|
+
// ../engine/src/research/providers/index.ts
|
|
72192
|
+
var init_providers = __esm({
|
|
72193
|
+
"../engine/src/research/providers/index.ts"() {
|
|
72194
|
+
"use strict";
|
|
72195
|
+
init_web_search_provider();
|
|
72196
|
+
init_page_fetch_provider();
|
|
72197
|
+
init_github_provider();
|
|
72198
|
+
init_local_docs_provider();
|
|
72199
|
+
init_llm_synthesis_provider();
|
|
72200
|
+
}
|
|
72201
|
+
});
|
|
72202
|
+
|
|
70495
72203
|
// ../engine/src/pr-monitor-gh.ts
|
|
70496
72204
|
function createDefaultPrMonitorGhClient() {
|
|
70497
72205
|
return {
|
|
@@ -71409,13 +73117,14 @@ async function sendNtfyNotification({
|
|
|
71409
73117
|
schedulerLog.log(`Failed to send ntfy notification: ${err}`);
|
|
71410
73118
|
}
|
|
71411
73119
|
}
|
|
71412
|
-
var DEFAULT_NTFY_BASE_URL, DEFAULT_NTFY_EVENTS, NtfyNotifier;
|
|
73120
|
+
var DEFAULT_NTFY_BASE_URL, GRIDLOCK_NOTIFICATION_COOLDOWN_MS, DEFAULT_NTFY_EVENTS, NtfyNotifier;
|
|
71413
73121
|
var init_notifier = __esm({
|
|
71414
73122
|
"../engine/src/notifier.ts"() {
|
|
71415
73123
|
"use strict";
|
|
71416
73124
|
init_logger2();
|
|
71417
73125
|
init_notification();
|
|
71418
73126
|
DEFAULT_NTFY_BASE_URL = "https://ntfy.sh";
|
|
73127
|
+
GRIDLOCK_NOTIFICATION_COOLDOWN_MS = 15 * 60 * 1e3;
|
|
71419
73128
|
DEFAULT_NTFY_EVENTS = [
|
|
71420
73129
|
"in-review",
|
|
71421
73130
|
"merged",
|
|
@@ -71446,8 +73155,8 @@ var init_notifier = __esm({
|
|
|
71446
73155
|
ntfyBaseUrl;
|
|
71447
73156
|
defaultNtfyBaseUrl;
|
|
71448
73157
|
projectId;
|
|
71449
|
-
notifiedEvents = /* @__PURE__ */ new Set();
|
|
71450
73158
|
abortController = null;
|
|
73159
|
+
lastGridlockNotificationAt = null;
|
|
71451
73160
|
async start() {
|
|
71452
73161
|
this.abortController = new AbortController();
|
|
71453
73162
|
const settings = await this.store.getSettings();
|
|
@@ -71480,8 +73189,16 @@ var init_notifier = __esm({
|
|
|
71480
73189
|
this.ntfyBaseUrl = resolveNtfyBaseUrl(settings.ntfyBaseUrl, this.defaultNtfyBaseUrl);
|
|
71481
73190
|
}
|
|
71482
73191
|
notifyGridlock(event) {
|
|
73192
|
+
if (event === null) {
|
|
73193
|
+
this.lastGridlockNotificationAt = null;
|
|
73194
|
+
return;
|
|
73195
|
+
}
|
|
71483
73196
|
if (!this.config.enabled || !this.config.topic || !this.isEventEnabled("gridlock")) return;
|
|
71484
|
-
const
|
|
73197
|
+
const now = Date.now();
|
|
73198
|
+
if (this.lastGridlockNotificationAt !== null && now - this.lastGridlockNotificationAt < GRIDLOCK_NOTIFICATION_COOLDOWN_MS) {
|
|
73199
|
+
return;
|
|
73200
|
+
}
|
|
73201
|
+
const blockedTasks = [...event.blockedTaskIds].sort();
|
|
71485
73202
|
const reasonSummary = Object.values(event.reasons).reduce((acc, reason) => {
|
|
71486
73203
|
acc[reason] = (acc[reason] ?? 0) + 1;
|
|
71487
73204
|
return acc;
|
|
@@ -71493,31 +73210,21 @@ var init_notifier = __esm({
|
|
|
71493
73210
|
dashboardHost: this.config.dashboardHost,
|
|
71494
73211
|
projectId: this.projectId
|
|
71495
73212
|
});
|
|
71496
|
-
|
|
71497
|
-
|
|
71498
|
-
|
|
71499
|
-
|
|
71500
|
-
|
|
71501
|
-
|
|
71502
|
-
|
|
71503
|
-
|
|
71504
|
-
|
|
71505
|
-
|
|
71506
|
-
|
|
71507
|
-
})
|
|
71508
|
-
);
|
|
73213
|
+
this.lastGridlockNotificationAt = now;
|
|
73214
|
+
sendNtfyNotification({
|
|
73215
|
+
ntfyBaseUrl: this.ntfyBaseUrl,
|
|
73216
|
+
topic: this.config.topic,
|
|
73217
|
+
title: "Pipeline gridlocked",
|
|
73218
|
+
message: `${event.blockedTaskCount} todo tasks are blocked (${reasons.join(", ")}). Blocked: ${blockedTasks.join(", ")}. Blocking: ${event.blockingTaskIds.join(", ") || "none"}.`,
|
|
73219
|
+
priority: "high",
|
|
73220
|
+
clickUrl,
|
|
73221
|
+
signal: this.abortController?.signal
|
|
73222
|
+
}).catch(() => {
|
|
73223
|
+
});
|
|
71509
73224
|
}
|
|
71510
73225
|
isEventEnabled(event) {
|
|
71511
73226
|
return isNtfyEventEnabled(this.config.events, event);
|
|
71512
73227
|
}
|
|
71513
|
-
maybeNotifyByKey(key, notifyFn) {
|
|
71514
|
-
if (this.notifiedEvents.has(key)) {
|
|
71515
|
-
return;
|
|
71516
|
-
}
|
|
71517
|
-
this.notifiedEvents.add(key);
|
|
71518
|
-
notifyFn().catch(() => {
|
|
71519
|
-
});
|
|
71520
|
-
}
|
|
71521
73228
|
getConfig() {
|
|
71522
73229
|
return { ...this.config, events: [...this.config.events] };
|
|
71523
73230
|
}
|
|
@@ -71574,7 +73281,7 @@ function truncateOutput(stdout, stderr) {
|
|
|
71574
73281
|
}
|
|
71575
73282
|
return combined;
|
|
71576
73283
|
}
|
|
71577
|
-
var execAsync6,
|
|
73284
|
+
var execAsync6, log14, DEFAULT_TIMEOUT_MS6, MAX_BUFFER, MAX_OUTPUT_LENGTH, DEFAULT_POLL_INTERVAL_MS, MIN_POLL_INTERVAL_MS, CronRunner, AI_AUTOMATION_SYSTEM_PROMPT;
|
|
71578
73285
|
var init_cron_runner = __esm({
|
|
71579
73286
|
"../engine/src/cron-runner.ts"() {
|
|
71580
73287
|
"use strict";
|
|
@@ -71583,8 +73290,8 @@ var init_cron_runner = __esm({
|
|
|
71583
73290
|
init_shell_utils();
|
|
71584
73291
|
init_pi();
|
|
71585
73292
|
execAsync6 = promisify7(exec6);
|
|
71586
|
-
|
|
71587
|
-
|
|
73293
|
+
log14 = createLogger2("cron-runner");
|
|
73294
|
+
DEFAULT_TIMEOUT_MS6 = 5 * 60 * 1e3;
|
|
71588
73295
|
MAX_BUFFER = 1024 * 1024;
|
|
71589
73296
|
MAX_OUTPUT_LENGTH = 10 * 1024;
|
|
71590
73297
|
DEFAULT_POLL_INTERVAL_MS = 60 * 1e3;
|
|
@@ -71616,7 +73323,7 @@ var init_cron_runner = __esm({
|
|
|
71616
73323
|
start() {
|
|
71617
73324
|
if (this.running) return;
|
|
71618
73325
|
this.running = true;
|
|
71619
|
-
|
|
73326
|
+
log14.log(`Started (poll every ${this.pollIntervalMs / 1e3}s, scope: ${this.scope})`);
|
|
71620
73327
|
void this.tick();
|
|
71621
73328
|
this.pollInterval = setInterval(() => {
|
|
71622
73329
|
void this.tick();
|
|
@@ -71630,7 +73337,7 @@ var init_cron_runner = __esm({
|
|
|
71630
73337
|
clearInterval(this.pollInterval);
|
|
71631
73338
|
this.pollInterval = null;
|
|
71632
73339
|
}
|
|
71633
|
-
|
|
73340
|
+
log14.log("Stopped");
|
|
71634
73341
|
}
|
|
71635
73342
|
/**
|
|
71636
73343
|
* Single poll cycle: find due schedules and execute them.
|
|
@@ -71654,29 +73361,29 @@ var init_cron_runner = __esm({
|
|
|
71654
73361
|
const executedIds = /* @__PURE__ */ new Set();
|
|
71655
73362
|
for (const schedule of dueSchedules) {
|
|
71656
73363
|
if (this.inFlight.has(schedule.id)) {
|
|
71657
|
-
|
|
73364
|
+
log14.warn(`Skipping ${schedule.name} (${schedule.id}) \u2014 still running from previous tick`);
|
|
71658
73365
|
continue;
|
|
71659
73366
|
}
|
|
71660
73367
|
if (executedIds.has(schedule.id)) {
|
|
71661
|
-
|
|
73368
|
+
log14.log(`Skipping ${schedule.name} (${schedule.id}) \u2014 already executed from another scope this tick`);
|
|
71662
73369
|
continue;
|
|
71663
73370
|
}
|
|
71664
73371
|
executedIds.add(schedule.id);
|
|
71665
73372
|
const scheduleScope = schedule.scope ?? "project";
|
|
71666
73373
|
if (scheduleScope !== this.scope && this.scope !== "all") {
|
|
71667
|
-
|
|
73374
|
+
log14.log(`Skipping ${schedule.name} (${schedule.id}) \u2014 belongs to ${scheduleScope} scope, not polling`);
|
|
71668
73375
|
continue;
|
|
71669
73376
|
}
|
|
71670
|
-
|
|
73377
|
+
log14.log(`Executing ${schedule.name} (${schedule.id}) [scope: ${scheduleScope}]`);
|
|
71671
73378
|
const currentSettings = await this.store.getSettings();
|
|
71672
73379
|
if (currentSettings.globalPause || currentSettings.enginePaused) {
|
|
71673
|
-
|
|
73380
|
+
log14.log("Pause detected mid-tick \u2014 stopping schedule execution");
|
|
71674
73381
|
break;
|
|
71675
73382
|
}
|
|
71676
73383
|
await this.executeSchedule(schedule);
|
|
71677
73384
|
}
|
|
71678
73385
|
} catch (err) {
|
|
71679
|
-
|
|
73386
|
+
log14.error(`Tick error: ${err.message}`);
|
|
71680
73387
|
} finally {
|
|
71681
73388
|
this.ticking = false;
|
|
71682
73389
|
}
|
|
@@ -71706,13 +73413,13 @@ var init_cron_runner = __esm({
|
|
|
71706
73413
|
try {
|
|
71707
73414
|
await this.automationStore.recordRun(schedule.id, result);
|
|
71708
73415
|
} catch (recordErr) {
|
|
71709
|
-
|
|
73416
|
+
log14.error(`Failed to record run for ${schedule.id}: ${recordErr.message}`);
|
|
71710
73417
|
}
|
|
71711
73418
|
if (this.onScheduleRunProcessed) {
|
|
71712
73419
|
try {
|
|
71713
73420
|
await this.onScheduleRunProcessed(schedule, result);
|
|
71714
73421
|
} catch (callbackErr) {
|
|
71715
|
-
|
|
73422
|
+
log14.error(
|
|
71716
73423
|
`Post-run callback failed for ${schedule.name} (${schedule.id}): ${callbackErr.message}`
|
|
71717
73424
|
);
|
|
71718
73425
|
}
|
|
@@ -71723,16 +73430,16 @@ var init_cron_runner = __esm({
|
|
|
71723
73430
|
* Execute a legacy single-command schedule.
|
|
71724
73431
|
*/
|
|
71725
73432
|
async executeLegacyCommand(schedule, startedAt) {
|
|
71726
|
-
|
|
73433
|
+
log14.log(`Executing ${schedule.name} (${schedule.id}): ${schedule.command}`);
|
|
71727
73434
|
try {
|
|
71728
|
-
const timeoutMs = schedule.timeoutMs ??
|
|
73435
|
+
const timeoutMs = schedule.timeoutMs ?? DEFAULT_TIMEOUT_MS6;
|
|
71729
73436
|
const { stdout, stderr } = await execAsync6(schedule.command, {
|
|
71730
73437
|
timeout: timeoutMs,
|
|
71731
73438
|
maxBuffer: MAX_BUFFER,
|
|
71732
73439
|
shell: defaultShell
|
|
71733
73440
|
});
|
|
71734
73441
|
const output = truncateOutput(stdout, stderr);
|
|
71735
|
-
|
|
73442
|
+
log14.log(`\u2713 ${schedule.name} completed (${output.length} bytes output)`);
|
|
71736
73443
|
return {
|
|
71737
73444
|
success: true,
|
|
71738
73445
|
output,
|
|
@@ -71743,8 +73450,8 @@ var init_cron_runner = __esm({
|
|
|
71743
73450
|
const stdout = err.stdout ?? "";
|
|
71744
73451
|
const stderr = err.stderr ?? "";
|
|
71745
73452
|
const output = truncateOutput(stdout, stderr);
|
|
71746
|
-
const errorMessage = err.killed ? `Command timed out after ${(schedule.timeoutMs ??
|
|
71747
|
-
|
|
73453
|
+
const errorMessage = err.killed ? `Command timed out after ${(schedule.timeoutMs ?? DEFAULT_TIMEOUT_MS6) / 1e3}s` : err.message ?? String(err);
|
|
73454
|
+
log14.warn(`\u2717 ${schedule.name} failed: ${errorMessage}`);
|
|
71748
73455
|
return {
|
|
71749
73456
|
success: false,
|
|
71750
73457
|
output,
|
|
@@ -71760,25 +73467,25 @@ var init_cron_runner = __esm({
|
|
|
71760
73467
|
*/
|
|
71761
73468
|
async executeSteps(schedule, startedAt) {
|
|
71762
73469
|
const steps = schedule.steps;
|
|
71763
|
-
|
|
73470
|
+
log14.log(`Executing ${schedule.name} (${schedule.id}): ${steps.length} steps`);
|
|
71764
73471
|
const stepResults = [];
|
|
71765
73472
|
let overallSuccess = true;
|
|
71766
73473
|
let stoppedEarly = false;
|
|
71767
73474
|
for (let i = 0; i < steps.length; i++) {
|
|
71768
73475
|
const step = steps[i];
|
|
71769
|
-
|
|
73476
|
+
log14.log(` Step ${i + 1}/${steps.length}: ${step.name} (${step.type})`);
|
|
71770
73477
|
const stepResult = await this.executeStep(schedule, step, i);
|
|
71771
73478
|
stepResults.push(stepResult);
|
|
71772
73479
|
if (!stepResult.success) {
|
|
71773
73480
|
overallSuccess = false;
|
|
71774
73481
|
if (!step.continueOnFailure) {
|
|
71775
|
-
|
|
73482
|
+
log14.warn(` Step "${step.name}" failed \u2014 stopping execution`);
|
|
71776
73483
|
stoppedEarly = true;
|
|
71777
73484
|
break;
|
|
71778
73485
|
}
|
|
71779
|
-
|
|
73486
|
+
log14.warn(` Step "${step.name}" failed \u2014 continuing (continueOnFailure=true)`);
|
|
71780
73487
|
} else {
|
|
71781
|
-
|
|
73488
|
+
log14.log(` \u2713 Step "${step.name}" completed`);
|
|
71782
73489
|
}
|
|
71783
73490
|
}
|
|
71784
73491
|
const outputParts = [];
|
|
@@ -71791,7 +73498,7 @@ var init_cron_runner = __esm({
|
|
|
71791
73498
|
const failedSteps = stepResults.filter((sr) => !sr.success);
|
|
71792
73499
|
const error = failedSteps.length > 0 ? `${failedSteps.length} step(s) failed: ${failedSteps.map((s) => s.stepName).join(", ")}${stoppedEarly ? " (execution stopped)" : ""}` : void 0;
|
|
71793
73500
|
const status = overallSuccess ? "\u2713" : "\u2717";
|
|
71794
|
-
|
|
73501
|
+
log14.log(`${status} ${schedule.name}: ${stepResults.length}/${steps.length} steps executed, ${failedSteps.length} failed`);
|
|
71795
73502
|
return {
|
|
71796
73503
|
success: overallSuccess,
|
|
71797
73504
|
output,
|
|
@@ -71806,7 +73513,7 @@ var init_cron_runner = __esm({
|
|
|
71806
73513
|
*/
|
|
71807
73514
|
async executeStep(schedule, step, stepIndex) {
|
|
71808
73515
|
const stepStartedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
71809
|
-
const timeoutMs = step.timeoutMs ?? schedule.timeoutMs ??
|
|
73516
|
+
const timeoutMs = step.timeoutMs ?? schedule.timeoutMs ?? DEFAULT_TIMEOUT_MS6;
|
|
71810
73517
|
if (step.type === "command") {
|
|
71811
73518
|
return this.executeCommandStep(step, stepIndex, timeoutMs, stepStartedAt);
|
|
71812
73519
|
} else if (step.type === "ai-prompt") {
|
|
@@ -71908,8 +73615,8 @@ var init_cron_runner = __esm({
|
|
|
71908
73615
|
const modelProvider = step.modelProvider?.trim() || defaultModel.provider;
|
|
71909
73616
|
const modelId = step.modelId?.trim() || defaultModel.modelId;
|
|
71910
73617
|
const model = modelProvider && modelId ? `${modelProvider}/${modelId}` : "default";
|
|
71911
|
-
|
|
71912
|
-
|
|
73618
|
+
log14.log(` AI prompt step "${step.name}" using model: ${model}`);
|
|
73619
|
+
log14.log(` Prompt: ${step.prompt.slice(0, 100)}${step.prompt.length > 100 ? "\u2026" : ""}`);
|
|
71913
73620
|
try {
|
|
71914
73621
|
const resultPromise = this.aiPromptExecutor(step.prompt, modelProvider, modelId);
|
|
71915
73622
|
const timeoutPromise = new Promise((_resolve, reject) => {
|
|
@@ -71917,7 +73624,7 @@ var init_cron_runner = __esm({
|
|
|
71917
73624
|
});
|
|
71918
73625
|
const response = await Promise.race([resultPromise, timeoutPromise]);
|
|
71919
73626
|
const output = response.length > MAX_OUTPUT_LENGTH ? response.slice(0, MAX_OUTPUT_LENGTH) + "\n[output truncated]" : response;
|
|
71920
|
-
|
|
73627
|
+
log14.log(` \u2713 AI prompt step "${step.name}" completed (${response.length} chars)`);
|
|
71921
73628
|
return {
|
|
71922
73629
|
stepId: step.id,
|
|
71923
73630
|
stepName: step.name,
|
|
@@ -71929,7 +73636,7 @@ var init_cron_runner = __esm({
|
|
|
71929
73636
|
};
|
|
71930
73637
|
} catch (err) {
|
|
71931
73638
|
const errorMessage = err.message ?? String(err);
|
|
71932
|
-
|
|
73639
|
+
log14.warn(` \u2717 AI prompt step "${step.name}" failed: ${errorMessage}`);
|
|
71933
73640
|
return {
|
|
71934
73641
|
stepId: step.id,
|
|
71935
73642
|
stepName: step.name,
|
|
@@ -71973,7 +73680,7 @@ var init_cron_runner = __esm({
|
|
|
71973
73680
|
try {
|
|
71974
73681
|
const task = await this.store.createTask(taskInput);
|
|
71975
73682
|
const output = `Created task ${task.id}: ${task.title || task.description.slice(0, 80)}`;
|
|
71976
|
-
|
|
73683
|
+
log14.log(` \u2713 Create-task step "${step.name}" created task ${task.id}`);
|
|
71977
73684
|
return {
|
|
71978
73685
|
stepId: step.id,
|
|
71979
73686
|
stepName: step.name,
|
|
@@ -71985,7 +73692,7 @@ var init_cron_runner = __esm({
|
|
|
71985
73692
|
};
|
|
71986
73693
|
} catch (err) {
|
|
71987
73694
|
const errorMessage = err.message ?? String(err);
|
|
71988
|
-
|
|
73695
|
+
log14.warn(` \u2717 Create-task step "${step.name}" failed: ${errorMessage}`);
|
|
71989
73696
|
return {
|
|
71990
73697
|
stepId: step.id,
|
|
71991
73698
|
stepName: step.name,
|
|
@@ -72027,16 +73734,16 @@ function truncateOutput2(stdout, stderr) {
|
|
|
72027
73734
|
}
|
|
72028
73735
|
return output;
|
|
72029
73736
|
}
|
|
72030
|
-
var import_cron_parser4,
|
|
73737
|
+
var import_cron_parser4, log15, execAsync7, DEFAULT_TIMEOUT_MS7, MAX_BUFFER2, MAX_OUTPUT_LENGTH2, MAX_CATCH_UP_INTERVALS, RoutineRunner;
|
|
72031
73738
|
var init_routine_runner = __esm({
|
|
72032
73739
|
"../engine/src/routine-runner.ts"() {
|
|
72033
73740
|
"use strict";
|
|
72034
73741
|
import_cron_parser4 = __toESM(require_dist2(), 1);
|
|
72035
73742
|
init_logger2();
|
|
72036
73743
|
init_shell_utils();
|
|
72037
|
-
|
|
73744
|
+
log15 = createLogger2("routine-runner");
|
|
72038
73745
|
execAsync7 = promisify8(exec7);
|
|
72039
|
-
|
|
73746
|
+
DEFAULT_TIMEOUT_MS7 = 5 * 60 * 1e3;
|
|
72040
73747
|
MAX_BUFFER2 = 1024 * 1024;
|
|
72041
73748
|
MAX_OUTPUT_LENGTH2 = 10 * 1024;
|
|
72042
73749
|
MAX_CATCH_UP_INTERVALS = 10;
|
|
@@ -72071,7 +73778,7 @@ var init_routine_runner = __esm({
|
|
|
72071
73778
|
}
|
|
72072
73779
|
const concurrency = routine.executionPolicy ?? "queue";
|
|
72073
73780
|
if (concurrency === "reject" && this.inFlightExecutions.has(routineId)) {
|
|
72074
|
-
|
|
73781
|
+
log15.log(`Routine ${routineId} rejected \u2014 already running`);
|
|
72075
73782
|
return {
|
|
72076
73783
|
routineId,
|
|
72077
73784
|
success: false,
|
|
@@ -72082,7 +73789,7 @@ var init_routine_runner = __esm({
|
|
|
72082
73789
|
};
|
|
72083
73790
|
}
|
|
72084
73791
|
if (concurrency === "queue" && this.inFlightExecutions.has(routineId)) {
|
|
72085
|
-
|
|
73792
|
+
log15.log(`Routine ${routineId} queued \u2014 waiting for existing execution`);
|
|
72086
73793
|
const existingResult = await this.inFlightExecutions.get(routineId);
|
|
72087
73794
|
if (existingResult) {
|
|
72088
73795
|
await existingResult;
|
|
@@ -72130,7 +73837,7 @@ var init_routine_runner = __esm({
|
|
|
72130
73837
|
};
|
|
72131
73838
|
} catch (err) {
|
|
72132
73839
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
72133
|
-
|
|
73840
|
+
log15.error(`Routine ${routineId} execution failed: ${errorMessage}`);
|
|
72134
73841
|
try {
|
|
72135
73842
|
await this.options.routineStore.completeRoutineExecution(routineId, {
|
|
72136
73843
|
completedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -72138,7 +73845,7 @@ var init_routine_runner = __esm({
|
|
|
72138
73845
|
error: errorMessage
|
|
72139
73846
|
});
|
|
72140
73847
|
} catch (persistError) {
|
|
72141
|
-
|
|
73848
|
+
log15.error(`[${routineId}] Failed to persist error state: ${persistError}`);
|
|
72142
73849
|
}
|
|
72143
73850
|
return {
|
|
72144
73851
|
routineId,
|
|
@@ -72191,7 +73898,7 @@ var init_routine_runner = __esm({
|
|
|
72191
73898
|
async executeCommand(command, timeoutMs, startedAt) {
|
|
72192
73899
|
try {
|
|
72193
73900
|
const { stdout, stderr } = await execAsync7(command, {
|
|
72194
|
-
timeout: timeoutMs ??
|
|
73901
|
+
timeout: timeoutMs ?? DEFAULT_TIMEOUT_MS7,
|
|
72195
73902
|
maxBuffer: MAX_BUFFER2,
|
|
72196
73903
|
shell: defaultShell
|
|
72197
73904
|
});
|
|
@@ -72205,7 +73912,7 @@ var init_routine_runner = __esm({
|
|
|
72205
73912
|
const errObj = err;
|
|
72206
73913
|
const stdout = typeof errObj.stdout === "string" ? errObj.stdout : "";
|
|
72207
73914
|
const stderr = typeof errObj.stderr === "string" ? errObj.stderr : "";
|
|
72208
|
-
const error = errObj.killed === true ? `Command timed out after ${(timeoutMs ??
|
|
73915
|
+
const error = errObj.killed === true ? `Command timed out after ${(timeoutMs ?? DEFAULT_TIMEOUT_MS7) / 1e3}s` : (err instanceof Error ? err.message : null) ?? String(err);
|
|
72209
73916
|
return {
|
|
72210
73917
|
success: false,
|
|
72211
73918
|
output: truncateOutput2(stdout, stderr),
|
|
@@ -72250,7 +73957,7 @@ var init_routine_runner = __esm({
|
|
|
72250
73957
|
}
|
|
72251
73958
|
async executeStep(routine, step, stepIndex) {
|
|
72252
73959
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
72253
|
-
const timeoutMs = step.timeoutMs ?? routine.timeoutMs ??
|
|
73960
|
+
const timeoutMs = step.timeoutMs ?? routine.timeoutMs ?? DEFAULT_TIMEOUT_MS7;
|
|
72254
73961
|
if (step.type === "command") {
|
|
72255
73962
|
const result = await this.executeCommand(step.command ?? "", timeoutMs, startedAt);
|
|
72256
73963
|
return {
|
|
@@ -72352,7 +74059,7 @@ var init_routine_runner = __esm({
|
|
|
72352
74059
|
if (missedIntervals.length === 0) {
|
|
72353
74060
|
return;
|
|
72354
74061
|
}
|
|
72355
|
-
|
|
74062
|
+
log15.log(`[${routine.id}] Running ${missedIntervals.length} catch-up executions`);
|
|
72356
74063
|
for (const missedInterval of missedIntervals) {
|
|
72357
74064
|
try {
|
|
72358
74065
|
await this.executeRoutine(routine.id, "cron", {
|
|
@@ -72360,11 +74067,11 @@ var init_routine_runner = __esm({
|
|
|
72360
74067
|
missedInterval: missedInterval.toISOString()
|
|
72361
74068
|
});
|
|
72362
74069
|
} catch (err) {
|
|
72363
|
-
|
|
74070
|
+
log15.error(`[${routine.id}] Catch-up execution failed: ${err}`);
|
|
72364
74071
|
}
|
|
72365
74072
|
}
|
|
72366
74073
|
} catch (err) {
|
|
72367
|
-
|
|
74074
|
+
log15.error(`[${routine.id}] Error calculating catch-up intervals: ${err}`);
|
|
72368
74075
|
}
|
|
72369
74076
|
}
|
|
72370
74077
|
/**
|
|
@@ -72891,7 +74598,7 @@ var init_stuck_task_detector = __esm({
|
|
|
72891
74598
|
import { exec as exec8 } from "node:child_process";
|
|
72892
74599
|
import { promisify as promisify9 } from "node:util";
|
|
72893
74600
|
import { existsSync as existsSync28, readdirSync as readdirSync5, rmSync as rmSync3, statSync as statSync5 } from "node:fs";
|
|
72894
|
-
import { isAbsolute as isAbsolute12, join as
|
|
74601
|
+
import { isAbsolute as isAbsolute12, join as join36, relative as relative8, resolve as resolve16 } from "node:path";
|
|
72895
74602
|
function shellQuote(value) {
|
|
72896
74603
|
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
72897
74604
|
}
|
|
@@ -72925,19 +74632,25 @@ function isNoTaskDoneFailure(task) {
|
|
|
72925
74632
|
function hasStepProgress(task) {
|
|
72926
74633
|
return task.steps.some((step) => step.status !== "pending");
|
|
72927
74634
|
}
|
|
72928
|
-
var
|
|
74635
|
+
var log16, execAsync8, APPROVED_TRIAGE_RECOVERY_GRACE_MS, ORPHANED_EXECUTION_RECOVERY_GRACE_MS, ACTIVE_MERGE_STATUSES, NON_TERMINAL_STEP_STATUSES2, GHOST_REVIEW_PRESERVED_STATUSES, ORPHANED_WITH_WORKTREE_GRACE_MS, MAX_TASK_DONE_RETRIES, SelfHealingManager;
|
|
72929
74636
|
var init_self_healing = __esm({
|
|
72930
74637
|
"../engine/src/self-healing.ts"() {
|
|
72931
74638
|
"use strict";
|
|
72932
74639
|
init_src();
|
|
72933
74640
|
init_logger2();
|
|
72934
74641
|
init_worktree_pool();
|
|
72935
|
-
|
|
74642
|
+
log16 = createLogger2("self-healing");
|
|
72936
74643
|
execAsync8 = promisify9(exec8);
|
|
72937
74644
|
APPROVED_TRIAGE_RECOVERY_GRACE_MS = 6e4;
|
|
72938
74645
|
ORPHANED_EXECUTION_RECOVERY_GRACE_MS = 6e4;
|
|
72939
74646
|
ACTIVE_MERGE_STATUSES = /* @__PURE__ */ new Set(["merging", "merging-pr"]);
|
|
72940
74647
|
NON_TERMINAL_STEP_STATUSES2 = /* @__PURE__ */ new Set(["pending", "in-progress"]);
|
|
74648
|
+
GHOST_REVIEW_PRESERVED_STATUSES = /* @__PURE__ */ new Set([
|
|
74649
|
+
"awaiting-user-review",
|
|
74650
|
+
"awaiting-approval",
|
|
74651
|
+
"merging",
|
|
74652
|
+
"merging-pr"
|
|
74653
|
+
]);
|
|
72941
74654
|
ORPHANED_WITH_WORKTREE_GRACE_MS = 3e5;
|
|
72942
74655
|
MAX_TASK_DONE_RETRIES = 3;
|
|
72943
74656
|
SelfHealingManager = class _SelfHealingManager {
|
|
@@ -72962,7 +74675,7 @@ var init_self_healing = __esm({
|
|
|
72962
74675
|
};
|
|
72963
74676
|
this.store.on("settings:updated", this.settingsListener);
|
|
72964
74677
|
this.startMaintenance();
|
|
72965
|
-
|
|
74678
|
+
log16.log("Started");
|
|
72966
74679
|
}
|
|
72967
74680
|
/**
|
|
72968
74681
|
* Run only the recovery subset needed at runtime startup, after the executor
|
|
@@ -72987,10 +74700,10 @@ var init_self_healing = __esm({
|
|
|
72987
74700
|
for (const step of steps) {
|
|
72988
74701
|
try {
|
|
72989
74702
|
await step.fn();
|
|
72990
|
-
|
|
74703
|
+
log16.log(`Startup recovery step "${step.name}" completed`);
|
|
72991
74704
|
} catch (stepErr) {
|
|
72992
74705
|
const stepErrMessage = stepErr instanceof Error ? stepErr.message : String(stepErr);
|
|
72993
|
-
|
|
74706
|
+
log16.error(`Startup recovery step "${step.name}" failed: ${stepErrMessage} \u2014 continuing with remaining steps`);
|
|
72994
74707
|
}
|
|
72995
74708
|
}
|
|
72996
74709
|
}
|
|
@@ -73000,7 +74713,7 @@ var init_self_healing = __esm({
|
|
|
73000
74713
|
this.store.removeListener("settings:updated", this.settingsListener);
|
|
73001
74714
|
} catch (err) {
|
|
73002
74715
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
73003
|
-
|
|
74716
|
+
log16.warn(`Failed to remove settings:updated listener during stop(): ${errorMessage}`);
|
|
73004
74717
|
}
|
|
73005
74718
|
this.settingsListener = null;
|
|
73006
74719
|
}
|
|
@@ -73009,22 +74722,22 @@ var init_self_healing = __esm({
|
|
|
73009
74722
|
clearInterval(this.maintenanceInterval);
|
|
73010
74723
|
this.maintenanceInterval = null;
|
|
73011
74724
|
}
|
|
73012
|
-
|
|
74725
|
+
log16.log("Stopped");
|
|
73013
74726
|
}
|
|
73014
74727
|
// ── Auto-unpause ───────────────────────────────────────────────────
|
|
73015
74728
|
onSettingsUpdated(settings, previous) {
|
|
73016
74729
|
if (!previous.globalPause && settings.globalPause) {
|
|
73017
74730
|
if (!settings.autoUnpauseEnabled) {
|
|
73018
|
-
|
|
74731
|
+
log16.log("Global pause activated \u2014 auto-unpause disabled, requires manual intervention");
|
|
73019
74732
|
return;
|
|
73020
74733
|
}
|
|
73021
74734
|
if (settings.globalPauseReason === "manual") {
|
|
73022
|
-
|
|
74735
|
+
log16.log("Global pause activated manually \u2014 auto-unpause skipped, requires manual intervention");
|
|
73023
74736
|
return;
|
|
73024
74737
|
}
|
|
73025
74738
|
if (this.lastUnpauseAt && Date.now() - this.lastUnpauseAt < 6e4) {
|
|
73026
74739
|
this.unpauseAttempt++;
|
|
73027
|
-
|
|
74740
|
+
log16.warn(`Global pause re-triggered within 60s \u2014 escalating to attempt ${this.unpauseAttempt}`);
|
|
73028
74741
|
}
|
|
73029
74742
|
this.lastPauseTriggeredAt = Date.now();
|
|
73030
74743
|
const baseDelay = settings.autoUnpauseBaseDelayMs ?? 3e5;
|
|
@@ -73044,7 +74757,7 @@ var init_self_healing = __esm({
|
|
|
73044
74757
|
const delaySec = Math.round(delayMs / 1e3);
|
|
73045
74758
|
const delayMin = Math.round(delaySec / 60);
|
|
73046
74759
|
const display = delayMin >= 1 ? `${delayMin}m` : `${delaySec}s`;
|
|
73047
|
-
|
|
74760
|
+
log16.warn(`Auto-unpause scheduled in ${display} (attempt ${this.unpauseAttempt + 1})`);
|
|
73048
74761
|
this.unpauseTimer = setTimeout(() => {
|
|
73049
74762
|
this.unpauseTimer = null;
|
|
73050
74763
|
void this.attemptUnpause();
|
|
@@ -73054,16 +74767,16 @@ var init_self_healing = __esm({
|
|
|
73054
74767
|
try {
|
|
73055
74768
|
const settings = await this.store.getSettings();
|
|
73056
74769
|
if (!settings.globalPause) {
|
|
73057
|
-
|
|
74770
|
+
log16.log("Auto-unpause: already unpaused \u2014 no action needed");
|
|
73058
74771
|
this.unpauseAttempt = 0;
|
|
73059
74772
|
return;
|
|
73060
74773
|
}
|
|
73061
|
-
|
|
74774
|
+
log16.warn("Auto-unpause: clearing globalPause");
|
|
73062
74775
|
this.lastUnpauseAt = Date.now();
|
|
73063
74776
|
await this.store.updateSettings({ globalPause: false, globalPauseReason: void 0 });
|
|
73064
74777
|
} catch (err) {
|
|
73065
74778
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
73066
|
-
|
|
74779
|
+
log16.error(`Auto-unpause failed: ${errorMessage}`);
|
|
73067
74780
|
}
|
|
73068
74781
|
}
|
|
73069
74782
|
cancelUnpauseTimer() {
|
|
@@ -73087,7 +74800,7 @@ var init_self_healing = __esm({
|
|
|
73087
74800
|
const task = await this.store.getTask(taskId);
|
|
73088
74801
|
const newCount = (task.stuckKillCount ?? 0) + 1;
|
|
73089
74802
|
if (newCount > maxKills) {
|
|
73090
|
-
|
|
74803
|
+
log16.warn(`${taskId} exceeded stuck kill budget (${newCount}/${maxKills}) \u2014 marking failed`);
|
|
73091
74804
|
await this.store.updateTask(taskId, {
|
|
73092
74805
|
stuckKillCount: newCount,
|
|
73093
74806
|
status: "failed",
|
|
@@ -73097,7 +74810,7 @@ var init_self_healing = __esm({
|
|
|
73097
74810
|
await this.store.moveTask(taskId, "in-review");
|
|
73098
74811
|
} catch (moveErr) {
|
|
73099
74812
|
const moveErrMessage = moveErr instanceof Error ? moveErr.message : String(moveErr);
|
|
73100
|
-
|
|
74813
|
+
log16.warn(`${taskId} moveTask("in-review") failed (${moveErrMessage}) \u2014 task already marked failed, not re-queuing`);
|
|
73101
74814
|
}
|
|
73102
74815
|
await this.store.logEntry(
|
|
73103
74816
|
taskId,
|
|
@@ -73105,7 +74818,7 @@ var init_self_healing = __esm({
|
|
|
73105
74818
|
);
|
|
73106
74819
|
return false;
|
|
73107
74820
|
}
|
|
73108
|
-
|
|
74821
|
+
log16.log(`${taskId} stuck kill ${newCount}/${maxKills} \u2014 will re-queue`);
|
|
73109
74822
|
await this.store.updateTask(taskId, { stuckKillCount: newCount });
|
|
73110
74823
|
await this.store.logEntry(
|
|
73111
74824
|
taskId,
|
|
@@ -73114,7 +74827,7 @@ var init_self_healing = __esm({
|
|
|
73114
74827
|
return true;
|
|
73115
74828
|
} catch (err) {
|
|
73116
74829
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
73117
|
-
|
|
74830
|
+
log16.error(`checkStuckBudget failed for ${taskId}: ${errorMessage}`);
|
|
73118
74831
|
return true;
|
|
73119
74832
|
}
|
|
73120
74833
|
}
|
|
@@ -73143,7 +74856,7 @@ var init_self_healing = __esm({
|
|
|
73143
74856
|
);
|
|
73144
74857
|
const branchHead = branchHeadOut.trim();
|
|
73145
74858
|
if (mergeBase === branchHead) {
|
|
73146
|
-
|
|
74859
|
+
log16.warn(
|
|
73147
74860
|
`${task.id} branch has no unique commits \u2014 resetting ${completedSteps.length} step(s) to pending`
|
|
73148
74861
|
);
|
|
73149
74862
|
for (let i = 0; i < task.steps.length; i++) {
|
|
@@ -73158,7 +74871,7 @@ var init_self_healing = __esm({
|
|
|
73158
74871
|
}
|
|
73159
74872
|
} catch (err) {
|
|
73160
74873
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
73161
|
-
|
|
74874
|
+
log16.warn(
|
|
73162
74875
|
`Failed to reset steps for ${task.id} after branch/worktree loss (${branchName}): ${errorMessage} \u2014 non-fatal`
|
|
73163
74876
|
);
|
|
73164
74877
|
}
|
|
@@ -73168,10 +74881,10 @@ var init_self_healing = __esm({
|
|
|
73168
74881
|
const settings = await this.store.getSettings();
|
|
73169
74882
|
const intervalMs = settings.maintenanceIntervalMs ?? 9e5;
|
|
73170
74883
|
if (intervalMs <= 0) {
|
|
73171
|
-
|
|
74884
|
+
log16.log("Periodic maintenance disabled (maintenanceIntervalMs <= 0)");
|
|
73172
74885
|
return;
|
|
73173
74886
|
}
|
|
73174
|
-
|
|
74887
|
+
log16.log(`Periodic maintenance every ${Math.round(intervalMs / 6e4)}m`);
|
|
73175
74888
|
this.maintenanceInterval = setInterval(() => {
|
|
73176
74889
|
void this.runMaintenance();
|
|
73177
74890
|
}, intervalMs);
|
|
@@ -73234,7 +74947,7 @@ var init_self_healing = __esm({
|
|
|
73234
74947
|
out = r.stdout;
|
|
73235
74948
|
} catch (err) {
|
|
73236
74949
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
73237
|
-
|
|
74950
|
+
log16.warn(
|
|
73238
74951
|
`Failed to read git log for landed commit lookup (${task.id}): ${errorMessage} \u2014 retrying with HEAD range`
|
|
73239
74952
|
);
|
|
73240
74953
|
if (!task.baseCommitSha) return "";
|
|
@@ -73265,7 +74978,7 @@ var init_self_healing = __esm({
|
|
|
73265
74978
|
Object.assign(commit, parseShortstat(stats.stdout));
|
|
73266
74979
|
} catch (err) {
|
|
73267
74980
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
73268
|
-
|
|
74981
|
+
log16.warn(
|
|
73269
74982
|
`Failed to read shortstat for landed commit ${sha} (${task.id}): ${errorMessage} \u2014 continuing without stats`
|
|
73270
74983
|
);
|
|
73271
74984
|
}
|
|
@@ -73280,7 +74993,7 @@ var init_self_healing = __esm({
|
|
|
73280
74993
|
});
|
|
73281
74994
|
} catch (err) {
|
|
73282
74995
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
73283
|
-
|
|
74996
|
+
log16.warn(
|
|
73284
74997
|
`Failed to remove interrupted-merge worktree ${task.worktree} for ${task.id}: ${errorMessage} \u2014 non-fatal, cleanup can retry later`
|
|
73285
74998
|
);
|
|
73286
74999
|
}
|
|
@@ -73293,19 +75006,19 @@ var init_self_healing = __esm({
|
|
|
73293
75006
|
});
|
|
73294
75007
|
} catch (err) {
|
|
73295
75008
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
73296
|
-
|
|
75009
|
+
log16.warn(
|
|
73297
75010
|
`Failed to delete interrupted-merge branch ${branch} for ${task.id}: ${errorMessage} \u2014 non-fatal`
|
|
73298
75011
|
);
|
|
73299
75012
|
}
|
|
73300
75013
|
}
|
|
73301
75014
|
async runMaintenance() {
|
|
73302
75015
|
if (this.maintenanceRunning) {
|
|
73303
|
-
|
|
75016
|
+
log16.log("Maintenance cycle skipped \u2014 previous cycle still running");
|
|
73304
75017
|
return;
|
|
73305
75018
|
}
|
|
73306
75019
|
this.maintenanceRunning = true;
|
|
73307
75020
|
const startMs = Date.now();
|
|
73308
|
-
|
|
75021
|
+
log16.log("Maintenance cycle starting");
|
|
73309
75022
|
try {
|
|
73310
75023
|
const batch1Fns = [
|
|
73311
75024
|
{ name: "prune-worktrees", fn: () => this.pruneWorktrees() },
|
|
@@ -73317,9 +75030,9 @@ var init_self_healing = __esm({
|
|
|
73317
75030
|
for (const fn of batch1Fns) {
|
|
73318
75031
|
try {
|
|
73319
75032
|
await fn.fn();
|
|
73320
|
-
|
|
75033
|
+
log16.log(`Maintenance batch 1 step "${fn.name}" succeeded`);
|
|
73321
75034
|
} catch (stepErr) {
|
|
73322
|
-
|
|
75035
|
+
log16.error(`Maintenance batch 1 step "${fn.name}" failed: ${stepErr instanceof Error ? stepErr.message : String(stepErr)}`);
|
|
73323
75036
|
}
|
|
73324
75037
|
}
|
|
73325
75038
|
const batch2Fns = [
|
|
@@ -73334,14 +75047,15 @@ var init_self_healing = __esm({
|
|
|
73334
75047
|
{ name: "recover-partial-progress-no-task-done", fn: () => this.recoverPartialProgressNoTaskDoneFailures() },
|
|
73335
75048
|
{ name: "recover-orphaned-executions", fn: () => this.recoverOrphanedExecutions() },
|
|
73336
75049
|
{ name: "recover-approved-triage", fn: () => this.recoverApprovedTriageTasks() },
|
|
73337
|
-
{ name: "recover-orphaned-planning", fn: () => this.recoverOrphanedPlanningTasks() }
|
|
75050
|
+
{ name: "recover-orphaned-planning", fn: () => this.recoverOrphanedPlanningTasks() },
|
|
75051
|
+
{ name: "recover-ghost-review", fn: () => this.recoverGhostReviewTasks() }
|
|
73338
75052
|
];
|
|
73339
75053
|
for (const fn of batch2Fns) {
|
|
73340
75054
|
try {
|
|
73341
75055
|
await fn.fn();
|
|
73342
|
-
|
|
75056
|
+
log16.log(`Maintenance batch 2 step "${fn.name}" succeeded`);
|
|
73343
75057
|
} catch (stepErr) {
|
|
73344
|
-
|
|
75058
|
+
log16.error(`Maintenance batch 2 step "${fn.name}" failed: ${stepErr instanceof Error ? stepErr.message : String(stepErr)}`);
|
|
73345
75059
|
}
|
|
73346
75060
|
}
|
|
73347
75061
|
const batch3Fns = [
|
|
@@ -73350,13 +75064,13 @@ var init_self_healing = __esm({
|
|
|
73350
75064
|
for (const fn of batch3Fns) {
|
|
73351
75065
|
try {
|
|
73352
75066
|
await fn.fn();
|
|
73353
|
-
|
|
75067
|
+
log16.log(`Maintenance batch 3 step "${fn.name}" succeeded`);
|
|
73354
75068
|
} catch (stepErr) {
|
|
73355
|
-
|
|
75069
|
+
log16.error(`Maintenance batch 3 step "${fn.name}" failed: ${stepErr instanceof Error ? stepErr.message : String(stepErr)}`);
|
|
73356
75070
|
}
|
|
73357
75071
|
}
|
|
73358
75072
|
const elapsedMs = Date.now() - startMs;
|
|
73359
|
-
|
|
75073
|
+
log16.log(`Maintenance cycle completed in ${elapsedMs}ms`);
|
|
73360
75074
|
} finally {
|
|
73361
75075
|
this.maintenanceRunning = false;
|
|
73362
75076
|
}
|
|
@@ -73389,7 +75103,7 @@ var init_self_healing = __esm({
|
|
|
73389
75103
|
return movedAt < cutoff;
|
|
73390
75104
|
});
|
|
73391
75105
|
if (stale.length === 0) return 0;
|
|
73392
|
-
|
|
75106
|
+
log16.log(`Auto-archiving ${stale.length} done task(s) older than ${archiveAfterMs}ms`);
|
|
73393
75107
|
let archived = 0;
|
|
73394
75108
|
for (const task of stale) {
|
|
73395
75109
|
try {
|
|
@@ -73397,16 +75111,16 @@ var init_self_healing = __esm({
|
|
|
73397
75111
|
archived++;
|
|
73398
75112
|
} catch (err) {
|
|
73399
75113
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
73400
|
-
|
|
75114
|
+
log16.error(`Failed to auto-archive ${task.id}: ${errorMessage}`);
|
|
73401
75115
|
}
|
|
73402
75116
|
}
|
|
73403
75117
|
if (archived > 0) {
|
|
73404
|
-
|
|
75118
|
+
log16.log(`Auto-archived ${archived} stale done task(s)`);
|
|
73405
75119
|
}
|
|
73406
75120
|
return archived;
|
|
73407
75121
|
} catch (err) {
|
|
73408
75122
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
73409
|
-
|
|
75123
|
+
log16.error(`Auto-archive sweep failed: ${errorMessage}`);
|
|
73410
75124
|
return 0;
|
|
73411
75125
|
}
|
|
73412
75126
|
}
|
|
@@ -73431,25 +75145,25 @@ var init_self_healing = __esm({
|
|
|
73431
75145
|
(t) => t.column === "in-progress" && !t.paused && !executingIds.has(t.id) && t.steps.length > 0 && t.steps.every((s) => s.status === "done" || s.status === "skipped")
|
|
73432
75146
|
);
|
|
73433
75147
|
if (stuckCompleted.length === 0) return 0;
|
|
73434
|
-
|
|
75148
|
+
log16.warn(`Found ${stuckCompleted.length} completed task(s) stuck in in-progress`);
|
|
73435
75149
|
let recovered = 0;
|
|
73436
75150
|
for (const task of stuckCompleted) {
|
|
73437
75151
|
const latestExecutingIds = this.options.getExecutingTaskIds?.() ?? /* @__PURE__ */ new Set();
|
|
73438
75152
|
if (latestExecutingIds.has(task.id)) {
|
|
73439
|
-
|
|
75153
|
+
log16.log(`${task.id} started executing concurrently \u2014 skipping recovery this cycle`);
|
|
73440
75154
|
continue;
|
|
73441
75155
|
}
|
|
73442
|
-
|
|
75156
|
+
log16.log(`Recovering completed task ${task.id}: ${task.title || task.description?.slice(0, 60) || "(untitled)"}`);
|
|
73443
75157
|
const success = await recoverFn(task);
|
|
73444
75158
|
if (success) recovered++;
|
|
73445
75159
|
}
|
|
73446
75160
|
if (recovered > 0) {
|
|
73447
|
-
|
|
75161
|
+
log16.log(`Recovered ${recovered} completed task(s) \u2192 in-review`);
|
|
73448
75162
|
}
|
|
73449
75163
|
return recovered;
|
|
73450
75164
|
} catch (err) {
|
|
73451
75165
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
73452
|
-
|
|
75166
|
+
log16.error(`Completed task recovery failed: ${errorMessage}`);
|
|
73453
75167
|
return 0;
|
|
73454
75168
|
}
|
|
73455
75169
|
}
|
|
@@ -73472,7 +75186,7 @@ var init_self_healing = __esm({
|
|
|
73472
75186
|
(t) => t.column === "in-review" && !t.paused && Boolean(t.worktree) && t.mergeDetails?.mergeConfirmed !== true && getTaskMergeBlocker(t) === void 0
|
|
73473
75187
|
);
|
|
73474
75188
|
if (mergeable.length === 0) return 0;
|
|
73475
|
-
|
|
75189
|
+
log16.warn(`Found ${mergeable.length} mergeable review task(s) stuck in in-review`);
|
|
73476
75190
|
const enqueueMerge = this.options.enqueueMerge;
|
|
73477
75191
|
let recovered = 0;
|
|
73478
75192
|
for (const task of mergeable) {
|
|
@@ -73486,20 +75200,20 @@ var init_self_healing = __esm({
|
|
|
73486
75200
|
task.id,
|
|
73487
75201
|
enqueueMerge ? "Auto-recovered: eligible in-review task re-enqueued for merge" : "Auto-recovered: eligible in-review task was merged and moved to done"
|
|
73488
75202
|
);
|
|
73489
|
-
|
|
75203
|
+
log16.log(`Recovered mergeable review task ${task.id}`);
|
|
73490
75204
|
recovered++;
|
|
73491
75205
|
} catch (err) {
|
|
73492
75206
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
73493
|
-
|
|
75207
|
+
log16.error(`Failed to recover mergeable review task ${task.id}: ${errorMessage}`);
|
|
73494
75208
|
}
|
|
73495
75209
|
}
|
|
73496
75210
|
if (recovered > 0) {
|
|
73497
|
-
|
|
75211
|
+
log16.log(`Recovered ${recovered} mergeable review task(s) \u2192 done`);
|
|
73498
75212
|
}
|
|
73499
75213
|
return recovered;
|
|
73500
75214
|
} catch (err) {
|
|
73501
75215
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
73502
|
-
|
|
75216
|
+
log16.error(`Mergeable review recovery failed: ${errorMessage}`);
|
|
73503
75217
|
return 0;
|
|
73504
75218
|
}
|
|
73505
75219
|
}
|
|
@@ -73547,7 +75261,7 @@ var init_self_healing = __esm({
|
|
|
73547
75261
|
return true;
|
|
73548
75262
|
});
|
|
73549
75263
|
if (candidates.length === 0) return 0;
|
|
73550
|
-
|
|
75264
|
+
log16.warn(`Found ${candidates.length} in-review task(s) with failed pre-merge workflow steps \u2014 auto-reviving`);
|
|
73551
75265
|
let recovered = 0;
|
|
73552
75266
|
for (const task of candidates) {
|
|
73553
75267
|
const nextCount = (task.postReviewFixCount ?? 0) + 1;
|
|
@@ -73559,23 +75273,23 @@ var init_self_healing = __esm({
|
|
|
73559
75273
|
);
|
|
73560
75274
|
const sentBack = await recoverFn(task);
|
|
73561
75275
|
if (sentBack) {
|
|
73562
|
-
|
|
75276
|
+
log16.log(`Revived ${task.id}: sent back for fix (${nextCount}/${maxFixes})`);
|
|
73563
75277
|
recovered++;
|
|
73564
75278
|
} else {
|
|
73565
|
-
|
|
75279
|
+
log16.warn(`Revival of ${task.id} was skipped by executor \u2014 budget already consumed`);
|
|
73566
75280
|
}
|
|
73567
75281
|
} catch (err) {
|
|
73568
75282
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
73569
|
-
|
|
75283
|
+
log16.error(`Failed to revive ${task.id}: ${errorMessage}`);
|
|
73570
75284
|
}
|
|
73571
75285
|
}
|
|
73572
75286
|
if (recovered > 0) {
|
|
73573
|
-
|
|
75287
|
+
log16.log(`Auto-revived ${recovered} in-review task(s) for pre-merge workflow step fix`);
|
|
73574
75288
|
}
|
|
73575
75289
|
return recovered;
|
|
73576
75290
|
} catch (err) {
|
|
73577
75291
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
73578
|
-
|
|
75292
|
+
log16.error(`Failed pre-merge workflow step revival failed: ${errorMessage}`);
|
|
73579
75293
|
return 0;
|
|
73580
75294
|
}
|
|
73581
75295
|
}
|
|
@@ -73599,7 +75313,7 @@ var init_self_healing = __esm({
|
|
|
73599
75313
|
(task) => task.column === "in-review" && !task.paused && !task.status && task.steps.length > 0 && task.steps.some((step) => NON_TERMINAL_STEP_STATUSES2.has(step.status)) && now - new Date(task.updatedAt).getTime() >= timeoutMs
|
|
73600
75314
|
);
|
|
73601
75315
|
if (staleIncomplete.length === 0) return 0;
|
|
73602
|
-
|
|
75316
|
+
log16.warn(`Found ${staleIncomplete.length} stale in-review task(s) with incomplete steps`);
|
|
73603
75317
|
let recovered = 0;
|
|
73604
75318
|
for (const task of staleIncomplete) {
|
|
73605
75319
|
try {
|
|
@@ -73608,17 +75322,82 @@ var init_self_healing = __esm({
|
|
|
73608
75322
|
"Auto-recovered: in-review task still had incomplete steps \u2014 moved back to todo for retry"
|
|
73609
75323
|
);
|
|
73610
75324
|
await this.store.moveTask(task.id, "todo");
|
|
73611
|
-
|
|
75325
|
+
log16.log(`Recovered stale incomplete review task ${task.id}: moved back to todo`);
|
|
73612
75326
|
recovered++;
|
|
73613
75327
|
} catch (err) {
|
|
73614
75328
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
73615
|
-
|
|
75329
|
+
log16.error(`Failed to recover stale incomplete review task ${task.id}: ${errorMessage}`);
|
|
73616
75330
|
}
|
|
73617
75331
|
}
|
|
73618
75332
|
return recovered;
|
|
73619
75333
|
} catch (err) {
|
|
73620
75334
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
73621
|
-
|
|
75335
|
+
log16.error(`Stale incomplete review recovery failed: ${errorMessage}`);
|
|
75336
|
+
return 0;
|
|
75337
|
+
}
|
|
75338
|
+
}
|
|
75339
|
+
/**
|
|
75340
|
+
* Final-fallback recovery for `in-review` tasks that fell through every other
|
|
75341
|
+
* scan and have sat untouched longer than `taskStuckTimeoutMs`.
|
|
75342
|
+
*
|
|
75343
|
+
* The other review-recovery scans each require a specific shape (failed
|
|
75344
|
+
* pre-merge step, incomplete steps, mergeable + worktree present, confirmed
|
|
75345
|
+
* merge, transient merge status). A task whose state doesn't match any of
|
|
75346
|
+
* those shapes — e.g. `status: "failed"` with no failed pre-merge step, or
|
|
75347
|
+
* any other unanticipated combination — has no recovery path and stays
|
|
75348
|
+
* silent in review forever.
|
|
75349
|
+
*
|
|
75350
|
+
* This catch-all kicks any such task back to `todo`, clearing transient
|
|
75351
|
+
* `status` so the scheduler can pick it up. Worktree state is intentionally
|
|
75352
|
+
* not considered: the executor will recreate one if needed.
|
|
75353
|
+
*
|
|
75354
|
+
* Preserved statuses (skipped):
|
|
75355
|
+
* - `awaiting-user-review`, `awaiting-approval`: explicit human handoff
|
|
75356
|
+
* - `merging`, `merging-pr`: handled by `recoverInterruptedMergingTasks`
|
|
75357
|
+
*
|
|
75358
|
+
* Rate-limiting comes from the `updatedAt >= taskStuckTimeoutMs` gate —
|
|
75359
|
+
* each kick refreshes `updatedAt`, so a task that re-enters review and gets
|
|
75360
|
+
* stuck again can only be kicked once per `taskStuckTimeoutMs` window.
|
|
75361
|
+
*
|
|
75362
|
+
* @returns Number of tasks kicked back to todo
|
|
75363
|
+
*/
|
|
75364
|
+
async recoverGhostReviewTasks() {
|
|
75365
|
+
try {
|
|
75366
|
+
const settings = await this.store.getSettings();
|
|
75367
|
+
const timeoutMs = settings.taskStuckTimeoutMs;
|
|
75368
|
+
if (!timeoutMs || timeoutMs <= 0) return 0;
|
|
75369
|
+
if (settings.globalPause || settings.enginePaused) return 0;
|
|
75370
|
+
const now = Date.now();
|
|
75371
|
+
const executingIds = this.options.getExecutingTaskIds?.() ?? /* @__PURE__ */ new Set();
|
|
75372
|
+
const tasks = await this.store.listTasks({ column: "in-review", slim: true });
|
|
75373
|
+
const ghosts = tasks.filter(
|
|
75374
|
+
(task) => task.column === "in-review" && !task.paused && !executingIds.has(task.id) && !(task.status && GHOST_REVIEW_PRESERVED_STATUSES.has(task.status)) && // Confirmed merges belong in `done` (handled by `recoverMergedReviewTasks`).
|
|
75375
|
+
task.mergeDetails?.mergeConfirmed !== true && now - new Date(task.updatedAt).getTime() >= timeoutMs
|
|
75376
|
+
);
|
|
75377
|
+
if (ghosts.length === 0) return 0;
|
|
75378
|
+
log16.warn(`Found ${ghosts.length} ghost in-review task(s) \u2014 kicking back to todo`);
|
|
75379
|
+
let recovered = 0;
|
|
75380
|
+
for (const task of ghosts) {
|
|
75381
|
+
try {
|
|
75382
|
+
if (task.status) {
|
|
75383
|
+
await this.store.updateTask(task.id, { status: null, error: null });
|
|
75384
|
+
}
|
|
75385
|
+
await this.store.logEntry(
|
|
75386
|
+
task.id,
|
|
75387
|
+
"Auto-recovered: in-review task idle past stuck-task timeout \u2014 kicked back to todo"
|
|
75388
|
+
);
|
|
75389
|
+
await this.store.moveTask(task.id, "todo");
|
|
75390
|
+
log16.log(`Kicked ghost review task ${task.id} back to todo`);
|
|
75391
|
+
recovered++;
|
|
75392
|
+
} catch (err) {
|
|
75393
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
75394
|
+
log16.error(`Failed to kick ghost review task ${task.id}: ${errorMessage}`);
|
|
75395
|
+
}
|
|
75396
|
+
}
|
|
75397
|
+
return recovered;
|
|
75398
|
+
} catch (err) {
|
|
75399
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
75400
|
+
log16.error(`Ghost review recovery failed: ${errorMessage}`);
|
|
73622
75401
|
return 0;
|
|
73623
75402
|
}
|
|
73624
75403
|
}
|
|
@@ -73646,7 +75425,7 @@ var init_self_healing = __esm({
|
|
|
73646
75425
|
(task) => task.column === "in-review" && !task.paused && Boolean(task.status && ACTIVE_MERGE_STATUSES.has(task.status)) && this.isPastInterruptedMergeGrace(task, timeoutMs)
|
|
73647
75426
|
);
|
|
73648
75427
|
if (candidates.length === 0) return 0;
|
|
73649
|
-
|
|
75428
|
+
log16.warn(`Found ${candidates.length} stale merging task(s) in in-review`);
|
|
73650
75429
|
let recovered = 0;
|
|
73651
75430
|
for (const task of candidates) {
|
|
73652
75431
|
try {
|
|
@@ -73674,7 +75453,7 @@ var init_self_healing = __esm({
|
|
|
73674
75453
|
task.id,
|
|
73675
75454
|
`Auto-recovered: stale merge status finalized from landed commit ${landedCommit.sha.slice(0, 8)}`
|
|
73676
75455
|
);
|
|
73677
|
-
|
|
75456
|
+
log16.log(`Recovered interrupted merge ${task.id}: finalized landed commit ${landedCommit.sha.slice(0, 8)}`);
|
|
73678
75457
|
recovered++;
|
|
73679
75458
|
continue;
|
|
73680
75459
|
}
|
|
@@ -73683,27 +75462,27 @@ var init_self_healing = __esm({
|
|
|
73683
75462
|
task.id,
|
|
73684
75463
|
"Auto-recovered: stale merge status cleared; merge will be retried"
|
|
73685
75464
|
);
|
|
73686
|
-
|
|
75465
|
+
log16.log(`Recovered interrupted merge ${task.id}: cleared stale status for retry`);
|
|
73687
75466
|
try {
|
|
73688
75467
|
this.options.enqueueMerge?.(task.id);
|
|
73689
75468
|
} catch (enqueueErr) {
|
|
73690
|
-
|
|
75469
|
+
log16.warn(
|
|
73691
75470
|
`Failed to re-enqueue ${task.id} after stale-merge recovery (will rely on polling sweep): ${enqueueErr instanceof Error ? enqueueErr.message : String(enqueueErr)}`
|
|
73692
75471
|
);
|
|
73693
75472
|
}
|
|
73694
75473
|
recovered++;
|
|
73695
75474
|
} catch (err) {
|
|
73696
75475
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
73697
|
-
|
|
75476
|
+
log16.error(`Failed to recover interrupted merge ${task.id}: ${errorMessage}`);
|
|
73698
75477
|
}
|
|
73699
75478
|
}
|
|
73700
75479
|
if (recovered > 0) {
|
|
73701
|
-
|
|
75480
|
+
log16.log(`Recovered ${recovered} interrupted merge task(s)`);
|
|
73702
75481
|
}
|
|
73703
75482
|
return recovered;
|
|
73704
75483
|
} catch (err) {
|
|
73705
75484
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
73706
|
-
|
|
75485
|
+
log16.error(`Interrupted merge recovery failed: ${errorMessage}`);
|
|
73707
75486
|
return 0;
|
|
73708
75487
|
}
|
|
73709
75488
|
}
|
|
@@ -73724,7 +75503,7 @@ var init_self_healing = __esm({
|
|
|
73724
75503
|
(t) => t.column === "in-review" && !t.paused && t.mergeDetails?.mergeConfirmed === true
|
|
73725
75504
|
);
|
|
73726
75505
|
if (mergedButNotDone.length === 0) return 0;
|
|
73727
|
-
|
|
75506
|
+
log16.warn(`Found ${mergedButNotDone.length} merged task(s) stuck in in-review`);
|
|
73728
75507
|
let recovered = 0;
|
|
73729
75508
|
for (const task of mergedButNotDone) {
|
|
73730
75509
|
try {
|
|
@@ -73738,20 +75517,20 @@ var init_self_healing = __esm({
|
|
|
73738
75517
|
task.id,
|
|
73739
75518
|
"Auto-recovered: merge already confirmed \u2014 moved from in-review to done"
|
|
73740
75519
|
);
|
|
73741
|
-
|
|
75520
|
+
log16.log(`Recovered merged task ${task.id}: moved to done`);
|
|
73742
75521
|
recovered++;
|
|
73743
75522
|
} catch (err) {
|
|
73744
75523
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
73745
|
-
|
|
75524
|
+
log16.error(`Failed to recover merged task ${task.id}: ${errorMessage}`);
|
|
73746
75525
|
}
|
|
73747
75526
|
}
|
|
73748
75527
|
if (recovered > 0) {
|
|
73749
|
-
|
|
75528
|
+
log16.log(`Recovered ${recovered} merged task(s) \u2192 done`);
|
|
73750
75529
|
}
|
|
73751
75530
|
return recovered;
|
|
73752
75531
|
} catch (err) {
|
|
73753
75532
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
73754
|
-
|
|
75533
|
+
log16.error(`Merged review recovery failed: ${errorMessage}`);
|
|
73755
75534
|
return 0;
|
|
73756
75535
|
}
|
|
73757
75536
|
}
|
|
@@ -73772,7 +75551,7 @@ var init_self_healing = __esm({
|
|
|
73772
75551
|
(t) => t.column === "in-review" && !t.paused && t.status === "failed" && t.error?.includes("without calling task_done") && t.steps.length > 0 && t.steps.every((s) => s.status === "done" || s.status === "skipped")
|
|
73773
75552
|
);
|
|
73774
75553
|
if (misclassified.length === 0) return 0;
|
|
73775
|
-
|
|
75554
|
+
log16.warn(`Found ${misclassified.length} misclassified failure(s) with all steps done`);
|
|
73776
75555
|
let recovered = 0;
|
|
73777
75556
|
for (const task of misclassified) {
|
|
73778
75557
|
try {
|
|
@@ -73784,20 +75563,20 @@ var init_self_healing = __esm({
|
|
|
73784
75563
|
task.id,
|
|
73785
75564
|
"Auto-recovered: all steps complete despite 'no task_done' failure \u2014 cleared error for normal review"
|
|
73786
75565
|
);
|
|
73787
|
-
|
|
75566
|
+
log16.log(`Recovered misclassified failure ${task.id}: ${task.title || task.description?.slice(0, 60) || "(untitled)"}`);
|
|
73788
75567
|
recovered++;
|
|
73789
75568
|
} catch (err) {
|
|
73790
75569
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
73791
|
-
|
|
75570
|
+
log16.error(`Failed to recover misclassified failure ${task.id}: ${errorMessage}`);
|
|
73792
75571
|
}
|
|
73793
75572
|
}
|
|
73794
75573
|
if (recovered > 0) {
|
|
73795
|
-
|
|
75574
|
+
log16.log(`Recovered ${recovered} misclassified failure(s) \u2192 cleared for review`);
|
|
73796
75575
|
}
|
|
73797
75576
|
return recovered;
|
|
73798
75577
|
} catch (err) {
|
|
73799
75578
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
73800
|
-
|
|
75579
|
+
log16.error(`Misclassified failure recovery failed: ${errorMessage}`);
|
|
73801
75580
|
return 0;
|
|
73802
75581
|
}
|
|
73803
75582
|
}
|
|
@@ -73821,7 +75600,7 @@ var init_self_healing = __esm({
|
|
|
73821
75600
|
return staleness >= graceMs;
|
|
73822
75601
|
});
|
|
73823
75602
|
if (orphaned.length === 0) return 0;
|
|
73824
|
-
|
|
75603
|
+
log16.warn(`Found ${orphaned.length} orphaned executor task(s) stuck in in-progress`);
|
|
73825
75604
|
let recovered = 0;
|
|
73826
75605
|
for (const task of orphaned) {
|
|
73827
75606
|
try {
|
|
@@ -73841,16 +75620,16 @@ var init_self_healing = __esm({
|
|
|
73841
75620
|
recovered++;
|
|
73842
75621
|
} catch (err) {
|
|
73843
75622
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
73844
|
-
|
|
75623
|
+
log16.error(`Failed to recover orphaned executor task ${task.id}: ${errorMessage}`);
|
|
73845
75624
|
}
|
|
73846
75625
|
}
|
|
73847
75626
|
if (recovered > 0) {
|
|
73848
|
-
|
|
75627
|
+
log16.log(`Recovered ${recovered} orphaned executor task(s) \u2192 todo`);
|
|
73849
75628
|
}
|
|
73850
75629
|
return recovered;
|
|
73851
75630
|
} catch (err) {
|
|
73852
75631
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
73853
|
-
|
|
75632
|
+
log16.error(`Orphaned executor recovery failed: ${errorMessage}`);
|
|
73854
75633
|
return 0;
|
|
73855
75634
|
}
|
|
73856
75635
|
}
|
|
@@ -73871,12 +75650,12 @@ var init_self_healing = __esm({
|
|
|
73871
75650
|
(task) => task.column === "in-progress" && task.status === "failed" && isNoTaskDoneFailure(task) && !task.paused && !executingIds.has(task.id) && !isTaskWorkComplete(task) && !hasStepProgress(task)
|
|
73872
75651
|
);
|
|
73873
75652
|
if (candidates.length === 0) return 0;
|
|
73874
|
-
|
|
75653
|
+
log16.warn(`Found ${candidates.length} no-progress no-task_done failure(s) in in-progress`);
|
|
73875
75654
|
let recovered = 0;
|
|
73876
75655
|
for (const task of candidates) {
|
|
73877
75656
|
try {
|
|
73878
75657
|
if (await this.hasRecoverableGitWork(task)) {
|
|
73879
|
-
|
|
75658
|
+
log16.log(`${task.id} has recoverable git work \u2014 leaving in-progress for inspection`);
|
|
73880
75659
|
continue;
|
|
73881
75660
|
}
|
|
73882
75661
|
await this.store.updateTask(task.id, {
|
|
@@ -73892,16 +75671,16 @@ var init_self_healing = __esm({
|
|
|
73892
75671
|
recovered++;
|
|
73893
75672
|
} catch (err) {
|
|
73894
75673
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
73895
|
-
|
|
75674
|
+
log16.error(`Failed to recover no-progress no-task_done failure ${task.id}: ${errorMessage}`);
|
|
73896
75675
|
}
|
|
73897
75676
|
}
|
|
73898
75677
|
if (recovered > 0) {
|
|
73899
|
-
|
|
75678
|
+
log16.log(`Recovered ${recovered} no-progress no-task_done failure(s) \u2192 todo`);
|
|
73900
75679
|
}
|
|
73901
75680
|
return recovered;
|
|
73902
75681
|
} catch (err) {
|
|
73903
75682
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
73904
|
-
|
|
75683
|
+
log16.error(`No-progress no-task_done recovery failed: ${errorMessage}`);
|
|
73905
75684
|
return 0;
|
|
73906
75685
|
}
|
|
73907
75686
|
}
|
|
@@ -73932,7 +75711,7 @@ var init_self_healing = __esm({
|
|
|
73932
75711
|
(task) => task.column === "in-review" && task.status === "failed" && isNoTaskDoneFailure(task) && !task.paused && !isTaskWorkComplete(task) && hasStepProgress(task) && (task.taskDoneRetryCount ?? 0) < MAX_TASK_DONE_RETRIES
|
|
73933
75712
|
);
|
|
73934
75713
|
if (candidates.length === 0) return 0;
|
|
73935
|
-
|
|
75714
|
+
log16.warn(
|
|
73936
75715
|
`Found ${candidates.length} partial-progress no-task_done failure(s) eligible for auto-retry`
|
|
73937
75716
|
);
|
|
73938
75717
|
let recovered = 0;
|
|
@@ -73953,20 +75732,20 @@ var init_self_healing = __esm({
|
|
|
73953
75732
|
recovered++;
|
|
73954
75733
|
} catch (err) {
|
|
73955
75734
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
73956
|
-
|
|
75735
|
+
log16.error(
|
|
73957
75736
|
`Failed to auto-retry partial-progress no-task_done failure ${task.id}: ${errorMessage}`
|
|
73958
75737
|
);
|
|
73959
75738
|
}
|
|
73960
75739
|
}
|
|
73961
75740
|
if (recovered > 0) {
|
|
73962
|
-
|
|
75741
|
+
log16.log(
|
|
73963
75742
|
`Auto-retried ${recovered} partial-progress no-task_done failure(s) \u2192 todo`
|
|
73964
75743
|
);
|
|
73965
75744
|
}
|
|
73966
75745
|
return recovered;
|
|
73967
75746
|
} catch (err) {
|
|
73968
75747
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
73969
|
-
|
|
75748
|
+
log16.error(`Partial-progress no-task_done recovery failed: ${errorMessage}`);
|
|
73970
75749
|
return 0;
|
|
73971
75750
|
}
|
|
73972
75751
|
}
|
|
@@ -73980,7 +75759,7 @@ var init_self_healing = __esm({
|
|
|
73980
75759
|
if (status.trim().length > 0) return true;
|
|
73981
75760
|
} catch (err) {
|
|
73982
75761
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
73983
|
-
|
|
75762
|
+
log16.warn(
|
|
73984
75763
|
`Failed to inspect worktree status for ${task.id} at ${task.worktree}: ${errorMessage} \u2014 preserving worktree`
|
|
73985
75764
|
);
|
|
73986
75765
|
return true;
|
|
@@ -74003,7 +75782,7 @@ var init_self_healing = __esm({
|
|
|
74003
75782
|
return Number.parseInt(uniqueCommits.trim(), 10) > 0;
|
|
74004
75783
|
} catch (err) {
|
|
74005
75784
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
74006
|
-
|
|
75785
|
+
log16.warn(
|
|
74007
75786
|
`Failed to compare branch ${branchName} against HEAD for ${task.id}: ${errorMessage} \u2014 preserving branch`
|
|
74008
75787
|
);
|
|
74009
75788
|
return true;
|
|
@@ -74028,20 +75807,20 @@ var init_self_healing = __esm({
|
|
|
74028
75807
|
(t) => t.column === "triage" && t.status === "planning" && !t.paused && !planningIds.has(t.id) && now - new Date(t.updatedAt).getTime() >= APPROVED_TRIAGE_RECOVERY_GRACE_MS && hasLatestSpecReviewApproval2(t)
|
|
74029
75808
|
);
|
|
74030
75809
|
if (orphanedApproved.length === 0) return 0;
|
|
74031
|
-
|
|
75810
|
+
log16.warn(`Found ${orphanedApproved.length} approved triage task(s) stuck in planning`);
|
|
74032
75811
|
let recovered = 0;
|
|
74033
75812
|
for (const task of orphanedApproved) {
|
|
74034
|
-
|
|
75813
|
+
log16.log(`Recovering approved triage task ${task.id}: ${task.title || task.description?.slice(0, 60) || "(untitled)"}`);
|
|
74035
75814
|
const success = await recoverFn(task);
|
|
74036
75815
|
if (success) recovered++;
|
|
74037
75816
|
}
|
|
74038
75817
|
if (recovered > 0) {
|
|
74039
|
-
|
|
75818
|
+
log16.log(`Recovered ${recovered} approved triage task(s) out of planning`);
|
|
74040
75819
|
}
|
|
74041
75820
|
return recovered;
|
|
74042
75821
|
} catch (err) {
|
|
74043
75822
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
74044
|
-
|
|
75823
|
+
log16.error(`Approved triage recovery failed: ${errorMessage}`);
|
|
74045
75824
|
return 0;
|
|
74046
75825
|
}
|
|
74047
75826
|
}
|
|
@@ -74067,11 +75846,11 @@ var init_self_healing = __esm({
|
|
|
74067
75846
|
(t) => t.column === "triage" && t.status === "planning" && !t.paused && !planningIds.has(t.id) && now - new Date(t.updatedAt).getTime() >= APPROVED_TRIAGE_RECOVERY_GRACE_MS && !hasLatestSpecReviewApproval2(t)
|
|
74068
75847
|
);
|
|
74069
75848
|
if (orphaned.length === 0) return 0;
|
|
74070
|
-
|
|
75849
|
+
log16.warn(`Found ${orphaned.length} orphaned planning triage task(s) without approval`);
|
|
74071
75850
|
let recovered = 0;
|
|
74072
75851
|
for (const task of orphaned) {
|
|
74073
75852
|
try {
|
|
74074
|
-
|
|
75853
|
+
log16.log(`Recovering orphaned planning task ${task.id}: ${task.title || task.description?.slice(0, 60) || "(untitled)"}`);
|
|
74075
75854
|
await this.store.updateTask(task.id, { status: null });
|
|
74076
75855
|
await this.store.logEntry(
|
|
74077
75856
|
task.id,
|
|
@@ -74080,16 +75859,16 @@ var init_self_healing = __esm({
|
|
|
74080
75859
|
recovered++;
|
|
74081
75860
|
} catch (err) {
|
|
74082
75861
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
74083
|
-
|
|
75862
|
+
log16.error(`Failed to recover orphaned planning task ${task.id}: ${errorMessage}`);
|
|
74084
75863
|
}
|
|
74085
75864
|
}
|
|
74086
75865
|
if (recovered > 0) {
|
|
74087
|
-
|
|
75866
|
+
log16.log(`Recovered ${recovered} orphaned planning task(s) \u2014 cleared for re-planning`);
|
|
74088
75867
|
}
|
|
74089
75868
|
return recovered;
|
|
74090
75869
|
} catch (err) {
|
|
74091
75870
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
74092
|
-
|
|
75871
|
+
log16.error(`Orphaned planning task recovery failed: ${errorMessage}`);
|
|
74093
75872
|
return 0;
|
|
74094
75873
|
}
|
|
74095
75874
|
}
|
|
@@ -74100,10 +75879,10 @@ var init_self_healing = __esm({
|
|
|
74100
75879
|
cwd: this.options.rootDir,
|
|
74101
75880
|
timeout: 3e4
|
|
74102
75881
|
});
|
|
74103
|
-
|
|
75882
|
+
log16.log("Worktree prune completed");
|
|
74104
75883
|
} catch (err) {
|
|
74105
75884
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
74106
|
-
|
|
75885
|
+
log16.error(`Worktree prune failed: ${errorMessage}`);
|
|
74107
75886
|
}
|
|
74108
75887
|
}
|
|
74109
75888
|
/**
|
|
@@ -74136,16 +75915,16 @@ var init_self_healing = __esm({
|
|
|
74136
75915
|
cleaned++;
|
|
74137
75916
|
} catch (err) {
|
|
74138
75917
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
74139
|
-
|
|
75918
|
+
log16.warn(`Failed to remove orphaned worktree ${worktreePath}: ${errorMessage} \u2014 non-fatal`);
|
|
74140
75919
|
}
|
|
74141
75920
|
}
|
|
74142
75921
|
if (cleaned > 0) {
|
|
74143
|
-
|
|
75922
|
+
log16.log(`Cleaned ${cleaned} orphaned worktree(s)`);
|
|
74144
75923
|
}
|
|
74145
75924
|
return cleaned;
|
|
74146
75925
|
} catch (err) {
|
|
74147
75926
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
74148
|
-
|
|
75927
|
+
log16.error(`Orphan cleanup failed: ${errorMessage}`);
|
|
74149
75928
|
return 0;
|
|
74150
75929
|
}
|
|
74151
75930
|
}
|
|
@@ -74156,35 +75935,35 @@ var init_self_healing = __esm({
|
|
|
74156
75935
|
* tracks registered idle worktrees, never these orphans.
|
|
74157
75936
|
*/
|
|
74158
75937
|
async reapUnregisteredOrphans() {
|
|
74159
|
-
const worktreesDir =
|
|
75938
|
+
const worktreesDir = join36(this.options.rootDir, ".worktrees");
|
|
74160
75939
|
if (!existsSync28(worktreesDir)) return 0;
|
|
74161
75940
|
let dirs;
|
|
74162
75941
|
try {
|
|
74163
|
-
dirs = readdirSync5(worktreesDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) =>
|
|
75942
|
+
dirs = readdirSync5(worktreesDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => join36(worktreesDir, e.name));
|
|
74164
75943
|
} catch (err) {
|
|
74165
|
-
|
|
75944
|
+
log16.warn(`Failed to read .worktrees/ for unregistered orphan reap: ${err instanceof Error ? err.message : String(err)}`);
|
|
74166
75945
|
return 0;
|
|
74167
75946
|
}
|
|
74168
75947
|
if (dirs.length === 0) return 0;
|
|
74169
75948
|
const registered = await getRegisteredWorktreePaths(this.options.rootDir);
|
|
74170
|
-
const unregistered = dirs.filter((d) => !registered.has(
|
|
75949
|
+
const unregistered = dirs.filter((d) => !registered.has(resolve16(d)));
|
|
74171
75950
|
let cleaned = 0;
|
|
74172
75951
|
for (const path2 of unregistered) {
|
|
74173
|
-
const rel =
|
|
75952
|
+
const rel = relative8(worktreesDir, path2);
|
|
74174
75953
|
if (rel === "" || rel.startsWith("..") || isAbsolute12(rel)) {
|
|
74175
|
-
|
|
75954
|
+
log16.warn(`Refusing to remove path outside .worktrees: ${path2}`);
|
|
74176
75955
|
continue;
|
|
74177
75956
|
}
|
|
74178
75957
|
try {
|
|
74179
75958
|
rmSync3(path2, { recursive: true, force: true });
|
|
74180
|
-
|
|
75959
|
+
log16.log(`Cleaned unregistered worktree dir: ${path2}`);
|
|
74181
75960
|
cleaned++;
|
|
74182
75961
|
} catch (err) {
|
|
74183
|
-
|
|
75962
|
+
log16.warn(`Failed to remove unregistered worktree dir ${path2}: ${err instanceof Error ? err.message : String(err)}`);
|
|
74184
75963
|
}
|
|
74185
75964
|
}
|
|
74186
75965
|
if (cleaned > 0) {
|
|
74187
|
-
|
|
75966
|
+
log16.log(`Cleaned ${cleaned} unregistered worktree dir(s) (recycle mode preserves registered idle worktrees)`);
|
|
74188
75967
|
}
|
|
74189
75968
|
return cleaned;
|
|
74190
75969
|
}
|
|
@@ -74213,12 +75992,12 @@ var init_self_healing = __esm({
|
|
|
74213
75992
|
cwd: this.options.rootDir,
|
|
74214
75993
|
timeout: 3e4
|
|
74215
75994
|
});
|
|
74216
|
-
|
|
75995
|
+
log16.log(`Deleted branch: ${branch}`);
|
|
74217
75996
|
cleaned++;
|
|
74218
75997
|
deletedBranches.push(branch);
|
|
74219
75998
|
} catch (err) {
|
|
74220
75999
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
74221
|
-
|
|
76000
|
+
log16.warn(
|
|
74222
76001
|
`Safe delete failed for orphaned branch ${branch}: ${errorMessage} \u2014 attempting force delete`
|
|
74223
76002
|
);
|
|
74224
76003
|
try {
|
|
@@ -74226,28 +76005,28 @@ var init_self_healing = __esm({
|
|
|
74226
76005
|
cwd: this.options.rootDir,
|
|
74227
76006
|
timeout: 3e4
|
|
74228
76007
|
});
|
|
74229
|
-
|
|
76008
|
+
log16.log(`Force-deleted branch: ${branch}`);
|
|
74230
76009
|
cleaned++;
|
|
74231
76010
|
deletedBranches.push(branch);
|
|
74232
76011
|
} catch (forceErr) {
|
|
74233
76012
|
const forceErrorMessage = forceErr instanceof Error ? forceErr.message : String(forceErr);
|
|
74234
|
-
|
|
76013
|
+
log16.warn(`Failed to force-delete orphaned branch ${branch}: ${forceErrorMessage} \u2014 non-fatal`);
|
|
74235
76014
|
}
|
|
74236
76015
|
}
|
|
74237
76016
|
}
|
|
74238
76017
|
if (deletedBranches.length > 0) {
|
|
74239
76018
|
const cleared = this.store.clearStaleBaseBranchReferences(deletedBranches);
|
|
74240
76019
|
if (cleared.length > 0) {
|
|
74241
|
-
|
|
76020
|
+
log16.log(`Cleared stale baseBranch on ${cleared.length} task(s): ${cleared.join(", ")}`);
|
|
74242
76021
|
}
|
|
74243
76022
|
}
|
|
74244
76023
|
if (cleaned > 0) {
|
|
74245
|
-
|
|
76024
|
+
log16.log(`Cleaned ${cleaned} orphaned branch(es)`);
|
|
74246
76025
|
}
|
|
74247
76026
|
return cleaned;
|
|
74248
76027
|
} catch (err) {
|
|
74249
76028
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
74250
|
-
|
|
76029
|
+
log16.error(`Orphaned branch cleanup failed: ${errorMessage}`);
|
|
74251
76030
|
return 0;
|
|
74252
76031
|
}
|
|
74253
76032
|
}
|
|
@@ -74256,16 +76035,16 @@ var init_self_healing = __esm({
|
|
|
74256
76035
|
try {
|
|
74257
76036
|
const result = this.store.walCheckpoint();
|
|
74258
76037
|
if (result.log > 0) {
|
|
74259
|
-
|
|
76038
|
+
log16.log(`WAL checkpoint: ${result.checkpointed}/${result.log} pages checkpointed` + (result.busy > 0 ? ` (${result.busy} busy)` : ""));
|
|
74260
76039
|
}
|
|
74261
76040
|
} catch (err) {
|
|
74262
76041
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
74263
|
-
|
|
76042
|
+
log16.error(`WAL checkpoint failed: ${errorMessage}`);
|
|
74264
76043
|
}
|
|
74265
76044
|
}
|
|
74266
76045
|
/** Remove oldest idle worktrees if total count exceeds 2× maxWorktrees. */
|
|
74267
76046
|
async enforceWorktreeCap() {
|
|
74268
|
-
const worktreesDir =
|
|
76047
|
+
const worktreesDir = join36(this.options.rootDir, ".worktrees");
|
|
74269
76048
|
if (!existsSync28(worktreesDir)) return;
|
|
74270
76049
|
try {
|
|
74271
76050
|
const settings = await this.store.getSettings();
|
|
@@ -74280,7 +76059,7 @@ var init_self_healing = __esm({
|
|
|
74280
76059
|
return { path: p, mtime: statSync5(p).mtimeMs };
|
|
74281
76060
|
} catch (err) {
|
|
74282
76061
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
74283
|
-
|
|
76062
|
+
log16.warn(`Failed to read mtime for worktree ${p}: ${errorMessage} \u2014 defaulting mtime to 0`);
|
|
74284
76063
|
return { path: p, mtime: 0 };
|
|
74285
76064
|
}
|
|
74286
76065
|
});
|
|
@@ -74297,15 +76076,15 @@ var init_self_healing = __esm({
|
|
|
74297
76076
|
removed++;
|
|
74298
76077
|
} catch (err) {
|
|
74299
76078
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
74300
|
-
|
|
76079
|
+
log16.warn(`Failed to remove idle worktree ${worktreePath} during cap enforcement: ${errorMessage} \u2014 non-fatal`);
|
|
74301
76080
|
}
|
|
74302
76081
|
}
|
|
74303
76082
|
if (removed > 0) {
|
|
74304
|
-
|
|
76083
|
+
log16.warn(`Worktree cap: removed ${removed} idle worktree(s) (was ${dirs.length}, cap ${cap})`);
|
|
74305
76084
|
}
|
|
74306
76085
|
} catch (err) {
|
|
74307
76086
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
74308
|
-
|
|
76087
|
+
log16.error(`Worktree cap enforcement failed: ${errorMessage}`);
|
|
74309
76088
|
}
|
|
74310
76089
|
}
|
|
74311
76090
|
};
|
|
@@ -74804,13 +76583,13 @@ var init_plugin_runner = __esm({
|
|
|
74804
76583
|
* Returns the result on success, throws on timeout.
|
|
74805
76584
|
*/
|
|
74806
76585
|
withTimeout(promise, ms, timeoutMessage) {
|
|
74807
|
-
return new Promise((
|
|
76586
|
+
return new Promise((resolve19, reject) => {
|
|
74808
76587
|
const timer = setTimeout(() => {
|
|
74809
76588
|
reject(new Error(timeoutMessage));
|
|
74810
76589
|
}, ms);
|
|
74811
76590
|
promise.then((result) => {
|
|
74812
76591
|
clearTimeout(timer);
|
|
74813
|
-
|
|
76592
|
+
resolve19(result);
|
|
74814
76593
|
}).catch((err) => {
|
|
74815
76594
|
clearTimeout(timer);
|
|
74816
76595
|
reject(err);
|
|
@@ -75275,7 +77054,7 @@ var init_in_process_runtime = __esm({
|
|
|
75275
77054
|
try {
|
|
75276
77055
|
const { RoutineStore: RoutineStoreClass } = await Promise.resolve().then(() => (init_src(), src_exports));
|
|
75277
77056
|
if (typeof RoutineStoreClass.prototype.getDueRoutines === "function") {
|
|
75278
|
-
const routineStore = new RoutineStoreClass(this.
|
|
77057
|
+
const routineStore = new RoutineStoreClass(this.config.workingDirectory);
|
|
75279
77058
|
await routineStore.init();
|
|
75280
77059
|
this.routineStore = routineStore;
|
|
75281
77060
|
if (this.heartbeatMonitor) {
|
|
@@ -75446,7 +77225,7 @@ var init_in_process_runtime = __esm({
|
|
|
75446
77225
|
runtimeLog.log(
|
|
75447
77226
|
`Waiting for ${metrics.inFlightTasks} in-flight tasks to complete...`
|
|
75448
77227
|
);
|
|
75449
|
-
await new Promise((
|
|
77228
|
+
await new Promise((resolve19) => setTimeout(resolve19, 1e3));
|
|
75450
77229
|
}
|
|
75451
77230
|
const finalMetrics = this.getMetrics();
|
|
75452
77231
|
if (finalMetrics.inFlightTasks > 0) {
|
|
@@ -75843,13 +77622,13 @@ var init_ipc_host = __esm({
|
|
|
75843
77622
|
}
|
|
75844
77623
|
const id = generateCorrelationId();
|
|
75845
77624
|
const message = { type, id, payload };
|
|
75846
|
-
return new Promise((
|
|
77625
|
+
return new Promise((resolve19, reject) => {
|
|
75847
77626
|
const timeout = setTimeout(() => {
|
|
75848
77627
|
this.pendingCommands.delete(id);
|
|
75849
77628
|
reject(new Error(`Command ${type} timed out after ${timeoutMs ?? this.commandTimeoutMs}ms`));
|
|
75850
77629
|
}, timeoutMs ?? this.commandTimeoutMs);
|
|
75851
77630
|
this.pendingCommands.set(id, {
|
|
75852
|
-
resolve:
|
|
77631
|
+
resolve: resolve19,
|
|
75853
77632
|
reject,
|
|
75854
77633
|
timeout,
|
|
75855
77634
|
type
|
|
@@ -75927,7 +77706,7 @@ var init_ipc_host = __esm({
|
|
|
75927
77706
|
import { EventEmitter as EventEmitter20 } from "node:events";
|
|
75928
77707
|
import { fork } from "node:child_process";
|
|
75929
77708
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
75930
|
-
import { dirname as dirname10, join as
|
|
77709
|
+
import { dirname as dirname10, join as join37 } from "node:path";
|
|
75931
77710
|
var HealthMonitor, ChildProcessRuntime;
|
|
75932
77711
|
var init_child_process_runtime = __esm({
|
|
75933
77712
|
"../engine/src/runtimes/child-process-runtime.ts"() {
|
|
@@ -76089,7 +77868,7 @@ var init_child_process_runtime = __esm({
|
|
|
76089
77868
|
const isCompiled = !import.meta.url.endsWith(".ts");
|
|
76090
77869
|
const currentDir = dirname10(fileURLToPath3(import.meta.url));
|
|
76091
77870
|
const workerFile = isCompiled ? "child-process-worker.js" : "child-process-worker.ts";
|
|
76092
|
-
return
|
|
77871
|
+
return join37(currentDir, workerFile);
|
|
76093
77872
|
}
|
|
76094
77873
|
/**
|
|
76095
77874
|
* Set up event forwarding from IPC host to runtime listeners.
|
|
@@ -76320,7 +78099,7 @@ var init_child_process_runtime = __esm({
|
|
|
76320
78099
|
});
|
|
76321
78100
|
|
|
76322
78101
|
// ../engine/src/runtimes/remote-node-client.ts
|
|
76323
|
-
var RemoteNodeRequestError,
|
|
78102
|
+
var RemoteNodeRequestError, RETRY_BASE_DELAY_MS3, DEFAULT_MAX_RETRIES, RemoteNodeClient;
|
|
76324
78103
|
var init_remote_node_client = __esm({
|
|
76325
78104
|
"../engine/src/runtimes/remote-node-client.ts"() {
|
|
76326
78105
|
"use strict";
|
|
@@ -76333,7 +78112,7 @@ var init_remote_node_client = __esm({
|
|
|
76333
78112
|
this.name = "RemoteNodeRequestError";
|
|
76334
78113
|
}
|
|
76335
78114
|
};
|
|
76336
|
-
|
|
78115
|
+
RETRY_BASE_DELAY_MS3 = 1e3;
|
|
76337
78116
|
DEFAULT_MAX_RETRIES = 3;
|
|
76338
78117
|
RemoteNodeClient = class {
|
|
76339
78118
|
baseUrl;
|
|
@@ -76638,7 +78417,7 @@ var init_remote_node_client = __esm({
|
|
|
76638
78417
|
if (!isRetryable || attempt >= maxRetries) {
|
|
76639
78418
|
throw error;
|
|
76640
78419
|
}
|
|
76641
|
-
const delayMs =
|
|
78420
|
+
const delayMs = RETRY_BASE_DELAY_MS3 * 2 ** attempt;
|
|
76642
78421
|
attempt += 1;
|
|
76643
78422
|
remoteNodeLog.warn(
|
|
76644
78423
|
`Request failed, retrying in ${delayMs}ms (attempt ${attempt}/${maxRetries})`,
|
|
@@ -76658,8 +78437,8 @@ var init_remote_node_client = __esm({
|
|
|
76658
78437
|
return error instanceof TypeError;
|
|
76659
78438
|
}
|
|
76660
78439
|
async sleep(ms) {
|
|
76661
|
-
await new Promise((
|
|
76662
|
-
setTimeout(
|
|
78440
|
+
await new Promise((resolve19) => {
|
|
78441
|
+
setTimeout(resolve19, ms);
|
|
76663
78442
|
});
|
|
76664
78443
|
}
|
|
76665
78444
|
};
|
|
@@ -76923,14 +78702,14 @@ var init_remote_node_runtime = __esm({
|
|
|
76923
78702
|
return error instanceof Error ? error : new Error(String(error));
|
|
76924
78703
|
}
|
|
76925
78704
|
async sleep(ms, signal) {
|
|
76926
|
-
await new Promise((
|
|
78705
|
+
await new Promise((resolve19) => {
|
|
76927
78706
|
const timeout = setTimeout(() => {
|
|
76928
78707
|
cleanup();
|
|
76929
|
-
|
|
78708
|
+
resolve19();
|
|
76930
78709
|
}, ms);
|
|
76931
78710
|
const onAbort = () => {
|
|
76932
78711
|
cleanup();
|
|
76933
|
-
|
|
78712
|
+
resolve19();
|
|
76934
78713
|
};
|
|
76935
78714
|
const cleanup = () => {
|
|
76936
78715
|
clearTimeout(timeout);
|
|
@@ -77271,11 +79050,13 @@ var init_gridlock_detector = __esm({
|
|
|
77271
79050
|
this.pollIntervalMs = options.pollIntervalMs ?? 3e4;
|
|
77272
79051
|
this.missionStore = options.missionStore;
|
|
77273
79052
|
this.onGridlock = options.onGridlock;
|
|
79053
|
+
this.onGridlockCleared = options.onGridlockCleared;
|
|
77274
79054
|
}
|
|
77275
79055
|
interval = null;
|
|
77276
79056
|
pollIntervalMs;
|
|
77277
79057
|
missionStore;
|
|
77278
79058
|
onGridlock;
|
|
79059
|
+
onGridlockCleared;
|
|
77279
79060
|
lastGridlockKey = null;
|
|
77280
79061
|
start() {
|
|
77281
79062
|
if (this.interval) return;
|
|
@@ -77305,12 +79086,12 @@ var init_gridlock_detector = __esm({
|
|
|
77305
79086
|
return true;
|
|
77306
79087
|
});
|
|
77307
79088
|
if (schedulable.length === 0) {
|
|
77308
|
-
this.
|
|
79089
|
+
this.clearGridlockState();
|
|
77309
79090
|
return null;
|
|
77310
79091
|
}
|
|
77311
79092
|
const active = tasks.filter((task) => task.column === "in-progress" || task.column === "in-review" && Boolean(task.worktree));
|
|
77312
79093
|
if (active.length === 0) {
|
|
77313
|
-
this.
|
|
79094
|
+
this.clearGridlockState();
|
|
77314
79095
|
return null;
|
|
77315
79096
|
}
|
|
77316
79097
|
const overlapIgnorePaths = settings.overlapIgnorePaths ?? [];
|
|
@@ -77348,7 +79129,7 @@ var init_gridlock_detector = __esm({
|
|
|
77348
79129
|
}
|
|
77349
79130
|
const blockedTaskIds = Object.keys(reasons).sort();
|
|
77350
79131
|
if (blockedTaskIds.length !== schedulable.length) {
|
|
77351
|
-
this.
|
|
79132
|
+
this.clearGridlockState();
|
|
77352
79133
|
return null;
|
|
77353
79134
|
}
|
|
77354
79135
|
const gridlockKey = blockedTaskIds.join(",");
|
|
@@ -77365,6 +79146,12 @@ var init_gridlock_detector = __esm({
|
|
|
77365
79146
|
}
|
|
77366
79147
|
return event;
|
|
77367
79148
|
}
|
|
79149
|
+
clearGridlockState() {
|
|
79150
|
+
if (this.lastGridlockKey !== null) {
|
|
79151
|
+
this.lastGridlockKey = null;
|
|
79152
|
+
this.onGridlockCleared?.();
|
|
79153
|
+
}
|
|
79154
|
+
}
|
|
77368
79155
|
isMissionBlocked(task) {
|
|
77369
79156
|
if (!this.missionStore || !task.sliceId) return false;
|
|
77370
79157
|
try {
|
|
@@ -77825,10 +79612,10 @@ var init_tunnel_process_manager = __esm({
|
|
|
77825
79612
|
lastError: null
|
|
77826
79613
|
});
|
|
77827
79614
|
this.emitLog("info", "manager", `Stopping ${currentHandle.provider} tunnel (pid=${currentHandle.child.pid ?? "n/a"})`);
|
|
77828
|
-
this.activeStopPromise = new Promise((
|
|
79615
|
+
this.activeStopPromise = new Promise((resolve19) => {
|
|
77829
79616
|
const onClose = () => {
|
|
77830
79617
|
currentHandle.child.removeListener("close", onClose);
|
|
77831
|
-
|
|
79618
|
+
resolve19();
|
|
77832
79619
|
};
|
|
77833
79620
|
currentHandle.child.once("close", onClose);
|
|
77834
79621
|
killManagedProcess(currentHandle.child, "SIGTERM");
|
|
@@ -77979,6 +79766,8 @@ var init_project_engine = __esm({
|
|
|
77979
79766
|
init_merger();
|
|
77980
79767
|
init_concurrency();
|
|
77981
79768
|
init_logger2();
|
|
79769
|
+
init_research_orchestrator();
|
|
79770
|
+
init_research_step_runner();
|
|
77982
79771
|
init_tunnel_process_manager();
|
|
77983
79772
|
execFileAsync = promisify10(execFile3);
|
|
77984
79773
|
MERGE_HANDOFF_GRACE_MS = 300;
|
|
@@ -78009,6 +79798,7 @@ var init_project_engine = __esm({
|
|
|
78009
79798
|
gridlockDetector;
|
|
78010
79799
|
cronRunner;
|
|
78011
79800
|
automationStore;
|
|
79801
|
+
researchOrchestrator;
|
|
78012
79802
|
remoteTunnelManager;
|
|
78013
79803
|
remoteTunnelRestoreDiagnostics = {
|
|
78014
79804
|
outcome: "skipped",
|
|
@@ -78063,6 +79853,14 @@ var init_project_engine = __esm({
|
|
|
78063
79853
|
await this.runtime.start();
|
|
78064
79854
|
const store = this.runtime.getTaskStore();
|
|
78065
79855
|
const cwd = this.config.workingDirectory;
|
|
79856
|
+
const settings = await store.getSettings();
|
|
79857
|
+
if (typeof store.getResearchStore === "function") {
|
|
79858
|
+
this.researchOrchestrator = new ResearchOrchestrator({
|
|
79859
|
+
store: store.getResearchStore(),
|
|
79860
|
+
stepRunner: new ResearchStepRunner(),
|
|
79861
|
+
maxConcurrentRuns: settings.researchMaxConcurrentRuns ?? 3
|
|
79862
|
+
});
|
|
79863
|
+
}
|
|
78066
79864
|
this.remoteTunnelManager = new TunnelProcessManager();
|
|
78067
79865
|
try {
|
|
78068
79866
|
await this.restoreRemoteTunnelIfNeeded(store);
|
|
@@ -78093,7 +79891,8 @@ var init_project_engine = __esm({
|
|
|
78093
79891
|
await this.notifier.start();
|
|
78094
79892
|
}
|
|
78095
79893
|
this.gridlockDetector = new GridlockDetector(store, {
|
|
78096
|
-
onGridlock: (event) => this.notifier?.notifyGridlock(event)
|
|
79894
|
+
onGridlock: (event) => this.notifier?.notifyGridlock(event),
|
|
79895
|
+
onGridlockCleared: () => this.notifier?.notifyGridlock(null)
|
|
78097
79896
|
});
|
|
78098
79897
|
this.gridlockDetector.start();
|
|
78099
79898
|
this.setAutomationSubsystemHealth(
|
|
@@ -78112,11 +79911,11 @@ var init_project_engine = __esm({
|
|
|
78112
79911
|
scope: "project"
|
|
78113
79912
|
// Project-scoped execution — global schedules run separately
|
|
78114
79913
|
});
|
|
78115
|
-
const
|
|
79914
|
+
const settings2 = await store.getSettings();
|
|
78116
79915
|
const startupSyncFailures = [];
|
|
78117
79916
|
if (typeof coreAutomationModule.syncInsightExtractionAutomation === "function") {
|
|
78118
79917
|
try {
|
|
78119
|
-
await coreAutomationModule.syncInsightExtractionAutomation(this.automationStore,
|
|
79918
|
+
await coreAutomationModule.syncInsightExtractionAutomation(this.automationStore, settings2);
|
|
78120
79919
|
} catch (err) {
|
|
78121
79920
|
const { message, detail } = formatErrorDetails(err);
|
|
78122
79921
|
startupSyncFailures.push(`insight extraction: ${message}`);
|
|
@@ -78128,7 +79927,7 @@ ${detail}`);
|
|
|
78128
79927
|
}
|
|
78129
79928
|
if (typeof coreAutomationModule.syncAutoSummarizeAutomation === "function") {
|
|
78130
79929
|
try {
|
|
78131
|
-
await coreAutomationModule.syncAutoSummarizeAutomation(this.automationStore,
|
|
79930
|
+
await coreAutomationModule.syncAutoSummarizeAutomation(this.automationStore, settings2);
|
|
78132
79931
|
} catch (err) {
|
|
78133
79932
|
const { message, detail } = formatErrorDetails(err);
|
|
78134
79933
|
startupSyncFailures.push(`auto-summarize: ${message}`);
|
|
@@ -78140,7 +79939,7 @@ ${detail}`);
|
|
|
78140
79939
|
}
|
|
78141
79940
|
if (typeof coreAutomationModule.syncMemoryDreamsAutomation === "function") {
|
|
78142
79941
|
try {
|
|
78143
|
-
await coreAutomationModule.syncMemoryDreamsAutomation(this.automationStore,
|
|
79942
|
+
await coreAutomationModule.syncMemoryDreamsAutomation(this.automationStore, settings2);
|
|
78144
79943
|
} catch (err) {
|
|
78145
79944
|
const { message, detail } = formatErrorDetails(err);
|
|
78146
79945
|
startupSyncFailures.push(`memory dreams: ${message}`);
|
|
@@ -78305,6 +80104,10 @@ ${detail}`
|
|
|
78305
80104
|
getRoutineStore() {
|
|
78306
80105
|
return this.runtime.getRoutineStore();
|
|
78307
80106
|
}
|
|
80107
|
+
/** Get the ResearchOrchestrator (if initialized). Returns undefined before start(). */
|
|
80108
|
+
getResearchOrchestrator() {
|
|
80109
|
+
return this.researchOrchestrator;
|
|
80110
|
+
}
|
|
78308
80111
|
/** Get the remote tunnel manager (available after start()). */
|
|
78309
80112
|
getRemoteTunnelManager() {
|
|
78310
80113
|
return this.remoteTunnelManager;
|
|
@@ -78395,12 +80198,12 @@ ${detail}`
|
|
|
78395
80198
|
*/
|
|
78396
80199
|
async onMerge(taskId) {
|
|
78397
80200
|
if (this.mergeActive.has(taskId)) {
|
|
78398
|
-
return new Promise((
|
|
78399
|
-
this.manualMergeResolvers.set(taskId, { resolve:
|
|
80201
|
+
return new Promise((resolve19, reject) => {
|
|
80202
|
+
this.manualMergeResolvers.set(taskId, { resolve: resolve19, reject });
|
|
78400
80203
|
});
|
|
78401
80204
|
}
|
|
78402
|
-
return new Promise((
|
|
78403
|
-
this.manualMergeResolvers.set(taskId, { resolve:
|
|
80205
|
+
return new Promise((resolve19, reject) => {
|
|
80206
|
+
this.manualMergeResolvers.set(taskId, { resolve: resolve19, reject });
|
|
78404
80207
|
this.internalEnqueueMerge(taskId);
|
|
78405
80208
|
});
|
|
78406
80209
|
}
|
|
@@ -80138,11 +81941,14 @@ __export(src_exports2, {
|
|
|
80138
81941
|
AgentSemaphore: () => AgentSemaphore,
|
|
80139
81942
|
CronRunner: () => CronRunner,
|
|
80140
81943
|
DEFAULT_NTFY_EVENTS: () => DEFAULT_NTFY_EVENTS,
|
|
81944
|
+
GitHubProvider: () => GitHubProvider,
|
|
80141
81945
|
HEARTBEAT_NO_TASK_SYSTEM_PROMPT: () => HEARTBEAT_NO_TASK_SYSTEM_PROMPT,
|
|
80142
81946
|
HEARTBEAT_PROCEDURE: () => HEARTBEAT_PROCEDURE,
|
|
80143
81947
|
HEARTBEAT_SYSTEM_PROMPT: () => HEARTBEAT_SYSTEM_PROMPT,
|
|
80144
81948
|
HeartbeatMonitor: () => HeartbeatMonitor,
|
|
80145
81949
|
HeartbeatTriggerScheduler: () => HeartbeatTriggerScheduler,
|
|
81950
|
+
LLMSynthesisProvider: () => LLMSynthesisProvider,
|
|
81951
|
+
LocalDocsProvider: () => LocalDocsProvider,
|
|
80146
81952
|
MissionAutopilot: () => MissionAutopilot,
|
|
80147
81953
|
MissionExecutionLoop: () => MissionExecutionLoop,
|
|
80148
81954
|
NodeHealthMonitor: () => NodeHealthMonitor,
|
|
@@ -80152,6 +81958,7 @@ __export(src_exports2, {
|
|
|
80152
81958
|
PRIORITY_EXECUTE: () => PRIORITY_EXECUTE,
|
|
80153
81959
|
PRIORITY_MERGE: () => PRIORITY_MERGE,
|
|
80154
81960
|
PRIORITY_SPECIFY: () => PRIORITY_SPECIFY,
|
|
81961
|
+
PageFetchProvider: () => PageFetchProvider,
|
|
80155
81962
|
PeerExchangeService: () => PeerExchangeService,
|
|
80156
81963
|
PluginRunner: () => PluginRunner,
|
|
80157
81964
|
PrCommentHandler: () => PrCommentHandler,
|
|
@@ -80161,6 +81968,13 @@ __export(src_exports2, {
|
|
|
80161
81968
|
ProjectManager: () => ProjectManager,
|
|
80162
81969
|
RemoteNodeClient: () => RemoteNodeClient,
|
|
80163
81970
|
RemoteNodeRuntime: () => RemoteNodeRuntime,
|
|
81971
|
+
ResearchOrchestrator: () => ResearchOrchestrator,
|
|
81972
|
+
ResearchProviderError: () => ResearchProviderError,
|
|
81973
|
+
ResearchProviderRegistry: () => ResearchProviderRegistry,
|
|
81974
|
+
ResearchStepAbortError: () => ResearchStepAbortError,
|
|
81975
|
+
ResearchStepProviderError: () => ResearchStepProviderError,
|
|
81976
|
+
ResearchStepRunner: () => ResearchStepRunner,
|
|
81977
|
+
ResearchStepTimeoutError: () => ResearchStepTimeoutError,
|
|
80164
81978
|
RoutineRunner: () => RoutineRunner,
|
|
80165
81979
|
RoutineScheduler: () => RoutineScheduler,
|
|
80166
81980
|
Scheduler: () => Scheduler,
|
|
@@ -80172,6 +81986,7 @@ __export(src_exports2, {
|
|
|
80172
81986
|
TriageProcessor: () => TriageProcessor,
|
|
80173
81987
|
TunnelProcessManager: () => TunnelProcessManager,
|
|
80174
81988
|
UsageLimitPauser: () => UsageLimitPauser,
|
|
81989
|
+
WebSearchProvider: () => WebSearchProvider,
|
|
80175
81990
|
WebhookNotificationProvider: () => WebhookNotificationProvider,
|
|
80176
81991
|
WorktreePool: () => WorktreePool,
|
|
80177
81992
|
aiMergeTask: () => aiMergeTask,
|
|
@@ -80244,6 +82059,11 @@ var init_src2 = __esm({
|
|
|
80244
82059
|
init_logger2();
|
|
80245
82060
|
init_usage_limit_detector();
|
|
80246
82061
|
init_rate_limit_retry();
|
|
82062
|
+
init_research_orchestrator();
|
|
82063
|
+
init_research_step_runner();
|
|
82064
|
+
init_provider_registry();
|
|
82065
|
+
init_types2();
|
|
82066
|
+
init_providers();
|
|
80247
82067
|
init_pr_monitor();
|
|
80248
82068
|
init_pr_comment_handler();
|
|
80249
82069
|
init_notifier();
|
|
@@ -82028,7 +83848,7 @@ function buildHermesArgs(prompt, settings, resumeSessionId) {
|
|
|
82028
83848
|
async function invokeHermesCli(prompt, settings, resumeSessionId, signal) {
|
|
82029
83849
|
const args = buildHermesArgs(prompt, settings, resumeSessionId);
|
|
82030
83850
|
const binary = resolveBinaryForSpawn(settings.binaryPath);
|
|
82031
|
-
return new Promise((
|
|
83851
|
+
return new Promise((resolve19, reject) => {
|
|
82032
83852
|
let settled = false;
|
|
82033
83853
|
const spawnEnv = { ...process.env, PYTHONUNBUFFERED: "1" };
|
|
82034
83854
|
if (settings.profile) {
|
|
@@ -82096,7 +83916,7 @@ ${combined}`));
|
|
|
82096
83916
|
return;
|
|
82097
83917
|
}
|
|
82098
83918
|
try {
|
|
82099
|
-
|
|
83919
|
+
resolve19(parseHermesOutput(stdout, stderr));
|
|
82100
83920
|
} catch (parseErr) {
|
|
82101
83921
|
reject(parseErr);
|
|
82102
83922
|
}
|
|
@@ -82322,7 +84142,7 @@ async function promptCli(session, message, config, callbacks, signal) {
|
|
|
82322
84142
|
const args = buildOpenClawArgs(config, session.sessionId, message);
|
|
82323
84143
|
const cb = { ...session.callbacks, ...callbacks };
|
|
82324
84144
|
cb.onToolStart?.("openclaw.agent", { sessionId: session.sessionId });
|
|
82325
|
-
return new Promise((
|
|
84145
|
+
return new Promise((resolve19, reject) => {
|
|
82326
84146
|
let settled = false;
|
|
82327
84147
|
const child = spawn5(config.binaryPath, args, {
|
|
82328
84148
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -82415,7 +84235,7 @@ async function promptCli(session, message, config, callbacks, signal) {
|
|
|
82415
84235
|
...metaError ? { error: metaError } : {},
|
|
82416
84236
|
...errorText.length > 0 ? { toolErrors: errorText } : {}
|
|
82417
84237
|
});
|
|
82418
|
-
|
|
84238
|
+
resolve19();
|
|
82419
84239
|
});
|
|
82420
84240
|
});
|
|
82421
84241
|
}
|
|
@@ -83468,7 +85288,7 @@ var require_galois_field = __commonJS({
|
|
|
83468
85288
|
EXP_TABLE[i] = EXP_TABLE[i - 255];
|
|
83469
85289
|
}
|
|
83470
85290
|
})();
|
|
83471
|
-
exports.log = function
|
|
85291
|
+
exports.log = function log17(n) {
|
|
83472
85292
|
if (n < 1) throw new Error("log(" + n + ")");
|
|
83473
85293
|
return LOG_TABLE[n];
|
|
83474
85294
|
};
|
|
@@ -86690,7 +88510,7 @@ var require_utils2 = __commonJS({
|
|
|
86690
88510
|
var require_png2 = __commonJS({
|
|
86691
88511
|
"../../node_modules/.pnpm/qrcode@1.5.4/node_modules/qrcode/lib/renderer/png.js"(exports) {
|
|
86692
88512
|
"use strict";
|
|
86693
|
-
var
|
|
88513
|
+
var fs2 = __require("fs");
|
|
86694
88514
|
var PNG = require_png().PNG;
|
|
86695
88515
|
var Utils = require_utils2();
|
|
86696
88516
|
exports.render = function render(qrData, options) {
|
|
@@ -86742,7 +88562,7 @@ var require_png2 = __commonJS({
|
|
|
86742
88562
|
called = true;
|
|
86743
88563
|
cb.apply(null, args);
|
|
86744
88564
|
};
|
|
86745
|
-
const stream =
|
|
88565
|
+
const stream = fs2.createWriteStream(path2);
|
|
86746
88566
|
stream.on("error", done);
|
|
86747
88567
|
stream.on("close", done);
|
|
86748
88568
|
exports.renderToFileStream(stream, qrData, options);
|
|
@@ -86810,9 +88630,9 @@ var require_utf8 = __commonJS({
|
|
|
86810
88630
|
cb = options;
|
|
86811
88631
|
options = void 0;
|
|
86812
88632
|
}
|
|
86813
|
-
const
|
|
88633
|
+
const fs2 = __require("fs");
|
|
86814
88634
|
const utf8 = exports.render(qrData, options);
|
|
86815
|
-
|
|
88635
|
+
fs2.writeFile(path2, utf8, cb);
|
|
86816
88636
|
};
|
|
86817
88637
|
}
|
|
86818
88638
|
});
|
|
@@ -86991,10 +88811,10 @@ var require_svg = __commonJS({
|
|
|
86991
88811
|
cb = options;
|
|
86992
88812
|
options = void 0;
|
|
86993
88813
|
}
|
|
86994
|
-
const
|
|
88814
|
+
const fs2 = __require("fs");
|
|
86995
88815
|
const svgTag = exports.render(qrData, options);
|
|
86996
88816
|
const xmlStr = '<?xml version="1.0" encoding="utf-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">' + svgTag;
|
|
86997
|
-
|
|
88817
|
+
fs2.writeFile(path2, xmlStr, cb);
|
|
86998
88818
|
};
|
|
86999
88819
|
}
|
|
87000
88820
|
});
|
|
@@ -87099,10 +88919,10 @@ var require_browser3 = __commonJS({
|
|
|
87099
88919
|
text = canvas;
|
|
87100
88920
|
canvas = void 0;
|
|
87101
88921
|
}
|
|
87102
|
-
return new Promise(function(
|
|
88922
|
+
return new Promise(function(resolve19, reject) {
|
|
87103
88923
|
try {
|
|
87104
88924
|
const data = QRCode2.create(text, opts);
|
|
87105
|
-
|
|
88925
|
+
resolve19(renderFunc(data, canvas, opts));
|
|
87106
88926
|
} catch (e) {
|
|
87107
88927
|
reject(e);
|
|
87108
88928
|
}
|
|
@@ -87184,11 +89004,11 @@ var require_server = __commonJS({
|
|
|
87184
89004
|
}
|
|
87185
89005
|
function render(renderFunc, text, params) {
|
|
87186
89006
|
if (!params.cb) {
|
|
87187
|
-
return new Promise(function(
|
|
89007
|
+
return new Promise(function(resolve19, reject) {
|
|
87188
89008
|
try {
|
|
87189
89009
|
const data = QRCode2.create(text, params.opts);
|
|
87190
89010
|
return renderFunc(data, params.opts, function(err, data2) {
|
|
87191
|
-
return err ? reject(err) :
|
|
89011
|
+
return err ? reject(err) : resolve19(data2);
|
|
87192
89012
|
});
|
|
87193
89013
|
} catch (e) {
|
|
87194
89014
|
reject(e);
|
|
@@ -87297,7 +89117,7 @@ var init_register_messaging_scripts = __esm({
|
|
|
87297
89117
|
|
|
87298
89118
|
// ../dashboard/src/github.ts
|
|
87299
89119
|
function delay(ms) {
|
|
87300
|
-
return new Promise((
|
|
89120
|
+
return new Promise((resolve19) => setTimeout(resolve19, ms));
|
|
87301
89121
|
}
|
|
87302
89122
|
function normalizeCheckState(state) {
|
|
87303
89123
|
switch ((state ?? "").toLowerCase()) {
|
|
@@ -89848,13 +91668,13 @@ function resolvePaperclipConfig(settings) {
|
|
|
89848
91668
|
};
|
|
89849
91669
|
}
|
|
89850
91670
|
async function discoverPaperclipCliConfig(opts = {}) {
|
|
89851
|
-
const
|
|
91671
|
+
const fs2 = await import("node:fs/promises");
|
|
89852
91672
|
const path2 = await import("node:path");
|
|
89853
91673
|
const os3 = await import("node:os");
|
|
89854
91674
|
const configPath = opts.configPath ?? path2.join(os3.homedir(), ".paperclip", "instances", "default", "config.json");
|
|
89855
91675
|
let raw;
|
|
89856
91676
|
try {
|
|
89857
|
-
raw = await
|
|
91677
|
+
raw = await fs2.readFile(configPath, "utf-8");
|
|
89858
91678
|
} catch (error) {
|
|
89859
91679
|
const code = error.code;
|
|
89860
91680
|
return {
|
|
@@ -90017,7 +91837,7 @@ async function spawnPaperclipCliJson(args, opts) {
|
|
|
90017
91837
|
}
|
|
90018
91838
|
const timeoutMs = opts.cliTimeoutMs ?? 15e3;
|
|
90019
91839
|
const label = ["paperclipai", ...args].join(" ");
|
|
90020
|
-
return new Promise((
|
|
91840
|
+
return new Promise((resolve19, reject) => {
|
|
90021
91841
|
let child;
|
|
90022
91842
|
try {
|
|
90023
91843
|
child = spawn10(bin, fullArgs, { stdio: ["ignore", "pipe", "pipe"] });
|
|
@@ -90063,7 +91883,7 @@ async function spawnPaperclipCliJson(args, opts) {
|
|
|
90063
91883
|
return;
|
|
90064
91884
|
}
|
|
90065
91885
|
try {
|
|
90066
|
-
|
|
91886
|
+
resolve19(JSON.parse(cleaned));
|
|
90067
91887
|
} catch {
|
|
90068
91888
|
reject(new Error(`${label} returned non-JSON output: ${cleaned.slice(0, 200)}`));
|
|
90069
91889
|
}
|
|
@@ -90132,8 +91952,8 @@ var init_paperclip_client = __esm({
|
|
|
90132
91952
|
|
|
90133
91953
|
// ../../plugins/fusion-plugin-paperclip-runtime/dist/runtime-adapter.js
|
|
90134
91954
|
import { randomUUID as randomUUID14 } from "node:crypto";
|
|
90135
|
-
function
|
|
90136
|
-
return new Promise((
|
|
91955
|
+
function sleep4(ms) {
|
|
91956
|
+
return new Promise((resolve19) => setTimeout(resolve19, ms));
|
|
90137
91957
|
}
|
|
90138
91958
|
function asString2(value) {
|
|
90139
91959
|
return typeof value === "string" ? value : void 0;
|
|
@@ -90435,7 +92255,7 @@ var init_runtime_adapter3 = __esm({
|
|
|
90435
92255
|
this.logger.warn(`Paperclip run ${runId} exceeded local runTimeoutMs=${session.runTimeoutMs}; abandoning poll. Run continues server-side.`);
|
|
90436
92256
|
break;
|
|
90437
92257
|
}
|
|
90438
|
-
await
|
|
92258
|
+
await sleep4(interval);
|
|
90439
92259
|
interval = Math.min(interval * 2, session.pollIntervalMaxMs);
|
|
90440
92260
|
}
|
|
90441
92261
|
return { text: textBuf, thinking: thinkBuf, runStatus, timedOutLocally };
|
|
@@ -94593,7 +96413,7 @@ var init_auth_middleware = __esm({
|
|
|
94593
96413
|
|
|
94594
96414
|
// ../dashboard/src/server.ts
|
|
94595
96415
|
import express from "express";
|
|
94596
|
-
import { join as
|
|
96416
|
+
import { join as join38, dirname as dirname11 } from "node:path";
|
|
94597
96417
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
94598
96418
|
function clearAiSessionCleanupInterval() {
|
|
94599
96419
|
if (!aiSessionCleanupIntervalHandle) {
|
|
@@ -94701,7 +96521,7 @@ var init_src4 = __esm({
|
|
|
94701
96521
|
});
|
|
94702
96522
|
|
|
94703
96523
|
// src/project-context.ts
|
|
94704
|
-
import { resolve as
|
|
96524
|
+
import { resolve as resolve17, dirname as dirname12, basename as basename9 } from "node:path";
|
|
94705
96525
|
async function resolveProject(projectNameFlag, cwd = process.cwd(), globalDir) {
|
|
94706
96526
|
const central = new CentralCore(globalDir);
|
|
94707
96527
|
await central.init();
|
|
@@ -94777,9 +96597,9 @@ async function clearDefaultProject(globalDir) {
|
|
|
94777
96597
|
await globalStore.updateSettings(rest);
|
|
94778
96598
|
}
|
|
94779
96599
|
async function detectProjectFromCwd(cwd, central) {
|
|
94780
|
-
let currentDir =
|
|
96600
|
+
let currentDir = resolve17(cwd);
|
|
94781
96601
|
while (true) {
|
|
94782
|
-
const kbPath =
|
|
96602
|
+
const kbPath = resolve17(currentDir, ".fusion", "fusion.db");
|
|
94783
96603
|
if (isValidSqliteDatabaseFile(kbPath)) {
|
|
94784
96604
|
const project = await central.getProjectByPath(currentDir);
|
|
94785
96605
|
if (project) {
|
|
@@ -94883,7 +96703,7 @@ __export(task_exports, {
|
|
|
94883
96703
|
});
|
|
94884
96704
|
import { createInterface as createInterface2 } from "node:readline/promises";
|
|
94885
96705
|
import { watchFile, unwatchFile, statSync as statSync6, existsSync as existsSync29, readFileSync as readFileSync8 } from "node:fs";
|
|
94886
|
-
import { basename as basename10, join as
|
|
96706
|
+
import { basename as basename10, join as join39 } from "node:path";
|
|
94887
96707
|
function getGitHubIssueUrl(sourceMetadata) {
|
|
94888
96708
|
if (!sourceMetadata || typeof sourceMetadata !== "object") return void 0;
|
|
94889
96709
|
const issueUrl = sourceMetadata.issueUrl;
|
|
@@ -95047,11 +96867,11 @@ async function runTaskCreate(descriptionArg, attachFiles, depends, projectName,
|
|
|
95047
96867
|
console.log(` Path: .fusion/tasks/${task.id}/`);
|
|
95048
96868
|
if (attachFiles && attachFiles.length > 0) {
|
|
95049
96869
|
const { readFile: readFile19 } = await import("node:fs/promises");
|
|
95050
|
-
const { basename: basename12, extname:
|
|
96870
|
+
const { basename: basename12, extname: extname4, resolve: resolve19 } = await import("node:path");
|
|
95051
96871
|
for (const filePath of attachFiles) {
|
|
95052
|
-
const resolvedPath =
|
|
96872
|
+
const resolvedPath = resolve19(filePath);
|
|
95053
96873
|
const filename = basename12(resolvedPath);
|
|
95054
|
-
const ext =
|
|
96874
|
+
const ext = extname4(filename).toLowerCase();
|
|
95055
96875
|
const mimeType = MIME_TYPES[ext];
|
|
95056
96876
|
if (!mimeType) {
|
|
95057
96877
|
console.error(` \u2717 Unsupported file type: ${ext} (${filename})`);
|
|
@@ -95183,7 +97003,7 @@ async function runTaskLogs(id, options = {}, projectName) {
|
|
|
95183
97003
|
printEntries(filteredEntries);
|
|
95184
97004
|
if (options.follow) {
|
|
95185
97005
|
const projectPath = projectContext?.projectPath ?? process.cwd();
|
|
95186
|
-
const logPath =
|
|
97006
|
+
const logPath = join39(projectPath, ".fusion", "tasks", id, "agent.log");
|
|
95187
97007
|
if (!existsSync29(logPath)) {
|
|
95188
97008
|
console.log(`
|
|
95189
97009
|
Waiting for log file to be created...`);
|
|
@@ -95346,11 +97166,11 @@ async function runTaskMerge(id, projectName) {
|
|
|
95346
97166
|
}
|
|
95347
97167
|
async function runTaskAttach(id, filePath, projectName) {
|
|
95348
97168
|
const { readFile: readFile19 } = await import("node:fs/promises");
|
|
95349
|
-
const { basename: basename12, extname:
|
|
95350
|
-
const { resolve:
|
|
95351
|
-
const resolvedPath =
|
|
97169
|
+
const { basename: basename12, extname: extname4 } = await import("node:path");
|
|
97170
|
+
const { resolve: resolve19 } = await import("node:path");
|
|
97171
|
+
const resolvedPath = resolve19(filePath);
|
|
95352
97172
|
const filename = basename12(resolvedPath);
|
|
95353
|
-
const ext =
|
|
97173
|
+
const ext = extname4(filename).toLowerCase();
|
|
95354
97174
|
const mimeType = MIME_TYPES[ext];
|
|
95355
97175
|
if (!mimeType) {
|
|
95356
97176
|
console.error(`Unsupported file type: ${ext}`);
|
|
@@ -95887,12 +97707,12 @@ async function promptText(question) {
|
|
|
95887
97707
|
console.log(" (Enter your response. Type DONE on its own line when finished):\n");
|
|
95888
97708
|
const rl = createInterface2({ input: process.stdin, output: process.stdout });
|
|
95889
97709
|
const lines = [];
|
|
95890
|
-
return new Promise((
|
|
97710
|
+
return new Promise((resolve19) => {
|
|
95891
97711
|
const askLine = () => {
|
|
95892
97712
|
rl.question(" ").then((line) => {
|
|
95893
97713
|
if (line.trim() === "DONE") {
|
|
95894
97714
|
rl.close();
|
|
95895
|
-
|
|
97715
|
+
resolve19(lines.join("\n"));
|
|
95896
97716
|
} else {
|
|
95897
97717
|
lines.push(line);
|
|
95898
97718
|
askLine();
|
|
@@ -96297,9 +98117,9 @@ async function runSkillsInstall(args, options) {
|
|
|
96297
98117
|
stdio: "inherit",
|
|
96298
98118
|
shell: true
|
|
96299
98119
|
});
|
|
96300
|
-
const exitCode = await new Promise((
|
|
98120
|
+
const exitCode = await new Promise((resolve19, reject) => {
|
|
96301
98121
|
child.on("exit", (code) => {
|
|
96302
|
-
|
|
98122
|
+
resolve19(code ?? 1);
|
|
96303
98123
|
});
|
|
96304
98124
|
child.on("error", (err) => {
|
|
96305
98125
|
reject(err);
|
|
@@ -96326,7 +98146,7 @@ init_src();
|
|
|
96326
98146
|
init_gh_cli();
|
|
96327
98147
|
import { Type as Type7 } from "typebox";
|
|
96328
98148
|
import { StringEnum } from "@mariozechner/pi-ai";
|
|
96329
|
-
import { resolve as
|
|
98149
|
+
import { resolve as resolve18, basename as basename11, extname as extname3, join as join40 } from "node:path";
|
|
96330
98150
|
import { readFile as readFile18 } from "node:fs/promises";
|
|
96331
98151
|
import { existsSync as existsSync30 } from "node:fs";
|
|
96332
98152
|
import { spawn as spawn9 } from "node:child_process";
|
|
@@ -96346,14 +98166,14 @@ var MIME_TYPES2 = {
|
|
|
96346
98166
|
".xml": "application/xml"
|
|
96347
98167
|
};
|
|
96348
98168
|
function resolveProjectRoot(cwd) {
|
|
96349
|
-
let current =
|
|
98169
|
+
let current = resolve18(cwd);
|
|
96350
98170
|
while (true) {
|
|
96351
|
-
if (existsSync30(
|
|
98171
|
+
if (existsSync30(join40(current, ".fusion"))) {
|
|
96352
98172
|
return current;
|
|
96353
98173
|
}
|
|
96354
|
-
const parent =
|
|
98174
|
+
const parent = resolve18(current, "..");
|
|
96355
98175
|
if (parent === current) {
|
|
96356
|
-
return
|
|
98176
|
+
return resolve18(cwd);
|
|
96357
98177
|
}
|
|
96358
98178
|
current = parent;
|
|
96359
98179
|
}
|
|
@@ -96369,7 +98189,7 @@ async function getStore2(cwd) {
|
|
|
96369
98189
|
return store;
|
|
96370
98190
|
}
|
|
96371
98191
|
function getFusionDir(cwd) {
|
|
96372
|
-
return
|
|
98192
|
+
return join40(resolveProjectRoot(cwd), ".fusion");
|
|
96373
98193
|
}
|
|
96374
98194
|
async function validateAssignableAgentId(cwd, agentId) {
|
|
96375
98195
|
const { AgentStore: AgentStore2, isEphemeralAgent: isEphemeralAgent2 } = await Promise.resolve().then(() => (init_src(), src_exports));
|
|
@@ -96698,9 +98518,9 @@ Column: triage
|
|
|
96698
98518
|
path: Type7.String({ description: "Path to the file to attach" })
|
|
96699
98519
|
}),
|
|
96700
98520
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
96701
|
-
const filePath =
|
|
98521
|
+
const filePath = resolve18(ctx.cwd, params.path.replace(/^@/, ""));
|
|
96702
98522
|
const filename = basename11(filePath);
|
|
96703
|
-
const ext =
|
|
98523
|
+
const ext = extname3(filename).toLowerCase();
|
|
96704
98524
|
const mimeType = MIME_TYPES2[ext];
|
|
96705
98525
|
if (!mimeType) {
|
|
96706
98526
|
throw new Error(
|
|
@@ -97815,12 +99635,12 @@ Status: ${updated.status}`
|
|
|
97815
99635
|
child.stderr?.on("data", (data) => {
|
|
97816
99636
|
stderr += data.toString();
|
|
97817
99637
|
});
|
|
97818
|
-
const exitCode = await new Promise((
|
|
99638
|
+
const exitCode = await new Promise((resolve19) => {
|
|
97819
99639
|
child.on("exit", (code) => {
|
|
97820
|
-
|
|
99640
|
+
resolve19(code ?? 1);
|
|
97821
99641
|
});
|
|
97822
99642
|
child.on("error", () => {
|
|
97823
|
-
|
|
99643
|
+
resolve19(1);
|
|
97824
99644
|
});
|
|
97825
99645
|
});
|
|
97826
99646
|
try {
|