@runfusion/fusion 0.17.2 → 0.18.1

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