@runfusion/fusion 0.17.2 → 0.18.0

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