@runfusion/fusion 0.8.1 → 0.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/dist/bin.js +604 -484
  2. package/dist/client/assets/{AgentDetailView-CLzxf6Z7.js → AgentDetailView-B6wfIQ9j.js} +1 -1
  3. package/dist/client/assets/{AgentsView-CXaYJX_G.js → AgentsView-Fcym0XCw.js} +3 -3
  4. package/dist/client/assets/{ChatView-iXxGAaN1.js → ChatView-DEckS3f3.js} +1 -1
  5. package/dist/client/assets/{DevServerView-BeXfFkF4.js → DevServerView-CmMS4D6V.js} +1 -1
  6. package/dist/client/assets/{DirectoryPicker-BMn5fjn9.js → DirectoryPicker-CQtE-YyA.js} +1 -1
  7. package/dist/client/assets/{DocumentsView-CjrtI3TX.js → DocumentsView-C4HDkN_0.js} +1 -1
  8. package/dist/client/assets/{InsightsView-BkfQ-TV1.js → InsightsView-oNQ7h5e8.js} +1 -1
  9. package/dist/client/assets/{MemoryView-1G0zWu1i.js → MemoryView-DPYGW09y.js} +1 -1
  10. package/dist/client/assets/{NodesView-Bn_1R73N.js → NodesView-DsM-c8RF.js} +1 -1
  11. package/dist/client/assets/{PiExtensionsManager-CqGOtQnR.js → PiExtensionsManager-DHgMjjRE.js} +1 -1
  12. package/dist/client/assets/{PluginManager-CM5QGvSG.js → PluginManager-N-7Sw_tE.js} +1 -1
  13. package/dist/client/assets/{RoadmapsView-B4VnQP83.js → RoadmapsView-BgX79uYM.js} +1 -1
  14. package/dist/client/assets/{SettingsModal-C3LckzfT.js → SettingsModal-BIKEMPwb.js} +1 -1
  15. package/dist/client/assets/{SettingsModal-BiLA-BeG.js → SettingsModal-C4EwtN6K.js} +3 -3
  16. package/dist/client/assets/{SetupWizardModal-Bk_8HfLm.js → SetupWizardModal-D2m0i9Io.js} +1 -1
  17. package/dist/client/assets/{SkillsView-CRvqF8P1.js → SkillsView-Eb1Mngnt.js} +1 -1
  18. package/dist/client/assets/{TodoView-Vzui5Eha.js → TodoView-BN8FQYyp.js} +1 -1
  19. package/dist/client/assets/{folder-open-CMF89prE.js → folder-open-BqZBHfoZ.js} +1 -1
  20. package/dist/client/assets/{index-B8kH5y4Q.js → index-DlHPhpDu.js} +3 -3
  21. package/dist/client/assets/{list-checks-M95d1uAy.js → list-checks-D2EURsP0.js} +1 -1
  22. package/dist/client/assets/{star-DHhJD6ow.js → star-CNQlAD8p.js} +1 -1
  23. package/dist/client/assets/{upload-CEq8jic8.js → upload-uoxlYkig.js} +1 -1
  24. package/dist/client/assets/{users-CUA8Tv-d.js → users-BLvidusm.js} +1 -1
  25. package/dist/client/index.html +1 -1
  26. package/dist/client/version.json +1 -1
  27. package/dist/extension.js +313 -155
  28. package/dist/pi-claude-cli/package.json +1 -4
  29. package/package.json +1 -1
  30. package/dist/pi-claude-cli/src/types/cross-spawn.d.ts +0 -7
package/dist/extension.js CHANGED
@@ -6484,9 +6484,9 @@ var init_global_settings = __esm({
6484
6484
  * Serialize operations via promise chain to prevent lost-update races.
6485
6485
  */
6486
6486
  withLock(fn) {
6487
- let resolve17;
6487
+ let resolve19;
6488
6488
  const next = new Promise((r) => {
6489
- resolve17 = r;
6489
+ resolve19 = r;
6490
6490
  });
6491
6491
  const prev = this.lock;
6492
6492
  this.lock = next;
@@ -6494,7 +6494,7 @@ var init_global_settings = __esm({
6494
6494
  try {
6495
6495
  return await fn();
6496
6496
  } finally {
6497
- resolve17();
6497
+ resolve19();
6498
6498
  }
6499
6499
  });
6500
6500
  }
@@ -11804,8 +11804,8 @@ import { join as join8, dirname as dirname2 } from "node:path";
11804
11804
  import { fileURLToPath } from "node:url";
11805
11805
  function getAppVersion() {
11806
11806
  if (cachedVersion !== null) return cachedVersion;
11807
- const __dirname3 = dirname2(fileURLToPath(import.meta.url));
11808
- let currentDir = __dirname3;
11807
+ const __dirname2 = dirname2(fileURLToPath(import.meta.url));
11808
+ let currentDir = __dirname2;
11809
11809
  for (let i = 0; i < 10; i++) {
11810
11810
  try {
11811
11811
  const pkgPath = join8(currentDir, "package.json");
@@ -27790,9 +27790,9 @@ var init_automation_store = __esm({
27790
27790
  */
27791
27791
  withScheduleLock(id, fn) {
27792
27792
  const prev = this.scheduleLocks.get(id) ?? Promise.resolve();
27793
- let resolve17;
27793
+ let resolve19;
27794
27794
  const next = new Promise((r) => {
27795
- resolve17 = r;
27795
+ resolve19 = r;
27796
27796
  });
27797
27797
  this.scheduleLocks.set(id, next);
27798
27798
  return prev.then(async () => {
@@ -27802,7 +27802,7 @@ var init_automation_store = __esm({
27802
27802
  if (this.scheduleLocks.get(id) === next) {
27803
27803
  this.scheduleLocks.delete(id);
27804
27804
  }
27805
- resolve17();
27805
+ resolve19();
27806
27806
  }
27807
27807
  });
27808
27808
  }
@@ -29590,7 +29590,7 @@ var init_project_memory = __esm({
29590
29590
  // ../core/src/run-command.ts
29591
29591
  import { spawn } from "node:child_process";
29592
29592
  function runCommandAsync(command, options = {}) {
29593
- return new Promise((resolve17) => {
29593
+ return new Promise((resolve19) => {
29594
29594
  const maxBuffer = options.maxBuffer ?? DEFAULT_MAX_BUFFER;
29595
29595
  let stdout = "";
29596
29596
  let stderr = "";
@@ -29649,7 +29649,7 @@ function runCommandAsync(command, options = {}) {
29649
29649
  clearTimeout(forceKillTimer);
29650
29650
  forceKillTimer = null;
29651
29651
  }
29652
- resolve17({
29652
+ resolve19({
29653
29653
  stdout,
29654
29654
  stderr,
29655
29655
  exitCode: null,
@@ -29667,7 +29667,7 @@ function runCommandAsync(command, options = {}) {
29667
29667
  }
29668
29668
  signalProcessGroup("SIGTERM");
29669
29669
  scheduleForceKill(NORMAL_CLEANUP_FORCE_KILL_DELAY_MS);
29670
- resolve17({
29670
+ resolve19({
29671
29671
  stdout,
29672
29672
  stderr,
29673
29673
  exitCode: code,
@@ -30817,9 +30817,9 @@ ${outcome}`;
30817
30817
  * lost-update races on the nextId counter.
30818
30818
  */
30819
30819
  withConfigLock(fn) {
30820
- let resolve17;
30820
+ let resolve19;
30821
30821
  const next = new Promise((r) => {
30822
- resolve17 = r;
30822
+ resolve19 = r;
30823
30823
  });
30824
30824
  const prev = this.configLock;
30825
30825
  this.configLock = next;
@@ -30827,7 +30827,7 @@ ${outcome}`;
30827
30827
  try {
30828
30828
  return await fn();
30829
30829
  } finally {
30830
- resolve17();
30830
+ resolve19();
30831
30831
  }
30832
30832
  });
30833
30833
  }
@@ -30837,9 +30837,9 @@ ${outcome}`;
30837
30837
  */
30838
30838
  withTaskLock(id, fn) {
30839
30839
  const prev = this.taskLocks.get(id) ?? Promise.resolve();
30840
- let resolve17;
30840
+ let resolve19;
30841
30841
  const next = new Promise((r) => {
30842
- resolve17 = r;
30842
+ resolve19 = r;
30843
30843
  });
30844
30844
  this.taskLocks.set(id, next);
30845
30845
  return prev.then(async () => {
@@ -30849,7 +30849,7 @@ ${outcome}`;
30849
30849
  if (this.taskLocks.get(id) === next) {
30850
30850
  this.taskLocks.delete(id);
30851
30851
  }
30852
- resolve17();
30852
+ resolve19();
30853
30853
  }
30854
30854
  });
30855
30855
  }
@@ -33004,7 +33004,7 @@ ${task.description}
33004
33004
  }
33005
33005
  }
33006
33006
  }
33007
- await new Promise((resolve17) => setImmediate(resolve17));
33007
+ await new Promise((resolve19) => setImmediate(resolve19));
33008
33008
  const selectClause = this.getTaskSelectClause(true);
33009
33009
  const changedRows = this.lastPollTime ? this.db.prepare(`SELECT ${selectClause} FROM tasks WHERE updatedAt > ? OR columnMovedAt > ?`).all(this.lastPollTime, this.lastPollTime) : this.db.prepare(`SELECT ${selectClause} FROM tasks`).all();
33010
33010
  this.lastPollTime = (/* @__PURE__ */ new Date()).toISOString();
@@ -33024,7 +33024,7 @@ ${task.description}
33024
33024
  this.emit("task:updated", task);
33025
33025
  }
33026
33026
  if (i > 0 && i % 50 === 0) {
33027
- await new Promise((resolve17) => setImmediate(resolve17));
33027
+ await new Promise((resolve19) => setImmediate(resolve19));
33028
33028
  }
33029
33029
  }
33030
33030
  const elapsed = Date.now() - startTime;
@@ -34850,7 +34850,7 @@ function runGh(args, cwd) {
34850
34850
  }
34851
34851
  function runGhAsync(args, cwdOrOptions) {
34852
34852
  const { cwd, signal: externalSignal, timeoutMs = DEFAULT_GH_TIMEOUT_MS } = normalizeRunGhOptions(cwdOrOptions);
34853
- return new Promise((resolve17, reject) => {
34853
+ return new Promise((resolve19, reject) => {
34854
34854
  if (externalSignal?.aborted) {
34855
34855
  reject(makeGhError(`gh command aborted: ${describeAbortReason(externalSignal.reason)}`, "ABORT_ERR"));
34856
34856
  return;
@@ -34901,7 +34901,7 @@ function runGhAsync(args, cwdOrOptions) {
34901
34901
  ghError.stderr = stderr ?? "";
34902
34902
  reject(ghError);
34903
34903
  } else {
34904
- resolve17(stdout ?? "");
34904
+ resolve19(stdout ?? "");
34905
34905
  }
34906
34906
  }
34907
34907
  );
@@ -35189,9 +35189,9 @@ var init_routine_store = __esm({
35189
35189
  */
35190
35190
  withRoutineLock(id, fn) {
35191
35191
  const prev = this.routineLocks.get(id) ?? Promise.resolve();
35192
- let resolve17;
35192
+ let resolve19;
35193
35193
  const next = new Promise((r) => {
35194
- resolve17 = r;
35194
+ resolve19 = r;
35195
35195
  });
35196
35196
  this.routineLocks.set(id, next);
35197
35197
  return prev.then(async () => {
@@ -35201,7 +35201,7 @@ var init_routine_store = __esm({
35201
35201
  if (this.routineLocks.get(id) === next) {
35202
35202
  this.routineLocks.delete(id);
35203
35203
  }
35204
- resolve17();
35204
+ resolve19();
35205
35205
  }
35206
35206
  });
35207
35207
  }
@@ -35800,13 +35800,13 @@ var init_plugin_loader = __esm({
35800
35800
  * Execute a promise with a timeout.
35801
35801
  */
35802
35802
  withTimeout(promise, ms, timeoutMessage) {
35803
- return new Promise((resolve17, reject) => {
35803
+ return new Promise((resolve19, reject) => {
35804
35804
  const timer = setTimeout(() => {
35805
35805
  reject(new Error(timeoutMessage));
35806
35806
  }, ms);
35807
35807
  promise.then((result) => {
35808
35808
  clearTimeout(timer);
35809
- resolve17(result);
35809
+ resolve19(result);
35810
35810
  }).catch((err) => {
35811
35811
  clearTimeout(timer);
35812
35812
  reject(err);
@@ -39013,7 +39013,7 @@ var require_get_stream = __commonJS({
39013
39013
  };
39014
39014
  const { maxBuffer } = options;
39015
39015
  let stream;
39016
- await new Promise((resolve17, reject) => {
39016
+ await new Promise((resolve19, reject) => {
39017
39017
  const rejectPromise = (error) => {
39018
39018
  if (error && stream.getBufferedLength() <= BufferConstants.MAX_LENGTH) {
39019
39019
  error.bufferedData = stream.getBufferedValue();
@@ -39025,7 +39025,7 @@ var require_get_stream = __commonJS({
39025
39025
  rejectPromise(error);
39026
39026
  return;
39027
39027
  }
39028
- resolve17();
39028
+ resolve19();
39029
39029
  });
39030
39030
  stream.on("data", () => {
39031
39031
  if (stream.getBufferedLength() > maxBuffer) {
@@ -40319,7 +40319,7 @@ var require_extract_zip = __commonJS({
40319
40319
  debug("opening", this.zipPath, "with opts", this.opts);
40320
40320
  this.zipfile = await openZip(this.zipPath, { lazyEntries: true });
40321
40321
  this.canceled = false;
40322
- return new Promise((resolve17, reject) => {
40322
+ return new Promise((resolve19, reject) => {
40323
40323
  this.zipfile.on("error", (err) => {
40324
40324
  this.canceled = true;
40325
40325
  reject(err);
@@ -40328,7 +40328,7 @@ var require_extract_zip = __commonJS({
40328
40328
  this.zipfile.on("close", () => {
40329
40329
  if (!this.canceled) {
40330
40330
  debug("zip extraction complete");
40331
- resolve17();
40331
+ resolve19();
40332
40332
  }
40333
40333
  });
40334
40334
  this.zipfile.on("entry", async (entry) => {
@@ -50377,12 +50377,12 @@ var init_concurrency = __esm({
50377
50377
  this._active++;
50378
50378
  return Promise.resolve();
50379
50379
  }
50380
- return new Promise((resolve17) => {
50380
+ return new Promise((resolve19) => {
50381
50381
  this._waiters.push({
50382
50382
  priority,
50383
50383
  resolve: () => {
50384
50384
  this._active++;
50385
- resolve17();
50385
+ resolve19();
50386
50386
  }
50387
50387
  });
50388
50388
  });
@@ -52803,20 +52803,20 @@ async function withRateLimitRetry(fn, options = {}) {
52803
52803
  throw lastError ?? new Error("withRateLimitRetry: unexpected state");
52804
52804
  }
52805
52805
  function sleep(ms, signal) {
52806
- return new Promise((resolve17, reject) => {
52806
+ return new Promise((resolve19, reject) => {
52807
52807
  if (signal?.aborted) {
52808
52808
  reject(signal.reason ?? new Error("Aborted"));
52809
52809
  return;
52810
52810
  }
52811
- const timer = setTimeout(resolve17, ms);
52811
+ const timer = setTimeout(resolve19, ms);
52812
52812
  if (signal) {
52813
52813
  const onAbort = () => {
52814
52814
  clearTimeout(timer);
52815
52815
  reject(signal.reason ?? new Error("Aborted"));
52816
52816
  };
52817
52817
  signal.addEventListener("abort", onAbort, { once: true });
52818
- const origResolve = resolve17;
52819
- resolve17 = () => {
52818
+ const origResolve = resolve19;
52819
+ resolve19 = () => {
52820
52820
  signal.removeEventListener("abort", onAbort);
52821
52821
  origResolve();
52822
52822
  };
@@ -52896,9 +52896,9 @@ async function readAttachmentContents(rootDir, taskId, attachments) {
52896
52896
  return { attachmentContents, imageContents };
52897
52897
  }
52898
52898
  const { readFile: readFile19 } = await import("node:fs/promises");
52899
- const { join: join39 } = await import("node:path");
52899
+ const { join: join38 } = await import("node:path");
52900
52900
  for (const att of attachments) {
52901
- const filePath = join39(
52901
+ const filePath = join38(
52902
52902
  rootDir,
52903
52903
  ".fusion",
52904
52904
  "tasks",
@@ -54412,9 +54412,9 @@ Remove or replace these ids and call fn_task_create again.`
54412
54412
  }
54413
54413
  try {
54414
54414
  const { readFile: readFile19 } = await import("node:fs/promises");
54415
- const { join: join39 } = await import("node:path");
54415
+ const { join: join38 } = await import("node:path");
54416
54416
  const promptContent = await readFile19(
54417
- join39(rootDir, promptPath),
54417
+ join38(rootDir, promptPath),
54418
54418
  "utf-8"
54419
54419
  ).catch((err) => {
54420
54420
  const msg = err instanceof Error ? err.message : String(err);
@@ -54783,7 +54783,7 @@ import { existsSync as existsSync21 } from "node:fs";
54783
54783
  import { join as join26 } from "node:path";
54784
54784
  import { Type as Type3 } from "typebox";
54785
54785
  async function execWithProcessGroup(command, options) {
54786
- return new Promise((resolve17, reject) => {
54786
+ return new Promise((resolve19, reject) => {
54787
54787
  if (options.signal?.aborted) {
54788
54788
  reject(Object.assign(
54789
54789
  new Error(`Command aborted before start: ${command}`),
@@ -54876,7 +54876,7 @@ async function execWithProcessGroup(command, options) {
54876
54876
  return;
54877
54877
  }
54878
54878
  if (code === 0) {
54879
- resolve17({ stdout, stderr, bufferOverflow: stdoutOverflow || stderrOverflow });
54879
+ resolve19({ stdout, stderr, bufferOverflow: stdoutOverflow || stderrOverflow });
54880
54880
  return;
54881
54881
  }
54882
54882
  reject(Object.assign(
@@ -58845,7 +58845,7 @@ function resolveExecutorModelPair(taskModelProvider, taskModelId, settings) {
58845
58845
  return { provider: void 0, modelId: void 0 };
58846
58846
  }
58847
58847
  function sleep2(ms) {
58848
- return new Promise((resolve17) => setTimeout(resolve17, ms));
58848
+ return new Promise((resolve19) => setTimeout(resolve19, ms));
58849
58849
  }
58850
58850
  var execAsync4, stepExecLog, MAX_STEP_RETRIES, RETRY_DELAYS_MS, NOOP_TASK_STORE, StepSessionExecutor;
58851
58851
  var init_step_session_executor = __esm({
@@ -59729,7 +59729,7 @@ function detectReviewHandoffIntent(commentText) {
59729
59729
  ];
59730
59730
  return handoffPhrases.some((phrase) => text.includes(phrase));
59731
59731
  }
59732
- var execAsync5, STEP_STATUSES, MAX_WORKFLOW_STEP_RETRIES, MAX_TASK_DONE_SESSION_RETRIES, MAX_TASK_DONE_REQUEUE_RETRIES, WORKFLOW_SCRIPT_OUTPUT_MAX_CHARS2, NonRetryableWorktreeError, taskUpdateParams, taskAddDepParams, spawnAgentParams, reviewStepParams, EXECUTOR_SYSTEM_PROMPT, TaskExecutor;
59732
+ var execAsync5, STEP_STATUSES, MAX_WORKFLOW_STEP_RETRIES, MAX_TASK_DONE_SESSION_RETRIES, MAX_TASK_DONE_REQUEUE_RETRIES, COMPLETED_TASK_WATCHDOG_MS, WORKFLOW_RERUN_WATCHDOG_MS, WORKFLOW_SCRIPT_OUTPUT_MAX_CHARS2, NonRetryableWorktreeError, taskUpdateParams, taskAddDepParams, spawnAgentParams, reviewStepParams, EXECUTOR_SYSTEM_PROMPT, TaskExecutor;
59733
59733
  var init_executor = __esm({
59734
59734
  "../engine/src/executor.ts"() {
59735
59735
  "use strict";
@@ -59765,6 +59765,8 @@ var init_executor = __esm({
59765
59765
  MAX_WORKFLOW_STEP_RETRIES = 3;
59766
59766
  MAX_TASK_DONE_SESSION_RETRIES = 3;
59767
59767
  MAX_TASK_DONE_REQUEUE_RETRIES = 3;
59768
+ COMPLETED_TASK_WATCHDOG_MS = 6e4;
59769
+ WORKFLOW_RERUN_WATCHDOG_MS = 15e3;
59768
59770
  WORKFLOW_SCRIPT_OUTPUT_MAX_CHARS2 = 4e3;
59769
59771
  NonRetryableWorktreeError = class extends Error {
59770
59772
  };
@@ -59975,6 +59977,7 @@ Lint, tests, and typecheck are also hard quality gates:
59975
59977
  store.on("task:moved", ({ task, from, to }) => {
59976
59978
  executorLog.log(`[event:task:moved] ${task.id}: ${from} \u2192 ${to}`);
59977
59979
  if (to === "in-progress") {
59980
+ this.clearWorkflowRerunWatchdog(task.id);
59978
59981
  executorLog.log(`[event:task:moved] Initiating execute() for ${task.id}`);
59979
59982
  void (async () => {
59980
59983
  const taskForExecution = await this.resetMergeStateIfNeeded(task, from);
@@ -59983,6 +59986,7 @@ Lint, tests, and typecheck are also hard quality gates:
59983
59986
  (err) => executorLog.error(`Failed to start ${task.id}:`, err)
59984
59987
  );
59985
59988
  } else if (from === "in-progress") {
59989
+ this.clearCompletedTaskWatchdog(task.id);
59986
59990
  if (this.activeSessions.has(task.id)) {
59987
59991
  executorLog.log(`${task.id} moved from in-progress to ${to} \u2014 terminating agent session`);
59988
59992
  this.pausedAborted.add(task.id);
@@ -60174,6 +60178,10 @@ Lint, tests, and typecheck are also hard quality gates:
60174
60178
  spawnedAgents = /* @__PURE__ */ new Map();
60175
60179
  /** Per-task baseline of session stats used for delta persistence across repeated updates. */
60176
60180
  tokenUsageBaselines = /* @__PURE__ */ new Map();
60181
+ /** One-shot watchdogs for completed tasks that should have transitioned to in-review. */
60182
+ completedTaskWatchdogs = /* @__PURE__ */ new Map();
60183
+ /** One-shot watchdogs for workflow reruns that should have bounced back to in-progress. */
60184
+ workflowRerunWatchdogs = /* @__PURE__ */ new Map();
60177
60185
  async finalizeAlreadyReviewedTask(taskId) {
60178
60186
  const latestTask = await this.store.getTask(taskId);
60179
60187
  if (!latestTask || latestTask.column !== "in-review") {
@@ -60329,6 +60337,120 @@ Lint, tests, and typecheck are also hard quality gates:
60329
60337
  await this.store.updateTask(task.id, updates);
60330
60338
  }
60331
60339
  }
60340
+ clearCompletedTaskWatchdog(taskId) {
60341
+ const handle = this.completedTaskWatchdogs.get(taskId);
60342
+ if (!handle) return;
60343
+ clearTimeout(handle);
60344
+ this.completedTaskWatchdogs.delete(taskId);
60345
+ }
60346
+ clearWorkflowRerunWatchdog(taskId) {
60347
+ const handle = this.workflowRerunWatchdogs.get(taskId);
60348
+ if (!handle) return;
60349
+ clearTimeout(handle);
60350
+ this.workflowRerunWatchdogs.delete(taskId);
60351
+ }
60352
+ scheduleCompletedTaskWatchdog(taskId, trigger) {
60353
+ this.clearCompletedTaskWatchdog(taskId);
60354
+ const handle = setTimeout(async () => {
60355
+ this.completedTaskWatchdogs.delete(taskId);
60356
+ let currentTask = null;
60357
+ try {
60358
+ currentTask = await this.store.getTask(taskId);
60359
+ } catch (err) {
60360
+ const errorMessage = err instanceof Error ? err.message : String(err);
60361
+ executorLog.warn(`${taskId}: completed-task watchdog could not read latest task state: ${errorMessage}`);
60362
+ return;
60363
+ }
60364
+ if (!currentTask || currentTask.column !== "in-progress" || currentTask.paused) {
60365
+ return;
60366
+ }
60367
+ if (this.activeSessions.has(taskId) || this.activeStepExecutors.has(taskId) || this.recoveringCompleted.has(taskId)) {
60368
+ return;
60369
+ }
60370
+ if (!this.isTaskWorkComplete(currentTask)) {
60371
+ return;
60372
+ }
60373
+ executorLog.warn(
60374
+ `${taskId}: completed-task watchdog fired after ${COMPLETED_TASK_WATCHDOG_MS / 1e3}s (${trigger}) \u2014 attempting direct recovery to in-review`
60375
+ );
60376
+ await this.store.logEntry(
60377
+ taskId,
60378
+ `Watchdog: task remained in-progress ${COMPLETED_TASK_WATCHDOG_MS / 1e3}s after ${trigger} \u2014 attempting direct recovery to in-review`
60379
+ ).catch(() => void 0);
60380
+ this.recoveringCompleted.add(taskId);
60381
+ try {
60382
+ const recovered = await this.recoverCompletedTask(currentTask);
60383
+ if (!recovered) {
60384
+ await this.store.logEntry(
60385
+ taskId,
60386
+ "Watchdog recovery attempt could not finalize completed task \u2014 leaving for follow-up recovery"
60387
+ ).catch(() => void 0);
60388
+ }
60389
+ } finally {
60390
+ this.recoveringCompleted.delete(taskId);
60391
+ }
60392
+ }, COMPLETED_TASK_WATCHDOG_MS);
60393
+ this.completedTaskWatchdogs.set(taskId, handle);
60394
+ }
60395
+ async performWorkflowRerunBounce(taskId, worktreePath) {
60396
+ const latestTask = await this.store.getTask(taskId);
60397
+ if (!latestTask) {
60398
+ throw new Error("task missing during workflow rerun bounce");
60399
+ }
60400
+ if (latestTask.column === "in-progress") {
60401
+ await this.store.moveTask(taskId, "todo");
60402
+ await this.store.updateTask(taskId, { worktree: worktreePath });
60403
+ await this.store.moveTask(taskId, "in-progress");
60404
+ return;
60405
+ }
60406
+ if (latestTask.column === "todo") {
60407
+ await this.store.updateTask(taskId, { worktree: worktreePath });
60408
+ await this.store.moveTask(taskId, "in-progress");
60409
+ return;
60410
+ }
60411
+ throw new Error(`task is in '${latestTask.column}', cannot bounce to in-progress`);
60412
+ }
60413
+ scheduleWorkflowRerun(taskId, worktreePath, successMessage) {
60414
+ this.clearWorkflowRerunWatchdog(taskId);
60415
+ setTimeout(async () => {
60416
+ try {
60417
+ await this.performWorkflowRerunBounce(taskId, worktreePath);
60418
+ executorLog.log(successMessage);
60419
+ } catch (err) {
60420
+ const errorMessage = err instanceof Error ? err.message : String(err);
60421
+ executorLog.error(`${taskId}: failed to schedule rerun bounce: ${errorMessage}`);
60422
+ }
60423
+ }, 0);
60424
+ const watchdog = setTimeout(async () => {
60425
+ this.workflowRerunWatchdogs.delete(taskId);
60426
+ let currentTask = null;
60427
+ try {
60428
+ currentTask = await this.store.getTask(taskId);
60429
+ } catch (err) {
60430
+ const errorMessage = err instanceof Error ? err.message : String(err);
60431
+ executorLog.warn(`${taskId}: workflow rerun watchdog could not read latest task state: ${errorMessage}`);
60432
+ return;
60433
+ }
60434
+ if (!currentTask || currentTask.paused || currentTask.column === "in-progress") {
60435
+ return;
60436
+ }
60437
+ executorLog.warn(
60438
+ `${taskId}: workflow rerun watchdog fired after ${WORKFLOW_RERUN_WATCHDOG_MS / 1e3}s \u2014 task is still ${currentTask.column}; retrying handoff once`
60439
+ );
60440
+ await this.store.logEntry(
60441
+ taskId,
60442
+ `Watchdog: workflow rerun handoff stalled for ${WORKFLOW_RERUN_WATCHDOG_MS / 1e3}s (still ${currentTask.column}) \u2014 retrying once`
60443
+ ).catch(() => void 0);
60444
+ try {
60445
+ await this.performWorkflowRerunBounce(taskId, worktreePath);
60446
+ executorLog.warn(`${taskId}: workflow rerun watchdog retry succeeded`);
60447
+ } catch (err) {
60448
+ const errorMessage = err instanceof Error ? err.message : String(err);
60449
+ executorLog.error(`${taskId}: workflow rerun watchdog retry failed: ${errorMessage}`);
60450
+ }
60451
+ }, WORKFLOW_RERUN_WATCHDOG_MS);
60452
+ this.workflowRerunWatchdogs.set(taskId, watchdog);
60453
+ }
60332
60454
  async shouldFinalizeCompletedTask(taskId, taskDone) {
60333
60455
  const task = await this.store.getTask(taskId);
60334
60456
  const completionBlocker = await this.getTaskCompletionBlocker(task);
@@ -60462,6 +60584,7 @@ Lint, tests, and typecheck are also hard quality gates:
60462
60584
  }
60463
60585
  await this.persistTokenUsage(task.id);
60464
60586
  await this.store.moveTask(task.id, "in-review");
60587
+ this.clearCompletedTaskWatchdog(task.id);
60465
60588
  await this.store.logEntry(task.id, "Auto-recovered: task work was complete but stuck in in-progress \u2014 moved to in-review");
60466
60589
  executorLog.log(`\u2713 ${task.id} auto-recovered completed task \u2192 in-review`);
60467
60590
  this.options.onComplete?.(task);
@@ -60958,6 +61081,7 @@ Lint, tests, and typecheck are also hard quality gates:
60958
61081
  executorLog.log(`${task.id}: captured ${modifiedFiles.length} modified files`);
60959
61082
  await audit.filesystem({ type: "file:capture-modified", target: task.id, metadata: { files: modifiedFiles } });
60960
61083
  }
61084
+ this.scheduleCompletedTaskWatchdog(task.id, "step-session completion");
60961
61085
  if (executionMode !== "fast") {
60962
61086
  const workflowResult = await this.runWorkflowSteps(task, worktreePath, settings);
60963
61087
  if (!workflowResult.allPassed) {
@@ -60978,6 +61102,7 @@ Lint, tests, and typecheck are also hard quality gates:
60978
61102
  }
60979
61103
  await this.store.updateTask(task.id, { workflowStepRetries: void 0, taskDoneRetryCount: null });
60980
61104
  await this.store.moveTask(task.id, "in-review");
61105
+ this.clearCompletedTaskWatchdog(task.id);
60981
61106
  await audit.database({ type: "task:move", target: task.id, metadata: { to: "in-review" } });
60982
61107
  executorLog.log(`\u2713 ${task.id} completed (step-session) \u2192 in-review`);
60983
61108
  this.options.onComplete?.(task);
@@ -61314,6 +61439,7 @@ Lint, tests, and typecheck are also hard quality gates:
61314
61439
  await this.store.logEntry(task.id, "Execution paused after completion \u2014 finalizing to in-review");
61315
61440
  await this.persistTokenUsage(task.id);
61316
61441
  await this.store.moveTask(task.id, "in-review");
61442
+ this.clearCompletedTaskWatchdog(task.id);
61317
61443
  this.options.onComplete?.(task);
61318
61444
  } else {
61319
61445
  executorLog.log(`${task.id} paused (graceful session exit) \u2014 moving to todo`);
@@ -61334,6 +61460,7 @@ Lint, tests, and typecheck are also hard quality gates:
61334
61460
  taskDone = true;
61335
61461
  executorLog.log(`${task.id} all steps done \u2014 treating as implicit fn_task_done`);
61336
61462
  await this.store.logEntry(task.id, "All steps complete \u2014 implicit fn_task_done (agent did not call tool explicitly)", void 0, this.currentRunContext);
61463
+ this.scheduleCompletedTaskWatchdog(task.id, "implicit fn_task_done");
61337
61464
  }
61338
61465
  }
61339
61466
  if (taskDone) {
@@ -61343,6 +61470,7 @@ Lint, tests, and typecheck are also hard quality gates:
61343
61470
  await this.store.updateTask(task.id, { modifiedFiles });
61344
61471
  executorLog.log(`${task.id}: captured ${modifiedFiles.length} modified files`);
61345
61472
  }
61473
+ this.scheduleCompletedTaskWatchdog(task.id, "task completion");
61346
61474
  if (executionMode !== "fast") {
61347
61475
  const workflowResult = await this.runWorkflowSteps(task, worktreePath, settings);
61348
61476
  if (!workflowResult.allPassed) {
@@ -61364,6 +61492,7 @@ Lint, tests, and typecheck are also hard quality gates:
61364
61492
  await this.store.updateTask(task.id, { workflowStepRetries: void 0, taskDoneRetryCount: null });
61365
61493
  await this.persistTokenUsage(task.id);
61366
61494
  await this.store.moveTask(task.id, "in-review");
61495
+ this.clearCompletedTaskWatchdog(task.id);
61367
61496
  executorLog.log(`\u2713 ${task.id} completed \u2192 in-review`);
61368
61497
  this.options.onComplete?.(task);
61369
61498
  } else {
@@ -61437,6 +61566,7 @@ Lint, tests, and typecheck are also hard quality gates:
61437
61566
  taskDone = true;
61438
61567
  executorLog.log(`${task.id} all steps done \u2014 treating as implicit fn_task_done`);
61439
61568
  await this.store.logEntry(task.id, "All steps complete \u2014 implicit fn_task_done (agent did not call tool explicitly)", void 0, this.currentRunContext);
61569
+ this.scheduleCompletedTaskWatchdog(task.id, "implicit fn_task_done");
61440
61570
  }
61441
61571
  }
61442
61572
  }
@@ -61447,6 +61577,7 @@ Lint, tests, and typecheck are also hard quality gates:
61447
61577
  await this.store.updateTask(task.id, { modifiedFiles });
61448
61578
  executorLog.log(`${task.id}: captured ${modifiedFiles.length} modified files`);
61449
61579
  }
61580
+ this.scheduleCompletedTaskWatchdog(task.id, "task completion retry");
61450
61581
  if (executionMode !== "fast") {
61451
61582
  const workflowResult = await this.runWorkflowSteps(task, worktreePath, settings);
61452
61583
  if (!workflowResult.allPassed) {
@@ -61464,6 +61595,7 @@ Lint, tests, and typecheck are also hard quality gates:
61464
61595
  await this.store.updateTask(task.id, { workflowStepRetries: void 0, taskDoneRetryCount: null });
61465
61596
  await this.persistTokenUsage(task.id);
61466
61597
  await this.store.moveTask(task.id, "in-review");
61598
+ this.clearCompletedTaskWatchdog(task.id);
61467
61599
  executorLog.log(`\u2713 ${task.id} completed on retry \u2192 in-review`);
61468
61600
  this.options.onComplete?.(task);
61469
61601
  } else {
@@ -61891,6 +62023,7 @@ Lint, tests, and typecheck are also hard quality gates:
61891
62023
  await store.updateTask(taskId, { summary: params.summary });
61892
62024
  }
61893
62025
  await store.logEntry(taskId, "Task marked done by agent");
62026
+ this.scheduleCompletedTaskWatchdog(taskId, "fn_task_done");
61894
62027
  const successMessage = params.summary ? "Task marked complete with summary. All steps done. Moving to in-review." : "Task marked complete. All steps done. Moving to in-review.";
61895
62028
  return {
61896
62029
  content: [{ type: "text", text: successMessage }],
@@ -62117,6 +62250,7 @@ Take a different approach. Do NOT repeat the rejected strategy. Re-read the step
62117
62250
  */
62118
62251
  async handleWorkflowRevisionRequest(task, worktreePath, feedback, stepName) {
62119
62252
  executorLog.log(`${task.id}: workflow revision requested by step "${stepName}"`);
62253
+ this.clearCompletedTaskWatchdog(task.id);
62120
62254
  const updatedTask = await this.store.getTask(task.id);
62121
62255
  const reopen = await this.reopenLastStepForRevision(task.id, updatedTask);
62122
62256
  const reopenSummary = reopen ? `re-opening Step ${reopen.index + 1} ("${reopen.name}") for in-place fix` : "no step to re-open (none were completed)";
@@ -62131,21 +62265,11 @@ Take a different approach. Do NOT repeat the rejected strategy. Re-read the step
62131
62265
  sessionFile: null
62132
62266
  });
62133
62267
  executorLog.log(`${task.id}: scheduling fresh execution after revision request`);
62134
- setTimeout(async () => {
62135
- try {
62136
- await this.store.moveTask(task.id, "todo");
62137
- await this.store.updateTask(task.id, { worktree: worktreePath });
62138
- await this.store.moveTask(task.id, "in-progress");
62139
- executorLog.log(`${task.id}: revision rerun scheduled \u2014 moved to todo then in-progress`);
62140
- } catch (err) {
62141
- const errorMessage = err instanceof Error ? err.message : String(err);
62142
- executorLog.error(`${task.id}: failed to schedule revision rerun: ${errorMessage}`);
62143
- await this.store.logEntry(
62144
- task.id,
62145
- "Workflow revision requested \u2014 executor ready for fresh execution"
62146
- );
62147
- }
62148
- }, 0);
62268
+ this.scheduleWorkflowRerun(
62269
+ task.id,
62270
+ worktreePath,
62271
+ `${task.id}: revision rerun scheduled \u2014 moved to todo then in-progress`
62272
+ );
62149
62273
  }
62150
62274
  /**
62151
62275
  * Re-open the last non-pending step so a revision/failure handler gives the
@@ -62232,6 +62356,7 @@ ${feedback}
62232
62356
  * @returns true if a retry was scheduled, false if retries are exhausted
62233
62357
  */
62234
62358
  async handleWorkflowStepFailure(task, worktreePath, failureFeedback, stepName) {
62359
+ this.clearCompletedTaskWatchdog(task.id);
62235
62360
  const currentRetries = task.workflowStepRetries ?? 0;
62236
62361
  if (currentRetries >= MAX_WORKFLOW_STEP_RETRIES) {
62237
62362
  executorLog.warn(`${task.id}: workflow step "${stepName}" failed \u2014 retries exhausted (${MAX_WORKFLOW_STEP_RETRIES}/${MAX_WORKFLOW_STEP_RETRIES})`);
@@ -62250,21 +62375,11 @@ ${feedback}
62250
62375
  sessionFile: null
62251
62376
  });
62252
62377
  executorLog.log(`${task.id}: scheduling fresh execution after workflow step failure (retry ${retryCount}/${MAX_WORKFLOW_STEP_RETRIES})`);
62253
- setTimeout(async () => {
62254
- try {
62255
- await this.store.moveTask(task.id, "todo");
62256
- await this.store.updateTask(task.id, { worktree: worktreePath });
62257
- await this.store.moveTask(task.id, "in-progress");
62258
- executorLog.log(`${task.id}: workflow step retry scheduled \u2014 moved to todo then in-progress`);
62259
- } catch (err) {
62260
- const errorMessage = err instanceof Error ? err.message : String(err);
62261
- executorLog.error(`${task.id}: failed to schedule workflow step retry: ${errorMessage}`);
62262
- await this.store.logEntry(
62263
- task.id,
62264
- "Workflow step failed \u2014 executor ready for fresh execution"
62265
- );
62266
- }
62267
- }, 0);
62378
+ this.scheduleWorkflowRerun(
62379
+ task.id,
62380
+ worktreePath,
62381
+ `${task.id}: workflow step retry scheduled \u2014 moved to todo then in-progress`
62382
+ );
62268
62383
  return true;
62269
62384
  }
62270
62385
  /**
@@ -62274,6 +62389,7 @@ ${feedback}
62274
62389
  */
62275
62390
  async sendTaskBackForFix(task, worktreePath, failureFeedback, stepName, reason) {
62276
62391
  const taskId = task.id;
62392
+ this.clearCompletedTaskWatchdog(taskId);
62277
62393
  await this.store.addTaskComment(
62278
62394
  taskId,
62279
62395
  `${reason}. The failing workflow step was "${stepName}". Feedback:
@@ -62295,17 +62411,11 @@ Please fix the issues so the verification can pass on the next attempt.`,
62295
62411
  sessionFile: null,
62296
62412
  workflowStepRetries: 0
62297
62413
  });
62298
- setTimeout(async () => {
62299
- try {
62300
- await this.store.moveTask(taskId, "todo");
62301
- await this.store.updateTask(taskId, { worktree: worktreePath });
62302
- await this.store.moveTask(taskId, "in-progress");
62303
- executorLog.log(`${taskId}: sent back to in-progress for remediation`);
62304
- } catch (err) {
62305
- const errorMessage = err instanceof Error ? err.message : String(err);
62306
- executorLog.error(`${taskId}: failed to move back to in-progress: ${errorMessage}`);
62307
- }
62308
- }, 0);
62414
+ this.scheduleWorkflowRerun(
62415
+ taskId,
62416
+ worktreePath,
62417
+ `${taskId}: sent back to in-progress for remediation`
62418
+ );
62309
62419
  }
62310
62420
  /**
62311
62421
  * Inject or update the "Workflow Step Failure" section in PROMPT.md.
@@ -62856,7 +62966,7 @@ Review the work done in this worktree and evaluate it against the criteria in yo
62856
62966
  );
62857
62967
  }
62858
62968
  const delay2 = this.WORKTREE_RETRY_DELAYS[attempt] || 1e3;
62859
- await new Promise((resolve17) => setTimeout(resolve17, delay2));
62969
+ await new Promise((resolve19) => setTimeout(resolve19, delay2));
62860
62970
  }
62861
62971
  }
62862
62972
  throw new Error("Unexpected exit from worktree creation retry loop");
@@ -72180,13 +72290,13 @@ var init_plugin_runner = __esm({
72180
72290
  * Returns the result on success, throws on timeout.
72181
72291
  */
72182
72292
  withTimeout(promise, ms, timeoutMessage) {
72183
- return new Promise((resolve17, reject) => {
72293
+ return new Promise((resolve19, reject) => {
72184
72294
  const timer = setTimeout(() => {
72185
72295
  reject(new Error(timeoutMessage));
72186
72296
  }, ms);
72187
72297
  promise.then((result) => {
72188
72298
  clearTimeout(timer);
72189
- resolve17(result);
72299
+ resolve19(result);
72190
72300
  }).catch((err) => {
72191
72301
  clearTimeout(timer);
72192
72302
  reject(err);
@@ -72855,7 +72965,7 @@ var init_in_process_runtime = __esm({
72855
72965
  runtimeLog.log(
72856
72966
  `Waiting for ${metrics.inFlightTasks} in-flight tasks to complete...`
72857
72967
  );
72858
- await new Promise((resolve17) => setTimeout(resolve17, 1e3));
72968
+ await new Promise((resolve19) => setTimeout(resolve19, 1e3));
72859
72969
  }
72860
72970
  const finalMetrics = this.getMetrics();
72861
72971
  if (finalMetrics.inFlightTasks > 0) {
@@ -73252,13 +73362,13 @@ var init_ipc_host = __esm({
73252
73362
  }
73253
73363
  const id = generateCorrelationId();
73254
73364
  const message = { type, id, payload };
73255
- return new Promise((resolve17, reject) => {
73365
+ return new Promise((resolve19, reject) => {
73256
73366
  const timeout = setTimeout(() => {
73257
73367
  this.pendingCommands.delete(id);
73258
73368
  reject(new Error(`Command ${type} timed out after ${timeoutMs ?? this.commandTimeoutMs}ms`));
73259
73369
  }, timeoutMs ?? this.commandTimeoutMs);
73260
73370
  this.pendingCommands.set(id, {
73261
- resolve: resolve17,
73371
+ resolve: resolve19,
73262
73372
  reject,
73263
73373
  timeout,
73264
73374
  type
@@ -74067,8 +74177,8 @@ var init_remote_node_client = __esm({
74067
74177
  return error instanceof TypeError;
74068
74178
  }
74069
74179
  async sleep(ms) {
74070
- await new Promise((resolve17) => {
74071
- setTimeout(resolve17, ms);
74180
+ await new Promise((resolve19) => {
74181
+ setTimeout(resolve19, ms);
74072
74182
  });
74073
74183
  }
74074
74184
  };
@@ -74332,14 +74442,14 @@ var init_remote_node_runtime = __esm({
74332
74442
  return error instanceof Error ? error : new Error(String(error));
74333
74443
  }
74334
74444
  async sleep(ms, signal) {
74335
- await new Promise((resolve17) => {
74445
+ await new Promise((resolve19) => {
74336
74446
  const timeout = setTimeout(() => {
74337
74447
  cleanup();
74338
- resolve17();
74448
+ resolve19();
74339
74449
  }, ms);
74340
74450
  const onAbort = () => {
74341
74451
  cleanup();
74342
- resolve17();
74452
+ resolve19();
74343
74453
  };
74344
74454
  const cleanup = () => {
74345
74455
  clearTimeout(timeout);
@@ -75226,10 +75336,10 @@ var init_tunnel_process_manager = __esm({
75226
75336
  lastError: null
75227
75337
  });
75228
75338
  this.emitLog("info", "manager", `Stopping ${currentHandle.provider} tunnel (pid=${currentHandle.child.pid ?? "n/a"})`);
75229
- this.activeStopPromise = new Promise((resolve17) => {
75339
+ this.activeStopPromise = new Promise((resolve19) => {
75230
75340
  const onClose = () => {
75231
75341
  currentHandle.child.removeListener("close", onClose);
75232
- resolve17();
75342
+ resolve19();
75233
75343
  };
75234
75344
  currentHandle.child.once("close", onClose);
75235
75345
  killManagedProcess(currentHandle.child, "SIGTERM");
@@ -75785,12 +75895,12 @@ ${detail}`
75785
75895
  */
75786
75896
  async onMerge(taskId) {
75787
75897
  if (this.mergeActive.has(taskId)) {
75788
- return new Promise((resolve17, reject) => {
75789
- this.manualMergeResolvers.set(taskId, { resolve: resolve17, reject });
75898
+ return new Promise((resolve19, reject) => {
75899
+ this.manualMergeResolvers.set(taskId, { resolve: resolve19, reject });
75790
75900
  });
75791
75901
  }
75792
- return new Promise((resolve17, reject) => {
75793
- this.manualMergeResolvers.set(taskId, { resolve: resolve17, reject });
75902
+ return new Promise((resolve19, reject) => {
75903
+ this.manualMergeResolvers.set(taskId, { resolve: resolve19, reject });
75794
75904
  this.internalEnqueueMerge(taskId);
75795
75905
  });
75796
75906
  }
@@ -79314,7 +79424,7 @@ function buildHermesArgs(prompt, settings, resumeSessionId) {
79314
79424
  async function invokeHermesCli(prompt, settings, resumeSessionId, signal) {
79315
79425
  const args = buildHermesArgs(prompt, settings, resumeSessionId);
79316
79426
  const binary = resolveBinaryForSpawn(settings.binaryPath);
79317
- return new Promise((resolve17, reject) => {
79427
+ return new Promise((resolve19, reject) => {
79318
79428
  let settled = false;
79319
79429
  const spawnEnv = { ...process.env, PYTHONUNBUFFERED: "1" };
79320
79430
  if (settings.profile) {
@@ -79382,7 +79492,7 @@ ${combined}`));
79382
79492
  return;
79383
79493
  }
79384
79494
  try {
79385
- resolve17(parseHermesOutput(stdout, stderr));
79495
+ resolve19(parseHermesOutput(stdout, stderr));
79386
79496
  } catch (parseErr) {
79387
79497
  reject(parseErr);
79388
79498
  }
@@ -79608,7 +79718,7 @@ async function promptCli(session, message, config, callbacks, signal) {
79608
79718
  const args = buildOpenClawArgs(config, session.sessionId, message);
79609
79719
  const cb = { ...session.callbacks, ...callbacks };
79610
79720
  cb.onToolStart?.("openclaw.agent", { sessionId: session.sessionId });
79611
- return new Promise((resolve17, reject) => {
79721
+ return new Promise((resolve19, reject) => {
79612
79722
  let settled = false;
79613
79723
  const child = spawn5(config.binaryPath, args, {
79614
79724
  stdio: ["ignore", "pipe", "pipe"]
@@ -79701,7 +79811,7 @@ async function promptCli(session, message, config, callbacks, signal) {
79701
79811
  ...metaError ? { error: metaError } : {},
79702
79812
  ...errorText.length > 0 ? { toolErrors: errorText } : {}
79703
79813
  });
79704
- resolve17();
79814
+ resolve19();
79705
79815
  });
79706
79816
  });
79707
79817
  }
@@ -80012,7 +80122,7 @@ var init_register_messaging_scripts = __esm({
80012
80122
 
80013
80123
  // ../dashboard/src/github.ts
80014
80124
  function delay(ms) {
80015
- return new Promise((resolve17) => setTimeout(resolve17, ms));
80125
+ return new Promise((resolve19) => setTimeout(resolve19, ms));
80016
80126
  }
80017
80127
  function normalizeCheckState(state) {
80018
80128
  switch ((state ?? "").toLowerCase()) {
@@ -82723,7 +82833,7 @@ async function spawnPaperclipCliJson(args, opts) {
82723
82833
  }
82724
82834
  const timeoutMs = opts.cliTimeoutMs ?? 15e3;
82725
82835
  const label = ["paperclipai", ...args].join(" ");
82726
- return new Promise((resolve17, reject) => {
82836
+ return new Promise((resolve19, reject) => {
82727
82837
  let child;
82728
82838
  try {
82729
82839
  child = spawn10(bin, fullArgs, { stdio: ["ignore", "pipe", "pipe"] });
@@ -82769,7 +82879,7 @@ async function spawnPaperclipCliJson(args, opts) {
82769
82879
  return;
82770
82880
  }
82771
82881
  try {
82772
- resolve17(JSON.parse(cleaned));
82882
+ resolve19(JSON.parse(cleaned));
82773
82883
  } catch {
82774
82884
  reject(new Error(`${label} returned non-JSON output: ${cleaned.slice(0, 200)}`));
82775
82885
  }
@@ -82839,7 +82949,7 @@ var init_paperclip_client = __esm({
82839
82949
  // ../../plugins/fusion-plugin-paperclip-runtime/dist/runtime-adapter.js
82840
82950
  import { randomUUID as randomUUID13 } from "node:crypto";
82841
82951
  function sleep3(ms) {
82842
- return new Promise((resolve17) => setTimeout(resolve17, ms));
82952
+ return new Promise((resolve19) => setTimeout(resolve19, ms));
82843
82953
  }
82844
82954
  function asString2(value) {
82845
82955
  return typeof value === "string" ? value : void 0;
@@ -83248,22 +83358,29 @@ var init_update_check = __esm({
83248
83358
  });
83249
83359
 
83250
83360
  // ../dashboard/src/routes/register-update-check-routes.ts
83251
- import { readFileSync as readFileSync7 } from "node:fs";
83252
- import { dirname as dirname9, join as join35 } from "node:path";
83361
+ import { existsSync as existsSync28, readFileSync as readFileSync7 } from "node:fs";
83362
+ import { dirname as dirname9, resolve as resolve15 } from "node:path";
83253
83363
  import { fileURLToPath as fileURLToPath3 } from "node:url";
83254
- var __dirname, CLI_PACKAGE_VERSION;
83364
+ var CLI_PACKAGE_VERSION;
83255
83365
  var init_register_update_check_routes = __esm({
83256
83366
  "../dashboard/src/routes/register-update-check-routes.ts"() {
83257
83367
  "use strict";
83258
83368
  init_src();
83259
83369
  init_update_check();
83260
- __dirname = dirname9(fileURLToPath3(import.meta.url));
83261
83370
  CLI_PACKAGE_VERSION = (() => {
83262
83371
  try {
83263
- const packageJsonPath = join35(__dirname, "..", "..", "..", "cli", "package.json");
83264
- const packageJson = JSON.parse(readFileSync7(packageJsonPath, "utf-8"));
83265
- if (typeof packageJson.version === "string" && packageJson.version.length > 0) {
83266
- return packageJson.version;
83372
+ let cur = dirname9(fileURLToPath3(import.meta.url));
83373
+ for (let i = 0; i < 8; i++) {
83374
+ const pkgPath = resolve15(cur, "package.json");
83375
+ if (existsSync28(pkgPath)) {
83376
+ const parsed = JSON.parse(readFileSync7(pkgPath, "utf-8"));
83377
+ if (parsed.name === "@runfusion/fusion" && typeof parsed.version === "string" && parsed.version.length > 0) {
83378
+ return parsed.version;
83379
+ }
83380
+ }
83381
+ const parent = resolve15(cur, "..");
83382
+ if (parent === cur) break;
83383
+ cur = parent;
83267
83384
  }
83268
83385
  } catch {
83269
83386
  }
@@ -87295,8 +87412,8 @@ var init_auth_middleware = __esm({
87295
87412
 
87296
87413
  // ../dashboard/src/server.ts
87297
87414
  import express from "express";
87298
- import { join as join36, dirname as dirname10 } from "node:path";
87299
- import { existsSync as existsSync28, readFileSync as readFileSync8 } from "node:fs";
87415
+ import { join as join35, dirname as dirname10, resolve as resolve16 } from "node:path";
87416
+ import { existsSync as existsSync29, readFileSync as readFileSync8 } from "node:fs";
87300
87417
  import { fileURLToPath as fileURLToPath4 } from "node:url";
87301
87418
  function clearAiSessionCleanupInterval() {
87302
87419
  if (!aiSessionCleanupIntervalHandle) {
@@ -87305,7 +87422,7 @@ function clearAiSessionCleanupInterval() {
87305
87422
  clearInterval(aiSessionCleanupIntervalHandle);
87306
87423
  aiSessionCleanupIntervalHandle = void 0;
87307
87424
  }
87308
- var __dirname2, PACKAGE_VERSION, CLI_PACKAGE_VERSION2, MIN_AI_SESSION_TTL_MS, MAX_AI_SESSION_TTL_MS, MIN_AI_SESSION_CLEANUP_INTERVAL_MS, MAX_AI_SESSION_CLEANUP_INTERVAL_MS, aiSessionCleanupIntervalHandle;
87425
+ var __dirname, PACKAGE_VERSION, CLI_PACKAGE_VERSION2, MIN_AI_SESSION_TTL_MS, MAX_AI_SESSION_TTL_MS, MIN_AI_SESSION_CLEANUP_INTERVAL_MS, MAX_AI_SESSION_CLEANUP_INTERVAL_MS, aiSessionCleanupIntervalHandle;
87309
87426
  var init_server = __esm({
87310
87427
  "../dashboard/src/server.ts"() {
87311
87428
  "use strict";
@@ -87331,10 +87448,10 @@ var init_server = __esm({
87331
87448
  init_dev_server_routes();
87332
87449
  init_auth_middleware();
87333
87450
  init_remote_auth();
87334
- __dirname2 = dirname10(fileURLToPath4(import.meta.url));
87451
+ __dirname = dirname10(fileURLToPath4(import.meta.url));
87335
87452
  PACKAGE_VERSION = (() => {
87336
87453
  try {
87337
- const packageJsonPath = join36(__dirname2, "..", "package.json");
87454
+ const packageJsonPath = join35(__dirname, "..", "package.json");
87338
87455
  const packageJson = JSON.parse(readFileSync8(packageJsonPath, "utf-8"));
87339
87456
  if (typeof packageJson.version === "string" && packageJson.version.length > 0) {
87340
87457
  return packageJson.version;
@@ -87345,10 +87462,18 @@ var init_server = __esm({
87345
87462
  })();
87346
87463
  CLI_PACKAGE_VERSION2 = (() => {
87347
87464
  try {
87348
- const packageJsonPath = join36(__dirname2, "..", "..", "cli", "package.json");
87349
- const packageJson = JSON.parse(readFileSync8(packageJsonPath, "utf-8"));
87350
- if (typeof packageJson.version === "string" && packageJson.version.length > 0) {
87351
- return packageJson.version;
87465
+ let cur = __dirname;
87466
+ for (let i = 0; i < 8; i++) {
87467
+ const pkgPath = resolve16(cur, "package.json");
87468
+ if (existsSync29(pkgPath)) {
87469
+ const parsed = JSON.parse(readFileSync8(pkgPath, "utf-8"));
87470
+ if (parsed.name === "@runfusion/fusion" && typeof parsed.version === "string" && parsed.version.length > 0) {
87471
+ return parsed.version;
87472
+ }
87473
+ }
87474
+ const parent = resolve16(cur, "..");
87475
+ if (parent === cur) break;
87476
+ cur = parent;
87352
87477
  }
87353
87478
  } catch {
87354
87479
  }
@@ -87424,8 +87549,8 @@ var init_src4 = __esm({
87424
87549
  });
87425
87550
 
87426
87551
  // src/project-context.ts
87427
- import { resolve as resolve15, dirname as dirname11 } from "node:path";
87428
- import { existsSync as existsSync29 } from "node:fs";
87552
+ import { resolve as resolve17, dirname as dirname11 } from "node:path";
87553
+ import { existsSync as existsSync30 } from "node:fs";
87429
87554
  async function resolveProject(projectNameFlag, cwd = process.cwd(), globalDir) {
87430
87555
  const central = new CentralCore(globalDir);
87431
87556
  await central.init();
@@ -87501,10 +87626,10 @@ async function clearDefaultProject(globalDir) {
87501
87626
  await globalStore.updateSettings(rest);
87502
87627
  }
87503
87628
  async function detectProjectFromCwd(cwd, central) {
87504
- let currentDir = resolve15(cwd);
87629
+ let currentDir = resolve17(cwd);
87505
87630
  while (true) {
87506
- const kbPath = resolve15(currentDir, ".fusion", "fusion.db");
87507
- if (existsSync29(kbPath)) {
87631
+ const kbPath = resolve17(currentDir, ".fusion", "fusion.db");
87632
+ if (existsSync30(kbPath)) {
87508
87633
  const project = await central.getProjectByPath(currentDir);
87509
87634
  if (project) {
87510
87635
  return project;
@@ -87604,8 +87729,8 @@ __export(task_exports, {
87604
87729
  runTaskUpdate: () => runTaskUpdate
87605
87730
  });
87606
87731
  import { createInterface as createInterface2 } from "node:readline/promises";
87607
- import { watchFile, unwatchFile, statSync as statSync6, existsSync as existsSync30, readFileSync as readFileSync9 } from "node:fs";
87608
- import { join as join37 } from "node:path";
87732
+ import { watchFile, unwatchFile, statSync as statSync6, existsSync as existsSync31, readFileSync as readFileSync9 } from "node:fs";
87733
+ import { join as join36 } from "node:path";
87609
87734
  function asLocalProjectContext(store) {
87610
87735
  const cwd = process.cwd();
87611
87736
  return {
@@ -87725,9 +87850,9 @@ async function runTaskCreate(descriptionArg, attachFiles, depends, projectName,
87725
87850
  console.log(` Path: .fusion/tasks/${task.id}/`);
87726
87851
  if (attachFiles && attachFiles.length > 0) {
87727
87852
  const { readFile: readFile19 } = await import("node:fs/promises");
87728
- const { basename: basename9, extname: extname3, resolve: resolve17 } = await import("node:path");
87853
+ const { basename: basename9, extname: extname3, resolve: resolve19 } = await import("node:path");
87729
87854
  for (const filePath of attachFiles) {
87730
- const resolvedPath = resolve17(filePath);
87855
+ const resolvedPath = resolve19(filePath);
87731
87856
  const filename = basename9(resolvedPath);
87732
87857
  const ext = extname3(filename).toLowerCase();
87733
87858
  const mimeType = MIME_TYPES[ext];
@@ -87861,8 +87986,8 @@ async function runTaskLogs(id, options = {}, projectName) {
87861
87986
  printEntries(filteredEntries);
87862
87987
  if (options.follow) {
87863
87988
  const projectPath = projectContext?.projectPath ?? process.cwd();
87864
- const logPath = join37(projectPath, ".fusion", "tasks", id, "agent.log");
87865
- if (!existsSync30(logPath)) {
87989
+ const logPath = join36(projectPath, ".fusion", "tasks", id, "agent.log");
87990
+ if (!existsSync31(logPath)) {
87866
87991
  console.log(`
87867
87992
  Waiting for log file to be created...`);
87868
87993
  }
@@ -88021,8 +88146,8 @@ async function runTaskMerge(id, projectName) {
88021
88146
  async function runTaskAttach(id, filePath, projectName) {
88022
88147
  const { readFile: readFile19 } = await import("node:fs/promises");
88023
88148
  const { basename: basename9, extname: extname3 } = await import("node:path");
88024
- const { resolve: resolve17 } = await import("node:path");
88025
- const resolvedPath = resolve17(filePath);
88149
+ const { resolve: resolve19 } = await import("node:path");
88150
+ const resolvedPath = resolve19(filePath);
88026
88151
  const filename = basename9(resolvedPath);
88027
88152
  const ext = extname3(filename).toLowerCase();
88028
88153
  const mimeType = MIME_TYPES[ext];
@@ -88553,12 +88678,12 @@ async function promptText(question) {
88553
88678
  console.log(" (Enter your response. Type DONE on its own line when finished):\n");
88554
88679
  const rl = createInterface2({ input: process.stdin, output: process.stdout });
88555
88680
  const lines = [];
88556
- return new Promise((resolve17) => {
88681
+ return new Promise((resolve19) => {
88557
88682
  const askLine = () => {
88558
88683
  rl.question(" ").then((line) => {
88559
88684
  if (line.trim() === "DONE") {
88560
88685
  rl.close();
88561
- resolve17(lines.join("\n"));
88686
+ resolve19(lines.join("\n"));
88562
88687
  } else {
88563
88688
  lines.push(line);
88564
88689
  askLine();
@@ -88962,9 +89087,9 @@ async function runSkillsInstall(args, options) {
88962
89087
  stdio: "inherit",
88963
89088
  shell: true
88964
89089
  });
88965
- const exitCode = await new Promise((resolve17, reject) => {
89090
+ const exitCode = await new Promise((resolve19, reject) => {
88966
89091
  child.on("exit", (code) => {
88967
- resolve17(code ?? 1);
89092
+ resolve19(code ?? 1);
88968
89093
  });
88969
89094
  child.on("error", (err) => {
88970
89095
  reject(err);
@@ -88991,9 +89116,9 @@ init_src();
88991
89116
  init_gh_cli();
88992
89117
  import { Type as Type7 } from "typebox";
88993
89118
  import { StringEnum } from "@mariozechner/pi-ai";
88994
- import { resolve as resolve16, basename as basename8, extname as extname2, join as join38 } from "node:path";
89119
+ import { resolve as resolve18, basename as basename8, extname as extname2, join as join37 } from "node:path";
88995
89120
  import { readFile as readFile18 } from "node:fs/promises";
88996
- import { existsSync as existsSync31 } from "node:fs";
89121
+ import { existsSync as existsSync32 } from "node:fs";
88997
89122
  import { spawn as spawn9 } from "node:child_process";
88998
89123
  var MIME_TYPES2 = {
88999
89124
  ".png": "image/png",
@@ -89011,14 +89136,14 @@ var MIME_TYPES2 = {
89011
89136
  ".xml": "application/xml"
89012
89137
  };
89013
89138
  function resolveProjectRoot(cwd) {
89014
- let current = resolve16(cwd);
89139
+ let current = resolve18(cwd);
89015
89140
  while (true) {
89016
- if (existsSync31(join38(current, ".fusion"))) {
89141
+ if (existsSync32(join37(current, ".fusion"))) {
89017
89142
  return current;
89018
89143
  }
89019
- const parent = resolve16(current, "..");
89144
+ const parent = resolve18(current, "..");
89020
89145
  if (parent === current) {
89021
- return resolve16(cwd);
89146
+ return resolve18(cwd);
89022
89147
  }
89023
89148
  current = parent;
89024
89149
  }
@@ -89034,7 +89159,20 @@ async function getStore2(cwd) {
89034
89159
  return store;
89035
89160
  }
89036
89161
  function getFusionDir(cwd) {
89037
- return join38(resolveProjectRoot(cwd), ".fusion");
89162
+ return join37(resolveProjectRoot(cwd), ".fusion");
89163
+ }
89164
+ async function validateAssignableAgentId(cwd, agentId) {
89165
+ const { AgentStore: AgentStore2, isEphemeralAgent: isEphemeralAgent2 } = await Promise.resolve().then(() => (init_src(), src_exports));
89166
+ const agentStore = new AgentStore2({ rootDir: getFusionDir(cwd) });
89167
+ await agentStore.init();
89168
+ const agent = await agentStore.getAgent(agentId);
89169
+ if (!agent) {
89170
+ return `Agent ${agentId} not found`;
89171
+ }
89172
+ if (isEphemeralAgent2(agent)) {
89173
+ return `Cannot assign task to ephemeral/runtime agent ${agentId}`;
89174
+ }
89175
+ return null;
89038
89176
  }
89039
89177
  function formatTaskLine(t) {
89040
89178
  const label = t.title || t.description.slice(0, 60) + (t.description.length > 60 ? "\u2026" : "");
@@ -89097,6 +89235,16 @@ function kbExtension(pi) {
89097
89235
  }),
89098
89236
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
89099
89237
  const store = await getStore2(ctx.cwd);
89238
+ if (params.agentId !== void 0) {
89239
+ const error = await validateAssignableAgentId(ctx.cwd, params.agentId);
89240
+ if (error) {
89241
+ return {
89242
+ content: [{ type: "text", text: error }],
89243
+ isError: true,
89244
+ details: { error }
89245
+ };
89246
+ }
89247
+ }
89100
89248
  const task = await store.createTask({
89101
89249
  description: params.description.trim(),
89102
89250
  dependencies: params.depends,
@@ -89182,6 +89330,16 @@ Column: triage
89182
89330
  updatedFields.push("dependencies");
89183
89331
  }
89184
89332
  if (params.agentId !== void 0) {
89333
+ if (params.agentId !== null) {
89334
+ const error = await validateAssignableAgentId(ctx.cwd, params.agentId);
89335
+ if (error) {
89336
+ return {
89337
+ content: [{ type: "text", text: error }],
89338
+ isError: true,
89339
+ details: { error }
89340
+ };
89341
+ }
89342
+ }
89185
89343
  updates.assignedAgentId = params.agentId;
89186
89344
  updatedFields.push("agentId");
89187
89345
  }
@@ -89329,7 +89487,7 @@ Column: triage
89329
89487
  path: Type7.String({ description: "Path to the file to attach" })
89330
89488
  }),
89331
89489
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
89332
- const filePath = resolve16(ctx.cwd, params.path.replace(/^@/, ""));
89490
+ const filePath = resolve18(ctx.cwd, params.path.replace(/^@/, ""));
89333
89491
  const filename = basename8(filePath);
89334
89492
  const ext = extname2(filename).toLowerCase();
89335
89493
  const mimeType = MIME_TYPES2[ext];
@@ -90438,12 +90596,12 @@ Status: ${updated.status}`
90438
90596
  child.stderr?.on("data", (data) => {
90439
90597
  stderr += data.toString();
90440
90598
  });
90441
- const exitCode = await new Promise((resolve17) => {
90599
+ const exitCode = await new Promise((resolve19) => {
90442
90600
  child.on("exit", (code) => {
90443
- resolve17(code ?? 1);
90601
+ resolve19(code ?? 1);
90444
90602
  });
90445
90603
  child.on("error", () => {
90446
- resolve17(1);
90604
+ resolve19(1);
90447
90605
  });
90448
90606
  });
90449
90607
  try {