@runfusion/fusion 0.17.2 → 0.18.1
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 +1035 -660
- package/dist/client/assets/ChatView-3Sqm6teN.js +1 -0
- package/dist/client/assets/{DevServerView-GFFVXHVP.js → DevServerView-r6V3FqkY.js} +1 -1
- package/dist/client/assets/DirectoryPicker-CTZE95Fk.js +1 -0
- package/dist/client/assets/DocumentsView-DSEf1Lmg.js +1 -0
- package/dist/client/assets/{InsightsView-Bxu0TJkt.js → InsightsView-F5PZsX5u.js} +2 -2
- package/dist/client/assets/MemoryView-DicXjec9.js +2 -0
- package/dist/client/assets/NodesView-DddCS7zB.js +14 -0
- package/dist/client/assets/NodesView-sJgPLTzz.css +1 -0
- package/dist/client/assets/{PiExtensionsManager-4e3MlD62.js → PiExtensionsManager-Ch7si-v8.js} +3 -3
- package/dist/client/assets/PluginManager-LcTh_fHP.js +1 -0
- package/dist/client/assets/ResearchView-D0TY1VcX.js +1 -0
- package/dist/client/assets/{RoadmapsView-jHTOK0RQ.js → RoadmapsView-DfEF3mql.js} +2 -2
- package/dist/client/assets/SettingsModal-BNSrO1M9.css +1 -0
- package/dist/client/assets/SettingsModal-SOADcCNJ.js +31 -0
- package/dist/client/assets/{SettingsModal-4Z8ZJMzD.js → SettingsModal-YcScdFiG.js} +1 -1
- package/dist/client/assets/SettingsModal-oOnIed5O.css +1 -0
- package/dist/client/assets/SetupWizardModal-EDYuf9Yc.js +1 -0
- package/dist/client/assets/SkillsView-Dkq2CQla.js +1 -0
- package/dist/client/assets/index-4hC8zoTD.css +1 -0
- package/dist/client/assets/index-DNzA4aZ7.js +1229 -0
- package/dist/client/assets/{users-D3u6f2Rz.js → users-Cp5TSxVm.js} +2 -2
- package/dist/client/index.html +2 -2
- package/dist/client/version.json +1 -1
- package/dist/droid-cli/index.ts +12 -7
- package/dist/droid-cli/package.json +4 -1
- package/dist/droid-cli/src/__tests__/provider.test.ts +42 -6
- package/dist/extension.js +505 -70
- package/dist/pi-claude-cli/package.json +1 -1
- package/dist/plugins/fusion-plugin-dependency-graph/package.json +1 -1
- package/package.json +2 -1
- package/skill/fusion/SKILL.md +2 -2
- package/skill/fusion/references/best-practices.md +33 -0
- package/skill/fusion/references/extension-tools.md +47 -2
- package/skill/fusion/references/fusion-capabilities.md +7 -2
- package/dist/client/assets/AgentDetailView-17J-F0Rl.js +0 -18
- package/dist/client/assets/AgentDetailView-yu8Xltqk.css +0 -1
- package/dist/client/assets/AgentsView-Bs03ptrd.css +0 -1
- package/dist/client/assets/AgentsView-sbBkb7Wd.js +0 -517
- package/dist/client/assets/ChatView-BR5cvK_B.js +0 -1
- package/dist/client/assets/DirectoryPicker-WPDSBdT6.js +0 -1
- package/dist/client/assets/DocumentsView-BHpDsIIt.js +0 -1
- package/dist/client/assets/MemoryView-CmnzZorw.js +0 -2
- package/dist/client/assets/NodesView-CO9_4hCr.js +0 -14
- package/dist/client/assets/NodesView-DuAXX_0j.css +0 -1
- package/dist/client/assets/PluginManager-DGN2rvOY.js +0 -1
- package/dist/client/assets/ResearchView-Dsa6Gykl.js +0 -1
- package/dist/client/assets/SettingsModal-D0kuJpBA.js +0 -31
- package/dist/client/assets/SettingsModal-D_AFkDJa.css +0 -1
- package/dist/client/assets/SettingsModal-Dq4a5KSX.css +0 -1
- package/dist/client/assets/SetupWizardModal-Bhumd4Rf.js +0 -1
- package/dist/client/assets/SkillsView-MHweJTz4.js +0 -1
- package/dist/client/assets/folder-open-BNQW9dE9.js +0 -6
- package/dist/client/assets/index-DEVBHvyW.css +0 -1
- package/dist/client/assets/index-k_85J1DS.js +0 -682
- package/dist/client/assets/star-7L86NZrT.js +0 -6
- package/dist/client/assets/upload-DsAS6tno.js +0 -6
package/dist/bin.js
CHANGED
|
@@ -2773,7 +2773,7 @@ var init_db = __esm({
|
|
|
2773
2773
|
"use strict";
|
|
2774
2774
|
init_sqlite_adapter();
|
|
2775
2775
|
init_types();
|
|
2776
|
-
SCHEMA_VERSION =
|
|
2776
|
+
SCHEMA_VERSION = 60;
|
|
2777
2777
|
SCHEMA_SQL = `
|
|
2778
2778
|
-- Tasks table with JSON columns for nested data
|
|
2779
2779
|
CREATE TABLE IF NOT EXISTS tasks (
|
|
@@ -2841,6 +2841,7 @@ CREATE TABLE IF NOT EXISTS tasks (
|
|
|
2841
2841
|
missionId TEXT,
|
|
2842
2842
|
sliceId TEXT,
|
|
2843
2843
|
assignedAgentId TEXT,
|
|
2844
|
+
pausedByAgentId TEXT,
|
|
2844
2845
|
assigneeUserId TEXT,
|
|
2845
2846
|
sourceType TEXT,
|
|
2846
2847
|
sourceAgentId TEXT,
|
|
@@ -4638,6 +4639,12 @@ This means a caller passed a .fusion directory where a project root was expected
|
|
|
4638
4639
|
this.db.exec(`CREATE INDEX IF NOT EXISTS idxInsightRunEventsRunIdSeq ON project_insight_run_events(runId, seq)`);
|
|
4639
4640
|
});
|
|
4640
4641
|
}
|
|
4642
|
+
if (version < 60) {
|
|
4643
|
+
this.applyMigration(60, () => {
|
|
4644
|
+
this.addColumnIfMissing("tasks", "pausedByAgentId", "TEXT");
|
|
4645
|
+
this.db.exec(`CREATE INDEX IF NOT EXISTS idxTasksPausedByAgentId ON tasks(pausedByAgentId)`);
|
|
4646
|
+
});
|
|
4647
|
+
}
|
|
4641
4648
|
}
|
|
4642
4649
|
/**
|
|
4643
4650
|
* Run a single migration step inside a transaction and bump the version.
|
|
@@ -5014,27 +5021,27 @@ var init_agent_store = __esm({
|
|
|
5014
5021
|
}
|
|
5015
5022
|
try {
|
|
5016
5023
|
const content = await readFile(join3(runDir, file), "utf-8");
|
|
5017
|
-
const
|
|
5018
|
-
if (typeof
|
|
5024
|
+
const run2 = JSON.parse(content);
|
|
5025
|
+
if (typeof run2.id !== "string" || typeof run2.startedAt !== "string" || !["active", "completed", "terminated", "failed"].includes(String(run2.status))) {
|
|
5019
5026
|
continue;
|
|
5020
5027
|
}
|
|
5021
5028
|
const normalizedRun = {
|
|
5022
|
-
id:
|
|
5023
|
-
agentId: typeof
|
|
5024
|
-
startedAt:
|
|
5025
|
-
endedAt: typeof
|
|
5026
|
-
status:
|
|
5027
|
-
invocationSource:
|
|
5028
|
-
triggerDetail:
|
|
5029
|
-
processPid:
|
|
5030
|
-
exitCode:
|
|
5031
|
-
sessionIdBefore:
|
|
5032
|
-
sessionIdAfter:
|
|
5033
|
-
usageJson:
|
|
5034
|
-
resultJson:
|
|
5035
|
-
contextSnapshot:
|
|
5036
|
-
stdoutExcerpt:
|
|
5037
|
-
stderrExcerpt:
|
|
5029
|
+
id: run2.id,
|
|
5030
|
+
agentId: typeof run2.agentId === "string" ? run2.agentId : agentId,
|
|
5031
|
+
startedAt: run2.startedAt,
|
|
5032
|
+
endedAt: typeof run2.endedAt === "string" ? run2.endedAt : null,
|
|
5033
|
+
status: run2.status,
|
|
5034
|
+
invocationSource: run2.invocationSource,
|
|
5035
|
+
triggerDetail: run2.triggerDetail,
|
|
5036
|
+
processPid: run2.processPid,
|
|
5037
|
+
exitCode: run2.exitCode,
|
|
5038
|
+
sessionIdBefore: run2.sessionIdBefore,
|
|
5039
|
+
sessionIdAfter: run2.sessionIdAfter,
|
|
5040
|
+
usageJson: run2.usageJson,
|
|
5041
|
+
resultJson: run2.resultJson,
|
|
5042
|
+
contextSnapshot: run2.contextSnapshot,
|
|
5043
|
+
stdoutExcerpt: run2.stdoutExcerpt,
|
|
5044
|
+
stderrExcerpt: run2.stderrExcerpt
|
|
5038
5045
|
};
|
|
5039
5046
|
const result = this.db.prepare(`
|
|
5040
5047
|
INSERT OR IGNORE INTO agentRuns (id, agentId, data, startedAt, endedAt, status)
|
|
@@ -5962,16 +5969,16 @@ var init_agent_store = __esm({
|
|
|
5962
5969
|
async startHeartbeatRun(agentId) {
|
|
5963
5970
|
const runId = `run-${randomUUID().slice(0, 8)}`;
|
|
5964
5971
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
5965
|
-
const
|
|
5972
|
+
const run2 = {
|
|
5966
5973
|
id: runId,
|
|
5967
5974
|
agentId,
|
|
5968
5975
|
startedAt: now,
|
|
5969
5976
|
endedAt: null,
|
|
5970
5977
|
status: "active"
|
|
5971
5978
|
};
|
|
5972
|
-
await this.saveRun(
|
|
5979
|
+
await this.saveRun(run2);
|
|
5973
5980
|
await this.recordHeartbeat(agentId, "ok", runId);
|
|
5974
|
-
return
|
|
5981
|
+
return run2;
|
|
5975
5982
|
}
|
|
5976
5983
|
/**
|
|
5977
5984
|
* End a heartbeat run.
|
|
@@ -6010,7 +6017,7 @@ var init_agent_store = __esm({
|
|
|
6010
6017
|
*/
|
|
6011
6018
|
async getActiveHeartbeatRun(agentId) {
|
|
6012
6019
|
const recentRuns = await this.getRecentRuns(agentId, 50);
|
|
6013
|
-
return recentRuns.find((
|
|
6020
|
+
return recentRuns.find((run2) => run2.status === "active") ?? null;
|
|
6014
6021
|
}
|
|
6015
6022
|
/**
|
|
6016
6023
|
* Get all completed heartbeat runs for an agent.
|
|
@@ -6022,7 +6029,7 @@ var init_agent_store = __esm({
|
|
|
6022
6029
|
*/
|
|
6023
6030
|
async getCompletedHeartbeatRuns(agentId) {
|
|
6024
6031
|
const recentRuns = await this.getRecentRuns(agentId, 50);
|
|
6025
|
-
return recentRuns.filter((
|
|
6032
|
+
return recentRuns.filter((run2) => run2.status !== "active").sort((a, b) => new Date(b.startedAt).getTime() - new Date(a.startedAt).getTime());
|
|
6026
6033
|
}
|
|
6027
6034
|
// ─────────────────────────────────────────────────────────────────────────
|
|
6028
6035
|
// Task Session Management
|
|
@@ -6165,7 +6172,7 @@ var init_agent_store = __esm({
|
|
|
6165
6172
|
* Save a rich heartbeat run record (structured JSON, not JSONL events).
|
|
6166
6173
|
* @param run - The heartbeat run data
|
|
6167
6174
|
*/
|
|
6168
|
-
async saveRun(
|
|
6175
|
+
async saveRun(run2) {
|
|
6169
6176
|
this.db.prepare(`
|
|
6170
6177
|
INSERT INTO agentRuns (id, agentId, data, startedAt, endedAt, status)
|
|
6171
6178
|
VALUES (?, ?, ?, ?, ?, ?)
|
|
@@ -6175,7 +6182,7 @@ var init_agent_store = __esm({
|
|
|
6175
6182
|
startedAt = excluded.startedAt,
|
|
6176
6183
|
endedAt = excluded.endedAt,
|
|
6177
6184
|
status = excluded.status
|
|
6178
|
-
`).run(
|
|
6185
|
+
`).run(run2.id, run2.agentId, JSON.stringify(run2), run2.startedAt, run2.endedAt, run2.status);
|
|
6179
6186
|
this.db.bumpLastModified();
|
|
6180
6187
|
}
|
|
6181
6188
|
/**
|
|
@@ -6203,7 +6210,7 @@ var init_agent_store = __esm({
|
|
|
6203
6210
|
ORDER BY startedAt DESC
|
|
6204
6211
|
LIMIT ?
|
|
6205
6212
|
`).all(agentId, limit);
|
|
6206
|
-
return rows.map((row) => this.parseJson(row.data, null)).filter((
|
|
6213
|
+
return rows.map((row) => this.parseJson(row.data, null)).filter((run2) => run2 !== null);
|
|
6207
6214
|
}
|
|
6208
6215
|
// ─────────────────────────────────────────────────────────────────────────
|
|
6209
6216
|
// Run-scoped log storage (JSONL files alongside run JSON in agentsDir)
|
|
@@ -9485,7 +9492,7 @@ var init_mission_store = __esm({
|
|
|
9485
9492
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
9486
9493
|
const id = this.generateValidatorRunId();
|
|
9487
9494
|
const newValidatorAttemptCount = (feature.validatorAttemptCount ?? 0) + 1;
|
|
9488
|
-
const
|
|
9495
|
+
const run2 = {
|
|
9489
9496
|
id,
|
|
9490
9497
|
featureId,
|
|
9491
9498
|
milestoneId: milestone.id,
|
|
@@ -9504,28 +9511,28 @@ var init_mission_store = __esm({
|
|
|
9504
9511
|
INSERT INTO mission_validator_runs (id, featureId, milestoneId, sliceId, status, triggerType, implementationAttempt, validatorAttempt, taskId, startedAt, createdAt, updatedAt)
|
|
9505
9512
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
9506
9513
|
`).run(
|
|
9507
|
-
|
|
9508
|
-
|
|
9509
|
-
|
|
9510
|
-
|
|
9511
|
-
|
|
9512
|
-
|
|
9513
|
-
|
|
9514
|
-
|
|
9515
|
-
|
|
9516
|
-
|
|
9517
|
-
|
|
9518
|
-
|
|
9514
|
+
run2.id,
|
|
9515
|
+
run2.featureId,
|
|
9516
|
+
run2.milestoneId,
|
|
9517
|
+
run2.sliceId,
|
|
9518
|
+
run2.status,
|
|
9519
|
+
run2.triggerType ?? "auto",
|
|
9520
|
+
run2.implementationAttempt,
|
|
9521
|
+
run2.validatorAttempt,
|
|
9522
|
+
run2.taskId ?? null,
|
|
9523
|
+
run2.startedAt,
|
|
9524
|
+
run2.createdAt,
|
|
9525
|
+
run2.updatedAt
|
|
9519
9526
|
);
|
|
9520
9527
|
this.updateFeature(featureId, {
|
|
9521
9528
|
validatorAttemptCount: newValidatorAttemptCount,
|
|
9522
|
-
lastValidatorRunId:
|
|
9529
|
+
lastValidatorRunId: run2.id,
|
|
9523
9530
|
loopState: "validating"
|
|
9524
9531
|
});
|
|
9525
9532
|
});
|
|
9526
9533
|
this.db.bumpLastModified();
|
|
9527
|
-
this.emit("validator-run:started",
|
|
9528
|
-
return
|
|
9534
|
+
this.emit("validator-run:started", run2);
|
|
9535
|
+
return run2;
|
|
9529
9536
|
}
|
|
9530
9537
|
/**
|
|
9531
9538
|
* Complete a validator run with the given result.
|
|
@@ -9545,16 +9552,16 @@ var init_mission_store = __esm({
|
|
|
9545
9552
|
* @throws Error if run not found
|
|
9546
9553
|
*/
|
|
9547
9554
|
completeValidatorRun(runId, result, summary, blockedReason) {
|
|
9548
|
-
const
|
|
9549
|
-
if (!
|
|
9555
|
+
const run2 = this.getValidatorRun(runId);
|
|
9556
|
+
if (!run2) {
|
|
9550
9557
|
throw new Error(`Validator run ${runId} not found`);
|
|
9551
9558
|
}
|
|
9552
|
-
if (
|
|
9559
|
+
if (run2.status !== "running") {
|
|
9553
9560
|
throw new Error(`Validator run ${runId} is not in 'running' status`);
|
|
9554
9561
|
}
|
|
9555
9562
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
9556
9563
|
const completedAt = now;
|
|
9557
|
-
const startedAtMs = new Date(
|
|
9564
|
+
const startedAtMs = new Date(run2.startedAt).getTime();
|
|
9558
9565
|
const completedAtMs = new Date(completedAt).getTime();
|
|
9559
9566
|
const durationMs = Math.max(0, completedAtMs - startedAtMs);
|
|
9560
9567
|
let featureLoopState;
|
|
@@ -9594,7 +9601,7 @@ var init_mission_store = __esm({
|
|
|
9594
9601
|
now,
|
|
9595
9602
|
runId
|
|
9596
9603
|
);
|
|
9597
|
-
this.updateFeature(
|
|
9604
|
+
this.updateFeature(run2.featureId, {
|
|
9598
9605
|
loopState: featureLoopState,
|
|
9599
9606
|
lastValidatorStatus: featureLastValidatorStatus
|
|
9600
9607
|
});
|
|
@@ -9625,8 +9632,8 @@ var init_mission_store = __esm({
|
|
|
9625
9632
|
* @returns The created failure records
|
|
9626
9633
|
*/
|
|
9627
9634
|
recordValidatorFailures(runId, failures) {
|
|
9628
|
-
const
|
|
9629
|
-
if (!
|
|
9635
|
+
const run2 = this.getValidatorRun(runId);
|
|
9636
|
+
if (!run2) {
|
|
9630
9637
|
throw new Error(`Validator run ${runId} not found`);
|
|
9631
9638
|
}
|
|
9632
9639
|
const createdRecords = [];
|
|
@@ -9709,8 +9716,8 @@ var init_mission_store = __esm({
|
|
|
9709
9716
|
if (!sourceFeature) {
|
|
9710
9717
|
throw new Error(`Feature ${sourceFeatureId} not found`);
|
|
9711
9718
|
}
|
|
9712
|
-
const
|
|
9713
|
-
if (!
|
|
9719
|
+
const run2 = this.getValidatorRun(runId);
|
|
9720
|
+
if (!run2) {
|
|
9714
9721
|
throw new Error(`Validator run ${runId} not found`);
|
|
9715
9722
|
}
|
|
9716
9723
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -9809,8 +9816,8 @@ var init_mission_store = __esm({
|
|
|
9809
9816
|
}
|
|
9810
9817
|
const validatorRuns = this.getValidatorRunsByFeature(featureId);
|
|
9811
9818
|
const failures = [];
|
|
9812
|
-
for (const
|
|
9813
|
-
const runFailures = this.getFailuresForRun(
|
|
9819
|
+
for (const run2 of validatorRuns) {
|
|
9820
|
+
const runFailures = this.getFailuresForRun(run2.id);
|
|
9814
9821
|
failures.push(...runFailures);
|
|
9815
9822
|
}
|
|
9816
9823
|
const sourceLineageRows = this.db.prepare(
|
|
@@ -10544,50 +10551,40 @@ ${feature.acceptanceCriteria}`);
|
|
|
10544
10551
|
}
|
|
10545
10552
|
}
|
|
10546
10553
|
// ── ID Generators ───────────────────────────────────────────────────
|
|
10547
|
-
|
|
10548
|
-
|
|
10554
|
+
idSequence = 0;
|
|
10555
|
+
generateId(prefix) {
|
|
10556
|
+
const timestamp = Date.now().toString(36).toUpperCase();
|
|
10557
|
+
this.idSequence += 1;
|
|
10558
|
+
const sequence = this.idSequence.toString(36).toUpperCase().padStart(4, "0");
|
|
10549
10559
|
const random = Math.random().toString(36).substring(2, 6).toUpperCase();
|
|
10550
|
-
return
|
|
10560
|
+
return `${prefix}-${timestamp}-${sequence}-${random}`;
|
|
10561
|
+
}
|
|
10562
|
+
generateMissionId() {
|
|
10563
|
+
return this.generateId("M");
|
|
10551
10564
|
}
|
|
10552
10565
|
generateMilestoneId() {
|
|
10553
|
-
|
|
10554
|
-
const random = Math.random().toString(36).substring(2, 6).toUpperCase();
|
|
10555
|
-
return `MS-${timestamp.toString(36).toUpperCase()}-${random}`;
|
|
10566
|
+
return this.generateId("MS");
|
|
10556
10567
|
}
|
|
10557
10568
|
generateSliceId() {
|
|
10558
|
-
|
|
10559
|
-
const random = Math.random().toString(36).substring(2, 6).toUpperCase();
|
|
10560
|
-
return `SL-${timestamp.toString(36).toUpperCase()}-${random}`;
|
|
10569
|
+
return this.generateId("SL");
|
|
10561
10570
|
}
|
|
10562
10571
|
generateFeatureId() {
|
|
10563
|
-
|
|
10564
|
-
const random = Math.random().toString(36).substring(2, 6).toUpperCase();
|
|
10565
|
-
return `F-${timestamp.toString(36).toUpperCase()}-${random}`;
|
|
10572
|
+
return this.generateId("F");
|
|
10566
10573
|
}
|
|
10567
10574
|
generateMissionEventId() {
|
|
10568
|
-
|
|
10569
|
-
const random = Math.random().toString(36).substring(2, 6).toUpperCase();
|
|
10570
|
-
return `ME-${timestamp.toString(36).toUpperCase()}-${random}`;
|
|
10575
|
+
return this.generateId("ME");
|
|
10571
10576
|
}
|
|
10572
10577
|
generateAssertionId() {
|
|
10573
|
-
|
|
10574
|
-
const random = Math.random().toString(36).substring(2, 6).toUpperCase();
|
|
10575
|
-
return `CA-${timestamp.toString(36).toUpperCase()}-${random}`;
|
|
10578
|
+
return this.generateId("CA");
|
|
10576
10579
|
}
|
|
10577
10580
|
generateValidatorRunId() {
|
|
10578
|
-
|
|
10579
|
-
const random = Math.random().toString(36).substring(2, 6).toUpperCase();
|
|
10580
|
-
return `VR-${timestamp.toString(36).toUpperCase()}-${random}`;
|
|
10581
|
+
return this.generateId("VR");
|
|
10581
10582
|
}
|
|
10582
10583
|
generateFailureId() {
|
|
10583
|
-
|
|
10584
|
-
const random = Math.random().toString(36).substring(2, 6).toUpperCase();
|
|
10585
|
-
return `VF-${timestamp.toString(36).toUpperCase()}-${random}`;
|
|
10584
|
+
return this.generateId("VF");
|
|
10586
10585
|
}
|
|
10587
10586
|
generateLineageId() {
|
|
10588
|
-
|
|
10589
|
-
const random = Math.random().toString(36).substring(2, 6).toUpperCase();
|
|
10590
|
-
return `FL-${timestamp.toString(36).toUpperCase()}-${random}`;
|
|
10587
|
+
return this.generateId("FL");
|
|
10591
10588
|
}
|
|
10592
10589
|
};
|
|
10593
10590
|
}
|
|
@@ -12407,7 +12404,7 @@ var init_insight_store = __esm({
|
|
|
12407
12404
|
null
|
|
12408
12405
|
);
|
|
12409
12406
|
this.db.bumpLastModified();
|
|
12410
|
-
const
|
|
12407
|
+
const run2 = {
|
|
12411
12408
|
id,
|
|
12412
12409
|
projectId,
|
|
12413
12410
|
trigger: input.trigger,
|
|
@@ -12424,8 +12421,8 @@ var init_insight_store = __esm({
|
|
|
12424
12421
|
cancelledAt: null,
|
|
12425
12422
|
lifecycle
|
|
12426
12423
|
};
|
|
12427
|
-
this.emit("run:created",
|
|
12428
|
-
return
|
|
12424
|
+
this.emit("run:created", run2);
|
|
12425
|
+
return run2;
|
|
12429
12426
|
}
|
|
12430
12427
|
/**
|
|
12431
12428
|
* Get a single run by ID.
|
|
@@ -12588,8 +12585,8 @@ var init_insight_store = __esm({
|
|
|
12588
12585
|
return this.createRun(projectId, input);
|
|
12589
12586
|
}
|
|
12590
12587
|
appendRunEvent(runId, event) {
|
|
12591
|
-
const
|
|
12592
|
-
if (!
|
|
12588
|
+
const run2 = this.getRun(runId);
|
|
12589
|
+
if (!run2) {
|
|
12593
12590
|
throw new Error(`Insight run not found: ${runId}`);
|
|
12594
12591
|
}
|
|
12595
12592
|
const createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -12765,7 +12762,7 @@ var init_research_store = __esm({
|
|
|
12765
12762
|
}
|
|
12766
12763
|
createRun(input) {
|
|
12767
12764
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
12768
|
-
const
|
|
12765
|
+
const run2 = {
|
|
12769
12766
|
id: generateRunId2(),
|
|
12770
12767
|
query: input.query,
|
|
12771
12768
|
topic: input.topic,
|
|
@@ -12794,30 +12791,30 @@ var init_research_store = __esm({
|
|
|
12794
12791
|
tokenUsage, tags, metadata, lifecycle, createdAt, updatedAt, startedAt, completedAt, cancelledAt
|
|
12795
12792
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
12796
12793
|
`).run(
|
|
12797
|
-
|
|
12798
|
-
|
|
12799
|
-
|
|
12800
|
-
|
|
12801
|
-
|
|
12802
|
-
|
|
12803
|
-
toJsonNullable(
|
|
12804
|
-
toJson(
|
|
12805
|
-
toJson(
|
|
12806
|
-
toJsonNullable(
|
|
12794
|
+
run2.id,
|
|
12795
|
+
run2.query,
|
|
12796
|
+
run2.topic ?? null,
|
|
12797
|
+
run2.status,
|
|
12798
|
+
run2.projectId ?? null,
|
|
12799
|
+
run2.trigger ?? null,
|
|
12800
|
+
toJsonNullable(run2.providerConfig),
|
|
12801
|
+
toJson(run2.sources),
|
|
12802
|
+
toJson(run2.events),
|
|
12803
|
+
toJsonNullable(run2.results),
|
|
12807
12804
|
null,
|
|
12808
12805
|
null,
|
|
12809
|
-
toJson(
|
|
12810
|
-
toJsonNullable(
|
|
12811
|
-
toJsonNullable(
|
|
12812
|
-
|
|
12813
|
-
|
|
12806
|
+
toJson(run2.tags),
|
|
12807
|
+
toJsonNullable(run2.metadata),
|
|
12808
|
+
toJsonNullable(run2.lifecycle),
|
|
12809
|
+
run2.createdAt,
|
|
12810
|
+
run2.updatedAt,
|
|
12814
12811
|
null,
|
|
12815
12812
|
null,
|
|
12816
12813
|
null
|
|
12817
12814
|
);
|
|
12818
12815
|
this.db.bumpLastModified();
|
|
12819
|
-
this.emit("run:created",
|
|
12820
|
-
return
|
|
12816
|
+
this.emit("run:created", run2);
|
|
12817
|
+
return run2;
|
|
12821
12818
|
}
|
|
12822
12819
|
getRun(id) {
|
|
12823
12820
|
const row = this.db.prepare("SELECT * FROM research_runs WHERE id = ?").get(id);
|
|
@@ -12907,8 +12904,8 @@ var init_research_store = __esm({
|
|
|
12907
12904
|
return deleted;
|
|
12908
12905
|
}
|
|
12909
12906
|
addEvent(runId, event) {
|
|
12910
|
-
const
|
|
12911
|
-
if (!
|
|
12907
|
+
const run2 = this.getRun(runId);
|
|
12908
|
+
if (!run2) throw new Error(`Research run not found: ${runId}`);
|
|
12912
12909
|
const created = {
|
|
12913
12910
|
id: generateId("REVT"),
|
|
12914
12911
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -12926,12 +12923,12 @@ var init_research_store = __esm({
|
|
|
12926
12923
|
seq2,
|
|
12927
12924
|
created.type,
|
|
12928
12925
|
created.message,
|
|
12929
|
-
|
|
12926
|
+
run2.status,
|
|
12930
12927
|
null,
|
|
12931
12928
|
toJsonNullable(created.metadata),
|
|
12932
12929
|
created.timestamp
|
|
12933
12930
|
);
|
|
12934
|
-
this.persistRun({ ...
|
|
12931
|
+
this.persistRun({ ...run2, events: [...run2.events, created], updatedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
12935
12932
|
this.db.bumpLastModified();
|
|
12936
12933
|
this.emit("event:added", { runId, event: created });
|
|
12937
12934
|
return created;
|
|
@@ -12940,8 +12937,8 @@ var init_research_store = __esm({
|
|
|
12940
12937
|
return this.addEvent(runId, event);
|
|
12941
12938
|
}
|
|
12942
12939
|
appendLifecycleEvent(runId, event) {
|
|
12943
|
-
const
|
|
12944
|
-
if (!
|
|
12940
|
+
const run2 = this.getRun(runId);
|
|
12941
|
+
if (!run2) throw new Error(`Research run not found: ${runId}`);
|
|
12945
12942
|
const createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
12946
12943
|
const lifecycleEvent = {
|
|
12947
12944
|
id: generateId("REVT"),
|
|
@@ -12990,17 +12987,17 @@ var init_research_store = __esm({
|
|
|
12990
12987
|
}));
|
|
12991
12988
|
}
|
|
12992
12989
|
addSource(runId, source) {
|
|
12993
|
-
const
|
|
12994
|
-
if (!
|
|
12990
|
+
const run2 = this.getRun(runId);
|
|
12991
|
+
if (!run2) throw new Error(`Research run not found: ${runId}`);
|
|
12995
12992
|
const created = { ...source, id: generateId("RSRC") };
|
|
12996
|
-
this.updateRun(runId, { sources: [...
|
|
12993
|
+
this.updateRun(runId, { sources: [...run2.sources, created] });
|
|
12997
12994
|
this.emit("source:added", { runId, source: created });
|
|
12998
12995
|
return created;
|
|
12999
12996
|
}
|
|
13000
12997
|
updateSource(runId, sourceId, updates) {
|
|
13001
|
-
const
|
|
13002
|
-
if (!
|
|
13003
|
-
const next =
|
|
12998
|
+
const run2 = this.getRun(runId);
|
|
12999
|
+
if (!run2) throw new Error(`Research run not found: ${runId}`);
|
|
13000
|
+
const next = run2.sources.map((source) => {
|
|
13004
13001
|
if (source.id !== sourceId) return source;
|
|
13005
13002
|
return {
|
|
13006
13003
|
...source,
|
|
@@ -13015,20 +13012,20 @@ var init_research_store = __esm({
|
|
|
13015
13012
|
if (!updated) throw new Error(`Research run not found: ${runId}`);
|
|
13016
13013
|
}
|
|
13017
13014
|
updateStatus(runId, status, extra) {
|
|
13018
|
-
const
|
|
13019
|
-
if (!
|
|
13015
|
+
const run2 = this.getRun(runId);
|
|
13016
|
+
if (!run2) throw new Error(`Research run not found: ${runId}`);
|
|
13020
13017
|
const normalizedStatus = normalizeStatus(status);
|
|
13021
13018
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
13022
13019
|
const patch = {
|
|
13023
13020
|
...extra ?? {},
|
|
13024
13021
|
status: normalizedStatus,
|
|
13025
13022
|
lifecycle: {
|
|
13026
|
-
...
|
|
13023
|
+
...run2.lifecycle ?? {}
|
|
13027
13024
|
}
|
|
13028
13025
|
};
|
|
13029
|
-
if (normalizedStatus === "running" && !
|
|
13030
|
-
if (TERMINAL_STATUSES.has(normalizedStatus) && !
|
|
13031
|
-
if (normalizedStatus === "cancelled" && !
|
|
13026
|
+
if (normalizedStatus === "running" && !run2.startedAt) patch.startedAt = now;
|
|
13027
|
+
if (TERMINAL_STATUSES.has(normalizedStatus) && !run2.completedAt) patch.completedAt = now;
|
|
13028
|
+
if (normalizedStatus === "cancelled" && !run2.cancelledAt) patch.cancelledAt = now;
|
|
13032
13029
|
if (normalizedStatus === "completed") {
|
|
13033
13030
|
patch.lifecycle = { ...patch.lifecycle ?? {}, terminalReason: "completed", retryable: false, errorCode: void 0 };
|
|
13034
13031
|
} else if (normalizedStatus === "failed") {
|
|
@@ -13160,18 +13157,18 @@ var init_research_store = __esm({
|
|
|
13160
13157
|
}
|
|
13161
13158
|
}
|
|
13162
13159
|
requestCancellation(runId, reason = "Cancelled by user") {
|
|
13163
|
-
const
|
|
13164
|
-
if (!
|
|
13165
|
-
if (TERMINAL_STATUSES.has(
|
|
13166
|
-
return
|
|
13160
|
+
const run2 = this.getRun(runId);
|
|
13161
|
+
if (!run2) throw new Error(`Research run not found: ${runId}`);
|
|
13162
|
+
if (TERMINAL_STATUSES.has(run2.status)) {
|
|
13163
|
+
return run2;
|
|
13167
13164
|
}
|
|
13168
13165
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
13169
|
-
const alreadyCancelling =
|
|
13166
|
+
const alreadyCancelling = run2.status === "cancelling";
|
|
13170
13167
|
const updated = this.updateRun(runId, {
|
|
13171
13168
|
status: "cancelling",
|
|
13172
13169
|
lifecycle: {
|
|
13173
|
-
...
|
|
13174
|
-
cancellationRequestedAt:
|
|
13170
|
+
...run2.lifecycle ?? {},
|
|
13171
|
+
cancellationRequestedAt: run2.lifecycle?.cancellationRequestedAt ?? now,
|
|
13175
13172
|
terminalCause: reason,
|
|
13176
13173
|
errorCode: "RUN_CANCELLED",
|
|
13177
13174
|
retryable: false
|
|
@@ -13184,34 +13181,34 @@ var init_research_store = __esm({
|
|
|
13184
13181
|
return updated;
|
|
13185
13182
|
}
|
|
13186
13183
|
createRetryRun(runId, maxAttempts) {
|
|
13187
|
-
const
|
|
13188
|
-
if (!
|
|
13189
|
-
if (
|
|
13190
|
-
throw new ResearchLifecycleError(`Run ${runId} is not retryable from status ${
|
|
13184
|
+
const run2 = this.getRun(runId);
|
|
13185
|
+
if (!run2) throw new Error(`Research run not found: ${runId}`);
|
|
13186
|
+
if (run2.status !== "failed" && run2.status !== "timed_out") {
|
|
13187
|
+
throw new ResearchLifecycleError(`Run ${runId} is not retryable from status ${run2.status}`, "invalid_transition");
|
|
13191
13188
|
}
|
|
13192
|
-
const currentAttempt =
|
|
13193
|
-
const configuredMaxAttempts = maxAttempts ??
|
|
13189
|
+
const currentAttempt = run2.lifecycle?.attempt ?? 1;
|
|
13190
|
+
const configuredMaxAttempts = maxAttempts ?? run2.lifecycle?.maxAttempts ?? 3;
|
|
13194
13191
|
const nextAttempt = currentAttempt + 1;
|
|
13195
13192
|
if (nextAttempt > configuredMaxAttempts) {
|
|
13196
13193
|
this.updateRun(runId, { status: "retry_exhausted" });
|
|
13197
13194
|
throw new ResearchLifecycleError(`Run ${runId} exhausted retries`, "not_retryable");
|
|
13198
13195
|
}
|
|
13199
|
-
if (!
|
|
13196
|
+
if (!run2.lifecycle?.retryable) {
|
|
13200
13197
|
throw new ResearchLifecycleError(`Run ${runId} is non-retryable`, "not_retryable");
|
|
13201
13198
|
}
|
|
13202
|
-
const rootRunId =
|
|
13199
|
+
const rootRunId = run2.lifecycle?.rootRunId ?? run2.id;
|
|
13203
13200
|
const retryRun = this.createRun({
|
|
13204
|
-
query:
|
|
13205
|
-
topic:
|
|
13206
|
-
projectId:
|
|
13207
|
-
trigger:
|
|
13208
|
-
providerConfig:
|
|
13209
|
-
tags:
|
|
13210
|
-
metadata:
|
|
13201
|
+
query: run2.query,
|
|
13202
|
+
topic: run2.topic,
|
|
13203
|
+
projectId: run2.projectId,
|
|
13204
|
+
trigger: run2.trigger,
|
|
13205
|
+
providerConfig: run2.providerConfig,
|
|
13206
|
+
tags: run2.tags,
|
|
13207
|
+
metadata: run2.metadata,
|
|
13211
13208
|
lifecycle: {
|
|
13212
13209
|
attempt: nextAttempt,
|
|
13213
13210
|
maxAttempts: configuredMaxAttempts,
|
|
13214
|
-
retryOfRunId:
|
|
13211
|
+
retryOfRunId: run2.id,
|
|
13215
13212
|
rootRunId
|
|
13216
13213
|
}
|
|
13217
13214
|
});
|
|
@@ -13223,8 +13220,8 @@ var init_research_store = __esm({
|
|
|
13223
13220
|
});
|
|
13224
13221
|
this.appendLifecycleEvent(retryRun.id, {
|
|
13225
13222
|
type: "retry_scheduled",
|
|
13226
|
-
message: `Retry scheduled from ${
|
|
13227
|
-
metadata: { retryOfRunId:
|
|
13223
|
+
message: `Retry scheduled from ${run2.id}`,
|
|
13224
|
+
metadata: { retryOfRunId: run2.id, rootRunId, attempt: nextAttempt }
|
|
13228
13225
|
});
|
|
13229
13226
|
return retryRun;
|
|
13230
13227
|
}
|
|
@@ -13232,7 +13229,7 @@ var init_research_store = __esm({
|
|
|
13232
13229
|
const row = this.db.prepare("SELECT COALESCE(MAX(seq), 0) AS seq FROM research_run_events WHERE runId = ?").get(runId);
|
|
13233
13230
|
return Number(row?.seq ?? 0) + 1;
|
|
13234
13231
|
}
|
|
13235
|
-
persistRun(
|
|
13232
|
+
persistRun(run2) {
|
|
13236
13233
|
this.db.prepare(`
|
|
13237
13234
|
UPDATE research_runs
|
|
13238
13235
|
SET query = ?, topic = ?, status = ?, projectId = ?, trigger = ?, providerConfig = ?, sources = ?, events = ?,
|
|
@@ -13240,25 +13237,25 @@ var init_research_store = __esm({
|
|
|
13240
13237
|
startedAt = ?, completedAt = ?, cancelledAt = ?
|
|
13241
13238
|
WHERE id = ?
|
|
13242
13239
|
`).run(
|
|
13243
|
-
|
|
13244
|
-
|
|
13245
|
-
|
|
13246
|
-
|
|
13247
|
-
|
|
13248
|
-
toJsonNullable(
|
|
13249
|
-
toJson(
|
|
13250
|
-
toJson(
|
|
13251
|
-
toJsonNullable(
|
|
13252
|
-
|
|
13253
|
-
toJsonNullable(
|
|
13254
|
-
toJson(
|
|
13255
|
-
toJsonNullable(
|
|
13256
|
-
toJsonNullable(
|
|
13257
|
-
|
|
13258
|
-
|
|
13259
|
-
|
|
13260
|
-
|
|
13261
|
-
|
|
13240
|
+
run2.query,
|
|
13241
|
+
run2.topic ?? null,
|
|
13242
|
+
run2.status,
|
|
13243
|
+
run2.projectId ?? null,
|
|
13244
|
+
run2.trigger ?? null,
|
|
13245
|
+
toJsonNullable(run2.providerConfig),
|
|
13246
|
+
toJson(run2.sources),
|
|
13247
|
+
toJson(run2.events),
|
|
13248
|
+
toJsonNullable(run2.results),
|
|
13249
|
+
run2.error ?? null,
|
|
13250
|
+
toJsonNullable(run2.tokenUsage),
|
|
13251
|
+
toJson(run2.tags),
|
|
13252
|
+
toJsonNullable(run2.metadata),
|
|
13253
|
+
toJsonNullable(run2.lifecycle),
|
|
13254
|
+
run2.updatedAt,
|
|
13255
|
+
run2.startedAt ?? null,
|
|
13256
|
+
run2.completedAt ?? null,
|
|
13257
|
+
run2.cancelledAt ?? null,
|
|
13258
|
+
run2.id
|
|
13262
13259
|
);
|
|
13263
13260
|
this.db.bumpLastModified();
|
|
13264
13261
|
}
|
|
@@ -16651,12 +16648,12 @@ var require_thunky = __commonJS({
|
|
|
16651
16648
|
process.nextTick(upgrade, 42);
|
|
16652
16649
|
module.exports = thunky;
|
|
16653
16650
|
function thunky(fn) {
|
|
16654
|
-
var state =
|
|
16651
|
+
var state = run2;
|
|
16655
16652
|
return thunk;
|
|
16656
16653
|
function thunk(callback) {
|
|
16657
16654
|
state(callback || noop);
|
|
16658
16655
|
}
|
|
16659
|
-
function
|
|
16656
|
+
function run2(callback) {
|
|
16660
16657
|
var stack = [callback];
|
|
16661
16658
|
state = wait;
|
|
16662
16659
|
fn(done);
|
|
@@ -16665,7 +16662,7 @@ var require_thunky = __commonJS({
|
|
|
16665
16662
|
}
|
|
16666
16663
|
function done(err) {
|
|
16667
16664
|
var args = arguments;
|
|
16668
|
-
state = isError(err) ?
|
|
16665
|
+
state = isError(err) ? run2 : finished;
|
|
16669
16666
|
while (stack.length) finished(stack.shift());
|
|
16670
16667
|
function finished(callback2) {
|
|
16671
16668
|
nextTick2(apply2, callback2, args);
|
|
@@ -32647,6 +32644,7 @@ var init_store = __esm({
|
|
|
32647
32644
|
missionId: row.missionId || void 0,
|
|
32648
32645
|
sliceId: row.sliceId || void 0,
|
|
32649
32646
|
assignedAgentId: row.assignedAgentId || void 0,
|
|
32647
|
+
pausedByAgentId: row.pausedByAgentId || void 0,
|
|
32650
32648
|
assigneeUserId: row.assigneeUserId || void 0,
|
|
32651
32649
|
nodeId: row.nodeId || void 0,
|
|
32652
32650
|
effectiveNodeId: row.effectiveNodeId || void 0,
|
|
@@ -32911,6 +32909,7 @@ ${recentText}` : void 0
|
|
|
32911
32909
|
"missionId",
|
|
32912
32910
|
"sliceId",
|
|
32913
32911
|
"assignedAgentId",
|
|
32912
|
+
"pausedByAgentId",
|
|
32914
32913
|
"assigneeUserId",
|
|
32915
32914
|
"nodeId",
|
|
32916
32915
|
"effectiveNodeId",
|
|
@@ -33023,6 +33022,7 @@ ${outcome}`;
|
|
|
33023
33022
|
"missionId",
|
|
33024
33023
|
"sliceId",
|
|
33025
33024
|
"assignedAgentId",
|
|
33025
|
+
"pausedByAgentId",
|
|
33026
33026
|
"assigneeUserId",
|
|
33027
33027
|
"nodeId",
|
|
33028
33028
|
"effectiveNodeId",
|
|
@@ -33073,9 +33073,9 @@ ${outcome}`;
|
|
|
33073
33073
|
dependencies, steps, log, attachments, steeringComments,
|
|
33074
33074
|
comments, workflowStepResults, prInfo, issueInfo,
|
|
33075
33075
|
sourceIssueProvider, sourceIssueRepository, sourceIssueExternalIssueId, sourceIssueNumber, sourceIssueUrl,
|
|
33076
|
-
mergeDetails, breakIntoSubtasks, enabledWorkflowSteps, modifiedFiles, missionId, sliceId, assignedAgentId, assigneeUserId, nodeId, effectiveNodeId, effectiveNodeSource, sourceType, sourceAgentId, sourceRunId, sourceSessionId, sourceMessageId, sourceParentTaskId, sourceMetadata, checkedOutBy, checkedOutAt
|
|
33076
|
+
mergeDetails, breakIntoSubtasks, enabledWorkflowSteps, modifiedFiles, missionId, sliceId, assignedAgentId, pausedByAgentId, assigneeUserId, nodeId, effectiveNodeId, effectiveNodeSource, sourceType, sourceAgentId, sourceRunId, sourceSessionId, sourceMessageId, sourceParentTaskId, sourceMetadata, checkedOutBy, checkedOutAt
|
|
33077
33077
|
) VALUES (
|
|
33078
|
-
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
|
33078
|
+
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
|
33079
33079
|
)
|
|
33080
33080
|
ON CONFLICT(id) DO UPDATE SET
|
|
33081
33081
|
title = excluded.title,
|
|
@@ -33144,6 +33144,7 @@ ${outcome}`;
|
|
|
33144
33144
|
missionId = excluded.missionId,
|
|
33145
33145
|
sliceId = excluded.sliceId,
|
|
33146
33146
|
assignedAgentId = excluded.assignedAgentId,
|
|
33147
|
+
pausedByAgentId = excluded.pausedByAgentId,
|
|
33147
33148
|
assigneeUserId = excluded.assigneeUserId,
|
|
33148
33149
|
nodeId = excluded.nodeId,
|
|
33149
33150
|
effectiveNodeId = excluded.effectiveNodeId,
|
|
@@ -33225,6 +33226,7 @@ ${outcome}`;
|
|
|
33225
33226
|
task.missionId ?? null,
|
|
33226
33227
|
task.sliceId ?? null,
|
|
33227
33228
|
task.assignedAgentId ?? null,
|
|
33229
|
+
task.pausedByAgentId ?? null,
|
|
33228
33230
|
task.assigneeUserId ?? null,
|
|
33229
33231
|
task.nodeId ?? null,
|
|
33230
33232
|
task.effectiveNodeId ?? null,
|
|
@@ -34342,6 +34344,23 @@ ${newTask.description}
|
|
|
34342
34344
|
const matches = [...activeMatches, ...archiveMatches];
|
|
34343
34345
|
return limit >= 0 ? matches.slice(0, limit) : matches;
|
|
34344
34346
|
}
|
|
34347
|
+
async getTasksByAssignedAgent(agentId, options) {
|
|
34348
|
+
const whereClauses = ["assignedAgentId = ?"];
|
|
34349
|
+
const params = [agentId];
|
|
34350
|
+
if (options?.pausedOnly) {
|
|
34351
|
+
whereClauses.push("paused = 1");
|
|
34352
|
+
}
|
|
34353
|
+
if (options?.excludeArchived) {
|
|
34354
|
+
whereClauses.push(`"column" != 'archived'`);
|
|
34355
|
+
}
|
|
34356
|
+
const selectClause = this.getTaskSelectClause(false);
|
|
34357
|
+
const rows = this.db.prepare(`
|
|
34358
|
+
SELECT ${selectClause} FROM tasks
|
|
34359
|
+
WHERE ${whereClauses.join(" AND ")}
|
|
34360
|
+
ORDER BY createdAt ASC
|
|
34361
|
+
`).all(...params);
|
|
34362
|
+
return rows.map((row) => this.rowToTask(row));
|
|
34363
|
+
}
|
|
34345
34364
|
async selectNextTaskForAgent(agentId) {
|
|
34346
34365
|
const tasks = await this.listTasks({ slim: true });
|
|
34347
34366
|
if (tasks.length === 0) {
|
|
@@ -34579,6 +34598,11 @@ ${newTask.description}
|
|
|
34579
34598
|
} else if (updates.assignedAgentId !== void 0) {
|
|
34580
34599
|
task.assignedAgentId = updates.assignedAgentId;
|
|
34581
34600
|
}
|
|
34601
|
+
if (updates.pausedByAgentId === null) {
|
|
34602
|
+
task.pausedByAgentId = void 0;
|
|
34603
|
+
} else if (updates.pausedByAgentId !== void 0) {
|
|
34604
|
+
task.pausedByAgentId = updates.pausedByAgentId;
|
|
34605
|
+
}
|
|
34582
34606
|
if (updates.assigneeUserId === null) {
|
|
34583
34607
|
task.assigneeUserId = void 0;
|
|
34584
34608
|
} else if (updates.assigneeUserId !== void 0) {
|
|
@@ -34817,14 +34841,21 @@ ${newTask.description}
|
|
|
34817
34841
|
* Pause or unpause a task. Paused tasks are excluded from all automated
|
|
34818
34842
|
* agent and scheduler interaction. Logs the action and emits `task:updated`.
|
|
34819
34843
|
*/
|
|
34820
|
-
async pauseTask(id, paused, runContext) {
|
|
34844
|
+
async pauseTask(id, paused, runContext, agentOptions) {
|
|
34821
34845
|
return this.withTaskLock(id, async () => {
|
|
34822
34846
|
const dir2 = this.taskDir(id);
|
|
34823
34847
|
const task = await this.readTaskJson(dir2);
|
|
34824
34848
|
if (!task.log) {
|
|
34825
34849
|
task.log = [];
|
|
34826
34850
|
}
|
|
34851
|
+
const previousPausedByAgentId = task.pausedByAgentId;
|
|
34827
34852
|
task.paused = paused || void 0;
|
|
34853
|
+
if (paused && agentOptions?.pausedByAgentId) {
|
|
34854
|
+
task.pausedByAgentId = agentOptions.pausedByAgentId;
|
|
34855
|
+
}
|
|
34856
|
+
if (!paused) {
|
|
34857
|
+
task.pausedByAgentId = void 0;
|
|
34858
|
+
}
|
|
34828
34859
|
if (task.column === "in-progress" || task.column === "in-review") {
|
|
34829
34860
|
task.status = paused ? "paused" : void 0;
|
|
34830
34861
|
}
|
|
@@ -34832,7 +34863,7 @@ ${newTask.description}
|
|
|
34832
34863
|
task.updatedAt = now;
|
|
34833
34864
|
const logEntry = {
|
|
34834
34865
|
timestamp: now,
|
|
34835
|
-
action: paused ? "Task paused" : "Task unpaused"
|
|
34866
|
+
action: paused ? agentOptions?.pausedByAgentId ? `Task paused (agent ${agentOptions.pausedByAgentId} paused)` : "Task paused" : previousPausedByAgentId ? `Task unpaused (agent ${previousPausedByAgentId} resumed)` : "Task unpaused"
|
|
34836
34867
|
};
|
|
34837
34868
|
if (runContext) {
|
|
34838
34869
|
logEntry.runContext = runContext;
|
|
@@ -39983,10 +40014,17 @@ var init_docker_client = __esm({
|
|
|
39983
40014
|
}
|
|
39984
40015
|
return this.createDockerInstance(hostConfig);
|
|
39985
40016
|
}
|
|
39986
|
-
async getContainerInfo(containerId) {
|
|
40017
|
+
async getContainerInfo(containerId, hostConfig) {
|
|
39987
40018
|
try {
|
|
39988
|
-
const docker = await this.
|
|
40019
|
+
const docker = await this.getDockerInstance(hostConfig);
|
|
39989
40020
|
const inspect = await docker.getContainer(containerId).inspect();
|
|
40021
|
+
const ports = Object.entries(inspect.NetworkSettings?.Ports ?? {}).reduce((acc, [key, value]) => {
|
|
40022
|
+
const binding = Array.isArray(value) && value.length > 0 ? value[0] : void 0;
|
|
40023
|
+
if (binding?.HostPort) {
|
|
40024
|
+
acc[key] = binding.HostPort;
|
|
40025
|
+
}
|
|
40026
|
+
return acc;
|
|
40027
|
+
}, {});
|
|
39990
40028
|
return {
|
|
39991
40029
|
id: inspect.Id,
|
|
39992
40030
|
name: (inspect.Name ?? "").replace(/^\//, ""),
|
|
@@ -39998,8 +40036,12 @@ var init_docker_client = __esm({
|
|
|
39998
40036
|
paused: Boolean(inspect.State?.Paused),
|
|
39999
40037
|
restarting: Boolean(inspect.State?.Restarting),
|
|
40000
40038
|
dead: Boolean(inspect.State?.Dead),
|
|
40001
|
-
error: inspect.State?.Error || void 0
|
|
40002
|
-
|
|
40039
|
+
error: inspect.State?.Error || void 0,
|
|
40040
|
+
exitCode: typeof inspect.State?.ExitCode === "number" ? inspect.State.ExitCode : void 0,
|
|
40041
|
+
startedAt: inspect.State?.StartedAt || void 0,
|
|
40042
|
+
finishedAt: inspect.State?.FinishedAt || void 0
|
|
40043
|
+
},
|
|
40044
|
+
ports
|
|
40003
40045
|
};
|
|
40004
40046
|
} catch (error) {
|
|
40005
40047
|
const message = toErrorMessage(error);
|
|
@@ -40009,6 +40051,18 @@ var init_docker_client = __esm({
|
|
|
40009
40051
|
throw error;
|
|
40010
40052
|
}
|
|
40011
40053
|
}
|
|
40054
|
+
async getContainerLogs(containerId, hostConfig, options) {
|
|
40055
|
+
const docker = await this.getDockerInstance(hostConfig);
|
|
40056
|
+
const stream = await docker.getContainer(containerId).logs({
|
|
40057
|
+
stdout: true,
|
|
40058
|
+
stderr: true,
|
|
40059
|
+
tail: options?.tail ?? 100
|
|
40060
|
+
});
|
|
40061
|
+
if (Buffer.isBuffer(stream)) {
|
|
40062
|
+
return stream.toString("utf8");
|
|
40063
|
+
}
|
|
40064
|
+
return String(stream ?? "");
|
|
40065
|
+
}
|
|
40012
40066
|
async getInstance() {
|
|
40013
40067
|
if (!this.dockerInstance) {
|
|
40014
40068
|
this.dockerInstance = await this.createDockerInstance(this.defaultHostConfig);
|
|
@@ -41509,17 +41563,17 @@ function patchForStatus(status, patch) {
|
|
|
41509
41563
|
}
|
|
41510
41564
|
return patch;
|
|
41511
41565
|
}
|
|
41512
|
-
async function executeExistingRun(store,
|
|
41513
|
-
const started = store.updateRun(
|
|
41566
|
+
async function executeExistingRun(store, run2, options) {
|
|
41567
|
+
const started = store.updateRun(run2.id, {
|
|
41514
41568
|
status: "running",
|
|
41515
|
-
startedAt:
|
|
41569
|
+
startedAt: run2.startedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
41516
41570
|
lifecycle: {
|
|
41517
|
-
...
|
|
41571
|
+
...run2.lifecycle,
|
|
41518
41572
|
maxAttempts: options.maxAttempts,
|
|
41519
|
-
attempt:
|
|
41573
|
+
attempt: run2.lifecycle.attempt ?? 1
|
|
41520
41574
|
}
|
|
41521
41575
|
});
|
|
41522
|
-
let active = started ??
|
|
41576
|
+
let active = started ?? run2;
|
|
41523
41577
|
store.appendRunEvent(active.id, { type: "status_changed", status: "running", message: "Run started" });
|
|
41524
41578
|
for (let attempt = active.lifecycle.attempt ?? 1; attempt <= options.maxAttempts; attempt += 1) {
|
|
41525
41579
|
const { signal, clear } = composeSignal(options.timeoutMs, options.signal);
|
|
@@ -41615,9 +41669,9 @@ async function executeExistingRun(store, run, options) {
|
|
|
41615
41669
|
async function executeInsightRunLifecycle(options) {
|
|
41616
41670
|
const maxAttempts = Math.max(1, options.maxAttempts ?? 2);
|
|
41617
41671
|
const retryDelayMs = Math.max(0, options.retryDelayMs ?? 250);
|
|
41618
|
-
let
|
|
41672
|
+
let run2;
|
|
41619
41673
|
try {
|
|
41620
|
-
|
|
41674
|
+
run2 = options.store.createRunOrThrowConflict(options.projectId, {
|
|
41621
41675
|
...options.input,
|
|
41622
41676
|
lifecycle: {
|
|
41623
41677
|
...options.input.lifecycle,
|
|
@@ -41632,12 +41686,12 @@ async function executeInsightRunLifecycle(options) {
|
|
|
41632
41686
|
}
|
|
41633
41687
|
throw error;
|
|
41634
41688
|
}
|
|
41635
|
-
options.store.appendRunEvent(
|
|
41689
|
+
options.store.appendRunEvent(run2.id, {
|
|
41636
41690
|
type: "status_changed",
|
|
41637
41691
|
status: "pending",
|
|
41638
41692
|
message: "Run created"
|
|
41639
41693
|
});
|
|
41640
|
-
return executeExistingRun(options.store,
|
|
41694
|
+
return executeExistingRun(options.store, run2, {
|
|
41641
41695
|
...options,
|
|
41642
41696
|
maxAttempts,
|
|
41643
41697
|
retryDelayMs
|
|
@@ -41654,7 +41708,7 @@ async function retryInsightRunLifecycle(options) {
|
|
|
41654
41708
|
if (!original.lifecycle.retryable || original.lifecycle.failureClass !== "retryable_transient") {
|
|
41655
41709
|
throw new InsightLifecycleError(`Run ${original.id} is non-retryable`, "not_retryable");
|
|
41656
41710
|
}
|
|
41657
|
-
const
|
|
41711
|
+
const run2 = await executeInsightRunLifecycle({
|
|
41658
41712
|
...options,
|
|
41659
41713
|
projectId: original.projectId,
|
|
41660
41714
|
input: {
|
|
@@ -41667,7 +41721,7 @@ async function retryInsightRunLifecycle(options) {
|
|
|
41667
41721
|
}
|
|
41668
41722
|
}
|
|
41669
41723
|
});
|
|
41670
|
-
return { run, retryOf: original };
|
|
41724
|
+
return { run: run2, retryOf: original };
|
|
41671
41725
|
}
|
|
41672
41726
|
var init_insight_run_executor = __esm({
|
|
41673
41727
|
"../core/src/insight-run-executor.ts"() {
|
|
@@ -54701,7 +54755,7 @@ var init_research_orchestrator = __esm({
|
|
|
54701
54755
|
this.semaphore = new AgentSemaphore(options.maxConcurrentRuns ?? 3);
|
|
54702
54756
|
}
|
|
54703
54757
|
createRun(config) {
|
|
54704
|
-
const
|
|
54758
|
+
const run2 = this.store.createRun({
|
|
54705
54759
|
query: "",
|
|
54706
54760
|
providerConfig: config,
|
|
54707
54761
|
metadata: {
|
|
@@ -54712,12 +54766,12 @@ var init_research_orchestrator = __esm({
|
|
|
54712
54766
|
}
|
|
54713
54767
|
}
|
|
54714
54768
|
});
|
|
54715
|
-
return
|
|
54769
|
+
return run2.id;
|
|
54716
54770
|
}
|
|
54717
54771
|
async startRun(runId, query, options = {}) {
|
|
54718
|
-
const
|
|
54719
|
-
if (!
|
|
54720
|
-
const config =
|
|
54772
|
+
const run2 = this.store.getRun(runId);
|
|
54773
|
+
if (!run2) throw new Error(`Research run not found: ${runId}`);
|
|
54774
|
+
const config = run2.providerConfig ?? {};
|
|
54721
54775
|
const controller = new AbortController();
|
|
54722
54776
|
if (options.abortSignal) {
|
|
54723
54777
|
options.abortSignal.addEventListener("abort", () => controller.abort(options.abortSignal?.reason), { once: true });
|
|
@@ -54745,8 +54799,8 @@ var init_research_orchestrator = __esm({
|
|
|
54745
54799
|
}
|
|
54746
54800
|
cancelRun(runId) {
|
|
54747
54801
|
const active = this.activeRuns.get(runId);
|
|
54748
|
-
const
|
|
54749
|
-
if (!
|
|
54802
|
+
const run2 = this.store.getRun(runId);
|
|
54803
|
+
if (!run2) return false;
|
|
54750
54804
|
this.store.requestCancellation(runId);
|
|
54751
54805
|
if (!active) {
|
|
54752
54806
|
this.store.updateStatus(runId, "cancelled", { error: "Cancelled by user" });
|
|
@@ -54768,39 +54822,39 @@ var init_research_orchestrator = __esm({
|
|
|
54768
54822
|
return true;
|
|
54769
54823
|
}
|
|
54770
54824
|
retryRun(runId) {
|
|
54771
|
-
const
|
|
54772
|
-
if (!
|
|
54773
|
-
if (
|
|
54774
|
-
throw new Error(`Research run ${runId} is not retryable (status=${
|
|
54825
|
+
const run2 = this.store.getRun(runId);
|
|
54826
|
+
if (!run2) throw new Error(`Research run not found: ${runId}`);
|
|
54827
|
+
if (run2.status !== "failed" && run2.status !== "cancelled") {
|
|
54828
|
+
throw new Error(`Research run ${runId} is not retryable (status=${run2.status})`);
|
|
54775
54829
|
}
|
|
54776
54830
|
const next = this.store.createRun({
|
|
54777
|
-
query:
|
|
54778
|
-
topic:
|
|
54779
|
-
providerConfig:
|
|
54780
|
-
tags: [...
|
|
54831
|
+
query: run2.query,
|
|
54832
|
+
topic: run2.topic,
|
|
54833
|
+
providerConfig: run2.providerConfig,
|
|
54834
|
+
tags: [...run2.tags],
|
|
54781
54835
|
metadata: {
|
|
54782
|
-
...
|
|
54783
|
-
retryOfRunId:
|
|
54836
|
+
...run2.metadata ?? {},
|
|
54837
|
+
retryOfRunId: run2.id
|
|
54784
54838
|
}
|
|
54785
54839
|
});
|
|
54786
54840
|
this.store.addEvent(next.id, {
|
|
54787
54841
|
type: "info",
|
|
54788
|
-
message: `Retry run created from ${
|
|
54789
|
-
metadata: { retryOfRunId:
|
|
54842
|
+
message: `Retry run created from ${run2.id}`,
|
|
54843
|
+
metadata: { retryOfRunId: run2.id }
|
|
54790
54844
|
});
|
|
54791
54845
|
return next.id;
|
|
54792
54846
|
}
|
|
54793
54847
|
getRunStatus(runId) {
|
|
54794
|
-
const
|
|
54795
|
-
if (!
|
|
54848
|
+
const run2 = this.store.getRun(runId);
|
|
54849
|
+
if (!run2) throw new Error(`Research run not found: ${runId}`);
|
|
54796
54850
|
const active = this.activeRuns.get(runId);
|
|
54797
|
-
const metadata =
|
|
54798
|
-
const phase = active?.phase ?? metadata.phase ?? this.statusToPhase(
|
|
54851
|
+
const metadata = run2.metadata?.orchestration ?? {};
|
|
54852
|
+
const phase = active?.phase ?? metadata.phase ?? this.statusToPhase(run2.status);
|
|
54799
54853
|
const stepIndex = active?.stepIndex ?? Number(metadata.stepIndex ?? 0);
|
|
54800
54854
|
const totalSteps = active?.totalSteps ?? Number(metadata.totalSteps ?? 0);
|
|
54801
54855
|
return {
|
|
54802
54856
|
runId,
|
|
54803
|
-
status:
|
|
54857
|
+
status: run2.status,
|
|
54804
54858
|
phase,
|
|
54805
54859
|
stepIndex,
|
|
54806
54860
|
totalSteps,
|
|
@@ -54974,8 +55028,8 @@ var init_research_orchestrator = __esm({
|
|
|
54974
55028
|
});
|
|
54975
55029
|
}
|
|
54976
55030
|
onCancelled(runId) {
|
|
54977
|
-
const
|
|
54978
|
-
if (!
|
|
55031
|
+
const run2 = this.store.getRun(runId);
|
|
55032
|
+
if (!run2 || run2.status === "cancelled") return;
|
|
54979
55033
|
const cancellation = this.cancellation.get(runId);
|
|
54980
55034
|
this.store.addEvent(runId, {
|
|
54981
55035
|
type: "warning",
|
|
@@ -55097,9 +55151,9 @@ var init_research_orchestrator = __esm({
|
|
|
55097
55151
|
}
|
|
55098
55152
|
}
|
|
55099
55153
|
canWriteRunData(runId) {
|
|
55100
|
-
const
|
|
55101
|
-
if (!
|
|
55102
|
-
return !["cancelled", "completed", "failed", "timed_out", "retry_exhausted"].includes(
|
|
55154
|
+
const run2 = this.store.getRun(runId);
|
|
55155
|
+
if (!run2) return false;
|
|
55156
|
+
return !["cancelled", "completed", "failed", "timed_out", "retry_exhausted"].includes(run2.status);
|
|
55103
55157
|
}
|
|
55104
55158
|
};
|
|
55105
55159
|
}
|
|
@@ -55433,6 +55487,9 @@ function normalizePattern(pattern) {
|
|
|
55433
55487
|
function isExclusionPattern(pattern) {
|
|
55434
55488
|
return pattern.startsWith("-");
|
|
55435
55489
|
}
|
|
55490
|
+
function bareSkillName(name) {
|
|
55491
|
+
return name.replace(/\/SKILL\.md$/i, "");
|
|
55492
|
+
}
|
|
55436
55493
|
function resolveSessionSkills(context) {
|
|
55437
55494
|
const { requestedSkillNames } = context;
|
|
55438
55495
|
const projectRootDir = resolveProjectRoot(context.projectRootDir);
|
|
@@ -55526,25 +55583,40 @@ function createSkillsOverrideFromSelection(selection, options = {}) {
|
|
|
55526
55583
|
const hasRequestedNames = Boolean(requestedSkillNames && requestedSkillNames.length > 0);
|
|
55527
55584
|
const hasExcluded = excludedSkillPaths.size > 0;
|
|
55528
55585
|
let filteredSkills;
|
|
55586
|
+
const skillNameMatches = (skill, pattern) => bareSkillName(skill.name).toLowerCase() === bareSkillName(pattern).toLowerCase() || skill.filePath === pattern;
|
|
55587
|
+
const isExcluded = (skill) => {
|
|
55588
|
+
for (const ep of excludedSkillPaths) {
|
|
55589
|
+
if (skillNameMatches(skill, ep)) return true;
|
|
55590
|
+
}
|
|
55591
|
+
return false;
|
|
55592
|
+
};
|
|
55593
|
+
const isAllowed = (skill) => {
|
|
55594
|
+
for (const ap of allowedSkillPaths) {
|
|
55595
|
+
if (skillNameMatches(skill, ap)) return true;
|
|
55596
|
+
}
|
|
55597
|
+
return false;
|
|
55598
|
+
};
|
|
55529
55599
|
if (hasRequestedNames) {
|
|
55530
|
-
const
|
|
55600
|
+
const requestedBareNamesLower = new Set(requestedSkillNames.map((n) => bareSkillName(n).toLowerCase()));
|
|
55531
55601
|
filteredSkills = base.skills.filter(
|
|
55532
|
-
(skill) =>
|
|
55602
|
+
(skill) => requestedBareNamesLower.has(bareSkillName(skill.name).toLowerCase()) && !isExcluded(skill)
|
|
55533
55603
|
);
|
|
55534
55604
|
} else if (hasPatterns) {
|
|
55535
55605
|
filteredSkills = base.skills.filter(
|
|
55536
|
-
(skill) =>
|
|
55606
|
+
(skill) => isAllowed(skill) && !isExcluded(skill)
|
|
55537
55607
|
);
|
|
55538
55608
|
} else if (hasExcluded) {
|
|
55539
|
-
filteredSkills = base.skills.filter((skill) => !
|
|
55609
|
+
filteredSkills = base.skills.filter((skill) => !isExcluded(skill));
|
|
55540
55610
|
} else {
|
|
55541
55611
|
filteredSkills = base.skills;
|
|
55542
55612
|
}
|
|
55543
55613
|
const newDiagnostics = [];
|
|
55544
55614
|
const purpose = sessionPurpose ? ` [${sessionPurpose}]` : "";
|
|
55545
|
-
const
|
|
55615
|
+
const discoveredBareNames = new Set(base.skills.map((s) => bareSkillName(s.name).toLowerCase()));
|
|
55616
|
+
const discoveredFilePaths = new Set(base.skills.map((s) => s.filePath));
|
|
55617
|
+
const hasDiscoveredMatch = (pattern) => discoveredBareNames.has(bareSkillName(pattern).toLowerCase()) || discoveredFilePaths.has(pattern);
|
|
55546
55618
|
for (const excludedPath of excludedSkillPaths) {
|
|
55547
|
-
if (
|
|
55619
|
+
if (hasDiscoveredMatch(excludedPath)) {
|
|
55548
55620
|
newDiagnostics.push({
|
|
55549
55621
|
type: "warning",
|
|
55550
55622
|
message: `Skill at '${excludedPath}' exists but is disabled by project execution settings${purpose}`,
|
|
@@ -55553,7 +55625,7 @@ function createSkillsOverrideFromSelection(selection, options = {}) {
|
|
|
55553
55625
|
}
|
|
55554
55626
|
}
|
|
55555
55627
|
for (const allowedPath of allowedSkillPaths) {
|
|
55556
|
-
if (!
|
|
55628
|
+
if (!hasDiscoveredMatch(allowedPath)) {
|
|
55557
55629
|
newDiagnostics.push({
|
|
55558
55630
|
type: "warning",
|
|
55559
55631
|
message: `Configured skill pattern '${allowedPath}' not found in discovered skills${purpose}`,
|
|
@@ -55562,9 +55634,9 @@ function createSkillsOverrideFromSelection(selection, options = {}) {
|
|
|
55562
55634
|
}
|
|
55563
55635
|
}
|
|
55564
55636
|
if (requestedSkillNames) {
|
|
55565
|
-
const
|
|
55637
|
+
const discoveredBareNamesLower = new Set(base.skills.map((s) => bareSkillName(s.name).toLowerCase()));
|
|
55566
55638
|
for (const requestedName of requestedSkillNames) {
|
|
55567
|
-
if (!
|
|
55639
|
+
if (!discoveredBareNamesLower.has(bareSkillName(requestedName).toLowerCase()) && !isBuiltInFallbackRequest(requestedName)) {
|
|
55568
55640
|
const purpose2 = sessionPurpose ? ` [${sessionPurpose}]` : "";
|
|
55569
55641
|
newDiagnostics.push({
|
|
55570
55642
|
type: "warning",
|
|
@@ -55918,6 +55990,11 @@ async function promptSessionAndCheck(session, prompt, options) {
|
|
|
55918
55990
|
piLog.warn(`pi state error \u2014 failed to inspect transcript: ${inspectErr instanceof Error ? inspectErr.message : String(inspectErr)}`);
|
|
55919
55991
|
}
|
|
55920
55992
|
}
|
|
55993
|
+
if (/Provider finish_reason:\s*repeat\b/i.test(stateError)) {
|
|
55994
|
+
piLog.warn(`pi state error \u2014 treating provider finish_reason=repeat as soft stop: ${stateError}`);
|
|
55995
|
+
clearSessionStateError(session);
|
|
55996
|
+
return;
|
|
55997
|
+
}
|
|
55921
55998
|
throw new Error(stateError);
|
|
55922
55999
|
}
|
|
55923
56000
|
}
|
|
@@ -58493,18 +58570,18 @@ function createSendMessageTool(messageStore, fromAgentId) {
|
|
|
58493
58570
|
}
|
|
58494
58571
|
};
|
|
58495
58572
|
}
|
|
58496
|
-
function formatResearchRunDetails(
|
|
58497
|
-
const findings =
|
|
58498
|
-
const citations =
|
|
58573
|
+
function formatResearchRunDetails(run2) {
|
|
58574
|
+
const findings = run2.results?.findings ?? [];
|
|
58575
|
+
const citations = run2.results?.citations ?? [];
|
|
58499
58576
|
return {
|
|
58500
|
-
runId:
|
|
58501
|
-
status:
|
|
58502
|
-
query:
|
|
58503
|
-
summary:
|
|
58577
|
+
runId: run2.id,
|
|
58578
|
+
status: run2.status,
|
|
58579
|
+
query: run2.query,
|
|
58580
|
+
summary: run2.results?.summary ?? null,
|
|
58504
58581
|
findings,
|
|
58505
58582
|
citations,
|
|
58506
|
-
sourceCount:
|
|
58507
|
-
error:
|
|
58583
|
+
sourceCount: run2.sources.length,
|
|
58584
|
+
error: run2.error ?? null,
|
|
58508
58585
|
setup: null
|
|
58509
58586
|
};
|
|
58510
58587
|
}
|
|
@@ -58629,10 +58706,10 @@ function createResearchTools(options) {
|
|
|
58629
58706
|
status: params.status,
|
|
58630
58707
|
limit
|
|
58631
58708
|
});
|
|
58632
|
-
const text = runs.length ? runs.map((
|
|
58709
|
+
const text = runs.length ? runs.map((run2) => `- ${run2.id} [${run2.status}] ${run2.query}`).join("\n") : "No research runs found.";
|
|
58633
58710
|
return {
|
|
58634
58711
|
content: [{ type: "text", text }],
|
|
58635
|
-
details: { runs: runs.map((
|
|
58712
|
+
details: { runs: runs.map((run2) => formatResearchRunDetails(run2)) }
|
|
58636
58713
|
};
|
|
58637
58714
|
}
|
|
58638
58715
|
};
|
|
@@ -58642,16 +58719,16 @@ function createResearchTools(options) {
|
|
|
58642
58719
|
description: "Get one research run with structured findings and citations.",
|
|
58643
58720
|
parameters: researchGetParams,
|
|
58644
58721
|
execute: async (_id, params) => {
|
|
58645
|
-
const
|
|
58646
|
-
if (!
|
|
58722
|
+
const run2 = options.store.getResearchStore().getRun(params.id);
|
|
58723
|
+
if (!run2) {
|
|
58647
58724
|
return {
|
|
58648
58725
|
content: [{ type: "text", text: `Research run ${params.id} not found.` }],
|
|
58649
58726
|
details: { runId: params.id, status: "missing", summary: null, findings: [], citations: [], error: "not found", setup: null }
|
|
58650
58727
|
};
|
|
58651
58728
|
}
|
|
58652
|
-
const details = formatResearchRunDetails(
|
|
58729
|
+
const details = formatResearchRunDetails(run2);
|
|
58653
58730
|
return {
|
|
58654
|
-
content: [{ type: "text", text: `Research run ${
|
|
58731
|
+
content: [{ type: "text", text: `Research run ${run2.id} is ${run2.status}.` }],
|
|
58655
58732
|
details
|
|
58656
58733
|
};
|
|
58657
58734
|
}
|
|
@@ -58667,8 +58744,8 @@ function createResearchTools(options) {
|
|
|
58667
58744
|
return researchUnavailable("provider-unavailable", "Research orchestrator is unavailable because research providers are not configured.");
|
|
58668
58745
|
}
|
|
58669
58746
|
const cancelled = orchestrator.cancelRun(params.id);
|
|
58670
|
-
const
|
|
58671
|
-
if (!
|
|
58747
|
+
const run2 = options.store.getResearchStore().getRun(params.id);
|
|
58748
|
+
if (!run2) {
|
|
58672
58749
|
return {
|
|
58673
58750
|
content: [{ type: "text", text: `Research run ${params.id} not found.` }],
|
|
58674
58751
|
details: { runId: params.id, status: "missing", summary: null, findings: [], citations: [], error: "not found", setup: null }
|
|
@@ -58676,7 +58753,7 @@ function createResearchTools(options) {
|
|
|
58676
58753
|
}
|
|
58677
58754
|
return {
|
|
58678
58755
|
content: [{ type: "text", text: cancelled ? `Cancellation requested for ${params.id}.` : `Run ${params.id} is not active.` }],
|
|
58679
|
-
details: formatResearchRunDetails(
|
|
58756
|
+
details: formatResearchRunDetails(run2)
|
|
58680
58757
|
};
|
|
58681
58758
|
}
|
|
58682
58759
|
};
|
|
@@ -59099,9 +59176,18 @@ function normalizeAgentSkills(metadataSkills) {
|
|
|
59099
59176
|
name = namedEntry.trim();
|
|
59100
59177
|
}
|
|
59101
59178
|
}
|
|
59102
|
-
if (name && name.length > 0
|
|
59103
|
-
|
|
59104
|
-
|
|
59179
|
+
if (name && name.length > 0) {
|
|
59180
|
+
if (name.includes("::")) {
|
|
59181
|
+
const idPath = name.split("::").pop();
|
|
59182
|
+
const parts = idPath.replace(/\\/g, "/").split("/").filter(Boolean);
|
|
59183
|
+
if (parts.length >= 2) {
|
|
59184
|
+
name = parts.slice(-2).join("/");
|
|
59185
|
+
}
|
|
59186
|
+
}
|
|
59187
|
+
if (!seen.has(name)) {
|
|
59188
|
+
seen.add(name);
|
|
59189
|
+
result.push(name);
|
|
59190
|
+
}
|
|
59105
59191
|
}
|
|
59106
59192
|
}
|
|
59107
59193
|
return result;
|
|
@@ -69724,20 +69810,26 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
69724
69810
|
if (from !== "in-review" && from !== "done") {
|
|
69725
69811
|
return task;
|
|
69726
69812
|
}
|
|
69727
|
-
const hasMergeEvidence = Boolean(task.mergeDetails) || (task.mergeRetries ?? 0) > 0 || (task.verificationFailureCount ?? 0) > 0 || task.status === "merging" || task.status === "merging-pr";
|
|
69813
|
+
const hasMergeEvidence = Boolean(task.mergeDetails) || (task.mergeRetries ?? 0) > 0 || (task.verificationFailureCount ?? 0) > 0 || task.status === "merging" || task.status === "merging-pr" || task.status === "merging-fix";
|
|
69728
69814
|
if (!hasMergeEvidence) {
|
|
69729
69815
|
return task;
|
|
69730
69816
|
}
|
|
69731
69817
|
return this.cleanupMergeStateForReverification(
|
|
69732
69818
|
task,
|
|
69733
|
-
`Task returned to in-progress from ${from} column \u2014 resetting verification steps and merge state for re-verification
|
|
69819
|
+
`Task returned to in-progress from ${from} column \u2014 resetting verification steps and merge state for re-verification`,
|
|
69820
|
+
{
|
|
69821
|
+
// Keep deterministic merge-verification bounce budget across remediation
|
|
69822
|
+
// cycles. Status may be cleared by intermediate paths, so the counter is
|
|
69823
|
+
// the canonical signal once a bounce has started.
|
|
69824
|
+
preserveVerificationFailureCount: (task.verificationFailureCount ?? 0) > 0
|
|
69825
|
+
}
|
|
69734
69826
|
);
|
|
69735
69827
|
}
|
|
69736
|
-
async cleanupMergeStateForReverification(task, logMessage) {
|
|
69828
|
+
async cleanupMergeStateForReverification(task, logMessage, options) {
|
|
69737
69829
|
await this.store.updateTask(task.id, {
|
|
69738
69830
|
mergeDetails: null,
|
|
69739
69831
|
mergeRetries: 0,
|
|
69740
|
-
verificationFailureCount: 0,
|
|
69832
|
+
verificationFailureCount: options?.preserveVerificationFailureCount ? task.verificationFailureCount ?? 0 : 0,
|
|
69741
69833
|
workflowStepResults: []
|
|
69742
69834
|
});
|
|
69743
69835
|
const refreshedTask = await this.store.getTask(task.id);
|
|
@@ -70332,7 +70424,7 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
70332
70424
|
};
|
|
70333
70425
|
const audit = createRunAuditor(this.store, engineRunContext);
|
|
70334
70426
|
const activeColumns = /* @__PURE__ */ new Set(["in-progress", "in-review", "done"]);
|
|
70335
|
-
const activeMergeStatuses = /* @__PURE__ */ new Set(["merging", "merging-pr"]);
|
|
70427
|
+
const activeMergeStatuses = /* @__PURE__ */ new Set(["merging", "merging-pr", "merging-fix"]);
|
|
70336
70428
|
const isActiveTask = activeColumns.has(task.column) || activeMergeStatuses.has(task.status ?? "");
|
|
70337
70429
|
if (!isActiveTask) {
|
|
70338
70430
|
const tasksDir = join36(this.store.getFusionDir(), "tasks");
|
|
@@ -70671,7 +70763,9 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
70671
70763
|
`${failedType} command \`${failedCommand}\` failed (exit ${failedResult.exitCode}):
|
|
70672
70764
|
${summary}`,
|
|
70673
70765
|
`Verification (${failedType})`,
|
|
70674
|
-
`Deterministic verification failed (${failedType})
|
|
70766
|
+
`Deterministic verification failed (${failedType})`,
|
|
70767
|
+
true,
|
|
70768
|
+
true
|
|
70675
70769
|
);
|
|
70676
70770
|
return;
|
|
70677
70771
|
}
|
|
@@ -70717,7 +70811,9 @@ ${summary}`,
|
|
|
70717
70811
|
`${failedType} command \`${failedCommand}\` failed (exit ${failedResult.exitCode}) after ${maxFixRetries} fix attempts:
|
|
70718
70812
|
${summary}`,
|
|
70719
70813
|
`Verification (${failedType})`,
|
|
70720
|
-
`Deterministic verification failed after ${maxFixRetries} fix attempts
|
|
70814
|
+
`Deterministic verification failed after ${maxFixRetries} fix attempts`,
|
|
70815
|
+
true,
|
|
70816
|
+
true
|
|
70721
70817
|
);
|
|
70722
70818
|
return;
|
|
70723
70819
|
}
|
|
@@ -72406,7 +72502,7 @@ ${failureContext.output.slice(0, VERIFICATION_LOG_MAX_CHARS)}
|
|
|
72406
72502
|
* Injects failure feedback into PROMPT.md, resets steps, clears session,
|
|
72407
72503
|
* and schedules a move to todo → in-progress after the executing guard clears.
|
|
72408
72504
|
*/
|
|
72409
|
-
async sendTaskBackForFix(task, worktreePath, failureFeedback, stepName, reason, preserveResumeState = true) {
|
|
72505
|
+
async sendTaskBackForFix(task, worktreePath, failureFeedback, stepName, reason, preserveResumeState = true, mergeVerificationFailure = false) {
|
|
72410
72506
|
const taskId = task.id;
|
|
72411
72507
|
this.clearCompletedTaskWatchdog(taskId);
|
|
72412
72508
|
await this.store.addTaskComment(
|
|
@@ -72425,7 +72521,7 @@ Please fix the issues so the verification can pass on the next attempt.`,
|
|
|
72425
72521
|
const updatedTask = await this.store.getTask(taskId);
|
|
72426
72522
|
await this.reopenLastStepForRevision(taskId, updatedTask);
|
|
72427
72523
|
await this.store.updateTask(taskId, {
|
|
72428
|
-
status: null,
|
|
72524
|
+
status: mergeVerificationFailure ? "merging-fix" : null,
|
|
72429
72525
|
error: null,
|
|
72430
72526
|
sessionFile: null,
|
|
72431
72527
|
workflowStepRetries: 0
|
|
@@ -76001,17 +76097,17 @@ Assertions: ${assertions.map((a) => a.title).join(", ")}`,
|
|
|
76001
76097
|
validationTaskId = validationTask.id;
|
|
76002
76098
|
await this.taskStore.updateTask(validationTaskId, { status: "mission-validation" });
|
|
76003
76099
|
loopLog.log(`Created validation board task ${validationTaskId} for feature ${feature.id}`);
|
|
76004
|
-
const
|
|
76005
|
-
loopLog.log(`Started validator run ${
|
|
76006
|
-
const result = await this.runValidation(feature, assertions,
|
|
76100
|
+
const run2 = this.missionStore.startValidatorRun(feature.id, "task_completion", validationTaskId);
|
|
76101
|
+
loopLog.log(`Started validator run ${run2.id} for feature ${feature.id}`);
|
|
76102
|
+
const result = await this.runValidation(feature, assertions, run2);
|
|
76007
76103
|
if (result.status === "pass") {
|
|
76008
|
-
await this.handleValidationPass(feature.id,
|
|
76104
|
+
await this.handleValidationPass(feature.id, run2.id, result.summary, validationTaskId);
|
|
76009
76105
|
} else if (result.status === "fail") {
|
|
76010
|
-
await this.handleValidationFail(feature.id,
|
|
76106
|
+
await this.handleValidationFail(feature.id, run2.id, result, validationTaskId);
|
|
76011
76107
|
} else if (result.status === "blocked") {
|
|
76012
|
-
await this.handleValidationBlocked(feature.id,
|
|
76108
|
+
await this.handleValidationBlocked(feature.id, run2.id, result.blockedReason, validationTaskId);
|
|
76013
76109
|
} else if (result.status === "error") {
|
|
76014
|
-
await this.handleValidationError(feature.id,
|
|
76110
|
+
await this.handleValidationError(feature.id, run2.id, result.summary, validationTaskId);
|
|
76015
76111
|
}
|
|
76016
76112
|
} finally {
|
|
76017
76113
|
this.activeValidations.delete(feature.id);
|
|
@@ -76746,7 +76842,7 @@ Rules:
|
|
|
76746
76842
|
const tasksFailed = outcomes.filter((outcome) => outcome.outcome !== "completed").length;
|
|
76747
76843
|
const durations = outcomes.map((outcome) => outcome.durationMs).filter((duration) => typeof duration === "number" && Number.isFinite(duration));
|
|
76748
76844
|
const avgDurationMs = durations.length > 0 ? Math.round(durations.reduce((sum, duration) => sum + duration, 0) / durations.length) : performanceSummary?.avgDurationMs ?? 0;
|
|
76749
|
-
const runErrors = recentRuns.map((
|
|
76845
|
+
const runErrors = recentRuns.map((run2) => this.extractRunError(run2)).filter((value) => Boolean(value));
|
|
76750
76846
|
const mergedErrors = [
|
|
76751
76847
|
...performanceSummary?.commonErrors ?? [],
|
|
76752
76848
|
...outcomes.filter((outcome) => outcome.outcome !== "completed").map((outcome) => `${outcome.outcome}: ${outcome.taskId}`),
|
|
@@ -76760,11 +76856,11 @@ Rules:
|
|
|
76760
76856
|
commonErrors
|
|
76761
76857
|
};
|
|
76762
76858
|
}
|
|
76763
|
-
extractRunError(
|
|
76764
|
-
if (typeof
|
|
76765
|
-
return
|
|
76859
|
+
extractRunError(run2) {
|
|
76860
|
+
if (typeof run2.stderrExcerpt === "string" && run2.stderrExcerpt.trim()) {
|
|
76861
|
+
return run2.stderrExcerpt.trim().split("\n")[0] ?? null;
|
|
76766
76862
|
}
|
|
76767
|
-
const resultError =
|
|
76863
|
+
const resultError = run2.resultJson && typeof run2.resultJson.error === "string" ? run2.resultJson.error.trim() : "";
|
|
76768
76864
|
if (resultError) {
|
|
76769
76865
|
return resultError;
|
|
76770
76866
|
}
|
|
@@ -76772,8 +76868,8 @@ Rules:
|
|
|
76772
76868
|
}
|
|
76773
76869
|
extractTaskIdsFromRuns(runs) {
|
|
76774
76870
|
const ids = /* @__PURE__ */ new Set();
|
|
76775
|
-
for (const
|
|
76776
|
-
const taskId =
|
|
76871
|
+
for (const run2 of runs) {
|
|
76872
|
+
const taskId = run2.contextSnapshot?.taskId;
|
|
76777
76873
|
if (typeof taskId === "string" && taskId.trim()) {
|
|
76778
76874
|
ids.add(taskId.trim());
|
|
76779
76875
|
}
|
|
@@ -77309,9 +77405,9 @@ not loop on the same plan across heartbeats without recording why.`;
|
|
|
77309
77405
|
const msg = activeRunCheckErr instanceof Error ? activeRunCheckErr.message : String(activeRunCheckErr);
|
|
77310
77406
|
heartbeatLog.warn(`Failed to check for existing active run for ${agentId}: ${msg} \u2014 continuing with new run`);
|
|
77311
77407
|
}
|
|
77312
|
-
const
|
|
77408
|
+
const run2 = await this.store.startHeartbeatRun(agentId);
|
|
77313
77409
|
const enrichedRun = {
|
|
77314
|
-
...
|
|
77410
|
+
...run2,
|
|
77315
77411
|
invocationSource: options?.source ?? "on_demand",
|
|
77316
77412
|
triggerDetail: options?.triggerDetail ?? "manual",
|
|
77317
77413
|
contextSnapshot: options?.contextSnapshot,
|
|
@@ -77333,14 +77429,14 @@ not loop on the same plan across heartbeats without recording why.`;
|
|
|
77333
77429
|
* @param result - Execution results
|
|
77334
77430
|
*/
|
|
77335
77431
|
async completeRun(agentId, runId, result) {
|
|
77336
|
-
const
|
|
77337
|
-
if (!
|
|
77432
|
+
const run2 = await this.store.getRunDetail(agentId, runId);
|
|
77433
|
+
if (!run2) return;
|
|
77338
77434
|
const tracked = this.trackedAgents.get(agentId);
|
|
77339
77435
|
let completionResult = result;
|
|
77340
77436
|
const createdTasks = this.runCreatedTasks.get(agentId);
|
|
77341
77437
|
const enrichedResultJson = createdTasks?.length ? { ...completionResult.resultJson, tasksCreated: createdTasks } : completionResult.resultJson;
|
|
77342
77438
|
const completedRun = {
|
|
77343
|
-
...
|
|
77439
|
+
...run2,
|
|
77344
77440
|
endedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
77345
77441
|
status: completionResult.status,
|
|
77346
77442
|
exitCode: completionResult.exitCode,
|
|
@@ -77585,18 +77681,18 @@ not loop on the same plan across heartbeats without recording why.`;
|
|
|
77585
77681
|
...effectiveTriggeringCommentIds?.length ? { triggeringCommentIds: effectiveTriggeringCommentIds } : {},
|
|
77586
77682
|
...effectiveTriggeringCommentType ? { triggeringCommentType: effectiveTriggeringCommentType } : {}
|
|
77587
77683
|
};
|
|
77588
|
-
const
|
|
77684
|
+
const run2 = await this.startRun(agentId, {
|
|
77589
77685
|
source,
|
|
77590
77686
|
triggerDetail,
|
|
77591
77687
|
contextSnapshot: Object.keys(runContextSnapshot).length > 0 ? runContextSnapshot : void 0
|
|
77592
77688
|
});
|
|
77593
77689
|
const runContext = {
|
|
77594
|
-
runId:
|
|
77690
|
+
runId: run2.id,
|
|
77595
77691
|
agentId,
|
|
77596
77692
|
source
|
|
77597
77693
|
};
|
|
77598
77694
|
const engineRunContext = {
|
|
77599
|
-
runId:
|
|
77695
|
+
runId: run2.id,
|
|
77600
77696
|
agentId,
|
|
77601
77697
|
source,
|
|
77602
77698
|
phase: "heartbeat"
|
|
@@ -77618,21 +77714,21 @@ not loop on the same plan across heartbeats without recording why.`;
|
|
|
77618
77714
|
const budgetStatus = await this.store.getBudgetStatus(agentId);
|
|
77619
77715
|
if (budgetStatus.isOverBudget) {
|
|
77620
77716
|
heartbeatLog.log(`Agent ${agentId} budget exhausted \u2014 heartbeat skipped`);
|
|
77621
|
-
await this.completeRun(agentId,
|
|
77717
|
+
await this.completeRun(agentId, run2.id, {
|
|
77622
77718
|
status: "completed",
|
|
77623
77719
|
resultJson: { reason: "budget_exhausted", budgetStatus },
|
|
77624
77720
|
skipStateTransition: true
|
|
77625
77721
|
});
|
|
77626
|
-
return await this.store.getRunDetail(agentId,
|
|
77722
|
+
return await this.store.getRunDetail(agentId, run2.id);
|
|
77627
77723
|
}
|
|
77628
77724
|
if (budgetStatus.isOverThreshold && source === "timer") {
|
|
77629
77725
|
heartbeatLog.log(`Agent ${agentId} over budget threshold (${budgetStatus.usagePercent}%) \u2014 timer heartbeat skipped`);
|
|
77630
|
-
await this.completeRun(agentId,
|
|
77726
|
+
await this.completeRun(agentId, run2.id, {
|
|
77631
77727
|
status: "completed",
|
|
77632
77728
|
resultJson: { reason: "budget_threshold_exceeded", budgetStatus },
|
|
77633
77729
|
skipStateTransition: true
|
|
77634
77730
|
});
|
|
77635
|
-
return await this.store.getRunDetail(agentId,
|
|
77731
|
+
return await this.store.getRunDetail(agentId, run2.id);
|
|
77636
77732
|
}
|
|
77637
77733
|
} catch (budgetErr) {
|
|
77638
77734
|
heartbeatLog.warn(`Agent ${agentId} budget status check failed: ${budgetErr instanceof Error ? budgetErr.message : String(budgetErr)} \u2014 proceeding without budget check`);
|
|
@@ -77641,21 +77737,21 @@ not loop on the same plan across heartbeats without recording why.`;
|
|
|
77641
77737
|
const settings = await taskStore.getSettings();
|
|
77642
77738
|
if (settings.globalPause) {
|
|
77643
77739
|
heartbeatLog.log(`Agent ${agentId} heartbeat skipped \u2014 global pause active (source=${source})`);
|
|
77644
|
-
await this.completeRun(agentId,
|
|
77740
|
+
await this.completeRun(agentId, run2.id, {
|
|
77645
77741
|
status: "completed",
|
|
77646
77742
|
resultJson: { reason: "global_pause", source },
|
|
77647
77743
|
skipStateTransition: true
|
|
77648
77744
|
});
|
|
77649
|
-
return await this.store.getRunDetail(agentId,
|
|
77745
|
+
return await this.store.getRunDetail(agentId, run2.id);
|
|
77650
77746
|
}
|
|
77651
77747
|
if (settings.enginePaused && source === "timer") {
|
|
77652
77748
|
heartbeatLog.log(`Agent ${agentId} timer heartbeat skipped \u2014 engine paused (soft pause)`);
|
|
77653
|
-
await this.completeRun(agentId,
|
|
77749
|
+
await this.completeRun(agentId, run2.id, {
|
|
77654
77750
|
status: "completed",
|
|
77655
77751
|
resultJson: { reason: "engine_paused", source },
|
|
77656
77752
|
skipStateTransition: true
|
|
77657
77753
|
});
|
|
77658
|
-
return await this.store.getRunDetail(agentId,
|
|
77754
|
+
return await this.store.getRunDetail(agentId, run2.id);
|
|
77659
77755
|
}
|
|
77660
77756
|
} catch (pauseErr) {
|
|
77661
77757
|
heartbeatLog.warn(`Pause status check failed for ${agentId}: ${pauseErr instanceof Error ? pauseErr.message : String(pauseErr)} \u2014 proceeding`);
|
|
@@ -77663,11 +77759,11 @@ not loop on the same plan across heartbeats without recording why.`;
|
|
|
77663
77759
|
const agent = preloadedAgent ?? await this.store.getAgent(agentId);
|
|
77664
77760
|
if (!agent) {
|
|
77665
77761
|
heartbeatLog.warn(`Agent ${agentId} not found \u2014 completing run as failed`);
|
|
77666
|
-
await this.completeRun(agentId,
|
|
77762
|
+
await this.completeRun(agentId, run2.id, {
|
|
77667
77763
|
status: "failed",
|
|
77668
77764
|
stderrExcerpt: `Agent ${agentId} not found`
|
|
77669
77765
|
});
|
|
77670
|
-
return await this.store.getRunDetail(agentId,
|
|
77766
|
+
return await this.store.getRunDetail(agentId, run2.id);
|
|
77671
77767
|
}
|
|
77672
77768
|
const agentHasIdentity = hasAgentIdentity(agent);
|
|
77673
77769
|
const isAgentEphemeral = isEphemeralAgent(agent);
|
|
@@ -77696,11 +77792,11 @@ not loop on the same plan across heartbeats without recording why.`;
|
|
|
77696
77792
|
}
|
|
77697
77793
|
}
|
|
77698
77794
|
}
|
|
77699
|
-
if (taskId &&
|
|
77795
|
+
if (taskId && run2.contextSnapshot?.taskId !== taskId) {
|
|
77700
77796
|
const updatedRun = {
|
|
77701
|
-
...
|
|
77797
|
+
...run2,
|
|
77702
77798
|
contextSnapshot: {
|
|
77703
|
-
...
|
|
77799
|
+
...run2.contextSnapshot ?? {},
|
|
77704
77800
|
taskId
|
|
77705
77801
|
}
|
|
77706
77802
|
};
|
|
@@ -77710,11 +77806,11 @@ not loop on the same plan across heartbeats without recording why.`;
|
|
|
77710
77806
|
if (!taskId) {
|
|
77711
77807
|
if (!canRunNoTaskHeartbeat) {
|
|
77712
77808
|
heartbeatLog.log(`Agent ${agentId} has no task assignment \u2014 graceful exit`);
|
|
77713
|
-
await this.completeRun(agentId,
|
|
77809
|
+
await this.completeRun(agentId, run2.id, {
|
|
77714
77810
|
status: "completed",
|
|
77715
77811
|
resultJson: { reason: "no_assignment" }
|
|
77716
77812
|
});
|
|
77717
|
-
return await this.store.getRunDetail(agentId,
|
|
77813
|
+
return await this.store.getRunDetail(agentId, run2.id);
|
|
77718
77814
|
}
|
|
77719
77815
|
heartbeatLog.log(`Agent ${agentId} has no task but has identity \u2014 running no-task heartbeat`);
|
|
77720
77816
|
}
|
|
@@ -77723,12 +77819,12 @@ not loop on the same plan across heartbeats without recording why.`;
|
|
|
77723
77819
|
const validStates = ["active", "running", "idle"];
|
|
77724
77820
|
if (!validStates.includes(agent.state)) {
|
|
77725
77821
|
heartbeatLog.log(`Agent ${agentId} state is "${agent.state}" \u2014 graceful exit`);
|
|
77726
|
-
await this.completeRun(agentId,
|
|
77822
|
+
await this.completeRun(agentId, run2.id, {
|
|
77727
77823
|
status: "completed",
|
|
77728
77824
|
resultJson: { reason: "invalid_state", state: agent.state },
|
|
77729
77825
|
skipStateTransition: true
|
|
77730
77826
|
});
|
|
77731
|
-
return await this.store.getRunDetail(agentId,
|
|
77827
|
+
return await this.store.getRunDetail(agentId, run2.id);
|
|
77732
77828
|
}
|
|
77733
77829
|
}
|
|
77734
77830
|
let taskDetail;
|
|
@@ -77738,11 +77834,11 @@ not loop on the same plan across heartbeats without recording why.`;
|
|
|
77738
77834
|
taskDetail = await taskStore.getTask(resolvedTaskId2);
|
|
77739
77835
|
} catch (taskDetailErr) {
|
|
77740
77836
|
heartbeatLog.warn(`Task ${resolvedTaskId2} fetch failed: ${taskDetailErr instanceof Error ? taskDetailErr.message : String(taskDetailErr)} \u2014 graceful exit`);
|
|
77741
|
-
await this.completeRun(agentId,
|
|
77837
|
+
await this.completeRun(agentId, run2.id, {
|
|
77742
77838
|
status: "completed",
|
|
77743
77839
|
resultJson: { reason: "task_not_found", taskId: resolvedTaskId2 }
|
|
77744
77840
|
});
|
|
77745
|
-
return await this.store.getRunDetail(agentId,
|
|
77841
|
+
return await this.store.getRunDetail(agentId, run2.id);
|
|
77746
77842
|
}
|
|
77747
77843
|
if (taskDetail.column === "done" || taskDetail.column === "archived") {
|
|
77748
77844
|
if (agent.taskId === resolvedTaskId2) {
|
|
@@ -77760,21 +77856,21 @@ not loop on the same plan across heartbeats without recording why.`;
|
|
|
77760
77856
|
taskDetail = void 0;
|
|
77761
77857
|
isNoTaskRun = true;
|
|
77762
77858
|
if (!canRunNoTaskHeartbeat) {
|
|
77763
|
-
await this.completeRun(agentId,
|
|
77859
|
+
await this.completeRun(agentId, run2.id, {
|
|
77764
77860
|
status: "completed",
|
|
77765
77861
|
resultJson: { reason: "no_assignment" }
|
|
77766
77862
|
});
|
|
77767
|
-
return await this.store.getRunDetail(agentId,
|
|
77863
|
+
return await this.store.getRunDetail(agentId, run2.id);
|
|
77768
77864
|
}
|
|
77769
77865
|
} else {
|
|
77770
77866
|
heartbeatLog.log(
|
|
77771
77867
|
`Heartbeat for ${agentId} targeted terminal task ${resolvedTaskId2} (${taskDetail.column}) \u2014 graceful exit`
|
|
77772
77868
|
);
|
|
77773
|
-
await this.completeRun(agentId,
|
|
77869
|
+
await this.completeRun(agentId, run2.id, {
|
|
77774
77870
|
status: "completed",
|
|
77775
77871
|
resultJson: { reason: "terminal_task", taskId: resolvedTaskId2, column: taskDetail.column }
|
|
77776
77872
|
});
|
|
77777
|
-
return await this.store.getRunDetail(agentId,
|
|
77873
|
+
return await this.store.getRunDetail(agentId, run2.id);
|
|
77778
77874
|
}
|
|
77779
77875
|
}
|
|
77780
77876
|
if (isNoTaskRun) {
|
|
@@ -77783,17 +77879,17 @@ not loop on the same plan across heartbeats without recording why.`;
|
|
|
77783
77879
|
const liveTaskDetail = taskDetail;
|
|
77784
77880
|
if (!liveTaskDetail) {
|
|
77785
77881
|
heartbeatLog.warn(`Task ${resolvedTaskId2} lost detail after terminal-assignment handling \u2014 graceful exit`);
|
|
77786
|
-
await this.completeRun(agentId,
|
|
77882
|
+
await this.completeRun(agentId, run2.id, {
|
|
77787
77883
|
status: "completed",
|
|
77788
77884
|
resultJson: { reason: "task_not_found", taskId: resolvedTaskId2 }
|
|
77789
77885
|
});
|
|
77790
|
-
return await this.store.getRunDetail(agentId,
|
|
77886
|
+
return await this.store.getRunDetail(agentId, run2.id);
|
|
77791
77887
|
}
|
|
77792
77888
|
if (liveTaskDetail.checkedOutBy && liveTaskDetail.checkedOutBy !== agentId) {
|
|
77793
77889
|
heartbeatLog.warn(
|
|
77794
77890
|
`Agent ${agentId} does not hold checkout for ${resolvedTaskId2} (held by ${liveTaskDetail.checkedOutBy}) \u2014 graceful exit`
|
|
77795
77891
|
);
|
|
77796
|
-
await this.completeRun(agentId,
|
|
77892
|
+
await this.completeRun(agentId, run2.id, {
|
|
77797
77893
|
status: "completed",
|
|
77798
77894
|
resultJson: {
|
|
77799
77895
|
reason: "checkout_conflict",
|
|
@@ -77801,7 +77897,7 @@ not loop on the same plan across heartbeats without recording why.`;
|
|
|
77801
77897
|
checkedOutBy: liveTaskDetail.checkedOutBy
|
|
77802
77898
|
}
|
|
77803
77899
|
});
|
|
77804
|
-
return await this.store.getRunDetail(agentId,
|
|
77900
|
+
return await this.store.getRunDetail(agentId, run2.id);
|
|
77805
77901
|
}
|
|
77806
77902
|
const blockedBy = typeof liveTaskDetail.blockedBy === "string" ? liveTaskDetail.blockedBy.trim() : "";
|
|
77807
77903
|
const isBlockedTask = liveTaskDetail.status === "queued" && blockedBy.length > 0;
|
|
@@ -77820,22 +77916,22 @@ not loop on the same plan across heartbeats without recording why.`;
|
|
|
77820
77916
|
};
|
|
77821
77917
|
const previousBlockedState = await this.store.getLastBlockedState(agentId);
|
|
77822
77918
|
if (previousBlockedState && isBlockedStateDuplicate(currentBlockedState, previousBlockedState)) {
|
|
77823
|
-
await this.completeRun(agentId,
|
|
77919
|
+
await this.completeRun(agentId, run2.id, {
|
|
77824
77920
|
status: "completed",
|
|
77825
77921
|
resultJson: { reason: "blocked_duplicate", taskId: resolvedTaskId2, blockedBy }
|
|
77826
77922
|
});
|
|
77827
|
-
return await this.store.getRunDetail(agentId,
|
|
77923
|
+
return await this.store.getRunDetail(agentId, run2.id);
|
|
77828
77924
|
}
|
|
77829
77925
|
const blockedMessage = `Task is blocked by ${blockedBy}; waiting for dependency/context changes before retrying.`;
|
|
77830
77926
|
await taskStore.addComment(resolvedTaskId2, blockedMessage, "agent", void 0, runContext);
|
|
77831
77927
|
await audit.database({ type: "task:comment:add", target: resolvedTaskId2, metadata: { blockedBy } });
|
|
77832
77928
|
await this.store.setLastBlockedState(agentId, currentBlockedState);
|
|
77833
77929
|
heartbeatLog.log(`Task ${resolvedTaskId2} is blocked by ${blockedBy} \u2014 recorded blocked state`);
|
|
77834
|
-
await this.completeRun(agentId,
|
|
77930
|
+
await this.completeRun(agentId, run2.id, {
|
|
77835
77931
|
status: "completed",
|
|
77836
77932
|
resultJson: { reason: "blocked", taskId: resolvedTaskId2, blockedBy }
|
|
77837
77933
|
});
|
|
77838
|
-
return await this.store.getRunDetail(agentId,
|
|
77934
|
+
return await this.store.getRunDetail(agentId, run2.id);
|
|
77839
77935
|
}
|
|
77840
77936
|
}
|
|
77841
77937
|
}
|
|
@@ -77930,7 +78026,7 @@ not loop on the same plan across heartbeats without recording why.`;
|
|
|
77930
78026
|
heartbeatTools.push(heartbeatDoneTool);
|
|
77931
78027
|
if (isNoTaskRun) {
|
|
77932
78028
|
agentLogger = new AgentLogger({
|
|
77933
|
-
appendLog: (entry) => this.store.appendRunLog(agentId,
|
|
78029
|
+
appendLog: (entry) => this.store.appendRunLog(agentId, run2.id, entry),
|
|
77934
78030
|
agent: agent.role,
|
|
77935
78031
|
persistAgentToolOutput: memorySettings?.persistAgentToolOutput
|
|
77936
78032
|
});
|
|
@@ -77939,7 +78035,7 @@ not loop on the same plan across heartbeats without recording why.`;
|
|
|
77939
78035
|
store: taskStore,
|
|
77940
78036
|
taskId,
|
|
77941
78037
|
agent: agent.role,
|
|
77942
|
-
appendLog: (entry) => this.store.appendRunLog(agentId,
|
|
78038
|
+
appendLog: (entry) => this.store.appendRunLog(agentId, run2.id, entry),
|
|
77943
78039
|
persistAgentToolOutput: memorySettings?.persistAgentToolOutput
|
|
77944
78040
|
});
|
|
77945
78041
|
}
|
|
@@ -77973,7 +78069,7 @@ not loop on the same plan across heartbeats without recording why.`;
|
|
|
77973
78069
|
// Skill selection: use waking agent's skills (heartbeat has no role fallback)
|
|
77974
78070
|
...skillContext.skillSelectionContext ? { skillSelection: skillContext.skillSelectionContext } : {}
|
|
77975
78071
|
});
|
|
77976
|
-
this.trackAgent(agentId, { dispose: () => session.dispose() },
|
|
78072
|
+
this.trackAgent(agentId, { dispose: () => session.dispose() }, run2.id);
|
|
77977
78073
|
try {
|
|
77978
78074
|
let pendingMessages = [];
|
|
77979
78075
|
let executionPrompt;
|
|
@@ -78137,15 +78233,15 @@ ${taskDetail.prompt}` : "No PROMPT.md available.",
|
|
|
78137
78233
|
}
|
|
78138
78234
|
try {
|
|
78139
78235
|
const runWithPrompts = {
|
|
78140
|
-
...
|
|
78236
|
+
...run2,
|
|
78141
78237
|
systemPrompt: truncatePrompt(systemPrompt, 1e5),
|
|
78142
78238
|
executionPrompt: truncatePrompt(executionPrompt, 1e5),
|
|
78143
78239
|
heartbeatProcedureSource: customProcedure ? "custom" : "default"
|
|
78144
78240
|
};
|
|
78145
78241
|
await this.store.saveRun(runWithPrompts);
|
|
78146
|
-
Object.assign(
|
|
78242
|
+
Object.assign(run2, { systemPrompt: runWithPrompts.systemPrompt, executionPrompt: runWithPrompts.executionPrompt, heartbeatProcedureSource: runWithPrompts.heartbeatProcedureSource });
|
|
78147
78243
|
} catch (promptPersistErr) {
|
|
78148
|
-
heartbeatLog.warn(`Failed to persist prompts for ${agentId}/${
|
|
78244
|
+
heartbeatLog.warn(`Failed to persist prompts for ${agentId}/${run2.id}: ${promptPersistErr instanceof Error ? promptPersistErr.message : String(promptPersistErr)}`);
|
|
78149
78245
|
}
|
|
78150
78246
|
await promptWithFallback(session, executionPrompt);
|
|
78151
78247
|
let usageInput = 0;
|
|
@@ -78189,7 +78285,7 @@ ${taskDetail.prompt}` : "No PROMPT.md available.",
|
|
|
78189
78285
|
completionResultJson.priority = inboxSelection.priority;
|
|
78190
78286
|
completionResultJson.taskId = taskId;
|
|
78191
78287
|
}
|
|
78192
|
-
await this.completeRun(agentId,
|
|
78288
|
+
await this.completeRun(agentId, run2.id, {
|
|
78193
78289
|
status: "completed",
|
|
78194
78290
|
usageJson: { inputTokens: usageInput, outputTokens: usageOutput, cachedTokens: usageCached },
|
|
78195
78291
|
resultJson: completionResultJson,
|
|
@@ -78200,7 +78296,7 @@ ${taskDetail.prompt}` : "No PROMPT.md available.",
|
|
|
78200
78296
|
const errorDetail = formatError(err).detail;
|
|
78201
78297
|
heartbeatLog.error(`Heartbeat execution failed for ${agentId}: ${errorDetail}`);
|
|
78202
78298
|
await flushAgentLogger();
|
|
78203
|
-
await this.completeRun(agentId,
|
|
78299
|
+
await this.completeRun(agentId, run2.id, {
|
|
78204
78300
|
status: "failed",
|
|
78205
78301
|
stderrExcerpt: errorDetail,
|
|
78206
78302
|
stdoutExcerpt: stdoutExcerpt || void 0
|
|
@@ -78219,22 +78315,22 @@ ${taskDetail.prompt}` : "No PROMPT.md available.",
|
|
|
78219
78315
|
heartbeatLog.warn(`session.dispose() failed for ${agentId}: ${errorMessage}`);
|
|
78220
78316
|
}
|
|
78221
78317
|
}
|
|
78222
|
-
return await this.store.getRunDetail(agentId,
|
|
78318
|
+
return await this.store.getRunDetail(agentId, run2.id);
|
|
78223
78319
|
} catch (err) {
|
|
78224
78320
|
const errorDetail = formatError(err).detail;
|
|
78225
78321
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
78226
78322
|
heartbeatLog.error(`Heartbeat execution error for ${agentId}: ${errorDetail}`);
|
|
78227
78323
|
await flushAgentLogger();
|
|
78228
78324
|
try {
|
|
78229
|
-
await this.completeRun(agentId,
|
|
78325
|
+
await this.completeRun(agentId, run2.id, {
|
|
78230
78326
|
status: "failed",
|
|
78231
78327
|
stderrExcerpt: errorDetail
|
|
78232
78328
|
});
|
|
78233
78329
|
} catch (completeRunErr) {
|
|
78234
78330
|
const completeRunErrMsg = completeRunErr instanceof Error ? completeRunErr.message : String(completeRunErr);
|
|
78235
|
-
heartbeatLog.error(`completeRun failed for ${agentId}/${
|
|
78331
|
+
heartbeatLog.error(`completeRun failed for ${agentId}/${run2.id}: ${completeRunErrMsg} \u2014 attempting safety-net completion`);
|
|
78236
78332
|
try {
|
|
78237
|
-
const runDetail = await this.store.getRunDetail(agentId,
|
|
78333
|
+
const runDetail = await this.store.getRunDetail(agentId, run2.id);
|
|
78238
78334
|
if (runDetail && runDetail.status !== "completed" && runDetail.status !== "failed" && runDetail.status !== "terminated") {
|
|
78239
78335
|
await this.store.saveRun({
|
|
78240
78336
|
...runDetail,
|
|
@@ -78242,16 +78338,16 @@ ${taskDetail.prompt}` : "No PROMPT.md available.",
|
|
|
78242
78338
|
status: "failed",
|
|
78243
78339
|
stderrExcerpt: `Heartbeat execution failed: ${errorMessage}. Run completion also failed: ${completeRunErrMsg}`
|
|
78244
78340
|
});
|
|
78245
|
-
await this.store.endHeartbeatRun(
|
|
78341
|
+
await this.store.endHeartbeatRun(run2.id, "terminated");
|
|
78246
78342
|
this.clearRunState(agentId);
|
|
78247
|
-
heartbeatLog.log(`Safety-net run completion for ${agentId}/${
|
|
78343
|
+
heartbeatLog.log(`Safety-net run completion for ${agentId}/${run2.id} \u2014 run terminated`);
|
|
78248
78344
|
}
|
|
78249
78345
|
} catch (safetyNetErr) {
|
|
78250
78346
|
const safetyNetErrMsg = safetyNetErr instanceof Error ? safetyNetErr.message : String(safetyNetErr);
|
|
78251
|
-
heartbeatLog.error(`Safety-net run completion also failed for ${agentId}/${
|
|
78347
|
+
heartbeatLog.error(`Safety-net run completion also failed for ${agentId}/${run2.id}: ${safetyNetErrMsg} \u2014 run may be stuck permanently`);
|
|
78252
78348
|
}
|
|
78253
78349
|
}
|
|
78254
|
-
return await this.store.getRunDetail(agentId,
|
|
78350
|
+
return await this.store.getRunDetail(agentId, run2.id);
|
|
78255
78351
|
}
|
|
78256
78352
|
});
|
|
78257
78353
|
}
|
|
@@ -79988,7 +80084,7 @@ var init_routine_runner = __esm({
|
|
|
79988
80084
|
return Boolean(routine.steps && routine.steps.length > 0 || routine.command?.trim());
|
|
79989
80085
|
}
|
|
79990
80086
|
async executeAgentRoutine(routine, triggerType, context) {
|
|
79991
|
-
const
|
|
80087
|
+
const run2 = await this.options.heartbeatMonitor.executeHeartbeat({
|
|
79992
80088
|
agentId: routine.agentId,
|
|
79993
80089
|
source: "routine",
|
|
79994
80090
|
triggerDetail: `routine:${routine.id}:${triggerType}`,
|
|
@@ -79999,8 +80095,8 @@ var init_routine_runner = __esm({
|
|
|
79999
80095
|
...context
|
|
80000
80096
|
}
|
|
80001
80097
|
});
|
|
80002
|
-
if (
|
|
80003
|
-
const error =
|
|
80098
|
+
if (run2.status === "failed" || run2.status === "terminated") {
|
|
80099
|
+
const error = run2.stderrExcerpt || `Run ${run2.status}`;
|
|
80004
80100
|
return {
|
|
80005
80101
|
success: false,
|
|
80006
80102
|
output: error,
|
|
@@ -80011,7 +80107,7 @@ var init_routine_runner = __esm({
|
|
|
80011
80107
|
}
|
|
80012
80108
|
return {
|
|
80013
80109
|
success: true,
|
|
80014
|
-
output:
|
|
80110
|
+
output: run2.resultJson ? JSON.stringify(run2.resultJson) : "Routine completed successfully",
|
|
80015
80111
|
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
80016
80112
|
completedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
80017
80113
|
};
|
|
@@ -80794,14 +80890,15 @@ var init_self_healing = __esm({
|
|
|
80794
80890
|
execAsync7 = promisify9(exec9);
|
|
80795
80891
|
APPROVED_TRIAGE_RECOVERY_GRACE_MS = 6e4;
|
|
80796
80892
|
ORPHANED_EXECUTION_RECOVERY_GRACE_MS = 6e4;
|
|
80797
|
-
ACTIVE_MERGE_STATUSES = /* @__PURE__ */ new Set(["merging", "merging-pr"]);
|
|
80893
|
+
ACTIVE_MERGE_STATUSES = /* @__PURE__ */ new Set(["merging", "merging-pr", "merging-fix"]);
|
|
80798
80894
|
NON_TERMINAL_STEP_STATUSES2 = /* @__PURE__ */ new Set(["pending", "in-progress"]);
|
|
80799
80895
|
GHOST_REVIEW_PRESERVED_STATUSES = /* @__PURE__ */ new Set([
|
|
80800
80896
|
"failed",
|
|
80801
80897
|
"awaiting-user-review",
|
|
80802
80898
|
"awaiting-approval",
|
|
80803
80899
|
"merging",
|
|
80804
|
-
"merging-pr"
|
|
80900
|
+
"merging-pr",
|
|
80901
|
+
"merging-fix"
|
|
80805
80902
|
]);
|
|
80806
80903
|
ORPHANED_WITH_WORKTREE_GRACE_MS = 3e5;
|
|
80807
80904
|
MAX_TASK_DONE_RETRIES = 3;
|
|
@@ -81524,7 +81621,7 @@ var init_self_healing = __esm({
|
|
|
81524
81621
|
*
|
|
81525
81622
|
* Preserved statuses (skipped):
|
|
81526
81623
|
* - `awaiting-user-review`, `awaiting-approval`: explicit human handoff
|
|
81527
|
-
* - `merging`, `merging-pr`: handled by `recoverInterruptedMergingTasks`
|
|
81624
|
+
* - `merging`, `merging-pr`, `merging-fix`: handled by `recoverInterruptedMergingTasks`
|
|
81528
81625
|
*
|
|
81529
81626
|
* Rate-limiting comes from the `updatedAt >= taskStuckTimeoutMs` gate —
|
|
81530
81627
|
* each kick refreshes `updatedAt`, so a task that re-enters review and gets
|
|
@@ -87140,7 +87237,7 @@ ${detail}`
|
|
|
87140
87237
|
"agent"
|
|
87141
87238
|
);
|
|
87142
87239
|
await store.updateTask(taskId, {
|
|
87143
|
-
status:
|
|
87240
|
+
status: "merging-fix",
|
|
87144
87241
|
mergeRetries: 0,
|
|
87145
87242
|
error: null,
|
|
87146
87243
|
verificationFailureCount: nextBounces
|
|
@@ -87148,10 +87245,10 @@ ${detail}`
|
|
|
87148
87245
|
await store.moveTask(taskId, "in-progress");
|
|
87149
87246
|
await store.logEntry(
|
|
87150
87247
|
taskId,
|
|
87151
|
-
`Deterministic ${failedKind} verification failed (${nextBounces}/${cap}) \u2014 moved back to in-progress for remediation`
|
|
87248
|
+
`Deterministic ${failedKind} verification failed (${nextBounces}/${cap}) \u2014 moved back to in-progress with status=merging-fix for remediation`
|
|
87152
87249
|
);
|
|
87153
87250
|
runtimeLog.log(
|
|
87154
|
-
`Auto-merge: ${taskId} deterministic ${failedKind} verification failed (${nextBounces}/${cap}) \u2014 moved to in-progress`
|
|
87251
|
+
`Auto-merge: ${taskId} deterministic ${failedKind} verification failed (${nextBounces}/${cap}) \u2014 moved to in-progress with status=merging-fix`
|
|
87155
87252
|
);
|
|
87156
87253
|
} catch {
|
|
87157
87254
|
runtimeLog.error(
|
|
@@ -94297,9 +94394,22 @@ function registerTaskWorkflowRoutes(ctx, deps) {
|
|
|
94297
94394
|
const { store: scopedStore } = await getProjectContext3(req);
|
|
94298
94395
|
const task = await scopedStore.getTask(req.params.id);
|
|
94299
94396
|
const retrySpecification = task.column === "triage" && (task.status === "failed" || task.status === "planning" || task.status === "needs-replan" || (task.stuckKillCount ?? 0) > 0);
|
|
94397
|
+
const isInReviewRetry = task.column === "in-review" && (task.status === "failed" || task.status === "stuck-killed");
|
|
94300
94398
|
if (task.status !== "failed" && task.status !== "stuck-killed" && !retrySpecification) {
|
|
94301
94399
|
throw badRequest(`Task is not in a retryable state (current status: ${task.status || "none"})`);
|
|
94302
94400
|
}
|
|
94401
|
+
if (isInReviewRetry) {
|
|
94402
|
+
await scopedStore.updateTask(req.params.id, {
|
|
94403
|
+
status: null,
|
|
94404
|
+
error: null,
|
|
94405
|
+
stuckKillCount: 0,
|
|
94406
|
+
mergeRetries: 0
|
|
94407
|
+
});
|
|
94408
|
+
await scopedStore.logEntry(req.params.id, "Retry requested from dashboard (in-review retry, mergeRetries reset)");
|
|
94409
|
+
const updated2 = await scopedStore.getTask(req.params.id);
|
|
94410
|
+
res.json(updated2);
|
|
94411
|
+
return;
|
|
94412
|
+
}
|
|
94303
94413
|
await scopedStore.updateTask(req.params.id, {
|
|
94304
94414
|
status: retrySpecification ? "needs-replan" : null,
|
|
94305
94415
|
error: null,
|
|
@@ -94722,8 +94832,12 @@ function registerTaskWorkflowRoutes(ctx, deps) {
|
|
|
94722
94832
|
router.post("/tasks/:id/pause", async (req, res) => {
|
|
94723
94833
|
try {
|
|
94724
94834
|
const { store: scopedStore } = await getProjectContext3(req);
|
|
94725
|
-
const task = await scopedStore.
|
|
94726
|
-
|
|
94835
|
+
const task = await scopedStore.getTask(req.params.id);
|
|
94836
|
+
if (task.assignedAgentId) {
|
|
94837
|
+
throw conflict(`Cannot manually pause/unpause task assigned to agent ${task.assignedAgentId}. Use agent pause controls instead.`);
|
|
94838
|
+
}
|
|
94839
|
+
const updated = await scopedStore.pauseTask(req.params.id, true);
|
|
94840
|
+
res.json(updated);
|
|
94727
94841
|
} catch (err) {
|
|
94728
94842
|
if (err instanceof ApiError) {
|
|
94729
94843
|
throw err;
|
|
@@ -94734,8 +94848,12 @@ function registerTaskWorkflowRoutes(ctx, deps) {
|
|
|
94734
94848
|
router.post("/tasks/:id/unpause", async (req, res) => {
|
|
94735
94849
|
try {
|
|
94736
94850
|
const { store: scopedStore } = await getProjectContext3(req);
|
|
94737
|
-
const task = await scopedStore.
|
|
94738
|
-
|
|
94851
|
+
const task = await scopedStore.getTask(req.params.id);
|
|
94852
|
+
if (task.assignedAgentId) {
|
|
94853
|
+
throw conflict(`Cannot manually pause/unpause task assigned to agent ${task.assignedAgentId}. Use agent pause controls instead.`);
|
|
94854
|
+
}
|
|
94855
|
+
const updated = await scopedStore.pauseTask(req.params.id, false);
|
|
94856
|
+
res.json(updated);
|
|
94739
94857
|
} catch (err) {
|
|
94740
94858
|
if (err instanceof ApiError) {
|
|
94741
94859
|
throw err;
|
|
@@ -111900,8 +112018,8 @@ function auto(tasks, concurrency, callback) {
|
|
|
111900
112018
|
return callback(null, results);
|
|
111901
112019
|
}
|
|
111902
112020
|
while (readyTasks.length && runningTasks < concurrency) {
|
|
111903
|
-
var
|
|
111904
|
-
|
|
112021
|
+
var run2 = readyTasks.shift();
|
|
112022
|
+
run2();
|
|
111905
112023
|
}
|
|
111906
112024
|
}
|
|
111907
112025
|
function addListener(taskName, fn) {
|
|
@@ -136929,7 +137047,7 @@ var init_register_project_routes = __esm({
|
|
|
136929
137047
|
} = fsPromises);
|
|
136930
137048
|
registerProjectRoutes = (ctx) => {
|
|
136931
137049
|
const { router, options, runtimeLogger, prioritizeProjectsForCurrentDirectory, rethrowAsApiError: rethrowAsApiError8 } = ctx;
|
|
136932
|
-
async function withCentralCore(
|
|
137050
|
+
async function withCentralCore(run2, onError) {
|
|
136933
137051
|
const sharedCentral = options?.centralCore;
|
|
136934
137052
|
const shouldClose = !sharedCentral;
|
|
136935
137053
|
const central = sharedCentral ?? new (await Promise.resolve().then(() => (init_src(), src_exports))).CentralCore();
|
|
@@ -136937,7 +137055,7 @@ var init_register_project_routes = __esm({
|
|
|
136937
137055
|
if (!sharedCentral || typeof central.isInitialized === "function" && !central.isInitialized()) {
|
|
136938
137056
|
await central.init();
|
|
136939
137057
|
}
|
|
136940
|
-
return await
|
|
137058
|
+
return await run2(central);
|
|
136941
137059
|
} catch (error) {
|
|
136942
137060
|
if (onError) {
|
|
136943
137061
|
return await onError(error);
|
|
@@ -137674,6 +137792,32 @@ function sanitizeExtraClis(input) {
|
|
|
137674
137792
|
}
|
|
137675
137793
|
return input;
|
|
137676
137794
|
}
|
|
137795
|
+
function toManagedDockerNodeInfo(managedNode, linkedNode) {
|
|
137796
|
+
return {
|
|
137797
|
+
...managedNode,
|
|
137798
|
+
hostConfig: {
|
|
137799
|
+
type: managedNode.hostConfig.host || managedNode.hostConfig.context ? "remote" : "local",
|
|
137800
|
+
host: managedNode.hostConfig.host,
|
|
137801
|
+
context: managedNode.hostConfig.context,
|
|
137802
|
+
tlsOptions: {
|
|
137803
|
+
tlsVerify: managedNode.hostConfig.tlsVerify,
|
|
137804
|
+
tlsCaPath: managedNode.hostConfig.tlsCaPath,
|
|
137805
|
+
tlsCertPath: managedNode.hostConfig.tlsCertPath,
|
|
137806
|
+
tlsKeyPath: managedNode.hostConfig.tlsKeyPath
|
|
137807
|
+
}
|
|
137808
|
+
},
|
|
137809
|
+
volumeMounts: managedNode.volumeMounts.map((mount) => ({
|
|
137810
|
+
hostPath: mount.hostPath,
|
|
137811
|
+
containerPath: mount.containerPath,
|
|
137812
|
+
readOnly: mount.mode === "ro" ? true : void 0
|
|
137813
|
+
})),
|
|
137814
|
+
resourceSizing: {
|
|
137815
|
+
cpuLimit: managedNode.resourceSizing?.cpus !== void 0 ? String(managedNode.resourceSizing.cpus) : void 0,
|
|
137816
|
+
memoryLimit: managedNode.resourceSizing?.memoryMB !== void 0 ? `${managedNode.resourceSizing.memoryMB}MB` : void 0
|
|
137817
|
+
},
|
|
137818
|
+
linkedNode: linkedNode ?? void 0
|
|
137819
|
+
};
|
|
137820
|
+
}
|
|
137677
137821
|
var VALID_EXTRA_CLIS, registerDockerNodeRoutes;
|
|
137678
137822
|
var init_register_docker_node_routes = __esm({
|
|
137679
137823
|
"../dashboard/src/routes/register-docker-node-routes.ts"() {
|
|
@@ -137722,6 +137866,126 @@ var init_register_docker_node_routes = __esm({
|
|
|
137722
137866
|
res.json({ available: false, error: message });
|
|
137723
137867
|
}
|
|
137724
137868
|
});
|
|
137869
|
+
router.get("/docker/nodes", async (_req, res) => {
|
|
137870
|
+
try {
|
|
137871
|
+
const { CentralCore: CentralCore2 } = await Promise.resolve().then(() => (init_src(), src_exports));
|
|
137872
|
+
const central = new CentralCore2();
|
|
137873
|
+
await central.init();
|
|
137874
|
+
try {
|
|
137875
|
+
const nodes = await central.listManagedDockerNodes();
|
|
137876
|
+
const enriched = await Promise.all(nodes.map(async (managedNode) => {
|
|
137877
|
+
const linkedNode = managedNode.nodeId ? await central.getNode(managedNode.nodeId) : void 0;
|
|
137878
|
+
return toManagedDockerNodeInfo(managedNode, linkedNode);
|
|
137879
|
+
}));
|
|
137880
|
+
enriched.sort((a, b) => (a.name ?? "").localeCompare(b.name ?? ""));
|
|
137881
|
+
res.json(enriched);
|
|
137882
|
+
} finally {
|
|
137883
|
+
await central.close();
|
|
137884
|
+
}
|
|
137885
|
+
} catch (error) {
|
|
137886
|
+
if (error instanceof ApiError) {
|
|
137887
|
+
throw error;
|
|
137888
|
+
}
|
|
137889
|
+
rethrowAsApiError8(error);
|
|
137890
|
+
}
|
|
137891
|
+
});
|
|
137892
|
+
router.get("/docker/nodes/:managedId/container-status", async (req, res) => {
|
|
137893
|
+
try {
|
|
137894
|
+
const { CentralCore: CentralCore2, DockerClientService: DockerClientService2 } = await Promise.resolve().then(() => (init_src(), src_exports));
|
|
137895
|
+
const central = new CentralCore2();
|
|
137896
|
+
await central.init();
|
|
137897
|
+
try {
|
|
137898
|
+
const managedNode = await central.getManagedDockerNode(req.params.managedId);
|
|
137899
|
+
if (!managedNode) {
|
|
137900
|
+
throw notFound("Managed Docker node not found");
|
|
137901
|
+
}
|
|
137902
|
+
if (!managedNode.containerId) {
|
|
137903
|
+
throw badRequest(`Node has no container yet (status: ${managedNode.status})`);
|
|
137904
|
+
}
|
|
137905
|
+
try {
|
|
137906
|
+
const dockerService = new DockerClientService2(managedNode.hostConfig);
|
|
137907
|
+
const info = await dockerService.getContainerInfo(managedNode.containerId, managedNode.hostConfig);
|
|
137908
|
+
if (!info) {
|
|
137909
|
+
throw notFound("Container not found");
|
|
137910
|
+
}
|
|
137911
|
+
res.json({
|
|
137912
|
+
running: info.state.running,
|
|
137913
|
+
status: info.status,
|
|
137914
|
+
startedAt: info.state.startedAt,
|
|
137915
|
+
finishedAt: info.state.finishedAt,
|
|
137916
|
+
exitCode: info.state.exitCode,
|
|
137917
|
+
error: info.state.error,
|
|
137918
|
+
ports: info.ports
|
|
137919
|
+
});
|
|
137920
|
+
} catch (error) {
|
|
137921
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
137922
|
+
res.status(503).json({ error: `Docker unreachable: ${message}` });
|
|
137923
|
+
}
|
|
137924
|
+
} finally {
|
|
137925
|
+
await central.close();
|
|
137926
|
+
}
|
|
137927
|
+
} catch (error) {
|
|
137928
|
+
if (error instanceof ApiError) {
|
|
137929
|
+
throw error;
|
|
137930
|
+
}
|
|
137931
|
+
rethrowAsApiError8(error);
|
|
137932
|
+
}
|
|
137933
|
+
});
|
|
137934
|
+
router.get("/docker/nodes/:managedId/logs", async (req, res) => {
|
|
137935
|
+
try {
|
|
137936
|
+
const { CentralCore: CentralCore2, DockerClientService: DockerClientService2 } = await Promise.resolve().then(() => (init_src(), src_exports));
|
|
137937
|
+
const central = new CentralCore2();
|
|
137938
|
+
await central.init();
|
|
137939
|
+
try {
|
|
137940
|
+
const managedNode = await central.getManagedDockerNode(req.params.managedId);
|
|
137941
|
+
if (!managedNode) {
|
|
137942
|
+
throw notFound("Managed Docker node not found");
|
|
137943
|
+
}
|
|
137944
|
+
if (!managedNode.containerId) {
|
|
137945
|
+
throw badRequest(`Node has no container yet (status: ${managedNode.status})`);
|
|
137946
|
+
}
|
|
137947
|
+
const tailValue = Number(req.query.tail ?? 100);
|
|
137948
|
+
const tail = Number.isFinite(tailValue) ? Math.max(1, Math.min(1e3, Math.floor(tailValue))) : 100;
|
|
137949
|
+
try {
|
|
137950
|
+
const dockerService = new DockerClientService2(managedNode.hostConfig);
|
|
137951
|
+
const logs = await dockerService.getContainerLogs(managedNode.containerId, managedNode.hostConfig, { tail });
|
|
137952
|
+
res.json({ logs });
|
|
137953
|
+
} catch (error) {
|
|
137954
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
137955
|
+
res.status(503).json({ error: `Docker unreachable: ${message}` });
|
|
137956
|
+
}
|
|
137957
|
+
} finally {
|
|
137958
|
+
await central.close();
|
|
137959
|
+
}
|
|
137960
|
+
} catch (error) {
|
|
137961
|
+
if (error instanceof ApiError) {
|
|
137962
|
+
throw error;
|
|
137963
|
+
}
|
|
137964
|
+
rethrowAsApiError8(error);
|
|
137965
|
+
}
|
|
137966
|
+
});
|
|
137967
|
+
router.get("/docker/nodes/:managedId", async (req, res) => {
|
|
137968
|
+
try {
|
|
137969
|
+
const { CentralCore: CentralCore2 } = await Promise.resolve().then(() => (init_src(), src_exports));
|
|
137970
|
+
const central = new CentralCore2();
|
|
137971
|
+
await central.init();
|
|
137972
|
+
try {
|
|
137973
|
+
const node = await central.getManagedDockerNode(req.params.managedId);
|
|
137974
|
+
if (!node) {
|
|
137975
|
+
throw notFound("Managed Docker node not found");
|
|
137976
|
+
}
|
|
137977
|
+
const linkedNode = node.nodeId ? await central.getNode(node.nodeId) : void 0;
|
|
137978
|
+
res.json(toManagedDockerNodeInfo(node, linkedNode));
|
|
137979
|
+
} finally {
|
|
137980
|
+
await central.close();
|
|
137981
|
+
}
|
|
137982
|
+
} catch (error) {
|
|
137983
|
+
if (error instanceof ApiError) {
|
|
137984
|
+
throw error;
|
|
137985
|
+
}
|
|
137986
|
+
rethrowAsApiError8(error);
|
|
137987
|
+
}
|
|
137988
|
+
});
|
|
137725
137989
|
router.get("/docker-nodes", async (_req, res) => {
|
|
137726
137990
|
try {
|
|
137727
137991
|
const { CentralCore: CentralCore2 } = await Promise.resolve().then(() => (init_src(), src_exports));
|
|
@@ -139578,6 +139842,7 @@ function registerAgentRuntimeRoutes(ctx, deps) {
|
|
|
139578
139842
|
hasHeartbeatExecutor,
|
|
139579
139843
|
heartbeatMonitor,
|
|
139580
139844
|
isHeartbeatMonitorForProject,
|
|
139845
|
+
resolveHeartbeatMonitor,
|
|
139581
139846
|
runExcerptToAgentLogs: runExcerptToAgentLogs2,
|
|
139582
139847
|
parseRunAuditFilters: parseRunAuditFilters2,
|
|
139583
139848
|
normalizeRunAuditEvent: normalizeRunAuditEvent2,
|
|
@@ -139873,6 +140138,33 @@ function registerAgentRuntimeRoutes(ctx, deps) {
|
|
|
139873
140138
|
}
|
|
139874
140139
|
}
|
|
139875
140140
|
}
|
|
140141
|
+
if (nextState === "paused") {
|
|
140142
|
+
const assignedTasks = await scopedStore.getTasksByAssignedAgent(agentId, { excludeArchived: true });
|
|
140143
|
+
const toPause = assignedTasks.filter((task) => task.paused !== true);
|
|
140144
|
+
const results = await Promise.allSettled(
|
|
140145
|
+
toPause.map((task) => scopedStore.pauseTask(task.id, true, void 0, { pausedByAgentId: agentId }))
|
|
140146
|
+
);
|
|
140147
|
+
results.forEach((result, index2) => {
|
|
140148
|
+
if (result.status === "rejected") {
|
|
140149
|
+
console.error(`[agent-state] failed to pause assigned task ${toPause[index2]?.id} for ${agentId}:`, result.reason);
|
|
140150
|
+
}
|
|
140151
|
+
});
|
|
140152
|
+
}
|
|
140153
|
+
if (nextState === "active" || nextState === "terminated") {
|
|
140154
|
+
const pausedTasks = await scopedStore.getTasksByAssignedAgent(agentId, {
|
|
140155
|
+
pausedOnly: true,
|
|
140156
|
+
excludeArchived: true
|
|
140157
|
+
});
|
|
140158
|
+
const toUnpause = pausedTasks.filter((task) => task.pausedByAgentId === agentId);
|
|
140159
|
+
const results = await Promise.allSettled(
|
|
140160
|
+
toUnpause.map((task) => scopedStore.pauseTask(task.id, false))
|
|
140161
|
+
);
|
|
140162
|
+
results.forEach((result, index2) => {
|
|
140163
|
+
if (result.status === "rejected") {
|
|
140164
|
+
console.error(`[agent-state] failed to unpause assigned task ${toUnpause[index2]?.id} for ${agentId}:`, result.reason);
|
|
140165
|
+
}
|
|
140166
|
+
});
|
|
140167
|
+
}
|
|
139876
140168
|
const isHeartbeatEnabled = currentAgent.runtimeConfig?.enabled !== false;
|
|
139877
140169
|
if (nextState === "active" && isHeartbeatEnabled && projectHeartbeatMonitor) {
|
|
139878
140170
|
await projectHeartbeatMonitor.executeHeartbeat({
|
|
@@ -140140,19 +140432,22 @@ function registerAgentRuntimeRoutes(ctx, deps) {
|
|
|
140140
140432
|
const agentStore = new AgentStore2({ rootDir: scopedStore.getFusionDir() });
|
|
140141
140433
|
await agentStore.init();
|
|
140142
140434
|
const event = await agentStore.recordHeartbeat(req.params.id, status);
|
|
140143
|
-
let
|
|
140144
|
-
if (triggerExecution && hasHeartbeatExecutor && heartbeatMonitor
|
|
140145
|
-
|
|
140146
|
-
|
|
140147
|
-
|
|
140148
|
-
|
|
140149
|
-
|
|
140150
|
-
|
|
140151
|
-
|
|
140152
|
-
|
|
140153
|
-
|
|
140435
|
+
let run2;
|
|
140436
|
+
if (triggerExecution && hasHeartbeatExecutor && heartbeatMonitor) {
|
|
140437
|
+
const resolvedMonitor = isHeartbeatMonitorForProject(scopedStore) ? heartbeatMonitor : resolveHeartbeatMonitor(scopedStore);
|
|
140438
|
+
if (resolvedMonitor) {
|
|
140439
|
+
run2 = await resolvedMonitor.executeHeartbeat({
|
|
140440
|
+
agentId: req.params.id,
|
|
140441
|
+
source: "on_demand",
|
|
140442
|
+
triggerDetail: "Triggered from heartbeat",
|
|
140443
|
+
contextSnapshot: {
|
|
140444
|
+
wakeReason: "on_demand",
|
|
140445
|
+
triggerDetail: "Triggered from heartbeat"
|
|
140446
|
+
}
|
|
140447
|
+
});
|
|
140448
|
+
}
|
|
140154
140449
|
}
|
|
140155
|
-
res.json(
|
|
140450
|
+
res.json(run2 ? { event, run: run2 } : event);
|
|
140156
140451
|
} catch (err) {
|
|
140157
140452
|
if (err instanceof ApiError) {
|
|
140158
140453
|
throw err;
|
|
@@ -140230,8 +140525,9 @@ function registerAgentRuntimeRoutes(ctx, deps) {
|
|
|
140230
140525
|
}
|
|
140231
140526
|
if (hasHeartbeatExecutor && heartbeatMonitor) {
|
|
140232
140527
|
const { store: scopedStore } = await getProjectContext3(req);
|
|
140233
|
-
|
|
140234
|
-
|
|
140528
|
+
const resolvedMonitor = isHeartbeatMonitorForProject(scopedStore) ? heartbeatMonitor : resolveHeartbeatMonitor(scopedStore);
|
|
140529
|
+
if (!resolvedMonitor) {
|
|
140530
|
+
throw new ApiError(400, "No heartbeat executor available for this project.");
|
|
140235
140531
|
}
|
|
140236
140532
|
const { AgentStore: AgentStoreClass } = await Promise.resolve().then(() => (init_src(), src_exports));
|
|
140237
140533
|
const agentStore = new AgentStoreClass({ rootDir: scopedStore.getFusionDir() });
|
|
@@ -140244,7 +140540,7 @@ function registerAgentRuntimeRoutes(ctx, deps) {
|
|
|
140244
140540
|
if (activeRun) {
|
|
140245
140541
|
throw new ApiError(409, "Agent already has an active run", { runId: activeRun.id });
|
|
140246
140542
|
}
|
|
140247
|
-
const
|
|
140543
|
+
const run2 = await resolvedMonitor.executeHeartbeat({
|
|
140248
140544
|
agentId: req.params.id,
|
|
140249
140545
|
source: invocationSource,
|
|
140250
140546
|
triggerDetail: trigger,
|
|
@@ -140253,7 +140549,7 @@ function registerAgentRuntimeRoutes(ctx, deps) {
|
|
|
140253
140549
|
triggeringCommentType: normalizedTriggeringCommentType,
|
|
140254
140550
|
contextSnapshot
|
|
140255
140551
|
});
|
|
140256
|
-
res.status(201).json(
|
|
140552
|
+
res.status(201).json(run2);
|
|
140257
140553
|
} else {
|
|
140258
140554
|
const { store: scopedStore } = await getProjectContext3(req);
|
|
140259
140555
|
const { AgentStore: AgentStore2 } = await Promise.resolve().then(() => (init_src(), src_exports));
|
|
@@ -140267,12 +140563,12 @@ function registerAgentRuntimeRoutes(ctx, deps) {
|
|
|
140267
140563
|
if (activeRun) {
|
|
140268
140564
|
throw new ApiError(409, "Agent already has an active run", { runId: activeRun.id });
|
|
140269
140565
|
}
|
|
140270
|
-
const
|
|
140271
|
-
|
|
140272
|
-
|
|
140273
|
-
|
|
140274
|
-
await agentStore.saveRun(
|
|
140275
|
-
res.status(201).json(
|
|
140566
|
+
const run2 = await agentStore.startHeartbeatRun(req.params.id);
|
|
140567
|
+
run2.invocationSource = invocationSource;
|
|
140568
|
+
run2.triggerDetail = trigger;
|
|
140569
|
+
run2.contextSnapshot = contextSnapshot;
|
|
140570
|
+
await agentStore.saveRun(run2);
|
|
140571
|
+
res.status(201).json(run2);
|
|
140276
140572
|
}
|
|
140277
140573
|
} catch (err) {
|
|
140278
140574
|
if (err instanceof ApiError) {
|
|
@@ -140300,8 +140596,26 @@ function registerAgentRuntimeRoutes(ctx, deps) {
|
|
|
140300
140596
|
res.status(200).json({ ok: true, message: "No active run" });
|
|
140301
140597
|
return;
|
|
140302
140598
|
}
|
|
140303
|
-
if (hasHeartbeatExecutor && heartbeatMonitor
|
|
140304
|
-
|
|
140599
|
+
if (hasHeartbeatExecutor && heartbeatMonitor) {
|
|
140600
|
+
const resolvedMonitor = isHeartbeatMonitorForProject(scopedStore) ? heartbeatMonitor : resolveHeartbeatMonitor(scopedStore);
|
|
140601
|
+
if (resolvedMonitor) {
|
|
140602
|
+
await resolvedMonitor.stopRun(req.params.id);
|
|
140603
|
+
} else {
|
|
140604
|
+
const existingRun = await agentStore.getRunDetail(req.params.id, activeRun.id);
|
|
140605
|
+
if (existingRun) {
|
|
140606
|
+
await agentStore.saveRun({
|
|
140607
|
+
...existingRun,
|
|
140608
|
+
endedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
140609
|
+
status: "terminated",
|
|
140610
|
+
stderrExcerpt: existingRun.stderrExcerpt ?? "Run stopped by user"
|
|
140611
|
+
});
|
|
140612
|
+
}
|
|
140613
|
+
await agentStore.endHeartbeatRun(activeRun.id, "terminated");
|
|
140614
|
+
try {
|
|
140615
|
+
await agentStore.updateAgentState(req.params.id, "active");
|
|
140616
|
+
} catch {
|
|
140617
|
+
}
|
|
140618
|
+
}
|
|
140305
140619
|
} else {
|
|
140306
140620
|
const existingRun = await agentStore.getRunDetail(req.params.id, activeRun.id);
|
|
140307
140621
|
if (existingRun) {
|
|
@@ -140335,11 +140649,11 @@ function registerAgentRuntimeRoutes(ctx, deps) {
|
|
|
140335
140649
|
const { AgentStore: AgentStore2 } = await Promise.resolve().then(() => (init_src(), src_exports));
|
|
140336
140650
|
const agentStore = new AgentStore2({ rootDir: scopedStore.getFusionDir() });
|
|
140337
140651
|
await agentStore.init();
|
|
140338
|
-
const
|
|
140339
|
-
if (!
|
|
140652
|
+
const run2 = await agentStore.getRunDetail(req.params.id, req.params.runId);
|
|
140653
|
+
if (!run2) {
|
|
140340
140654
|
throw notFound("Run not found");
|
|
140341
140655
|
}
|
|
140342
|
-
res.json(
|
|
140656
|
+
res.json(run2);
|
|
140343
140657
|
} catch (err) {
|
|
140344
140658
|
if (err instanceof ApiError) {
|
|
140345
140659
|
throw err;
|
|
@@ -140357,8 +140671,8 @@ function registerAgentRuntimeRoutes(ctx, deps) {
|
|
|
140357
140671
|
const { AgentStore: AgentStore2 } = await Promise.resolve().then(() => (init_src(), src_exports));
|
|
140358
140672
|
const agentStore = new AgentStore2({ rootDir: scopedStore.getFusionDir() });
|
|
140359
140673
|
await agentStore.init();
|
|
140360
|
-
const
|
|
140361
|
-
if (!
|
|
140674
|
+
const run2 = await agentStore.getRunDetail(req.params.id, req.params.runId);
|
|
140675
|
+
if (!run2) {
|
|
140362
140676
|
throw notFound("Run not found");
|
|
140363
140677
|
}
|
|
140364
140678
|
const runLogs = await agentStore.getRunLogs(req.params.id, req.params.runId);
|
|
@@ -140366,17 +140680,17 @@ function registerAgentRuntimeRoutes(ctx, deps) {
|
|
|
140366
140680
|
res.json(runLogs);
|
|
140367
140681
|
return;
|
|
140368
140682
|
}
|
|
140369
|
-
const taskId =
|
|
140683
|
+
const taskId = run2.contextSnapshot?.taskId;
|
|
140370
140684
|
if (!taskId) {
|
|
140371
|
-
res.json(runExcerptToAgentLogs2(
|
|
140685
|
+
res.json(runExcerptToAgentLogs2(run2));
|
|
140372
140686
|
return;
|
|
140373
140687
|
}
|
|
140374
140688
|
const logs = await scopedStore.getAgentLogsByTimeRange(
|
|
140375
140689
|
taskId,
|
|
140376
|
-
|
|
140377
|
-
|
|
140690
|
+
run2.startedAt,
|
|
140691
|
+
run2.endedAt
|
|
140378
140692
|
);
|
|
140379
|
-
res.json(logs.length > 0 ? logs : runExcerptToAgentLogs2(
|
|
140693
|
+
res.json(logs.length > 0 ? logs : runExcerptToAgentLogs2(run2));
|
|
140380
140694
|
} catch (err) {
|
|
140381
140695
|
if (err instanceof ApiError) {
|
|
140382
140696
|
throw err;
|
|
@@ -140394,8 +140708,8 @@ function registerAgentRuntimeRoutes(ctx, deps) {
|
|
|
140394
140708
|
const { AgentStore: AgentStore2 } = await Promise.resolve().then(() => (init_src(), src_exports));
|
|
140395
140709
|
const agentStore = new AgentStore2({ rootDir: scopedStore.getFusionDir() });
|
|
140396
140710
|
await agentStore.init();
|
|
140397
|
-
const
|
|
140398
|
-
if (!
|
|
140711
|
+
const run2 = await agentStore.getRunDetail(req.params.id, req.params.runId);
|
|
140712
|
+
if (!run2) {
|
|
140399
140713
|
throw notFound("Run not found");
|
|
140400
140714
|
}
|
|
140401
140715
|
const mutations = await scopedStore.getMutationsForRun(req.params.runId);
|
|
@@ -140421,8 +140735,8 @@ function registerAgentRuntimeRoutes(ctx, deps) {
|
|
|
140421
140735
|
if (!runId || runId.trim().length === 0) {
|
|
140422
140736
|
throw badRequest("runId is required");
|
|
140423
140737
|
}
|
|
140424
|
-
const
|
|
140425
|
-
if (!
|
|
140738
|
+
const run2 = await agentStore.getRunDetail(req.params.id, req.params.runId);
|
|
140739
|
+
if (!run2) {
|
|
140426
140740
|
throw notFound("Run not found");
|
|
140427
140741
|
}
|
|
140428
140742
|
const filters = parseRunAuditFilters2(req.query);
|
|
@@ -140476,8 +140790,8 @@ function registerAgentRuntimeRoutes(ctx, deps) {
|
|
|
140476
140790
|
if (!runId || runId.trim().length === 0) {
|
|
140477
140791
|
throw badRequest("runId is required");
|
|
140478
140792
|
}
|
|
140479
|
-
const
|
|
140480
|
-
if (!
|
|
140793
|
+
const run2 = await agentStore.getRunDetail(req.params.id, req.params.runId);
|
|
140794
|
+
if (!run2) {
|
|
140481
140795
|
throw notFound("Run not found");
|
|
140482
140796
|
}
|
|
140483
140797
|
const filters = parseRunAuditFilters2(req.query);
|
|
@@ -140492,7 +140806,7 @@ function registerAgentRuntimeRoutes(ctx, deps) {
|
|
|
140492
140806
|
}
|
|
140493
140807
|
return true;
|
|
140494
140808
|
})();
|
|
140495
|
-
const auditTaskId = filters.taskId ??
|
|
140809
|
+
const auditTaskId = filters.taskId ?? run2.contextSnapshot?.taskId ?? void 0;
|
|
140496
140810
|
const auditEvents = scopedStore.getRunAuditEvents({
|
|
140497
140811
|
runId: req.params.runId,
|
|
140498
140812
|
taskId: auditTaskId,
|
|
@@ -140520,13 +140834,13 @@ function registerAgentRuntimeRoutes(ctx, deps) {
|
|
|
140520
140834
|
for (const event of auditEvents) {
|
|
140521
140835
|
timelineEntries.push(auditEventToTimelineEntry2(event));
|
|
140522
140836
|
}
|
|
140523
|
-
if (includeLogs &&
|
|
140837
|
+
if (includeLogs && run2.startedAt) {
|
|
140524
140838
|
const taskId = auditTaskId;
|
|
140525
140839
|
if (taskId) {
|
|
140526
140840
|
const logs = await scopedStore.getAgentLogsByTimeRange(
|
|
140527
140841
|
taskId,
|
|
140528
|
-
|
|
140529
|
-
|
|
140842
|
+
run2.startedAt,
|
|
140843
|
+
run2.endedAt
|
|
140530
140844
|
);
|
|
140531
140845
|
for (const log19 of logs) {
|
|
140532
140846
|
timelineEntries.push(logEntryToTimelineEntry2(log19));
|
|
@@ -140536,11 +140850,11 @@ function registerAgentRuntimeRoutes(ctx, deps) {
|
|
|
140536
140850
|
timelineEntries.sort(compareTimelineEntries2);
|
|
140537
140851
|
const response = {
|
|
140538
140852
|
run: {
|
|
140539
|
-
id:
|
|
140540
|
-
agentId:
|
|
140541
|
-
startedAt:
|
|
140542
|
-
endedAt:
|
|
140543
|
-
status:
|
|
140853
|
+
id: run2.id,
|
|
140854
|
+
agentId: run2.agentId,
|
|
140855
|
+
startedAt: run2.startedAt,
|
|
140856
|
+
endedAt: run2.endedAt ?? void 0,
|
|
140857
|
+
status: run2.status,
|
|
140544
140858
|
taskId: auditTaskId ?? void 0
|
|
140545
140859
|
},
|
|
140546
140860
|
auditByDomain,
|
|
@@ -140548,8 +140862,8 @@ function registerAgentRuntimeRoutes(ctx, deps) {
|
|
|
140548
140862
|
auditEvents: normalizedAuditEvents.length,
|
|
140549
140863
|
logEntries: includeLogs && auditTaskId ? (await scopedStore.getAgentLogsByTimeRange(
|
|
140550
140864
|
auditTaskId,
|
|
140551
|
-
|
|
140552
|
-
|
|
140865
|
+
run2.startedAt,
|
|
140866
|
+
run2.endedAt
|
|
140553
140867
|
)).length : 0
|
|
140554
140868
|
},
|
|
140555
140869
|
timeline: timelineEntries
|
|
@@ -144457,96 +144771,70 @@ var init_claude_cli_probe = __esm({
|
|
|
144457
144771
|
}
|
|
144458
144772
|
});
|
|
144459
144773
|
|
|
144460
|
-
//
|
|
144774
|
+
// ../../plugins/fusion-plugin-droid-runtime/dist/probe.js
|
|
144461
144775
|
import { spawn as spawn13 } from "node:child_process";
|
|
144462
|
-
async function
|
|
144463
|
-
|
|
144464
|
-
|
|
144465
|
-
|
|
144466
|
-
|
|
144467
|
-
const finish = (result) => {
|
|
144468
|
-
resolvePromise({ ...result, probeDurationMs: Date.now() - startedAt });
|
|
144469
|
-
};
|
|
144470
|
-
let settled = false;
|
|
144471
|
-
const child = spawn13(binaryPath ?? "droid", ["--version"], {
|
|
144472
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
144473
|
-
});
|
|
144776
|
+
async function run(binary, args, timeoutMs = 2e3) {
|
|
144777
|
+
return new Promise((resolve44) => {
|
|
144778
|
+
const child = spawn13(binary, args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
144779
|
+
let stdout = "";
|
|
144780
|
+
let stderr = "";
|
|
144474
144781
|
const timer = setTimeout(() => {
|
|
144475
|
-
if (settled) return;
|
|
144476
|
-
settled = true;
|
|
144477
144782
|
try {
|
|
144478
144783
|
child.kill("SIGKILL");
|
|
144479
144784
|
} catch {
|
|
144480
144785
|
}
|
|
144481
|
-
|
|
144482
|
-
available: false,
|
|
144483
|
-
binaryPath,
|
|
144484
|
-
reason: `Probe timed out after ${timeoutMs}ms`
|
|
144485
|
-
});
|
|
144786
|
+
resolve44({ code: 124, stdout, stderr });
|
|
144486
144787
|
}, timeoutMs);
|
|
144487
|
-
|
|
144488
|
-
|
|
144489
|
-
child.stdout?.on("data", (chunk) => {
|
|
144490
|
-
stdout += chunk.toString("utf-8");
|
|
144788
|
+
child.stdout?.on("data", (c) => {
|
|
144789
|
+
stdout += c.toString("utf-8");
|
|
144491
144790
|
});
|
|
144492
|
-
child.stderr?.on("data", (
|
|
144493
|
-
stderr +=
|
|
144791
|
+
child.stderr?.on("data", (c) => {
|
|
144792
|
+
stderr += c.toString("utf-8");
|
|
144494
144793
|
});
|
|
144495
|
-
child.on("error", (
|
|
144496
|
-
if (settled) return;
|
|
144497
|
-
settled = true;
|
|
144794
|
+
child.on("error", () => {
|
|
144498
144795
|
clearTimeout(timer);
|
|
144499
|
-
|
|
144500
|
-
finish({
|
|
144501
|
-
available: false,
|
|
144502
|
-
binaryPath,
|
|
144503
|
-
reason: isNotFound ? "`droid` not found on PATH" : err.message
|
|
144504
|
-
});
|
|
144796
|
+
resolve44({ code: 127, stdout, stderr });
|
|
144505
144797
|
});
|
|
144506
144798
|
child.on("close", (code) => {
|
|
144507
|
-
if (settled) return;
|
|
144508
|
-
settled = true;
|
|
144509
144799
|
clearTimeout(timer);
|
|
144510
|
-
|
|
144511
|
-
finish({
|
|
144512
|
-
available: true,
|
|
144513
|
-
version: stdout.trim() || void 0,
|
|
144514
|
-
binaryPath
|
|
144515
|
-
});
|
|
144516
|
-
} else {
|
|
144517
|
-
finish({
|
|
144518
|
-
available: false,
|
|
144519
|
-
binaryPath,
|
|
144520
|
-
reason: stderr.trim() || `droid --version exited with code ${String(code)}`
|
|
144521
|
-
});
|
|
144522
|
-
}
|
|
144800
|
+
resolve44({ code, stdout, stderr });
|
|
144523
144801
|
});
|
|
144524
144802
|
});
|
|
144525
144803
|
}
|
|
144526
|
-
async function
|
|
144527
|
-
|
|
144528
|
-
|
|
144529
|
-
|
|
144530
|
-
|
|
144531
|
-
|
|
144532
|
-
|
|
144533
|
-
|
|
144534
|
-
|
|
144535
|
-
|
|
144536
|
-
|
|
144537
|
-
|
|
144538
|
-
|
|
144539
|
-
|
|
144540
|
-
|
|
144541
|
-
|
|
144542
|
-
|
|
144543
|
-
|
|
144804
|
+
async function probeDroidBinary(options) {
|
|
144805
|
+
const startedAt = Date.now();
|
|
144806
|
+
const binaryPath = options?.binaryPath?.trim() || "droid";
|
|
144807
|
+
const timeoutMs = options?.timeoutMs ?? 2e3;
|
|
144808
|
+
const versionRun = await run(binaryPath, ["--version"], timeoutMs);
|
|
144809
|
+
if (versionRun.code !== 0) {
|
|
144810
|
+
return {
|
|
144811
|
+
available: false,
|
|
144812
|
+
binaryPath,
|
|
144813
|
+
reason: versionRun.code === 124 ? `Probe timed out after ${timeoutMs}ms` : "`droid` not found on PATH",
|
|
144814
|
+
probeDurationMs: Date.now() - startedAt
|
|
144815
|
+
};
|
|
144816
|
+
}
|
|
144817
|
+
return {
|
|
144818
|
+
available: true,
|
|
144819
|
+
binaryPath,
|
|
144820
|
+
version: versionRun.stdout.trim() || void 0,
|
|
144821
|
+
probeDurationMs: Date.now() - startedAt
|
|
144822
|
+
};
|
|
144823
|
+
}
|
|
144824
|
+
var init_probe3 = __esm({
|
|
144825
|
+
"../../plugins/fusion-plugin-droid-runtime/dist/probe.js"() {
|
|
144826
|
+
"use strict";
|
|
144827
|
+
}
|
|
144828
|
+
});
|
|
144829
|
+
|
|
144830
|
+
// ../dashboard/src/droid-cli-probe.ts
|
|
144831
|
+
async function probeDroidCli(options = {}) {
|
|
144832
|
+
return probeDroidBinary({ timeoutMs: options.timeoutMs });
|
|
144544
144833
|
}
|
|
144545
|
-
var PROBE_TIMEOUT_MS2;
|
|
144546
144834
|
var init_droid_cli_probe = __esm({
|
|
144547
144835
|
"../dashboard/src/droid-cli-probe.ts"() {
|
|
144548
144836
|
"use strict";
|
|
144549
|
-
|
|
144837
|
+
init_probe3();
|
|
144550
144838
|
}
|
|
144551
144839
|
});
|
|
144552
144840
|
|
|
@@ -148050,24 +148338,24 @@ function createMissionRouter(store, missionAutopilot, aiSessionStore, missionExe
|
|
|
148050
148338
|
const validationRounds = [];
|
|
148051
148339
|
for (const feature of allFeatures) {
|
|
148052
148340
|
const runs = missionStore.getValidatorRunsByFeature(feature.id);
|
|
148053
|
-
for (const
|
|
148341
|
+
for (const run2 of runs) {
|
|
148054
148342
|
let failedAssertionIds = [];
|
|
148055
|
-
if (
|
|
148056
|
-
failedAssertionIds = missionStore.getFailuresForRun(
|
|
148057
|
-
failedAssertionIdsByRunId.set(
|
|
148343
|
+
if (run2.status === "failed") {
|
|
148344
|
+
failedAssertionIds = missionStore.getFailuresForRun(run2.id).map((failure) => failure.assertionId);
|
|
148345
|
+
failedAssertionIdsByRunId.set(run2.id, failedAssertionIds);
|
|
148058
148346
|
}
|
|
148059
148347
|
validationRounds.push({
|
|
148060
|
-
roundId:
|
|
148061
|
-
featureId:
|
|
148348
|
+
roundId: run2.id,
|
|
148349
|
+
featureId: run2.featureId,
|
|
148062
148350
|
featureTitle: feature.title,
|
|
148063
|
-
validatorStatus:
|
|
148064
|
-
implementationAttempt:
|
|
148065
|
-
validatorAttempt:
|
|
148351
|
+
validatorStatus: run2.status,
|
|
148352
|
+
implementationAttempt: run2.implementationAttempt,
|
|
148353
|
+
validatorAttempt: run2.validatorAttempt,
|
|
148066
148354
|
failedAssertionIds,
|
|
148067
|
-
generatedFixFeatureIds: generatedFixFeatureIdsByRunId.get(
|
|
148068
|
-
blockedReason:
|
|
148069
|
-
startedAt:
|
|
148070
|
-
completedAt:
|
|
148355
|
+
generatedFixFeatureIds: generatedFixFeatureIdsByRunId.get(run2.id) ?? [],
|
|
148356
|
+
blockedReason: run2.blockedReason,
|
|
148357
|
+
startedAt: run2.startedAt,
|
|
148358
|
+
completedAt: run2.completedAt
|
|
148071
148359
|
});
|
|
148072
148360
|
}
|
|
148073
148361
|
}
|
|
@@ -148130,15 +148418,15 @@ function createMissionRouter(store, missionAutopilot, aiSessionStore, missionExe
|
|
|
148130
148418
|
missionStore.updateFeature(featureId, {
|
|
148131
148419
|
loopState: "validating"
|
|
148132
148420
|
});
|
|
148133
|
-
const
|
|
148421
|
+
const run2 = missionStore.startValidatorRun(featureId, "manual");
|
|
148134
148422
|
res.status(202).json({
|
|
148135
|
-
runId:
|
|
148136
|
-
featureId:
|
|
148137
|
-
status:
|
|
148138
|
-
triggerType:
|
|
148139
|
-
implementationAttempt:
|
|
148140
|
-
validatorAttempt:
|
|
148141
|
-
startedAt:
|
|
148423
|
+
runId: run2.id,
|
|
148424
|
+
featureId: run2.featureId,
|
|
148425
|
+
status: run2.status,
|
|
148426
|
+
triggerType: run2.triggerType,
|
|
148427
|
+
implementationAttempt: run2.implementationAttempt,
|
|
148428
|
+
validatorAttempt: run2.validatorAttempt,
|
|
148429
|
+
startedAt: run2.startedAt
|
|
148142
148430
|
});
|
|
148143
148431
|
})
|
|
148144
148432
|
);
|
|
@@ -148188,13 +148476,13 @@ function createMissionRouter(store, missionAutopilot, aiSessionStore, missionExe
|
|
|
148188
148476
|
if (!runId || typeof runId !== "string") {
|
|
148189
148477
|
throw badRequest("Run ID is required");
|
|
148190
148478
|
}
|
|
148191
|
-
const
|
|
148192
|
-
if (!
|
|
148479
|
+
const run2 = missionStore.getValidatorRun(runId);
|
|
148480
|
+
if (!run2) {
|
|
148193
148481
|
throw notFound("Validator run not found");
|
|
148194
148482
|
}
|
|
148195
148483
|
const failures = missionStore.getFailuresForRun(runId);
|
|
148196
148484
|
res.json({
|
|
148197
|
-
...
|
|
148485
|
+
...run2,
|
|
148198
148486
|
failures
|
|
148199
148487
|
});
|
|
148200
148488
|
})
|
|
@@ -150353,7 +150641,7 @@ function createInsightsRouter(store) {
|
|
|
150353
150641
|
if (!taskStore) throw new ApiError(500, "Store context not available");
|
|
150354
150642
|
const rootDir = taskStore.getRootDir();
|
|
150355
150643
|
const controller = new AbortController();
|
|
150356
|
-
const
|
|
150644
|
+
const run2 = await executeInsightRunLifecycle({
|
|
150357
150645
|
store: insightStore,
|
|
150358
150646
|
projectId,
|
|
150359
150647
|
input: {
|
|
@@ -150364,19 +150652,19 @@ function createInsightsRouter(store) {
|
|
|
150364
150652
|
timeoutMs: typeof req.body.timeoutMs === "number" ? req.body.timeoutMs : 12e4,
|
|
150365
150653
|
maxAttempts: 2,
|
|
150366
150654
|
retryDelayMs: 250,
|
|
150367
|
-
executeAttempt: async ({ run:
|
|
150368
|
-
activeRunControllers.set(
|
|
150655
|
+
executeAttempt: async ({ run: run3, signal }) => {
|
|
150656
|
+
activeRunControllers.set(run3.id, controller);
|
|
150369
150657
|
return executeInsightAttempt({
|
|
150370
150658
|
rootDir,
|
|
150371
150659
|
projectId,
|
|
150372
|
-
runId:
|
|
150660
|
+
runId: run3.id,
|
|
150373
150661
|
signal,
|
|
150374
150662
|
insightStore
|
|
150375
150663
|
});
|
|
150376
150664
|
}
|
|
150377
150665
|
});
|
|
150378
|
-
activeRunControllers.delete(
|
|
150379
|
-
res.status(201).json(
|
|
150666
|
+
activeRunControllers.delete(run2.id);
|
|
150667
|
+
res.status(201).json(run2);
|
|
150380
150668
|
} catch (error) {
|
|
150381
150669
|
if (error instanceof InsightLifecycleError && error.code === "active_run_conflict") {
|
|
150382
150670
|
throw new ApiError(409, error.message);
|
|
@@ -150426,11 +150714,11 @@ function createInsightsRouter(store) {
|
|
|
150426
150714
|
try {
|
|
150427
150715
|
const id = String(req.params.id);
|
|
150428
150716
|
const store2 = getInsightStore();
|
|
150429
|
-
const
|
|
150430
|
-
if (!
|
|
150717
|
+
const run2 = store2.getRun(id);
|
|
150718
|
+
if (!run2) {
|
|
150431
150719
|
throw notFound(`Run not found: ${id}`);
|
|
150432
150720
|
}
|
|
150433
|
-
res.json(
|
|
150721
|
+
res.json(run2);
|
|
150434
150722
|
} catch (error) {
|
|
150435
150723
|
rethrowAsApiError5(error, "Failed to get run");
|
|
150436
150724
|
}
|
|
@@ -150439,8 +150727,8 @@ function createInsightsRouter(store) {
|
|
|
150439
150727
|
try {
|
|
150440
150728
|
const id = String(req.params.id);
|
|
150441
150729
|
const store2 = getInsightStore();
|
|
150442
|
-
const
|
|
150443
|
-
if (!
|
|
150730
|
+
const run2 = store2.getRun(id);
|
|
150731
|
+
if (!run2) throw notFound(`Run not found: ${id}`);
|
|
150444
150732
|
res.json({ events: store2.listRunEvents(id) });
|
|
150445
150733
|
} catch (error) {
|
|
150446
150734
|
rethrowAsApiError5(error, "Failed to list run events");
|
|
@@ -150450,34 +150738,34 @@ function createInsightsRouter(store) {
|
|
|
150450
150738
|
try {
|
|
150451
150739
|
const id = String(req.params.id);
|
|
150452
150740
|
const store2 = getInsightStore();
|
|
150453
|
-
const
|
|
150454
|
-
if (!
|
|
150455
|
-
if (!["pending", "running"].includes(
|
|
150741
|
+
const run2 = store2.getRun(id);
|
|
150742
|
+
if (!run2) throw notFound(`Run not found: ${id}`);
|
|
150743
|
+
if (!["pending", "running"].includes(run2.status)) {
|
|
150456
150744
|
throw new ApiError(409, `Run ${id} is already terminal`);
|
|
150457
150745
|
}
|
|
150458
150746
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
150459
|
-
store2.appendRunEvent(id, { type: "cancel_requested", status:
|
|
150747
|
+
store2.appendRunEvent(id, { type: "cancel_requested", status: run2.status, message: "Cancellation requested" });
|
|
150460
150748
|
const updated = store2.updateRun(id, {
|
|
150461
|
-
lifecycle: { ...
|
|
150749
|
+
lifecycle: { ...run2.lifecycle, cancellationRequestedAt: now }
|
|
150462
150750
|
});
|
|
150463
|
-
if (
|
|
150751
|
+
if (run2.status === "pending") {
|
|
150464
150752
|
const cancelled = store2.updateRun(id, {
|
|
150465
150753
|
status: "cancelled",
|
|
150466
150754
|
error: "Cancelled before execution started",
|
|
150467
150755
|
cancelledAt: now,
|
|
150468
150756
|
lifecycle: {
|
|
150469
|
-
...updated?.lifecycle ??
|
|
150757
|
+
...updated?.lifecycle ?? run2.lifecycle,
|
|
150470
150758
|
terminalReason: "cancelled",
|
|
150471
150759
|
terminalCause: "cancel_requested",
|
|
150472
150760
|
failureClass: "cancelled",
|
|
150473
150761
|
retryable: false
|
|
150474
150762
|
}
|
|
150475
150763
|
});
|
|
150476
|
-
res.json(cancelled ?? updated ??
|
|
150764
|
+
res.json(cancelled ?? updated ?? run2);
|
|
150477
150765
|
return;
|
|
150478
150766
|
}
|
|
150479
150767
|
activeRunControllers.get(id)?.abort(new DOMException("Run cancelled", "AbortError"));
|
|
150480
|
-
res.json(updated ??
|
|
150768
|
+
res.json(updated ?? run2);
|
|
150481
150769
|
} catch (error) {
|
|
150482
150770
|
rethrowAsApiError5(error, "Failed to cancel run");
|
|
150483
150771
|
}
|
|
@@ -150498,26 +150786,26 @@ function createInsightsRouter(store) {
|
|
|
150498
150786
|
if (!taskStore) throw new ApiError(500, "Store context not available");
|
|
150499
150787
|
const rootDir = taskStore.getRootDir();
|
|
150500
150788
|
const controller = new AbortController();
|
|
150501
|
-
const { run } = await retryInsightRunLifecycle({
|
|
150789
|
+
const { run: run2 } = await retryInsightRunLifecycle({
|
|
150502
150790
|
store: store2,
|
|
150503
150791
|
runId: id,
|
|
150504
150792
|
timeoutMs: typeof req.body?.timeoutMs === "number" ? req.body.timeoutMs : 12e4,
|
|
150505
150793
|
maxAttempts: 2,
|
|
150506
150794
|
retryDelayMs: 250,
|
|
150507
150795
|
signal: controller.signal,
|
|
150508
|
-
executeAttempt: async ({ run:
|
|
150509
|
-
activeRunControllers.set(
|
|
150796
|
+
executeAttempt: async ({ run: run3, signal }) => {
|
|
150797
|
+
activeRunControllers.set(run3.id, controller);
|
|
150510
150798
|
return executeInsightAttempt({
|
|
150511
150799
|
rootDir,
|
|
150512
150800
|
projectId: existing.projectId,
|
|
150513
|
-
runId:
|
|
150801
|
+
runId: run3.id,
|
|
150514
150802
|
signal,
|
|
150515
150803
|
insightStore: store2
|
|
150516
150804
|
});
|
|
150517
150805
|
}
|
|
150518
150806
|
});
|
|
150519
|
-
activeRunControllers.delete(
|
|
150520
|
-
res.status(201).json(
|
|
150807
|
+
activeRunControllers.delete(run2.id);
|
|
150808
|
+
res.status(201).json(run2);
|
|
150521
150809
|
} catch (error) {
|
|
150522
150810
|
if (error instanceof InsightLifecycleError && error.code === "not_retryable") {
|
|
150523
150811
|
throw new ApiError(409, error.message);
|
|
@@ -150658,32 +150946,33 @@ import { AsyncLocalStorage as AsyncLocalStorage4 } from "node:async_hooks";
|
|
|
150658
150946
|
function rethrowAsApiError6(error, fallback2 = "Internal server error") {
|
|
150659
150947
|
if (error instanceof ApiError) throw error;
|
|
150660
150948
|
if (error instanceof ResearchLifecycleError) {
|
|
150661
|
-
const status = error.code === "invalid_transition" || error.code === "active_run_conflict" ? 409 : 400;
|
|
150662
|
-
|
|
150949
|
+
const status = error.code === "invalid_transition" || error.code === "active_run_conflict" || error.code === "not_retryable" ? 409 : 400;
|
|
150950
|
+
const mappedCode = error.code === "not_retryable" ? "NON_RETRYABLE_PROVIDER_ERROR" : "INVALID_TRANSITION";
|
|
150951
|
+
throw new ApiError(status, error.message, { code: mappedCode, retryable: false });
|
|
150663
150952
|
}
|
|
150664
|
-
if (error instanceof Error) throw new ApiError(500, error.message);
|
|
150665
|
-
throw new ApiError(500, fallback2);
|
|
150953
|
+
if (error instanceof Error) throw new ApiError(500, error.message, { code: "INTERNAL_ERROR" });
|
|
150954
|
+
throw new ApiError(500, fallback2, { code: "INTERNAL_ERROR" });
|
|
150666
150955
|
}
|
|
150667
150956
|
function getProjectId2(req) {
|
|
150668
150957
|
if (typeof req.query.projectId === "string" && req.query.projectId.trim()) return req.query.projectId;
|
|
150669
150958
|
if (req.body && typeof req.body === "object" && typeof req.body.projectId === "string" && req.body.projectId.trim()) return req.body.projectId;
|
|
150670
150959
|
return void 0;
|
|
150671
150960
|
}
|
|
150672
|
-
function toRunListItem(
|
|
150961
|
+
function toRunListItem(run2) {
|
|
150673
150962
|
return {
|
|
150674
|
-
id:
|
|
150675
|
-
query:
|
|
150676
|
-
title:
|
|
150677
|
-
status:
|
|
150678
|
-
summary:
|
|
150679
|
-
createdAt:
|
|
150680
|
-
updatedAt:
|
|
150963
|
+
id: run2.id,
|
|
150964
|
+
query: run2.query,
|
|
150965
|
+
title: run2.topic || run2.query,
|
|
150966
|
+
status: run2.status,
|
|
150967
|
+
summary: run2.results?.summary,
|
|
150968
|
+
createdAt: run2.createdAt,
|
|
150969
|
+
updatedAt: run2.updatedAt
|
|
150681
150970
|
};
|
|
150682
150971
|
}
|
|
150683
|
-
function toRunDetail(
|
|
150972
|
+
function toRunDetail(run2) {
|
|
150684
150973
|
return {
|
|
150685
|
-
...
|
|
150686
|
-
title:
|
|
150974
|
+
...run2,
|
|
150975
|
+
title: run2.topic || run2.query
|
|
150687
150976
|
};
|
|
150688
150977
|
}
|
|
150689
150978
|
function getFindingId(finding, index2) {
|
|
@@ -150691,8 +150980,8 @@ function getFindingId(finding, index2) {
|
|
|
150691
150980
|
const explicitId = typeof maybeFinding.id === "string" ? maybeFinding.id.trim() : "";
|
|
150692
150981
|
return explicitId || `finding-${index2 + 1}`;
|
|
150693
150982
|
}
|
|
150694
|
-
function getFindingById(
|
|
150695
|
-
const findings =
|
|
150983
|
+
function getFindingById(run2, findingId) {
|
|
150984
|
+
const findings = run2.results?.findings ?? [];
|
|
150696
150985
|
for (const [index2, finding] of findings.entries()) {
|
|
150697
150986
|
if (getFindingId(finding, index2) === findingId) {
|
|
150698
150987
|
return { finding, findingId };
|
|
@@ -150700,24 +150989,24 @@ function getFindingById(run, findingId) {
|
|
|
150700
150989
|
}
|
|
150701
150990
|
return null;
|
|
150702
150991
|
}
|
|
150703
|
-
function buildFindingTaskSummary(
|
|
150992
|
+
function buildFindingTaskSummary(run2, finding) {
|
|
150704
150993
|
const heading = finding.heading?.trim() || "Research finding";
|
|
150705
150994
|
const content = finding.content?.trim() || "";
|
|
150706
150995
|
const firstSentence = content.split(/(?<=[.!?])\s+/)[0]?.trim() || content;
|
|
150707
|
-
const scope =
|
|
150996
|
+
const scope = run2.topic || run2.query;
|
|
150708
150997
|
return `${heading} \u2014 ${firstSentence || "Review cited research details."}
|
|
150709
150998
|
|
|
150710
150999
|
Context: ${scope}`;
|
|
150711
151000
|
}
|
|
150712
|
-
function buildFindingMarkdown(
|
|
151001
|
+
function buildFindingMarkdown(run2, findingId, finding) {
|
|
150713
151002
|
const citations = (finding.sources ?? []).map((source) => `- ${source}`).join("\n");
|
|
150714
|
-
const runSummary =
|
|
151003
|
+
const runSummary = run2.results?.summary?.trim();
|
|
150715
151004
|
return [
|
|
150716
151005
|
`# Research Finding`,
|
|
150717
151006
|
``,
|
|
150718
|
-
`- Run ID: ${
|
|
151007
|
+
`- Run ID: ${run2.id}`,
|
|
150719
151008
|
`- Finding ID: ${findingId}`,
|
|
150720
|
-
`- Query: ${
|
|
151009
|
+
`- Query: ${run2.query}`,
|
|
150721
151010
|
``,
|
|
150722
151011
|
`## ${finding.heading || "Finding"}`,
|
|
150723
151012
|
finding.content || "",
|
|
@@ -150788,7 +151077,7 @@ function createResearchRouter(store) {
|
|
|
150788
151077
|
if (typeof req.body?.query !== "string" || !req.body.query.trim()) {
|
|
150789
151078
|
throw badRequest("query is required");
|
|
150790
151079
|
}
|
|
150791
|
-
const
|
|
151080
|
+
const run2 = getStore4().createRun({
|
|
150792
151081
|
query: req.body.query,
|
|
150793
151082
|
topic: req.body.query,
|
|
150794
151083
|
providerConfig: {
|
|
@@ -150801,55 +151090,85 @@ function createResearchRouter(store) {
|
|
|
150801
151090
|
depth: req.body.depth
|
|
150802
151091
|
}
|
|
150803
151092
|
});
|
|
150804
|
-
res.status(201).json({ run: toRunDetail(
|
|
151093
|
+
res.status(201).json({ run: toRunDetail(run2), availability: DEFAULT_AVAILABILITY });
|
|
150805
151094
|
} catch (error) {
|
|
150806
151095
|
rethrowAsApiError6(error, "Failed to create research run");
|
|
150807
151096
|
}
|
|
150808
151097
|
});
|
|
150809
151098
|
router.get("/runs/:id", (req, res) => {
|
|
150810
151099
|
try {
|
|
150811
|
-
const
|
|
150812
|
-
if (!
|
|
150813
|
-
res.json({ run: toRunDetail(
|
|
151100
|
+
const run2 = getStore4().getRun(req.params.id);
|
|
151101
|
+
if (!run2) throw notFound(`Run not found: ${req.params.id}`);
|
|
151102
|
+
res.json({ run: toRunDetail(run2), availability: DEFAULT_AVAILABILITY });
|
|
150814
151103
|
} catch (error) {
|
|
150815
151104
|
rethrowAsApiError6(error, "Failed to get research run");
|
|
150816
151105
|
}
|
|
150817
151106
|
});
|
|
150818
151107
|
router.post("/runs/:id/cancel", (req, res) => {
|
|
150819
151108
|
try {
|
|
150820
|
-
const
|
|
150821
|
-
|
|
151109
|
+
const existing = getStore4().getRun(req.params.id);
|
|
151110
|
+
if (!existing) throw notFound(`Run not found: ${req.params.id}`);
|
|
151111
|
+
if (["completed", "failed", "cancelled", "timed_out", "retry_exhausted"].includes(existing.status)) {
|
|
151112
|
+
res.status(409).json({
|
|
151113
|
+
error: `Run ${req.params.id} cannot be cancelled from status ${existing.status}`,
|
|
151114
|
+
details: { code: "INVALID_TRANSITION", retryable: false }
|
|
151115
|
+
});
|
|
151116
|
+
return;
|
|
151117
|
+
}
|
|
151118
|
+
const run2 = getStore4().requestCancellation(req.params.id);
|
|
151119
|
+
res.json({ run: toRunDetail(run2) });
|
|
150822
151120
|
} catch (error) {
|
|
150823
151121
|
rethrowAsApiError6(error, "Failed to cancel research run");
|
|
150824
151122
|
}
|
|
150825
151123
|
});
|
|
150826
151124
|
router.post("/runs/:id/retry", (req, res) => {
|
|
150827
151125
|
try {
|
|
151126
|
+
const existing = getStore4().getRun(req.params.id);
|
|
151127
|
+
if (!existing) throw notFound(`Run not found: ${req.params.id}`);
|
|
150828
151128
|
const retryRun = getStore4().createRetryRun(req.params.id);
|
|
150829
151129
|
res.json({ run: toRunDetail(retryRun) });
|
|
150830
151130
|
} catch (error) {
|
|
151131
|
+
if (error instanceof ResearchLifecycleError && error.code === "not_retryable") {
|
|
151132
|
+
const run2 = getStore4().getRun(req.params.id);
|
|
151133
|
+
const exhausted = run2?.status === "retry_exhausted" || run2?.lifecycle?.errorCode === "RETRY_EXHAUSTED";
|
|
151134
|
+
res.status(409).json({
|
|
151135
|
+
error: error.message,
|
|
151136
|
+
details: {
|
|
151137
|
+
code: exhausted ? "RETRY_EXHAUSTED" : "NON_RETRYABLE_PROVIDER_ERROR",
|
|
151138
|
+
retryable: false
|
|
151139
|
+
}
|
|
151140
|
+
});
|
|
151141
|
+
return;
|
|
151142
|
+
}
|
|
151143
|
+
if (error instanceof ResearchLifecycleError && error.code === "invalid_transition") {
|
|
151144
|
+
res.status(409).json({
|
|
151145
|
+
error: error.message,
|
|
151146
|
+
details: { code: "INVALID_TRANSITION", retryable: false }
|
|
151147
|
+
});
|
|
151148
|
+
return;
|
|
151149
|
+
}
|
|
150831
151150
|
rethrowAsApiError6(error, "Failed to retry research run");
|
|
150832
151151
|
}
|
|
150833
151152
|
});
|
|
150834
151153
|
router.get("/runs/:id/export", (req, res) => {
|
|
150835
151154
|
try {
|
|
150836
|
-
const
|
|
150837
|
-
if (!
|
|
151155
|
+
const run2 = getStore4().getRun(req.params.id);
|
|
151156
|
+
if (!run2) throw notFound(`Run not found: ${req.params.id}`);
|
|
150838
151157
|
const format = String(req.query.format ?? "markdown");
|
|
150839
151158
|
if (format === "json") {
|
|
150840
|
-
res.json({ format, filename: `${
|
|
151159
|
+
res.json({ format, filename: `${run2.id}.json`, content: JSON.stringify(run2, null, 2) });
|
|
150841
151160
|
return;
|
|
150842
151161
|
}
|
|
150843
151162
|
if (format === "html") {
|
|
150844
|
-
const html = `<h1>${
|
|
150845
|
-
res.json({ format, filename: `${
|
|
151163
|
+
const html = `<h1>${run2.topic || run2.query}</h1><p>${run2.results?.summary ?? ""}</p>`;
|
|
151164
|
+
res.json({ format, filename: `${run2.id}.html`, content: html });
|
|
150846
151165
|
return;
|
|
150847
151166
|
}
|
|
150848
151167
|
if (format !== "markdown") throw badRequest(`Unsupported format: ${format}`);
|
|
150849
|
-
const markdown = `# ${
|
|
151168
|
+
const markdown = `# ${run2.topic || run2.query}
|
|
150850
151169
|
|
|
150851
|
-
${
|
|
150852
|
-
res.json({ format: "markdown", filename: `${
|
|
151170
|
+
${run2.results?.summary ?? ""}`;
|
|
151171
|
+
res.json({ format: "markdown", filename: `${run2.id}.md`, content: markdown });
|
|
150853
151172
|
} catch (error) {
|
|
150854
151173
|
rethrowAsApiError6(error, "Failed to export research run");
|
|
150855
151174
|
}
|
|
@@ -150858,9 +151177,9 @@ ${run.results?.summary ?? ""}`;
|
|
|
150858
151177
|
try {
|
|
150859
151178
|
const scopedStore = requestContext.getStore();
|
|
150860
151179
|
if (!scopedStore) throw new ApiError(500, "Task store context unavailable");
|
|
150861
|
-
const
|
|
150862
|
-
if (!
|
|
150863
|
-
const found = getFindingById(
|
|
151180
|
+
const run2 = getStore4().getRun(req.params.runId);
|
|
151181
|
+
if (!run2) throw notFound(`Run not found: ${req.params.runId}`);
|
|
151182
|
+
const found = getFindingById(run2, req.params.findingId);
|
|
150864
151183
|
if (!found) throw notFound(`Finding not found: ${req.params.findingId}`);
|
|
150865
151184
|
let documentKey;
|
|
150866
151185
|
try {
|
|
@@ -150868,8 +151187,8 @@ ${run.results?.summary ?? ""}`;
|
|
|
150868
151187
|
} catch {
|
|
150869
151188
|
throw badRequest("Invalid run id for research document key");
|
|
150870
151189
|
}
|
|
150871
|
-
const title = typeof req.body?.title === "string" && req.body.title.trim() ? req.body.title.trim() : `Research: ${found.finding.heading ||
|
|
150872
|
-
const description = typeof req.body?.description === "string" && req.body.description.trim() ? req.body.description.trim() : buildFindingTaskSummary(
|
|
151190
|
+
const title = typeof req.body?.title === "string" && req.body.title.trim() ? req.body.title.trim() : `Research: ${found.finding.heading || run2.topic || run2.query}`;
|
|
151191
|
+
const description = typeof req.body?.description === "string" && req.body.description.trim() ? req.body.description.trim() : buildFindingTaskSummary(run2, found.finding);
|
|
150873
151192
|
const priority = req.body?.priority;
|
|
150874
151193
|
if (priority !== void 0 && !["low", "normal", "high", "urgent"].includes(priority)) {
|
|
150875
151194
|
throw badRequest("priority must be one of: low, normal, high, urgent");
|
|
@@ -150881,9 +151200,9 @@ ${run.results?.summary ?? ""}`;
|
|
|
150881
151200
|
priority,
|
|
150882
151201
|
source: {
|
|
150883
151202
|
sourceType: "research",
|
|
150884
|
-
sourceRunId:
|
|
151203
|
+
sourceRunId: run2.id,
|
|
150885
151204
|
sourceMetadata: {
|
|
150886
|
-
runId:
|
|
151205
|
+
runId: run2.id,
|
|
150887
151206
|
findingId: found.findingId,
|
|
150888
151207
|
findingLabel: found.finding.heading,
|
|
150889
151208
|
documentKey
|
|
@@ -150891,13 +151210,13 @@ ${run.results?.summary ?? ""}`;
|
|
|
150891
151210
|
}
|
|
150892
151211
|
};
|
|
150893
151212
|
const task = await scopedStore.createTask(taskInput);
|
|
150894
|
-
const markdown = buildFindingMarkdown(
|
|
151213
|
+
const markdown = buildFindingMarkdown(run2, found.findingId, found.finding);
|
|
150895
151214
|
await scopedStore.upsertTaskDocument(task.id, {
|
|
150896
151215
|
key: documentKey,
|
|
150897
151216
|
content: markdown,
|
|
150898
151217
|
author: "research",
|
|
150899
151218
|
metadata: {
|
|
150900
|
-
runId:
|
|
151219
|
+
runId: run2.id,
|
|
150901
151220
|
findingId: found.findingId,
|
|
150902
151221
|
findingLabel: found.finding.heading
|
|
150903
151222
|
}
|
|
@@ -150905,7 +151224,7 @@ ${run.results?.summary ?? ""}`;
|
|
|
150905
151224
|
if (typeof scopedStore.appendAgentLog === "function") {
|
|
150906
151225
|
await scopedStore.appendAgentLog(
|
|
150907
151226
|
task.id,
|
|
150908
|
-
`Task created from research finding ${found.findingId} in run ${
|
|
151227
|
+
`Task created from research finding ${found.findingId} in run ${run2.id}`,
|
|
150909
151228
|
"text",
|
|
150910
151229
|
"research-task-integration",
|
|
150911
151230
|
"executor"
|
|
@@ -150913,7 +151232,7 @@ ${run.results?.summary ?? ""}`;
|
|
|
150913
151232
|
}
|
|
150914
151233
|
let attachmentFilename;
|
|
150915
151234
|
if (attachExport) {
|
|
150916
|
-
const filename = `${
|
|
151235
|
+
const filename = `${run2.id}-${found.findingId}.md`;
|
|
150917
151236
|
const existing = await scopedStore.getTask(task.id);
|
|
150918
151237
|
if (!existing.attachments?.some((attachment) => attachment.originalName === filename)) {
|
|
150919
151238
|
attachmentFilename = await addFindingAttachment(scopedStore, task.id, filename, markdown);
|
|
@@ -150934,9 +151253,9 @@ ${run.results?.summary ?? ""}`;
|
|
|
150934
151253
|
try {
|
|
150935
151254
|
const scopedStore = requestContext.getStore();
|
|
150936
151255
|
if (!scopedStore) throw new ApiError(500, "Task store context unavailable");
|
|
150937
|
-
const
|
|
150938
|
-
if (!
|
|
150939
|
-
const found = getFindingById(
|
|
151256
|
+
const run2 = getStore4().getRun(req.params.runId);
|
|
151257
|
+
if (!run2) throw notFound(`Run not found: ${req.params.runId}`);
|
|
151258
|
+
const found = getFindingById(run2, req.params.findingId);
|
|
150940
151259
|
if (!found) throw notFound(`Finding not found: ${req.params.findingId}`);
|
|
150941
151260
|
const task = await scopedStore.getTask(req.params.taskId);
|
|
150942
151261
|
if (!task) throw notFound(`Task not found: ${req.params.taskId}`);
|
|
@@ -150947,13 +151266,13 @@ ${run.results?.summary ?? ""}`;
|
|
|
150947
151266
|
} catch {
|
|
150948
151267
|
throw badRequest("Invalid run id for research document key");
|
|
150949
151268
|
}
|
|
150950
|
-
const markdown = buildFindingMarkdown(
|
|
151269
|
+
const markdown = buildFindingMarkdown(run2, found.findingId, found.finding);
|
|
150951
151270
|
const document2 = await scopedStore.upsertTaskDocument(task.id, {
|
|
150952
151271
|
key: documentKey,
|
|
150953
151272
|
content: markdown,
|
|
150954
151273
|
author: "research",
|
|
150955
151274
|
metadata: {
|
|
150956
|
-
runId:
|
|
151275
|
+
runId: run2.id,
|
|
150957
151276
|
findingId: found.findingId,
|
|
150958
151277
|
findingLabel: found.finding.heading
|
|
150959
151278
|
}
|
|
@@ -150961,7 +151280,7 @@ ${run.results?.summary ?? ""}`;
|
|
|
150961
151280
|
const attachExport = validateAttachExport(req.body?.attachExport);
|
|
150962
151281
|
let attachmentFilename;
|
|
150963
151282
|
if (attachExport) {
|
|
150964
|
-
const filename = `${
|
|
151283
|
+
const filename = `${run2.id}-${found.findingId}.md`;
|
|
150965
151284
|
if (!task.attachments?.some((attachment) => attachment.originalName === filename)) {
|
|
150966
151285
|
attachmentFilename = await addFindingAttachment(scopedStore, task.id, filename, markdown);
|
|
150967
151286
|
}
|
|
@@ -150969,7 +151288,7 @@ ${run.results?.summary ?? ""}`;
|
|
|
150969
151288
|
if (typeof scopedStore.appendAgentLog === "function") {
|
|
150970
151289
|
await scopedStore.appendAgentLog(
|
|
150971
151290
|
task.id,
|
|
150972
|
-
`Task enriched from research finding ${found.findingId} in run ${
|
|
151291
|
+
`Task enriched from research finding ${found.findingId} in run ${run2.id}`,
|
|
150973
151292
|
"text",
|
|
150974
151293
|
"research-task-integration",
|
|
150975
151294
|
"executor"
|
|
@@ -151046,9 +151365,9 @@ ${run.results?.summary ?? ""}`;
|
|
|
151046
151365
|
const status = req.body?.status;
|
|
151047
151366
|
if (!status || !RESEARCH_RUN_STATUSES.includes(status)) throw badRequest(`Invalid status: ${String(status)}`);
|
|
151048
151367
|
getStore4().updateStatus(req.params.id, status, req.body?.extra);
|
|
151049
|
-
const
|
|
151050
|
-
if (!
|
|
151051
|
-
res.json(
|
|
151368
|
+
const run2 = getStore4().getRun(req.params.id);
|
|
151369
|
+
if (!run2) throw notFound(`Run not found: ${req.params.id}`);
|
|
151370
|
+
res.json(run2);
|
|
151052
151371
|
} catch (error) {
|
|
151053
151372
|
rethrowAsApiError6(error, "Failed to update research status");
|
|
151054
151373
|
}
|
|
@@ -151107,7 +151426,8 @@ var init_research_routes = __esm({
|
|
|
151107
151426
|
DEFAULT_AVAILABILITY = {
|
|
151108
151427
|
available: true,
|
|
151109
151428
|
supportedProviders: ["web-search", "page-fetch", "github", "local-docs", "llm-synthesis"],
|
|
151110
|
-
supportedExportFormats: ["markdown", "json", "html"]
|
|
151429
|
+
supportedExportFormats: ["markdown", "json", "html"],
|
|
151430
|
+
setupInstructions: "If research fails to start, check Settings \u2192 Models and Authentication for provider enablement and credentials."
|
|
151111
151431
|
};
|
|
151112
151432
|
}
|
|
151113
151433
|
});
|
|
@@ -153027,32 +153347,32 @@ function logEntryToTimelineEntry(entry) {
|
|
|
153027
153347
|
log: entry
|
|
153028
153348
|
};
|
|
153029
153349
|
}
|
|
153030
|
-
function runExcerptToAgentLogs(
|
|
153350
|
+
function runExcerptToAgentLogs(run2) {
|
|
153031
153351
|
const entries = [];
|
|
153032
|
-
const taskId = typeof
|
|
153033
|
-
if (
|
|
153352
|
+
const taskId = typeof run2.contextSnapshot?.taskId === "string" ? run2.contextSnapshot.taskId : "agent-run";
|
|
153353
|
+
if (run2.stdoutExcerpt?.trim()) {
|
|
153034
153354
|
entries.push({
|
|
153035
|
-
timestamp:
|
|
153355
|
+
timestamp: run2.endedAt ?? run2.startedAt,
|
|
153036
153356
|
taskId,
|
|
153037
153357
|
type: "text",
|
|
153038
|
-
text:
|
|
153358
|
+
text: run2.stdoutExcerpt
|
|
153039
153359
|
});
|
|
153040
153360
|
}
|
|
153041
|
-
if (
|
|
153361
|
+
if (run2.stderrExcerpt?.trim()) {
|
|
153042
153362
|
entries.push({
|
|
153043
|
-
timestamp:
|
|
153363
|
+
timestamp: run2.endedAt ?? run2.startedAt,
|
|
153044
153364
|
taskId,
|
|
153045
153365
|
type: "tool_error",
|
|
153046
153366
|
text: "stderr",
|
|
153047
|
-
detail:
|
|
153367
|
+
detail: run2.stderrExcerpt
|
|
153048
153368
|
});
|
|
153049
153369
|
}
|
|
153050
|
-
if (
|
|
153370
|
+
if (run2.resultJson && Object.keys(run2.resultJson).length > 0 && entries.length === 0) {
|
|
153051
153371
|
entries.push({
|
|
153052
|
-
timestamp:
|
|
153372
|
+
timestamp: run2.endedAt ?? run2.startedAt,
|
|
153053
153373
|
taskId,
|
|
153054
153374
|
type: "text",
|
|
153055
|
-
text: JSON.stringify(
|
|
153375
|
+
text: JSON.stringify(run2.resultJson, null, 2)
|
|
153056
153376
|
});
|
|
153057
153377
|
}
|
|
153058
153378
|
return entries;
|
|
@@ -153183,11 +153503,26 @@ function createApiRoutes(store, options) {
|
|
|
153183
153503
|
return true;
|
|
153184
153504
|
}
|
|
153185
153505
|
}
|
|
153506
|
+
function resolveHeartbeatMonitor(scopedStore) {
|
|
153507
|
+
const engineManager = options?.engineManager;
|
|
153508
|
+
if (!engineManager) return void 0;
|
|
153509
|
+
try {
|
|
153510
|
+
const storeRoot = resolve27(scopedStore.getRootDir());
|
|
153511
|
+
for (const engine of engineManager.getAllEngines().values()) {
|
|
153512
|
+
if (resolve27(engine.getWorkingDirectory()) === storeRoot) {
|
|
153513
|
+
return engine.getHeartbeatMonitor() ?? void 0;
|
|
153514
|
+
}
|
|
153515
|
+
}
|
|
153516
|
+
} catch {
|
|
153517
|
+
}
|
|
153518
|
+
return void 0;
|
|
153519
|
+
}
|
|
153186
153520
|
const triggerCommentWakeForAssignedAgent = async (scopedStore, task, wake) => {
|
|
153187
153521
|
if (!hasHeartbeatExecutor || !heartbeatMonitor || !task.assignedAgentId) {
|
|
153188
153522
|
return;
|
|
153189
153523
|
}
|
|
153190
|
-
|
|
153524
|
+
const resolvedMonitor = isHeartbeatMonitorForProject(scopedStore) ? heartbeatMonitor : resolveHeartbeatMonitor(scopedStore);
|
|
153525
|
+
if (!resolvedMonitor) {
|
|
153191
153526
|
return;
|
|
153192
153527
|
}
|
|
153193
153528
|
const { AgentStore: AgentStore2 } = await Promise.resolve().then(() => (init_src(), src_exports));
|
|
@@ -153213,7 +153548,7 @@ function createApiRoutes(store, options) {
|
|
|
153213
153548
|
...triggeringCommentIds?.length ? { triggeringCommentIds } : {},
|
|
153214
153549
|
triggeringCommentType: wake.triggeringCommentType
|
|
153215
153550
|
};
|
|
153216
|
-
await
|
|
153551
|
+
await resolvedMonitor.executeHeartbeat({
|
|
153217
153552
|
agentId: assignedAgent.id,
|
|
153218
153553
|
source: "on_demand",
|
|
153219
153554
|
triggerDetail: wake.triggerDetail,
|
|
@@ -154643,6 +154978,7 @@ Description: ${step.description}`
|
|
|
154643
154978
|
hasHeartbeatExecutor,
|
|
154644
154979
|
heartbeatMonitor,
|
|
154645
154980
|
isHeartbeatMonitorForProject,
|
|
154981
|
+
resolveHeartbeatMonitor,
|
|
154646
154982
|
runExcerptToAgentLogs,
|
|
154647
154983
|
parseRunAuditFilters,
|
|
154648
154984
|
normalizeRunAuditEvent,
|
|
@@ -155979,39 +156315,39 @@ data: ${JSON.stringify(stripTaskEventHeavyFields(result))}
|
|
|
155979
156315
|
|
|
155980
156316
|
`);
|
|
155981
156317
|
};
|
|
155982
|
-
const onResearchRunCreated = (
|
|
156318
|
+
const onResearchRunCreated = (run2) => {
|
|
155983
156319
|
send(`event: research:run:created
|
|
155984
|
-
data: ${JSON.stringify(
|
|
156320
|
+
data: ${JSON.stringify(run2)}
|
|
155985
156321
|
|
|
155986
156322
|
`);
|
|
155987
156323
|
};
|
|
155988
|
-
const onResearchRunUpdated = (
|
|
156324
|
+
const onResearchRunUpdated = (run2) => {
|
|
155989
156325
|
send(`event: research:run:updated
|
|
155990
|
-
data: ${JSON.stringify(
|
|
156326
|
+
data: ${JSON.stringify(run2)}
|
|
155991
156327
|
|
|
155992
156328
|
`);
|
|
155993
156329
|
};
|
|
155994
|
-
const onResearchRunCompleted = (
|
|
156330
|
+
const onResearchRunCompleted = (run2) => {
|
|
155995
156331
|
send(`event: research:run:completed
|
|
155996
|
-
data: ${JSON.stringify(
|
|
156332
|
+
data: ${JSON.stringify(run2)}
|
|
155997
156333
|
|
|
155998
156334
|
`);
|
|
155999
156335
|
};
|
|
156000
|
-
const onResearchRunFailed = (
|
|
156336
|
+
const onResearchRunFailed = (run2) => {
|
|
156001
156337
|
send(`event: research:run:failed
|
|
156002
|
-
data: ${JSON.stringify(
|
|
156338
|
+
data: ${JSON.stringify(run2)}
|
|
156003
156339
|
|
|
156004
156340
|
`);
|
|
156005
156341
|
};
|
|
156006
|
-
const onResearchRunCancelled = (
|
|
156342
|
+
const onResearchRunCancelled = (run2) => {
|
|
156007
156343
|
send(`event: research:run:cancelled
|
|
156008
|
-
data: ${JSON.stringify(
|
|
156344
|
+
data: ${JSON.stringify(run2)}
|
|
156009
156345
|
|
|
156010
156346
|
`);
|
|
156011
156347
|
};
|
|
156012
|
-
const onResearchRunTimedOut = (
|
|
156348
|
+
const onResearchRunTimedOut = (run2) => {
|
|
156013
156349
|
send(`event: research:run:timed_out
|
|
156014
|
-
data: ${JSON.stringify(
|
|
156350
|
+
data: ${JSON.stringify(run2)}
|
|
156015
156351
|
|
|
156016
156352
|
`);
|
|
156017
156353
|
};
|
|
@@ -156141,15 +156477,15 @@ data: ${JSON.stringify(data)}
|
|
|
156141
156477
|
|
|
156142
156478
|
`);
|
|
156143
156479
|
};
|
|
156144
|
-
const onValidatorRunStarted = (
|
|
156480
|
+
const onValidatorRunStarted = (run2) => {
|
|
156145
156481
|
send(`event: validator-run:started
|
|
156146
|
-
data: ${JSON.stringify(
|
|
156482
|
+
data: ${JSON.stringify(run2)}
|
|
156147
156483
|
|
|
156148
156484
|
`);
|
|
156149
156485
|
};
|
|
156150
|
-
const onValidatorRunCompleted = (
|
|
156486
|
+
const onValidatorRunCompleted = (run2) => {
|
|
156151
156487
|
send(`event: validator-run:completed
|
|
156152
|
-
data: ${JSON.stringify(
|
|
156488
|
+
data: ${JSON.stringify(run2)}
|
|
156153
156489
|
|
|
156154
156490
|
`);
|
|
156155
156491
|
};
|
|
@@ -161973,7 +162309,8 @@ function createSkillsAdapter(options) {
|
|
|
161973
162309
|
}
|
|
161974
162310
|
const discoveredSkills = [];
|
|
161975
162311
|
for (const resource of skillResources) {
|
|
161976
|
-
const
|
|
162312
|
+
const relPath = relative13(resource.metadata.baseDir ?? "", resource.path).replaceAll("\\", "/");
|
|
162313
|
+
const skillRelativePath = relPath.startsWith("skills/") ? relPath : `skills/${relPath}`;
|
|
161977
162314
|
const skillId = computeSkillId(resource.metadata.source, skillRelativePath);
|
|
161978
162315
|
const skillName = extractSkillName(skillRelativePath, resource.metadata.source);
|
|
161979
162316
|
discoveredSkills.push({
|
|
@@ -165200,22 +165537,22 @@ function runStatusColor(status) {
|
|
|
165200
165537
|
return "gray";
|
|
165201
165538
|
}
|
|
165202
165539
|
}
|
|
165203
|
-
function getRunLogLines(
|
|
165204
|
-
if (Array.isArray(
|
|
165540
|
+
function getRunLogLines(run2) {
|
|
165541
|
+
if (Array.isArray(run2.logs) && run2.logs.length > 0) return run2.logs;
|
|
165205
165542
|
const lines = [];
|
|
165206
|
-
if (
|
|
165207
|
-
if (
|
|
165208
|
-
if (
|
|
165543
|
+
if (run2.triggerDetail) lines.push(`trigger: ${run2.triggerDetail}`);
|
|
165544
|
+
if (run2.invocationSource) lines.push(`source: ${run2.invocationSource}`);
|
|
165545
|
+
if (run2.stdoutExcerpt) {
|
|
165209
165546
|
lines.push("stdout:");
|
|
165210
|
-
lines.push(...
|
|
165547
|
+
lines.push(...run2.stdoutExcerpt.split(/\r?\n/).filter((line) => line.length > 0));
|
|
165211
165548
|
}
|
|
165212
|
-
if (
|
|
165549
|
+
if (run2.stderrExcerpt) {
|
|
165213
165550
|
lines.push("stderr:");
|
|
165214
|
-
lines.push(...
|
|
165551
|
+
lines.push(...run2.stderrExcerpt.split(/\r?\n/).filter((line) => line.length > 0));
|
|
165215
165552
|
}
|
|
165216
|
-
if (
|
|
165553
|
+
if (run2.resultJson) {
|
|
165217
165554
|
lines.push("result:");
|
|
165218
|
-
lines.push(JSON.stringify(
|
|
165555
|
+
lines.push(JSON.stringify(run2.resultJson));
|
|
165219
165556
|
}
|
|
165220
165557
|
return lines.length > 0 ? lines : ["No logs captured for this run."];
|
|
165221
165558
|
}
|
|
@@ -165470,12 +165807,12 @@ function AgentsView({ state }) {
|
|
|
165470
165807
|
recentRuns.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
165471
165808
|
/* @__PURE__ */ jsx(Box, { height: 1 }),
|
|
165472
165809
|
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "Run history (latest first):" }),
|
|
165473
|
-
recentRuns.slice(0, 5).map((
|
|
165810
|
+
recentRuns.slice(0, 5).map((run2, i) => /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, marginLeft: 1, children: [
|
|
165474
165811
|
/* @__PURE__ */ jsx(Text, { color: detailFocused && i === selectedRunIndex ? "white" : "gray", children: detailFocused && i === selectedRunIndex ? "\u25B6" : " " }),
|
|
165475
|
-
/* @__PURE__ */ jsx(Text, { color: runStatusColor(
|
|
165476
|
-
/* @__PURE__ */ jsx(Text, { dimColor: true, children:
|
|
165477
|
-
|
|
165478
|
-
] },
|
|
165812
|
+
/* @__PURE__ */ jsx(Text, { color: runStatusColor(run2.status), children: formatRunStatusLabel(run2.status) }),
|
|
165813
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: run2.startedAt.slice(11, 19) }),
|
|
165814
|
+
run2.triggerDetail && /* @__PURE__ */ jsx(Text, { dimColor: true, wrap: "truncate-end", children: run2.triggerDetail })
|
|
165815
|
+
] }, run2.id)),
|
|
165479
165816
|
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "[Enter] open logs" })
|
|
165480
165817
|
] })
|
|
165481
165818
|
] })
|
|
@@ -168741,6 +169078,20 @@ async function runDashboard(port, opts = {}) {
|
|
|
168741
169078
|
logSink.log("Starting in paused mode \u2014 automation disabled", "engine");
|
|
168742
169079
|
}
|
|
168743
169080
|
const onMergeImpl = async (taskId) => {
|
|
169081
|
+
const settings = await store.getSettings();
|
|
169082
|
+
if (getMergeStrategy(settings) === "pull-request") {
|
|
169083
|
+
const githubClient = new GitHubClient();
|
|
169084
|
+
const outcome = await processPullRequestMergeTask(store, cwd, taskId, githubClient, getTaskMergeBlocker);
|
|
169085
|
+
const task = await store.getTask(taskId);
|
|
169086
|
+
return {
|
|
169087
|
+
task,
|
|
169088
|
+
branch: getTaskBranchName(taskId),
|
|
169089
|
+
merged: outcome === "merged",
|
|
169090
|
+
worktreeRemoved: false,
|
|
169091
|
+
branchDeleted: false,
|
|
169092
|
+
error: outcome === "waiting" ? "pull request not ready" : void 0
|
|
169093
|
+
};
|
|
169094
|
+
}
|
|
168744
169095
|
const streamedMergeLog = new StreamedLogBuffer(
|
|
168745
169096
|
(line) => logSink.log(line, "merge"),
|
|
168746
169097
|
STREAM_LOG_FLUSH_IDLE_MS
|
|
@@ -176354,12 +176705,27 @@ async function getStore3(projectName) {
|
|
|
176354
176705
|
await store.init();
|
|
176355
176706
|
return store;
|
|
176356
176707
|
}
|
|
176708
|
+
function hasProviderCredentials(settings, providerId) {
|
|
176709
|
+
if (!providerId) return false;
|
|
176710
|
+
if (providerId === "searxng") return Boolean(settings.researchSearxngUrl);
|
|
176711
|
+
if (providerId === "brave") return Boolean(settings.researchBraveApiKey);
|
|
176712
|
+
if (providerId === "google") return Boolean(settings.researchGoogleSearchApiKey && settings.researchGoogleSearchCx);
|
|
176713
|
+
if (providerId === "tavily") return Boolean(settings.researchTavilyApiKey);
|
|
176714
|
+
return false;
|
|
176715
|
+
}
|
|
176357
176716
|
async function getResearchRuntime(store) {
|
|
176358
176717
|
const settings = await store.getSettings();
|
|
176359
176718
|
const resolved = resolveResearchSettings(settings);
|
|
176360
176719
|
if (!resolved.enabled) {
|
|
176361
176720
|
throw new Error("feature-disabled: Research is disabled in settings.");
|
|
176362
176721
|
}
|
|
176722
|
+
const configuredProvider = resolved.searchProvider ?? settings.researchWebSearchProvider;
|
|
176723
|
+
if (!configuredProvider) {
|
|
176724
|
+
throw new Error("provider-unavailable: Research providers are not configured. Add provider credentials in settings.");
|
|
176725
|
+
}
|
|
176726
|
+
if (!hasProviderCredentials(settings, configuredProvider)) {
|
|
176727
|
+
throw new Error(`missing-credentials: ${configuredProvider} credentials are missing. Configure Authentication and Research defaults in settings.`);
|
|
176728
|
+
}
|
|
176363
176729
|
const registry = new ResearchProviderRegistry(settings, process.cwd());
|
|
176364
176730
|
const availableProviderTypes = registry.getAvailableProviders();
|
|
176365
176731
|
if (availableProviderTypes.length === 0) {
|
|
@@ -176375,17 +176741,17 @@ async function getResearchRuntime(store) {
|
|
|
176375
176741
|
});
|
|
176376
176742
|
return { orchestrator, settings, resolved, availableProviderTypes };
|
|
176377
176743
|
}
|
|
176378
|
-
function printRun(
|
|
176379
|
-
console.log(`Run: ${
|
|
176380
|
-
console.log(`Status: ${
|
|
176381
|
-
console.log(`Query: ${
|
|
176382
|
-
console.log(`Created: ${
|
|
176383
|
-
console.log(`Updated: ${
|
|
176384
|
-
if (
|
|
176385
|
-
if (
|
|
176386
|
-
if (
|
|
176387
|
-
if (
|
|
176388
|
-
if (
|
|
176744
|
+
function printRun(run2) {
|
|
176745
|
+
console.log(`Run: ${run2.id}`);
|
|
176746
|
+
console.log(`Status: ${run2.status}`);
|
|
176747
|
+
console.log(`Query: ${run2.query}`);
|
|
176748
|
+
console.log(`Created: ${run2.createdAt}`);
|
|
176749
|
+
console.log(`Updated: ${run2.updatedAt}`);
|
|
176750
|
+
if (run2.startedAt) console.log(`Started: ${run2.startedAt}`);
|
|
176751
|
+
if (run2.completedAt) console.log(`Completed: ${run2.completedAt}`);
|
|
176752
|
+
if (run2.cancelledAt) console.log(`Cancelled: ${run2.cancelledAt}`);
|
|
176753
|
+
if (run2.results?.summary) console.log(`Summary: ${run2.results.summary}`);
|
|
176754
|
+
if (run2.error) console.log(`Error: ${run2.error}`);
|
|
176389
176755
|
}
|
|
176390
176756
|
function jsonOut(payload) {
|
|
176391
176757
|
console.log(JSON.stringify(payload, null, 2));
|
|
@@ -176408,12 +176774,12 @@ async function runResearchCreate(options) {
|
|
|
176408
176774
|
});
|
|
176409
176775
|
const runPromise = orchestrator.startRun(runId, options.query);
|
|
176410
176776
|
if (!options.waitForCompletion) {
|
|
176411
|
-
const
|
|
176777
|
+
const run2 = store.getResearchStore().getRun(runId);
|
|
176412
176778
|
if (options.json) {
|
|
176413
|
-
jsonOut(
|
|
176779
|
+
jsonOut(run2);
|
|
176414
176780
|
} else {
|
|
176415
176781
|
console.log(`Created research run ${runId}.`);
|
|
176416
|
-
if (
|
|
176782
|
+
if (run2) printRun(run2);
|
|
176417
176783
|
}
|
|
176418
176784
|
return;
|
|
176419
176785
|
}
|
|
@@ -176461,8 +176827,8 @@ async function runResearchList(options = {}) {
|
|
|
176461
176827
|
console.log("No research runs found.");
|
|
176462
176828
|
return;
|
|
176463
176829
|
}
|
|
176464
|
-
for (const
|
|
176465
|
-
console.log(`${
|
|
176830
|
+
for (const run2 of runs) {
|
|
176831
|
+
console.log(`${run2.id} [${run2.status}] ${run2.query}`);
|
|
176466
176832
|
}
|
|
176467
176833
|
} catch (error) {
|
|
176468
176834
|
handleError(error);
|
|
@@ -176471,46 +176837,46 @@ async function runResearchList(options = {}) {
|
|
|
176471
176837
|
async function runResearchShow(runId, options = {}) {
|
|
176472
176838
|
try {
|
|
176473
176839
|
const store = await getStore3(options.projectName);
|
|
176474
|
-
const
|
|
176475
|
-
if (!
|
|
176840
|
+
const run2 = store.getResearchStore().getRun(runId);
|
|
176841
|
+
if (!run2) throw new Error(`Research run not found: ${runId}`);
|
|
176476
176842
|
if (options.json) {
|
|
176477
|
-
jsonOut(
|
|
176843
|
+
jsonOut(run2);
|
|
176478
176844
|
return;
|
|
176479
176845
|
}
|
|
176480
|
-
printRun(
|
|
176846
|
+
printRun(run2);
|
|
176481
176847
|
} catch (error) {
|
|
176482
176848
|
handleError(error);
|
|
176483
176849
|
}
|
|
176484
176850
|
}
|
|
176485
|
-
function renderMarkdown(
|
|
176486
|
-
const citations =
|
|
176851
|
+
function renderMarkdown(run2) {
|
|
176852
|
+
const citations = run2.results?.citations?.length ? `
|
|
176487
176853
|
## Citations
|
|
176488
|
-
${
|
|
176489
|
-
return `# ${
|
|
176854
|
+
${run2.results.citations.map((citation) => `- ${citation}`).join("\n")}` : "";
|
|
176855
|
+
return `# ${run2.topic || run2.query}
|
|
176490
176856
|
|
|
176491
176857
|
## Summary
|
|
176492
|
-
${
|
|
176858
|
+
${run2.results?.summary ?? ""}${citations}
|
|
176493
176859
|
`;
|
|
176494
176860
|
}
|
|
176495
176861
|
async function runResearchExport(options) {
|
|
176496
176862
|
try {
|
|
176497
176863
|
const store = await getStore3(options.projectName);
|
|
176498
|
-
const
|
|
176499
|
-
if (!
|
|
176864
|
+
const run2 = store.getResearchStore().getRun(options.runId);
|
|
176865
|
+
if (!run2) throw new Error(`Research run not found: ${options.runId}`);
|
|
176500
176866
|
const format = options.format ?? "markdown";
|
|
176501
176867
|
if (!RESEARCH_EXPORT_FORMATS.includes(format)) {
|
|
176502
176868
|
throw new Error(`Unsupported export format: ${format}`);
|
|
176503
176869
|
}
|
|
176504
|
-
const content = format === "json" ? JSON.stringify(
|
|
176870
|
+
const content = format === "json" ? JSON.stringify(run2, null, 2) : renderMarkdown(run2);
|
|
176505
176871
|
const ext = format === "json" ? "json" : "md";
|
|
176506
|
-
const outputPath = options.output ? resolve42(options.output) : join69(process.cwd(), `research-${
|
|
176872
|
+
const outputPath = options.output ? resolve42(options.output) : join69(process.cwd(), `research-${run2.id.toLowerCase()}.${ext}`);
|
|
176507
176873
|
await writeFile19(outputPath, content, "utf8");
|
|
176508
|
-
store.getResearchStore().createExport(
|
|
176874
|
+
store.getResearchStore().createExport(run2.id, format, content);
|
|
176509
176875
|
if (options.json) {
|
|
176510
|
-
jsonOut({ runId:
|
|
176876
|
+
jsonOut({ runId: run2.id, format, outputPath, bytes: Buffer.byteLength(content, "utf8") });
|
|
176511
176877
|
return;
|
|
176512
176878
|
}
|
|
176513
|
-
console.log(`Exported ${
|
|
176879
|
+
console.log(`Exported ${run2.id} (${format}) to ${outputPath}`);
|
|
176514
176880
|
} catch (error) {
|
|
176515
176881
|
handleError(error);
|
|
176516
176882
|
}
|
|
@@ -176518,16 +176884,19 @@ async function runResearchExport(options) {
|
|
|
176518
176884
|
async function runResearchCancel(runId, options = {}) {
|
|
176519
176885
|
try {
|
|
176520
176886
|
const store = await getStore3(options.projectName);
|
|
176521
|
-
const
|
|
176522
|
-
if (!
|
|
176887
|
+
const run2 = store.getResearchStore().getRun(runId);
|
|
176888
|
+
if (!run2) throw new Error(`Research run not found: ${runId}`);
|
|
176889
|
+
if (!["queued", "running", "cancelling", "retry_waiting"].includes(run2.status)) {
|
|
176890
|
+
throw new Error(`invalid-transition: Run ${runId} cannot be cancelled from status ${run2.status}.`);
|
|
176891
|
+
}
|
|
176523
176892
|
const { orchestrator } = await getResearchRuntime(store);
|
|
176524
176893
|
const cancelled = orchestrator.cancelRun(runId);
|
|
176525
176894
|
if (options.json) {
|
|
176526
|
-
jsonOut({ cancelled, run });
|
|
176895
|
+
jsonOut({ cancelled, run: run2 });
|
|
176527
176896
|
return;
|
|
176528
176897
|
}
|
|
176529
176898
|
console.log(cancelled ? `Cancellation requested for ${runId}.` : `Run ${runId} is not active.`);
|
|
176530
|
-
printRun(
|
|
176899
|
+
printRun(run2);
|
|
176531
176900
|
} catch (error) {
|
|
176532
176901
|
handleError(error);
|
|
176533
176902
|
}
|
|
@@ -176537,15 +176906,21 @@ async function runResearchRetry(runId, options = {}) {
|
|
|
176537
176906
|
const store = await getStore3(options.projectName);
|
|
176538
176907
|
const existing = store.getResearchStore().getRun(runId);
|
|
176539
176908
|
if (!existing) throw new Error(`Research run not found: ${runId}`);
|
|
176909
|
+
if (existing.status === "retry_exhausted" || existing.lifecycle?.errorCode === "RETRY_EXHAUSTED") {
|
|
176910
|
+
throw new Error(`retry-exhausted: Run ${runId} has exhausted retry attempts.`);
|
|
176911
|
+
}
|
|
176912
|
+
if (existing.lifecycle?.retryable === false) {
|
|
176913
|
+
throw new Error(`non-retryable-provider-error: Run ${runId} is marked non-retryable.`);
|
|
176914
|
+
}
|
|
176540
176915
|
const { orchestrator } = await getResearchRuntime(store);
|
|
176541
176916
|
const newRunId = orchestrator.retryRun(runId);
|
|
176542
|
-
const
|
|
176917
|
+
const run2 = store.getResearchStore().getRun(newRunId);
|
|
176543
176918
|
if (options.json) {
|
|
176544
|
-
jsonOut({ retryOf: runId, run });
|
|
176919
|
+
jsonOut({ retryOf: runId, run: run2 });
|
|
176545
176920
|
return;
|
|
176546
176921
|
}
|
|
176547
176922
|
console.log(`Created retry run ${newRunId} from ${runId}.`);
|
|
176548
|
-
if (
|
|
176923
|
+
if (run2) printRun(run2);
|
|
176549
176924
|
} catch (error) {
|
|
176550
176925
|
handleError(error);
|
|
176551
176926
|
}
|