@runfusion/fusion 0.8.0 → 0.8.2
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 +962 -566
- package/dist/client/assets/{AgentDetailView-C3Xcrxnp.js → AgentDetailView-B6wfIQ9j.js} +1 -1
- package/dist/client/assets/{AgentsView-EjE4y4rM.js → AgentsView-Fcym0XCw.js} +3 -3
- package/dist/client/assets/{ChatView-DQLvKCYj.js → ChatView-DEckS3f3.js} +1 -1
- package/dist/client/assets/{DevServerView-CX7paFRQ.js → DevServerView-CmMS4D6V.js} +1 -1
- package/dist/client/assets/{DirectoryPicker-_cBPx6Nx.js → DirectoryPicker-CQtE-YyA.js} +1 -1
- package/dist/client/assets/{DocumentsView-Wz33aYqp.js → DocumentsView-C4HDkN_0.js} +1 -1
- package/dist/client/assets/{InsightsView-C7YPnS92.js → InsightsView-oNQ7h5e8.js} +1 -1
- package/dist/client/assets/{MemoryView-DKQtFzFQ.js → MemoryView-DPYGW09y.js} +1 -1
- package/dist/client/assets/{NodesView-CI4rUQC4.js → NodesView-DsM-c8RF.js} +1 -1
- package/dist/client/assets/{PiExtensionsManager-BFmdKgHZ.js → PiExtensionsManager-DHgMjjRE.js} +1 -1
- package/dist/client/assets/{PluginManager-BGQU1IIw.js → PluginManager-N-7Sw_tE.js} +1 -1
- package/dist/client/assets/{RoadmapsView-Cts3hoIS.js → RoadmapsView-BgX79uYM.js} +1 -1
- package/dist/client/assets/{SettingsModal-DXvBGZHf.js → SettingsModal-BIKEMPwb.js} +1 -1
- package/dist/client/assets/{SettingsModal-DvRd0ZOE.js → SettingsModal-C4EwtN6K.js} +4 -4
- package/dist/client/assets/SetupWizardModal-D2m0i9Io.js +1 -0
- package/dist/client/assets/{SkillsView-BXvrHzEZ.js → SkillsView-Eb1Mngnt.js} +1 -1
- package/dist/client/assets/{TodoView-NZHkv9YQ.js → TodoView-BN8FQYyp.js} +1 -1
- package/dist/client/assets/{folder-open-Kh0ScTc5.js → folder-open-BqZBHfoZ.js} +1 -1
- package/dist/client/assets/{index-CWz44REw.css → index-D2fXOwWF.css} +1 -1
- package/dist/client/assets/{index-D1gavMG-.js → index-DlHPhpDu.js} +3 -3
- package/dist/client/assets/{list-checks-CvoT0bwU.js → list-checks-D2EURsP0.js} +1 -1
- package/dist/client/assets/{star-BdfwSLBU.js → star-CNQlAD8p.js} +1 -1
- package/dist/client/assets/{upload-Bx8Yk_7Q.js → upload-uoxlYkig.js} +1 -1
- package/dist/client/assets/{users-DgVaFEsz.js → users-BLvidusm.js} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/client/version.json +1 -1
- package/dist/extension.js +500 -196
- package/dist/pi-claude-cli/package.json +1 -1
- package/dist/pi-claude-cli/src/process-manager.ts +1 -1
- package/package.json +1 -1
- package/dist/client/assets/SetupWizardModal-Y2ewEE8Y.js +0 -1
- package/dist/pi-claude-cli/src/types/cross-spawn.d.ts +0 -7
package/dist/extension.js
CHANGED
|
@@ -2432,7 +2432,7 @@ var init_db = __esm({
|
|
|
2432
2432
|
"use strict";
|
|
2433
2433
|
init_sqlite_adapter();
|
|
2434
2434
|
init_types();
|
|
2435
|
-
SCHEMA_VERSION =
|
|
2435
|
+
SCHEMA_VERSION = 51;
|
|
2436
2436
|
SCHEMA_SQL = `
|
|
2437
2437
|
-- Tasks table with JSON columns for nested data
|
|
2438
2438
|
CREATE TABLE IF NOT EXISTS tasks (
|
|
@@ -3965,6 +3965,13 @@ CREATE INDEX IF NOT EXISTS idxTodoItemsSortOrder ON todo_items(listId, sortOrder
|
|
|
3965
3965
|
this.addColumnIfMissing("tasks", "effectiveNodeSource", "TEXT");
|
|
3966
3966
|
});
|
|
3967
3967
|
}
|
|
3968
|
+
if (version < 51) {
|
|
3969
|
+
this.applyMigration(51, () => {
|
|
3970
|
+
if (this.hasTable("chat_messages")) {
|
|
3971
|
+
this.addColumnIfMissing("chat_messages", "attachments", "TEXT");
|
|
3972
|
+
}
|
|
3973
|
+
});
|
|
3974
|
+
}
|
|
3968
3975
|
}
|
|
3969
3976
|
/**
|
|
3970
3977
|
* Run a single migration step inside a transaction and bump the version.
|
|
@@ -6477,9 +6484,9 @@ var init_global_settings = __esm({
|
|
|
6477
6484
|
* Serialize operations via promise chain to prevent lost-update races.
|
|
6478
6485
|
*/
|
|
6479
6486
|
withLock(fn) {
|
|
6480
|
-
let
|
|
6487
|
+
let resolve19;
|
|
6481
6488
|
const next = new Promise((r) => {
|
|
6482
|
-
|
|
6489
|
+
resolve19 = r;
|
|
6483
6490
|
});
|
|
6484
6491
|
const prev = this.lock;
|
|
6485
6492
|
this.lock = next;
|
|
@@ -6487,7 +6494,7 @@ var init_global_settings = __esm({
|
|
|
6487
6494
|
try {
|
|
6488
6495
|
return await fn();
|
|
6489
6496
|
} finally {
|
|
6490
|
-
|
|
6497
|
+
resolve19();
|
|
6491
6498
|
}
|
|
6492
6499
|
});
|
|
6493
6500
|
}
|
|
@@ -11797,8 +11804,8 @@ import { join as join8, dirname as dirname2 } from "node:path";
|
|
|
11797
11804
|
import { fileURLToPath } from "node:url";
|
|
11798
11805
|
function getAppVersion() {
|
|
11799
11806
|
if (cachedVersion !== null) return cachedVersion;
|
|
11800
|
-
const
|
|
11801
|
-
let currentDir =
|
|
11807
|
+
const __dirname2 = dirname2(fileURLToPath(import.meta.url));
|
|
11808
|
+
let currentDir = __dirname2;
|
|
11802
11809
|
for (let i = 0; i < 10; i++) {
|
|
11803
11810
|
try {
|
|
11804
11811
|
const pkgPath = join8(currentDir, "package.json");
|
|
@@ -27783,9 +27790,9 @@ var init_automation_store = __esm({
|
|
|
27783
27790
|
*/
|
|
27784
27791
|
withScheduleLock(id, fn) {
|
|
27785
27792
|
const prev = this.scheduleLocks.get(id) ?? Promise.resolve();
|
|
27786
|
-
let
|
|
27793
|
+
let resolve19;
|
|
27787
27794
|
const next = new Promise((r) => {
|
|
27788
|
-
|
|
27795
|
+
resolve19 = r;
|
|
27789
27796
|
});
|
|
27790
27797
|
this.scheduleLocks.set(id, next);
|
|
27791
27798
|
return prev.then(async () => {
|
|
@@ -27795,7 +27802,7 @@ var init_automation_store = __esm({
|
|
|
27795
27802
|
if (this.scheduleLocks.get(id) === next) {
|
|
27796
27803
|
this.scheduleLocks.delete(id);
|
|
27797
27804
|
}
|
|
27798
|
-
|
|
27805
|
+
resolve19();
|
|
27799
27806
|
}
|
|
27800
27807
|
});
|
|
27801
27808
|
}
|
|
@@ -29583,7 +29590,7 @@ var init_project_memory = __esm({
|
|
|
29583
29590
|
// ../core/src/run-command.ts
|
|
29584
29591
|
import { spawn } from "node:child_process";
|
|
29585
29592
|
function runCommandAsync(command, options = {}) {
|
|
29586
|
-
return new Promise((
|
|
29593
|
+
return new Promise((resolve19) => {
|
|
29587
29594
|
const maxBuffer = options.maxBuffer ?? DEFAULT_MAX_BUFFER;
|
|
29588
29595
|
let stdout = "";
|
|
29589
29596
|
let stderr = "";
|
|
@@ -29642,7 +29649,7 @@ function runCommandAsync(command, options = {}) {
|
|
|
29642
29649
|
clearTimeout(forceKillTimer);
|
|
29643
29650
|
forceKillTimer = null;
|
|
29644
29651
|
}
|
|
29645
|
-
|
|
29652
|
+
resolve19({
|
|
29646
29653
|
stdout,
|
|
29647
29654
|
stderr,
|
|
29648
29655
|
exitCode: null,
|
|
@@ -29660,7 +29667,7 @@ function runCommandAsync(command, options = {}) {
|
|
|
29660
29667
|
}
|
|
29661
29668
|
signalProcessGroup("SIGTERM");
|
|
29662
29669
|
scheduleForceKill(NORMAL_CLEANUP_FORCE_KILL_DELAY_MS);
|
|
29663
|
-
|
|
29670
|
+
resolve19({
|
|
29664
29671
|
stdout,
|
|
29665
29672
|
stderr,
|
|
29666
29673
|
exitCode: code,
|
|
@@ -30810,9 +30817,9 @@ ${outcome}`;
|
|
|
30810
30817
|
* lost-update races on the nextId counter.
|
|
30811
30818
|
*/
|
|
30812
30819
|
withConfigLock(fn) {
|
|
30813
|
-
let
|
|
30820
|
+
let resolve19;
|
|
30814
30821
|
const next = new Promise((r) => {
|
|
30815
|
-
|
|
30822
|
+
resolve19 = r;
|
|
30816
30823
|
});
|
|
30817
30824
|
const prev = this.configLock;
|
|
30818
30825
|
this.configLock = next;
|
|
@@ -30820,7 +30827,7 @@ ${outcome}`;
|
|
|
30820
30827
|
try {
|
|
30821
30828
|
return await fn();
|
|
30822
30829
|
} finally {
|
|
30823
|
-
|
|
30830
|
+
resolve19();
|
|
30824
30831
|
}
|
|
30825
30832
|
});
|
|
30826
30833
|
}
|
|
@@ -30830,9 +30837,9 @@ ${outcome}`;
|
|
|
30830
30837
|
*/
|
|
30831
30838
|
withTaskLock(id, fn) {
|
|
30832
30839
|
const prev = this.taskLocks.get(id) ?? Promise.resolve();
|
|
30833
|
-
let
|
|
30840
|
+
let resolve19;
|
|
30834
30841
|
const next = new Promise((r) => {
|
|
30835
|
-
|
|
30842
|
+
resolve19 = r;
|
|
30836
30843
|
});
|
|
30837
30844
|
this.taskLocks.set(id, next);
|
|
30838
30845
|
return prev.then(async () => {
|
|
@@ -30842,7 +30849,7 @@ ${outcome}`;
|
|
|
30842
30849
|
if (this.taskLocks.get(id) === next) {
|
|
30843
30850
|
this.taskLocks.delete(id);
|
|
30844
30851
|
}
|
|
30845
|
-
|
|
30852
|
+
resolve19();
|
|
30846
30853
|
}
|
|
30847
30854
|
});
|
|
30848
30855
|
}
|
|
@@ -32997,7 +33004,7 @@ ${task.description}
|
|
|
32997
33004
|
}
|
|
32998
33005
|
}
|
|
32999
33006
|
}
|
|
33000
|
-
await new Promise((
|
|
33007
|
+
await new Promise((resolve19) => setImmediate(resolve19));
|
|
33001
33008
|
const selectClause = this.getTaskSelectClause(true);
|
|
33002
33009
|
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();
|
|
33003
33010
|
this.lastPollTime = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -33017,7 +33024,7 @@ ${task.description}
|
|
|
33017
33024
|
this.emit("task:updated", task);
|
|
33018
33025
|
}
|
|
33019
33026
|
if (i > 0 && i % 50 === 0) {
|
|
33020
|
-
await new Promise((
|
|
33027
|
+
await new Promise((resolve19) => setImmediate(resolve19));
|
|
33021
33028
|
}
|
|
33022
33029
|
}
|
|
33023
33030
|
const elapsed = Date.now() - startTime;
|
|
@@ -34843,7 +34850,7 @@ function runGh(args, cwd) {
|
|
|
34843
34850
|
}
|
|
34844
34851
|
function runGhAsync(args, cwdOrOptions) {
|
|
34845
34852
|
const { cwd, signal: externalSignal, timeoutMs = DEFAULT_GH_TIMEOUT_MS } = normalizeRunGhOptions(cwdOrOptions);
|
|
34846
|
-
return new Promise((
|
|
34853
|
+
return new Promise((resolve19, reject) => {
|
|
34847
34854
|
if (externalSignal?.aborted) {
|
|
34848
34855
|
reject(makeGhError(`gh command aborted: ${describeAbortReason(externalSignal.reason)}`, "ABORT_ERR"));
|
|
34849
34856
|
return;
|
|
@@ -34894,7 +34901,7 @@ function runGhAsync(args, cwdOrOptions) {
|
|
|
34894
34901
|
ghError.stderr = stderr ?? "";
|
|
34895
34902
|
reject(ghError);
|
|
34896
34903
|
} else {
|
|
34897
|
-
|
|
34904
|
+
resolve19(stdout ?? "");
|
|
34898
34905
|
}
|
|
34899
34906
|
}
|
|
34900
34907
|
);
|
|
@@ -35182,9 +35189,9 @@ var init_routine_store = __esm({
|
|
|
35182
35189
|
*/
|
|
35183
35190
|
withRoutineLock(id, fn) {
|
|
35184
35191
|
const prev = this.routineLocks.get(id) ?? Promise.resolve();
|
|
35185
|
-
let
|
|
35192
|
+
let resolve19;
|
|
35186
35193
|
const next = new Promise((r) => {
|
|
35187
|
-
|
|
35194
|
+
resolve19 = r;
|
|
35188
35195
|
});
|
|
35189
35196
|
this.routineLocks.set(id, next);
|
|
35190
35197
|
return prev.then(async () => {
|
|
@@ -35194,7 +35201,7 @@ var init_routine_store = __esm({
|
|
|
35194
35201
|
if (this.routineLocks.get(id) === next) {
|
|
35195
35202
|
this.routineLocks.delete(id);
|
|
35196
35203
|
}
|
|
35197
|
-
|
|
35204
|
+
resolve19();
|
|
35198
35205
|
}
|
|
35199
35206
|
});
|
|
35200
35207
|
}
|
|
@@ -35793,13 +35800,13 @@ var init_plugin_loader = __esm({
|
|
|
35793
35800
|
* Execute a promise with a timeout.
|
|
35794
35801
|
*/
|
|
35795
35802
|
withTimeout(promise, ms, timeoutMessage) {
|
|
35796
|
-
return new Promise((
|
|
35803
|
+
return new Promise((resolve19, reject) => {
|
|
35797
35804
|
const timer = setTimeout(() => {
|
|
35798
35805
|
reject(new Error(timeoutMessage));
|
|
35799
35806
|
}, ms);
|
|
35800
35807
|
promise.then((result) => {
|
|
35801
35808
|
clearTimeout(timer);
|
|
35802
|
-
|
|
35809
|
+
resolve19(result);
|
|
35803
35810
|
}).catch((err) => {
|
|
35804
35811
|
clearTimeout(timer);
|
|
35805
35812
|
reject(err);
|
|
@@ -39006,7 +39013,7 @@ var require_get_stream = __commonJS({
|
|
|
39006
39013
|
};
|
|
39007
39014
|
const { maxBuffer } = options;
|
|
39008
39015
|
let stream;
|
|
39009
|
-
await new Promise((
|
|
39016
|
+
await new Promise((resolve19, reject) => {
|
|
39010
39017
|
const rejectPromise = (error) => {
|
|
39011
39018
|
if (error && stream.getBufferedLength() <= BufferConstants.MAX_LENGTH) {
|
|
39012
39019
|
error.bufferedData = stream.getBufferedValue();
|
|
@@ -39018,7 +39025,7 @@ var require_get_stream = __commonJS({
|
|
|
39018
39025
|
rejectPromise(error);
|
|
39019
39026
|
return;
|
|
39020
39027
|
}
|
|
39021
|
-
|
|
39028
|
+
resolve19();
|
|
39022
39029
|
});
|
|
39023
39030
|
stream.on("data", () => {
|
|
39024
39031
|
if (stream.getBufferedLength() > maxBuffer) {
|
|
@@ -40312,7 +40319,7 @@ var require_extract_zip = __commonJS({
|
|
|
40312
40319
|
debug("opening", this.zipPath, "with opts", this.opts);
|
|
40313
40320
|
this.zipfile = await openZip(this.zipPath, { lazyEntries: true });
|
|
40314
40321
|
this.canceled = false;
|
|
40315
|
-
return new Promise((
|
|
40322
|
+
return new Promise((resolve19, reject) => {
|
|
40316
40323
|
this.zipfile.on("error", (err) => {
|
|
40317
40324
|
this.canceled = true;
|
|
40318
40325
|
reject(err);
|
|
@@ -40321,7 +40328,7 @@ var require_extract_zip = __commonJS({
|
|
|
40321
40328
|
this.zipfile.on("close", () => {
|
|
40322
40329
|
if (!this.canceled) {
|
|
40323
40330
|
debug("zip extraction complete");
|
|
40324
|
-
|
|
40331
|
+
resolve19();
|
|
40325
40332
|
}
|
|
40326
40333
|
});
|
|
40327
40334
|
this.zipfile.on("entry", async (entry) => {
|
|
@@ -48501,6 +48508,7 @@ var init_chat_store = __esm({
|
|
|
48501
48508
|
content: row.content,
|
|
48502
48509
|
thinkingOutput: row.thinkingOutput ?? null,
|
|
48503
48510
|
metadata: fromJson(row.metadata) ?? null,
|
|
48511
|
+
attachments: fromJson(row.attachments) ?? void 0,
|
|
48504
48512
|
createdAt: row.createdAt
|
|
48505
48513
|
};
|
|
48506
48514
|
}
|
|
@@ -48719,11 +48727,12 @@ var init_chat_store = __esm({
|
|
|
48719
48727
|
content: input.content,
|
|
48720
48728
|
thinkingOutput: input.thinkingOutput ?? null,
|
|
48721
48729
|
metadata: input.metadata ?? null,
|
|
48730
|
+
attachments: input.attachments,
|
|
48722
48731
|
createdAt: now
|
|
48723
48732
|
};
|
|
48724
48733
|
this.db.prepare(`
|
|
48725
|
-
INSERT INTO chat_messages (id, sessionId, role, content, thinkingOutput, metadata, createdAt)
|
|
48726
|
-
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
48734
|
+
INSERT INTO chat_messages (id, sessionId, role, content, thinkingOutput, metadata, attachments, createdAt)
|
|
48735
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
48727
48736
|
`).run(
|
|
48728
48737
|
message.id,
|
|
48729
48738
|
message.sessionId,
|
|
@@ -48731,6 +48740,7 @@ var init_chat_store = __esm({
|
|
|
48731
48740
|
message.content,
|
|
48732
48741
|
message.thinkingOutput,
|
|
48733
48742
|
toJsonNullable(message.metadata),
|
|
48743
|
+
toJsonNullable(message.attachments),
|
|
48734
48744
|
message.createdAt
|
|
48735
48745
|
);
|
|
48736
48746
|
this.db.prepare("UPDATE chat_sessions SET updatedAt = ? WHERE id = ?").run(now, sessionId);
|
|
@@ -48738,6 +48748,28 @@ var init_chat_store = __esm({
|
|
|
48738
48748
|
this.emit("chat:message:added", message);
|
|
48739
48749
|
return message;
|
|
48740
48750
|
}
|
|
48751
|
+
/**
|
|
48752
|
+
* Append a file attachment metadata record to an existing message.
|
|
48753
|
+
*/
|
|
48754
|
+
addMessageAttachment(sessionId, messageId, attachment) {
|
|
48755
|
+
const message = this.getMessage(messageId);
|
|
48756
|
+
if (!message || message.sessionId !== sessionId) {
|
|
48757
|
+
throw new Error(`Message ${messageId} not found in session ${sessionId}`);
|
|
48758
|
+
}
|
|
48759
|
+
const updatedAttachments = [...message.attachments ?? [], attachment];
|
|
48760
|
+
this.db.prepare(`
|
|
48761
|
+
UPDATE chat_messages
|
|
48762
|
+
SET attachments = ?
|
|
48763
|
+
WHERE id = ?
|
|
48764
|
+
`).run(toJsonNullable(updatedAttachments), messageId);
|
|
48765
|
+
const updated = this.getMessage(messageId);
|
|
48766
|
+
if (!updated) {
|
|
48767
|
+
throw new Error(`Failed to update message ${messageId}`);
|
|
48768
|
+
}
|
|
48769
|
+
this.db.bumpLastModified();
|
|
48770
|
+
this.emit("chat:message:updated", updated);
|
|
48771
|
+
return updated;
|
|
48772
|
+
}
|
|
48741
48773
|
/**
|
|
48742
48774
|
* Get messages for a chat session with optional filtering.
|
|
48743
48775
|
*
|
|
@@ -50345,12 +50377,12 @@ var init_concurrency = __esm({
|
|
|
50345
50377
|
this._active++;
|
|
50346
50378
|
return Promise.resolve();
|
|
50347
50379
|
}
|
|
50348
|
-
return new Promise((
|
|
50380
|
+
return new Promise((resolve19) => {
|
|
50349
50381
|
this._waiters.push({
|
|
50350
50382
|
priority,
|
|
50351
50383
|
resolve: () => {
|
|
50352
50384
|
this._active++;
|
|
50353
|
-
|
|
50385
|
+
resolve19();
|
|
50354
50386
|
}
|
|
50355
50387
|
});
|
|
50356
50388
|
});
|
|
@@ -52771,20 +52803,20 @@ async function withRateLimitRetry(fn, options = {}) {
|
|
|
52771
52803
|
throw lastError ?? new Error("withRateLimitRetry: unexpected state");
|
|
52772
52804
|
}
|
|
52773
52805
|
function sleep(ms, signal) {
|
|
52774
|
-
return new Promise((
|
|
52806
|
+
return new Promise((resolve19, reject) => {
|
|
52775
52807
|
if (signal?.aborted) {
|
|
52776
52808
|
reject(signal.reason ?? new Error("Aborted"));
|
|
52777
52809
|
return;
|
|
52778
52810
|
}
|
|
52779
|
-
const timer = setTimeout(
|
|
52811
|
+
const timer = setTimeout(resolve19, ms);
|
|
52780
52812
|
if (signal) {
|
|
52781
52813
|
const onAbort = () => {
|
|
52782
52814
|
clearTimeout(timer);
|
|
52783
52815
|
reject(signal.reason ?? new Error("Aborted"));
|
|
52784
52816
|
};
|
|
52785
52817
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
52786
|
-
const origResolve =
|
|
52787
|
-
|
|
52818
|
+
const origResolve = resolve19;
|
|
52819
|
+
resolve19 = () => {
|
|
52788
52820
|
signal.removeEventListener("abort", onAbort);
|
|
52789
52821
|
origResolve();
|
|
52790
52822
|
};
|
|
@@ -52864,9 +52896,9 @@ async function readAttachmentContents(rootDir, taskId, attachments) {
|
|
|
52864
52896
|
return { attachmentContents, imageContents };
|
|
52865
52897
|
}
|
|
52866
52898
|
const { readFile: readFile19 } = await import("node:fs/promises");
|
|
52867
|
-
const { join:
|
|
52899
|
+
const { join: join38 } = await import("node:path");
|
|
52868
52900
|
for (const att of attachments) {
|
|
52869
|
-
const filePath =
|
|
52901
|
+
const filePath = join38(
|
|
52870
52902
|
rootDir,
|
|
52871
52903
|
".fusion",
|
|
52872
52904
|
"tasks",
|
|
@@ -54380,9 +54412,9 @@ Remove or replace these ids and call fn_task_create again.`
|
|
|
54380
54412
|
}
|
|
54381
54413
|
try {
|
|
54382
54414
|
const { readFile: readFile19 } = await import("node:fs/promises");
|
|
54383
|
-
const { join:
|
|
54415
|
+
const { join: join38 } = await import("node:path");
|
|
54384
54416
|
const promptContent = await readFile19(
|
|
54385
|
-
|
|
54417
|
+
join38(rootDir, promptPath),
|
|
54386
54418
|
"utf-8"
|
|
54387
54419
|
).catch((err) => {
|
|
54388
54420
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -54751,7 +54783,7 @@ import { existsSync as existsSync21 } from "node:fs";
|
|
|
54751
54783
|
import { join as join26 } from "node:path";
|
|
54752
54784
|
import { Type as Type3 } from "typebox";
|
|
54753
54785
|
async function execWithProcessGroup(command, options) {
|
|
54754
|
-
return new Promise((
|
|
54786
|
+
return new Promise((resolve19, reject) => {
|
|
54755
54787
|
if (options.signal?.aborted) {
|
|
54756
54788
|
reject(Object.assign(
|
|
54757
54789
|
new Error(`Command aborted before start: ${command}`),
|
|
@@ -54844,7 +54876,7 @@ async function execWithProcessGroup(command, options) {
|
|
|
54844
54876
|
return;
|
|
54845
54877
|
}
|
|
54846
54878
|
if (code === 0) {
|
|
54847
|
-
|
|
54879
|
+
resolve19({ stdout, stderr, bufferOverflow: stdoutOverflow || stderrOverflow });
|
|
54848
54880
|
return;
|
|
54849
54881
|
}
|
|
54850
54882
|
reject(Object.assign(
|
|
@@ -55407,21 +55439,31 @@ ${failureContext.output.slice(0, VERIFICATION_LOG_MAX_CHARS)}
|
|
|
55407
55439
|
return false;
|
|
55408
55440
|
}
|
|
55409
55441
|
}
|
|
55410
|
-
|
|
55442
|
+
function resetMergeWithWarn(rootDir, taskId, label) {
|
|
55443
|
+
try {
|
|
55444
|
+
execSync("git reset --merge", { cwd: rootDir, stdio: "pipe" });
|
|
55445
|
+
} catch (err) {
|
|
55446
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
55447
|
+
mergerLog.warn(`${taskId}: git reset --merge cleanup failed during ${label}: ${msg}`);
|
|
55448
|
+
}
|
|
55449
|
+
}
|
|
55450
|
+
function buildDeterministicMergeMessage(params) {
|
|
55451
|
+
const { taskId, branch, commitLog, includeTaskId } = params;
|
|
55452
|
+
const prefix = includeTaskId ? `feat(${taskId})` : "feat";
|
|
55453
|
+
const subject = `${prefix}: merge ${branch}`;
|
|
55454
|
+
const body = commitLog && commitLog.trim().length > 0 ? commitLog.trim() : `- merge ${branch}`;
|
|
55455
|
+
const escape = (s) => s.replace(/(["\\$`])/g, "\\$1");
|
|
55456
|
+
return {
|
|
55457
|
+
subjectArg: `-m "${escape(subject)}"`,
|
|
55458
|
+
bodyArg: `-m "${escape(body)}"`
|
|
55459
|
+
};
|
|
55460
|
+
}
|
|
55461
|
+
async function commitOrAmendMergeWithFixes(rootDir, taskId, branch, commitLog, includeTaskId, preAttemptHeadSha, authorArg) {
|
|
55411
55462
|
try {
|
|
55412
|
-
const { stdout: stagedFiles } = await execAsync2("git diff --cached --name-only", {
|
|
55413
|
-
cwd: rootDir,
|
|
55414
|
-
encoding: "utf-8"
|
|
55415
|
-
});
|
|
55416
55463
|
const { stdout: unstagedFiles } = await execAsync2("git diff --name-only", {
|
|
55417
55464
|
cwd: rootDir,
|
|
55418
55465
|
encoding: "utf-8"
|
|
55419
55466
|
});
|
|
55420
|
-
const hasChanges = stagedFiles.trim().length > 0 || unstagedFiles.trim().length > 0;
|
|
55421
|
-
if (!hasChanges) {
|
|
55422
|
-
mergerLog.log(`${taskId}: no changes to amend after verification fix`);
|
|
55423
|
-
return false;
|
|
55424
|
-
}
|
|
55425
55467
|
if (unstagedFiles.trim().length > 0) {
|
|
55426
55468
|
await execAsync2("git add -A", { cwd: rootDir });
|
|
55427
55469
|
}
|
|
@@ -55429,12 +55471,10 @@ async function amendMergeCommitWithFixes(rootDir, taskId, authorArg) {
|
|
|
55429
55471
|
cwd: rootDir,
|
|
55430
55472
|
encoding: "utf-8"
|
|
55431
55473
|
});
|
|
55432
|
-
const gitlinkPaths = [];
|
|
55433
55474
|
for (const line of staged.split("\n")) {
|
|
55434
55475
|
const match = line.match(/^:\d{6} 160000 [^\t]+\t(.+)$/);
|
|
55435
|
-
if (match)
|
|
55436
|
-
|
|
55437
|
-
for (const path2 of gitlinkPaths) {
|
|
55476
|
+
if (!match) continue;
|
|
55477
|
+
const path2 = match[1];
|
|
55438
55478
|
mergerLog.warn(`${taskId}: refusing to stage gitlink "${path2}" (project uses no submodules \u2014 likely a nested worktree). Unstaging.`);
|
|
55439
55479
|
try {
|
|
55440
55480
|
await execAsync2(`git reset HEAD -- "${path2}"`, { cwd: rootDir });
|
|
@@ -55447,15 +55487,43 @@ async function amendMergeCommitWithFixes(rootDir, taskId, authorArg) {
|
|
|
55447
55487
|
cwd: rootDir,
|
|
55448
55488
|
encoding: "utf-8"
|
|
55449
55489
|
});
|
|
55450
|
-
|
|
55451
|
-
|
|
55452
|
-
|
|
55490
|
+
const hasStaged = finalStaged.trim().length > 0;
|
|
55491
|
+
const { stdout: currentHeadOut } = await execAsync2("git rev-parse HEAD", {
|
|
55492
|
+
cwd: rootDir,
|
|
55493
|
+
encoding: "utf-8"
|
|
55494
|
+
});
|
|
55495
|
+
const currentHead = currentHeadOut.trim();
|
|
55496
|
+
const headMoved = currentHead !== preAttemptHeadSha;
|
|
55497
|
+
if (!hasStaged && !headMoved) {
|
|
55498
|
+
mergerLog.warn(
|
|
55499
|
+
`${taskId}: refusing to record merge \u2014 no commit was created and no changes are staged. This usually means the AI agent never ran git commit and the in-merge fix had nothing to add.`
|
|
55500
|
+
);
|
|
55501
|
+
return false;
|
|
55502
|
+
}
|
|
55503
|
+
const { subjectArg, bodyArg } = buildDeterministicMergeMessage({
|
|
55504
|
+
taskId,
|
|
55505
|
+
branch,
|
|
55506
|
+
commitLog,
|
|
55507
|
+
includeTaskId
|
|
55508
|
+
});
|
|
55509
|
+
const trailerArg = buildTaskIdTrailerArg(taskId);
|
|
55510
|
+
if (!headMoved) {
|
|
55511
|
+
await execAsync2(
|
|
55512
|
+
`git commit ${subjectArg} ${bodyArg}${trailerArg}${authorArg}`,
|
|
55513
|
+
{ cwd: rootDir }
|
|
55514
|
+
);
|
|
55515
|
+
mergerLog.log(`${taskId}: created fresh merge commit after verification fix (no prior commit to amend)`);
|
|
55453
55516
|
return true;
|
|
55454
55517
|
}
|
|
55455
|
-
|
|
55518
|
+
await execAsync2(
|
|
55519
|
+
`git commit --amend ${subjectArg} ${bodyArg}${trailerArg}${authorArg}`,
|
|
55520
|
+
{ cwd: rootDir }
|
|
55521
|
+
);
|
|
55522
|
+
mergerLog.log(`${taskId}: amended merge commit with verification fixes (deterministic message)`);
|
|
55523
|
+
return true;
|
|
55456
55524
|
} catch (err) {
|
|
55457
55525
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
55458
|
-
mergerLog.warn(`${taskId}: failed to
|
|
55526
|
+
mergerLog.warn(`${taskId}: failed to finalize merge commit: ${errorMessage}`);
|
|
55459
55527
|
return false;
|
|
55460
55528
|
}
|
|
55461
55529
|
}
|
|
@@ -55706,6 +55774,11 @@ function getCommitAuthorArg(settings) {
|
|
|
55706
55774
|
const email = settings.commitAuthorEmail || "noreply@runfusion.ai";
|
|
55707
55775
|
return ` --author="${name} <${email}>"`;
|
|
55708
55776
|
}
|
|
55777
|
+
function buildSourceIssueRef(sourceIssue) {
|
|
55778
|
+
if (!sourceIssue || sourceIssue.provider !== "github") return "";
|
|
55779
|
+
if (!sourceIssue.repository || !sourceIssue.issueNumber) return "";
|
|
55780
|
+
return `${sourceIssue.repository}#${sourceIssue.issueNumber}`;
|
|
55781
|
+
}
|
|
55709
55782
|
function buildMergeSystemPrompt(includeTaskId, agentPrompts, authorArg) {
|
|
55710
55783
|
const commitFormat = includeTaskId ? `\`\`\`
|
|
55711
55784
|
git commit -m "<type>(<scope>): <summary>" -m "<body>"${authorArg || ""}
|
|
@@ -55716,6 +55789,7 @@ Message format:
|
|
|
55716
55789
|
- **Scope:** the task ID (e.g., KB-001)
|
|
55717
55790
|
- **Summary:** one line describing what the squash brings in (imperative mood)
|
|
55718
55791
|
- **Body:** 2-5 bullet points summarizing the key changes, each starting with "- "
|
|
55792
|
+
- **GitHub reference:** when the prompt includes a source issue reference, add \`Ref: owner/repo#N\` to the commit body
|
|
55719
55793
|
${authorArg ? `- **Author:** Always include the --author flag as shown in the example above.` : ""}
|
|
55720
55794
|
|
|
55721
55795
|
Example:
|
|
@@ -55733,6 +55807,7 @@ Message format:
|
|
|
55733
55807
|
- **Type:** feat, fix, refactor, docs, test, chore
|
|
55734
55808
|
- **Summary:** one line describing what the squash brings in (imperative mood)
|
|
55735
55809
|
- **Body:** 2-5 bullet points summarizing the key changes, each starting with "- "
|
|
55810
|
+
- **GitHub reference:** when the prompt includes a source issue reference, add \`Ref: owner/repo#N\` to the commit body
|
|
55736
55811
|
${authorArg ? `- **Author:** Always include the --author flag as shown in the example above.` : ""}
|
|
55737
55812
|
Do NOT include a scope in the commit message type.
|
|
55738
55813
|
|
|
@@ -56116,6 +56191,7 @@ async function aiMergeTask(store, rootDir, taskId, options = {}) {
|
|
|
56116
56191
|
throw new Error(`Cannot merge ${taskId}: ${mergeBlocker}`);
|
|
56117
56192
|
}
|
|
56118
56193
|
const branch = task.branch || `fusion/${taskId.toLowerCase()}`;
|
|
56194
|
+
const sourceIssueRef = buildSourceIssueRef(task.sourceIssue);
|
|
56119
56195
|
const worktreePath = task.worktree;
|
|
56120
56196
|
const result = {
|
|
56121
56197
|
task,
|
|
@@ -56419,6 +56495,17 @@ async function aiMergeTask(store, rootDir, taskId, options = {}) {
|
|
|
56419
56495
|
void 0,
|
|
56420
56496
|
"merger"
|
|
56421
56497
|
);
|
|
56498
|
+
let preAttemptHeadSha = "";
|
|
56499
|
+
try {
|
|
56500
|
+
const { stdout } = await execAsync2("git rev-parse HEAD", {
|
|
56501
|
+
cwd: rootDir,
|
|
56502
|
+
encoding: "utf-8"
|
|
56503
|
+
});
|
|
56504
|
+
preAttemptHeadSha = stdout.trim();
|
|
56505
|
+
} catch (err) {
|
|
56506
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
56507
|
+
mergerLog.warn(`${taskId}: failed to capture pre-attempt HEAD (${msg}) \u2014 verification-fix finalizer will fall back to amend`);
|
|
56508
|
+
}
|
|
56422
56509
|
try {
|
|
56423
56510
|
const success = await executeMergeAttempt({
|
|
56424
56511
|
store,
|
|
@@ -56428,6 +56515,7 @@ async function aiMergeTask(store, rootDir, taskId, options = {}) {
|
|
|
56428
56515
|
commitLog,
|
|
56429
56516
|
diffStat,
|
|
56430
56517
|
includeTaskId,
|
|
56518
|
+
sourceIssueRef,
|
|
56431
56519
|
smartConflictResolution,
|
|
56432
56520
|
mergeConflictStrategy,
|
|
56433
56521
|
attemptNum,
|
|
@@ -56536,12 +56624,27 @@ async function aiMergeTask(store, rootDir, taskId, options = {}) {
|
|
|
56536
56624
|
}
|
|
56537
56625
|
if (fixSuccess) {
|
|
56538
56626
|
const authorArg = getCommitAuthorArg(settings);
|
|
56539
|
-
await
|
|
56627
|
+
const finalized = await commitOrAmendMergeWithFixes(
|
|
56628
|
+
rootDir,
|
|
56629
|
+
taskId,
|
|
56630
|
+
branch,
|
|
56631
|
+
commitLog,
|
|
56632
|
+
includeTaskId,
|
|
56633
|
+
preAttemptHeadSha,
|
|
56634
|
+
authorArg
|
|
56635
|
+
);
|
|
56636
|
+
if (!finalized) {
|
|
56637
|
+
resetMergeWithWarn(rootDir, taskId, "verification-fix finalize");
|
|
56638
|
+
throw new Error(
|
|
56639
|
+
`${taskId}: verification fix succeeded but no merge commit could be created \u2014 refusing to mark merge complete.`
|
|
56640
|
+
);
|
|
56641
|
+
}
|
|
56540
56642
|
return true;
|
|
56541
56643
|
}
|
|
56542
56644
|
}
|
|
56543
56645
|
}
|
|
56544
56646
|
mergerLog.error(`${taskId}: deterministic verification failed \u2014 aborting merge (in-merge fix exhausted or disabled)`);
|
|
56647
|
+
resetMergeWithWarn(rootDir, taskId, "deterministic-verification rollback");
|
|
56545
56648
|
throw error;
|
|
56546
56649
|
}
|
|
56547
56650
|
if (error.message?.includes("Build verification failed")) {
|
|
@@ -56612,7 +56715,21 @@ async function aiMergeTask(store, rootDir, taskId, options = {}) {
|
|
|
56612
56715
|
}
|
|
56613
56716
|
if (fixSuccess) {
|
|
56614
56717
|
const authorArg = getCommitAuthorArg(settings);
|
|
56615
|
-
await
|
|
56718
|
+
const finalized = await commitOrAmendMergeWithFixes(
|
|
56719
|
+
rootDir,
|
|
56720
|
+
taskId,
|
|
56721
|
+
branch,
|
|
56722
|
+
commitLog,
|
|
56723
|
+
includeTaskId,
|
|
56724
|
+
preAttemptHeadSha,
|
|
56725
|
+
authorArg
|
|
56726
|
+
);
|
|
56727
|
+
if (!finalized) {
|
|
56728
|
+
resetMergeWithWarn(rootDir, taskId, "build-verification fix finalize");
|
|
56729
|
+
throw new Error(
|
|
56730
|
+
`${taskId}: build verification fix succeeded but no merge commit could be created \u2014 refusing to mark merge complete.`
|
|
56731
|
+
);
|
|
56732
|
+
}
|
|
56616
56733
|
return true;
|
|
56617
56734
|
}
|
|
56618
56735
|
}
|
|
@@ -56626,10 +56743,11 @@ async function aiMergeTask(store, rootDir, taskId, options = {}) {
|
|
|
56626
56743
|
await audit.git({ type: "reset:hard", target: branch, metadata: { purpose: "build-retry" } });
|
|
56627
56744
|
} catch (err) {
|
|
56628
56745
|
const msg = err instanceof Error ? err.message : String(err);
|
|
56629
|
-
mergerLog.warn(`${taskId}: git reset --merge cleanup failed (build-retry): ${msg}`);
|
|
56746
|
+
mergerLog.warn(`${taskId}: git reset --merge cleanup failed during build-verification rollback (build-retry): ${msg}`);
|
|
56630
56747
|
}
|
|
56631
56748
|
return false;
|
|
56632
56749
|
}
|
|
56750
|
+
resetMergeWithWarn(rootDir, taskId, "build-verification rollback (no retries left)");
|
|
56633
56751
|
throw error;
|
|
56634
56752
|
}
|
|
56635
56753
|
if (error.name === "MergeNonConflictError") {
|
|
@@ -56977,6 +57095,7 @@ async function executeMergeAttempt(params, aiTracker) {
|
|
|
56977
57095
|
commitLog,
|
|
56978
57096
|
diffStat,
|
|
56979
57097
|
includeTaskId,
|
|
57098
|
+
sourceIssueRef,
|
|
56980
57099
|
smartConflictResolution,
|
|
56981
57100
|
attemptNum,
|
|
56982
57101
|
options,
|
|
@@ -57167,19 +57286,12 @@ async function executeMergeAttempt(params, aiTracker) {
|
|
|
57167
57286
|
simplifiedContext: attemptNum === 2,
|
|
57168
57287
|
options,
|
|
57169
57288
|
testCommand,
|
|
57170
|
-
buildCommand: buildCommand2
|
|
57289
|
+
buildCommand: buildCommand2,
|
|
57290
|
+
sourceIssueRef
|
|
57171
57291
|
});
|
|
57172
57292
|
if (!agentResult.success) {
|
|
57173
57293
|
const errorMessage = agentResult.error || "Build verification failed";
|
|
57174
57294
|
await store.logEntry(taskId, "Build verification failed during merge", errorMessage);
|
|
57175
|
-
try {
|
|
57176
|
-
execSync("git reset --merge", { cwd: rootDir, stdio: "pipe" });
|
|
57177
|
-
} catch (resetErr) {
|
|
57178
|
-
const msg = resetErr instanceof Error ? resetErr.message : String(resetErr);
|
|
57179
|
-
mergerLog.warn(
|
|
57180
|
-
`${taskId}: git reset --merge cleanup failed during build-verification rollback (build-verification reset, build-retry): ${msg}`
|
|
57181
|
-
);
|
|
57182
|
-
}
|
|
57183
57295
|
throw new Error(`Build verification failed for ${taskId}: ${errorMessage}`);
|
|
57184
57296
|
}
|
|
57185
57297
|
if (testCommand || buildCommand2) {
|
|
@@ -57195,6 +57307,24 @@ async function executeMergeAttempt(params, aiTracker) {
|
|
|
57195
57307
|
options.signal
|
|
57196
57308
|
);
|
|
57197
57309
|
}
|
|
57310
|
+
try {
|
|
57311
|
+
const authorArg = getCommitAuthorArg(params.settings);
|
|
57312
|
+
const { subjectArg, bodyArg } = buildDeterministicMergeMessage({
|
|
57313
|
+
taskId,
|
|
57314
|
+
branch,
|
|
57315
|
+
commitLog,
|
|
57316
|
+
includeTaskId
|
|
57317
|
+
});
|
|
57318
|
+
const trailerArg = buildTaskIdTrailerArg(taskId);
|
|
57319
|
+
await execAsync2(
|
|
57320
|
+
`git commit --amend ${subjectArg} ${bodyArg}${trailerArg}${authorArg}`,
|
|
57321
|
+
{ cwd: rootDir }
|
|
57322
|
+
);
|
|
57323
|
+
mergerLog.log(`${taskId}: rewrote AI-authored merge commit message with deterministic body`);
|
|
57324
|
+
} catch (err) {
|
|
57325
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
57326
|
+
mergerLog.warn(`${taskId}: failed to canonicalize merge commit message (${msg}) \u2014 keeping AI-written message`);
|
|
57327
|
+
}
|
|
57198
57328
|
return true;
|
|
57199
57329
|
} catch (error) {
|
|
57200
57330
|
if (error instanceof Error && error.name === "MergeAbortedError") {
|
|
@@ -57217,7 +57347,7 @@ async function executeMergeAttempt(params, aiTracker) {
|
|
|
57217
57347
|
}
|
|
57218
57348
|
}
|
|
57219
57349
|
async function attemptWithSideStrategy(params, side = "theirs", aiTracker) {
|
|
57220
|
-
const { rootDir, branch, commitLog, includeTaskId, taskId, store, settings, testCommand, buildCommand: buildCommand2, testSource, buildSource } = params;
|
|
57350
|
+
const { rootDir, branch, commitLog, includeTaskId, sourceIssueRef, taskId, store, settings, testCommand, buildCommand: buildCommand2, testSource, buildSource } = params;
|
|
57221
57351
|
mergerLog.log(`${taskId}: attempting merge with -X ${side} strategy`);
|
|
57222
57352
|
try {
|
|
57223
57353
|
throwIfAborted(params.options.signal, taskId);
|
|
@@ -57258,8 +57388,9 @@ async function attemptWithSideStrategy(params, side = "theirs", aiTracker) {
|
|
|
57258
57388
|
const fallbackPrefix = includeTaskId ? `feat(${taskId})` : "feat";
|
|
57259
57389
|
const authorArg = getCommitAuthorArg(settings);
|
|
57260
57390
|
const trailerArg = buildTaskIdTrailerArg(taskId);
|
|
57391
|
+
const issueRefBodyArg = sourceIssueRef ? ` -m "Ref: ${sourceIssueRef}"` : "";
|
|
57261
57392
|
await execAsync2(
|
|
57262
|
-
`git commit -m "${fallbackPrefix}: merge ${branch} (auto-resolved)" -m "${escapedLog}"${trailerArg}${authorArg}`,
|
|
57393
|
+
`git commit -m "${fallbackPrefix}: merge ${branch} (auto-resolved)" -m "${escapedLog}"${issueRefBodyArg}${trailerArg}${authorArg}`,
|
|
57263
57394
|
{ cwd: rootDir }
|
|
57264
57395
|
);
|
|
57265
57396
|
mergerLog.log(`${taskId}: committed with -X ${side} auto-resolution`);
|
|
@@ -57296,6 +57427,7 @@ async function runAiAgentForCommit(params) {
|
|
|
57296
57427
|
includeTaskId,
|
|
57297
57428
|
hasConflicts,
|
|
57298
57429
|
simplifiedContext,
|
|
57430
|
+
sourceIssueRef,
|
|
57299
57431
|
options,
|
|
57300
57432
|
testCommand,
|
|
57301
57433
|
buildCommand: buildCommand2
|
|
@@ -57394,7 +57526,8 @@ async function runAiAgentForCommit(params) {
|
|
|
57394
57526
|
simplifiedContext,
|
|
57395
57527
|
testCommand,
|
|
57396
57528
|
buildCommand: buildCommand2,
|
|
57397
|
-
authorArg
|
|
57529
|
+
authorArg,
|
|
57530
|
+
sourceIssueRef
|
|
57398
57531
|
});
|
|
57399
57532
|
mergerLog.log(`${taskId}: starting fresh merge agent session`);
|
|
57400
57533
|
try {
|
|
@@ -57426,7 +57559,8 @@ async function runAiAgentForCommit(params) {
|
|
|
57426
57559
|
// Also skip detailed context
|
|
57427
57560
|
testCommand,
|
|
57428
57561
|
buildCommand: buildCommand2,
|
|
57429
|
-
authorArg
|
|
57562
|
+
authorArg,
|
|
57563
|
+
sourceIssueRef
|
|
57430
57564
|
});
|
|
57431
57565
|
try {
|
|
57432
57566
|
await withRateLimitRetry(async () => {
|
|
@@ -57468,8 +57602,9 @@ async function runAiAgentForCommit(params) {
|
|
|
57468
57602
|
const fallbackPrefix = includeTaskId ? `feat(${taskId})` : "feat";
|
|
57469
57603
|
const authorArg2 = getCommitAuthorArg(settings);
|
|
57470
57604
|
const trailerArg = buildTaskIdTrailerArg(taskId);
|
|
57605
|
+
const issueRefBodyArg = sourceIssueRef ? ` -m "Ref: ${sourceIssueRef}"` : "";
|
|
57471
57606
|
await execAsync2(
|
|
57472
|
-
`git commit -m "${fallbackPrefix}: merge ${branch}" -m "${escapedLog}"${trailerArg}${authorArg2}`,
|
|
57607
|
+
`git commit -m "${fallbackPrefix}: merge ${branch}" -m "${escapedLog}"${issueRefBodyArg}${trailerArg}${authorArg2}`,
|
|
57473
57608
|
{ cwd: rootDir }
|
|
57474
57609
|
);
|
|
57475
57610
|
} else {
|
|
@@ -57492,7 +57627,7 @@ async function runAiAgentForCommit(params) {
|
|
|
57492
57627
|
}
|
|
57493
57628
|
}
|
|
57494
57629
|
function buildMergePrompt(params) {
|
|
57495
|
-
const { taskId, branch, commitLog, diffStat, hasConflicts, simplifiedContext, testCommand, buildCommand: buildCommand2, authorArg } = params;
|
|
57630
|
+
const { taskId, branch, commitLog, diffStat, hasConflicts, simplifiedContext, sourceIssueRef, testCommand, buildCommand: buildCommand2, authorArg } = params;
|
|
57496
57631
|
const truncatedCommitLog = truncateWithEllipsis(commitLog, MERGE_COMMIT_LOG_MAX_CHARS);
|
|
57497
57632
|
const truncatedDiffStat = truncateWithEllipsis(diffStat, MERGE_DIFF_STAT_MAX_CHARS);
|
|
57498
57633
|
const parts = [
|
|
@@ -57528,6 +57663,13 @@ function buildMergePrompt(params) {
|
|
|
57528
57663
|
`Write and run the \`git commit\` command with a good message summarizing the work.${authorArg ? ` Be sure to include \`${authorArg.trim()}\` in the commit command.` : ""}`
|
|
57529
57664
|
);
|
|
57530
57665
|
}
|
|
57666
|
+
if (sourceIssueRef) {
|
|
57667
|
+
parts.push(
|
|
57668
|
+
"",
|
|
57669
|
+
"Include this in the commit message body:",
|
|
57670
|
+
`- Ref: ${sourceIssueRef}`
|
|
57671
|
+
);
|
|
57672
|
+
}
|
|
57531
57673
|
if (testCommand) {
|
|
57532
57674
|
parts.push(
|
|
57533
57675
|
"",
|
|
@@ -58703,7 +58845,7 @@ function resolveExecutorModelPair(taskModelProvider, taskModelId, settings) {
|
|
|
58703
58845
|
return { provider: void 0, modelId: void 0 };
|
|
58704
58846
|
}
|
|
58705
58847
|
function sleep2(ms) {
|
|
58706
|
-
return new Promise((
|
|
58848
|
+
return new Promise((resolve19) => setTimeout(resolve19, ms));
|
|
58707
58849
|
}
|
|
58708
58850
|
var execAsync4, stepExecLog, MAX_STEP_RETRIES, RETRY_DELAYS_MS, NOOP_TASK_STORE, StepSessionExecutor;
|
|
58709
58851
|
var init_step_session_executor = __esm({
|
|
@@ -59451,6 +59593,7 @@ function buildExecutionPrompt(task, rootDir, settings, worktreePath) {
|
|
|
59451
59593
|
const reviewMatch = prompt.match(/##\s*Review Level[:\s]*(\d)/);
|
|
59452
59594
|
const reviewLevel = reviewMatch ? parseInt(reviewMatch[1], 10) : 0;
|
|
59453
59595
|
const authorArg = settings?.commitAuthorEnabled !== false ? ` --author="${settings?.commitAuthorName || "Fusion"} <${settings?.commitAuthorEmail || "noreply@runfusion.ai"}>"` : "";
|
|
59596
|
+
const sourceIssueRef = task.sourceIssue?.provider === "github" && task.sourceIssue.repository && task.sourceIssue.issueNumber ? `${task.sourceIssue.repository}#${task.sourceIssue.issueNumber}` : "";
|
|
59454
59597
|
const hasProgress = task.steps.length > 0 && task.steps.some((s) => s.status !== "pending");
|
|
59455
59598
|
let progressSection = "";
|
|
59456
59599
|
if (hasProgress) {
|
|
@@ -59553,7 +59696,7 @@ ${hasProgress ? `Resume from Step ${task.currentStep}. Do NOT redo completed ste
|
|
|
59553
59696
|
Use \`fn_task_update\` to report progress on every step transition.
|
|
59554
59697
|
Use \`fn_task_log\` for important actions and decisions.
|
|
59555
59698
|
Use \`fn_task_create\` for truly separate follow-up work, not for fixes required to get tests, build, or typecheck back to green.
|
|
59556
|
-
Commit at step boundaries: \`git commit -m "feat(${task.id}): complete Step N \u2014 description"${authorArg}\`
|
|
59699
|
+
Commit at step boundaries: \`git commit -m "feat(${task.id}): complete Step N \u2014 description"${sourceIssueRef ? ` -m "Ref: ${sourceIssueRef}"` : ""}${authorArg}\`
|
|
59557
59700
|
When all steps are complete: call \`fn_task_done()\`
|
|
59558
59701
|
|
|
59559
59702
|
If a build command is configured, run that exact command in this worktree before calling \`fn_task_done()\`.
|
|
@@ -59586,7 +59729,7 @@ function detectReviewHandoffIntent(commentText) {
|
|
|
59586
59729
|
];
|
|
59587
59730
|
return handoffPhrases.some((phrase) => text.includes(phrase));
|
|
59588
59731
|
}
|
|
59589
|
-
var execAsync5, STEP_STATUSES, MAX_WORKFLOW_STEP_RETRIES, MAX_TASK_DONE_SESSION_RETRIES, MAX_TASK_DONE_REQUEUE_RETRIES, WORKFLOW_SCRIPT_OUTPUT_MAX_CHARS2, NonRetryableWorktreeError, taskUpdateParams, taskAddDepParams, spawnAgentParams, reviewStepParams, EXECUTOR_SYSTEM_PROMPT, TaskExecutor;
|
|
59732
|
+
var execAsync5, STEP_STATUSES, MAX_WORKFLOW_STEP_RETRIES, MAX_TASK_DONE_SESSION_RETRIES, MAX_TASK_DONE_REQUEUE_RETRIES, COMPLETED_TASK_WATCHDOG_MS, WORKFLOW_RERUN_WATCHDOG_MS, WORKFLOW_SCRIPT_OUTPUT_MAX_CHARS2, NonRetryableWorktreeError, taskUpdateParams, taskAddDepParams, spawnAgentParams, reviewStepParams, EXECUTOR_SYSTEM_PROMPT, TaskExecutor;
|
|
59590
59733
|
var init_executor = __esm({
|
|
59591
59734
|
"../engine/src/executor.ts"() {
|
|
59592
59735
|
"use strict";
|
|
@@ -59622,6 +59765,8 @@ var init_executor = __esm({
|
|
|
59622
59765
|
MAX_WORKFLOW_STEP_RETRIES = 3;
|
|
59623
59766
|
MAX_TASK_DONE_SESSION_RETRIES = 3;
|
|
59624
59767
|
MAX_TASK_DONE_REQUEUE_RETRIES = 3;
|
|
59768
|
+
COMPLETED_TASK_WATCHDOG_MS = 6e4;
|
|
59769
|
+
WORKFLOW_RERUN_WATCHDOG_MS = 15e3;
|
|
59625
59770
|
WORKFLOW_SCRIPT_OUTPUT_MAX_CHARS2 = 4e3;
|
|
59626
59771
|
NonRetryableWorktreeError = class extends Error {
|
|
59627
59772
|
};
|
|
@@ -59739,6 +59884,7 @@ If the task's PROMPT.md includes a "Documentation Requirements" section listing
|
|
|
59739
59884
|
## Git discipline
|
|
59740
59885
|
- Commit after completing each step (not after every file change)
|
|
59741
59886
|
- Use conventional commit messages prefixed with the task ID
|
|
59887
|
+
- When the task has a GitHub issue reference, include \`Ref: owner/repo#N\` in the commit body
|
|
59742
59888
|
- Do NOT commit broken or half-implemented code
|
|
59743
59889
|
|
|
59744
59890
|
## Worktree Boundaries
|
|
@@ -59831,6 +59977,7 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
59831
59977
|
store.on("task:moved", ({ task, from, to }) => {
|
|
59832
59978
|
executorLog.log(`[event:task:moved] ${task.id}: ${from} \u2192 ${to}`);
|
|
59833
59979
|
if (to === "in-progress") {
|
|
59980
|
+
this.clearWorkflowRerunWatchdog(task.id);
|
|
59834
59981
|
executorLog.log(`[event:task:moved] Initiating execute() for ${task.id}`);
|
|
59835
59982
|
void (async () => {
|
|
59836
59983
|
const taskForExecution = await this.resetMergeStateIfNeeded(task, from);
|
|
@@ -59839,6 +59986,7 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
59839
59986
|
(err) => executorLog.error(`Failed to start ${task.id}:`, err)
|
|
59840
59987
|
);
|
|
59841
59988
|
} else if (from === "in-progress") {
|
|
59989
|
+
this.clearCompletedTaskWatchdog(task.id);
|
|
59842
59990
|
if (this.activeSessions.has(task.id)) {
|
|
59843
59991
|
executorLog.log(`${task.id} moved from in-progress to ${to} \u2014 terminating agent session`);
|
|
59844
59992
|
this.pausedAborted.add(task.id);
|
|
@@ -60030,6 +60178,10 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
60030
60178
|
spawnedAgents = /* @__PURE__ */ new Map();
|
|
60031
60179
|
/** Per-task baseline of session stats used for delta persistence across repeated updates. */
|
|
60032
60180
|
tokenUsageBaselines = /* @__PURE__ */ new Map();
|
|
60181
|
+
/** One-shot watchdogs for completed tasks that should have transitioned to in-review. */
|
|
60182
|
+
completedTaskWatchdogs = /* @__PURE__ */ new Map();
|
|
60183
|
+
/** One-shot watchdogs for workflow reruns that should have bounced back to in-progress. */
|
|
60184
|
+
workflowRerunWatchdogs = /* @__PURE__ */ new Map();
|
|
60033
60185
|
async finalizeAlreadyReviewedTask(taskId) {
|
|
60034
60186
|
const latestTask = await this.store.getTask(taskId);
|
|
60035
60187
|
if (!latestTask || latestTask.column !== "in-review") {
|
|
@@ -60185,6 +60337,120 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
60185
60337
|
await this.store.updateTask(task.id, updates);
|
|
60186
60338
|
}
|
|
60187
60339
|
}
|
|
60340
|
+
clearCompletedTaskWatchdog(taskId) {
|
|
60341
|
+
const handle = this.completedTaskWatchdogs.get(taskId);
|
|
60342
|
+
if (!handle) return;
|
|
60343
|
+
clearTimeout(handle);
|
|
60344
|
+
this.completedTaskWatchdogs.delete(taskId);
|
|
60345
|
+
}
|
|
60346
|
+
clearWorkflowRerunWatchdog(taskId) {
|
|
60347
|
+
const handle = this.workflowRerunWatchdogs.get(taskId);
|
|
60348
|
+
if (!handle) return;
|
|
60349
|
+
clearTimeout(handle);
|
|
60350
|
+
this.workflowRerunWatchdogs.delete(taskId);
|
|
60351
|
+
}
|
|
60352
|
+
scheduleCompletedTaskWatchdog(taskId, trigger) {
|
|
60353
|
+
this.clearCompletedTaskWatchdog(taskId);
|
|
60354
|
+
const handle = setTimeout(async () => {
|
|
60355
|
+
this.completedTaskWatchdogs.delete(taskId);
|
|
60356
|
+
let currentTask = null;
|
|
60357
|
+
try {
|
|
60358
|
+
currentTask = await this.store.getTask(taskId);
|
|
60359
|
+
} catch (err) {
|
|
60360
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
60361
|
+
executorLog.warn(`${taskId}: completed-task watchdog could not read latest task state: ${errorMessage}`);
|
|
60362
|
+
return;
|
|
60363
|
+
}
|
|
60364
|
+
if (!currentTask || currentTask.column !== "in-progress" || currentTask.paused) {
|
|
60365
|
+
return;
|
|
60366
|
+
}
|
|
60367
|
+
if (this.activeSessions.has(taskId) || this.activeStepExecutors.has(taskId) || this.recoveringCompleted.has(taskId)) {
|
|
60368
|
+
return;
|
|
60369
|
+
}
|
|
60370
|
+
if (!this.isTaskWorkComplete(currentTask)) {
|
|
60371
|
+
return;
|
|
60372
|
+
}
|
|
60373
|
+
executorLog.warn(
|
|
60374
|
+
`${taskId}: completed-task watchdog fired after ${COMPLETED_TASK_WATCHDOG_MS / 1e3}s (${trigger}) \u2014 attempting direct recovery to in-review`
|
|
60375
|
+
);
|
|
60376
|
+
await this.store.logEntry(
|
|
60377
|
+
taskId,
|
|
60378
|
+
`Watchdog: task remained in-progress ${COMPLETED_TASK_WATCHDOG_MS / 1e3}s after ${trigger} \u2014 attempting direct recovery to in-review`
|
|
60379
|
+
).catch(() => void 0);
|
|
60380
|
+
this.recoveringCompleted.add(taskId);
|
|
60381
|
+
try {
|
|
60382
|
+
const recovered = await this.recoverCompletedTask(currentTask);
|
|
60383
|
+
if (!recovered) {
|
|
60384
|
+
await this.store.logEntry(
|
|
60385
|
+
taskId,
|
|
60386
|
+
"Watchdog recovery attempt could not finalize completed task \u2014 leaving for follow-up recovery"
|
|
60387
|
+
).catch(() => void 0);
|
|
60388
|
+
}
|
|
60389
|
+
} finally {
|
|
60390
|
+
this.recoveringCompleted.delete(taskId);
|
|
60391
|
+
}
|
|
60392
|
+
}, COMPLETED_TASK_WATCHDOG_MS);
|
|
60393
|
+
this.completedTaskWatchdogs.set(taskId, handle);
|
|
60394
|
+
}
|
|
60395
|
+
async performWorkflowRerunBounce(taskId, worktreePath) {
|
|
60396
|
+
const latestTask = await this.store.getTask(taskId);
|
|
60397
|
+
if (!latestTask) {
|
|
60398
|
+
throw new Error("task missing during workflow rerun bounce");
|
|
60399
|
+
}
|
|
60400
|
+
if (latestTask.column === "in-progress") {
|
|
60401
|
+
await this.store.moveTask(taskId, "todo");
|
|
60402
|
+
await this.store.updateTask(taskId, { worktree: worktreePath });
|
|
60403
|
+
await this.store.moveTask(taskId, "in-progress");
|
|
60404
|
+
return;
|
|
60405
|
+
}
|
|
60406
|
+
if (latestTask.column === "todo") {
|
|
60407
|
+
await this.store.updateTask(taskId, { worktree: worktreePath });
|
|
60408
|
+
await this.store.moveTask(taskId, "in-progress");
|
|
60409
|
+
return;
|
|
60410
|
+
}
|
|
60411
|
+
throw new Error(`task is in '${latestTask.column}', cannot bounce to in-progress`);
|
|
60412
|
+
}
|
|
60413
|
+
scheduleWorkflowRerun(taskId, worktreePath, successMessage) {
|
|
60414
|
+
this.clearWorkflowRerunWatchdog(taskId);
|
|
60415
|
+
setTimeout(async () => {
|
|
60416
|
+
try {
|
|
60417
|
+
await this.performWorkflowRerunBounce(taskId, worktreePath);
|
|
60418
|
+
executorLog.log(successMessage);
|
|
60419
|
+
} catch (err) {
|
|
60420
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
60421
|
+
executorLog.error(`${taskId}: failed to schedule rerun bounce: ${errorMessage}`);
|
|
60422
|
+
}
|
|
60423
|
+
}, 0);
|
|
60424
|
+
const watchdog = setTimeout(async () => {
|
|
60425
|
+
this.workflowRerunWatchdogs.delete(taskId);
|
|
60426
|
+
let currentTask = null;
|
|
60427
|
+
try {
|
|
60428
|
+
currentTask = await this.store.getTask(taskId);
|
|
60429
|
+
} catch (err) {
|
|
60430
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
60431
|
+
executorLog.warn(`${taskId}: workflow rerun watchdog could not read latest task state: ${errorMessage}`);
|
|
60432
|
+
return;
|
|
60433
|
+
}
|
|
60434
|
+
if (!currentTask || currentTask.paused || currentTask.column === "in-progress") {
|
|
60435
|
+
return;
|
|
60436
|
+
}
|
|
60437
|
+
executorLog.warn(
|
|
60438
|
+
`${taskId}: workflow rerun watchdog fired after ${WORKFLOW_RERUN_WATCHDOG_MS / 1e3}s \u2014 task is still ${currentTask.column}; retrying handoff once`
|
|
60439
|
+
);
|
|
60440
|
+
await this.store.logEntry(
|
|
60441
|
+
taskId,
|
|
60442
|
+
`Watchdog: workflow rerun handoff stalled for ${WORKFLOW_RERUN_WATCHDOG_MS / 1e3}s (still ${currentTask.column}) \u2014 retrying once`
|
|
60443
|
+
).catch(() => void 0);
|
|
60444
|
+
try {
|
|
60445
|
+
await this.performWorkflowRerunBounce(taskId, worktreePath);
|
|
60446
|
+
executorLog.warn(`${taskId}: workflow rerun watchdog retry succeeded`);
|
|
60447
|
+
} catch (err) {
|
|
60448
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
60449
|
+
executorLog.error(`${taskId}: workflow rerun watchdog retry failed: ${errorMessage}`);
|
|
60450
|
+
}
|
|
60451
|
+
}, WORKFLOW_RERUN_WATCHDOG_MS);
|
|
60452
|
+
this.workflowRerunWatchdogs.set(taskId, watchdog);
|
|
60453
|
+
}
|
|
60188
60454
|
async shouldFinalizeCompletedTask(taskId, taskDone) {
|
|
60189
60455
|
const task = await this.store.getTask(taskId);
|
|
60190
60456
|
const completionBlocker = await this.getTaskCompletionBlocker(task);
|
|
@@ -60318,6 +60584,7 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
60318
60584
|
}
|
|
60319
60585
|
await this.persistTokenUsage(task.id);
|
|
60320
60586
|
await this.store.moveTask(task.id, "in-review");
|
|
60587
|
+
this.clearCompletedTaskWatchdog(task.id);
|
|
60321
60588
|
await this.store.logEntry(task.id, "Auto-recovered: task work was complete but stuck in in-progress \u2014 moved to in-review");
|
|
60322
60589
|
executorLog.log(`\u2713 ${task.id} auto-recovered completed task \u2192 in-review`);
|
|
60323
60590
|
this.options.onComplete?.(task);
|
|
@@ -60814,6 +61081,7 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
60814
61081
|
executorLog.log(`${task.id}: captured ${modifiedFiles.length} modified files`);
|
|
60815
61082
|
await audit.filesystem({ type: "file:capture-modified", target: task.id, metadata: { files: modifiedFiles } });
|
|
60816
61083
|
}
|
|
61084
|
+
this.scheduleCompletedTaskWatchdog(task.id, "step-session completion");
|
|
60817
61085
|
if (executionMode !== "fast") {
|
|
60818
61086
|
const workflowResult = await this.runWorkflowSteps(task, worktreePath, settings);
|
|
60819
61087
|
if (!workflowResult.allPassed) {
|
|
@@ -60834,6 +61102,7 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
60834
61102
|
}
|
|
60835
61103
|
await this.store.updateTask(task.id, { workflowStepRetries: void 0, taskDoneRetryCount: null });
|
|
60836
61104
|
await this.store.moveTask(task.id, "in-review");
|
|
61105
|
+
this.clearCompletedTaskWatchdog(task.id);
|
|
60837
61106
|
await audit.database({ type: "task:move", target: task.id, metadata: { to: "in-review" } });
|
|
60838
61107
|
executorLog.log(`\u2713 ${task.id} completed (step-session) \u2192 in-review`);
|
|
60839
61108
|
this.options.onComplete?.(task);
|
|
@@ -61170,6 +61439,7 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
61170
61439
|
await this.store.logEntry(task.id, "Execution paused after completion \u2014 finalizing to in-review");
|
|
61171
61440
|
await this.persistTokenUsage(task.id);
|
|
61172
61441
|
await this.store.moveTask(task.id, "in-review");
|
|
61442
|
+
this.clearCompletedTaskWatchdog(task.id);
|
|
61173
61443
|
this.options.onComplete?.(task);
|
|
61174
61444
|
} else {
|
|
61175
61445
|
executorLog.log(`${task.id} paused (graceful session exit) \u2014 moving to todo`);
|
|
@@ -61190,6 +61460,7 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
61190
61460
|
taskDone = true;
|
|
61191
61461
|
executorLog.log(`${task.id} all steps done \u2014 treating as implicit fn_task_done`);
|
|
61192
61462
|
await this.store.logEntry(task.id, "All steps complete \u2014 implicit fn_task_done (agent did not call tool explicitly)", void 0, this.currentRunContext);
|
|
61463
|
+
this.scheduleCompletedTaskWatchdog(task.id, "implicit fn_task_done");
|
|
61193
61464
|
}
|
|
61194
61465
|
}
|
|
61195
61466
|
if (taskDone) {
|
|
@@ -61199,6 +61470,7 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
61199
61470
|
await this.store.updateTask(task.id, { modifiedFiles });
|
|
61200
61471
|
executorLog.log(`${task.id}: captured ${modifiedFiles.length} modified files`);
|
|
61201
61472
|
}
|
|
61473
|
+
this.scheduleCompletedTaskWatchdog(task.id, "task completion");
|
|
61202
61474
|
if (executionMode !== "fast") {
|
|
61203
61475
|
const workflowResult = await this.runWorkflowSteps(task, worktreePath, settings);
|
|
61204
61476
|
if (!workflowResult.allPassed) {
|
|
@@ -61220,6 +61492,7 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
61220
61492
|
await this.store.updateTask(task.id, { workflowStepRetries: void 0, taskDoneRetryCount: null });
|
|
61221
61493
|
await this.persistTokenUsage(task.id);
|
|
61222
61494
|
await this.store.moveTask(task.id, "in-review");
|
|
61495
|
+
this.clearCompletedTaskWatchdog(task.id);
|
|
61223
61496
|
executorLog.log(`\u2713 ${task.id} completed \u2192 in-review`);
|
|
61224
61497
|
this.options.onComplete?.(task);
|
|
61225
61498
|
} else {
|
|
@@ -61293,6 +61566,7 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
61293
61566
|
taskDone = true;
|
|
61294
61567
|
executorLog.log(`${task.id} all steps done \u2014 treating as implicit fn_task_done`);
|
|
61295
61568
|
await this.store.logEntry(task.id, "All steps complete \u2014 implicit fn_task_done (agent did not call tool explicitly)", void 0, this.currentRunContext);
|
|
61569
|
+
this.scheduleCompletedTaskWatchdog(task.id, "implicit fn_task_done");
|
|
61296
61570
|
}
|
|
61297
61571
|
}
|
|
61298
61572
|
}
|
|
@@ -61303,6 +61577,7 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
61303
61577
|
await this.store.updateTask(task.id, { modifiedFiles });
|
|
61304
61578
|
executorLog.log(`${task.id}: captured ${modifiedFiles.length} modified files`);
|
|
61305
61579
|
}
|
|
61580
|
+
this.scheduleCompletedTaskWatchdog(task.id, "task completion retry");
|
|
61306
61581
|
if (executionMode !== "fast") {
|
|
61307
61582
|
const workflowResult = await this.runWorkflowSteps(task, worktreePath, settings);
|
|
61308
61583
|
if (!workflowResult.allPassed) {
|
|
@@ -61320,6 +61595,7 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
61320
61595
|
await this.store.updateTask(task.id, { workflowStepRetries: void 0, taskDoneRetryCount: null });
|
|
61321
61596
|
await this.persistTokenUsage(task.id);
|
|
61322
61597
|
await this.store.moveTask(task.id, "in-review");
|
|
61598
|
+
this.clearCompletedTaskWatchdog(task.id);
|
|
61323
61599
|
executorLog.log(`\u2713 ${task.id} completed on retry \u2192 in-review`);
|
|
61324
61600
|
this.options.onComplete?.(task);
|
|
61325
61601
|
} else {
|
|
@@ -61747,6 +62023,7 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
61747
62023
|
await store.updateTask(taskId, { summary: params.summary });
|
|
61748
62024
|
}
|
|
61749
62025
|
await store.logEntry(taskId, "Task marked done by agent");
|
|
62026
|
+
this.scheduleCompletedTaskWatchdog(taskId, "fn_task_done");
|
|
61750
62027
|
const successMessage = params.summary ? "Task marked complete with summary. All steps done. Moving to in-review." : "Task marked complete. All steps done. Moving to in-review.";
|
|
61751
62028
|
return {
|
|
61752
62029
|
content: [{ type: "text", text: successMessage }],
|
|
@@ -61973,6 +62250,7 @@ Take a different approach. Do NOT repeat the rejected strategy. Re-read the step
|
|
|
61973
62250
|
*/
|
|
61974
62251
|
async handleWorkflowRevisionRequest(task, worktreePath, feedback, stepName) {
|
|
61975
62252
|
executorLog.log(`${task.id}: workflow revision requested by step "${stepName}"`);
|
|
62253
|
+
this.clearCompletedTaskWatchdog(task.id);
|
|
61976
62254
|
const updatedTask = await this.store.getTask(task.id);
|
|
61977
62255
|
const reopen = await this.reopenLastStepForRevision(task.id, updatedTask);
|
|
61978
62256
|
const reopenSummary = reopen ? `re-opening Step ${reopen.index + 1} ("${reopen.name}") for in-place fix` : "no step to re-open (none were completed)";
|
|
@@ -61987,21 +62265,11 @@ Take a different approach. Do NOT repeat the rejected strategy. Re-read the step
|
|
|
61987
62265
|
sessionFile: null
|
|
61988
62266
|
});
|
|
61989
62267
|
executorLog.log(`${task.id}: scheduling fresh execution after revision request`);
|
|
61990
|
-
|
|
61991
|
-
|
|
61992
|
-
|
|
61993
|
-
|
|
61994
|
-
|
|
61995
|
-
executorLog.log(`${task.id}: revision rerun scheduled \u2014 moved to todo then in-progress`);
|
|
61996
|
-
} catch (err) {
|
|
61997
|
-
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
61998
|
-
executorLog.error(`${task.id}: failed to schedule revision rerun: ${errorMessage}`);
|
|
61999
|
-
await this.store.logEntry(
|
|
62000
|
-
task.id,
|
|
62001
|
-
"Workflow revision requested \u2014 executor ready for fresh execution"
|
|
62002
|
-
);
|
|
62003
|
-
}
|
|
62004
|
-
}, 0);
|
|
62268
|
+
this.scheduleWorkflowRerun(
|
|
62269
|
+
task.id,
|
|
62270
|
+
worktreePath,
|
|
62271
|
+
`${task.id}: revision rerun scheduled \u2014 moved to todo then in-progress`
|
|
62272
|
+
);
|
|
62005
62273
|
}
|
|
62006
62274
|
/**
|
|
62007
62275
|
* Re-open the last non-pending step so a revision/failure handler gives the
|
|
@@ -62088,6 +62356,7 @@ ${feedback}
|
|
|
62088
62356
|
* @returns true if a retry was scheduled, false if retries are exhausted
|
|
62089
62357
|
*/
|
|
62090
62358
|
async handleWorkflowStepFailure(task, worktreePath, failureFeedback, stepName) {
|
|
62359
|
+
this.clearCompletedTaskWatchdog(task.id);
|
|
62091
62360
|
const currentRetries = task.workflowStepRetries ?? 0;
|
|
62092
62361
|
if (currentRetries >= MAX_WORKFLOW_STEP_RETRIES) {
|
|
62093
62362
|
executorLog.warn(`${task.id}: workflow step "${stepName}" failed \u2014 retries exhausted (${MAX_WORKFLOW_STEP_RETRIES}/${MAX_WORKFLOW_STEP_RETRIES})`);
|
|
@@ -62106,21 +62375,11 @@ ${feedback}
|
|
|
62106
62375
|
sessionFile: null
|
|
62107
62376
|
});
|
|
62108
62377
|
executorLog.log(`${task.id}: scheduling fresh execution after workflow step failure (retry ${retryCount}/${MAX_WORKFLOW_STEP_RETRIES})`);
|
|
62109
|
-
|
|
62110
|
-
|
|
62111
|
-
|
|
62112
|
-
|
|
62113
|
-
|
|
62114
|
-
executorLog.log(`${task.id}: workflow step retry scheduled \u2014 moved to todo then in-progress`);
|
|
62115
|
-
} catch (err) {
|
|
62116
|
-
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
62117
|
-
executorLog.error(`${task.id}: failed to schedule workflow step retry: ${errorMessage}`);
|
|
62118
|
-
await this.store.logEntry(
|
|
62119
|
-
task.id,
|
|
62120
|
-
"Workflow step failed \u2014 executor ready for fresh execution"
|
|
62121
|
-
);
|
|
62122
|
-
}
|
|
62123
|
-
}, 0);
|
|
62378
|
+
this.scheduleWorkflowRerun(
|
|
62379
|
+
task.id,
|
|
62380
|
+
worktreePath,
|
|
62381
|
+
`${task.id}: workflow step retry scheduled \u2014 moved to todo then in-progress`
|
|
62382
|
+
);
|
|
62124
62383
|
return true;
|
|
62125
62384
|
}
|
|
62126
62385
|
/**
|
|
@@ -62130,6 +62389,7 @@ ${feedback}
|
|
|
62130
62389
|
*/
|
|
62131
62390
|
async sendTaskBackForFix(task, worktreePath, failureFeedback, stepName, reason) {
|
|
62132
62391
|
const taskId = task.id;
|
|
62392
|
+
this.clearCompletedTaskWatchdog(taskId);
|
|
62133
62393
|
await this.store.addTaskComment(
|
|
62134
62394
|
taskId,
|
|
62135
62395
|
`${reason}. The failing workflow step was "${stepName}". Feedback:
|
|
@@ -62151,17 +62411,11 @@ Please fix the issues so the verification can pass on the next attempt.`,
|
|
|
62151
62411
|
sessionFile: null,
|
|
62152
62412
|
workflowStepRetries: 0
|
|
62153
62413
|
});
|
|
62154
|
-
|
|
62155
|
-
|
|
62156
|
-
|
|
62157
|
-
|
|
62158
|
-
|
|
62159
|
-
executorLog.log(`${taskId}: sent back to in-progress for remediation`);
|
|
62160
|
-
} catch (err) {
|
|
62161
|
-
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
62162
|
-
executorLog.error(`${taskId}: failed to move back to in-progress: ${errorMessage}`);
|
|
62163
|
-
}
|
|
62164
|
-
}, 0);
|
|
62414
|
+
this.scheduleWorkflowRerun(
|
|
62415
|
+
taskId,
|
|
62416
|
+
worktreePath,
|
|
62417
|
+
`${taskId}: sent back to in-progress for remediation`
|
|
62418
|
+
);
|
|
62165
62419
|
}
|
|
62166
62420
|
/**
|
|
62167
62421
|
* Inject or update the "Workflow Step Failure" section in PROMPT.md.
|
|
@@ -62712,7 +62966,7 @@ Review the work done in this worktree and evaluate it against the criteria in yo
|
|
|
62712
62966
|
);
|
|
62713
62967
|
}
|
|
62714
62968
|
const delay2 = this.WORKTREE_RETRY_DELAYS[attempt] || 1e3;
|
|
62715
|
-
await new Promise((
|
|
62969
|
+
await new Promise((resolve19) => setTimeout(resolve19, delay2));
|
|
62716
62970
|
}
|
|
62717
62971
|
}
|
|
62718
62972
|
throw new Error("Unexpected exit from worktree creation retry loop");
|
|
@@ -72036,13 +72290,13 @@ var init_plugin_runner = __esm({
|
|
|
72036
72290
|
* Returns the result on success, throws on timeout.
|
|
72037
72291
|
*/
|
|
72038
72292
|
withTimeout(promise, ms, timeoutMessage) {
|
|
72039
|
-
return new Promise((
|
|
72293
|
+
return new Promise((resolve19, reject) => {
|
|
72040
72294
|
const timer = setTimeout(() => {
|
|
72041
72295
|
reject(new Error(timeoutMessage));
|
|
72042
72296
|
}, ms);
|
|
72043
72297
|
promise.then((result) => {
|
|
72044
72298
|
clearTimeout(timer);
|
|
72045
|
-
|
|
72299
|
+
resolve19(result);
|
|
72046
72300
|
}).catch((err) => {
|
|
72047
72301
|
clearTimeout(timer);
|
|
72048
72302
|
reject(err);
|
|
@@ -72711,7 +72965,7 @@ var init_in_process_runtime = __esm({
|
|
|
72711
72965
|
runtimeLog.log(
|
|
72712
72966
|
`Waiting for ${metrics.inFlightTasks} in-flight tasks to complete...`
|
|
72713
72967
|
);
|
|
72714
|
-
await new Promise((
|
|
72968
|
+
await new Promise((resolve19) => setTimeout(resolve19, 1e3));
|
|
72715
72969
|
}
|
|
72716
72970
|
const finalMetrics = this.getMetrics();
|
|
72717
72971
|
if (finalMetrics.inFlightTasks > 0) {
|
|
@@ -73108,13 +73362,13 @@ var init_ipc_host = __esm({
|
|
|
73108
73362
|
}
|
|
73109
73363
|
const id = generateCorrelationId();
|
|
73110
73364
|
const message = { type, id, payload };
|
|
73111
|
-
return new Promise((
|
|
73365
|
+
return new Promise((resolve19, reject) => {
|
|
73112
73366
|
const timeout = setTimeout(() => {
|
|
73113
73367
|
this.pendingCommands.delete(id);
|
|
73114
73368
|
reject(new Error(`Command ${type} timed out after ${timeoutMs ?? this.commandTimeoutMs}ms`));
|
|
73115
73369
|
}, timeoutMs ?? this.commandTimeoutMs);
|
|
73116
73370
|
this.pendingCommands.set(id, {
|
|
73117
|
-
resolve:
|
|
73371
|
+
resolve: resolve19,
|
|
73118
73372
|
reject,
|
|
73119
73373
|
timeout,
|
|
73120
73374
|
type
|
|
@@ -73923,8 +74177,8 @@ var init_remote_node_client = __esm({
|
|
|
73923
74177
|
return error instanceof TypeError;
|
|
73924
74178
|
}
|
|
73925
74179
|
async sleep(ms) {
|
|
73926
|
-
await new Promise((
|
|
73927
|
-
setTimeout(
|
|
74180
|
+
await new Promise((resolve19) => {
|
|
74181
|
+
setTimeout(resolve19, ms);
|
|
73928
74182
|
});
|
|
73929
74183
|
}
|
|
73930
74184
|
};
|
|
@@ -74188,14 +74442,14 @@ var init_remote_node_runtime = __esm({
|
|
|
74188
74442
|
return error instanceof Error ? error : new Error(String(error));
|
|
74189
74443
|
}
|
|
74190
74444
|
async sleep(ms, signal) {
|
|
74191
|
-
await new Promise((
|
|
74445
|
+
await new Promise((resolve19) => {
|
|
74192
74446
|
const timeout = setTimeout(() => {
|
|
74193
74447
|
cleanup();
|
|
74194
|
-
|
|
74448
|
+
resolve19();
|
|
74195
74449
|
}, ms);
|
|
74196
74450
|
const onAbort = () => {
|
|
74197
74451
|
cleanup();
|
|
74198
|
-
|
|
74452
|
+
resolve19();
|
|
74199
74453
|
};
|
|
74200
74454
|
const cleanup = () => {
|
|
74201
74455
|
clearTimeout(timeout);
|
|
@@ -75082,10 +75336,10 @@ var init_tunnel_process_manager = __esm({
|
|
|
75082
75336
|
lastError: null
|
|
75083
75337
|
});
|
|
75084
75338
|
this.emitLog("info", "manager", `Stopping ${currentHandle.provider} tunnel (pid=${currentHandle.child.pid ?? "n/a"})`);
|
|
75085
|
-
this.activeStopPromise = new Promise((
|
|
75339
|
+
this.activeStopPromise = new Promise((resolve19) => {
|
|
75086
75340
|
const onClose = () => {
|
|
75087
75341
|
currentHandle.child.removeListener("close", onClose);
|
|
75088
|
-
|
|
75342
|
+
resolve19();
|
|
75089
75343
|
};
|
|
75090
75344
|
currentHandle.child.once("close", onClose);
|
|
75091
75345
|
killManagedProcess(currentHandle.child, "SIGTERM");
|
|
@@ -75641,12 +75895,12 @@ ${detail}`
|
|
|
75641
75895
|
*/
|
|
75642
75896
|
async onMerge(taskId) {
|
|
75643
75897
|
if (this.mergeActive.has(taskId)) {
|
|
75644
|
-
return new Promise((
|
|
75645
|
-
this.manualMergeResolvers.set(taskId, { resolve:
|
|
75898
|
+
return new Promise((resolve19, reject) => {
|
|
75899
|
+
this.manualMergeResolvers.set(taskId, { resolve: resolve19, reject });
|
|
75646
75900
|
});
|
|
75647
75901
|
}
|
|
75648
|
-
return new Promise((
|
|
75649
|
-
this.manualMergeResolvers.set(taskId, { resolve:
|
|
75902
|
+
return new Promise((resolve19, reject) => {
|
|
75903
|
+
this.manualMergeResolvers.set(taskId, { resolve: resolve19, reject });
|
|
75650
75904
|
this.internalEnqueueMerge(taskId);
|
|
75651
75905
|
});
|
|
75652
75906
|
}
|
|
@@ -79170,7 +79424,7 @@ function buildHermesArgs(prompt, settings, resumeSessionId) {
|
|
|
79170
79424
|
async function invokeHermesCli(prompt, settings, resumeSessionId, signal) {
|
|
79171
79425
|
const args = buildHermesArgs(prompt, settings, resumeSessionId);
|
|
79172
79426
|
const binary = resolveBinaryForSpawn(settings.binaryPath);
|
|
79173
|
-
return new Promise((
|
|
79427
|
+
return new Promise((resolve19, reject) => {
|
|
79174
79428
|
let settled = false;
|
|
79175
79429
|
const spawnEnv = { ...process.env, PYTHONUNBUFFERED: "1" };
|
|
79176
79430
|
if (settings.profile) {
|
|
@@ -79238,7 +79492,7 @@ ${combined}`));
|
|
|
79238
79492
|
return;
|
|
79239
79493
|
}
|
|
79240
79494
|
try {
|
|
79241
|
-
|
|
79495
|
+
resolve19(parseHermesOutput(stdout, stderr));
|
|
79242
79496
|
} catch (parseErr) {
|
|
79243
79497
|
reject(parseErr);
|
|
79244
79498
|
}
|
|
@@ -79464,7 +79718,7 @@ async function promptCli(session, message, config, callbacks, signal) {
|
|
|
79464
79718
|
const args = buildOpenClawArgs(config, session.sessionId, message);
|
|
79465
79719
|
const cb = { ...session.callbacks, ...callbacks };
|
|
79466
79720
|
cb.onToolStart?.("openclaw.agent", { sessionId: session.sessionId });
|
|
79467
|
-
return new Promise((
|
|
79721
|
+
return new Promise((resolve19, reject) => {
|
|
79468
79722
|
let settled = false;
|
|
79469
79723
|
const child = spawn5(config.binaryPath, args, {
|
|
79470
79724
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -79557,7 +79811,7 @@ async function promptCli(session, message, config, callbacks, signal) {
|
|
|
79557
79811
|
...metaError ? { error: metaError } : {},
|
|
79558
79812
|
...errorText.length > 0 ? { toolErrors: errorText } : {}
|
|
79559
79813
|
});
|
|
79560
|
-
|
|
79814
|
+
resolve19();
|
|
79561
79815
|
});
|
|
79562
79816
|
});
|
|
79563
79817
|
}
|
|
@@ -79816,12 +80070,14 @@ var init_rate_limit = __esm({
|
|
|
79816
80070
|
});
|
|
79817
80071
|
|
|
79818
80072
|
// ../dashboard/src/routes/register-chat-routes.ts
|
|
80073
|
+
var CHAT_MAX_ATTACHMENT_SIZE;
|
|
79819
80074
|
var init_register_chat_routes = __esm({
|
|
79820
80075
|
"../dashboard/src/routes/register-chat-routes.ts"() {
|
|
79821
80076
|
"use strict";
|
|
79822
80077
|
init_api_error();
|
|
79823
80078
|
init_rate_limit();
|
|
79824
80079
|
init_sse_buffer();
|
|
80080
|
+
CHAT_MAX_ATTACHMENT_SIZE = 5 * 1024 * 1024;
|
|
79825
80081
|
}
|
|
79826
80082
|
});
|
|
79827
80083
|
|
|
@@ -79866,7 +80122,7 @@ var init_register_messaging_scripts = __esm({
|
|
|
79866
80122
|
|
|
79867
80123
|
// ../dashboard/src/github.ts
|
|
79868
80124
|
function delay(ms) {
|
|
79869
|
-
return new Promise((
|
|
80125
|
+
return new Promise((resolve19) => setTimeout(resolve19, ms));
|
|
79870
80126
|
}
|
|
79871
80127
|
function normalizeCheckState(state) {
|
|
79872
80128
|
switch ((state ?? "").toLowerCase()) {
|
|
@@ -82577,7 +82833,7 @@ async function spawnPaperclipCliJson(args, opts) {
|
|
|
82577
82833
|
}
|
|
82578
82834
|
const timeoutMs = opts.cliTimeoutMs ?? 15e3;
|
|
82579
82835
|
const label = ["paperclipai", ...args].join(" ");
|
|
82580
|
-
return new Promise((
|
|
82836
|
+
return new Promise((resolve19, reject) => {
|
|
82581
82837
|
let child;
|
|
82582
82838
|
try {
|
|
82583
82839
|
child = spawn10(bin, fullArgs, { stdio: ["ignore", "pipe", "pipe"] });
|
|
@@ -82623,7 +82879,7 @@ async function spawnPaperclipCliJson(args, opts) {
|
|
|
82623
82879
|
return;
|
|
82624
82880
|
}
|
|
82625
82881
|
try {
|
|
82626
|
-
|
|
82882
|
+
resolve19(JSON.parse(cleaned));
|
|
82627
82883
|
} catch {
|
|
82628
82884
|
reject(new Error(`${label} returned non-JSON output: ${cleaned.slice(0, 200)}`));
|
|
82629
82885
|
}
|
|
@@ -82693,7 +82949,7 @@ var init_paperclip_client = __esm({
|
|
|
82693
82949
|
// ../../plugins/fusion-plugin-paperclip-runtime/dist/runtime-adapter.js
|
|
82694
82950
|
import { randomUUID as randomUUID13 } from "node:crypto";
|
|
82695
82951
|
function sleep3(ms) {
|
|
82696
|
-
return new Promise((
|
|
82952
|
+
return new Promise((resolve19) => setTimeout(resolve19, ms));
|
|
82697
82953
|
}
|
|
82698
82954
|
function asString2(value) {
|
|
82699
82955
|
return typeof value === "string" ? value : void 0;
|
|
@@ -83102,22 +83358,29 @@ var init_update_check = __esm({
|
|
|
83102
83358
|
});
|
|
83103
83359
|
|
|
83104
83360
|
// ../dashboard/src/routes/register-update-check-routes.ts
|
|
83105
|
-
import { readFileSync as readFileSync7 } from "node:fs";
|
|
83106
|
-
import { dirname as dirname9,
|
|
83361
|
+
import { existsSync as existsSync28, readFileSync as readFileSync7 } from "node:fs";
|
|
83362
|
+
import { dirname as dirname9, resolve as resolve15 } from "node:path";
|
|
83107
83363
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
83108
|
-
var
|
|
83364
|
+
var CLI_PACKAGE_VERSION;
|
|
83109
83365
|
var init_register_update_check_routes = __esm({
|
|
83110
83366
|
"../dashboard/src/routes/register-update-check-routes.ts"() {
|
|
83111
83367
|
"use strict";
|
|
83112
83368
|
init_src();
|
|
83113
83369
|
init_update_check();
|
|
83114
|
-
__dirname = dirname9(fileURLToPath3(import.meta.url));
|
|
83115
83370
|
CLI_PACKAGE_VERSION = (() => {
|
|
83116
83371
|
try {
|
|
83117
|
-
|
|
83118
|
-
|
|
83119
|
-
|
|
83120
|
-
|
|
83372
|
+
let cur = dirname9(fileURLToPath3(import.meta.url));
|
|
83373
|
+
for (let i = 0; i < 8; i++) {
|
|
83374
|
+
const pkgPath = resolve15(cur, "package.json");
|
|
83375
|
+
if (existsSync28(pkgPath)) {
|
|
83376
|
+
const parsed = JSON.parse(readFileSync7(pkgPath, "utf-8"));
|
|
83377
|
+
if (parsed.name === "@runfusion/fusion" && typeof parsed.version === "string" && parsed.version.length > 0) {
|
|
83378
|
+
return parsed.version;
|
|
83379
|
+
}
|
|
83380
|
+
}
|
|
83381
|
+
const parent = resolve15(cur, "..");
|
|
83382
|
+
if (parent === cur) break;
|
|
83383
|
+
cur = parent;
|
|
83121
83384
|
}
|
|
83122
83385
|
} catch {
|
|
83123
83386
|
}
|
|
@@ -87149,8 +87412,8 @@ var init_auth_middleware = __esm({
|
|
|
87149
87412
|
|
|
87150
87413
|
// ../dashboard/src/server.ts
|
|
87151
87414
|
import express from "express";
|
|
87152
|
-
import { join as
|
|
87153
|
-
import { existsSync as
|
|
87415
|
+
import { join as join35, dirname as dirname10, resolve as resolve16 } from "node:path";
|
|
87416
|
+
import { existsSync as existsSync29, readFileSync as readFileSync8 } from "node:fs";
|
|
87154
87417
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
87155
87418
|
function clearAiSessionCleanupInterval() {
|
|
87156
87419
|
if (!aiSessionCleanupIntervalHandle) {
|
|
@@ -87159,7 +87422,7 @@ function clearAiSessionCleanupInterval() {
|
|
|
87159
87422
|
clearInterval(aiSessionCleanupIntervalHandle);
|
|
87160
87423
|
aiSessionCleanupIntervalHandle = void 0;
|
|
87161
87424
|
}
|
|
87162
|
-
var
|
|
87425
|
+
var __dirname, PACKAGE_VERSION, CLI_PACKAGE_VERSION2, MIN_AI_SESSION_TTL_MS, MAX_AI_SESSION_TTL_MS, MIN_AI_SESSION_CLEANUP_INTERVAL_MS, MAX_AI_SESSION_CLEANUP_INTERVAL_MS, aiSessionCleanupIntervalHandle;
|
|
87163
87426
|
var init_server = __esm({
|
|
87164
87427
|
"../dashboard/src/server.ts"() {
|
|
87165
87428
|
"use strict";
|
|
@@ -87185,10 +87448,10 @@ var init_server = __esm({
|
|
|
87185
87448
|
init_dev_server_routes();
|
|
87186
87449
|
init_auth_middleware();
|
|
87187
87450
|
init_remote_auth();
|
|
87188
|
-
|
|
87451
|
+
__dirname = dirname10(fileURLToPath4(import.meta.url));
|
|
87189
87452
|
PACKAGE_VERSION = (() => {
|
|
87190
87453
|
try {
|
|
87191
|
-
const packageJsonPath =
|
|
87454
|
+
const packageJsonPath = join35(__dirname, "..", "package.json");
|
|
87192
87455
|
const packageJson = JSON.parse(readFileSync8(packageJsonPath, "utf-8"));
|
|
87193
87456
|
if (typeof packageJson.version === "string" && packageJson.version.length > 0) {
|
|
87194
87457
|
return packageJson.version;
|
|
@@ -87199,10 +87462,18 @@ var init_server = __esm({
|
|
|
87199
87462
|
})();
|
|
87200
87463
|
CLI_PACKAGE_VERSION2 = (() => {
|
|
87201
87464
|
try {
|
|
87202
|
-
|
|
87203
|
-
|
|
87204
|
-
|
|
87205
|
-
|
|
87465
|
+
let cur = __dirname;
|
|
87466
|
+
for (let i = 0; i < 8; i++) {
|
|
87467
|
+
const pkgPath = resolve16(cur, "package.json");
|
|
87468
|
+
if (existsSync29(pkgPath)) {
|
|
87469
|
+
const parsed = JSON.parse(readFileSync8(pkgPath, "utf-8"));
|
|
87470
|
+
if (parsed.name === "@runfusion/fusion" && typeof parsed.version === "string" && parsed.version.length > 0) {
|
|
87471
|
+
return parsed.version;
|
|
87472
|
+
}
|
|
87473
|
+
}
|
|
87474
|
+
const parent = resolve16(cur, "..");
|
|
87475
|
+
if (parent === cur) break;
|
|
87476
|
+
cur = parent;
|
|
87206
87477
|
}
|
|
87207
87478
|
} catch {
|
|
87208
87479
|
}
|
|
@@ -87278,8 +87549,8 @@ var init_src4 = __esm({
|
|
|
87278
87549
|
});
|
|
87279
87550
|
|
|
87280
87551
|
// src/project-context.ts
|
|
87281
|
-
import { resolve as
|
|
87282
|
-
import { existsSync as
|
|
87552
|
+
import { resolve as resolve17, dirname as dirname11 } from "node:path";
|
|
87553
|
+
import { existsSync as existsSync30 } from "node:fs";
|
|
87283
87554
|
async function resolveProject(projectNameFlag, cwd = process.cwd(), globalDir) {
|
|
87284
87555
|
const central = new CentralCore(globalDir);
|
|
87285
87556
|
await central.init();
|
|
@@ -87355,10 +87626,10 @@ async function clearDefaultProject(globalDir) {
|
|
|
87355
87626
|
await globalStore.updateSettings(rest);
|
|
87356
87627
|
}
|
|
87357
87628
|
async function detectProjectFromCwd(cwd, central) {
|
|
87358
|
-
let currentDir =
|
|
87629
|
+
let currentDir = resolve17(cwd);
|
|
87359
87630
|
while (true) {
|
|
87360
|
-
const kbPath =
|
|
87361
|
-
if (
|
|
87631
|
+
const kbPath = resolve17(currentDir, ".fusion", "fusion.db");
|
|
87632
|
+
if (existsSync30(kbPath)) {
|
|
87362
87633
|
const project = await central.getProjectByPath(currentDir);
|
|
87363
87634
|
if (project) {
|
|
87364
87635
|
return project;
|
|
@@ -87458,8 +87729,8 @@ __export(task_exports, {
|
|
|
87458
87729
|
runTaskUpdate: () => runTaskUpdate
|
|
87459
87730
|
});
|
|
87460
87731
|
import { createInterface as createInterface2 } from "node:readline/promises";
|
|
87461
|
-
import { watchFile, unwatchFile, statSync as statSync6, existsSync as
|
|
87462
|
-
import { join as
|
|
87732
|
+
import { watchFile, unwatchFile, statSync as statSync6, existsSync as existsSync31, readFileSync as readFileSync9 } from "node:fs";
|
|
87733
|
+
import { join as join36 } from "node:path";
|
|
87463
87734
|
function asLocalProjectContext(store) {
|
|
87464
87735
|
const cwd = process.cwd();
|
|
87465
87736
|
return {
|
|
@@ -87579,9 +87850,9 @@ async function runTaskCreate(descriptionArg, attachFiles, depends, projectName,
|
|
|
87579
87850
|
console.log(` Path: .fusion/tasks/${task.id}/`);
|
|
87580
87851
|
if (attachFiles && attachFiles.length > 0) {
|
|
87581
87852
|
const { readFile: readFile19 } = await import("node:fs/promises");
|
|
87582
|
-
const { basename: basename9, extname: extname3, resolve:
|
|
87853
|
+
const { basename: basename9, extname: extname3, resolve: resolve19 } = await import("node:path");
|
|
87583
87854
|
for (const filePath of attachFiles) {
|
|
87584
|
-
const resolvedPath =
|
|
87855
|
+
const resolvedPath = resolve19(filePath);
|
|
87585
87856
|
const filename = basename9(resolvedPath);
|
|
87586
87857
|
const ext = extname3(filename).toLowerCase();
|
|
87587
87858
|
const mimeType = MIME_TYPES[ext];
|
|
@@ -87715,8 +87986,8 @@ async function runTaskLogs(id, options = {}, projectName) {
|
|
|
87715
87986
|
printEntries(filteredEntries);
|
|
87716
87987
|
if (options.follow) {
|
|
87717
87988
|
const projectPath = projectContext?.projectPath ?? process.cwd();
|
|
87718
|
-
const logPath =
|
|
87719
|
-
if (!
|
|
87989
|
+
const logPath = join36(projectPath, ".fusion", "tasks", id, "agent.log");
|
|
87990
|
+
if (!existsSync31(logPath)) {
|
|
87720
87991
|
console.log(`
|
|
87721
87992
|
Waiting for log file to be created...`);
|
|
87722
87993
|
}
|
|
@@ -87875,8 +88146,8 @@ async function runTaskMerge(id, projectName) {
|
|
|
87875
88146
|
async function runTaskAttach(id, filePath, projectName) {
|
|
87876
88147
|
const { readFile: readFile19 } = await import("node:fs/promises");
|
|
87877
88148
|
const { basename: basename9, extname: extname3 } = await import("node:path");
|
|
87878
|
-
const { resolve:
|
|
87879
|
-
const resolvedPath =
|
|
88149
|
+
const { resolve: resolve19 } = await import("node:path");
|
|
88150
|
+
const resolvedPath = resolve19(filePath);
|
|
87880
88151
|
const filename = basename9(resolvedPath);
|
|
87881
88152
|
const ext = extname3(filename).toLowerCase();
|
|
87882
88153
|
const mimeType = MIME_TYPES[ext];
|
|
@@ -88407,12 +88678,12 @@ async function promptText(question) {
|
|
|
88407
88678
|
console.log(" (Enter your response. Type DONE on its own line when finished):\n");
|
|
88408
88679
|
const rl = createInterface2({ input: process.stdin, output: process.stdout });
|
|
88409
88680
|
const lines = [];
|
|
88410
|
-
return new Promise((
|
|
88681
|
+
return new Promise((resolve19) => {
|
|
88411
88682
|
const askLine = () => {
|
|
88412
88683
|
rl.question(" ").then((line) => {
|
|
88413
88684
|
if (line.trim() === "DONE") {
|
|
88414
88685
|
rl.close();
|
|
88415
|
-
|
|
88686
|
+
resolve19(lines.join("\n"));
|
|
88416
88687
|
} else {
|
|
88417
88688
|
lines.push(line);
|
|
88418
88689
|
askLine();
|
|
@@ -88816,9 +89087,9 @@ async function runSkillsInstall(args, options) {
|
|
|
88816
89087
|
stdio: "inherit",
|
|
88817
89088
|
shell: true
|
|
88818
89089
|
});
|
|
88819
|
-
const exitCode = await new Promise((
|
|
89090
|
+
const exitCode = await new Promise((resolve19, reject) => {
|
|
88820
89091
|
child.on("exit", (code) => {
|
|
88821
|
-
|
|
89092
|
+
resolve19(code ?? 1);
|
|
88822
89093
|
});
|
|
88823
89094
|
child.on("error", (err) => {
|
|
88824
89095
|
reject(err);
|
|
@@ -88845,9 +89116,9 @@ init_src();
|
|
|
88845
89116
|
init_gh_cli();
|
|
88846
89117
|
import { Type as Type7 } from "typebox";
|
|
88847
89118
|
import { StringEnum } from "@mariozechner/pi-ai";
|
|
88848
|
-
import { resolve as
|
|
89119
|
+
import { resolve as resolve18, basename as basename8, extname as extname2, join as join37 } from "node:path";
|
|
88849
89120
|
import { readFile as readFile18 } from "node:fs/promises";
|
|
88850
|
-
import { existsSync as
|
|
89121
|
+
import { existsSync as existsSync32 } from "node:fs";
|
|
88851
89122
|
import { spawn as spawn9 } from "node:child_process";
|
|
88852
89123
|
var MIME_TYPES2 = {
|
|
88853
89124
|
".png": "image/png",
|
|
@@ -88865,14 +89136,14 @@ var MIME_TYPES2 = {
|
|
|
88865
89136
|
".xml": "application/xml"
|
|
88866
89137
|
};
|
|
88867
89138
|
function resolveProjectRoot(cwd) {
|
|
88868
|
-
let current =
|
|
89139
|
+
let current = resolve18(cwd);
|
|
88869
89140
|
while (true) {
|
|
88870
|
-
if (
|
|
89141
|
+
if (existsSync32(join37(current, ".fusion"))) {
|
|
88871
89142
|
return current;
|
|
88872
89143
|
}
|
|
88873
|
-
const parent =
|
|
89144
|
+
const parent = resolve18(current, "..");
|
|
88874
89145
|
if (parent === current) {
|
|
88875
|
-
return
|
|
89146
|
+
return resolve18(cwd);
|
|
88876
89147
|
}
|
|
88877
89148
|
current = parent;
|
|
88878
89149
|
}
|
|
@@ -88888,7 +89159,20 @@ async function getStore2(cwd) {
|
|
|
88888
89159
|
return store;
|
|
88889
89160
|
}
|
|
88890
89161
|
function getFusionDir(cwd) {
|
|
88891
|
-
return
|
|
89162
|
+
return join37(resolveProjectRoot(cwd), ".fusion");
|
|
89163
|
+
}
|
|
89164
|
+
async function validateAssignableAgentId(cwd, agentId) {
|
|
89165
|
+
const { AgentStore: AgentStore2, isEphemeralAgent: isEphemeralAgent2 } = await Promise.resolve().then(() => (init_src(), src_exports));
|
|
89166
|
+
const agentStore = new AgentStore2({ rootDir: getFusionDir(cwd) });
|
|
89167
|
+
await agentStore.init();
|
|
89168
|
+
const agent = await agentStore.getAgent(agentId);
|
|
89169
|
+
if (!agent) {
|
|
89170
|
+
return `Agent ${agentId} not found`;
|
|
89171
|
+
}
|
|
89172
|
+
if (isEphemeralAgent2(agent)) {
|
|
89173
|
+
return `Cannot assign task to ephemeral/runtime agent ${agentId}`;
|
|
89174
|
+
}
|
|
89175
|
+
return null;
|
|
88892
89176
|
}
|
|
88893
89177
|
function formatTaskLine(t) {
|
|
88894
89178
|
const label = t.title || t.description.slice(0, 60) + (t.description.length > 60 ? "\u2026" : "");
|
|
@@ -88951,6 +89235,16 @@ function kbExtension(pi) {
|
|
|
88951
89235
|
}),
|
|
88952
89236
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
88953
89237
|
const store = await getStore2(ctx.cwd);
|
|
89238
|
+
if (params.agentId !== void 0) {
|
|
89239
|
+
const error = await validateAssignableAgentId(ctx.cwd, params.agentId);
|
|
89240
|
+
if (error) {
|
|
89241
|
+
return {
|
|
89242
|
+
content: [{ type: "text", text: error }],
|
|
89243
|
+
isError: true,
|
|
89244
|
+
details: { error }
|
|
89245
|
+
};
|
|
89246
|
+
}
|
|
89247
|
+
}
|
|
88954
89248
|
const task = await store.createTask({
|
|
88955
89249
|
description: params.description.trim(),
|
|
88956
89250
|
dependencies: params.depends,
|
|
@@ -89036,6 +89330,16 @@ Column: triage
|
|
|
89036
89330
|
updatedFields.push("dependencies");
|
|
89037
89331
|
}
|
|
89038
89332
|
if (params.agentId !== void 0) {
|
|
89333
|
+
if (params.agentId !== null) {
|
|
89334
|
+
const error = await validateAssignableAgentId(ctx.cwd, params.agentId);
|
|
89335
|
+
if (error) {
|
|
89336
|
+
return {
|
|
89337
|
+
content: [{ type: "text", text: error }],
|
|
89338
|
+
isError: true,
|
|
89339
|
+
details: { error }
|
|
89340
|
+
};
|
|
89341
|
+
}
|
|
89342
|
+
}
|
|
89039
89343
|
updates.assignedAgentId = params.agentId;
|
|
89040
89344
|
updatedFields.push("agentId");
|
|
89041
89345
|
}
|
|
@@ -89183,7 +89487,7 @@ Column: triage
|
|
|
89183
89487
|
path: Type7.String({ description: "Path to the file to attach" })
|
|
89184
89488
|
}),
|
|
89185
89489
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
89186
|
-
const filePath =
|
|
89490
|
+
const filePath = resolve18(ctx.cwd, params.path.replace(/^@/, ""));
|
|
89187
89491
|
const filename = basename8(filePath);
|
|
89188
89492
|
const ext = extname2(filename).toLowerCase();
|
|
89189
89493
|
const mimeType = MIME_TYPES2[ext];
|
|
@@ -90292,12 +90596,12 @@ Status: ${updated.status}`
|
|
|
90292
90596
|
child.stderr?.on("data", (data) => {
|
|
90293
90597
|
stderr += data.toString();
|
|
90294
90598
|
});
|
|
90295
|
-
const exitCode = await new Promise((
|
|
90599
|
+
const exitCode = await new Promise((resolve19) => {
|
|
90296
90600
|
child.on("exit", (code) => {
|
|
90297
|
-
|
|
90601
|
+
resolve19(code ?? 1);
|
|
90298
90602
|
});
|
|
90299
90603
|
child.on("error", () => {
|
|
90300
|
-
|
|
90604
|
+
resolve19(1);
|
|
90301
90605
|
});
|
|
90302
90606
|
});
|
|
90303
90607
|
try {
|