@runfusion/fusion 0.17.2 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.js +1004 -633
- package/dist/client/assets/ChatView-BomXmqar.js +1 -0
- package/dist/client/assets/{DevServerView-GFFVXHVP.js → DevServerView-yFvF4xL4.js} +1 -1
- package/dist/client/assets/DirectoryPicker-BDNodhtF.js +1 -0
- package/dist/client/assets/DocumentsView-CAWtDEaL.js +1 -0
- package/dist/client/assets/{InsightsView-Bxu0TJkt.js → InsightsView-CDkiJeW1.js} +2 -2
- package/dist/client/assets/MemoryView-ZRQ9EL9H.js +2 -0
- package/dist/client/assets/NodesView-DosrOyeH.js +14 -0
- package/dist/client/assets/NodesView-sJgPLTzz.css +1 -0
- package/dist/client/assets/{PiExtensionsManager-4e3MlD62.js → PiExtensionsManager-CzZ1LEpz.js} +3 -3
- package/dist/client/assets/PluginManager-Dp3vPsMO.js +1 -0
- package/dist/client/assets/ResearchView-PvNkdaQE.js +1 -0
- package/dist/client/assets/{RoadmapsView-jHTOK0RQ.js → RoadmapsView-BUW-HJz5.js} +2 -2
- package/dist/client/assets/SettingsModal-BNSrO1M9.css +1 -0
- package/dist/client/assets/{SettingsModal-4Z8ZJMzD.js → SettingsModal-ByVl_fUi.js} +1 -1
- package/dist/client/assets/SettingsModal-oOnIed5O.css +1 -0
- package/dist/client/assets/SettingsModal-uzo470XS.js +31 -0
- package/dist/client/assets/SetupWizardModal-DH1hpyiP.js +1 -0
- package/dist/client/assets/SkillsView-B-RqQSFE.js +1 -0
- package/dist/client/assets/index-CtiRbTNv.js +1229 -0
- package/dist/client/assets/index-Dy-xC2C2.css +1 -0
- package/dist/client/assets/{users-D3u6f2Rz.js → users-WyHhw14V.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 +488 -43
- 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(
|
|
@@ -12407,7 +12414,7 @@ var init_insight_store = __esm({
|
|
|
12407
12414
|
null
|
|
12408
12415
|
);
|
|
12409
12416
|
this.db.bumpLastModified();
|
|
12410
|
-
const
|
|
12417
|
+
const run2 = {
|
|
12411
12418
|
id,
|
|
12412
12419
|
projectId,
|
|
12413
12420
|
trigger: input.trigger,
|
|
@@ -12424,8 +12431,8 @@ var init_insight_store = __esm({
|
|
|
12424
12431
|
cancelledAt: null,
|
|
12425
12432
|
lifecycle
|
|
12426
12433
|
};
|
|
12427
|
-
this.emit("run:created",
|
|
12428
|
-
return
|
|
12434
|
+
this.emit("run:created", run2);
|
|
12435
|
+
return run2;
|
|
12429
12436
|
}
|
|
12430
12437
|
/**
|
|
12431
12438
|
* Get a single run by ID.
|
|
@@ -12588,8 +12595,8 @@ var init_insight_store = __esm({
|
|
|
12588
12595
|
return this.createRun(projectId, input);
|
|
12589
12596
|
}
|
|
12590
12597
|
appendRunEvent(runId, event) {
|
|
12591
|
-
const
|
|
12592
|
-
if (!
|
|
12598
|
+
const run2 = this.getRun(runId);
|
|
12599
|
+
if (!run2) {
|
|
12593
12600
|
throw new Error(`Insight run not found: ${runId}`);
|
|
12594
12601
|
}
|
|
12595
12602
|
const createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -12765,7 +12772,7 @@ var init_research_store = __esm({
|
|
|
12765
12772
|
}
|
|
12766
12773
|
createRun(input) {
|
|
12767
12774
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
12768
|
-
const
|
|
12775
|
+
const run2 = {
|
|
12769
12776
|
id: generateRunId2(),
|
|
12770
12777
|
query: input.query,
|
|
12771
12778
|
topic: input.topic,
|
|
@@ -12794,30 +12801,30 @@ var init_research_store = __esm({
|
|
|
12794
12801
|
tokenUsage, tags, metadata, lifecycle, createdAt, updatedAt, startedAt, completedAt, cancelledAt
|
|
12795
12802
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
12796
12803
|
`).run(
|
|
12797
|
-
|
|
12798
|
-
|
|
12799
|
-
|
|
12800
|
-
|
|
12801
|
-
|
|
12802
|
-
|
|
12803
|
-
toJsonNullable(
|
|
12804
|
-
toJson(
|
|
12805
|
-
toJson(
|
|
12806
|
-
toJsonNullable(
|
|
12804
|
+
run2.id,
|
|
12805
|
+
run2.query,
|
|
12806
|
+
run2.topic ?? null,
|
|
12807
|
+
run2.status,
|
|
12808
|
+
run2.projectId ?? null,
|
|
12809
|
+
run2.trigger ?? null,
|
|
12810
|
+
toJsonNullable(run2.providerConfig),
|
|
12811
|
+
toJson(run2.sources),
|
|
12812
|
+
toJson(run2.events),
|
|
12813
|
+
toJsonNullable(run2.results),
|
|
12807
12814
|
null,
|
|
12808
12815
|
null,
|
|
12809
|
-
toJson(
|
|
12810
|
-
toJsonNullable(
|
|
12811
|
-
toJsonNullable(
|
|
12812
|
-
|
|
12813
|
-
|
|
12816
|
+
toJson(run2.tags),
|
|
12817
|
+
toJsonNullable(run2.metadata),
|
|
12818
|
+
toJsonNullable(run2.lifecycle),
|
|
12819
|
+
run2.createdAt,
|
|
12820
|
+
run2.updatedAt,
|
|
12814
12821
|
null,
|
|
12815
12822
|
null,
|
|
12816
12823
|
null
|
|
12817
12824
|
);
|
|
12818
12825
|
this.db.bumpLastModified();
|
|
12819
|
-
this.emit("run:created",
|
|
12820
|
-
return
|
|
12826
|
+
this.emit("run:created", run2);
|
|
12827
|
+
return run2;
|
|
12821
12828
|
}
|
|
12822
12829
|
getRun(id) {
|
|
12823
12830
|
const row = this.db.prepare("SELECT * FROM research_runs WHERE id = ?").get(id);
|
|
@@ -12907,8 +12914,8 @@ var init_research_store = __esm({
|
|
|
12907
12914
|
return deleted;
|
|
12908
12915
|
}
|
|
12909
12916
|
addEvent(runId, event) {
|
|
12910
|
-
const
|
|
12911
|
-
if (!
|
|
12917
|
+
const run2 = this.getRun(runId);
|
|
12918
|
+
if (!run2) throw new Error(`Research run not found: ${runId}`);
|
|
12912
12919
|
const created = {
|
|
12913
12920
|
id: generateId("REVT"),
|
|
12914
12921
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -12926,12 +12933,12 @@ var init_research_store = __esm({
|
|
|
12926
12933
|
seq2,
|
|
12927
12934
|
created.type,
|
|
12928
12935
|
created.message,
|
|
12929
|
-
|
|
12936
|
+
run2.status,
|
|
12930
12937
|
null,
|
|
12931
12938
|
toJsonNullable(created.metadata),
|
|
12932
12939
|
created.timestamp
|
|
12933
12940
|
);
|
|
12934
|
-
this.persistRun({ ...
|
|
12941
|
+
this.persistRun({ ...run2, events: [...run2.events, created], updatedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
12935
12942
|
this.db.bumpLastModified();
|
|
12936
12943
|
this.emit("event:added", { runId, event: created });
|
|
12937
12944
|
return created;
|
|
@@ -12940,8 +12947,8 @@ var init_research_store = __esm({
|
|
|
12940
12947
|
return this.addEvent(runId, event);
|
|
12941
12948
|
}
|
|
12942
12949
|
appendLifecycleEvent(runId, event) {
|
|
12943
|
-
const
|
|
12944
|
-
if (!
|
|
12950
|
+
const run2 = this.getRun(runId);
|
|
12951
|
+
if (!run2) throw new Error(`Research run not found: ${runId}`);
|
|
12945
12952
|
const createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
12946
12953
|
const lifecycleEvent = {
|
|
12947
12954
|
id: generateId("REVT"),
|
|
@@ -12990,17 +12997,17 @@ var init_research_store = __esm({
|
|
|
12990
12997
|
}));
|
|
12991
12998
|
}
|
|
12992
12999
|
addSource(runId, source) {
|
|
12993
|
-
const
|
|
12994
|
-
if (!
|
|
13000
|
+
const run2 = this.getRun(runId);
|
|
13001
|
+
if (!run2) throw new Error(`Research run not found: ${runId}`);
|
|
12995
13002
|
const created = { ...source, id: generateId("RSRC") };
|
|
12996
|
-
this.updateRun(runId, { sources: [...
|
|
13003
|
+
this.updateRun(runId, { sources: [...run2.sources, created] });
|
|
12997
13004
|
this.emit("source:added", { runId, source: created });
|
|
12998
13005
|
return created;
|
|
12999
13006
|
}
|
|
13000
13007
|
updateSource(runId, sourceId, updates) {
|
|
13001
|
-
const
|
|
13002
|
-
if (!
|
|
13003
|
-
const next =
|
|
13008
|
+
const run2 = this.getRun(runId);
|
|
13009
|
+
if (!run2) throw new Error(`Research run not found: ${runId}`);
|
|
13010
|
+
const next = run2.sources.map((source) => {
|
|
13004
13011
|
if (source.id !== sourceId) return source;
|
|
13005
13012
|
return {
|
|
13006
13013
|
...source,
|
|
@@ -13015,20 +13022,20 @@ var init_research_store = __esm({
|
|
|
13015
13022
|
if (!updated) throw new Error(`Research run not found: ${runId}`);
|
|
13016
13023
|
}
|
|
13017
13024
|
updateStatus(runId, status, extra) {
|
|
13018
|
-
const
|
|
13019
|
-
if (!
|
|
13025
|
+
const run2 = this.getRun(runId);
|
|
13026
|
+
if (!run2) throw new Error(`Research run not found: ${runId}`);
|
|
13020
13027
|
const normalizedStatus = normalizeStatus(status);
|
|
13021
13028
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
13022
13029
|
const patch = {
|
|
13023
13030
|
...extra ?? {},
|
|
13024
13031
|
status: normalizedStatus,
|
|
13025
13032
|
lifecycle: {
|
|
13026
|
-
...
|
|
13033
|
+
...run2.lifecycle ?? {}
|
|
13027
13034
|
}
|
|
13028
13035
|
};
|
|
13029
|
-
if (normalizedStatus === "running" && !
|
|
13030
|
-
if (TERMINAL_STATUSES.has(normalizedStatus) && !
|
|
13031
|
-
if (normalizedStatus === "cancelled" && !
|
|
13036
|
+
if (normalizedStatus === "running" && !run2.startedAt) patch.startedAt = now;
|
|
13037
|
+
if (TERMINAL_STATUSES.has(normalizedStatus) && !run2.completedAt) patch.completedAt = now;
|
|
13038
|
+
if (normalizedStatus === "cancelled" && !run2.cancelledAt) patch.cancelledAt = now;
|
|
13032
13039
|
if (normalizedStatus === "completed") {
|
|
13033
13040
|
patch.lifecycle = { ...patch.lifecycle ?? {}, terminalReason: "completed", retryable: false, errorCode: void 0 };
|
|
13034
13041
|
} else if (normalizedStatus === "failed") {
|
|
@@ -13160,18 +13167,18 @@ var init_research_store = __esm({
|
|
|
13160
13167
|
}
|
|
13161
13168
|
}
|
|
13162
13169
|
requestCancellation(runId, reason = "Cancelled by user") {
|
|
13163
|
-
const
|
|
13164
|
-
if (!
|
|
13165
|
-
if (TERMINAL_STATUSES.has(
|
|
13166
|
-
return
|
|
13170
|
+
const run2 = this.getRun(runId);
|
|
13171
|
+
if (!run2) throw new Error(`Research run not found: ${runId}`);
|
|
13172
|
+
if (TERMINAL_STATUSES.has(run2.status)) {
|
|
13173
|
+
return run2;
|
|
13167
13174
|
}
|
|
13168
13175
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
13169
|
-
const alreadyCancelling =
|
|
13176
|
+
const alreadyCancelling = run2.status === "cancelling";
|
|
13170
13177
|
const updated = this.updateRun(runId, {
|
|
13171
13178
|
status: "cancelling",
|
|
13172
13179
|
lifecycle: {
|
|
13173
|
-
...
|
|
13174
|
-
cancellationRequestedAt:
|
|
13180
|
+
...run2.lifecycle ?? {},
|
|
13181
|
+
cancellationRequestedAt: run2.lifecycle?.cancellationRequestedAt ?? now,
|
|
13175
13182
|
terminalCause: reason,
|
|
13176
13183
|
errorCode: "RUN_CANCELLED",
|
|
13177
13184
|
retryable: false
|
|
@@ -13184,34 +13191,34 @@ var init_research_store = __esm({
|
|
|
13184
13191
|
return updated;
|
|
13185
13192
|
}
|
|
13186
13193
|
createRetryRun(runId, maxAttempts) {
|
|
13187
|
-
const
|
|
13188
|
-
if (!
|
|
13189
|
-
if (
|
|
13190
|
-
throw new ResearchLifecycleError(`Run ${runId} is not retryable from status ${
|
|
13194
|
+
const run2 = this.getRun(runId);
|
|
13195
|
+
if (!run2) throw new Error(`Research run not found: ${runId}`);
|
|
13196
|
+
if (run2.status !== "failed" && run2.status !== "timed_out") {
|
|
13197
|
+
throw new ResearchLifecycleError(`Run ${runId} is not retryable from status ${run2.status}`, "invalid_transition");
|
|
13191
13198
|
}
|
|
13192
|
-
const currentAttempt =
|
|
13193
|
-
const configuredMaxAttempts = maxAttempts ??
|
|
13199
|
+
const currentAttempt = run2.lifecycle?.attempt ?? 1;
|
|
13200
|
+
const configuredMaxAttempts = maxAttempts ?? run2.lifecycle?.maxAttempts ?? 3;
|
|
13194
13201
|
const nextAttempt = currentAttempt + 1;
|
|
13195
13202
|
if (nextAttempt > configuredMaxAttempts) {
|
|
13196
13203
|
this.updateRun(runId, { status: "retry_exhausted" });
|
|
13197
13204
|
throw new ResearchLifecycleError(`Run ${runId} exhausted retries`, "not_retryable");
|
|
13198
13205
|
}
|
|
13199
|
-
if (!
|
|
13206
|
+
if (!run2.lifecycle?.retryable) {
|
|
13200
13207
|
throw new ResearchLifecycleError(`Run ${runId} is non-retryable`, "not_retryable");
|
|
13201
13208
|
}
|
|
13202
|
-
const rootRunId =
|
|
13209
|
+
const rootRunId = run2.lifecycle?.rootRunId ?? run2.id;
|
|
13203
13210
|
const retryRun = this.createRun({
|
|
13204
|
-
query:
|
|
13205
|
-
topic:
|
|
13206
|
-
projectId:
|
|
13207
|
-
trigger:
|
|
13208
|
-
providerConfig:
|
|
13209
|
-
tags:
|
|
13210
|
-
metadata:
|
|
13211
|
+
query: run2.query,
|
|
13212
|
+
topic: run2.topic,
|
|
13213
|
+
projectId: run2.projectId,
|
|
13214
|
+
trigger: run2.trigger,
|
|
13215
|
+
providerConfig: run2.providerConfig,
|
|
13216
|
+
tags: run2.tags,
|
|
13217
|
+
metadata: run2.metadata,
|
|
13211
13218
|
lifecycle: {
|
|
13212
13219
|
attempt: nextAttempt,
|
|
13213
13220
|
maxAttempts: configuredMaxAttempts,
|
|
13214
|
-
retryOfRunId:
|
|
13221
|
+
retryOfRunId: run2.id,
|
|
13215
13222
|
rootRunId
|
|
13216
13223
|
}
|
|
13217
13224
|
});
|
|
@@ -13223,8 +13230,8 @@ var init_research_store = __esm({
|
|
|
13223
13230
|
});
|
|
13224
13231
|
this.appendLifecycleEvent(retryRun.id, {
|
|
13225
13232
|
type: "retry_scheduled",
|
|
13226
|
-
message: `Retry scheduled from ${
|
|
13227
|
-
metadata: { retryOfRunId:
|
|
13233
|
+
message: `Retry scheduled from ${run2.id}`,
|
|
13234
|
+
metadata: { retryOfRunId: run2.id, rootRunId, attempt: nextAttempt }
|
|
13228
13235
|
});
|
|
13229
13236
|
return retryRun;
|
|
13230
13237
|
}
|
|
@@ -13232,7 +13239,7 @@ var init_research_store = __esm({
|
|
|
13232
13239
|
const row = this.db.prepare("SELECT COALESCE(MAX(seq), 0) AS seq FROM research_run_events WHERE runId = ?").get(runId);
|
|
13233
13240
|
return Number(row?.seq ?? 0) + 1;
|
|
13234
13241
|
}
|
|
13235
|
-
persistRun(
|
|
13242
|
+
persistRun(run2) {
|
|
13236
13243
|
this.db.prepare(`
|
|
13237
13244
|
UPDATE research_runs
|
|
13238
13245
|
SET query = ?, topic = ?, status = ?, projectId = ?, trigger = ?, providerConfig = ?, sources = ?, events = ?,
|
|
@@ -13240,25 +13247,25 @@ var init_research_store = __esm({
|
|
|
13240
13247
|
startedAt = ?, completedAt = ?, cancelledAt = ?
|
|
13241
13248
|
WHERE id = ?
|
|
13242
13249
|
`).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
|
-
|
|
13250
|
+
run2.query,
|
|
13251
|
+
run2.topic ?? null,
|
|
13252
|
+
run2.status,
|
|
13253
|
+
run2.projectId ?? null,
|
|
13254
|
+
run2.trigger ?? null,
|
|
13255
|
+
toJsonNullable(run2.providerConfig),
|
|
13256
|
+
toJson(run2.sources),
|
|
13257
|
+
toJson(run2.events),
|
|
13258
|
+
toJsonNullable(run2.results),
|
|
13259
|
+
run2.error ?? null,
|
|
13260
|
+
toJsonNullable(run2.tokenUsage),
|
|
13261
|
+
toJson(run2.tags),
|
|
13262
|
+
toJsonNullable(run2.metadata),
|
|
13263
|
+
toJsonNullable(run2.lifecycle),
|
|
13264
|
+
run2.updatedAt,
|
|
13265
|
+
run2.startedAt ?? null,
|
|
13266
|
+
run2.completedAt ?? null,
|
|
13267
|
+
run2.cancelledAt ?? null,
|
|
13268
|
+
run2.id
|
|
13262
13269
|
);
|
|
13263
13270
|
this.db.bumpLastModified();
|
|
13264
13271
|
}
|
|
@@ -16651,12 +16658,12 @@ var require_thunky = __commonJS({
|
|
|
16651
16658
|
process.nextTick(upgrade, 42);
|
|
16652
16659
|
module.exports = thunky;
|
|
16653
16660
|
function thunky(fn) {
|
|
16654
|
-
var state =
|
|
16661
|
+
var state = run2;
|
|
16655
16662
|
return thunk;
|
|
16656
16663
|
function thunk(callback) {
|
|
16657
16664
|
state(callback || noop);
|
|
16658
16665
|
}
|
|
16659
|
-
function
|
|
16666
|
+
function run2(callback) {
|
|
16660
16667
|
var stack = [callback];
|
|
16661
16668
|
state = wait;
|
|
16662
16669
|
fn(done);
|
|
@@ -16665,7 +16672,7 @@ var require_thunky = __commonJS({
|
|
|
16665
16672
|
}
|
|
16666
16673
|
function done(err) {
|
|
16667
16674
|
var args = arguments;
|
|
16668
|
-
state = isError(err) ?
|
|
16675
|
+
state = isError(err) ? run2 : finished;
|
|
16669
16676
|
while (stack.length) finished(stack.shift());
|
|
16670
16677
|
function finished(callback2) {
|
|
16671
16678
|
nextTick2(apply2, callback2, args);
|
|
@@ -32647,6 +32654,7 @@ var init_store = __esm({
|
|
|
32647
32654
|
missionId: row.missionId || void 0,
|
|
32648
32655
|
sliceId: row.sliceId || void 0,
|
|
32649
32656
|
assignedAgentId: row.assignedAgentId || void 0,
|
|
32657
|
+
pausedByAgentId: row.pausedByAgentId || void 0,
|
|
32650
32658
|
assigneeUserId: row.assigneeUserId || void 0,
|
|
32651
32659
|
nodeId: row.nodeId || void 0,
|
|
32652
32660
|
effectiveNodeId: row.effectiveNodeId || void 0,
|
|
@@ -32911,6 +32919,7 @@ ${recentText}` : void 0
|
|
|
32911
32919
|
"missionId",
|
|
32912
32920
|
"sliceId",
|
|
32913
32921
|
"assignedAgentId",
|
|
32922
|
+
"pausedByAgentId",
|
|
32914
32923
|
"assigneeUserId",
|
|
32915
32924
|
"nodeId",
|
|
32916
32925
|
"effectiveNodeId",
|
|
@@ -33023,6 +33032,7 @@ ${outcome}`;
|
|
|
33023
33032
|
"missionId",
|
|
33024
33033
|
"sliceId",
|
|
33025
33034
|
"assignedAgentId",
|
|
33035
|
+
"pausedByAgentId",
|
|
33026
33036
|
"assigneeUserId",
|
|
33027
33037
|
"nodeId",
|
|
33028
33038
|
"effectiveNodeId",
|
|
@@ -33073,9 +33083,9 @@ ${outcome}`;
|
|
|
33073
33083
|
dependencies, steps, log, attachments, steeringComments,
|
|
33074
33084
|
comments, workflowStepResults, prInfo, issueInfo,
|
|
33075
33085
|
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
|
|
33086
|
+
mergeDetails, breakIntoSubtasks, enabledWorkflowSteps, modifiedFiles, missionId, sliceId, assignedAgentId, pausedByAgentId, assigneeUserId, nodeId, effectiveNodeId, effectiveNodeSource, sourceType, sourceAgentId, sourceRunId, sourceSessionId, sourceMessageId, sourceParentTaskId, sourceMetadata, checkedOutBy, checkedOutAt
|
|
33077
33087
|
) VALUES (
|
|
33078
|
-
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
|
33088
|
+
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
|
33079
33089
|
)
|
|
33080
33090
|
ON CONFLICT(id) DO UPDATE SET
|
|
33081
33091
|
title = excluded.title,
|
|
@@ -33144,6 +33154,7 @@ ${outcome}`;
|
|
|
33144
33154
|
missionId = excluded.missionId,
|
|
33145
33155
|
sliceId = excluded.sliceId,
|
|
33146
33156
|
assignedAgentId = excluded.assignedAgentId,
|
|
33157
|
+
pausedByAgentId = excluded.pausedByAgentId,
|
|
33147
33158
|
assigneeUserId = excluded.assigneeUserId,
|
|
33148
33159
|
nodeId = excluded.nodeId,
|
|
33149
33160
|
effectiveNodeId = excluded.effectiveNodeId,
|
|
@@ -33225,6 +33236,7 @@ ${outcome}`;
|
|
|
33225
33236
|
task.missionId ?? null,
|
|
33226
33237
|
task.sliceId ?? null,
|
|
33227
33238
|
task.assignedAgentId ?? null,
|
|
33239
|
+
task.pausedByAgentId ?? null,
|
|
33228
33240
|
task.assigneeUserId ?? null,
|
|
33229
33241
|
task.nodeId ?? null,
|
|
33230
33242
|
task.effectiveNodeId ?? null,
|
|
@@ -34342,6 +34354,23 @@ ${newTask.description}
|
|
|
34342
34354
|
const matches = [...activeMatches, ...archiveMatches];
|
|
34343
34355
|
return limit >= 0 ? matches.slice(0, limit) : matches;
|
|
34344
34356
|
}
|
|
34357
|
+
async getTasksByAssignedAgent(agentId, options) {
|
|
34358
|
+
const whereClauses = ["assignedAgentId = ?"];
|
|
34359
|
+
const params = [agentId];
|
|
34360
|
+
if (options?.pausedOnly) {
|
|
34361
|
+
whereClauses.push("paused = 1");
|
|
34362
|
+
}
|
|
34363
|
+
if (options?.excludeArchived) {
|
|
34364
|
+
whereClauses.push(`"column" != 'archived'`);
|
|
34365
|
+
}
|
|
34366
|
+
const selectClause = this.getTaskSelectClause(false);
|
|
34367
|
+
const rows = this.db.prepare(`
|
|
34368
|
+
SELECT ${selectClause} FROM tasks
|
|
34369
|
+
WHERE ${whereClauses.join(" AND ")}
|
|
34370
|
+
ORDER BY createdAt ASC
|
|
34371
|
+
`).all(...params);
|
|
34372
|
+
return rows.map((row) => this.rowToTask(row));
|
|
34373
|
+
}
|
|
34345
34374
|
async selectNextTaskForAgent(agentId) {
|
|
34346
34375
|
const tasks = await this.listTasks({ slim: true });
|
|
34347
34376
|
if (tasks.length === 0) {
|
|
@@ -34579,6 +34608,11 @@ ${newTask.description}
|
|
|
34579
34608
|
} else if (updates.assignedAgentId !== void 0) {
|
|
34580
34609
|
task.assignedAgentId = updates.assignedAgentId;
|
|
34581
34610
|
}
|
|
34611
|
+
if (updates.pausedByAgentId === null) {
|
|
34612
|
+
task.pausedByAgentId = void 0;
|
|
34613
|
+
} else if (updates.pausedByAgentId !== void 0) {
|
|
34614
|
+
task.pausedByAgentId = updates.pausedByAgentId;
|
|
34615
|
+
}
|
|
34582
34616
|
if (updates.assigneeUserId === null) {
|
|
34583
34617
|
task.assigneeUserId = void 0;
|
|
34584
34618
|
} else if (updates.assigneeUserId !== void 0) {
|
|
@@ -34817,14 +34851,21 @@ ${newTask.description}
|
|
|
34817
34851
|
* Pause or unpause a task. Paused tasks are excluded from all automated
|
|
34818
34852
|
* agent and scheduler interaction. Logs the action and emits `task:updated`.
|
|
34819
34853
|
*/
|
|
34820
|
-
async pauseTask(id, paused, runContext) {
|
|
34854
|
+
async pauseTask(id, paused, runContext, agentOptions) {
|
|
34821
34855
|
return this.withTaskLock(id, async () => {
|
|
34822
34856
|
const dir2 = this.taskDir(id);
|
|
34823
34857
|
const task = await this.readTaskJson(dir2);
|
|
34824
34858
|
if (!task.log) {
|
|
34825
34859
|
task.log = [];
|
|
34826
34860
|
}
|
|
34861
|
+
const previousPausedByAgentId = task.pausedByAgentId;
|
|
34827
34862
|
task.paused = paused || void 0;
|
|
34863
|
+
if (paused && agentOptions?.pausedByAgentId) {
|
|
34864
|
+
task.pausedByAgentId = agentOptions.pausedByAgentId;
|
|
34865
|
+
}
|
|
34866
|
+
if (!paused) {
|
|
34867
|
+
task.pausedByAgentId = void 0;
|
|
34868
|
+
}
|
|
34828
34869
|
if (task.column === "in-progress" || task.column === "in-review") {
|
|
34829
34870
|
task.status = paused ? "paused" : void 0;
|
|
34830
34871
|
}
|
|
@@ -34832,7 +34873,7 @@ ${newTask.description}
|
|
|
34832
34873
|
task.updatedAt = now;
|
|
34833
34874
|
const logEntry = {
|
|
34834
34875
|
timestamp: now,
|
|
34835
|
-
action: paused ? "Task paused" : "Task unpaused"
|
|
34876
|
+
action: paused ? agentOptions?.pausedByAgentId ? `Task paused (agent ${agentOptions.pausedByAgentId} paused)` : "Task paused" : previousPausedByAgentId ? `Task unpaused (agent ${previousPausedByAgentId} resumed)` : "Task unpaused"
|
|
34836
34877
|
};
|
|
34837
34878
|
if (runContext) {
|
|
34838
34879
|
logEntry.runContext = runContext;
|
|
@@ -39983,10 +40024,17 @@ var init_docker_client = __esm({
|
|
|
39983
40024
|
}
|
|
39984
40025
|
return this.createDockerInstance(hostConfig);
|
|
39985
40026
|
}
|
|
39986
|
-
async getContainerInfo(containerId) {
|
|
40027
|
+
async getContainerInfo(containerId, hostConfig) {
|
|
39987
40028
|
try {
|
|
39988
|
-
const docker = await this.
|
|
40029
|
+
const docker = await this.getDockerInstance(hostConfig);
|
|
39989
40030
|
const inspect = await docker.getContainer(containerId).inspect();
|
|
40031
|
+
const ports = Object.entries(inspect.NetworkSettings?.Ports ?? {}).reduce((acc, [key, value]) => {
|
|
40032
|
+
const binding = Array.isArray(value) && value.length > 0 ? value[0] : void 0;
|
|
40033
|
+
if (binding?.HostPort) {
|
|
40034
|
+
acc[key] = binding.HostPort;
|
|
40035
|
+
}
|
|
40036
|
+
return acc;
|
|
40037
|
+
}, {});
|
|
39990
40038
|
return {
|
|
39991
40039
|
id: inspect.Id,
|
|
39992
40040
|
name: (inspect.Name ?? "").replace(/^\//, ""),
|
|
@@ -39998,8 +40046,12 @@ var init_docker_client = __esm({
|
|
|
39998
40046
|
paused: Boolean(inspect.State?.Paused),
|
|
39999
40047
|
restarting: Boolean(inspect.State?.Restarting),
|
|
40000
40048
|
dead: Boolean(inspect.State?.Dead),
|
|
40001
|
-
error: inspect.State?.Error || void 0
|
|
40002
|
-
|
|
40049
|
+
error: inspect.State?.Error || void 0,
|
|
40050
|
+
exitCode: typeof inspect.State?.ExitCode === "number" ? inspect.State.ExitCode : void 0,
|
|
40051
|
+
startedAt: inspect.State?.StartedAt || void 0,
|
|
40052
|
+
finishedAt: inspect.State?.FinishedAt || void 0
|
|
40053
|
+
},
|
|
40054
|
+
ports
|
|
40003
40055
|
};
|
|
40004
40056
|
} catch (error) {
|
|
40005
40057
|
const message = toErrorMessage(error);
|
|
@@ -40009,6 +40061,18 @@ var init_docker_client = __esm({
|
|
|
40009
40061
|
throw error;
|
|
40010
40062
|
}
|
|
40011
40063
|
}
|
|
40064
|
+
async getContainerLogs(containerId, hostConfig, options) {
|
|
40065
|
+
const docker = await this.getDockerInstance(hostConfig);
|
|
40066
|
+
const stream = await docker.getContainer(containerId).logs({
|
|
40067
|
+
stdout: true,
|
|
40068
|
+
stderr: true,
|
|
40069
|
+
tail: options?.tail ?? 100
|
|
40070
|
+
});
|
|
40071
|
+
if (Buffer.isBuffer(stream)) {
|
|
40072
|
+
return stream.toString("utf8");
|
|
40073
|
+
}
|
|
40074
|
+
return String(stream ?? "");
|
|
40075
|
+
}
|
|
40012
40076
|
async getInstance() {
|
|
40013
40077
|
if (!this.dockerInstance) {
|
|
40014
40078
|
this.dockerInstance = await this.createDockerInstance(this.defaultHostConfig);
|
|
@@ -41509,17 +41573,17 @@ function patchForStatus(status, patch) {
|
|
|
41509
41573
|
}
|
|
41510
41574
|
return patch;
|
|
41511
41575
|
}
|
|
41512
|
-
async function executeExistingRun(store,
|
|
41513
|
-
const started = store.updateRun(
|
|
41576
|
+
async function executeExistingRun(store, run2, options) {
|
|
41577
|
+
const started = store.updateRun(run2.id, {
|
|
41514
41578
|
status: "running",
|
|
41515
|
-
startedAt:
|
|
41579
|
+
startedAt: run2.startedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
41516
41580
|
lifecycle: {
|
|
41517
|
-
...
|
|
41581
|
+
...run2.lifecycle,
|
|
41518
41582
|
maxAttempts: options.maxAttempts,
|
|
41519
|
-
attempt:
|
|
41583
|
+
attempt: run2.lifecycle.attempt ?? 1
|
|
41520
41584
|
}
|
|
41521
41585
|
});
|
|
41522
|
-
let active = started ??
|
|
41586
|
+
let active = started ?? run2;
|
|
41523
41587
|
store.appendRunEvent(active.id, { type: "status_changed", status: "running", message: "Run started" });
|
|
41524
41588
|
for (let attempt = active.lifecycle.attempt ?? 1; attempt <= options.maxAttempts; attempt += 1) {
|
|
41525
41589
|
const { signal, clear } = composeSignal(options.timeoutMs, options.signal);
|
|
@@ -41615,9 +41679,9 @@ async function executeExistingRun(store, run, options) {
|
|
|
41615
41679
|
async function executeInsightRunLifecycle(options) {
|
|
41616
41680
|
const maxAttempts = Math.max(1, options.maxAttempts ?? 2);
|
|
41617
41681
|
const retryDelayMs = Math.max(0, options.retryDelayMs ?? 250);
|
|
41618
|
-
let
|
|
41682
|
+
let run2;
|
|
41619
41683
|
try {
|
|
41620
|
-
|
|
41684
|
+
run2 = options.store.createRunOrThrowConflict(options.projectId, {
|
|
41621
41685
|
...options.input,
|
|
41622
41686
|
lifecycle: {
|
|
41623
41687
|
...options.input.lifecycle,
|
|
@@ -41632,12 +41696,12 @@ async function executeInsightRunLifecycle(options) {
|
|
|
41632
41696
|
}
|
|
41633
41697
|
throw error;
|
|
41634
41698
|
}
|
|
41635
|
-
options.store.appendRunEvent(
|
|
41699
|
+
options.store.appendRunEvent(run2.id, {
|
|
41636
41700
|
type: "status_changed",
|
|
41637
41701
|
status: "pending",
|
|
41638
41702
|
message: "Run created"
|
|
41639
41703
|
});
|
|
41640
|
-
return executeExistingRun(options.store,
|
|
41704
|
+
return executeExistingRun(options.store, run2, {
|
|
41641
41705
|
...options,
|
|
41642
41706
|
maxAttempts,
|
|
41643
41707
|
retryDelayMs
|
|
@@ -41654,7 +41718,7 @@ async function retryInsightRunLifecycle(options) {
|
|
|
41654
41718
|
if (!original.lifecycle.retryable || original.lifecycle.failureClass !== "retryable_transient") {
|
|
41655
41719
|
throw new InsightLifecycleError(`Run ${original.id} is non-retryable`, "not_retryable");
|
|
41656
41720
|
}
|
|
41657
|
-
const
|
|
41721
|
+
const run2 = await executeInsightRunLifecycle({
|
|
41658
41722
|
...options,
|
|
41659
41723
|
projectId: original.projectId,
|
|
41660
41724
|
input: {
|
|
@@ -41667,7 +41731,7 @@ async function retryInsightRunLifecycle(options) {
|
|
|
41667
41731
|
}
|
|
41668
41732
|
}
|
|
41669
41733
|
});
|
|
41670
|
-
return { run, retryOf: original };
|
|
41734
|
+
return { run: run2, retryOf: original };
|
|
41671
41735
|
}
|
|
41672
41736
|
var init_insight_run_executor = __esm({
|
|
41673
41737
|
"../core/src/insight-run-executor.ts"() {
|
|
@@ -54701,7 +54765,7 @@ var init_research_orchestrator = __esm({
|
|
|
54701
54765
|
this.semaphore = new AgentSemaphore(options.maxConcurrentRuns ?? 3);
|
|
54702
54766
|
}
|
|
54703
54767
|
createRun(config) {
|
|
54704
|
-
const
|
|
54768
|
+
const run2 = this.store.createRun({
|
|
54705
54769
|
query: "",
|
|
54706
54770
|
providerConfig: config,
|
|
54707
54771
|
metadata: {
|
|
@@ -54712,12 +54776,12 @@ var init_research_orchestrator = __esm({
|
|
|
54712
54776
|
}
|
|
54713
54777
|
}
|
|
54714
54778
|
});
|
|
54715
|
-
return
|
|
54779
|
+
return run2.id;
|
|
54716
54780
|
}
|
|
54717
54781
|
async startRun(runId, query, options = {}) {
|
|
54718
|
-
const
|
|
54719
|
-
if (!
|
|
54720
|
-
const config =
|
|
54782
|
+
const run2 = this.store.getRun(runId);
|
|
54783
|
+
if (!run2) throw new Error(`Research run not found: ${runId}`);
|
|
54784
|
+
const config = run2.providerConfig ?? {};
|
|
54721
54785
|
const controller = new AbortController();
|
|
54722
54786
|
if (options.abortSignal) {
|
|
54723
54787
|
options.abortSignal.addEventListener("abort", () => controller.abort(options.abortSignal?.reason), { once: true });
|
|
@@ -54745,8 +54809,8 @@ var init_research_orchestrator = __esm({
|
|
|
54745
54809
|
}
|
|
54746
54810
|
cancelRun(runId) {
|
|
54747
54811
|
const active = this.activeRuns.get(runId);
|
|
54748
|
-
const
|
|
54749
|
-
if (!
|
|
54812
|
+
const run2 = this.store.getRun(runId);
|
|
54813
|
+
if (!run2) return false;
|
|
54750
54814
|
this.store.requestCancellation(runId);
|
|
54751
54815
|
if (!active) {
|
|
54752
54816
|
this.store.updateStatus(runId, "cancelled", { error: "Cancelled by user" });
|
|
@@ -54768,39 +54832,39 @@ var init_research_orchestrator = __esm({
|
|
|
54768
54832
|
return true;
|
|
54769
54833
|
}
|
|
54770
54834
|
retryRun(runId) {
|
|
54771
|
-
const
|
|
54772
|
-
if (!
|
|
54773
|
-
if (
|
|
54774
|
-
throw new Error(`Research run ${runId} is not retryable (status=${
|
|
54835
|
+
const run2 = this.store.getRun(runId);
|
|
54836
|
+
if (!run2) throw new Error(`Research run not found: ${runId}`);
|
|
54837
|
+
if (run2.status !== "failed" && run2.status !== "cancelled") {
|
|
54838
|
+
throw new Error(`Research run ${runId} is not retryable (status=${run2.status})`);
|
|
54775
54839
|
}
|
|
54776
54840
|
const next = this.store.createRun({
|
|
54777
|
-
query:
|
|
54778
|
-
topic:
|
|
54779
|
-
providerConfig:
|
|
54780
|
-
tags: [...
|
|
54841
|
+
query: run2.query,
|
|
54842
|
+
topic: run2.topic,
|
|
54843
|
+
providerConfig: run2.providerConfig,
|
|
54844
|
+
tags: [...run2.tags],
|
|
54781
54845
|
metadata: {
|
|
54782
|
-
...
|
|
54783
|
-
retryOfRunId:
|
|
54846
|
+
...run2.metadata ?? {},
|
|
54847
|
+
retryOfRunId: run2.id
|
|
54784
54848
|
}
|
|
54785
54849
|
});
|
|
54786
54850
|
this.store.addEvent(next.id, {
|
|
54787
54851
|
type: "info",
|
|
54788
|
-
message: `Retry run created from ${
|
|
54789
|
-
metadata: { retryOfRunId:
|
|
54852
|
+
message: `Retry run created from ${run2.id}`,
|
|
54853
|
+
metadata: { retryOfRunId: run2.id }
|
|
54790
54854
|
});
|
|
54791
54855
|
return next.id;
|
|
54792
54856
|
}
|
|
54793
54857
|
getRunStatus(runId) {
|
|
54794
|
-
const
|
|
54795
|
-
if (!
|
|
54858
|
+
const run2 = this.store.getRun(runId);
|
|
54859
|
+
if (!run2) throw new Error(`Research run not found: ${runId}`);
|
|
54796
54860
|
const active = this.activeRuns.get(runId);
|
|
54797
|
-
const metadata =
|
|
54798
|
-
const phase = active?.phase ?? metadata.phase ?? this.statusToPhase(
|
|
54861
|
+
const metadata = run2.metadata?.orchestration ?? {};
|
|
54862
|
+
const phase = active?.phase ?? metadata.phase ?? this.statusToPhase(run2.status);
|
|
54799
54863
|
const stepIndex = active?.stepIndex ?? Number(metadata.stepIndex ?? 0);
|
|
54800
54864
|
const totalSteps = active?.totalSteps ?? Number(metadata.totalSteps ?? 0);
|
|
54801
54865
|
return {
|
|
54802
54866
|
runId,
|
|
54803
|
-
status:
|
|
54867
|
+
status: run2.status,
|
|
54804
54868
|
phase,
|
|
54805
54869
|
stepIndex,
|
|
54806
54870
|
totalSteps,
|
|
@@ -54974,8 +55038,8 @@ var init_research_orchestrator = __esm({
|
|
|
54974
55038
|
});
|
|
54975
55039
|
}
|
|
54976
55040
|
onCancelled(runId) {
|
|
54977
|
-
const
|
|
54978
|
-
if (!
|
|
55041
|
+
const run2 = this.store.getRun(runId);
|
|
55042
|
+
if (!run2 || run2.status === "cancelled") return;
|
|
54979
55043
|
const cancellation = this.cancellation.get(runId);
|
|
54980
55044
|
this.store.addEvent(runId, {
|
|
54981
55045
|
type: "warning",
|
|
@@ -55097,9 +55161,9 @@ var init_research_orchestrator = __esm({
|
|
|
55097
55161
|
}
|
|
55098
55162
|
}
|
|
55099
55163
|
canWriteRunData(runId) {
|
|
55100
|
-
const
|
|
55101
|
-
if (!
|
|
55102
|
-
return !["cancelled", "completed", "failed", "timed_out", "retry_exhausted"].includes(
|
|
55164
|
+
const run2 = this.store.getRun(runId);
|
|
55165
|
+
if (!run2) return false;
|
|
55166
|
+
return !["cancelled", "completed", "failed", "timed_out", "retry_exhausted"].includes(run2.status);
|
|
55103
55167
|
}
|
|
55104
55168
|
};
|
|
55105
55169
|
}
|
|
@@ -55433,6 +55497,9 @@ function normalizePattern(pattern) {
|
|
|
55433
55497
|
function isExclusionPattern(pattern) {
|
|
55434
55498
|
return pattern.startsWith("-");
|
|
55435
55499
|
}
|
|
55500
|
+
function bareSkillName(name) {
|
|
55501
|
+
return name.replace(/\/SKILL\.md$/i, "");
|
|
55502
|
+
}
|
|
55436
55503
|
function resolveSessionSkills(context) {
|
|
55437
55504
|
const { requestedSkillNames } = context;
|
|
55438
55505
|
const projectRootDir = resolveProjectRoot(context.projectRootDir);
|
|
@@ -55526,25 +55593,40 @@ function createSkillsOverrideFromSelection(selection, options = {}) {
|
|
|
55526
55593
|
const hasRequestedNames = Boolean(requestedSkillNames && requestedSkillNames.length > 0);
|
|
55527
55594
|
const hasExcluded = excludedSkillPaths.size > 0;
|
|
55528
55595
|
let filteredSkills;
|
|
55596
|
+
const skillNameMatches = (skill, pattern) => bareSkillName(skill.name).toLowerCase() === bareSkillName(pattern).toLowerCase() || skill.filePath === pattern;
|
|
55597
|
+
const isExcluded = (skill) => {
|
|
55598
|
+
for (const ep of excludedSkillPaths) {
|
|
55599
|
+
if (skillNameMatches(skill, ep)) return true;
|
|
55600
|
+
}
|
|
55601
|
+
return false;
|
|
55602
|
+
};
|
|
55603
|
+
const isAllowed = (skill) => {
|
|
55604
|
+
for (const ap of allowedSkillPaths) {
|
|
55605
|
+
if (skillNameMatches(skill, ap)) return true;
|
|
55606
|
+
}
|
|
55607
|
+
return false;
|
|
55608
|
+
};
|
|
55529
55609
|
if (hasRequestedNames) {
|
|
55530
|
-
const
|
|
55610
|
+
const requestedBareNamesLower = new Set(requestedSkillNames.map((n) => bareSkillName(n).toLowerCase()));
|
|
55531
55611
|
filteredSkills = base.skills.filter(
|
|
55532
|
-
(skill) =>
|
|
55612
|
+
(skill) => requestedBareNamesLower.has(bareSkillName(skill.name).toLowerCase()) && !isExcluded(skill)
|
|
55533
55613
|
);
|
|
55534
55614
|
} else if (hasPatterns) {
|
|
55535
55615
|
filteredSkills = base.skills.filter(
|
|
55536
|
-
(skill) =>
|
|
55616
|
+
(skill) => isAllowed(skill) && !isExcluded(skill)
|
|
55537
55617
|
);
|
|
55538
55618
|
} else if (hasExcluded) {
|
|
55539
|
-
filteredSkills = base.skills.filter((skill) => !
|
|
55619
|
+
filteredSkills = base.skills.filter((skill) => !isExcluded(skill));
|
|
55540
55620
|
} else {
|
|
55541
55621
|
filteredSkills = base.skills;
|
|
55542
55622
|
}
|
|
55543
55623
|
const newDiagnostics = [];
|
|
55544
55624
|
const purpose = sessionPurpose ? ` [${sessionPurpose}]` : "";
|
|
55545
|
-
const
|
|
55625
|
+
const discoveredBareNames = new Set(base.skills.map((s) => bareSkillName(s.name).toLowerCase()));
|
|
55626
|
+
const discoveredFilePaths = new Set(base.skills.map((s) => s.filePath));
|
|
55627
|
+
const hasDiscoveredMatch = (pattern) => discoveredBareNames.has(bareSkillName(pattern).toLowerCase()) || discoveredFilePaths.has(pattern);
|
|
55546
55628
|
for (const excludedPath of excludedSkillPaths) {
|
|
55547
|
-
if (
|
|
55629
|
+
if (hasDiscoveredMatch(excludedPath)) {
|
|
55548
55630
|
newDiagnostics.push({
|
|
55549
55631
|
type: "warning",
|
|
55550
55632
|
message: `Skill at '${excludedPath}' exists but is disabled by project execution settings${purpose}`,
|
|
@@ -55553,7 +55635,7 @@ function createSkillsOverrideFromSelection(selection, options = {}) {
|
|
|
55553
55635
|
}
|
|
55554
55636
|
}
|
|
55555
55637
|
for (const allowedPath of allowedSkillPaths) {
|
|
55556
|
-
if (!
|
|
55638
|
+
if (!hasDiscoveredMatch(allowedPath)) {
|
|
55557
55639
|
newDiagnostics.push({
|
|
55558
55640
|
type: "warning",
|
|
55559
55641
|
message: `Configured skill pattern '${allowedPath}' not found in discovered skills${purpose}`,
|
|
@@ -55562,9 +55644,9 @@ function createSkillsOverrideFromSelection(selection, options = {}) {
|
|
|
55562
55644
|
}
|
|
55563
55645
|
}
|
|
55564
55646
|
if (requestedSkillNames) {
|
|
55565
|
-
const
|
|
55647
|
+
const discoveredBareNamesLower = new Set(base.skills.map((s) => bareSkillName(s.name).toLowerCase()));
|
|
55566
55648
|
for (const requestedName of requestedSkillNames) {
|
|
55567
|
-
if (!
|
|
55649
|
+
if (!discoveredBareNamesLower.has(bareSkillName(requestedName).toLowerCase()) && !isBuiltInFallbackRequest(requestedName)) {
|
|
55568
55650
|
const purpose2 = sessionPurpose ? ` [${sessionPurpose}]` : "";
|
|
55569
55651
|
newDiagnostics.push({
|
|
55570
55652
|
type: "warning",
|
|
@@ -55918,6 +56000,11 @@ async function promptSessionAndCheck(session, prompt, options) {
|
|
|
55918
56000
|
piLog.warn(`pi state error \u2014 failed to inspect transcript: ${inspectErr instanceof Error ? inspectErr.message : String(inspectErr)}`);
|
|
55919
56001
|
}
|
|
55920
56002
|
}
|
|
56003
|
+
if (/Provider finish_reason:\s*repeat\b/i.test(stateError)) {
|
|
56004
|
+
piLog.warn(`pi state error \u2014 treating provider finish_reason=repeat as soft stop: ${stateError}`);
|
|
56005
|
+
clearSessionStateError(session);
|
|
56006
|
+
return;
|
|
56007
|
+
}
|
|
55921
56008
|
throw new Error(stateError);
|
|
55922
56009
|
}
|
|
55923
56010
|
}
|
|
@@ -58493,18 +58580,18 @@ function createSendMessageTool(messageStore, fromAgentId) {
|
|
|
58493
58580
|
}
|
|
58494
58581
|
};
|
|
58495
58582
|
}
|
|
58496
|
-
function formatResearchRunDetails(
|
|
58497
|
-
const findings =
|
|
58498
|
-
const citations =
|
|
58583
|
+
function formatResearchRunDetails(run2) {
|
|
58584
|
+
const findings = run2.results?.findings ?? [];
|
|
58585
|
+
const citations = run2.results?.citations ?? [];
|
|
58499
58586
|
return {
|
|
58500
|
-
runId:
|
|
58501
|
-
status:
|
|
58502
|
-
query:
|
|
58503
|
-
summary:
|
|
58587
|
+
runId: run2.id,
|
|
58588
|
+
status: run2.status,
|
|
58589
|
+
query: run2.query,
|
|
58590
|
+
summary: run2.results?.summary ?? null,
|
|
58504
58591
|
findings,
|
|
58505
58592
|
citations,
|
|
58506
|
-
sourceCount:
|
|
58507
|
-
error:
|
|
58593
|
+
sourceCount: run2.sources.length,
|
|
58594
|
+
error: run2.error ?? null,
|
|
58508
58595
|
setup: null
|
|
58509
58596
|
};
|
|
58510
58597
|
}
|
|
@@ -58629,10 +58716,10 @@ function createResearchTools(options) {
|
|
|
58629
58716
|
status: params.status,
|
|
58630
58717
|
limit
|
|
58631
58718
|
});
|
|
58632
|
-
const text = runs.length ? runs.map((
|
|
58719
|
+
const text = runs.length ? runs.map((run2) => `- ${run2.id} [${run2.status}] ${run2.query}`).join("\n") : "No research runs found.";
|
|
58633
58720
|
return {
|
|
58634
58721
|
content: [{ type: "text", text }],
|
|
58635
|
-
details: { runs: runs.map((
|
|
58722
|
+
details: { runs: runs.map((run2) => formatResearchRunDetails(run2)) }
|
|
58636
58723
|
};
|
|
58637
58724
|
}
|
|
58638
58725
|
};
|
|
@@ -58642,16 +58729,16 @@ function createResearchTools(options) {
|
|
|
58642
58729
|
description: "Get one research run with structured findings and citations.",
|
|
58643
58730
|
parameters: researchGetParams,
|
|
58644
58731
|
execute: async (_id, params) => {
|
|
58645
|
-
const
|
|
58646
|
-
if (!
|
|
58732
|
+
const run2 = options.store.getResearchStore().getRun(params.id);
|
|
58733
|
+
if (!run2) {
|
|
58647
58734
|
return {
|
|
58648
58735
|
content: [{ type: "text", text: `Research run ${params.id} not found.` }],
|
|
58649
58736
|
details: { runId: params.id, status: "missing", summary: null, findings: [], citations: [], error: "not found", setup: null }
|
|
58650
58737
|
};
|
|
58651
58738
|
}
|
|
58652
|
-
const details = formatResearchRunDetails(
|
|
58739
|
+
const details = formatResearchRunDetails(run2);
|
|
58653
58740
|
return {
|
|
58654
|
-
content: [{ type: "text", text: `Research run ${
|
|
58741
|
+
content: [{ type: "text", text: `Research run ${run2.id} is ${run2.status}.` }],
|
|
58655
58742
|
details
|
|
58656
58743
|
};
|
|
58657
58744
|
}
|
|
@@ -58667,8 +58754,8 @@ function createResearchTools(options) {
|
|
|
58667
58754
|
return researchUnavailable("provider-unavailable", "Research orchestrator is unavailable because research providers are not configured.");
|
|
58668
58755
|
}
|
|
58669
58756
|
const cancelled = orchestrator.cancelRun(params.id);
|
|
58670
|
-
const
|
|
58671
|
-
if (!
|
|
58757
|
+
const run2 = options.store.getResearchStore().getRun(params.id);
|
|
58758
|
+
if (!run2) {
|
|
58672
58759
|
return {
|
|
58673
58760
|
content: [{ type: "text", text: `Research run ${params.id} not found.` }],
|
|
58674
58761
|
details: { runId: params.id, status: "missing", summary: null, findings: [], citations: [], error: "not found", setup: null }
|
|
@@ -58676,7 +58763,7 @@ function createResearchTools(options) {
|
|
|
58676
58763
|
}
|
|
58677
58764
|
return {
|
|
58678
58765
|
content: [{ type: "text", text: cancelled ? `Cancellation requested for ${params.id}.` : `Run ${params.id} is not active.` }],
|
|
58679
|
-
details: formatResearchRunDetails(
|
|
58766
|
+
details: formatResearchRunDetails(run2)
|
|
58680
58767
|
};
|
|
58681
58768
|
}
|
|
58682
58769
|
};
|
|
@@ -59099,9 +59186,18 @@ function normalizeAgentSkills(metadataSkills) {
|
|
|
59099
59186
|
name = namedEntry.trim();
|
|
59100
59187
|
}
|
|
59101
59188
|
}
|
|
59102
|
-
if (name && name.length > 0
|
|
59103
|
-
|
|
59104
|
-
|
|
59189
|
+
if (name && name.length > 0) {
|
|
59190
|
+
if (name.includes("::")) {
|
|
59191
|
+
const idPath = name.split("::").pop();
|
|
59192
|
+
const parts = idPath.replace(/\\/g, "/").split("/").filter(Boolean);
|
|
59193
|
+
if (parts.length >= 2) {
|
|
59194
|
+
name = parts.slice(-2).join("/");
|
|
59195
|
+
}
|
|
59196
|
+
}
|
|
59197
|
+
if (!seen.has(name)) {
|
|
59198
|
+
seen.add(name);
|
|
59199
|
+
result.push(name);
|
|
59200
|
+
}
|
|
59105
59201
|
}
|
|
59106
59202
|
}
|
|
59107
59203
|
return result;
|
|
@@ -69724,20 +69820,26 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
69724
69820
|
if (from !== "in-review" && from !== "done") {
|
|
69725
69821
|
return task;
|
|
69726
69822
|
}
|
|
69727
|
-
const hasMergeEvidence = Boolean(task.mergeDetails) || (task.mergeRetries ?? 0) > 0 || (task.verificationFailureCount ?? 0) > 0 || task.status === "merging" || task.status === "merging-pr";
|
|
69823
|
+
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
69824
|
if (!hasMergeEvidence) {
|
|
69729
69825
|
return task;
|
|
69730
69826
|
}
|
|
69731
69827
|
return this.cleanupMergeStateForReverification(
|
|
69732
69828
|
task,
|
|
69733
|
-
`Task returned to in-progress from ${from} column \u2014 resetting verification steps and merge state for re-verification
|
|
69829
|
+
`Task returned to in-progress from ${from} column \u2014 resetting verification steps and merge state for re-verification`,
|
|
69830
|
+
{
|
|
69831
|
+
// Keep deterministic merge-verification bounce budget across remediation
|
|
69832
|
+
// cycles. Status may be cleared by intermediate paths, so the counter is
|
|
69833
|
+
// the canonical signal once a bounce has started.
|
|
69834
|
+
preserveVerificationFailureCount: (task.verificationFailureCount ?? 0) > 0
|
|
69835
|
+
}
|
|
69734
69836
|
);
|
|
69735
69837
|
}
|
|
69736
|
-
async cleanupMergeStateForReverification(task, logMessage) {
|
|
69838
|
+
async cleanupMergeStateForReverification(task, logMessage, options) {
|
|
69737
69839
|
await this.store.updateTask(task.id, {
|
|
69738
69840
|
mergeDetails: null,
|
|
69739
69841
|
mergeRetries: 0,
|
|
69740
|
-
verificationFailureCount: 0,
|
|
69842
|
+
verificationFailureCount: options?.preserveVerificationFailureCount ? task.verificationFailureCount ?? 0 : 0,
|
|
69741
69843
|
workflowStepResults: []
|
|
69742
69844
|
});
|
|
69743
69845
|
const refreshedTask = await this.store.getTask(task.id);
|
|
@@ -70332,7 +70434,7 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
70332
70434
|
};
|
|
70333
70435
|
const audit = createRunAuditor(this.store, engineRunContext);
|
|
70334
70436
|
const activeColumns = /* @__PURE__ */ new Set(["in-progress", "in-review", "done"]);
|
|
70335
|
-
const activeMergeStatuses = /* @__PURE__ */ new Set(["merging", "merging-pr"]);
|
|
70437
|
+
const activeMergeStatuses = /* @__PURE__ */ new Set(["merging", "merging-pr", "merging-fix"]);
|
|
70336
70438
|
const isActiveTask = activeColumns.has(task.column) || activeMergeStatuses.has(task.status ?? "");
|
|
70337
70439
|
if (!isActiveTask) {
|
|
70338
70440
|
const tasksDir = join36(this.store.getFusionDir(), "tasks");
|
|
@@ -70671,7 +70773,9 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
70671
70773
|
`${failedType} command \`${failedCommand}\` failed (exit ${failedResult.exitCode}):
|
|
70672
70774
|
${summary}`,
|
|
70673
70775
|
`Verification (${failedType})`,
|
|
70674
|
-
`Deterministic verification failed (${failedType})
|
|
70776
|
+
`Deterministic verification failed (${failedType})`,
|
|
70777
|
+
true,
|
|
70778
|
+
true
|
|
70675
70779
|
);
|
|
70676
70780
|
return;
|
|
70677
70781
|
}
|
|
@@ -70717,7 +70821,9 @@ ${summary}`,
|
|
|
70717
70821
|
`${failedType} command \`${failedCommand}\` failed (exit ${failedResult.exitCode}) after ${maxFixRetries} fix attempts:
|
|
70718
70822
|
${summary}`,
|
|
70719
70823
|
`Verification (${failedType})`,
|
|
70720
|
-
`Deterministic verification failed after ${maxFixRetries} fix attempts
|
|
70824
|
+
`Deterministic verification failed after ${maxFixRetries} fix attempts`,
|
|
70825
|
+
true,
|
|
70826
|
+
true
|
|
70721
70827
|
);
|
|
70722
70828
|
return;
|
|
70723
70829
|
}
|
|
@@ -72406,7 +72512,7 @@ ${failureContext.output.slice(0, VERIFICATION_LOG_MAX_CHARS)}
|
|
|
72406
72512
|
* Injects failure feedback into PROMPT.md, resets steps, clears session,
|
|
72407
72513
|
* and schedules a move to todo → in-progress after the executing guard clears.
|
|
72408
72514
|
*/
|
|
72409
|
-
async sendTaskBackForFix(task, worktreePath, failureFeedback, stepName, reason, preserveResumeState = true) {
|
|
72515
|
+
async sendTaskBackForFix(task, worktreePath, failureFeedback, stepName, reason, preserveResumeState = true, mergeVerificationFailure = false) {
|
|
72410
72516
|
const taskId = task.id;
|
|
72411
72517
|
this.clearCompletedTaskWatchdog(taskId);
|
|
72412
72518
|
await this.store.addTaskComment(
|
|
@@ -72425,7 +72531,7 @@ Please fix the issues so the verification can pass on the next attempt.`,
|
|
|
72425
72531
|
const updatedTask = await this.store.getTask(taskId);
|
|
72426
72532
|
await this.reopenLastStepForRevision(taskId, updatedTask);
|
|
72427
72533
|
await this.store.updateTask(taskId, {
|
|
72428
|
-
status: null,
|
|
72534
|
+
status: mergeVerificationFailure ? "merging-fix" : null,
|
|
72429
72535
|
error: null,
|
|
72430
72536
|
sessionFile: null,
|
|
72431
72537
|
workflowStepRetries: 0
|
|
@@ -76001,17 +76107,17 @@ Assertions: ${assertions.map((a) => a.title).join(", ")}`,
|
|
|
76001
76107
|
validationTaskId = validationTask.id;
|
|
76002
76108
|
await this.taskStore.updateTask(validationTaskId, { status: "mission-validation" });
|
|
76003
76109
|
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,
|
|
76110
|
+
const run2 = this.missionStore.startValidatorRun(feature.id, "task_completion", validationTaskId);
|
|
76111
|
+
loopLog.log(`Started validator run ${run2.id} for feature ${feature.id}`);
|
|
76112
|
+
const result = await this.runValidation(feature, assertions, run2);
|
|
76007
76113
|
if (result.status === "pass") {
|
|
76008
|
-
await this.handleValidationPass(feature.id,
|
|
76114
|
+
await this.handleValidationPass(feature.id, run2.id, result.summary, validationTaskId);
|
|
76009
76115
|
} else if (result.status === "fail") {
|
|
76010
|
-
await this.handleValidationFail(feature.id,
|
|
76116
|
+
await this.handleValidationFail(feature.id, run2.id, result, validationTaskId);
|
|
76011
76117
|
} else if (result.status === "blocked") {
|
|
76012
|
-
await this.handleValidationBlocked(feature.id,
|
|
76118
|
+
await this.handleValidationBlocked(feature.id, run2.id, result.blockedReason, validationTaskId);
|
|
76013
76119
|
} else if (result.status === "error") {
|
|
76014
|
-
await this.handleValidationError(feature.id,
|
|
76120
|
+
await this.handleValidationError(feature.id, run2.id, result.summary, validationTaskId);
|
|
76015
76121
|
}
|
|
76016
76122
|
} finally {
|
|
76017
76123
|
this.activeValidations.delete(feature.id);
|
|
@@ -76746,7 +76852,7 @@ Rules:
|
|
|
76746
76852
|
const tasksFailed = outcomes.filter((outcome) => outcome.outcome !== "completed").length;
|
|
76747
76853
|
const durations = outcomes.map((outcome) => outcome.durationMs).filter((duration) => typeof duration === "number" && Number.isFinite(duration));
|
|
76748
76854
|
const avgDurationMs = durations.length > 0 ? Math.round(durations.reduce((sum, duration) => sum + duration, 0) / durations.length) : performanceSummary?.avgDurationMs ?? 0;
|
|
76749
|
-
const runErrors = recentRuns.map((
|
|
76855
|
+
const runErrors = recentRuns.map((run2) => this.extractRunError(run2)).filter((value) => Boolean(value));
|
|
76750
76856
|
const mergedErrors = [
|
|
76751
76857
|
...performanceSummary?.commonErrors ?? [],
|
|
76752
76858
|
...outcomes.filter((outcome) => outcome.outcome !== "completed").map((outcome) => `${outcome.outcome}: ${outcome.taskId}`),
|
|
@@ -76760,11 +76866,11 @@ Rules:
|
|
|
76760
76866
|
commonErrors
|
|
76761
76867
|
};
|
|
76762
76868
|
}
|
|
76763
|
-
extractRunError(
|
|
76764
|
-
if (typeof
|
|
76765
|
-
return
|
|
76869
|
+
extractRunError(run2) {
|
|
76870
|
+
if (typeof run2.stderrExcerpt === "string" && run2.stderrExcerpt.trim()) {
|
|
76871
|
+
return run2.stderrExcerpt.trim().split("\n")[0] ?? null;
|
|
76766
76872
|
}
|
|
76767
|
-
const resultError =
|
|
76873
|
+
const resultError = run2.resultJson && typeof run2.resultJson.error === "string" ? run2.resultJson.error.trim() : "";
|
|
76768
76874
|
if (resultError) {
|
|
76769
76875
|
return resultError;
|
|
76770
76876
|
}
|
|
@@ -76772,8 +76878,8 @@ Rules:
|
|
|
76772
76878
|
}
|
|
76773
76879
|
extractTaskIdsFromRuns(runs) {
|
|
76774
76880
|
const ids = /* @__PURE__ */ new Set();
|
|
76775
|
-
for (const
|
|
76776
|
-
const taskId =
|
|
76881
|
+
for (const run2 of runs) {
|
|
76882
|
+
const taskId = run2.contextSnapshot?.taskId;
|
|
76777
76883
|
if (typeof taskId === "string" && taskId.trim()) {
|
|
76778
76884
|
ids.add(taskId.trim());
|
|
76779
76885
|
}
|
|
@@ -77309,9 +77415,9 @@ not loop on the same plan across heartbeats without recording why.`;
|
|
|
77309
77415
|
const msg = activeRunCheckErr instanceof Error ? activeRunCheckErr.message : String(activeRunCheckErr);
|
|
77310
77416
|
heartbeatLog.warn(`Failed to check for existing active run for ${agentId}: ${msg} \u2014 continuing with new run`);
|
|
77311
77417
|
}
|
|
77312
|
-
const
|
|
77418
|
+
const run2 = await this.store.startHeartbeatRun(agentId);
|
|
77313
77419
|
const enrichedRun = {
|
|
77314
|
-
...
|
|
77420
|
+
...run2,
|
|
77315
77421
|
invocationSource: options?.source ?? "on_demand",
|
|
77316
77422
|
triggerDetail: options?.triggerDetail ?? "manual",
|
|
77317
77423
|
contextSnapshot: options?.contextSnapshot,
|
|
@@ -77333,14 +77439,14 @@ not loop on the same plan across heartbeats without recording why.`;
|
|
|
77333
77439
|
* @param result - Execution results
|
|
77334
77440
|
*/
|
|
77335
77441
|
async completeRun(agentId, runId, result) {
|
|
77336
|
-
const
|
|
77337
|
-
if (!
|
|
77442
|
+
const run2 = await this.store.getRunDetail(agentId, runId);
|
|
77443
|
+
if (!run2) return;
|
|
77338
77444
|
const tracked = this.trackedAgents.get(agentId);
|
|
77339
77445
|
let completionResult = result;
|
|
77340
77446
|
const createdTasks = this.runCreatedTasks.get(agentId);
|
|
77341
77447
|
const enrichedResultJson = createdTasks?.length ? { ...completionResult.resultJson, tasksCreated: createdTasks } : completionResult.resultJson;
|
|
77342
77448
|
const completedRun = {
|
|
77343
|
-
...
|
|
77449
|
+
...run2,
|
|
77344
77450
|
endedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
77345
77451
|
status: completionResult.status,
|
|
77346
77452
|
exitCode: completionResult.exitCode,
|
|
@@ -77585,18 +77691,18 @@ not loop on the same plan across heartbeats without recording why.`;
|
|
|
77585
77691
|
...effectiveTriggeringCommentIds?.length ? { triggeringCommentIds: effectiveTriggeringCommentIds } : {},
|
|
77586
77692
|
...effectiveTriggeringCommentType ? { triggeringCommentType: effectiveTriggeringCommentType } : {}
|
|
77587
77693
|
};
|
|
77588
|
-
const
|
|
77694
|
+
const run2 = await this.startRun(agentId, {
|
|
77589
77695
|
source,
|
|
77590
77696
|
triggerDetail,
|
|
77591
77697
|
contextSnapshot: Object.keys(runContextSnapshot).length > 0 ? runContextSnapshot : void 0
|
|
77592
77698
|
});
|
|
77593
77699
|
const runContext = {
|
|
77594
|
-
runId:
|
|
77700
|
+
runId: run2.id,
|
|
77595
77701
|
agentId,
|
|
77596
77702
|
source
|
|
77597
77703
|
};
|
|
77598
77704
|
const engineRunContext = {
|
|
77599
|
-
runId:
|
|
77705
|
+
runId: run2.id,
|
|
77600
77706
|
agentId,
|
|
77601
77707
|
source,
|
|
77602
77708
|
phase: "heartbeat"
|
|
@@ -77618,21 +77724,21 @@ not loop on the same plan across heartbeats without recording why.`;
|
|
|
77618
77724
|
const budgetStatus = await this.store.getBudgetStatus(agentId);
|
|
77619
77725
|
if (budgetStatus.isOverBudget) {
|
|
77620
77726
|
heartbeatLog.log(`Agent ${agentId} budget exhausted \u2014 heartbeat skipped`);
|
|
77621
|
-
await this.completeRun(agentId,
|
|
77727
|
+
await this.completeRun(agentId, run2.id, {
|
|
77622
77728
|
status: "completed",
|
|
77623
77729
|
resultJson: { reason: "budget_exhausted", budgetStatus },
|
|
77624
77730
|
skipStateTransition: true
|
|
77625
77731
|
});
|
|
77626
|
-
return await this.store.getRunDetail(agentId,
|
|
77732
|
+
return await this.store.getRunDetail(agentId, run2.id);
|
|
77627
77733
|
}
|
|
77628
77734
|
if (budgetStatus.isOverThreshold && source === "timer") {
|
|
77629
77735
|
heartbeatLog.log(`Agent ${agentId} over budget threshold (${budgetStatus.usagePercent}%) \u2014 timer heartbeat skipped`);
|
|
77630
|
-
await this.completeRun(agentId,
|
|
77736
|
+
await this.completeRun(agentId, run2.id, {
|
|
77631
77737
|
status: "completed",
|
|
77632
77738
|
resultJson: { reason: "budget_threshold_exceeded", budgetStatus },
|
|
77633
77739
|
skipStateTransition: true
|
|
77634
77740
|
});
|
|
77635
|
-
return await this.store.getRunDetail(agentId,
|
|
77741
|
+
return await this.store.getRunDetail(agentId, run2.id);
|
|
77636
77742
|
}
|
|
77637
77743
|
} catch (budgetErr) {
|
|
77638
77744
|
heartbeatLog.warn(`Agent ${agentId} budget status check failed: ${budgetErr instanceof Error ? budgetErr.message : String(budgetErr)} \u2014 proceeding without budget check`);
|
|
@@ -77641,21 +77747,21 @@ not loop on the same plan across heartbeats without recording why.`;
|
|
|
77641
77747
|
const settings = await taskStore.getSettings();
|
|
77642
77748
|
if (settings.globalPause) {
|
|
77643
77749
|
heartbeatLog.log(`Agent ${agentId} heartbeat skipped \u2014 global pause active (source=${source})`);
|
|
77644
|
-
await this.completeRun(agentId,
|
|
77750
|
+
await this.completeRun(agentId, run2.id, {
|
|
77645
77751
|
status: "completed",
|
|
77646
77752
|
resultJson: { reason: "global_pause", source },
|
|
77647
77753
|
skipStateTransition: true
|
|
77648
77754
|
});
|
|
77649
|
-
return await this.store.getRunDetail(agentId,
|
|
77755
|
+
return await this.store.getRunDetail(agentId, run2.id);
|
|
77650
77756
|
}
|
|
77651
77757
|
if (settings.enginePaused && source === "timer") {
|
|
77652
77758
|
heartbeatLog.log(`Agent ${agentId} timer heartbeat skipped \u2014 engine paused (soft pause)`);
|
|
77653
|
-
await this.completeRun(agentId,
|
|
77759
|
+
await this.completeRun(agentId, run2.id, {
|
|
77654
77760
|
status: "completed",
|
|
77655
77761
|
resultJson: { reason: "engine_paused", source },
|
|
77656
77762
|
skipStateTransition: true
|
|
77657
77763
|
});
|
|
77658
|
-
return await this.store.getRunDetail(agentId,
|
|
77764
|
+
return await this.store.getRunDetail(agentId, run2.id);
|
|
77659
77765
|
}
|
|
77660
77766
|
} catch (pauseErr) {
|
|
77661
77767
|
heartbeatLog.warn(`Pause status check failed for ${agentId}: ${pauseErr instanceof Error ? pauseErr.message : String(pauseErr)} \u2014 proceeding`);
|
|
@@ -77663,11 +77769,11 @@ not loop on the same plan across heartbeats without recording why.`;
|
|
|
77663
77769
|
const agent = preloadedAgent ?? await this.store.getAgent(agentId);
|
|
77664
77770
|
if (!agent) {
|
|
77665
77771
|
heartbeatLog.warn(`Agent ${agentId} not found \u2014 completing run as failed`);
|
|
77666
|
-
await this.completeRun(agentId,
|
|
77772
|
+
await this.completeRun(agentId, run2.id, {
|
|
77667
77773
|
status: "failed",
|
|
77668
77774
|
stderrExcerpt: `Agent ${agentId} not found`
|
|
77669
77775
|
});
|
|
77670
|
-
return await this.store.getRunDetail(agentId,
|
|
77776
|
+
return await this.store.getRunDetail(agentId, run2.id);
|
|
77671
77777
|
}
|
|
77672
77778
|
const agentHasIdentity = hasAgentIdentity(agent);
|
|
77673
77779
|
const isAgentEphemeral = isEphemeralAgent(agent);
|
|
@@ -77696,11 +77802,11 @@ not loop on the same plan across heartbeats without recording why.`;
|
|
|
77696
77802
|
}
|
|
77697
77803
|
}
|
|
77698
77804
|
}
|
|
77699
|
-
if (taskId &&
|
|
77805
|
+
if (taskId && run2.contextSnapshot?.taskId !== taskId) {
|
|
77700
77806
|
const updatedRun = {
|
|
77701
|
-
...
|
|
77807
|
+
...run2,
|
|
77702
77808
|
contextSnapshot: {
|
|
77703
|
-
...
|
|
77809
|
+
...run2.contextSnapshot ?? {},
|
|
77704
77810
|
taskId
|
|
77705
77811
|
}
|
|
77706
77812
|
};
|
|
@@ -77710,11 +77816,11 @@ not loop on the same plan across heartbeats without recording why.`;
|
|
|
77710
77816
|
if (!taskId) {
|
|
77711
77817
|
if (!canRunNoTaskHeartbeat) {
|
|
77712
77818
|
heartbeatLog.log(`Agent ${agentId} has no task assignment \u2014 graceful exit`);
|
|
77713
|
-
await this.completeRun(agentId,
|
|
77819
|
+
await this.completeRun(agentId, run2.id, {
|
|
77714
77820
|
status: "completed",
|
|
77715
77821
|
resultJson: { reason: "no_assignment" }
|
|
77716
77822
|
});
|
|
77717
|
-
return await this.store.getRunDetail(agentId,
|
|
77823
|
+
return await this.store.getRunDetail(agentId, run2.id);
|
|
77718
77824
|
}
|
|
77719
77825
|
heartbeatLog.log(`Agent ${agentId} has no task but has identity \u2014 running no-task heartbeat`);
|
|
77720
77826
|
}
|
|
@@ -77723,12 +77829,12 @@ not loop on the same plan across heartbeats without recording why.`;
|
|
|
77723
77829
|
const validStates = ["active", "running", "idle"];
|
|
77724
77830
|
if (!validStates.includes(agent.state)) {
|
|
77725
77831
|
heartbeatLog.log(`Agent ${agentId} state is "${agent.state}" \u2014 graceful exit`);
|
|
77726
|
-
await this.completeRun(agentId,
|
|
77832
|
+
await this.completeRun(agentId, run2.id, {
|
|
77727
77833
|
status: "completed",
|
|
77728
77834
|
resultJson: { reason: "invalid_state", state: agent.state },
|
|
77729
77835
|
skipStateTransition: true
|
|
77730
77836
|
});
|
|
77731
|
-
return await this.store.getRunDetail(agentId,
|
|
77837
|
+
return await this.store.getRunDetail(agentId, run2.id);
|
|
77732
77838
|
}
|
|
77733
77839
|
}
|
|
77734
77840
|
let taskDetail;
|
|
@@ -77738,11 +77844,11 @@ not loop on the same plan across heartbeats without recording why.`;
|
|
|
77738
77844
|
taskDetail = await taskStore.getTask(resolvedTaskId2);
|
|
77739
77845
|
} catch (taskDetailErr) {
|
|
77740
77846
|
heartbeatLog.warn(`Task ${resolvedTaskId2} fetch failed: ${taskDetailErr instanceof Error ? taskDetailErr.message : String(taskDetailErr)} \u2014 graceful exit`);
|
|
77741
|
-
await this.completeRun(agentId,
|
|
77847
|
+
await this.completeRun(agentId, run2.id, {
|
|
77742
77848
|
status: "completed",
|
|
77743
77849
|
resultJson: { reason: "task_not_found", taskId: resolvedTaskId2 }
|
|
77744
77850
|
});
|
|
77745
|
-
return await this.store.getRunDetail(agentId,
|
|
77851
|
+
return await this.store.getRunDetail(agentId, run2.id);
|
|
77746
77852
|
}
|
|
77747
77853
|
if (taskDetail.column === "done" || taskDetail.column === "archived") {
|
|
77748
77854
|
if (agent.taskId === resolvedTaskId2) {
|
|
@@ -77760,21 +77866,21 @@ not loop on the same plan across heartbeats without recording why.`;
|
|
|
77760
77866
|
taskDetail = void 0;
|
|
77761
77867
|
isNoTaskRun = true;
|
|
77762
77868
|
if (!canRunNoTaskHeartbeat) {
|
|
77763
|
-
await this.completeRun(agentId,
|
|
77869
|
+
await this.completeRun(agentId, run2.id, {
|
|
77764
77870
|
status: "completed",
|
|
77765
77871
|
resultJson: { reason: "no_assignment" }
|
|
77766
77872
|
});
|
|
77767
|
-
return await this.store.getRunDetail(agentId,
|
|
77873
|
+
return await this.store.getRunDetail(agentId, run2.id);
|
|
77768
77874
|
}
|
|
77769
77875
|
} else {
|
|
77770
77876
|
heartbeatLog.log(
|
|
77771
77877
|
`Heartbeat for ${agentId} targeted terminal task ${resolvedTaskId2} (${taskDetail.column}) \u2014 graceful exit`
|
|
77772
77878
|
);
|
|
77773
|
-
await this.completeRun(agentId,
|
|
77879
|
+
await this.completeRun(agentId, run2.id, {
|
|
77774
77880
|
status: "completed",
|
|
77775
77881
|
resultJson: { reason: "terminal_task", taskId: resolvedTaskId2, column: taskDetail.column }
|
|
77776
77882
|
});
|
|
77777
|
-
return await this.store.getRunDetail(agentId,
|
|
77883
|
+
return await this.store.getRunDetail(agentId, run2.id);
|
|
77778
77884
|
}
|
|
77779
77885
|
}
|
|
77780
77886
|
if (isNoTaskRun) {
|
|
@@ -77783,17 +77889,17 @@ not loop on the same plan across heartbeats without recording why.`;
|
|
|
77783
77889
|
const liveTaskDetail = taskDetail;
|
|
77784
77890
|
if (!liveTaskDetail) {
|
|
77785
77891
|
heartbeatLog.warn(`Task ${resolvedTaskId2} lost detail after terminal-assignment handling \u2014 graceful exit`);
|
|
77786
|
-
await this.completeRun(agentId,
|
|
77892
|
+
await this.completeRun(agentId, run2.id, {
|
|
77787
77893
|
status: "completed",
|
|
77788
77894
|
resultJson: { reason: "task_not_found", taskId: resolvedTaskId2 }
|
|
77789
77895
|
});
|
|
77790
|
-
return await this.store.getRunDetail(agentId,
|
|
77896
|
+
return await this.store.getRunDetail(agentId, run2.id);
|
|
77791
77897
|
}
|
|
77792
77898
|
if (liveTaskDetail.checkedOutBy && liveTaskDetail.checkedOutBy !== agentId) {
|
|
77793
77899
|
heartbeatLog.warn(
|
|
77794
77900
|
`Agent ${agentId} does not hold checkout for ${resolvedTaskId2} (held by ${liveTaskDetail.checkedOutBy}) \u2014 graceful exit`
|
|
77795
77901
|
);
|
|
77796
|
-
await this.completeRun(agentId,
|
|
77902
|
+
await this.completeRun(agentId, run2.id, {
|
|
77797
77903
|
status: "completed",
|
|
77798
77904
|
resultJson: {
|
|
77799
77905
|
reason: "checkout_conflict",
|
|
@@ -77801,7 +77907,7 @@ not loop on the same plan across heartbeats without recording why.`;
|
|
|
77801
77907
|
checkedOutBy: liveTaskDetail.checkedOutBy
|
|
77802
77908
|
}
|
|
77803
77909
|
});
|
|
77804
|
-
return await this.store.getRunDetail(agentId,
|
|
77910
|
+
return await this.store.getRunDetail(agentId, run2.id);
|
|
77805
77911
|
}
|
|
77806
77912
|
const blockedBy = typeof liveTaskDetail.blockedBy === "string" ? liveTaskDetail.blockedBy.trim() : "";
|
|
77807
77913
|
const isBlockedTask = liveTaskDetail.status === "queued" && blockedBy.length > 0;
|
|
@@ -77820,22 +77926,22 @@ not loop on the same plan across heartbeats without recording why.`;
|
|
|
77820
77926
|
};
|
|
77821
77927
|
const previousBlockedState = await this.store.getLastBlockedState(agentId);
|
|
77822
77928
|
if (previousBlockedState && isBlockedStateDuplicate(currentBlockedState, previousBlockedState)) {
|
|
77823
|
-
await this.completeRun(agentId,
|
|
77929
|
+
await this.completeRun(agentId, run2.id, {
|
|
77824
77930
|
status: "completed",
|
|
77825
77931
|
resultJson: { reason: "blocked_duplicate", taskId: resolvedTaskId2, blockedBy }
|
|
77826
77932
|
});
|
|
77827
|
-
return await this.store.getRunDetail(agentId,
|
|
77933
|
+
return await this.store.getRunDetail(agentId, run2.id);
|
|
77828
77934
|
}
|
|
77829
77935
|
const blockedMessage = `Task is blocked by ${blockedBy}; waiting for dependency/context changes before retrying.`;
|
|
77830
77936
|
await taskStore.addComment(resolvedTaskId2, blockedMessage, "agent", void 0, runContext);
|
|
77831
77937
|
await audit.database({ type: "task:comment:add", target: resolvedTaskId2, metadata: { blockedBy } });
|
|
77832
77938
|
await this.store.setLastBlockedState(agentId, currentBlockedState);
|
|
77833
77939
|
heartbeatLog.log(`Task ${resolvedTaskId2} is blocked by ${blockedBy} \u2014 recorded blocked state`);
|
|
77834
|
-
await this.completeRun(agentId,
|
|
77940
|
+
await this.completeRun(agentId, run2.id, {
|
|
77835
77941
|
status: "completed",
|
|
77836
77942
|
resultJson: { reason: "blocked", taskId: resolvedTaskId2, blockedBy }
|
|
77837
77943
|
});
|
|
77838
|
-
return await this.store.getRunDetail(agentId,
|
|
77944
|
+
return await this.store.getRunDetail(agentId, run2.id);
|
|
77839
77945
|
}
|
|
77840
77946
|
}
|
|
77841
77947
|
}
|
|
@@ -77930,7 +78036,7 @@ not loop on the same plan across heartbeats without recording why.`;
|
|
|
77930
78036
|
heartbeatTools.push(heartbeatDoneTool);
|
|
77931
78037
|
if (isNoTaskRun) {
|
|
77932
78038
|
agentLogger = new AgentLogger({
|
|
77933
|
-
appendLog: (entry) => this.store.appendRunLog(agentId,
|
|
78039
|
+
appendLog: (entry) => this.store.appendRunLog(agentId, run2.id, entry),
|
|
77934
78040
|
agent: agent.role,
|
|
77935
78041
|
persistAgentToolOutput: memorySettings?.persistAgentToolOutput
|
|
77936
78042
|
});
|
|
@@ -77939,7 +78045,7 @@ not loop on the same plan across heartbeats without recording why.`;
|
|
|
77939
78045
|
store: taskStore,
|
|
77940
78046
|
taskId,
|
|
77941
78047
|
agent: agent.role,
|
|
77942
|
-
appendLog: (entry) => this.store.appendRunLog(agentId,
|
|
78048
|
+
appendLog: (entry) => this.store.appendRunLog(agentId, run2.id, entry),
|
|
77943
78049
|
persistAgentToolOutput: memorySettings?.persistAgentToolOutput
|
|
77944
78050
|
});
|
|
77945
78051
|
}
|
|
@@ -77973,7 +78079,7 @@ not loop on the same plan across heartbeats without recording why.`;
|
|
|
77973
78079
|
// Skill selection: use waking agent's skills (heartbeat has no role fallback)
|
|
77974
78080
|
...skillContext.skillSelectionContext ? { skillSelection: skillContext.skillSelectionContext } : {}
|
|
77975
78081
|
});
|
|
77976
|
-
this.trackAgent(agentId, { dispose: () => session.dispose() },
|
|
78082
|
+
this.trackAgent(agentId, { dispose: () => session.dispose() }, run2.id);
|
|
77977
78083
|
try {
|
|
77978
78084
|
let pendingMessages = [];
|
|
77979
78085
|
let executionPrompt;
|
|
@@ -78137,15 +78243,15 @@ ${taskDetail.prompt}` : "No PROMPT.md available.",
|
|
|
78137
78243
|
}
|
|
78138
78244
|
try {
|
|
78139
78245
|
const runWithPrompts = {
|
|
78140
|
-
...
|
|
78246
|
+
...run2,
|
|
78141
78247
|
systemPrompt: truncatePrompt(systemPrompt, 1e5),
|
|
78142
78248
|
executionPrompt: truncatePrompt(executionPrompt, 1e5),
|
|
78143
78249
|
heartbeatProcedureSource: customProcedure ? "custom" : "default"
|
|
78144
78250
|
};
|
|
78145
78251
|
await this.store.saveRun(runWithPrompts);
|
|
78146
|
-
Object.assign(
|
|
78252
|
+
Object.assign(run2, { systemPrompt: runWithPrompts.systemPrompt, executionPrompt: runWithPrompts.executionPrompt, heartbeatProcedureSource: runWithPrompts.heartbeatProcedureSource });
|
|
78147
78253
|
} catch (promptPersistErr) {
|
|
78148
|
-
heartbeatLog.warn(`Failed to persist prompts for ${agentId}/${
|
|
78254
|
+
heartbeatLog.warn(`Failed to persist prompts for ${agentId}/${run2.id}: ${promptPersistErr instanceof Error ? promptPersistErr.message : String(promptPersistErr)}`);
|
|
78149
78255
|
}
|
|
78150
78256
|
await promptWithFallback(session, executionPrompt);
|
|
78151
78257
|
let usageInput = 0;
|
|
@@ -78189,7 +78295,7 @@ ${taskDetail.prompt}` : "No PROMPT.md available.",
|
|
|
78189
78295
|
completionResultJson.priority = inboxSelection.priority;
|
|
78190
78296
|
completionResultJson.taskId = taskId;
|
|
78191
78297
|
}
|
|
78192
|
-
await this.completeRun(agentId,
|
|
78298
|
+
await this.completeRun(agentId, run2.id, {
|
|
78193
78299
|
status: "completed",
|
|
78194
78300
|
usageJson: { inputTokens: usageInput, outputTokens: usageOutput, cachedTokens: usageCached },
|
|
78195
78301
|
resultJson: completionResultJson,
|
|
@@ -78200,7 +78306,7 @@ ${taskDetail.prompt}` : "No PROMPT.md available.",
|
|
|
78200
78306
|
const errorDetail = formatError(err).detail;
|
|
78201
78307
|
heartbeatLog.error(`Heartbeat execution failed for ${agentId}: ${errorDetail}`);
|
|
78202
78308
|
await flushAgentLogger();
|
|
78203
|
-
await this.completeRun(agentId,
|
|
78309
|
+
await this.completeRun(agentId, run2.id, {
|
|
78204
78310
|
status: "failed",
|
|
78205
78311
|
stderrExcerpt: errorDetail,
|
|
78206
78312
|
stdoutExcerpt: stdoutExcerpt || void 0
|
|
@@ -78219,22 +78325,22 @@ ${taskDetail.prompt}` : "No PROMPT.md available.",
|
|
|
78219
78325
|
heartbeatLog.warn(`session.dispose() failed for ${agentId}: ${errorMessage}`);
|
|
78220
78326
|
}
|
|
78221
78327
|
}
|
|
78222
|
-
return await this.store.getRunDetail(agentId,
|
|
78328
|
+
return await this.store.getRunDetail(agentId, run2.id);
|
|
78223
78329
|
} catch (err) {
|
|
78224
78330
|
const errorDetail = formatError(err).detail;
|
|
78225
78331
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
78226
78332
|
heartbeatLog.error(`Heartbeat execution error for ${agentId}: ${errorDetail}`);
|
|
78227
78333
|
await flushAgentLogger();
|
|
78228
78334
|
try {
|
|
78229
|
-
await this.completeRun(agentId,
|
|
78335
|
+
await this.completeRun(agentId, run2.id, {
|
|
78230
78336
|
status: "failed",
|
|
78231
78337
|
stderrExcerpt: errorDetail
|
|
78232
78338
|
});
|
|
78233
78339
|
} catch (completeRunErr) {
|
|
78234
78340
|
const completeRunErrMsg = completeRunErr instanceof Error ? completeRunErr.message : String(completeRunErr);
|
|
78235
|
-
heartbeatLog.error(`completeRun failed for ${agentId}/${
|
|
78341
|
+
heartbeatLog.error(`completeRun failed for ${agentId}/${run2.id}: ${completeRunErrMsg} \u2014 attempting safety-net completion`);
|
|
78236
78342
|
try {
|
|
78237
|
-
const runDetail = await this.store.getRunDetail(agentId,
|
|
78343
|
+
const runDetail = await this.store.getRunDetail(agentId, run2.id);
|
|
78238
78344
|
if (runDetail && runDetail.status !== "completed" && runDetail.status !== "failed" && runDetail.status !== "terminated") {
|
|
78239
78345
|
await this.store.saveRun({
|
|
78240
78346
|
...runDetail,
|
|
@@ -78242,16 +78348,16 @@ ${taskDetail.prompt}` : "No PROMPT.md available.",
|
|
|
78242
78348
|
status: "failed",
|
|
78243
78349
|
stderrExcerpt: `Heartbeat execution failed: ${errorMessage}. Run completion also failed: ${completeRunErrMsg}`
|
|
78244
78350
|
});
|
|
78245
|
-
await this.store.endHeartbeatRun(
|
|
78351
|
+
await this.store.endHeartbeatRun(run2.id, "terminated");
|
|
78246
78352
|
this.clearRunState(agentId);
|
|
78247
|
-
heartbeatLog.log(`Safety-net run completion for ${agentId}/${
|
|
78353
|
+
heartbeatLog.log(`Safety-net run completion for ${agentId}/${run2.id} \u2014 run terminated`);
|
|
78248
78354
|
}
|
|
78249
78355
|
} catch (safetyNetErr) {
|
|
78250
78356
|
const safetyNetErrMsg = safetyNetErr instanceof Error ? safetyNetErr.message : String(safetyNetErr);
|
|
78251
|
-
heartbeatLog.error(`Safety-net run completion also failed for ${agentId}/${
|
|
78357
|
+
heartbeatLog.error(`Safety-net run completion also failed for ${agentId}/${run2.id}: ${safetyNetErrMsg} \u2014 run may be stuck permanently`);
|
|
78252
78358
|
}
|
|
78253
78359
|
}
|
|
78254
|
-
return await this.store.getRunDetail(agentId,
|
|
78360
|
+
return await this.store.getRunDetail(agentId, run2.id);
|
|
78255
78361
|
}
|
|
78256
78362
|
});
|
|
78257
78363
|
}
|
|
@@ -79988,7 +80094,7 @@ var init_routine_runner = __esm({
|
|
|
79988
80094
|
return Boolean(routine.steps && routine.steps.length > 0 || routine.command?.trim());
|
|
79989
80095
|
}
|
|
79990
80096
|
async executeAgentRoutine(routine, triggerType, context) {
|
|
79991
|
-
const
|
|
80097
|
+
const run2 = await this.options.heartbeatMonitor.executeHeartbeat({
|
|
79992
80098
|
agentId: routine.agentId,
|
|
79993
80099
|
source: "routine",
|
|
79994
80100
|
triggerDetail: `routine:${routine.id}:${triggerType}`,
|
|
@@ -79999,8 +80105,8 @@ var init_routine_runner = __esm({
|
|
|
79999
80105
|
...context
|
|
80000
80106
|
}
|
|
80001
80107
|
});
|
|
80002
|
-
if (
|
|
80003
|
-
const error =
|
|
80108
|
+
if (run2.status === "failed" || run2.status === "terminated") {
|
|
80109
|
+
const error = run2.stderrExcerpt || `Run ${run2.status}`;
|
|
80004
80110
|
return {
|
|
80005
80111
|
success: false,
|
|
80006
80112
|
output: error,
|
|
@@ -80011,7 +80117,7 @@ var init_routine_runner = __esm({
|
|
|
80011
80117
|
}
|
|
80012
80118
|
return {
|
|
80013
80119
|
success: true,
|
|
80014
|
-
output:
|
|
80120
|
+
output: run2.resultJson ? JSON.stringify(run2.resultJson) : "Routine completed successfully",
|
|
80015
80121
|
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
80016
80122
|
completedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
80017
80123
|
};
|
|
@@ -80794,14 +80900,15 @@ var init_self_healing = __esm({
|
|
|
80794
80900
|
execAsync7 = promisify9(exec9);
|
|
80795
80901
|
APPROVED_TRIAGE_RECOVERY_GRACE_MS = 6e4;
|
|
80796
80902
|
ORPHANED_EXECUTION_RECOVERY_GRACE_MS = 6e4;
|
|
80797
|
-
ACTIVE_MERGE_STATUSES = /* @__PURE__ */ new Set(["merging", "merging-pr"]);
|
|
80903
|
+
ACTIVE_MERGE_STATUSES = /* @__PURE__ */ new Set(["merging", "merging-pr", "merging-fix"]);
|
|
80798
80904
|
NON_TERMINAL_STEP_STATUSES2 = /* @__PURE__ */ new Set(["pending", "in-progress"]);
|
|
80799
80905
|
GHOST_REVIEW_PRESERVED_STATUSES = /* @__PURE__ */ new Set([
|
|
80800
80906
|
"failed",
|
|
80801
80907
|
"awaiting-user-review",
|
|
80802
80908
|
"awaiting-approval",
|
|
80803
80909
|
"merging",
|
|
80804
|
-
"merging-pr"
|
|
80910
|
+
"merging-pr",
|
|
80911
|
+
"merging-fix"
|
|
80805
80912
|
]);
|
|
80806
80913
|
ORPHANED_WITH_WORKTREE_GRACE_MS = 3e5;
|
|
80807
80914
|
MAX_TASK_DONE_RETRIES = 3;
|
|
@@ -81524,7 +81631,7 @@ var init_self_healing = __esm({
|
|
|
81524
81631
|
*
|
|
81525
81632
|
* Preserved statuses (skipped):
|
|
81526
81633
|
* - `awaiting-user-review`, `awaiting-approval`: explicit human handoff
|
|
81527
|
-
* - `merging`, `merging-pr`: handled by `recoverInterruptedMergingTasks`
|
|
81634
|
+
* - `merging`, `merging-pr`, `merging-fix`: handled by `recoverInterruptedMergingTasks`
|
|
81528
81635
|
*
|
|
81529
81636
|
* Rate-limiting comes from the `updatedAt >= taskStuckTimeoutMs` gate —
|
|
81530
81637
|
* each kick refreshes `updatedAt`, so a task that re-enters review and gets
|
|
@@ -87140,7 +87247,7 @@ ${detail}`
|
|
|
87140
87247
|
"agent"
|
|
87141
87248
|
);
|
|
87142
87249
|
await store.updateTask(taskId, {
|
|
87143
|
-
status:
|
|
87250
|
+
status: "merging-fix",
|
|
87144
87251
|
mergeRetries: 0,
|
|
87145
87252
|
error: null,
|
|
87146
87253
|
verificationFailureCount: nextBounces
|
|
@@ -87148,10 +87255,10 @@ ${detail}`
|
|
|
87148
87255
|
await store.moveTask(taskId, "in-progress");
|
|
87149
87256
|
await store.logEntry(
|
|
87150
87257
|
taskId,
|
|
87151
|
-
`Deterministic ${failedKind} verification failed (${nextBounces}/${cap}) \u2014 moved back to in-progress for remediation`
|
|
87258
|
+
`Deterministic ${failedKind} verification failed (${nextBounces}/${cap}) \u2014 moved back to in-progress with status=merging-fix for remediation`
|
|
87152
87259
|
);
|
|
87153
87260
|
runtimeLog.log(
|
|
87154
|
-
`Auto-merge: ${taskId} deterministic ${failedKind} verification failed (${nextBounces}/${cap}) \u2014 moved to in-progress`
|
|
87261
|
+
`Auto-merge: ${taskId} deterministic ${failedKind} verification failed (${nextBounces}/${cap}) \u2014 moved to in-progress with status=merging-fix`
|
|
87155
87262
|
);
|
|
87156
87263
|
} catch {
|
|
87157
87264
|
runtimeLog.error(
|
|
@@ -94297,9 +94404,22 @@ function registerTaskWorkflowRoutes(ctx, deps) {
|
|
|
94297
94404
|
const { store: scopedStore } = await getProjectContext3(req);
|
|
94298
94405
|
const task = await scopedStore.getTask(req.params.id);
|
|
94299
94406
|
const retrySpecification = task.column === "triage" && (task.status === "failed" || task.status === "planning" || task.status === "needs-replan" || (task.stuckKillCount ?? 0) > 0);
|
|
94407
|
+
const isInReviewRetry = task.column === "in-review" && (task.status === "failed" || task.status === "stuck-killed");
|
|
94300
94408
|
if (task.status !== "failed" && task.status !== "stuck-killed" && !retrySpecification) {
|
|
94301
94409
|
throw badRequest(`Task is not in a retryable state (current status: ${task.status || "none"})`);
|
|
94302
94410
|
}
|
|
94411
|
+
if (isInReviewRetry) {
|
|
94412
|
+
await scopedStore.updateTask(req.params.id, {
|
|
94413
|
+
status: null,
|
|
94414
|
+
error: null,
|
|
94415
|
+
stuckKillCount: 0,
|
|
94416
|
+
mergeRetries: 0
|
|
94417
|
+
});
|
|
94418
|
+
await scopedStore.logEntry(req.params.id, "Retry requested from dashboard (in-review retry, mergeRetries reset)");
|
|
94419
|
+
const updated2 = await scopedStore.getTask(req.params.id);
|
|
94420
|
+
res.json(updated2);
|
|
94421
|
+
return;
|
|
94422
|
+
}
|
|
94303
94423
|
await scopedStore.updateTask(req.params.id, {
|
|
94304
94424
|
status: retrySpecification ? "needs-replan" : null,
|
|
94305
94425
|
error: null,
|
|
@@ -94722,8 +94842,12 @@ function registerTaskWorkflowRoutes(ctx, deps) {
|
|
|
94722
94842
|
router.post("/tasks/:id/pause", async (req, res) => {
|
|
94723
94843
|
try {
|
|
94724
94844
|
const { store: scopedStore } = await getProjectContext3(req);
|
|
94725
|
-
const task = await scopedStore.
|
|
94726
|
-
|
|
94845
|
+
const task = await scopedStore.getTask(req.params.id);
|
|
94846
|
+
if (task.assignedAgentId) {
|
|
94847
|
+
throw conflict(`Cannot manually pause/unpause task assigned to agent ${task.assignedAgentId}. Use agent pause controls instead.`);
|
|
94848
|
+
}
|
|
94849
|
+
const updated = await scopedStore.pauseTask(req.params.id, true);
|
|
94850
|
+
res.json(updated);
|
|
94727
94851
|
} catch (err) {
|
|
94728
94852
|
if (err instanceof ApiError) {
|
|
94729
94853
|
throw err;
|
|
@@ -94734,8 +94858,12 @@ function registerTaskWorkflowRoutes(ctx, deps) {
|
|
|
94734
94858
|
router.post("/tasks/:id/unpause", async (req, res) => {
|
|
94735
94859
|
try {
|
|
94736
94860
|
const { store: scopedStore } = await getProjectContext3(req);
|
|
94737
|
-
const task = await scopedStore.
|
|
94738
|
-
|
|
94861
|
+
const task = await scopedStore.getTask(req.params.id);
|
|
94862
|
+
if (task.assignedAgentId) {
|
|
94863
|
+
throw conflict(`Cannot manually pause/unpause task assigned to agent ${task.assignedAgentId}. Use agent pause controls instead.`);
|
|
94864
|
+
}
|
|
94865
|
+
const updated = await scopedStore.pauseTask(req.params.id, false);
|
|
94866
|
+
res.json(updated);
|
|
94739
94867
|
} catch (err) {
|
|
94740
94868
|
if (err instanceof ApiError) {
|
|
94741
94869
|
throw err;
|
|
@@ -111900,8 +112028,8 @@ function auto(tasks, concurrency, callback) {
|
|
|
111900
112028
|
return callback(null, results);
|
|
111901
112029
|
}
|
|
111902
112030
|
while (readyTasks.length && runningTasks < concurrency) {
|
|
111903
|
-
var
|
|
111904
|
-
|
|
112031
|
+
var run2 = readyTasks.shift();
|
|
112032
|
+
run2();
|
|
111905
112033
|
}
|
|
111906
112034
|
}
|
|
111907
112035
|
function addListener(taskName, fn) {
|
|
@@ -136929,7 +137057,7 @@ var init_register_project_routes = __esm({
|
|
|
136929
137057
|
} = fsPromises);
|
|
136930
137058
|
registerProjectRoutes = (ctx) => {
|
|
136931
137059
|
const { router, options, runtimeLogger, prioritizeProjectsForCurrentDirectory, rethrowAsApiError: rethrowAsApiError8 } = ctx;
|
|
136932
|
-
async function withCentralCore(
|
|
137060
|
+
async function withCentralCore(run2, onError) {
|
|
136933
137061
|
const sharedCentral = options?.centralCore;
|
|
136934
137062
|
const shouldClose = !sharedCentral;
|
|
136935
137063
|
const central = sharedCentral ?? new (await Promise.resolve().then(() => (init_src(), src_exports))).CentralCore();
|
|
@@ -136937,7 +137065,7 @@ var init_register_project_routes = __esm({
|
|
|
136937
137065
|
if (!sharedCentral || typeof central.isInitialized === "function" && !central.isInitialized()) {
|
|
136938
137066
|
await central.init();
|
|
136939
137067
|
}
|
|
136940
|
-
return await
|
|
137068
|
+
return await run2(central);
|
|
136941
137069
|
} catch (error) {
|
|
136942
137070
|
if (onError) {
|
|
136943
137071
|
return await onError(error);
|
|
@@ -137674,6 +137802,32 @@ function sanitizeExtraClis(input) {
|
|
|
137674
137802
|
}
|
|
137675
137803
|
return input;
|
|
137676
137804
|
}
|
|
137805
|
+
function toManagedDockerNodeInfo(managedNode, linkedNode) {
|
|
137806
|
+
return {
|
|
137807
|
+
...managedNode,
|
|
137808
|
+
hostConfig: {
|
|
137809
|
+
type: managedNode.hostConfig.host || managedNode.hostConfig.context ? "remote" : "local",
|
|
137810
|
+
host: managedNode.hostConfig.host,
|
|
137811
|
+
context: managedNode.hostConfig.context,
|
|
137812
|
+
tlsOptions: {
|
|
137813
|
+
tlsVerify: managedNode.hostConfig.tlsVerify,
|
|
137814
|
+
tlsCaPath: managedNode.hostConfig.tlsCaPath,
|
|
137815
|
+
tlsCertPath: managedNode.hostConfig.tlsCertPath,
|
|
137816
|
+
tlsKeyPath: managedNode.hostConfig.tlsKeyPath
|
|
137817
|
+
}
|
|
137818
|
+
},
|
|
137819
|
+
volumeMounts: managedNode.volumeMounts.map((mount) => ({
|
|
137820
|
+
hostPath: mount.hostPath,
|
|
137821
|
+
containerPath: mount.containerPath,
|
|
137822
|
+
readOnly: mount.mode === "ro" ? true : void 0
|
|
137823
|
+
})),
|
|
137824
|
+
resourceSizing: {
|
|
137825
|
+
cpuLimit: managedNode.resourceSizing?.cpus !== void 0 ? String(managedNode.resourceSizing.cpus) : void 0,
|
|
137826
|
+
memoryLimit: managedNode.resourceSizing?.memoryMB !== void 0 ? `${managedNode.resourceSizing.memoryMB}MB` : void 0
|
|
137827
|
+
},
|
|
137828
|
+
linkedNode: linkedNode ?? void 0
|
|
137829
|
+
};
|
|
137830
|
+
}
|
|
137677
137831
|
var VALID_EXTRA_CLIS, registerDockerNodeRoutes;
|
|
137678
137832
|
var init_register_docker_node_routes = __esm({
|
|
137679
137833
|
"../dashboard/src/routes/register-docker-node-routes.ts"() {
|
|
@@ -137722,6 +137876,126 @@ var init_register_docker_node_routes = __esm({
|
|
|
137722
137876
|
res.json({ available: false, error: message });
|
|
137723
137877
|
}
|
|
137724
137878
|
});
|
|
137879
|
+
router.get("/docker/nodes", async (_req, res) => {
|
|
137880
|
+
try {
|
|
137881
|
+
const { CentralCore: CentralCore2 } = await Promise.resolve().then(() => (init_src(), src_exports));
|
|
137882
|
+
const central = new CentralCore2();
|
|
137883
|
+
await central.init();
|
|
137884
|
+
try {
|
|
137885
|
+
const nodes = await central.listManagedDockerNodes();
|
|
137886
|
+
const enriched = await Promise.all(nodes.map(async (managedNode) => {
|
|
137887
|
+
const linkedNode = managedNode.nodeId ? await central.getNode(managedNode.nodeId) : void 0;
|
|
137888
|
+
return toManagedDockerNodeInfo(managedNode, linkedNode);
|
|
137889
|
+
}));
|
|
137890
|
+
enriched.sort((a, b) => (a.name ?? "").localeCompare(b.name ?? ""));
|
|
137891
|
+
res.json(enriched);
|
|
137892
|
+
} finally {
|
|
137893
|
+
await central.close();
|
|
137894
|
+
}
|
|
137895
|
+
} catch (error) {
|
|
137896
|
+
if (error instanceof ApiError) {
|
|
137897
|
+
throw error;
|
|
137898
|
+
}
|
|
137899
|
+
rethrowAsApiError8(error);
|
|
137900
|
+
}
|
|
137901
|
+
});
|
|
137902
|
+
router.get("/docker/nodes/:managedId/container-status", async (req, res) => {
|
|
137903
|
+
try {
|
|
137904
|
+
const { CentralCore: CentralCore2, DockerClientService: DockerClientService2 } = await Promise.resolve().then(() => (init_src(), src_exports));
|
|
137905
|
+
const central = new CentralCore2();
|
|
137906
|
+
await central.init();
|
|
137907
|
+
try {
|
|
137908
|
+
const managedNode = await central.getManagedDockerNode(req.params.managedId);
|
|
137909
|
+
if (!managedNode) {
|
|
137910
|
+
throw notFound("Managed Docker node not found");
|
|
137911
|
+
}
|
|
137912
|
+
if (!managedNode.containerId) {
|
|
137913
|
+
throw badRequest(`Node has no container yet (status: ${managedNode.status})`);
|
|
137914
|
+
}
|
|
137915
|
+
try {
|
|
137916
|
+
const dockerService = new DockerClientService2(managedNode.hostConfig);
|
|
137917
|
+
const info = await dockerService.getContainerInfo(managedNode.containerId, managedNode.hostConfig);
|
|
137918
|
+
if (!info) {
|
|
137919
|
+
throw notFound("Container not found");
|
|
137920
|
+
}
|
|
137921
|
+
res.json({
|
|
137922
|
+
running: info.state.running,
|
|
137923
|
+
status: info.status,
|
|
137924
|
+
startedAt: info.state.startedAt,
|
|
137925
|
+
finishedAt: info.state.finishedAt,
|
|
137926
|
+
exitCode: info.state.exitCode,
|
|
137927
|
+
error: info.state.error,
|
|
137928
|
+
ports: info.ports
|
|
137929
|
+
});
|
|
137930
|
+
} catch (error) {
|
|
137931
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
137932
|
+
res.status(503).json({ error: `Docker unreachable: ${message}` });
|
|
137933
|
+
}
|
|
137934
|
+
} finally {
|
|
137935
|
+
await central.close();
|
|
137936
|
+
}
|
|
137937
|
+
} catch (error) {
|
|
137938
|
+
if (error instanceof ApiError) {
|
|
137939
|
+
throw error;
|
|
137940
|
+
}
|
|
137941
|
+
rethrowAsApiError8(error);
|
|
137942
|
+
}
|
|
137943
|
+
});
|
|
137944
|
+
router.get("/docker/nodes/:managedId/logs", async (req, res) => {
|
|
137945
|
+
try {
|
|
137946
|
+
const { CentralCore: CentralCore2, DockerClientService: DockerClientService2 } = await Promise.resolve().then(() => (init_src(), src_exports));
|
|
137947
|
+
const central = new CentralCore2();
|
|
137948
|
+
await central.init();
|
|
137949
|
+
try {
|
|
137950
|
+
const managedNode = await central.getManagedDockerNode(req.params.managedId);
|
|
137951
|
+
if (!managedNode) {
|
|
137952
|
+
throw notFound("Managed Docker node not found");
|
|
137953
|
+
}
|
|
137954
|
+
if (!managedNode.containerId) {
|
|
137955
|
+
throw badRequest(`Node has no container yet (status: ${managedNode.status})`);
|
|
137956
|
+
}
|
|
137957
|
+
const tailValue = Number(req.query.tail ?? 100);
|
|
137958
|
+
const tail = Number.isFinite(tailValue) ? Math.max(1, Math.min(1e3, Math.floor(tailValue))) : 100;
|
|
137959
|
+
try {
|
|
137960
|
+
const dockerService = new DockerClientService2(managedNode.hostConfig);
|
|
137961
|
+
const logs = await dockerService.getContainerLogs(managedNode.containerId, managedNode.hostConfig, { tail });
|
|
137962
|
+
res.json({ logs });
|
|
137963
|
+
} catch (error) {
|
|
137964
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
137965
|
+
res.status(503).json({ error: `Docker unreachable: ${message}` });
|
|
137966
|
+
}
|
|
137967
|
+
} finally {
|
|
137968
|
+
await central.close();
|
|
137969
|
+
}
|
|
137970
|
+
} catch (error) {
|
|
137971
|
+
if (error instanceof ApiError) {
|
|
137972
|
+
throw error;
|
|
137973
|
+
}
|
|
137974
|
+
rethrowAsApiError8(error);
|
|
137975
|
+
}
|
|
137976
|
+
});
|
|
137977
|
+
router.get("/docker/nodes/:managedId", async (req, res) => {
|
|
137978
|
+
try {
|
|
137979
|
+
const { CentralCore: CentralCore2 } = await Promise.resolve().then(() => (init_src(), src_exports));
|
|
137980
|
+
const central = new CentralCore2();
|
|
137981
|
+
await central.init();
|
|
137982
|
+
try {
|
|
137983
|
+
const node = await central.getManagedDockerNode(req.params.managedId);
|
|
137984
|
+
if (!node) {
|
|
137985
|
+
throw notFound("Managed Docker node not found");
|
|
137986
|
+
}
|
|
137987
|
+
const linkedNode = node.nodeId ? await central.getNode(node.nodeId) : void 0;
|
|
137988
|
+
res.json(toManagedDockerNodeInfo(node, linkedNode));
|
|
137989
|
+
} finally {
|
|
137990
|
+
await central.close();
|
|
137991
|
+
}
|
|
137992
|
+
} catch (error) {
|
|
137993
|
+
if (error instanceof ApiError) {
|
|
137994
|
+
throw error;
|
|
137995
|
+
}
|
|
137996
|
+
rethrowAsApiError8(error);
|
|
137997
|
+
}
|
|
137998
|
+
});
|
|
137725
137999
|
router.get("/docker-nodes", async (_req, res) => {
|
|
137726
138000
|
try {
|
|
137727
138001
|
const { CentralCore: CentralCore2 } = await Promise.resolve().then(() => (init_src(), src_exports));
|
|
@@ -139578,6 +139852,7 @@ function registerAgentRuntimeRoutes(ctx, deps) {
|
|
|
139578
139852
|
hasHeartbeatExecutor,
|
|
139579
139853
|
heartbeatMonitor,
|
|
139580
139854
|
isHeartbeatMonitorForProject,
|
|
139855
|
+
resolveHeartbeatMonitor,
|
|
139581
139856
|
runExcerptToAgentLogs: runExcerptToAgentLogs2,
|
|
139582
139857
|
parseRunAuditFilters: parseRunAuditFilters2,
|
|
139583
139858
|
normalizeRunAuditEvent: normalizeRunAuditEvent2,
|
|
@@ -139873,6 +140148,33 @@ function registerAgentRuntimeRoutes(ctx, deps) {
|
|
|
139873
140148
|
}
|
|
139874
140149
|
}
|
|
139875
140150
|
}
|
|
140151
|
+
if (nextState === "paused") {
|
|
140152
|
+
const assignedTasks = await scopedStore.getTasksByAssignedAgent(agentId, { excludeArchived: true });
|
|
140153
|
+
const toPause = assignedTasks.filter((task) => task.paused !== true);
|
|
140154
|
+
const results = await Promise.allSettled(
|
|
140155
|
+
toPause.map((task) => scopedStore.pauseTask(task.id, true, void 0, { pausedByAgentId: agentId }))
|
|
140156
|
+
);
|
|
140157
|
+
results.forEach((result, index2) => {
|
|
140158
|
+
if (result.status === "rejected") {
|
|
140159
|
+
console.error(`[agent-state] failed to pause assigned task ${toPause[index2]?.id} for ${agentId}:`, result.reason);
|
|
140160
|
+
}
|
|
140161
|
+
});
|
|
140162
|
+
}
|
|
140163
|
+
if (nextState === "active" || nextState === "terminated") {
|
|
140164
|
+
const pausedTasks = await scopedStore.getTasksByAssignedAgent(agentId, {
|
|
140165
|
+
pausedOnly: true,
|
|
140166
|
+
excludeArchived: true
|
|
140167
|
+
});
|
|
140168
|
+
const toUnpause = pausedTasks.filter((task) => task.pausedByAgentId === agentId);
|
|
140169
|
+
const results = await Promise.allSettled(
|
|
140170
|
+
toUnpause.map((task) => scopedStore.pauseTask(task.id, false))
|
|
140171
|
+
);
|
|
140172
|
+
results.forEach((result, index2) => {
|
|
140173
|
+
if (result.status === "rejected") {
|
|
140174
|
+
console.error(`[agent-state] failed to unpause assigned task ${toUnpause[index2]?.id} for ${agentId}:`, result.reason);
|
|
140175
|
+
}
|
|
140176
|
+
});
|
|
140177
|
+
}
|
|
139876
140178
|
const isHeartbeatEnabled = currentAgent.runtimeConfig?.enabled !== false;
|
|
139877
140179
|
if (nextState === "active" && isHeartbeatEnabled && projectHeartbeatMonitor) {
|
|
139878
140180
|
await projectHeartbeatMonitor.executeHeartbeat({
|
|
@@ -140140,19 +140442,22 @@ function registerAgentRuntimeRoutes(ctx, deps) {
|
|
|
140140
140442
|
const agentStore = new AgentStore2({ rootDir: scopedStore.getFusionDir() });
|
|
140141
140443
|
await agentStore.init();
|
|
140142
140444
|
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
|
-
|
|
140445
|
+
let run2;
|
|
140446
|
+
if (triggerExecution && hasHeartbeatExecutor && heartbeatMonitor) {
|
|
140447
|
+
const resolvedMonitor = isHeartbeatMonitorForProject(scopedStore) ? heartbeatMonitor : resolveHeartbeatMonitor(scopedStore);
|
|
140448
|
+
if (resolvedMonitor) {
|
|
140449
|
+
run2 = await resolvedMonitor.executeHeartbeat({
|
|
140450
|
+
agentId: req.params.id,
|
|
140451
|
+
source: "on_demand",
|
|
140452
|
+
triggerDetail: "Triggered from heartbeat",
|
|
140453
|
+
contextSnapshot: {
|
|
140454
|
+
wakeReason: "on_demand",
|
|
140455
|
+
triggerDetail: "Triggered from heartbeat"
|
|
140456
|
+
}
|
|
140457
|
+
});
|
|
140458
|
+
}
|
|
140154
140459
|
}
|
|
140155
|
-
res.json(
|
|
140460
|
+
res.json(run2 ? { event, run: run2 } : event);
|
|
140156
140461
|
} catch (err) {
|
|
140157
140462
|
if (err instanceof ApiError) {
|
|
140158
140463
|
throw err;
|
|
@@ -140230,8 +140535,9 @@ function registerAgentRuntimeRoutes(ctx, deps) {
|
|
|
140230
140535
|
}
|
|
140231
140536
|
if (hasHeartbeatExecutor && heartbeatMonitor) {
|
|
140232
140537
|
const { store: scopedStore } = await getProjectContext3(req);
|
|
140233
|
-
|
|
140234
|
-
|
|
140538
|
+
const resolvedMonitor = isHeartbeatMonitorForProject(scopedStore) ? heartbeatMonitor : resolveHeartbeatMonitor(scopedStore);
|
|
140539
|
+
if (!resolvedMonitor) {
|
|
140540
|
+
throw new ApiError(400, "No heartbeat executor available for this project.");
|
|
140235
140541
|
}
|
|
140236
140542
|
const { AgentStore: AgentStoreClass } = await Promise.resolve().then(() => (init_src(), src_exports));
|
|
140237
140543
|
const agentStore = new AgentStoreClass({ rootDir: scopedStore.getFusionDir() });
|
|
@@ -140244,7 +140550,7 @@ function registerAgentRuntimeRoutes(ctx, deps) {
|
|
|
140244
140550
|
if (activeRun) {
|
|
140245
140551
|
throw new ApiError(409, "Agent already has an active run", { runId: activeRun.id });
|
|
140246
140552
|
}
|
|
140247
|
-
const
|
|
140553
|
+
const run2 = await resolvedMonitor.executeHeartbeat({
|
|
140248
140554
|
agentId: req.params.id,
|
|
140249
140555
|
source: invocationSource,
|
|
140250
140556
|
triggerDetail: trigger,
|
|
@@ -140253,7 +140559,7 @@ function registerAgentRuntimeRoutes(ctx, deps) {
|
|
|
140253
140559
|
triggeringCommentType: normalizedTriggeringCommentType,
|
|
140254
140560
|
contextSnapshot
|
|
140255
140561
|
});
|
|
140256
|
-
res.status(201).json(
|
|
140562
|
+
res.status(201).json(run2);
|
|
140257
140563
|
} else {
|
|
140258
140564
|
const { store: scopedStore } = await getProjectContext3(req);
|
|
140259
140565
|
const { AgentStore: AgentStore2 } = await Promise.resolve().then(() => (init_src(), src_exports));
|
|
@@ -140267,12 +140573,12 @@ function registerAgentRuntimeRoutes(ctx, deps) {
|
|
|
140267
140573
|
if (activeRun) {
|
|
140268
140574
|
throw new ApiError(409, "Agent already has an active run", { runId: activeRun.id });
|
|
140269
140575
|
}
|
|
140270
|
-
const
|
|
140271
|
-
|
|
140272
|
-
|
|
140273
|
-
|
|
140274
|
-
await agentStore.saveRun(
|
|
140275
|
-
res.status(201).json(
|
|
140576
|
+
const run2 = await agentStore.startHeartbeatRun(req.params.id);
|
|
140577
|
+
run2.invocationSource = invocationSource;
|
|
140578
|
+
run2.triggerDetail = trigger;
|
|
140579
|
+
run2.contextSnapshot = contextSnapshot;
|
|
140580
|
+
await agentStore.saveRun(run2);
|
|
140581
|
+
res.status(201).json(run2);
|
|
140276
140582
|
}
|
|
140277
140583
|
} catch (err) {
|
|
140278
140584
|
if (err instanceof ApiError) {
|
|
@@ -140300,8 +140606,26 @@ function registerAgentRuntimeRoutes(ctx, deps) {
|
|
|
140300
140606
|
res.status(200).json({ ok: true, message: "No active run" });
|
|
140301
140607
|
return;
|
|
140302
140608
|
}
|
|
140303
|
-
if (hasHeartbeatExecutor && heartbeatMonitor
|
|
140304
|
-
|
|
140609
|
+
if (hasHeartbeatExecutor && heartbeatMonitor) {
|
|
140610
|
+
const resolvedMonitor = isHeartbeatMonitorForProject(scopedStore) ? heartbeatMonitor : resolveHeartbeatMonitor(scopedStore);
|
|
140611
|
+
if (resolvedMonitor) {
|
|
140612
|
+
await resolvedMonitor.stopRun(req.params.id);
|
|
140613
|
+
} else {
|
|
140614
|
+
const existingRun = await agentStore.getRunDetail(req.params.id, activeRun.id);
|
|
140615
|
+
if (existingRun) {
|
|
140616
|
+
await agentStore.saveRun({
|
|
140617
|
+
...existingRun,
|
|
140618
|
+
endedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
140619
|
+
status: "terminated",
|
|
140620
|
+
stderrExcerpt: existingRun.stderrExcerpt ?? "Run stopped by user"
|
|
140621
|
+
});
|
|
140622
|
+
}
|
|
140623
|
+
await agentStore.endHeartbeatRun(activeRun.id, "terminated");
|
|
140624
|
+
try {
|
|
140625
|
+
await agentStore.updateAgentState(req.params.id, "active");
|
|
140626
|
+
} catch {
|
|
140627
|
+
}
|
|
140628
|
+
}
|
|
140305
140629
|
} else {
|
|
140306
140630
|
const existingRun = await agentStore.getRunDetail(req.params.id, activeRun.id);
|
|
140307
140631
|
if (existingRun) {
|
|
@@ -140335,11 +140659,11 @@ function registerAgentRuntimeRoutes(ctx, deps) {
|
|
|
140335
140659
|
const { AgentStore: AgentStore2 } = await Promise.resolve().then(() => (init_src(), src_exports));
|
|
140336
140660
|
const agentStore = new AgentStore2({ rootDir: scopedStore.getFusionDir() });
|
|
140337
140661
|
await agentStore.init();
|
|
140338
|
-
const
|
|
140339
|
-
if (!
|
|
140662
|
+
const run2 = await agentStore.getRunDetail(req.params.id, req.params.runId);
|
|
140663
|
+
if (!run2) {
|
|
140340
140664
|
throw notFound("Run not found");
|
|
140341
140665
|
}
|
|
140342
|
-
res.json(
|
|
140666
|
+
res.json(run2);
|
|
140343
140667
|
} catch (err) {
|
|
140344
140668
|
if (err instanceof ApiError) {
|
|
140345
140669
|
throw err;
|
|
@@ -140357,8 +140681,8 @@ function registerAgentRuntimeRoutes(ctx, deps) {
|
|
|
140357
140681
|
const { AgentStore: AgentStore2 } = await Promise.resolve().then(() => (init_src(), src_exports));
|
|
140358
140682
|
const agentStore = new AgentStore2({ rootDir: scopedStore.getFusionDir() });
|
|
140359
140683
|
await agentStore.init();
|
|
140360
|
-
const
|
|
140361
|
-
if (!
|
|
140684
|
+
const run2 = await agentStore.getRunDetail(req.params.id, req.params.runId);
|
|
140685
|
+
if (!run2) {
|
|
140362
140686
|
throw notFound("Run not found");
|
|
140363
140687
|
}
|
|
140364
140688
|
const runLogs = await agentStore.getRunLogs(req.params.id, req.params.runId);
|
|
@@ -140366,17 +140690,17 @@ function registerAgentRuntimeRoutes(ctx, deps) {
|
|
|
140366
140690
|
res.json(runLogs);
|
|
140367
140691
|
return;
|
|
140368
140692
|
}
|
|
140369
|
-
const taskId =
|
|
140693
|
+
const taskId = run2.contextSnapshot?.taskId;
|
|
140370
140694
|
if (!taskId) {
|
|
140371
|
-
res.json(runExcerptToAgentLogs2(
|
|
140695
|
+
res.json(runExcerptToAgentLogs2(run2));
|
|
140372
140696
|
return;
|
|
140373
140697
|
}
|
|
140374
140698
|
const logs = await scopedStore.getAgentLogsByTimeRange(
|
|
140375
140699
|
taskId,
|
|
140376
|
-
|
|
140377
|
-
|
|
140700
|
+
run2.startedAt,
|
|
140701
|
+
run2.endedAt
|
|
140378
140702
|
);
|
|
140379
|
-
res.json(logs.length > 0 ? logs : runExcerptToAgentLogs2(
|
|
140703
|
+
res.json(logs.length > 0 ? logs : runExcerptToAgentLogs2(run2));
|
|
140380
140704
|
} catch (err) {
|
|
140381
140705
|
if (err instanceof ApiError) {
|
|
140382
140706
|
throw err;
|
|
@@ -140394,8 +140718,8 @@ function registerAgentRuntimeRoutes(ctx, deps) {
|
|
|
140394
140718
|
const { AgentStore: AgentStore2 } = await Promise.resolve().then(() => (init_src(), src_exports));
|
|
140395
140719
|
const agentStore = new AgentStore2({ rootDir: scopedStore.getFusionDir() });
|
|
140396
140720
|
await agentStore.init();
|
|
140397
|
-
const
|
|
140398
|
-
if (!
|
|
140721
|
+
const run2 = await agentStore.getRunDetail(req.params.id, req.params.runId);
|
|
140722
|
+
if (!run2) {
|
|
140399
140723
|
throw notFound("Run not found");
|
|
140400
140724
|
}
|
|
140401
140725
|
const mutations = await scopedStore.getMutationsForRun(req.params.runId);
|
|
@@ -140421,8 +140745,8 @@ function registerAgentRuntimeRoutes(ctx, deps) {
|
|
|
140421
140745
|
if (!runId || runId.trim().length === 0) {
|
|
140422
140746
|
throw badRequest("runId is required");
|
|
140423
140747
|
}
|
|
140424
|
-
const
|
|
140425
|
-
if (!
|
|
140748
|
+
const run2 = await agentStore.getRunDetail(req.params.id, req.params.runId);
|
|
140749
|
+
if (!run2) {
|
|
140426
140750
|
throw notFound("Run not found");
|
|
140427
140751
|
}
|
|
140428
140752
|
const filters = parseRunAuditFilters2(req.query);
|
|
@@ -140476,8 +140800,8 @@ function registerAgentRuntimeRoutes(ctx, deps) {
|
|
|
140476
140800
|
if (!runId || runId.trim().length === 0) {
|
|
140477
140801
|
throw badRequest("runId is required");
|
|
140478
140802
|
}
|
|
140479
|
-
const
|
|
140480
|
-
if (!
|
|
140803
|
+
const run2 = await agentStore.getRunDetail(req.params.id, req.params.runId);
|
|
140804
|
+
if (!run2) {
|
|
140481
140805
|
throw notFound("Run not found");
|
|
140482
140806
|
}
|
|
140483
140807
|
const filters = parseRunAuditFilters2(req.query);
|
|
@@ -140492,7 +140816,7 @@ function registerAgentRuntimeRoutes(ctx, deps) {
|
|
|
140492
140816
|
}
|
|
140493
140817
|
return true;
|
|
140494
140818
|
})();
|
|
140495
|
-
const auditTaskId = filters.taskId ??
|
|
140819
|
+
const auditTaskId = filters.taskId ?? run2.contextSnapshot?.taskId ?? void 0;
|
|
140496
140820
|
const auditEvents = scopedStore.getRunAuditEvents({
|
|
140497
140821
|
runId: req.params.runId,
|
|
140498
140822
|
taskId: auditTaskId,
|
|
@@ -140520,13 +140844,13 @@ function registerAgentRuntimeRoutes(ctx, deps) {
|
|
|
140520
140844
|
for (const event of auditEvents) {
|
|
140521
140845
|
timelineEntries.push(auditEventToTimelineEntry2(event));
|
|
140522
140846
|
}
|
|
140523
|
-
if (includeLogs &&
|
|
140847
|
+
if (includeLogs && run2.startedAt) {
|
|
140524
140848
|
const taskId = auditTaskId;
|
|
140525
140849
|
if (taskId) {
|
|
140526
140850
|
const logs = await scopedStore.getAgentLogsByTimeRange(
|
|
140527
140851
|
taskId,
|
|
140528
|
-
|
|
140529
|
-
|
|
140852
|
+
run2.startedAt,
|
|
140853
|
+
run2.endedAt
|
|
140530
140854
|
);
|
|
140531
140855
|
for (const log19 of logs) {
|
|
140532
140856
|
timelineEntries.push(logEntryToTimelineEntry2(log19));
|
|
@@ -140536,11 +140860,11 @@ function registerAgentRuntimeRoutes(ctx, deps) {
|
|
|
140536
140860
|
timelineEntries.sort(compareTimelineEntries2);
|
|
140537
140861
|
const response = {
|
|
140538
140862
|
run: {
|
|
140539
|
-
id:
|
|
140540
|
-
agentId:
|
|
140541
|
-
startedAt:
|
|
140542
|
-
endedAt:
|
|
140543
|
-
status:
|
|
140863
|
+
id: run2.id,
|
|
140864
|
+
agentId: run2.agentId,
|
|
140865
|
+
startedAt: run2.startedAt,
|
|
140866
|
+
endedAt: run2.endedAt ?? void 0,
|
|
140867
|
+
status: run2.status,
|
|
140544
140868
|
taskId: auditTaskId ?? void 0
|
|
140545
140869
|
},
|
|
140546
140870
|
auditByDomain,
|
|
@@ -140548,8 +140872,8 @@ function registerAgentRuntimeRoutes(ctx, deps) {
|
|
|
140548
140872
|
auditEvents: normalizedAuditEvents.length,
|
|
140549
140873
|
logEntries: includeLogs && auditTaskId ? (await scopedStore.getAgentLogsByTimeRange(
|
|
140550
140874
|
auditTaskId,
|
|
140551
|
-
|
|
140552
|
-
|
|
140875
|
+
run2.startedAt,
|
|
140876
|
+
run2.endedAt
|
|
140553
140877
|
)).length : 0
|
|
140554
140878
|
},
|
|
140555
140879
|
timeline: timelineEntries
|
|
@@ -144457,96 +144781,70 @@ var init_claude_cli_probe = __esm({
|
|
|
144457
144781
|
}
|
|
144458
144782
|
});
|
|
144459
144783
|
|
|
144460
|
-
//
|
|
144784
|
+
// ../../plugins/fusion-plugin-droid-runtime/dist/probe.js
|
|
144461
144785
|
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
|
-
});
|
|
144786
|
+
async function run(binary, args, timeoutMs = 2e3) {
|
|
144787
|
+
return new Promise((resolve44) => {
|
|
144788
|
+
const child = spawn13(binary, args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
144789
|
+
let stdout = "";
|
|
144790
|
+
let stderr = "";
|
|
144474
144791
|
const timer = setTimeout(() => {
|
|
144475
|
-
if (settled) return;
|
|
144476
|
-
settled = true;
|
|
144477
144792
|
try {
|
|
144478
144793
|
child.kill("SIGKILL");
|
|
144479
144794
|
} catch {
|
|
144480
144795
|
}
|
|
144481
|
-
|
|
144482
|
-
available: false,
|
|
144483
|
-
binaryPath,
|
|
144484
|
-
reason: `Probe timed out after ${timeoutMs}ms`
|
|
144485
|
-
});
|
|
144796
|
+
resolve44({ code: 124, stdout, stderr });
|
|
144486
144797
|
}, timeoutMs);
|
|
144487
|
-
|
|
144488
|
-
|
|
144489
|
-
child.stdout?.on("data", (chunk) => {
|
|
144490
|
-
stdout += chunk.toString("utf-8");
|
|
144798
|
+
child.stdout?.on("data", (c) => {
|
|
144799
|
+
stdout += c.toString("utf-8");
|
|
144491
144800
|
});
|
|
144492
|
-
child.stderr?.on("data", (
|
|
144493
|
-
stderr +=
|
|
144801
|
+
child.stderr?.on("data", (c) => {
|
|
144802
|
+
stderr += c.toString("utf-8");
|
|
144494
144803
|
});
|
|
144495
|
-
child.on("error", (
|
|
144496
|
-
if (settled) return;
|
|
144497
|
-
settled = true;
|
|
144804
|
+
child.on("error", () => {
|
|
144498
144805
|
clearTimeout(timer);
|
|
144499
|
-
|
|
144500
|
-
finish({
|
|
144501
|
-
available: false,
|
|
144502
|
-
binaryPath,
|
|
144503
|
-
reason: isNotFound ? "`droid` not found on PATH" : err.message
|
|
144504
|
-
});
|
|
144806
|
+
resolve44({ code: 127, stdout, stderr });
|
|
144505
144807
|
});
|
|
144506
144808
|
child.on("close", (code) => {
|
|
144507
|
-
if (settled) return;
|
|
144508
|
-
settled = true;
|
|
144509
144809
|
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
|
-
}
|
|
144810
|
+
resolve44({ code, stdout, stderr });
|
|
144523
144811
|
});
|
|
144524
144812
|
});
|
|
144525
144813
|
}
|
|
144526
|
-
async function
|
|
144527
|
-
|
|
144528
|
-
|
|
144529
|
-
|
|
144530
|
-
|
|
144531
|
-
|
|
144532
|
-
|
|
144533
|
-
|
|
144534
|
-
|
|
144535
|
-
|
|
144536
|
-
|
|
144537
|
-
|
|
144538
|
-
|
|
144539
|
-
|
|
144540
|
-
|
|
144541
|
-
|
|
144542
|
-
|
|
144543
|
-
|
|
144814
|
+
async function probeDroidBinary(options) {
|
|
144815
|
+
const startedAt = Date.now();
|
|
144816
|
+
const binaryPath = options?.binaryPath?.trim() || "droid";
|
|
144817
|
+
const timeoutMs = options?.timeoutMs ?? 2e3;
|
|
144818
|
+
const versionRun = await run(binaryPath, ["--version"], timeoutMs);
|
|
144819
|
+
if (versionRun.code !== 0) {
|
|
144820
|
+
return {
|
|
144821
|
+
available: false,
|
|
144822
|
+
binaryPath,
|
|
144823
|
+
reason: versionRun.code === 124 ? `Probe timed out after ${timeoutMs}ms` : "`droid` not found on PATH",
|
|
144824
|
+
probeDurationMs: Date.now() - startedAt
|
|
144825
|
+
};
|
|
144826
|
+
}
|
|
144827
|
+
return {
|
|
144828
|
+
available: true,
|
|
144829
|
+
binaryPath,
|
|
144830
|
+
version: versionRun.stdout.trim() || void 0,
|
|
144831
|
+
probeDurationMs: Date.now() - startedAt
|
|
144832
|
+
};
|
|
144833
|
+
}
|
|
144834
|
+
var init_probe3 = __esm({
|
|
144835
|
+
"../../plugins/fusion-plugin-droid-runtime/dist/probe.js"() {
|
|
144836
|
+
"use strict";
|
|
144837
|
+
}
|
|
144838
|
+
});
|
|
144839
|
+
|
|
144840
|
+
// ../dashboard/src/droid-cli-probe.ts
|
|
144841
|
+
async function probeDroidCli(options = {}) {
|
|
144842
|
+
return probeDroidBinary({ timeoutMs: options.timeoutMs });
|
|
144544
144843
|
}
|
|
144545
|
-
var PROBE_TIMEOUT_MS2;
|
|
144546
144844
|
var init_droid_cli_probe = __esm({
|
|
144547
144845
|
"../dashboard/src/droid-cli-probe.ts"() {
|
|
144548
144846
|
"use strict";
|
|
144549
|
-
|
|
144847
|
+
init_probe3();
|
|
144550
144848
|
}
|
|
144551
144849
|
});
|
|
144552
144850
|
|
|
@@ -148050,24 +148348,24 @@ function createMissionRouter(store, missionAutopilot, aiSessionStore, missionExe
|
|
|
148050
148348
|
const validationRounds = [];
|
|
148051
148349
|
for (const feature of allFeatures) {
|
|
148052
148350
|
const runs = missionStore.getValidatorRunsByFeature(feature.id);
|
|
148053
|
-
for (const
|
|
148351
|
+
for (const run2 of runs) {
|
|
148054
148352
|
let failedAssertionIds = [];
|
|
148055
|
-
if (
|
|
148056
|
-
failedAssertionIds = missionStore.getFailuresForRun(
|
|
148057
|
-
failedAssertionIdsByRunId.set(
|
|
148353
|
+
if (run2.status === "failed") {
|
|
148354
|
+
failedAssertionIds = missionStore.getFailuresForRun(run2.id).map((failure) => failure.assertionId);
|
|
148355
|
+
failedAssertionIdsByRunId.set(run2.id, failedAssertionIds);
|
|
148058
148356
|
}
|
|
148059
148357
|
validationRounds.push({
|
|
148060
|
-
roundId:
|
|
148061
|
-
featureId:
|
|
148358
|
+
roundId: run2.id,
|
|
148359
|
+
featureId: run2.featureId,
|
|
148062
148360
|
featureTitle: feature.title,
|
|
148063
|
-
validatorStatus:
|
|
148064
|
-
implementationAttempt:
|
|
148065
|
-
validatorAttempt:
|
|
148361
|
+
validatorStatus: run2.status,
|
|
148362
|
+
implementationAttempt: run2.implementationAttempt,
|
|
148363
|
+
validatorAttempt: run2.validatorAttempt,
|
|
148066
148364
|
failedAssertionIds,
|
|
148067
|
-
generatedFixFeatureIds: generatedFixFeatureIdsByRunId.get(
|
|
148068
|
-
blockedReason:
|
|
148069
|
-
startedAt:
|
|
148070
|
-
completedAt:
|
|
148365
|
+
generatedFixFeatureIds: generatedFixFeatureIdsByRunId.get(run2.id) ?? [],
|
|
148366
|
+
blockedReason: run2.blockedReason,
|
|
148367
|
+
startedAt: run2.startedAt,
|
|
148368
|
+
completedAt: run2.completedAt
|
|
148071
148369
|
});
|
|
148072
148370
|
}
|
|
148073
148371
|
}
|
|
@@ -148130,15 +148428,15 @@ function createMissionRouter(store, missionAutopilot, aiSessionStore, missionExe
|
|
|
148130
148428
|
missionStore.updateFeature(featureId, {
|
|
148131
148429
|
loopState: "validating"
|
|
148132
148430
|
});
|
|
148133
|
-
const
|
|
148431
|
+
const run2 = missionStore.startValidatorRun(featureId, "manual");
|
|
148134
148432
|
res.status(202).json({
|
|
148135
|
-
runId:
|
|
148136
|
-
featureId:
|
|
148137
|
-
status:
|
|
148138
|
-
triggerType:
|
|
148139
|
-
implementationAttempt:
|
|
148140
|
-
validatorAttempt:
|
|
148141
|
-
startedAt:
|
|
148433
|
+
runId: run2.id,
|
|
148434
|
+
featureId: run2.featureId,
|
|
148435
|
+
status: run2.status,
|
|
148436
|
+
triggerType: run2.triggerType,
|
|
148437
|
+
implementationAttempt: run2.implementationAttempt,
|
|
148438
|
+
validatorAttempt: run2.validatorAttempt,
|
|
148439
|
+
startedAt: run2.startedAt
|
|
148142
148440
|
});
|
|
148143
148441
|
})
|
|
148144
148442
|
);
|
|
@@ -148188,13 +148486,13 @@ function createMissionRouter(store, missionAutopilot, aiSessionStore, missionExe
|
|
|
148188
148486
|
if (!runId || typeof runId !== "string") {
|
|
148189
148487
|
throw badRequest("Run ID is required");
|
|
148190
148488
|
}
|
|
148191
|
-
const
|
|
148192
|
-
if (!
|
|
148489
|
+
const run2 = missionStore.getValidatorRun(runId);
|
|
148490
|
+
if (!run2) {
|
|
148193
148491
|
throw notFound("Validator run not found");
|
|
148194
148492
|
}
|
|
148195
148493
|
const failures = missionStore.getFailuresForRun(runId);
|
|
148196
148494
|
res.json({
|
|
148197
|
-
...
|
|
148495
|
+
...run2,
|
|
148198
148496
|
failures
|
|
148199
148497
|
});
|
|
148200
148498
|
})
|
|
@@ -150353,7 +150651,7 @@ function createInsightsRouter(store) {
|
|
|
150353
150651
|
if (!taskStore) throw new ApiError(500, "Store context not available");
|
|
150354
150652
|
const rootDir = taskStore.getRootDir();
|
|
150355
150653
|
const controller = new AbortController();
|
|
150356
|
-
const
|
|
150654
|
+
const run2 = await executeInsightRunLifecycle({
|
|
150357
150655
|
store: insightStore,
|
|
150358
150656
|
projectId,
|
|
150359
150657
|
input: {
|
|
@@ -150364,19 +150662,19 @@ function createInsightsRouter(store) {
|
|
|
150364
150662
|
timeoutMs: typeof req.body.timeoutMs === "number" ? req.body.timeoutMs : 12e4,
|
|
150365
150663
|
maxAttempts: 2,
|
|
150366
150664
|
retryDelayMs: 250,
|
|
150367
|
-
executeAttempt: async ({ run:
|
|
150368
|
-
activeRunControllers.set(
|
|
150665
|
+
executeAttempt: async ({ run: run3, signal }) => {
|
|
150666
|
+
activeRunControllers.set(run3.id, controller);
|
|
150369
150667
|
return executeInsightAttempt({
|
|
150370
150668
|
rootDir,
|
|
150371
150669
|
projectId,
|
|
150372
|
-
runId:
|
|
150670
|
+
runId: run3.id,
|
|
150373
150671
|
signal,
|
|
150374
150672
|
insightStore
|
|
150375
150673
|
});
|
|
150376
150674
|
}
|
|
150377
150675
|
});
|
|
150378
|
-
activeRunControllers.delete(
|
|
150379
|
-
res.status(201).json(
|
|
150676
|
+
activeRunControllers.delete(run2.id);
|
|
150677
|
+
res.status(201).json(run2);
|
|
150380
150678
|
} catch (error) {
|
|
150381
150679
|
if (error instanceof InsightLifecycleError && error.code === "active_run_conflict") {
|
|
150382
150680
|
throw new ApiError(409, error.message);
|
|
@@ -150426,11 +150724,11 @@ function createInsightsRouter(store) {
|
|
|
150426
150724
|
try {
|
|
150427
150725
|
const id = String(req.params.id);
|
|
150428
150726
|
const store2 = getInsightStore();
|
|
150429
|
-
const
|
|
150430
|
-
if (!
|
|
150727
|
+
const run2 = store2.getRun(id);
|
|
150728
|
+
if (!run2) {
|
|
150431
150729
|
throw notFound(`Run not found: ${id}`);
|
|
150432
150730
|
}
|
|
150433
|
-
res.json(
|
|
150731
|
+
res.json(run2);
|
|
150434
150732
|
} catch (error) {
|
|
150435
150733
|
rethrowAsApiError5(error, "Failed to get run");
|
|
150436
150734
|
}
|
|
@@ -150439,8 +150737,8 @@ function createInsightsRouter(store) {
|
|
|
150439
150737
|
try {
|
|
150440
150738
|
const id = String(req.params.id);
|
|
150441
150739
|
const store2 = getInsightStore();
|
|
150442
|
-
const
|
|
150443
|
-
if (!
|
|
150740
|
+
const run2 = store2.getRun(id);
|
|
150741
|
+
if (!run2) throw notFound(`Run not found: ${id}`);
|
|
150444
150742
|
res.json({ events: store2.listRunEvents(id) });
|
|
150445
150743
|
} catch (error) {
|
|
150446
150744
|
rethrowAsApiError5(error, "Failed to list run events");
|
|
@@ -150450,34 +150748,34 @@ function createInsightsRouter(store) {
|
|
|
150450
150748
|
try {
|
|
150451
150749
|
const id = String(req.params.id);
|
|
150452
150750
|
const store2 = getInsightStore();
|
|
150453
|
-
const
|
|
150454
|
-
if (!
|
|
150455
|
-
if (!["pending", "running"].includes(
|
|
150751
|
+
const run2 = store2.getRun(id);
|
|
150752
|
+
if (!run2) throw notFound(`Run not found: ${id}`);
|
|
150753
|
+
if (!["pending", "running"].includes(run2.status)) {
|
|
150456
150754
|
throw new ApiError(409, `Run ${id} is already terminal`);
|
|
150457
150755
|
}
|
|
150458
150756
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
150459
|
-
store2.appendRunEvent(id, { type: "cancel_requested", status:
|
|
150757
|
+
store2.appendRunEvent(id, { type: "cancel_requested", status: run2.status, message: "Cancellation requested" });
|
|
150460
150758
|
const updated = store2.updateRun(id, {
|
|
150461
|
-
lifecycle: { ...
|
|
150759
|
+
lifecycle: { ...run2.lifecycle, cancellationRequestedAt: now }
|
|
150462
150760
|
});
|
|
150463
|
-
if (
|
|
150761
|
+
if (run2.status === "pending") {
|
|
150464
150762
|
const cancelled = store2.updateRun(id, {
|
|
150465
150763
|
status: "cancelled",
|
|
150466
150764
|
error: "Cancelled before execution started",
|
|
150467
150765
|
cancelledAt: now,
|
|
150468
150766
|
lifecycle: {
|
|
150469
|
-
...updated?.lifecycle ??
|
|
150767
|
+
...updated?.lifecycle ?? run2.lifecycle,
|
|
150470
150768
|
terminalReason: "cancelled",
|
|
150471
150769
|
terminalCause: "cancel_requested",
|
|
150472
150770
|
failureClass: "cancelled",
|
|
150473
150771
|
retryable: false
|
|
150474
150772
|
}
|
|
150475
150773
|
});
|
|
150476
|
-
res.json(cancelled ?? updated ??
|
|
150774
|
+
res.json(cancelled ?? updated ?? run2);
|
|
150477
150775
|
return;
|
|
150478
150776
|
}
|
|
150479
150777
|
activeRunControllers.get(id)?.abort(new DOMException("Run cancelled", "AbortError"));
|
|
150480
|
-
res.json(updated ??
|
|
150778
|
+
res.json(updated ?? run2);
|
|
150481
150779
|
} catch (error) {
|
|
150482
150780
|
rethrowAsApiError5(error, "Failed to cancel run");
|
|
150483
150781
|
}
|
|
@@ -150498,26 +150796,26 @@ function createInsightsRouter(store) {
|
|
|
150498
150796
|
if (!taskStore) throw new ApiError(500, "Store context not available");
|
|
150499
150797
|
const rootDir = taskStore.getRootDir();
|
|
150500
150798
|
const controller = new AbortController();
|
|
150501
|
-
const { run } = await retryInsightRunLifecycle({
|
|
150799
|
+
const { run: run2 } = await retryInsightRunLifecycle({
|
|
150502
150800
|
store: store2,
|
|
150503
150801
|
runId: id,
|
|
150504
150802
|
timeoutMs: typeof req.body?.timeoutMs === "number" ? req.body.timeoutMs : 12e4,
|
|
150505
150803
|
maxAttempts: 2,
|
|
150506
150804
|
retryDelayMs: 250,
|
|
150507
150805
|
signal: controller.signal,
|
|
150508
|
-
executeAttempt: async ({ run:
|
|
150509
|
-
activeRunControllers.set(
|
|
150806
|
+
executeAttempt: async ({ run: run3, signal }) => {
|
|
150807
|
+
activeRunControllers.set(run3.id, controller);
|
|
150510
150808
|
return executeInsightAttempt({
|
|
150511
150809
|
rootDir,
|
|
150512
150810
|
projectId: existing.projectId,
|
|
150513
|
-
runId:
|
|
150811
|
+
runId: run3.id,
|
|
150514
150812
|
signal,
|
|
150515
150813
|
insightStore: store2
|
|
150516
150814
|
});
|
|
150517
150815
|
}
|
|
150518
150816
|
});
|
|
150519
|
-
activeRunControllers.delete(
|
|
150520
|
-
res.status(201).json(
|
|
150817
|
+
activeRunControllers.delete(run2.id);
|
|
150818
|
+
res.status(201).json(run2);
|
|
150521
150819
|
} catch (error) {
|
|
150522
150820
|
if (error instanceof InsightLifecycleError && error.code === "not_retryable") {
|
|
150523
150821
|
throw new ApiError(409, error.message);
|
|
@@ -150658,32 +150956,33 @@ import { AsyncLocalStorage as AsyncLocalStorage4 } from "node:async_hooks";
|
|
|
150658
150956
|
function rethrowAsApiError6(error, fallback2 = "Internal server error") {
|
|
150659
150957
|
if (error instanceof ApiError) throw error;
|
|
150660
150958
|
if (error instanceof ResearchLifecycleError) {
|
|
150661
|
-
const status = error.code === "invalid_transition" || error.code === "active_run_conflict" ? 409 : 400;
|
|
150662
|
-
|
|
150959
|
+
const status = error.code === "invalid_transition" || error.code === "active_run_conflict" || error.code === "not_retryable" ? 409 : 400;
|
|
150960
|
+
const mappedCode = error.code === "not_retryable" ? "NON_RETRYABLE_PROVIDER_ERROR" : "INVALID_TRANSITION";
|
|
150961
|
+
throw new ApiError(status, error.message, { code: mappedCode, retryable: false });
|
|
150663
150962
|
}
|
|
150664
|
-
if (error instanceof Error) throw new ApiError(500, error.message);
|
|
150665
|
-
throw new ApiError(500, fallback2);
|
|
150963
|
+
if (error instanceof Error) throw new ApiError(500, error.message, { code: "INTERNAL_ERROR" });
|
|
150964
|
+
throw new ApiError(500, fallback2, { code: "INTERNAL_ERROR" });
|
|
150666
150965
|
}
|
|
150667
150966
|
function getProjectId2(req) {
|
|
150668
150967
|
if (typeof req.query.projectId === "string" && req.query.projectId.trim()) return req.query.projectId;
|
|
150669
150968
|
if (req.body && typeof req.body === "object" && typeof req.body.projectId === "string" && req.body.projectId.trim()) return req.body.projectId;
|
|
150670
150969
|
return void 0;
|
|
150671
150970
|
}
|
|
150672
|
-
function toRunListItem(
|
|
150971
|
+
function toRunListItem(run2) {
|
|
150673
150972
|
return {
|
|
150674
|
-
id:
|
|
150675
|
-
query:
|
|
150676
|
-
title:
|
|
150677
|
-
status:
|
|
150678
|
-
summary:
|
|
150679
|
-
createdAt:
|
|
150680
|
-
updatedAt:
|
|
150973
|
+
id: run2.id,
|
|
150974
|
+
query: run2.query,
|
|
150975
|
+
title: run2.topic || run2.query,
|
|
150976
|
+
status: run2.status,
|
|
150977
|
+
summary: run2.results?.summary,
|
|
150978
|
+
createdAt: run2.createdAt,
|
|
150979
|
+
updatedAt: run2.updatedAt
|
|
150681
150980
|
};
|
|
150682
150981
|
}
|
|
150683
|
-
function toRunDetail(
|
|
150982
|
+
function toRunDetail(run2) {
|
|
150684
150983
|
return {
|
|
150685
|
-
...
|
|
150686
|
-
title:
|
|
150984
|
+
...run2,
|
|
150985
|
+
title: run2.topic || run2.query
|
|
150687
150986
|
};
|
|
150688
150987
|
}
|
|
150689
150988
|
function getFindingId(finding, index2) {
|
|
@@ -150691,8 +150990,8 @@ function getFindingId(finding, index2) {
|
|
|
150691
150990
|
const explicitId = typeof maybeFinding.id === "string" ? maybeFinding.id.trim() : "";
|
|
150692
150991
|
return explicitId || `finding-${index2 + 1}`;
|
|
150693
150992
|
}
|
|
150694
|
-
function getFindingById(
|
|
150695
|
-
const findings =
|
|
150993
|
+
function getFindingById(run2, findingId) {
|
|
150994
|
+
const findings = run2.results?.findings ?? [];
|
|
150696
150995
|
for (const [index2, finding] of findings.entries()) {
|
|
150697
150996
|
if (getFindingId(finding, index2) === findingId) {
|
|
150698
150997
|
return { finding, findingId };
|
|
@@ -150700,24 +150999,24 @@ function getFindingById(run, findingId) {
|
|
|
150700
150999
|
}
|
|
150701
151000
|
return null;
|
|
150702
151001
|
}
|
|
150703
|
-
function buildFindingTaskSummary(
|
|
151002
|
+
function buildFindingTaskSummary(run2, finding) {
|
|
150704
151003
|
const heading = finding.heading?.trim() || "Research finding";
|
|
150705
151004
|
const content = finding.content?.trim() || "";
|
|
150706
151005
|
const firstSentence = content.split(/(?<=[.!?])\s+/)[0]?.trim() || content;
|
|
150707
|
-
const scope =
|
|
151006
|
+
const scope = run2.topic || run2.query;
|
|
150708
151007
|
return `${heading} \u2014 ${firstSentence || "Review cited research details."}
|
|
150709
151008
|
|
|
150710
151009
|
Context: ${scope}`;
|
|
150711
151010
|
}
|
|
150712
|
-
function buildFindingMarkdown(
|
|
151011
|
+
function buildFindingMarkdown(run2, findingId, finding) {
|
|
150713
151012
|
const citations = (finding.sources ?? []).map((source) => `- ${source}`).join("\n");
|
|
150714
|
-
const runSummary =
|
|
151013
|
+
const runSummary = run2.results?.summary?.trim();
|
|
150715
151014
|
return [
|
|
150716
151015
|
`# Research Finding`,
|
|
150717
151016
|
``,
|
|
150718
|
-
`- Run ID: ${
|
|
151017
|
+
`- Run ID: ${run2.id}`,
|
|
150719
151018
|
`- Finding ID: ${findingId}`,
|
|
150720
|
-
`- Query: ${
|
|
151019
|
+
`- Query: ${run2.query}`,
|
|
150721
151020
|
``,
|
|
150722
151021
|
`## ${finding.heading || "Finding"}`,
|
|
150723
151022
|
finding.content || "",
|
|
@@ -150788,7 +151087,7 @@ function createResearchRouter(store) {
|
|
|
150788
151087
|
if (typeof req.body?.query !== "string" || !req.body.query.trim()) {
|
|
150789
151088
|
throw badRequest("query is required");
|
|
150790
151089
|
}
|
|
150791
|
-
const
|
|
151090
|
+
const run2 = getStore4().createRun({
|
|
150792
151091
|
query: req.body.query,
|
|
150793
151092
|
topic: req.body.query,
|
|
150794
151093
|
providerConfig: {
|
|
@@ -150801,55 +151100,85 @@ function createResearchRouter(store) {
|
|
|
150801
151100
|
depth: req.body.depth
|
|
150802
151101
|
}
|
|
150803
151102
|
});
|
|
150804
|
-
res.status(201).json({ run: toRunDetail(
|
|
151103
|
+
res.status(201).json({ run: toRunDetail(run2), availability: DEFAULT_AVAILABILITY });
|
|
150805
151104
|
} catch (error) {
|
|
150806
151105
|
rethrowAsApiError6(error, "Failed to create research run");
|
|
150807
151106
|
}
|
|
150808
151107
|
});
|
|
150809
151108
|
router.get("/runs/:id", (req, res) => {
|
|
150810
151109
|
try {
|
|
150811
|
-
const
|
|
150812
|
-
if (!
|
|
150813
|
-
res.json({ run: toRunDetail(
|
|
151110
|
+
const run2 = getStore4().getRun(req.params.id);
|
|
151111
|
+
if (!run2) throw notFound(`Run not found: ${req.params.id}`);
|
|
151112
|
+
res.json({ run: toRunDetail(run2), availability: DEFAULT_AVAILABILITY });
|
|
150814
151113
|
} catch (error) {
|
|
150815
151114
|
rethrowAsApiError6(error, "Failed to get research run");
|
|
150816
151115
|
}
|
|
150817
151116
|
});
|
|
150818
151117
|
router.post("/runs/:id/cancel", (req, res) => {
|
|
150819
151118
|
try {
|
|
150820
|
-
const
|
|
150821
|
-
|
|
151119
|
+
const existing = getStore4().getRun(req.params.id);
|
|
151120
|
+
if (!existing) throw notFound(`Run not found: ${req.params.id}`);
|
|
151121
|
+
if (["completed", "failed", "cancelled", "timed_out", "retry_exhausted"].includes(existing.status)) {
|
|
151122
|
+
res.status(409).json({
|
|
151123
|
+
error: `Run ${req.params.id} cannot be cancelled from status ${existing.status}`,
|
|
151124
|
+
details: { code: "INVALID_TRANSITION", retryable: false }
|
|
151125
|
+
});
|
|
151126
|
+
return;
|
|
151127
|
+
}
|
|
151128
|
+
const run2 = getStore4().requestCancellation(req.params.id);
|
|
151129
|
+
res.json({ run: toRunDetail(run2) });
|
|
150822
151130
|
} catch (error) {
|
|
150823
151131
|
rethrowAsApiError6(error, "Failed to cancel research run");
|
|
150824
151132
|
}
|
|
150825
151133
|
});
|
|
150826
151134
|
router.post("/runs/:id/retry", (req, res) => {
|
|
150827
151135
|
try {
|
|
151136
|
+
const existing = getStore4().getRun(req.params.id);
|
|
151137
|
+
if (!existing) throw notFound(`Run not found: ${req.params.id}`);
|
|
150828
151138
|
const retryRun = getStore4().createRetryRun(req.params.id);
|
|
150829
151139
|
res.json({ run: toRunDetail(retryRun) });
|
|
150830
151140
|
} catch (error) {
|
|
151141
|
+
if (error instanceof ResearchLifecycleError && error.code === "not_retryable") {
|
|
151142
|
+
const run2 = getStore4().getRun(req.params.id);
|
|
151143
|
+
const exhausted = run2?.status === "retry_exhausted" || run2?.lifecycle?.errorCode === "RETRY_EXHAUSTED";
|
|
151144
|
+
res.status(409).json({
|
|
151145
|
+
error: error.message,
|
|
151146
|
+
details: {
|
|
151147
|
+
code: exhausted ? "RETRY_EXHAUSTED" : "NON_RETRYABLE_PROVIDER_ERROR",
|
|
151148
|
+
retryable: false
|
|
151149
|
+
}
|
|
151150
|
+
});
|
|
151151
|
+
return;
|
|
151152
|
+
}
|
|
151153
|
+
if (error instanceof ResearchLifecycleError && error.code === "invalid_transition") {
|
|
151154
|
+
res.status(409).json({
|
|
151155
|
+
error: error.message,
|
|
151156
|
+
details: { code: "INVALID_TRANSITION", retryable: false }
|
|
151157
|
+
});
|
|
151158
|
+
return;
|
|
151159
|
+
}
|
|
150831
151160
|
rethrowAsApiError6(error, "Failed to retry research run");
|
|
150832
151161
|
}
|
|
150833
151162
|
});
|
|
150834
151163
|
router.get("/runs/:id/export", (req, res) => {
|
|
150835
151164
|
try {
|
|
150836
|
-
const
|
|
150837
|
-
if (!
|
|
151165
|
+
const run2 = getStore4().getRun(req.params.id);
|
|
151166
|
+
if (!run2) throw notFound(`Run not found: ${req.params.id}`);
|
|
150838
151167
|
const format = String(req.query.format ?? "markdown");
|
|
150839
151168
|
if (format === "json") {
|
|
150840
|
-
res.json({ format, filename: `${
|
|
151169
|
+
res.json({ format, filename: `${run2.id}.json`, content: JSON.stringify(run2, null, 2) });
|
|
150841
151170
|
return;
|
|
150842
151171
|
}
|
|
150843
151172
|
if (format === "html") {
|
|
150844
|
-
const html = `<h1>${
|
|
150845
|
-
res.json({ format, filename: `${
|
|
151173
|
+
const html = `<h1>${run2.topic || run2.query}</h1><p>${run2.results?.summary ?? ""}</p>`;
|
|
151174
|
+
res.json({ format, filename: `${run2.id}.html`, content: html });
|
|
150846
151175
|
return;
|
|
150847
151176
|
}
|
|
150848
151177
|
if (format !== "markdown") throw badRequest(`Unsupported format: ${format}`);
|
|
150849
|
-
const markdown = `# ${
|
|
151178
|
+
const markdown = `# ${run2.topic || run2.query}
|
|
150850
151179
|
|
|
150851
|
-
${
|
|
150852
|
-
res.json({ format: "markdown", filename: `${
|
|
151180
|
+
${run2.results?.summary ?? ""}`;
|
|
151181
|
+
res.json({ format: "markdown", filename: `${run2.id}.md`, content: markdown });
|
|
150853
151182
|
} catch (error) {
|
|
150854
151183
|
rethrowAsApiError6(error, "Failed to export research run");
|
|
150855
151184
|
}
|
|
@@ -150858,9 +151187,9 @@ ${run.results?.summary ?? ""}`;
|
|
|
150858
151187
|
try {
|
|
150859
151188
|
const scopedStore = requestContext.getStore();
|
|
150860
151189
|
if (!scopedStore) throw new ApiError(500, "Task store context unavailable");
|
|
150861
|
-
const
|
|
150862
|
-
if (!
|
|
150863
|
-
const found = getFindingById(
|
|
151190
|
+
const run2 = getStore4().getRun(req.params.runId);
|
|
151191
|
+
if (!run2) throw notFound(`Run not found: ${req.params.runId}`);
|
|
151192
|
+
const found = getFindingById(run2, req.params.findingId);
|
|
150864
151193
|
if (!found) throw notFound(`Finding not found: ${req.params.findingId}`);
|
|
150865
151194
|
let documentKey;
|
|
150866
151195
|
try {
|
|
@@ -150868,8 +151197,8 @@ ${run.results?.summary ?? ""}`;
|
|
|
150868
151197
|
} catch {
|
|
150869
151198
|
throw badRequest("Invalid run id for research document key");
|
|
150870
151199
|
}
|
|
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(
|
|
151200
|
+
const title = typeof req.body?.title === "string" && req.body.title.trim() ? req.body.title.trim() : `Research: ${found.finding.heading || run2.topic || run2.query}`;
|
|
151201
|
+
const description = typeof req.body?.description === "string" && req.body.description.trim() ? req.body.description.trim() : buildFindingTaskSummary(run2, found.finding);
|
|
150873
151202
|
const priority = req.body?.priority;
|
|
150874
151203
|
if (priority !== void 0 && !["low", "normal", "high", "urgent"].includes(priority)) {
|
|
150875
151204
|
throw badRequest("priority must be one of: low, normal, high, urgent");
|
|
@@ -150881,9 +151210,9 @@ ${run.results?.summary ?? ""}`;
|
|
|
150881
151210
|
priority,
|
|
150882
151211
|
source: {
|
|
150883
151212
|
sourceType: "research",
|
|
150884
|
-
sourceRunId:
|
|
151213
|
+
sourceRunId: run2.id,
|
|
150885
151214
|
sourceMetadata: {
|
|
150886
|
-
runId:
|
|
151215
|
+
runId: run2.id,
|
|
150887
151216
|
findingId: found.findingId,
|
|
150888
151217
|
findingLabel: found.finding.heading,
|
|
150889
151218
|
documentKey
|
|
@@ -150891,13 +151220,13 @@ ${run.results?.summary ?? ""}`;
|
|
|
150891
151220
|
}
|
|
150892
151221
|
};
|
|
150893
151222
|
const task = await scopedStore.createTask(taskInput);
|
|
150894
|
-
const markdown = buildFindingMarkdown(
|
|
151223
|
+
const markdown = buildFindingMarkdown(run2, found.findingId, found.finding);
|
|
150895
151224
|
await scopedStore.upsertTaskDocument(task.id, {
|
|
150896
151225
|
key: documentKey,
|
|
150897
151226
|
content: markdown,
|
|
150898
151227
|
author: "research",
|
|
150899
151228
|
metadata: {
|
|
150900
|
-
runId:
|
|
151229
|
+
runId: run2.id,
|
|
150901
151230
|
findingId: found.findingId,
|
|
150902
151231
|
findingLabel: found.finding.heading
|
|
150903
151232
|
}
|
|
@@ -150905,7 +151234,7 @@ ${run.results?.summary ?? ""}`;
|
|
|
150905
151234
|
if (typeof scopedStore.appendAgentLog === "function") {
|
|
150906
151235
|
await scopedStore.appendAgentLog(
|
|
150907
151236
|
task.id,
|
|
150908
|
-
`Task created from research finding ${found.findingId} in run ${
|
|
151237
|
+
`Task created from research finding ${found.findingId} in run ${run2.id}`,
|
|
150909
151238
|
"text",
|
|
150910
151239
|
"research-task-integration",
|
|
150911
151240
|
"executor"
|
|
@@ -150913,7 +151242,7 @@ ${run.results?.summary ?? ""}`;
|
|
|
150913
151242
|
}
|
|
150914
151243
|
let attachmentFilename;
|
|
150915
151244
|
if (attachExport) {
|
|
150916
|
-
const filename = `${
|
|
151245
|
+
const filename = `${run2.id}-${found.findingId}.md`;
|
|
150917
151246
|
const existing = await scopedStore.getTask(task.id);
|
|
150918
151247
|
if (!existing.attachments?.some((attachment) => attachment.originalName === filename)) {
|
|
150919
151248
|
attachmentFilename = await addFindingAttachment(scopedStore, task.id, filename, markdown);
|
|
@@ -150934,9 +151263,9 @@ ${run.results?.summary ?? ""}`;
|
|
|
150934
151263
|
try {
|
|
150935
151264
|
const scopedStore = requestContext.getStore();
|
|
150936
151265
|
if (!scopedStore) throw new ApiError(500, "Task store context unavailable");
|
|
150937
|
-
const
|
|
150938
|
-
if (!
|
|
150939
|
-
const found = getFindingById(
|
|
151266
|
+
const run2 = getStore4().getRun(req.params.runId);
|
|
151267
|
+
if (!run2) throw notFound(`Run not found: ${req.params.runId}`);
|
|
151268
|
+
const found = getFindingById(run2, req.params.findingId);
|
|
150940
151269
|
if (!found) throw notFound(`Finding not found: ${req.params.findingId}`);
|
|
150941
151270
|
const task = await scopedStore.getTask(req.params.taskId);
|
|
150942
151271
|
if (!task) throw notFound(`Task not found: ${req.params.taskId}`);
|
|
@@ -150947,13 +151276,13 @@ ${run.results?.summary ?? ""}`;
|
|
|
150947
151276
|
} catch {
|
|
150948
151277
|
throw badRequest("Invalid run id for research document key");
|
|
150949
151278
|
}
|
|
150950
|
-
const markdown = buildFindingMarkdown(
|
|
151279
|
+
const markdown = buildFindingMarkdown(run2, found.findingId, found.finding);
|
|
150951
151280
|
const document2 = await scopedStore.upsertTaskDocument(task.id, {
|
|
150952
151281
|
key: documentKey,
|
|
150953
151282
|
content: markdown,
|
|
150954
151283
|
author: "research",
|
|
150955
151284
|
metadata: {
|
|
150956
|
-
runId:
|
|
151285
|
+
runId: run2.id,
|
|
150957
151286
|
findingId: found.findingId,
|
|
150958
151287
|
findingLabel: found.finding.heading
|
|
150959
151288
|
}
|
|
@@ -150961,7 +151290,7 @@ ${run.results?.summary ?? ""}`;
|
|
|
150961
151290
|
const attachExport = validateAttachExport(req.body?.attachExport);
|
|
150962
151291
|
let attachmentFilename;
|
|
150963
151292
|
if (attachExport) {
|
|
150964
|
-
const filename = `${
|
|
151293
|
+
const filename = `${run2.id}-${found.findingId}.md`;
|
|
150965
151294
|
if (!task.attachments?.some((attachment) => attachment.originalName === filename)) {
|
|
150966
151295
|
attachmentFilename = await addFindingAttachment(scopedStore, task.id, filename, markdown);
|
|
150967
151296
|
}
|
|
@@ -150969,7 +151298,7 @@ ${run.results?.summary ?? ""}`;
|
|
|
150969
151298
|
if (typeof scopedStore.appendAgentLog === "function") {
|
|
150970
151299
|
await scopedStore.appendAgentLog(
|
|
150971
151300
|
task.id,
|
|
150972
|
-
`Task enriched from research finding ${found.findingId} in run ${
|
|
151301
|
+
`Task enriched from research finding ${found.findingId} in run ${run2.id}`,
|
|
150973
151302
|
"text",
|
|
150974
151303
|
"research-task-integration",
|
|
150975
151304
|
"executor"
|
|
@@ -151046,9 +151375,9 @@ ${run.results?.summary ?? ""}`;
|
|
|
151046
151375
|
const status = req.body?.status;
|
|
151047
151376
|
if (!status || !RESEARCH_RUN_STATUSES.includes(status)) throw badRequest(`Invalid status: ${String(status)}`);
|
|
151048
151377
|
getStore4().updateStatus(req.params.id, status, req.body?.extra);
|
|
151049
|
-
const
|
|
151050
|
-
if (!
|
|
151051
|
-
res.json(
|
|
151378
|
+
const run2 = getStore4().getRun(req.params.id);
|
|
151379
|
+
if (!run2) throw notFound(`Run not found: ${req.params.id}`);
|
|
151380
|
+
res.json(run2);
|
|
151052
151381
|
} catch (error) {
|
|
151053
151382
|
rethrowAsApiError6(error, "Failed to update research status");
|
|
151054
151383
|
}
|
|
@@ -151107,7 +151436,8 @@ var init_research_routes = __esm({
|
|
|
151107
151436
|
DEFAULT_AVAILABILITY = {
|
|
151108
151437
|
available: true,
|
|
151109
151438
|
supportedProviders: ["web-search", "page-fetch", "github", "local-docs", "llm-synthesis"],
|
|
151110
|
-
supportedExportFormats: ["markdown", "json", "html"]
|
|
151439
|
+
supportedExportFormats: ["markdown", "json", "html"],
|
|
151440
|
+
setupInstructions: "If research fails to start, check Settings \u2192 Models and Authentication for provider enablement and credentials."
|
|
151111
151441
|
};
|
|
151112
151442
|
}
|
|
151113
151443
|
});
|
|
@@ -153027,32 +153357,32 @@ function logEntryToTimelineEntry(entry) {
|
|
|
153027
153357
|
log: entry
|
|
153028
153358
|
};
|
|
153029
153359
|
}
|
|
153030
|
-
function runExcerptToAgentLogs(
|
|
153360
|
+
function runExcerptToAgentLogs(run2) {
|
|
153031
153361
|
const entries = [];
|
|
153032
|
-
const taskId = typeof
|
|
153033
|
-
if (
|
|
153362
|
+
const taskId = typeof run2.contextSnapshot?.taskId === "string" ? run2.contextSnapshot.taskId : "agent-run";
|
|
153363
|
+
if (run2.stdoutExcerpt?.trim()) {
|
|
153034
153364
|
entries.push({
|
|
153035
|
-
timestamp:
|
|
153365
|
+
timestamp: run2.endedAt ?? run2.startedAt,
|
|
153036
153366
|
taskId,
|
|
153037
153367
|
type: "text",
|
|
153038
|
-
text:
|
|
153368
|
+
text: run2.stdoutExcerpt
|
|
153039
153369
|
});
|
|
153040
153370
|
}
|
|
153041
|
-
if (
|
|
153371
|
+
if (run2.stderrExcerpt?.trim()) {
|
|
153042
153372
|
entries.push({
|
|
153043
|
-
timestamp:
|
|
153373
|
+
timestamp: run2.endedAt ?? run2.startedAt,
|
|
153044
153374
|
taskId,
|
|
153045
153375
|
type: "tool_error",
|
|
153046
153376
|
text: "stderr",
|
|
153047
|
-
detail:
|
|
153377
|
+
detail: run2.stderrExcerpt
|
|
153048
153378
|
});
|
|
153049
153379
|
}
|
|
153050
|
-
if (
|
|
153380
|
+
if (run2.resultJson && Object.keys(run2.resultJson).length > 0 && entries.length === 0) {
|
|
153051
153381
|
entries.push({
|
|
153052
|
-
timestamp:
|
|
153382
|
+
timestamp: run2.endedAt ?? run2.startedAt,
|
|
153053
153383
|
taskId,
|
|
153054
153384
|
type: "text",
|
|
153055
|
-
text: JSON.stringify(
|
|
153385
|
+
text: JSON.stringify(run2.resultJson, null, 2)
|
|
153056
153386
|
});
|
|
153057
153387
|
}
|
|
153058
153388
|
return entries;
|
|
@@ -153183,11 +153513,26 @@ function createApiRoutes(store, options) {
|
|
|
153183
153513
|
return true;
|
|
153184
153514
|
}
|
|
153185
153515
|
}
|
|
153516
|
+
function resolveHeartbeatMonitor(scopedStore) {
|
|
153517
|
+
const engineManager = options?.engineManager;
|
|
153518
|
+
if (!engineManager) return void 0;
|
|
153519
|
+
try {
|
|
153520
|
+
const storeRoot = resolve27(scopedStore.getRootDir());
|
|
153521
|
+
for (const engine of engineManager.getAllEngines().values()) {
|
|
153522
|
+
if (resolve27(engine.getWorkingDirectory()) === storeRoot) {
|
|
153523
|
+
return engine.getHeartbeatMonitor() ?? void 0;
|
|
153524
|
+
}
|
|
153525
|
+
}
|
|
153526
|
+
} catch {
|
|
153527
|
+
}
|
|
153528
|
+
return void 0;
|
|
153529
|
+
}
|
|
153186
153530
|
const triggerCommentWakeForAssignedAgent = async (scopedStore, task, wake) => {
|
|
153187
153531
|
if (!hasHeartbeatExecutor || !heartbeatMonitor || !task.assignedAgentId) {
|
|
153188
153532
|
return;
|
|
153189
153533
|
}
|
|
153190
|
-
|
|
153534
|
+
const resolvedMonitor = isHeartbeatMonitorForProject(scopedStore) ? heartbeatMonitor : resolveHeartbeatMonitor(scopedStore);
|
|
153535
|
+
if (!resolvedMonitor) {
|
|
153191
153536
|
return;
|
|
153192
153537
|
}
|
|
153193
153538
|
const { AgentStore: AgentStore2 } = await Promise.resolve().then(() => (init_src(), src_exports));
|
|
@@ -153213,7 +153558,7 @@ function createApiRoutes(store, options) {
|
|
|
153213
153558
|
...triggeringCommentIds?.length ? { triggeringCommentIds } : {},
|
|
153214
153559
|
triggeringCommentType: wake.triggeringCommentType
|
|
153215
153560
|
};
|
|
153216
|
-
await
|
|
153561
|
+
await resolvedMonitor.executeHeartbeat({
|
|
153217
153562
|
agentId: assignedAgent.id,
|
|
153218
153563
|
source: "on_demand",
|
|
153219
153564
|
triggerDetail: wake.triggerDetail,
|
|
@@ -154643,6 +154988,7 @@ Description: ${step.description}`
|
|
|
154643
154988
|
hasHeartbeatExecutor,
|
|
154644
154989
|
heartbeatMonitor,
|
|
154645
154990
|
isHeartbeatMonitorForProject,
|
|
154991
|
+
resolveHeartbeatMonitor,
|
|
154646
154992
|
runExcerptToAgentLogs,
|
|
154647
154993
|
parseRunAuditFilters,
|
|
154648
154994
|
normalizeRunAuditEvent,
|
|
@@ -155979,39 +156325,39 @@ data: ${JSON.stringify(stripTaskEventHeavyFields(result))}
|
|
|
155979
156325
|
|
|
155980
156326
|
`);
|
|
155981
156327
|
};
|
|
155982
|
-
const onResearchRunCreated = (
|
|
156328
|
+
const onResearchRunCreated = (run2) => {
|
|
155983
156329
|
send(`event: research:run:created
|
|
155984
|
-
data: ${JSON.stringify(
|
|
156330
|
+
data: ${JSON.stringify(run2)}
|
|
155985
156331
|
|
|
155986
156332
|
`);
|
|
155987
156333
|
};
|
|
155988
|
-
const onResearchRunUpdated = (
|
|
156334
|
+
const onResearchRunUpdated = (run2) => {
|
|
155989
156335
|
send(`event: research:run:updated
|
|
155990
|
-
data: ${JSON.stringify(
|
|
156336
|
+
data: ${JSON.stringify(run2)}
|
|
155991
156337
|
|
|
155992
156338
|
`);
|
|
155993
156339
|
};
|
|
155994
|
-
const onResearchRunCompleted = (
|
|
156340
|
+
const onResearchRunCompleted = (run2) => {
|
|
155995
156341
|
send(`event: research:run:completed
|
|
155996
|
-
data: ${JSON.stringify(
|
|
156342
|
+
data: ${JSON.stringify(run2)}
|
|
155997
156343
|
|
|
155998
156344
|
`);
|
|
155999
156345
|
};
|
|
156000
|
-
const onResearchRunFailed = (
|
|
156346
|
+
const onResearchRunFailed = (run2) => {
|
|
156001
156347
|
send(`event: research:run:failed
|
|
156002
|
-
data: ${JSON.stringify(
|
|
156348
|
+
data: ${JSON.stringify(run2)}
|
|
156003
156349
|
|
|
156004
156350
|
`);
|
|
156005
156351
|
};
|
|
156006
|
-
const onResearchRunCancelled = (
|
|
156352
|
+
const onResearchRunCancelled = (run2) => {
|
|
156007
156353
|
send(`event: research:run:cancelled
|
|
156008
|
-
data: ${JSON.stringify(
|
|
156354
|
+
data: ${JSON.stringify(run2)}
|
|
156009
156355
|
|
|
156010
156356
|
`);
|
|
156011
156357
|
};
|
|
156012
|
-
const onResearchRunTimedOut = (
|
|
156358
|
+
const onResearchRunTimedOut = (run2) => {
|
|
156013
156359
|
send(`event: research:run:timed_out
|
|
156014
|
-
data: ${JSON.stringify(
|
|
156360
|
+
data: ${JSON.stringify(run2)}
|
|
156015
156361
|
|
|
156016
156362
|
`);
|
|
156017
156363
|
};
|
|
@@ -156141,15 +156487,15 @@ data: ${JSON.stringify(data)}
|
|
|
156141
156487
|
|
|
156142
156488
|
`);
|
|
156143
156489
|
};
|
|
156144
|
-
const onValidatorRunStarted = (
|
|
156490
|
+
const onValidatorRunStarted = (run2) => {
|
|
156145
156491
|
send(`event: validator-run:started
|
|
156146
|
-
data: ${JSON.stringify(
|
|
156492
|
+
data: ${JSON.stringify(run2)}
|
|
156147
156493
|
|
|
156148
156494
|
`);
|
|
156149
156495
|
};
|
|
156150
|
-
const onValidatorRunCompleted = (
|
|
156496
|
+
const onValidatorRunCompleted = (run2) => {
|
|
156151
156497
|
send(`event: validator-run:completed
|
|
156152
|
-
data: ${JSON.stringify(
|
|
156498
|
+
data: ${JSON.stringify(run2)}
|
|
156153
156499
|
|
|
156154
156500
|
`);
|
|
156155
156501
|
};
|
|
@@ -161973,7 +162319,8 @@ function createSkillsAdapter(options) {
|
|
|
161973
162319
|
}
|
|
161974
162320
|
const discoveredSkills = [];
|
|
161975
162321
|
for (const resource of skillResources) {
|
|
161976
|
-
const
|
|
162322
|
+
const relPath = relative13(resource.metadata.baseDir ?? "", resource.path).replaceAll("\\", "/");
|
|
162323
|
+
const skillRelativePath = relPath.startsWith("skills/") ? relPath : `skills/${relPath}`;
|
|
161977
162324
|
const skillId = computeSkillId(resource.metadata.source, skillRelativePath);
|
|
161978
162325
|
const skillName = extractSkillName(skillRelativePath, resource.metadata.source);
|
|
161979
162326
|
discoveredSkills.push({
|
|
@@ -165200,22 +165547,22 @@ function runStatusColor(status) {
|
|
|
165200
165547
|
return "gray";
|
|
165201
165548
|
}
|
|
165202
165549
|
}
|
|
165203
|
-
function getRunLogLines(
|
|
165204
|
-
if (Array.isArray(
|
|
165550
|
+
function getRunLogLines(run2) {
|
|
165551
|
+
if (Array.isArray(run2.logs) && run2.logs.length > 0) return run2.logs;
|
|
165205
165552
|
const lines = [];
|
|
165206
|
-
if (
|
|
165207
|
-
if (
|
|
165208
|
-
if (
|
|
165553
|
+
if (run2.triggerDetail) lines.push(`trigger: ${run2.triggerDetail}`);
|
|
165554
|
+
if (run2.invocationSource) lines.push(`source: ${run2.invocationSource}`);
|
|
165555
|
+
if (run2.stdoutExcerpt) {
|
|
165209
165556
|
lines.push("stdout:");
|
|
165210
|
-
lines.push(...
|
|
165557
|
+
lines.push(...run2.stdoutExcerpt.split(/\r?\n/).filter((line) => line.length > 0));
|
|
165211
165558
|
}
|
|
165212
|
-
if (
|
|
165559
|
+
if (run2.stderrExcerpt) {
|
|
165213
165560
|
lines.push("stderr:");
|
|
165214
|
-
lines.push(...
|
|
165561
|
+
lines.push(...run2.stderrExcerpt.split(/\r?\n/).filter((line) => line.length > 0));
|
|
165215
165562
|
}
|
|
165216
|
-
if (
|
|
165563
|
+
if (run2.resultJson) {
|
|
165217
165564
|
lines.push("result:");
|
|
165218
|
-
lines.push(JSON.stringify(
|
|
165565
|
+
lines.push(JSON.stringify(run2.resultJson));
|
|
165219
165566
|
}
|
|
165220
165567
|
return lines.length > 0 ? lines : ["No logs captured for this run."];
|
|
165221
165568
|
}
|
|
@@ -165470,12 +165817,12 @@ function AgentsView({ state }) {
|
|
|
165470
165817
|
recentRuns.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
165471
165818
|
/* @__PURE__ */ jsx(Box, { height: 1 }),
|
|
165472
165819
|
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "Run history (latest first):" }),
|
|
165473
|
-
recentRuns.slice(0, 5).map((
|
|
165820
|
+
recentRuns.slice(0, 5).map((run2, i) => /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, marginLeft: 1, children: [
|
|
165474
165821
|
/* @__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
|
-
] },
|
|
165822
|
+
/* @__PURE__ */ jsx(Text, { color: runStatusColor(run2.status), children: formatRunStatusLabel(run2.status) }),
|
|
165823
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: run2.startedAt.slice(11, 19) }),
|
|
165824
|
+
run2.triggerDetail && /* @__PURE__ */ jsx(Text, { dimColor: true, wrap: "truncate-end", children: run2.triggerDetail })
|
|
165825
|
+
] }, run2.id)),
|
|
165479
165826
|
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "[Enter] open logs" })
|
|
165480
165827
|
] })
|
|
165481
165828
|
] })
|
|
@@ -176354,12 +176701,27 @@ async function getStore3(projectName) {
|
|
|
176354
176701
|
await store.init();
|
|
176355
176702
|
return store;
|
|
176356
176703
|
}
|
|
176704
|
+
function hasProviderCredentials(settings, providerId) {
|
|
176705
|
+
if (!providerId) return false;
|
|
176706
|
+
if (providerId === "searxng") return Boolean(settings.researchSearxngUrl);
|
|
176707
|
+
if (providerId === "brave") return Boolean(settings.researchBraveApiKey);
|
|
176708
|
+
if (providerId === "google") return Boolean(settings.researchGoogleSearchApiKey && settings.researchGoogleSearchCx);
|
|
176709
|
+
if (providerId === "tavily") return Boolean(settings.researchTavilyApiKey);
|
|
176710
|
+
return false;
|
|
176711
|
+
}
|
|
176357
176712
|
async function getResearchRuntime(store) {
|
|
176358
176713
|
const settings = await store.getSettings();
|
|
176359
176714
|
const resolved = resolveResearchSettings(settings);
|
|
176360
176715
|
if (!resolved.enabled) {
|
|
176361
176716
|
throw new Error("feature-disabled: Research is disabled in settings.");
|
|
176362
176717
|
}
|
|
176718
|
+
const configuredProvider = resolved.searchProvider ?? settings.researchWebSearchProvider;
|
|
176719
|
+
if (!configuredProvider) {
|
|
176720
|
+
throw new Error("provider-unavailable: Research providers are not configured. Add provider credentials in settings.");
|
|
176721
|
+
}
|
|
176722
|
+
if (!hasProviderCredentials(settings, configuredProvider)) {
|
|
176723
|
+
throw new Error(`missing-credentials: ${configuredProvider} credentials are missing. Configure Authentication and Research defaults in settings.`);
|
|
176724
|
+
}
|
|
176363
176725
|
const registry = new ResearchProviderRegistry(settings, process.cwd());
|
|
176364
176726
|
const availableProviderTypes = registry.getAvailableProviders();
|
|
176365
176727
|
if (availableProviderTypes.length === 0) {
|
|
@@ -176375,17 +176737,17 @@ async function getResearchRuntime(store) {
|
|
|
176375
176737
|
});
|
|
176376
176738
|
return { orchestrator, settings, resolved, availableProviderTypes };
|
|
176377
176739
|
}
|
|
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 (
|
|
176740
|
+
function printRun(run2) {
|
|
176741
|
+
console.log(`Run: ${run2.id}`);
|
|
176742
|
+
console.log(`Status: ${run2.status}`);
|
|
176743
|
+
console.log(`Query: ${run2.query}`);
|
|
176744
|
+
console.log(`Created: ${run2.createdAt}`);
|
|
176745
|
+
console.log(`Updated: ${run2.updatedAt}`);
|
|
176746
|
+
if (run2.startedAt) console.log(`Started: ${run2.startedAt}`);
|
|
176747
|
+
if (run2.completedAt) console.log(`Completed: ${run2.completedAt}`);
|
|
176748
|
+
if (run2.cancelledAt) console.log(`Cancelled: ${run2.cancelledAt}`);
|
|
176749
|
+
if (run2.results?.summary) console.log(`Summary: ${run2.results.summary}`);
|
|
176750
|
+
if (run2.error) console.log(`Error: ${run2.error}`);
|
|
176389
176751
|
}
|
|
176390
176752
|
function jsonOut(payload) {
|
|
176391
176753
|
console.log(JSON.stringify(payload, null, 2));
|
|
@@ -176408,12 +176770,12 @@ async function runResearchCreate(options) {
|
|
|
176408
176770
|
});
|
|
176409
176771
|
const runPromise = orchestrator.startRun(runId, options.query);
|
|
176410
176772
|
if (!options.waitForCompletion) {
|
|
176411
|
-
const
|
|
176773
|
+
const run2 = store.getResearchStore().getRun(runId);
|
|
176412
176774
|
if (options.json) {
|
|
176413
|
-
jsonOut(
|
|
176775
|
+
jsonOut(run2);
|
|
176414
176776
|
} else {
|
|
176415
176777
|
console.log(`Created research run ${runId}.`);
|
|
176416
|
-
if (
|
|
176778
|
+
if (run2) printRun(run2);
|
|
176417
176779
|
}
|
|
176418
176780
|
return;
|
|
176419
176781
|
}
|
|
@@ -176461,8 +176823,8 @@ async function runResearchList(options = {}) {
|
|
|
176461
176823
|
console.log("No research runs found.");
|
|
176462
176824
|
return;
|
|
176463
176825
|
}
|
|
176464
|
-
for (const
|
|
176465
|
-
console.log(`${
|
|
176826
|
+
for (const run2 of runs) {
|
|
176827
|
+
console.log(`${run2.id} [${run2.status}] ${run2.query}`);
|
|
176466
176828
|
}
|
|
176467
176829
|
} catch (error) {
|
|
176468
176830
|
handleError(error);
|
|
@@ -176471,46 +176833,46 @@ async function runResearchList(options = {}) {
|
|
|
176471
176833
|
async function runResearchShow(runId, options = {}) {
|
|
176472
176834
|
try {
|
|
176473
176835
|
const store = await getStore3(options.projectName);
|
|
176474
|
-
const
|
|
176475
|
-
if (!
|
|
176836
|
+
const run2 = store.getResearchStore().getRun(runId);
|
|
176837
|
+
if (!run2) throw new Error(`Research run not found: ${runId}`);
|
|
176476
176838
|
if (options.json) {
|
|
176477
|
-
jsonOut(
|
|
176839
|
+
jsonOut(run2);
|
|
176478
176840
|
return;
|
|
176479
176841
|
}
|
|
176480
|
-
printRun(
|
|
176842
|
+
printRun(run2);
|
|
176481
176843
|
} catch (error) {
|
|
176482
176844
|
handleError(error);
|
|
176483
176845
|
}
|
|
176484
176846
|
}
|
|
176485
|
-
function renderMarkdown(
|
|
176486
|
-
const citations =
|
|
176847
|
+
function renderMarkdown(run2) {
|
|
176848
|
+
const citations = run2.results?.citations?.length ? `
|
|
176487
176849
|
## Citations
|
|
176488
|
-
${
|
|
176489
|
-
return `# ${
|
|
176850
|
+
${run2.results.citations.map((citation) => `- ${citation}`).join("\n")}` : "";
|
|
176851
|
+
return `# ${run2.topic || run2.query}
|
|
176490
176852
|
|
|
176491
176853
|
## Summary
|
|
176492
|
-
${
|
|
176854
|
+
${run2.results?.summary ?? ""}${citations}
|
|
176493
176855
|
`;
|
|
176494
176856
|
}
|
|
176495
176857
|
async function runResearchExport(options) {
|
|
176496
176858
|
try {
|
|
176497
176859
|
const store = await getStore3(options.projectName);
|
|
176498
|
-
const
|
|
176499
|
-
if (!
|
|
176860
|
+
const run2 = store.getResearchStore().getRun(options.runId);
|
|
176861
|
+
if (!run2) throw new Error(`Research run not found: ${options.runId}`);
|
|
176500
176862
|
const format = options.format ?? "markdown";
|
|
176501
176863
|
if (!RESEARCH_EXPORT_FORMATS.includes(format)) {
|
|
176502
176864
|
throw new Error(`Unsupported export format: ${format}`);
|
|
176503
176865
|
}
|
|
176504
|
-
const content = format === "json" ? JSON.stringify(
|
|
176866
|
+
const content = format === "json" ? JSON.stringify(run2, null, 2) : renderMarkdown(run2);
|
|
176505
176867
|
const ext = format === "json" ? "json" : "md";
|
|
176506
|
-
const outputPath = options.output ? resolve42(options.output) : join69(process.cwd(), `research-${
|
|
176868
|
+
const outputPath = options.output ? resolve42(options.output) : join69(process.cwd(), `research-${run2.id.toLowerCase()}.${ext}`);
|
|
176507
176869
|
await writeFile19(outputPath, content, "utf8");
|
|
176508
|
-
store.getResearchStore().createExport(
|
|
176870
|
+
store.getResearchStore().createExport(run2.id, format, content);
|
|
176509
176871
|
if (options.json) {
|
|
176510
|
-
jsonOut({ runId:
|
|
176872
|
+
jsonOut({ runId: run2.id, format, outputPath, bytes: Buffer.byteLength(content, "utf8") });
|
|
176511
176873
|
return;
|
|
176512
176874
|
}
|
|
176513
|
-
console.log(`Exported ${
|
|
176875
|
+
console.log(`Exported ${run2.id} (${format}) to ${outputPath}`);
|
|
176514
176876
|
} catch (error) {
|
|
176515
176877
|
handleError(error);
|
|
176516
176878
|
}
|
|
@@ -176518,16 +176880,19 @@ async function runResearchExport(options) {
|
|
|
176518
176880
|
async function runResearchCancel(runId, options = {}) {
|
|
176519
176881
|
try {
|
|
176520
176882
|
const store = await getStore3(options.projectName);
|
|
176521
|
-
const
|
|
176522
|
-
if (!
|
|
176883
|
+
const run2 = store.getResearchStore().getRun(runId);
|
|
176884
|
+
if (!run2) throw new Error(`Research run not found: ${runId}`);
|
|
176885
|
+
if (!["queued", "running", "cancelling", "retry_waiting"].includes(run2.status)) {
|
|
176886
|
+
throw new Error(`invalid-transition: Run ${runId} cannot be cancelled from status ${run2.status}.`);
|
|
176887
|
+
}
|
|
176523
176888
|
const { orchestrator } = await getResearchRuntime(store);
|
|
176524
176889
|
const cancelled = orchestrator.cancelRun(runId);
|
|
176525
176890
|
if (options.json) {
|
|
176526
|
-
jsonOut({ cancelled, run });
|
|
176891
|
+
jsonOut({ cancelled, run: run2 });
|
|
176527
176892
|
return;
|
|
176528
176893
|
}
|
|
176529
176894
|
console.log(cancelled ? `Cancellation requested for ${runId}.` : `Run ${runId} is not active.`);
|
|
176530
|
-
printRun(
|
|
176895
|
+
printRun(run2);
|
|
176531
176896
|
} catch (error) {
|
|
176532
176897
|
handleError(error);
|
|
176533
176898
|
}
|
|
@@ -176537,15 +176902,21 @@ async function runResearchRetry(runId, options = {}) {
|
|
|
176537
176902
|
const store = await getStore3(options.projectName);
|
|
176538
176903
|
const existing = store.getResearchStore().getRun(runId);
|
|
176539
176904
|
if (!existing) throw new Error(`Research run not found: ${runId}`);
|
|
176905
|
+
if (existing.status === "retry_exhausted" || existing.lifecycle?.errorCode === "RETRY_EXHAUSTED") {
|
|
176906
|
+
throw new Error(`retry-exhausted: Run ${runId} has exhausted retry attempts.`);
|
|
176907
|
+
}
|
|
176908
|
+
if (existing.lifecycle?.retryable === false) {
|
|
176909
|
+
throw new Error(`non-retryable-provider-error: Run ${runId} is marked non-retryable.`);
|
|
176910
|
+
}
|
|
176540
176911
|
const { orchestrator } = await getResearchRuntime(store);
|
|
176541
176912
|
const newRunId = orchestrator.retryRun(runId);
|
|
176542
|
-
const
|
|
176913
|
+
const run2 = store.getResearchStore().getRun(newRunId);
|
|
176543
176914
|
if (options.json) {
|
|
176544
|
-
jsonOut({ retryOf: runId, run });
|
|
176915
|
+
jsonOut({ retryOf: runId, run: run2 });
|
|
176545
176916
|
return;
|
|
176546
176917
|
}
|
|
176547
176918
|
console.log(`Created retry run ${newRunId} from ${runId}.`);
|
|
176548
|
-
if (
|
|
176919
|
+
if (run2) printRun(run2);
|
|
176549
176920
|
} catch (error) {
|
|
176550
176921
|
handleError(error);
|
|
176551
176922
|
}
|