@runfusion/fusion 0.14.1 → 0.14.3

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 (40) hide show
  1. package/dist/bin.js +934 -166
  2. package/dist/client/assets/AgentDetailView-BBCnqhqI.js +18 -0
  3. package/dist/client/assets/{AgentsView-DoXb_amw.js → AgentsView-BY-Yq-Te.js} +3 -3
  4. package/dist/client/assets/ChatView-DkoJNxFW.js +1 -0
  5. package/dist/client/assets/{DevServerView-DbgM4tlT.js → DevServerView-qvs6pp6c.js} +1 -1
  6. package/dist/client/assets/{DirectoryPicker-DfmtfMiu.js → DirectoryPicker-BkAIXNrP.js} +1 -1
  7. package/dist/client/assets/{DocumentsView-_-Efkx_W.js → DocumentsView-BcaUGgaL.js} +1 -1
  8. package/dist/client/assets/{InsightsView-DUjcfW53.js → InsightsView-Dz9Ivclw.js} +1 -1
  9. package/dist/client/assets/{MemoryView-DxMPBb0q.js → MemoryView-BsweARBT.js} +1 -1
  10. package/dist/client/assets/{NodesView-BEBTI15s.js → NodesView-bAU-v4bJ.js} +1 -1
  11. package/dist/client/assets/{PiExtensionsManager-BpMYhHH_.js → PiExtensionsManager-C_U2g7y3.js} +2 -2
  12. package/dist/client/assets/{PluginManager-CPv7yQd3.js → PluginManager-pIDsTk5v.js} +1 -1
  13. package/dist/client/assets/{ResearchView-BrFvdyXT.js → ResearchView-D4Eib_uR.js} +1 -1
  14. package/dist/client/assets/{RoadmapsView-BDjLrtcj.js → RoadmapsView-BaGwsUGS.js} +1 -1
  15. package/dist/client/assets/{SettingsModal-CxDxiTRy.js → SettingsModal-BiZVi3cI.js} +1 -1
  16. package/dist/client/assets/SettingsModal-CRyg643t.js +31 -0
  17. package/dist/client/assets/{SetupWizardModal-DFUA4X3z.js → SetupWizardModal-BcIGBBpA.js} +1 -1
  18. package/dist/client/assets/{SkillMultiselect-BUWe5ujb.js → SkillMultiselect-DPARHJeQ.js} +1 -1
  19. package/dist/client/assets/{SkillsView-RAkqGX3y.js → SkillsView-Da_d_HPu.js} +1 -1
  20. package/dist/client/assets/{TodoView-Ceb0wrg1.js → TodoView-5rAeqYtV.js} +1 -1
  21. package/dist/client/assets/{folder-open-DcM-Vd6r.js → folder-open-CgjcFqww.js} +1 -1
  22. package/dist/client/assets/index-D1gTSlYB.css +1 -0
  23. package/dist/client/assets/{index-DH3aprf6.js → index-DoQ5ALYY.js} +150 -149
  24. package/dist/client/assets/{list-checks-ByGHVQpZ.js → list-checks-C9YWtF7h.js} +1 -1
  25. package/dist/client/assets/{star-DlEYI8GL.js → star-4nUh67-U.js} +1 -1
  26. package/dist/client/assets/{upload-DKshabz-.js → upload-CEt5-Bnq.js} +1 -1
  27. package/dist/client/assets/{users-X6tYPPBV.js → users-4I0JDmgO.js} +1 -1
  28. package/dist/client/index.html +2 -2
  29. package/dist/client/version.json +1 -1
  30. package/dist/extension.js +709 -146
  31. package/dist/pi-claude-cli/index.ts +2 -2
  32. package/dist/pi-claude-cli/package.json +1 -1
  33. package/dist/pi-claude-cli/src/__tests__/event-bridge.test.ts +107 -0
  34. package/dist/pi-claude-cli/src/event-bridge.ts +48 -4
  35. package/package.json +1 -1
  36. package/skill/fusion/references/engine-tools.md +0 -1
  37. package/dist/client/assets/AgentDetailView-B3KAsP2O.js +0 -18
  38. package/dist/client/assets/ChatView-BJ2c7wvd.js +0 -1
  39. package/dist/client/assets/SettingsModal-Cd-QGB0C.js +0 -31
  40. package/dist/client/assets/index-C1prPuSl.css +0 -1
package/dist/extension.js CHANGED
@@ -75,6 +75,7 @@ var init_settings_schema = __esm({
75
75
  favoriteModels: void 0,
76
76
  openrouterModelSync: true,
77
77
  updateCheckEnabled: true,
78
+ fnBinaryCheckEnabled: true,
78
79
  updateCheckFrequency: "daily",
79
80
  showGitHubStarButton: true,
80
81
  modelOnboardingComplete: void 0,
@@ -3290,6 +3291,12 @@ CREATE INDEX IF NOT EXISTS idxTodoItemsSortOrder ON todo_items(listId, sortOrder
3290
3291
  if (!inMemory && !isAbsolute(fusionDir)) {
3291
3292
  throw new Error(`[fusion] Database constructor requires an absolute fusionDir path, got: ${fusionDir}`);
3292
3293
  }
3294
+ if (!inMemory && /\.fusion[\\/]\.fusion(?:[\\/]|$)/.test(fusionDir)) {
3295
+ throw new Error(
3296
+ `[fusion] Refusing to open Database at nested .fusion/.fusion path: ${fusionDir}
3297
+ This means a caller passed a .fusion directory where a project root was expected. Audit the call site for an extra \`join(rootDir, '.fusion')\` step.`
3298
+ );
3299
+ }
3293
3300
  if (!inMemory && !existsSync(fusionDir)) {
3294
3301
  mkdirSync(fusionDir, { recursive: true });
3295
3302
  }
@@ -6016,6 +6023,7 @@ var init_agent_store = __esm({
6016
6023
  };
6017
6024
  const line = JSON.stringify(safeEntry) + "\n";
6018
6025
  await appendFile(this.runLogPath(agentId, runId), line, "utf-8");
6026
+ this.emit("run:log", agentId, runId, safeEntry);
6019
6027
  }
6020
6028
  /**
6021
6029
  * Read all log entries for a given run from its JSONL file.
@@ -36342,12 +36350,16 @@ var init_gh_cli = __esm({
36342
36350
 
36343
36351
  // ../core/src/fn-binary.ts
36344
36352
  import { spawn as spawn2 } from "node:child_process";
36345
- import { platform as platform2 } from "node:os";
36353
+ import { platform as platform2, tmpdir as tmpdir2 } from "node:os";
36346
36354
  function runProbe(command, args, timeoutMs) {
36347
36355
  return new Promise((resolve19) => {
36348
36356
  let stdout = "";
36349
36357
  let stderr = "";
36350
- const child = spawn2(command, args, { stdio: ["ignore", "pipe", "pipe"], shell: false });
36358
+ const child = spawn2(command, args, {
36359
+ stdio: ["ignore", "pipe", "pipe"],
36360
+ shell: false,
36361
+ cwd: tmpdir2()
36362
+ });
36351
36363
  const timer = setTimeout(() => {
36352
36364
  try {
36353
36365
  child.kill("SIGKILL");
@@ -37491,6 +37503,60 @@ var init_plugin_loader = __esm({
37491
37503
  }
37492
37504
  return runtimes;
37493
37505
  }
37506
+ /**
37507
+ * Get all skill contributions from loaded plugins.
37508
+ */
37509
+ getPluginSkills() {
37510
+ const skills = [];
37511
+ for (const [pluginId, plugin4] of this.plugins) {
37512
+ if (plugin4.skills) {
37513
+ for (const skill of plugin4.skills) {
37514
+ skills.push({ pluginId, skill });
37515
+ }
37516
+ }
37517
+ }
37518
+ return skills;
37519
+ }
37520
+ /**
37521
+ * Get all workflow step contributions from loaded plugins.
37522
+ */
37523
+ getPluginWorkflowSteps() {
37524
+ const steps = [];
37525
+ for (const [pluginId, plugin4] of this.plugins) {
37526
+ if (plugin4.workflowSteps) {
37527
+ for (const step of plugin4.workflowSteps) {
37528
+ steps.push({ pluginId, step });
37529
+ }
37530
+ }
37531
+ }
37532
+ return steps;
37533
+ }
37534
+ /**
37535
+ * Get all prompt contributions from loaded plugins.
37536
+ */
37537
+ getPluginPromptContributions() {
37538
+ const contributions = [];
37539
+ for (const [pluginId, plugin4] of this.plugins) {
37540
+ if (plugin4.promptContributions) {
37541
+ for (const contribution of plugin4.promptContributions.contributions) {
37542
+ contributions.push({ pluginId, contribution, config: plugin4.promptContributions });
37543
+ }
37544
+ }
37545
+ }
37546
+ return contributions;
37547
+ }
37548
+ /**
37549
+ * Get all setup metadata and hooks from loaded plugins.
37550
+ */
37551
+ getPluginSetupInfo() {
37552
+ const setups = [];
37553
+ for (const [pluginId, plugin4] of this.plugins) {
37554
+ if (plugin4.setup) {
37555
+ setups.push({ pluginId, manifest: plugin4.setup.manifest, hooks: plugin4.setup.hooks });
37556
+ }
37557
+ }
37558
+ return setups;
37559
+ }
37494
37560
  /**
37495
37561
  * Get all loaded plugin instances.
37496
37562
  */
@@ -37611,7 +37677,7 @@ async function syncBackupAutomation(automationStore, settings) {
37611
37677
  if (!AutomationStore2.isValidCron(schedule)) {
37612
37678
  throw new Error(`Invalid backup schedule: ${schedule}`);
37613
37679
  }
37614
- const command = "npx runfusion.ai backup --create";
37680
+ const command = "fn backup --create";
37615
37681
  if (existingSchedule) {
37616
37682
  return await automationStore.updateSchedule(existingSchedule.id, {
37617
37683
  scheduleType: "custom",
@@ -37644,7 +37710,7 @@ async function syncBackupRoutine(routineStore, settings) {
37644
37710
  if (!RoutineStore2.isValidCron(schedule)) {
37645
37711
  throw new Error(`Invalid backup schedule: ${schedule}`);
37646
37712
  }
37647
- const command = "npx runfusion.ai backup --create";
37713
+ const command = "fn backup --create";
37648
37714
  const input = {
37649
37715
  name: BACKUP_SCHEDULE_NAME,
37650
37716
  description: "Automatic database backup based on project settings",
@@ -49519,7 +49585,7 @@ var require_dist3 = __commonJS({
49519
49585
 
49520
49586
  // ../core/src/agent-companies-parser.ts
49521
49587
  import { existsSync as existsSync17, mkdtempSync, readdirSync as readdirSync2, readFileSync as readFileSync4, rmSync, statSync as statSync4 } from "node:fs";
49522
- import { tmpdir as tmpdir2 } from "node:os";
49588
+ import { tmpdir as tmpdir3 } from "node:os";
49523
49589
  import { join as join20, resolve as resolve9 } from "node:path";
49524
49590
  function slugifyAgentReference(value) {
49525
49591
  return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "");
@@ -49842,7 +49908,7 @@ async function extractTarArchive(archivePath, outputDir) {
49842
49908
  }
49843
49909
  async function parseCompanyArchive(archivePath) {
49844
49910
  const resolvedArchivePath = resolve9(archivePath);
49845
- const tempDir = mkdtempSync(join20(tmpdir2(), "agent-companies-"));
49911
+ const tempDir = mkdtempSync(join20(tmpdir3(), "agent-companies-"));
49846
49912
  try {
49847
49913
  if (resolvedArchivePath.endsWith(".tar.gz") || resolvedArchivePath.endsWith(".tgz")) {
49848
49914
  await extractTarArchive(resolvedArchivePath, tempDir);
@@ -55419,65 +55485,6 @@ ${lines.join("\n")}`
55419
55485
  }
55420
55486
  };
55421
55487
  }
55422
- function createIdentityTool({ agent, resolvedInstructions }) {
55423
- const identityParams = Type.Object({});
55424
- return {
55425
- name: "fn_identity",
55426
- label: "Identity Check",
55427
- description: "Return a structured summary of which soul, instructions, and memory are loaded for this heartbeat tick. Call this FIRST before any other tool.",
55428
- parameters: identityParams,
55429
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
55430
- execute: async (_id, _params, _signal, _onUpdate, _ctx) => {
55431
- const PREVIEW_CHARS = 500;
55432
- const INSTRUCTIONS_PREVIEW_CHARS = 1e3;
55433
- const MEMORY_PREVIEW_CHARS = 1e3;
55434
- const soulPresent = typeof agent.soul === "string" && agent.soul.trim().length > 0;
55435
- const instructionsPresent = resolvedInstructions.trim().length > 0;
55436
- const memoryPresent = typeof agent.memory === "string" && agent.memory.trim().length > 0;
55437
- const soulPreview = soulPresent ? agent.soul.slice(0, PREVIEW_CHARS) : "";
55438
- const instructionsPreview = instructionsPresent ? resolvedInstructions.slice(0, INSTRUCTIONS_PREVIEW_CHARS) : "";
55439
- const memoryPreview = memoryPresent ? agent.memory.slice(0, MEMORY_PREVIEW_CHARS) : "";
55440
- const result = {
55441
- agentId: agent.id,
55442
- name: agent.name,
55443
- role: agent.role,
55444
- soulPresent,
55445
- instructionsPresent,
55446
- memoryPresent,
55447
- soulPreview,
55448
- instructionsPreview,
55449
- memoryPreview
55450
- };
55451
- const lines = [
55452
- `agentId: ${result.agentId}`,
55453
- `name: ${result.name}`,
55454
- `role: ${result.role}`,
55455
- `soul: ${result.soulPresent ? "loaded" : "absent"}`,
55456
- `instructions: ${result.instructionsPresent ? "loaded" : "absent"}`,
55457
- `memory: ${result.memoryPresent ? "loaded" : "absent"}`
55458
- ];
55459
- if (result.soulPresent && result.soulPreview) {
55460
- lines.push(`
55461
- Soul preview (first ${PREVIEW_CHARS} chars):
55462
- ${result.soulPreview}`);
55463
- }
55464
- if (result.instructionsPresent && result.instructionsPreview) {
55465
- lines.push(`
55466
- Instructions preview (first ${INSTRUCTIONS_PREVIEW_CHARS} chars):
55467
- ${result.instructionsPreview}`);
55468
- }
55469
- if (result.memoryPresent && result.memoryPreview) {
55470
- lines.push(`
55471
- Memory preview (first ${MEMORY_PREVIEW_CHARS} chars):
55472
- ${result.memoryPreview}`);
55473
- }
55474
- return {
55475
- content: [{ type: "text", text: lines.join("\n") }],
55476
- details: result
55477
- };
55478
- }
55479
- };
55480
- }
55481
55488
  var taskCreateParams, taskLogParams, taskDocumentWriteParams, taskDocumentReadParams, reflectOnPerformanceParams, listAgentsParams, delegateTaskParams, sendMessageParams, readMessagesParams, memorySearchParams, memoryGetParams, researchRunParams, researchListParams, researchGetParams, researchCancelParams, memoryAppendParams, log10, AGENT_MEMORY_ROOT2, AGENT_MEMORY_FILENAME2, AGENT_DREAMS_FILENAME2, agentQmdRefreshState, AGENT_QMD_REFRESH_INTERVAL_MS, DAILY_AGENT_MEMORY_RE2;
55482
55489
  var init_agent_tools = __esm({
55483
55490
  "../engine/src/agent-tools.ts"() {
@@ -65013,6 +65020,20 @@ The tool prevents your session from being killed by the inactivity watchdog duri
65013
65020
  );
65014
65021
  this.activeStepExecutors.delete(task.id);
65015
65022
  }
65023
+ if (this.activeWorkflowStepSessions.has(task.id)) {
65024
+ executorLog.log(`${task.id} moved from in-progress to ${to} \u2014 terminating workflow step session`);
65025
+ this.pausedAborted.add(task.id);
65026
+ this.options.stuckTaskDetector?.untrackTask(task.id);
65027
+ const workflowSession = this.activeWorkflowStepSessions.get(task.id);
65028
+ const sessionWithAbort = workflowSession;
65029
+ if (typeof sessionWithAbort.abort === "function") {
65030
+ void sessionWithAbort.abort().catch((err) => {
65031
+ executorLog.warn(`Failed to abort workflow step session for ${task.id}: ${err}`);
65032
+ });
65033
+ }
65034
+ workflowSession.dispose();
65035
+ this.activeWorkflowStepSessions.delete(task.id);
65036
+ }
65016
65037
  this.disposeSubagentsForTask(task.id, `parent moved from in-progress to ${to}`);
65017
65038
  this.loopRecoveryState.delete(task.id);
65018
65039
  this.spawnedAgents.delete(task.id);
@@ -65045,8 +65066,42 @@ The tool prevents your session from being killed by the inactivity watchdog duri
65045
65066
  this.disposeSubagentsForTask(task.id, "task paused");
65046
65067
  return;
65047
65068
  }
65048
- if (!task.paused && task.column === "in-progress" && !this.activeSessions.has(task.id) && !this.activeStepExecutors.has(task.id)) {
65069
+ if (task.paused && this.activeWorkflowStepSessions.has(task.id)) {
65070
+ executorLog.log(`Pausing ${task.id} \u2014 terminating workflow step session`);
65071
+ this.pausedAborted.add(task.id);
65072
+ this.options.stuckTaskDetector?.untrackTask(task.id);
65073
+ const workflowSession = this.activeWorkflowStepSessions.get(task.id);
65074
+ const sessionWithAbort = workflowSession;
65075
+ if (typeof sessionWithAbort.abort === "function") {
65076
+ await sessionWithAbort.abort().catch(
65077
+ (err) => executorLog.warn(`Failed to abort workflow step session for pause ${task.id}: ${err}`)
65078
+ );
65079
+ }
65080
+ workflowSession.dispose();
65081
+ this.activeWorkflowStepSessions.delete(task.id);
65082
+ this.loopRecoveryState.delete(task.id);
65083
+ this.spawnedAgents.delete(task.id);
65084
+ this.stuckAborted.delete(task.id);
65085
+ this.disposeSubagentsForTask(task.id, "task paused");
65086
+ return;
65087
+ }
65088
+ if (!task.paused && task.column === "in-progress" && !this.activeSessions.has(task.id) && !this.activeStepExecutors.has(task.id) && !this.activeWorkflowStepSessions.has(task.id)) {
65049
65089
  if (!this.executing.has(task.id) && !this.resumingUnpaused.has(task.id) && !this.recoveringCompleted.has(task.id)) {
65090
+ const pauseLabel = await this.getExecutionPauseLabel();
65091
+ if (pauseLabel) {
65092
+ executorLog.log(`Skipping unpause resume for ${task.id} \u2014 ${pauseLabel} active`);
65093
+ return;
65094
+ }
65095
+ if (this.isTaskWorkComplete(task) && !task.mergeDetails) {
65096
+ this.recoveringCompleted.add(task.id);
65097
+ executorLog.log(`${task.id} unpaused with completed work and no session \u2014 recovering directly to in-review`);
65098
+ void this.recoverCompletedTask(task).catch(
65099
+ (err) => executorLog.error(`Failed to recover completed unpaused task ${task.id}:`, err)
65100
+ ).finally(() => {
65101
+ this.recoveringCompleted.delete(task.id);
65102
+ });
65103
+ return;
65104
+ }
65050
65105
  this.resumingUnpaused.add(task.id);
65051
65106
  executorLog.log(`Unpaused ${task.id} in-progress with no session \u2014 resuming execution`);
65052
65107
  try {
@@ -65165,6 +65220,22 @@ The tool prevents your session from being killed by the inactivity watchdog duri
65165
65220
  this.spawnedAgents.delete(taskId);
65166
65221
  this.stuckAborted.delete(taskId);
65167
65222
  }
65223
+ for (const [taskId, workflowSession] of this.activeWorkflowStepSessions) {
65224
+ executorLog.log(`Global pause \u2014 terminating workflow step session for ${taskId}`);
65225
+ this.pausedAborted.add(taskId);
65226
+ this.options.stuckTaskDetector?.untrackTask(taskId);
65227
+ const sessionWithAbort = workflowSession;
65228
+ if (typeof sessionWithAbort.abort === "function") {
65229
+ void sessionWithAbort.abort().catch((err) => {
65230
+ executorLog.warn(`Failed to abort workflow step session for ${taskId}: ${err}`);
65231
+ });
65232
+ }
65233
+ workflowSession.dispose();
65234
+ this.activeWorkflowStepSessions.delete(taskId);
65235
+ this.loopRecoveryState.delete(taskId);
65236
+ this.spawnedAgents.delete(taskId);
65237
+ this.stuckAborted.delete(taskId);
65238
+ }
65168
65239
  }
65169
65240
  });
65170
65241
  }
@@ -65182,6 +65253,8 @@ The tool prevents your session from being killed by the inactivity watchdog duri
65182
65253
  activeSessions = /* @__PURE__ */ new Map();
65183
65254
  /** Active step-session executors per task (mutually exclusive with activeSessions). */
65184
65255
  activeStepExecutors = /* @__PURE__ */ new Map();
65256
+ /** Active pre-merge workflow step sessions per task. */
65257
+ activeWorkflowStepSessions = /* @__PURE__ */ new Map();
65185
65258
  /**
65186
65259
  * Reviewer subagent sessions per task. Reviewers (`reviewer.ts`) create their
65187
65260
  * own AgentSessions that aren't part of `activeSessions`/`activeStepExecutors`,
@@ -65228,6 +65301,69 @@ The tool prevents your session from being killed by the inactivity watchdog duri
65228
65301
  await this.store.mergeTask(taskId);
65229
65302
  return "merged";
65230
65303
  }
65304
+ async getExecutionPauseLabel() {
65305
+ const settings = await this.store.getSettings();
65306
+ if (settings.globalPause) return "global pause";
65307
+ if (settings.enginePaused) return "engine pause";
65308
+ return null;
65309
+ }
65310
+ async shouldDeferCompletionForGlobalPause(taskId, context) {
65311
+ const settings = await this.store.getSettings();
65312
+ if (!settings.globalPause) {
65313
+ return false;
65314
+ }
65315
+ this.clearCompletedTaskWatchdog(taskId);
65316
+ executorLog.log(`${taskId}: completion handoff deferred \u2014 global pause active (${context})`);
65317
+ await this.store.logEntry(
65318
+ taskId,
65319
+ `Completion handoff deferred \u2014 global pause active (${context})`,
65320
+ void 0,
65321
+ this.currentRunContext
65322
+ ).catch(() => void 0);
65323
+ return true;
65324
+ }
65325
+ async shouldDeferWorkflowStepCompletion(taskId, context) {
65326
+ let latestTask = null;
65327
+ try {
65328
+ latestTask = await this.store.getTask(taskId);
65329
+ } catch {
65330
+ latestTask = null;
65331
+ }
65332
+ if (latestTask?.paused || this.pausedAborted.has(taskId)) {
65333
+ this.clearCompletedTaskWatchdog(taskId);
65334
+ executorLog.log(`${taskId}: completion handoff deferred \u2014 task paused (${context})`);
65335
+ await this.store.logEntry(
65336
+ taskId,
65337
+ `Completion handoff deferred \u2014 task paused (${context})`,
65338
+ void 0,
65339
+ this.currentRunContext
65340
+ ).catch(() => void 0);
65341
+ return true;
65342
+ }
65343
+ return this.shouldDeferCompletionForGlobalPause(taskId, context);
65344
+ }
65345
+ async parkTaskAfterWorkflowStepPause(taskId) {
65346
+ let latestTask = null;
65347
+ try {
65348
+ latestTask = await this.store.getTask(taskId);
65349
+ } catch {
65350
+ latestTask = null;
65351
+ }
65352
+ if (!latestTask?.paused) {
65353
+ return false;
65354
+ }
65355
+ executorLog.log(`${taskId}: workflow step interrupted by task pause \u2014 moving to todo`);
65356
+ await this.store.logEntry(
65357
+ taskId,
65358
+ "Execution paused during pre-merge workflow step \u2014 moved to todo",
65359
+ void 0,
65360
+ this.currentRunContext
65361
+ ).catch(() => void 0);
65362
+ if (latestTask.column === "in-progress") {
65363
+ await this.store.moveTask(taskId, "todo", { preserveResumeState: true });
65364
+ }
65365
+ return true;
65366
+ }
65231
65367
  /** Child agent sessions keyed by agent ID. Used for termination. */
65232
65368
  childSessions = /* @__PURE__ */ new Map();
65233
65369
  /** Total count of currently spawned agents (across all parents). */
@@ -65422,11 +65558,15 @@ The tool prevents your session from being killed by the inactivity watchdog duri
65422
65558
  this.clearCompletedTaskWatchdog(taskId);
65423
65559
  const handle = setTimeout(async () => {
65424
65560
  this.completedTaskWatchdogs.delete(taskId);
65425
- if (this.recoveringCompleted.has(taskId) || this.executing.has(taskId) || this.activeSessions.has(taskId) || this.activeStepExecutors.has(taskId) || this.resumingUnpaused.has(taskId)) {
65561
+ if (this.recoveringCompleted.has(taskId) || this.executing.has(taskId) || this.activeSessions.has(taskId) || this.activeStepExecutors.has(taskId) || this.activeWorkflowStepSessions.has(taskId) || this.resumingUnpaused.has(taskId)) {
65426
65562
  return;
65427
65563
  }
65428
65564
  this.recoveringCompleted.add(taskId);
65429
65565
  try {
65566
+ const pauseLabel = await this.getExecutionPauseLabel();
65567
+ if (pauseLabel) {
65568
+ return;
65569
+ }
65430
65570
  let currentTask = null;
65431
65571
  try {
65432
65572
  currentTask = await this.store.getTask(taskId);
@@ -65472,6 +65612,11 @@ The tool prevents your session from being killed by the inactivity watchdog duri
65472
65612
  * stuck.
65473
65613
  */
65474
65614
  async performWorkflowRerunBounce(taskId, worktreePath, preserveResumeState = true) {
65615
+ const pauseLabel = await this.getExecutionPauseLabel();
65616
+ if (pauseLabel) {
65617
+ executorLog.log(`${taskId}: workflow rerun deferred \u2014 ${pauseLabel} active`);
65618
+ return "deferred-paused";
65619
+ }
65475
65620
  if (this.workflowRerunPending.has(taskId)) {
65476
65621
  executorLog.warn(`${taskId}: workflow rerun bounce already in flight \u2014 skipping re-entry`);
65477
65622
  return "skipped-pending";
@@ -65482,6 +65627,10 @@ The tool prevents your session from being killed by the inactivity watchdog duri
65482
65627
  if (!latestTask) {
65483
65628
  throw new Error("task missing during workflow rerun bounce");
65484
65629
  }
65630
+ if (latestTask.paused) {
65631
+ executorLog.log(`${taskId}: workflow rerun deferred \u2014 task is paused`);
65632
+ return "deferred-paused";
65633
+ }
65485
65634
  if (latestTask.column === "in-progress") {
65486
65635
  const originalExecutionStartedAt = latestTask.executionStartedAt;
65487
65636
  if (preserveResumeState) {
@@ -65493,11 +65642,21 @@ The tool prevents your session from being killed by the inactivity watchdog duri
65493
65642
  worktree: worktreePath,
65494
65643
  executionStartedAt: originalExecutionStartedAt ?? null
65495
65644
  });
65645
+ const pauseLabelAfterTodo = await this.getExecutionPauseLabel();
65646
+ if (pauseLabelAfterTodo) {
65647
+ executorLog.log(`${taskId}: workflow rerun parked in todo \u2014 ${pauseLabelAfterTodo} became active during bounce`);
65648
+ return "deferred-paused";
65649
+ }
65496
65650
  await this.store.moveTask(taskId, "in-progress");
65497
65651
  return "bounced";
65498
65652
  }
65499
65653
  if (latestTask.column === "todo") {
65500
65654
  await this.store.updateTask(taskId, { worktree: worktreePath });
65655
+ const pauseLabelBeforeResume = await this.getExecutionPauseLabel();
65656
+ if (pauseLabelBeforeResume) {
65657
+ executorLog.log(`${taskId}: workflow rerun parked in todo \u2014 ${pauseLabelBeforeResume} became active before resume`);
65658
+ return "deferred-paused";
65659
+ }
65501
65660
  await this.store.moveTask(taskId, "in-progress");
65502
65661
  return "bounced";
65503
65662
  }
@@ -65513,8 +65672,10 @@ The tool prevents your session from being killed by the inactivity watchdog duri
65513
65672
  const outcome = await this.performWorkflowRerunBounce(taskId, worktreePath, preserveResumeState);
65514
65673
  if (outcome === "bounced") {
65515
65674
  executorLog.log(successMessage);
65516
- } else {
65675
+ } else if (outcome === "skipped-pending") {
65517
65676
  executorLog.warn(`${taskId}: rerun bounce skipped \u2014 another bounce already in flight`);
65677
+ } else {
65678
+ executorLog.log(`${taskId}: rerun bounce deferred while pause is active`);
65518
65679
  }
65519
65680
  } catch (err) {
65520
65681
  const errorMessage = err instanceof Error ? err.message : String(err);
@@ -65523,6 +65684,11 @@ The tool prevents your session from being killed by the inactivity watchdog duri
65523
65684
  }, 0);
65524
65685
  const watchdog = setTimeout(async () => {
65525
65686
  this.workflowRerunWatchdogs.delete(taskId);
65687
+ const pauseLabel = await this.getExecutionPauseLabel();
65688
+ if (pauseLabel) {
65689
+ executorLog.log(`${taskId}: workflow rerun watchdog skipped \u2014 ${pauseLabel} active`);
65690
+ return;
65691
+ }
65526
65692
  let currentTask = null;
65527
65693
  try {
65528
65694
  currentTask = await this.store.getTask(taskId);
@@ -65545,7 +65711,7 @@ The tool prevents your session from being killed by the inactivity watchdog duri
65545
65711
  const outcome = await this.performWorkflowRerunBounce(taskId, worktreePath, preserveResumeState);
65546
65712
  if (outcome === "bounced") {
65547
65713
  executorLog.warn(`${taskId}: workflow rerun watchdog retry succeeded`);
65548
- } else {
65714
+ } else if (outcome === "skipped-pending") {
65549
65715
  executorLog.error(
65550
65716
  `${taskId}: workflow rerun watchdog retry skipped \u2014 original bounce still in flight after ${WORKFLOW_RERUN_WATCHDOG_MS / 1e3}s; task may be stuck`
65551
65717
  );
@@ -65553,6 +65719,8 @@ The tool prevents your session from being killed by the inactivity watchdog duri
65553
65719
  taskId,
65554
65720
  `Workflow rerun watchdog retry skipped \u2014 original bounce still in flight after ${WORKFLOW_RERUN_WATCHDOG_MS / 1e3}s; task may be stuck`
65555
65721
  ).catch(() => void 0);
65722
+ } else {
65723
+ executorLog.log(`${taskId}: workflow rerun watchdog retry deferred while pause is active`);
65556
65724
  }
65557
65725
  } catch (err) {
65558
65726
  const errorMessage = err instanceof Error ? err.message : String(err);
@@ -65675,11 +65843,17 @@ The tool prevents your session from being killed by the inactivity watchdog duri
65675
65843
  */
65676
65844
  async recoverCompletedTask(task) {
65677
65845
  try {
65678
- if (this.executing.has(task.id) || this.activeSessions.has(task.id) || this.activeStepExecutors.has(task.id) || this.resumingUnpaused.has(task.id)) {
65846
+ if (this.executing.has(task.id) || this.activeSessions.has(task.id) || this.activeStepExecutors.has(task.id) || this.activeWorkflowStepSessions.has(task.id) || this.resumingUnpaused.has(task.id)) {
65679
65847
  executorLog.log(`${task.id}: skipping recoverCompletedTask \u2014 task has active execution in flight`);
65680
65848
  return false;
65681
65849
  }
65682
65850
  const settings = await this.store.getSettings();
65851
+ if (settings.globalPause || settings.enginePaused) {
65852
+ executorLog.log(
65853
+ `${task.id}: skipping recoverCompletedTask \u2014 ${settings.globalPause ? "global pause" : "engine pause"} active`
65854
+ );
65855
+ return false;
65856
+ }
65683
65857
  if (task.worktree && existsSync27(task.worktree)) {
65684
65858
  const modifiedFiles = await this.captureModifiedFiles(task.worktree, task.baseCommitSha);
65685
65859
  if (modifiedFiles.length > 0) {
@@ -65687,7 +65861,16 @@ The tool prevents your session from being killed by the inactivity watchdog duri
65687
65861
  executorLog.log(`${task.id}: recovered ${modifiedFiles.length} modified files`);
65688
65862
  }
65689
65863
  if (task.executionMode !== "fast") {
65864
+ if (await this.shouldDeferCompletionForGlobalPause(task.id, "before workflow steps during completed-task recovery")) {
65865
+ return false;
65866
+ }
65690
65867
  const workflowResult = await this.runWorkflowSteps(task, task.worktree, settings);
65868
+ if (workflowResult === "deferred-paused") {
65869
+ if (this.pausedAborted.has(task.id)) {
65870
+ this.pausedAborted.delete(task.id);
65871
+ }
65872
+ return false;
65873
+ }
65691
65874
  if (!workflowResult.allPassed) {
65692
65875
  await this.sendTaskBackForFix(task, task.worktree, workflowResult.feedback, workflowResult.stepName || "Unknown", "Workflow step failed during recovery", false);
65693
65876
  return true;
@@ -65696,6 +65879,9 @@ The tool prevents your session from being killed by the inactivity watchdog duri
65696
65879
  executorLog.log(`${task.id}: fast mode \u2014 skipping workflow steps on auto-recovery`);
65697
65880
  }
65698
65881
  }
65882
+ if (await this.shouldDeferCompletionForGlobalPause(task.id, "before in-review transition during completed-task recovery")) {
65883
+ return false;
65884
+ }
65699
65885
  await this.persistTokenUsage(task.id);
65700
65886
  await this.store.moveTask(task.id, "in-review");
65701
65887
  this.clearCompletedTaskWatchdog(task.id);
@@ -65761,6 +65947,13 @@ The tool prevents your session from being killed by the inactivity watchdog duri
65761
65947
  * directly to in-review without spawning a new agent session.
65762
65948
  */
65763
65949
  async resumeOrphaned() {
65950
+ const settings = await this.store.getSettings();
65951
+ if (settings.globalPause || settings.enginePaused) {
65952
+ executorLog.log(
65953
+ `resumeOrphaned skipped \u2014 ${settings.globalPause ? "global pause" : "engine pause"} is active`
65954
+ );
65955
+ return;
65956
+ }
65764
65957
  const tasks = await this.store.listTasks({ slim: true, column: "in-progress" });
65765
65958
  const inProgress = tasks.filter(
65766
65959
  (t) => t.column === "in-progress" && !this.executing.has(t.id) && !t.paused
@@ -66199,8 +66392,21 @@ The tool prevents your session from being killed by the inactivity watchdog duri
66199
66392
  await audit.filesystem({ type: "file:capture-modified", target: task.id, metadata: { files: modifiedFiles } });
66200
66393
  }
66201
66394
  this.scheduleCompletedTaskWatchdog(task.id, "step-session completion");
66395
+ if (await this.shouldDeferCompletionForGlobalPause(task.id, "before workflow steps after step-session completion")) {
66396
+ return;
66397
+ }
66202
66398
  if (executionMode !== "fast") {
66203
66399
  const workflowResult = await this.runWorkflowSteps(task, worktreePath, settings);
66400
+ if (workflowResult === "deferred-paused") {
66401
+ if (await this.parkTaskAfterWorkflowStepPause(task.id)) {
66402
+ this.pausedAborted.delete(task.id);
66403
+ return;
66404
+ }
66405
+ if (this.pausedAborted.has(task.id)) {
66406
+ this.pausedAborted.delete(task.id);
66407
+ }
66408
+ return;
66409
+ }
66204
66410
  if (!workflowResult.allPassed) {
66205
66411
  if (workflowResult.revisionRequested) {
66206
66412
  await this.handleWorkflowRevisionRequest(task, worktreePath, workflowResult.feedback, workflowResult.stepName);
@@ -66218,6 +66424,9 @@ The tool prevents your session from being killed by the inactivity watchdog duri
66218
66424
  await this.store.logEntry(task.id, "Fast mode \u2014 pre-merge workflow steps skipped", void 0, this.currentRunContext);
66219
66425
  }
66220
66426
  await this.store.updateTask(task.id, { workflowStepRetries: void 0, taskDoneRetryCount: null });
66427
+ if (await this.shouldDeferCompletionForGlobalPause(task.id, "before in-review transition after step-session completion")) {
66428
+ return;
66429
+ }
66221
66430
  await this.store.moveTask(task.id, "in-review");
66222
66431
  this.clearCompletedTaskWatchdog(task.id);
66223
66432
  await audit.database({ type: "task:move", target: task.id, metadata: { to: "in-review" } });
@@ -66570,6 +66779,9 @@ The tool prevents your session from being killed by the inactivity watchdog duri
66570
66779
  this.pausedAborted.delete(task.id);
66571
66780
  wasPaused = true;
66572
66781
  if (await this.shouldFinalizeCompletedTask(task.id, taskDone)) {
66782
+ if (await this.shouldDeferCompletionForGlobalPause(task.id, "paused after completion")) {
66783
+ return;
66784
+ }
66573
66785
  executorLog.log(`${task.id} paused after completion (graceful session exit) \u2014 finalizing to in-review`);
66574
66786
  await this.store.logEntry(task.id, "Execution paused after completion \u2014 finalizing to in-review");
66575
66787
  await this.persistTokenUsage(task.id);
@@ -66606,8 +66818,23 @@ The tool prevents your session from being killed by the inactivity watchdog duri
66606
66818
  executorLog.log(`${task.id}: captured ${modifiedFiles.length} modified files`);
66607
66819
  }
66608
66820
  this.scheduleCompletedTaskWatchdog(task.id, "task completion");
66821
+ if (await this.shouldDeferCompletionForGlobalPause(task.id, "before workflow steps after task completion")) {
66822
+ return;
66823
+ }
66609
66824
  if (executionMode !== "fast") {
66610
66825
  const workflowResult = await this.runWorkflowSteps(task, worktreePath, settings);
66826
+ if (workflowResult === "deferred-paused") {
66827
+ if (await this.parkTaskAfterWorkflowStepPause(task.id)) {
66828
+ this.pausedAborted.delete(task.id);
66829
+ wasPaused = true;
66830
+ return;
66831
+ }
66832
+ if (this.pausedAborted.has(task.id)) {
66833
+ this.pausedAborted.delete(task.id);
66834
+ wasPaused = true;
66835
+ }
66836
+ return;
66837
+ }
66611
66838
  if (!workflowResult.allPassed) {
66612
66839
  if (workflowResult.revisionRequested) {
66613
66840
  await this.handleWorkflowRevisionRequest(task, worktreePath, workflowResult.feedback, workflowResult.stepName);
@@ -66625,6 +66852,9 @@ The tool prevents your session from being killed by the inactivity watchdog duri
66625
66852
  await this.store.logEntry(task.id, "Fast mode \u2014 pre-merge workflow steps skipped", void 0, this.currentRunContext);
66626
66853
  }
66627
66854
  await this.store.updateTask(task.id, { workflowStepRetries: void 0, taskDoneRetryCount: null });
66855
+ if (await this.shouldDeferCompletionForGlobalPause(task.id, "before in-review transition after task completion")) {
66856
+ return;
66857
+ }
66628
66858
  await this.persistTokenUsage(task.id);
66629
66859
  await this.store.moveTask(task.id, "in-review");
66630
66860
  this.clearCompletedTaskWatchdog(task.id);
@@ -66749,8 +66979,23 @@ The tool prevents your session from being killed by the inactivity watchdog duri
66749
66979
  executorLog.log(`${task.id}: captured ${modifiedFiles.length} modified files`);
66750
66980
  }
66751
66981
  this.scheduleCompletedTaskWatchdog(task.id, "task completion retry");
66982
+ if (await this.shouldDeferCompletionForGlobalPause(task.id, "before workflow steps after task completion retry")) {
66983
+ return;
66984
+ }
66752
66985
  if (executionMode !== "fast") {
66753
66986
  const workflowResult = await this.runWorkflowSteps(task, worktreePath, settings);
66987
+ if (workflowResult === "deferred-paused") {
66988
+ if (await this.parkTaskAfterWorkflowStepPause(task.id)) {
66989
+ this.pausedAborted.delete(task.id);
66990
+ wasPaused = true;
66991
+ return;
66992
+ }
66993
+ if (this.pausedAborted.has(task.id)) {
66994
+ this.pausedAborted.delete(task.id);
66995
+ wasPaused = true;
66996
+ }
66997
+ return;
66998
+ }
66754
66999
  if (!workflowResult.allPassed) {
66755
67000
  if (workflowResult.revisionRequested) {
66756
67001
  await this.handleWorkflowRevisionRequest(task, worktreePath, workflowResult.feedback, workflowResult.stepName);
@@ -66764,6 +67009,9 @@ The tool prevents your session from being killed by the inactivity watchdog duri
66764
67009
  await this.store.logEntry(task.id, "Fast mode \u2014 pre-merge workflow steps skipped", void 0, this.currentRunContext);
66765
67010
  }
66766
67011
  await this.store.updateTask(task.id, { workflowStepRetries: void 0, taskDoneRetryCount: null });
67012
+ if (await this.shouldDeferCompletionForGlobalPause(task.id, "before in-review transition after task completion retry")) {
67013
+ return;
67014
+ }
66767
67015
  await this.persistTokenUsage(task.id);
66768
67016
  await this.store.moveTask(task.id, "in-review");
66769
67017
  this.clearCompletedTaskWatchdog(task.id);
@@ -66856,6 +67104,9 @@ The tool prevents your session from being killed by the inactivity watchdog duri
66856
67104
  } else if (this.pausedAborted.has(task.id)) {
66857
67105
  this.pausedAborted.delete(task.id);
66858
67106
  if (await this.shouldFinalizeCompletedTask(task.id, taskDone)) {
67107
+ if (await this.shouldDeferCompletionForGlobalPause(task.id, "paused after completion")) {
67108
+ return;
67109
+ }
66859
67110
  executorLog.log(`${task.id} paused after completion \u2014 finalizing to in-review`);
66860
67111
  await this.store.logEntry(task.id, "Execution paused after completion \u2014 finalizing to in-review", void 0, this.currentRunContext);
66861
67112
  await this.persistTokenUsage(task.id);
@@ -67214,22 +67465,28 @@ The tool prevents your session from being killed by the inactivity watchdog duri
67214
67465
  if (params.summary) {
67215
67466
  await store.updateTask(taskId, { summary: params.summary });
67216
67467
  }
67217
- await store.updateTask(taskId, { paused: false, status: null });
67468
+ const settings = await store.getSettings();
67469
+ const hardPauseActive = Boolean(task.paused || settings.globalPause);
67470
+ if (hardPauseActive) {
67471
+ await store.updateTask(taskId, { status: null });
67472
+ } else {
67473
+ await store.updateTask(taskId, { paused: false, status: null });
67474
+ }
67218
67475
  await store.logEntry(taskId, "Task marked done by agent");
67219
67476
  const latestTask = await store.getTask(taskId);
67220
67477
  let latestColumn = latestTask.column;
67221
67478
  if (latestColumn === "todo") {
67222
67479
  await store.logEntry(
67223
67480
  taskId,
67224
- "fn_task_done called while task was in todo \u2014 promoting to in-progress before completion handoff"
67481
+ hardPauseActive ? "fn_task_done called while task was in todo during pause \u2014 promoting to in-progress for deferred completion handoff" : "fn_task_done called while task was in todo \u2014 promoting to in-progress before completion handoff"
67225
67482
  );
67226
67483
  await store.moveTask(taskId, "in-progress");
67227
67484
  latestColumn = "in-progress";
67228
67485
  }
67229
- if (latestColumn === "in-progress") {
67486
+ if (latestColumn === "in-progress" && !hardPauseActive) {
67230
67487
  this.scheduleCompletedTaskWatchdog(taskId, "fn_task_done");
67231
67488
  }
67232
- 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.";
67489
+ const successMessage = hardPauseActive ? "Task marked complete. Completion handoff deferred until pause is cleared." : params.summary ? "Task marked complete with summary. All steps done. Moving to in-review." : "Task marked complete. All steps done. Moving to in-review.";
67233
67490
  return {
67234
67491
  content: [{ type: "text", text: successMessage }],
67235
67492
  details: {}
@@ -67804,6 +68061,9 @@ ${failureFeedback}
67804
68061
  await this.store.updateTask(task.id, { workflowStepResults: results });
67805
68062
  continue;
67806
68063
  }
68064
+ if (await this.shouldDeferWorkflowStepCompletion(task.id, `before workflow step '${ws.name}'`)) {
68065
+ return "deferred-paused";
68066
+ }
67807
68067
  await this.store.logEntry(task.id, `[pre-merge] Starting workflow step: ${ws.name} (${stepMode} mode)`);
67808
68068
  executorLog.log(`${task.id} \u2014 [pre-merge] running workflow step: ${ws.name} (${stepMode} mode)`);
67809
68069
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
@@ -67818,6 +68078,9 @@ ${failureFeedback}
67818
68078
  await this.store.updateTask(task.id, { workflowStepResults: results });
67819
68079
  try {
67820
68080
  const result = stepMode === "script" ? await this.executeScriptWorkflowStep(task, ws, worktreePath, settings) : await this.executeWorkflowStep(task, ws, worktreePath, settings);
68081
+ if (await this.shouldDeferWorkflowStepCompletion(task.id, `workflow step '${ws.name}'`)) {
68082
+ return "deferred-paused";
68083
+ }
67821
68084
  const completedAt = (/* @__PURE__ */ new Date()).toISOString();
67822
68085
  if (result.success) {
67823
68086
  await this.store.logEntry(task.id, `[timing] Workflow step '${ws.name}' completed in ${Date.now() - stepStartedAtMs}ms`);
@@ -67883,6 +68146,9 @@ ${failureFeedback}
67883
68146
  };
67884
68147
  }
67885
68148
  } catch (err) {
68149
+ if (await this.shouldDeferWorkflowStepCompletion(task.id, `workflow step '${ws.name}'`)) {
68150
+ return "deferred-paused";
68151
+ }
67886
68152
  const { message: errorMessage, detail: errorDetail, stack: errorStack } = formatError(err);
67887
68153
  const completedAt = (/* @__PURE__ */ new Date()).toISOString();
67888
68154
  await this.store.logEntry(
@@ -68048,6 +68314,7 @@ and show an appropriate message to the user.\`
68048
68314
  task.id,
68049
68315
  `Workflow step '${workflowStep.name}' using model: ${describeModel(session)}${useOverride && attemptLabel === "primary" ? " (workflow step override)" : ""}${attemptLabel === "fallback" ? " (fallback after timeout)" : ""}`
68050
68316
  );
68317
+ this.activeWorkflowStepSessions.set(task.id, session);
68051
68318
  let output = "";
68052
68319
  session.subscribe((event) => {
68053
68320
  if (event.type === "message_update") {
@@ -68120,6 +68387,10 @@ Review the work done in this worktree and evaluate it against the criteria in yo
68120
68387
  return { success: false, error: errorMessage };
68121
68388
  } finally {
68122
68389
  if (timeoutHandle) clearTimeout(timeoutHandle);
68390
+ const activeWorkflowStepSession = this.activeWorkflowStepSessions.get(task.id);
68391
+ if (activeWorkflowStepSession === session) {
68392
+ this.activeWorkflowStepSessions.delete(task.id);
68393
+ }
68123
68394
  void timedOut;
68124
68395
  }
68125
68396
  };
@@ -69863,6 +70134,15 @@ var init_scheduler = __esm({
69863
70134
  schedulerLog.log(`Task ${task.id} is paused \u2014 skipping dispatch`);
69864
70135
  continue;
69865
70136
  }
70137
+ const latestSettings = await this.store.getSettings();
70138
+ if (latestSettings.globalPause) {
70139
+ schedulerLog.log(`Task ${task.id} dispatch aborted \u2014 globalPause became active mid-pass`);
70140
+ continue;
70141
+ }
70142
+ if (latestSettings.enginePaused) {
70143
+ schedulerLog.log(`Task ${task.id} dispatch aborted \u2014 enginePaused became active mid-pass`);
70144
+ continue;
70145
+ }
69866
70146
  let effectiveNode = resolveEffectiveNode(freshTask, settings);
69867
70147
  schedulerLog.log(`Task ${task.id} routed to node=${effectiveNode.nodeId ?? "local"} (source=${effectiveNode.source})`);
69868
70148
  if (effectiveNode.nodeId !== void 0 && this.options.nodeHealthMonitor) {
@@ -71983,6 +72263,7 @@ Rules:
71983
72263
 
71984
72264
  // ../engine/src/agent-heartbeat.ts
71985
72265
  import { Type as Type6 } from "@mariozechner/pi-ai";
72266
+ import { createHash as createHash5 } from "node:crypto";
71986
72267
  function isBlockedStateDuplicate(current, previous) {
71987
72268
  return current.blockedBy === previous.blockedBy && current.contextHash === previous.contextHash;
71988
72269
  }
@@ -71992,39 +72273,30 @@ function truncatePrompt(text, maxChars) {
71992
72273
 
71993
72274
  ... (truncated, ${text.length} chars)`;
71994
72275
  }
72276
+ function shortContentHash(value) {
72277
+ return createHash5("sha256").update(value).digest("hex").slice(0, 8);
72278
+ }
71995
72279
  function buildIdentitySnapshot(args) {
71996
72280
  const { agent, resolvedInstructions } = args;
71997
- const SOUL_PREVIEW = 500;
71998
- const INSTR_PREVIEW = 1e3;
71999
- const MEM_PREVIEW = 1e3;
72000
- const soulPresent = typeof agent.soul === "string" && agent.soul.trim().length > 0;
72001
- const instrPresent = resolvedInstructions.trim().length > 0;
72002
- const memPresent = typeof agent.memory === "string" && agent.memory.trim().length > 0;
72003
- const lines = [
72281
+ const soulTrimmed = typeof agent.soul === "string" ? agent.soul.trim() : "";
72282
+ const instrTrimmed = resolvedInstructions.trim();
72283
+ const memTrimmed = typeof agent.memory === "string" ? agent.memory.trim() : "";
72284
+ const formatField = (trimmed) => {
72285
+ if (!trimmed) return "absent";
72286
+ return `loaded (${trimmed.length} chars, sha256:${shortContentHash(trimmed)})`;
72287
+ };
72288
+ return [
72004
72289
  "## Identity Snapshot",
72005
72290
  "",
72006
- "Verify these match what you expect. Surface any anomalies in your first text output before acting.",
72291
+ "Full content is in the Custom Instructions section of your system prompt. Surface anomalies in your first text output before acting.",
72007
72292
  "",
72008
72293
  `- agentId: ${agent.id}`,
72009
72294
  `- name: ${agent.name}`,
72010
72295
  `- role: ${agent.role}`,
72011
- `- soul: ${soulPresent ? "loaded" : "absent"}`,
72012
- `- instructions: ${instrPresent ? "loaded" : "absent"}`,
72013
- `- memory: ${memPresent ? "loaded" : "absent"}`
72014
- ];
72015
- if (soulPresent) {
72016
- const preview = agent.soul.trim().slice(0, SOUL_PREVIEW);
72017
- lines.push("", `### Soul (first ${SOUL_PREVIEW} chars)`, preview);
72018
- }
72019
- if (instrPresent) {
72020
- const preview = resolvedInstructions.trim().slice(0, INSTR_PREVIEW);
72021
- lines.push("", `### Instructions (first ${INSTR_PREVIEW} chars)`, preview);
72022
- }
72023
- if (memPresent) {
72024
- const preview = agent.memory.trim().slice(0, MEM_PREVIEW);
72025
- lines.push("", `### Memory (first ${MEM_PREVIEW} chars)`, preview);
72026
- }
72027
- return lines.join("\n");
72296
+ `- soul: ${formatField(soulTrimmed)}`,
72297
+ `- instructions: ${formatField(instrTrimmed)}`,
72298
+ `- memory: ${formatField(memTrimmed)}`
72299
+ ].join("\n");
72028
72300
  }
72029
72301
  async function getHeartbeatMemorySettings(taskStore) {
72030
72302
  const maybeGetSettings = taskStore.getSettings;
@@ -72206,9 +72478,8 @@ When sending messages:
72206
72478
  1. **Identity & context** \u2014 review the **Identity Snapshot** at the top of
72207
72479
  this prompt. Confirm your role, soul, instructions, and memory match what
72208
72480
  you expect, and surface any anomalies in your first text output before
72209
- doing anything else. (If fn_identity is available in your runtime you may
72210
- also call it for full structured detail; the snapshot above is the
72211
- authoritative source.)
72481
+ doing anything else. The full content is in the Custom Instructions
72482
+ section of your system prompt.
72212
72483
  2. **Inbox** \u2014 when fn_read_messages is available, call it. Process any pending
72213
72484
  messages first; reply with reply_to_message_id when answering.
72214
72485
  3. **Wake delta** \u2014 read the Wake Delta block above. The wake reason is the
@@ -72234,9 +72505,8 @@ a bug. Do not loop on the same plan across heartbeats without recording why.`;
72234
72505
  1. **Identity & context** \u2014 review the **Identity Snapshot** at the top of
72235
72506
  this prompt. Confirm your role, soul, instructions, and memory match what
72236
72507
  you expect, and surface any anomalies in your first text output before
72237
- doing anything else. (If fn_identity is available in your runtime you may
72238
- also call it for full structured detail; the snapshot above is the
72239
- authoritative source.)
72508
+ doing anything else. The full content is in the Custom Instructions
72509
+ section of your system prompt.
72240
72510
  2. **Inbox** \u2014 when fn_read_messages is available, call it. Process any pending
72241
72511
  messages first; reply with reply_to_message_id when answering.
72242
72512
  3. **Wake delta** \u2014 read the Wake Delta block above. The wake reason is the
@@ -73038,7 +73308,6 @@ not loop on the same plan across heartbeats without recording why.`;
73038
73308
  baseHeartbeatSystemPrompt,
73039
73309
  [resolvedInstructionsForIdentity, memoryInstructions].filter((part) => part.trim()).join("\n\n")
73040
73310
  );
73041
- heartbeatTools.push(createIdentityTool({ agent, resolvedInstructions: resolvedInstructionsForIdentity }));
73042
73311
  heartbeatTools.push(heartbeatDoneTool);
73043
73312
  if (isNoTaskRun) {
73044
73313
  agentLogger = new AgentLogger({
@@ -75011,6 +75280,36 @@ function execCommand(command, options) {
75011
75280
  });
75012
75281
  });
75013
75282
  }
75283
+ function isInProcessBackupCommand(command) {
75284
+ if (!command) return false;
75285
+ const trimmed = command.trim();
75286
+ if (!trimmed) return false;
75287
+ if (SHELL_METACHARACTERS_REGEX.test(trimmed)) return false;
75288
+ const tokens = trimmed.split(/\s+/).map((tok) => tok.toLowerCase());
75289
+ let cursor = 0;
75290
+ if (tokens[cursor] === "npx") {
75291
+ cursor += 1;
75292
+ while (cursor < tokens.length) {
75293
+ const tok = tokens[cursor];
75294
+ if (tok === void 0 || !tok.startsWith("-")) break;
75295
+ const takesValue = (tok === "-p" || tok === "--package") && cursor + 1 < tokens.length && tokens[cursor + 1] !== void 0 && !tokens[cursor + 1].startsWith("-");
75296
+ cursor += takesValue ? 2 : 1;
75297
+ }
75298
+ }
75299
+ const binary = tokens[cursor];
75300
+ if (!binary || !FUSION_BINARY_TOKENS.has(binary)) return false;
75301
+ cursor += 1;
75302
+ if (tokens[cursor] !== "backup") return false;
75303
+ cursor += 1;
75304
+ if (tokens[cursor] !== "--create") return false;
75305
+ cursor += 1;
75306
+ for (; cursor < tokens.length; cursor += 1) {
75307
+ const tok = tokens[cursor];
75308
+ if (!tok) continue;
75309
+ if (!tok.startsWith("-")) return false;
75310
+ }
75311
+ return true;
75312
+ }
75014
75313
  async function createAiPromptExecutor(cwd) {
75015
75314
  const disposeLog = createLogger2("cron-runner");
75016
75315
  return async (prompt, modelProvider, modelId) => {
@@ -75050,7 +75349,7 @@ function truncateOutput(stdout, stderr) {
75050
75349
  }
75051
75350
  return combined;
75052
75351
  }
75053
- var log14, DEFAULT_TIMEOUT_MS6, MAX_BUFFER, MAX_OUTPUT_LENGTH, DEFAULT_POLL_INTERVAL_MS, MIN_POLL_INTERVAL_MS, CronRunner, AI_AUTOMATION_SYSTEM_PROMPT;
75352
+ var log14, FUSION_BINARY_TOKENS, SHELL_METACHARACTERS_REGEX, DEFAULT_TIMEOUT_MS6, MAX_BUFFER, MAX_OUTPUT_LENGTH, DEFAULT_POLL_INTERVAL_MS, MIN_POLL_INTERVAL_MS, CronRunner, AI_AUTOMATION_SYSTEM_PROMPT;
75054
75353
  var init_cron_runner = __esm({
75055
75354
  "../engine/src/cron-runner.ts"() {
75056
75355
  "use strict";
@@ -75059,6 +75358,14 @@ var init_cron_runner = __esm({
75059
75358
  init_shell_utils();
75060
75359
  init_pi();
75061
75360
  log14 = createLogger2("cron-runner");
75361
+ FUSION_BINARY_TOKENS = /* @__PURE__ */ new Set([
75362
+ "fn",
75363
+ "fusion",
75364
+ "runfusion",
75365
+ "runfusion.ai",
75366
+ "@runfusion/fusion"
75367
+ ]);
75368
+ SHELL_METACHARACTERS_REGEX = /[&|;<>`$()]/;
75062
75369
  DEFAULT_TIMEOUT_MS6 = 5 * 60 * 1e3;
75063
75370
  MAX_BUFFER = 1024 * 1024;
75064
75371
  MAX_OUTPUT_LENGTH = 10 * 1024;
@@ -75199,6 +75506,9 @@ var init_cron_runner = __esm({
75199
75506
  */
75200
75507
  async executeLegacyCommand(schedule, startedAt) {
75201
75508
  log14.log(`Executing ${schedule.name} (${schedule.id}): ${schedule.command}`);
75509
+ if (isInProcessBackupCommand(schedule.command)) {
75510
+ return this.executeBackupInProcess(schedule, startedAt);
75511
+ }
75202
75512
  try {
75203
75513
  const timeoutMs = schedule.timeoutMs ?? DEFAULT_TIMEOUT_MS6;
75204
75514
  const { stdout, stderr } = await execCommand(schedule.command, {
@@ -75229,6 +75539,47 @@ var init_cron_runner = __esm({
75229
75539
  };
75230
75540
  }
75231
75541
  }
75542
+ /**
75543
+ * Run an auto-backup schedule in-process via the engine's open TaskStore,
75544
+ * bypassing the shell-out that would otherwise invoke an outdated fusion
75545
+ * binary on PATH. See `isInProcessBackupCommand` for the matching contract.
75546
+ */
75547
+ async executeBackupInProcess(schedule, startedAt) {
75548
+ const action = await this.runBackupActionInProcess();
75549
+ if (action.success) {
75550
+ log14.log(`\u2713 ${schedule.name} completed in-process`);
75551
+ } else {
75552
+ log14.warn(`\u2717 ${schedule.name} in-process backup ${action.error ? `threw: ${action.error}` : `reported failure: ${action.output}`}`);
75553
+ }
75554
+ return {
75555
+ success: action.success,
75556
+ output: action.output,
75557
+ error: action.error,
75558
+ startedAt,
75559
+ completedAt: (/* @__PURE__ */ new Date()).toISOString()
75560
+ };
75561
+ }
75562
+ /**
75563
+ * Shared in-process backup execution used by both the legacy-command path
75564
+ * and the command-step path. Returns the success/output/error tuple in
75565
+ * a shape that callers can wrap into either a run or a step result.
75566
+ */
75567
+ async runBackupActionInProcess() {
75568
+ try {
75569
+ const { runBackupCommand: runBackupCommand2 } = await Promise.resolve().then(() => (init_src(), src_exports));
75570
+ const fusionDir = this.store.getFusionDir();
75571
+ const settings = await this.store.getSettings();
75572
+ const result = await runBackupCommand2(fusionDir, settings);
75573
+ return {
75574
+ success: result.success,
75575
+ output: truncateOutput(result.output ?? "", ""),
75576
+ error: result.success ? void 0 : result.output
75577
+ };
75578
+ } catch (err) {
75579
+ const message = err instanceof Error ? err.message : String(err);
75580
+ return { success: false, output: "", error: message };
75581
+ }
75582
+ }
75232
75583
  /**
75233
75584
  * Execute multiple steps sequentially.
75234
75585
  * Aggregates per-step results into an overall AutomationRunResult.
@@ -75317,6 +75668,19 @@ var init_cron_runner = __esm({
75317
75668
  completedAt: (/* @__PURE__ */ new Date()).toISOString()
75318
75669
  };
75319
75670
  }
75671
+ if (isInProcessBackupCommand(step.command)) {
75672
+ const action = await this.runBackupActionInProcess();
75673
+ return {
75674
+ stepId: step.id,
75675
+ stepName: step.name,
75676
+ stepIndex,
75677
+ success: action.success,
75678
+ output: action.output,
75679
+ error: action.error,
75680
+ startedAt,
75681
+ completedAt: (/* @__PURE__ */ new Date()).toISOString()
75682
+ };
75683
+ }
75320
75684
  try {
75321
75685
  const { stdout, stderr } = await execCommand(step.command, {
75322
75686
  timeout: timeoutMs,
@@ -75508,6 +75872,7 @@ var init_routine_runner = __esm({
75508
75872
  "../engine/src/routine-runner.ts"() {
75509
75873
  "use strict";
75510
75874
  import_cron_parser4 = __toESM(require_dist2(), 1);
75875
+ init_cron_runner();
75511
75876
  init_logger2();
75512
75877
  init_shell_utils();
75513
75878
  log15 = createLogger2("routine-runner");
@@ -75665,6 +76030,30 @@ var init_routine_runner = __esm({
75665
76030
  return this.executeCommand(routine.command ?? "", routine.timeoutMs, startedAt);
75666
76031
  }
75667
76032
  async executeCommand(command, timeoutMs, startedAt) {
76033
+ if (isInProcessBackupCommand(command) && this.options.taskStore) {
76034
+ try {
76035
+ const { runBackupCommand: runBackupCommand2 } = await Promise.resolve().then(() => (init_src(), src_exports));
76036
+ const fusionDir = this.options.taskStore.getFusionDir();
76037
+ const settings = await this.options.taskStore.getSettings();
76038
+ const result = await runBackupCommand2(fusionDir, settings);
76039
+ return {
76040
+ success: result.success,
76041
+ output: truncateOutput2(result.output ?? "", ""),
76042
+ error: result.success ? void 0 : result.output,
76043
+ startedAt,
76044
+ completedAt: (/* @__PURE__ */ new Date()).toISOString()
76045
+ };
76046
+ } catch (err) {
76047
+ const message = err instanceof Error ? err.message : String(err);
76048
+ return {
76049
+ success: false,
76050
+ output: "",
76051
+ error: message,
76052
+ startedAt,
76053
+ completedAt: (/* @__PURE__ */ new Date()).toISOString()
76054
+ };
76055
+ }
76056
+ }
75668
76057
  try {
75669
76058
  const { stdout, stderr } = await execAsync6(command, {
75670
76059
  timeout: timeoutMs ?? DEFAULT_TIMEOUT_MS7,
@@ -76456,6 +76845,13 @@ var init_self_healing = __esm({
76456
76845
  * stale in-progress/planning tasks that no longer have a live worker.
76457
76846
  */
76458
76847
  async runStartupRecovery() {
76848
+ const settings = await this.store.getSettings();
76849
+ if (settings.globalPause || settings.enginePaused) {
76850
+ log16.log(
76851
+ `Startup recovery skipped \u2014 ${settings.globalPause ? "global pause" : "engine pause"} is active`
76852
+ );
76853
+ return;
76854
+ }
76459
76855
  const steps = [
76460
76856
  { name: "no-progress-no-task-done", fn: () => this.recoverNoProgressNoTaskDoneFailures().then(() => void 0) },
76461
76857
  { name: "completed-tasks", fn: () => this.recoverCompletedTasks().then(() => void 0) },
@@ -76806,27 +77202,34 @@ var init_self_healing = __esm({
76806
77202
  log16.error(`Maintenance batch 1 step "${fn.name}" failed: ${stepErr instanceof Error ? stepErr.message : String(stepErr)}`);
76807
77203
  }
76808
77204
  }
76809
- const batch2Fns = [
76810
- { name: "recover-completed-tasks", fn: () => this.recoverCompletedTasks() },
76811
- { name: "recover-stale-incomplete-review", fn: () => this.recoverStaleIncompleteReviewTasks() },
76812
- { name: "recover-failed-pre-merge-steps", fn: () => this.recoverReviewTasksWithFailedPreMergeSteps() },
76813
- { name: "recover-interrupted-merging", fn: () => this.recoverInterruptedMergingTasks() },
76814
- { name: "recover-mergeable-review", fn: () => this.recoverMergeableReviewTasks() },
76815
- { name: "recover-merged-review", fn: () => this.recoverMergedReviewTasks() },
76816
- { name: "recover-misclassified-failures", fn: () => this.recoverMisclassifiedFailures() },
76817
- { name: "recover-no-progress-no-task-done", fn: () => this.recoverNoProgressNoTaskDoneFailures() },
76818
- { name: "recover-partial-progress-no-task-done", fn: () => this.recoverPartialProgressNoTaskDoneFailures() },
76819
- { name: "recover-orphaned-executions", fn: () => this.recoverOrphanedExecutions() },
76820
- { name: "recover-approved-triage", fn: () => this.recoverApprovedTriageTasks() },
76821
- { name: "recover-orphaned-planning", fn: () => this.recoverOrphanedPlanningTasks() },
76822
- { name: "recover-ghost-review", fn: () => this.recoverGhostReviewTasks() }
76823
- ];
76824
- for (const fn of batch2Fns) {
76825
- try {
76826
- await fn.fn();
76827
- log16.log(`Maintenance batch 2 step "${fn.name}" succeeded`);
76828
- } catch (stepErr) {
76829
- log16.error(`Maintenance batch 2 step "${fn.name}" failed: ${stepErr instanceof Error ? stepErr.message : String(stepErr)}`);
77205
+ const recoverySettings = await this.store.getSettings();
77206
+ if (recoverySettings.globalPause || recoverySettings.enginePaused) {
77207
+ log16.log(
77208
+ `Maintenance batch 2 skipped \u2014 ${recoverySettings.globalPause ? "global pause" : "engine pause"} is active`
77209
+ );
77210
+ } else {
77211
+ const batch2Fns = [
77212
+ { name: "recover-completed-tasks", fn: () => this.recoverCompletedTasks() },
77213
+ { name: "recover-stale-incomplete-review", fn: () => this.recoverStaleIncompleteReviewTasks() },
77214
+ { name: "recover-failed-pre-merge-steps", fn: () => this.recoverReviewTasksWithFailedPreMergeSteps() },
77215
+ { name: "recover-interrupted-merging", fn: () => this.recoverInterruptedMergingTasks() },
77216
+ { name: "recover-mergeable-review", fn: () => this.recoverMergeableReviewTasks() },
77217
+ { name: "recover-merged-review", fn: () => this.recoverMergedReviewTasks() },
77218
+ { name: "recover-misclassified-failures", fn: () => this.recoverMisclassifiedFailures() },
77219
+ { name: "recover-no-progress-no-task-done", fn: () => this.recoverNoProgressNoTaskDoneFailures() },
77220
+ { name: "recover-partial-progress-no-task-done", fn: () => this.recoverPartialProgressNoTaskDoneFailures() },
77221
+ { name: "recover-orphaned-executions", fn: () => this.recoverOrphanedExecutions() },
77222
+ { name: "recover-approved-triage", fn: () => this.recoverApprovedTriageTasks() },
77223
+ { name: "recover-orphaned-planning", fn: () => this.recoverOrphanedPlanningTasks() },
77224
+ { name: "recover-ghost-review", fn: () => this.recoverGhostReviewTasks() }
77225
+ ];
77226
+ for (const fn of batch2Fns) {
77227
+ try {
77228
+ await fn.fn();
77229
+ log16.log(`Maintenance batch 2 step "${fn.name}" succeeded`);
77230
+ } catch (stepErr) {
77231
+ log16.error(`Maintenance batch 2 step "${fn.name}" failed: ${stepErr instanceof Error ? stepErr.message : String(stepErr)}`);
77232
+ }
76830
77233
  }
76831
77234
  }
76832
77235
  const batch3Fns = [
@@ -77892,10 +78295,18 @@ var init_plugin_runner = __esm({
77892
78295
  cachedRoutes = null;
77893
78296
  cachedUiSlots = null;
77894
78297
  cachedRuntimes = null;
78298
+ cachedSkills = null;
78299
+ cachedWorkflowSteps = null;
78300
+ cachedPromptContributions = null;
78301
+ cachedSetupInfo = null;
77895
78302
  toolsCacheVersion = 0;
77896
78303
  routesCacheVersion = 0;
77897
78304
  uiSlotsCacheVersion = 0;
77898
78305
  runtimesCacheVersion = 0;
78306
+ skillsCacheVersion = 0;
78307
+ workflowStepsCacheVersion = 0;
78308
+ promptContributionsCacheVersion = 0;
78309
+ setupCacheVersion = 0;
77899
78310
  hookTimeoutMs;
77900
78311
  // Event handler references for cleanup
77901
78312
  handlePluginEnabled;
@@ -77927,6 +78338,10 @@ var init_plugin_runner = __esm({
77927
78338
  this.invalidateRoutesCache();
77928
78339
  this.invalidateUiSlotsCache();
77929
78340
  this.invalidateRuntimesCache();
78341
+ this.invalidateSkillsCache();
78342
+ this.invalidateWorkflowStepsCache();
78343
+ this.invalidatePromptContributionsCache();
78344
+ this.invalidateSetupCache();
77930
78345
  }
77931
78346
  /**
77932
78347
  * Shutdown the plugin runner.
@@ -78007,6 +78422,54 @@ var init_plugin_runner = __esm({
78007
78422
  }
78008
78423
  return this.cachedRuntimes.runtimes;
78009
78424
  }
78425
+ getPluginSkills() {
78426
+ if (!this.cachedSkills || this.cachedSkills.version !== this.skillsCacheVersion) {
78427
+ this.cachedSkills = {
78428
+ skills: this.options.pluginLoader.getPluginSkills(),
78429
+ version: this.skillsCacheVersion
78430
+ };
78431
+ }
78432
+ return this.cachedSkills.skills;
78433
+ }
78434
+ getPluginWorkflowSteps() {
78435
+ if (!this.cachedWorkflowSteps || this.cachedWorkflowSteps.version !== this.workflowStepsCacheVersion) {
78436
+ this.cachedWorkflowSteps = {
78437
+ steps: this.options.pluginLoader.getPluginWorkflowSteps(),
78438
+ version: this.workflowStepsCacheVersion
78439
+ };
78440
+ }
78441
+ return this.cachedWorkflowSteps.steps;
78442
+ }
78443
+ getPluginPromptContributions() {
78444
+ if (!this.cachedPromptContributions || this.cachedPromptContributions.version !== this.promptContributionsCacheVersion) {
78445
+ this.cachedPromptContributions = {
78446
+ contributions: this.options.pluginLoader.getPluginPromptContributions(),
78447
+ version: this.promptContributionsCacheVersion
78448
+ };
78449
+ }
78450
+ return this.cachedPromptContributions.contributions;
78451
+ }
78452
+ getPluginSetupInfo() {
78453
+ if (!this.cachedSetupInfo || this.cachedSetupInfo.version !== this.setupCacheVersion) {
78454
+ this.cachedSetupInfo = {
78455
+ setups: this.options.pluginLoader.getPluginSetupInfo(),
78456
+ version: this.setupCacheVersion
78457
+ };
78458
+ }
78459
+ return this.cachedSetupInfo.setups;
78460
+ }
78461
+ getPromptContributionsForSurface(surface) {
78462
+ return this.getPluginPromptContributions().filter(({ pluginId, contribution, config }) => {
78463
+ const plugin4 = this.options.pluginLoader.getPlugin(pluginId);
78464
+ if (!plugin4 || plugin4.state !== "started") {
78465
+ return false;
78466
+ }
78467
+ if (contribution.surface !== surface) {
78468
+ return false;
78469
+ }
78470
+ return config.enabledByDefault !== false;
78471
+ });
78472
+ }
78010
78473
  /**
78011
78474
  * Get a specific runtime registration by its runtimeId.
78012
78475
  *
@@ -78040,6 +78503,10 @@ var init_plugin_runner = __esm({
78040
78503
  this.invalidateRoutesCache();
78041
78504
  this.invalidateUiSlotsCache();
78042
78505
  this.invalidateRuntimesCache();
78506
+ this.invalidateSkillsCache();
78507
+ this.invalidateWorkflowStepsCache();
78508
+ this.invalidatePromptContributionsCache();
78509
+ this.invalidateSetupCache();
78043
78510
  executorLog.log(`Plugin ${pluginId} reloaded`);
78044
78511
  }
78045
78512
  // ── Event Handlers for Hot-Load/Unload ─────────────────────────
@@ -78051,6 +78518,10 @@ var init_plugin_runner = __esm({
78051
78518
  this.invalidateRoutesCache();
78052
78519
  this.invalidateUiSlotsCache();
78053
78520
  this.invalidateRuntimesCache();
78521
+ this.invalidateSkillsCache();
78522
+ this.invalidateWorkflowStepsCache();
78523
+ this.invalidatePromptContributionsCache();
78524
+ this.invalidateSetupCache();
78054
78525
  try {
78055
78526
  executorLog.log(`Auto-loading enabled plugin: ${plugin4.id}`);
78056
78527
  await this.options.pluginLoader.loadPlugin(plugin4.id);
@@ -78066,6 +78537,10 @@ var init_plugin_runner = __esm({
78066
78537
  this.invalidateRoutesCache();
78067
78538
  this.invalidateUiSlotsCache();
78068
78539
  this.invalidateRuntimesCache();
78540
+ this.invalidateSkillsCache();
78541
+ this.invalidateWorkflowStepsCache();
78542
+ this.invalidatePromptContributionsCache();
78543
+ this.invalidateSetupCache();
78069
78544
  try {
78070
78545
  executorLog.log(`Auto-stopping disabled plugin: ${plugin4.id}`);
78071
78546
  await this.options.pluginLoader.stopPlugin(plugin4.id);
@@ -78081,6 +78556,10 @@ var init_plugin_runner = __esm({
78081
78556
  this.invalidateRoutesCache();
78082
78557
  this.invalidateUiSlotsCache();
78083
78558
  this.invalidateRuntimesCache();
78559
+ this.invalidateSkillsCache();
78560
+ this.invalidateWorkflowStepsCache();
78561
+ this.invalidatePromptContributionsCache();
78562
+ this.invalidateSetupCache();
78084
78563
  try {
78085
78564
  executorLog.log(`Stopping unregistered plugin: ${plugin4.id}`);
78086
78565
  await this.options.pluginLoader.stopPlugin(plugin4.id);
@@ -78097,6 +78576,10 @@ var init_plugin_runner = __esm({
78097
78576
  this.invalidateRoutesCache();
78098
78577
  this.invalidateUiSlotsCache();
78099
78578
  this.invalidateRuntimesCache();
78579
+ this.invalidateSkillsCache();
78580
+ this.invalidateWorkflowStepsCache();
78581
+ this.invalidatePromptContributionsCache();
78582
+ this.invalidateSetupCache();
78100
78583
  }
78101
78584
  /**
78102
78585
  * Handle plugin updates - invalidate caches.
@@ -78106,6 +78589,10 @@ var init_plugin_runner = __esm({
78106
78589
  this.invalidateRoutesCache();
78107
78590
  this.invalidateUiSlotsCache();
78108
78591
  this.invalidateRuntimesCache();
78592
+ this.invalidateSkillsCache();
78593
+ this.invalidateWorkflowStepsCache();
78594
+ this.invalidatePromptContributionsCache();
78595
+ this.invalidateSetupCache();
78109
78596
  }
78110
78597
  /**
78111
78598
  * Handle plugin:loaded event from loader - invalidate caches.
@@ -78115,6 +78602,10 @@ var init_plugin_runner = __esm({
78115
78602
  this.invalidateRoutesCache();
78116
78603
  this.invalidateUiSlotsCache();
78117
78604
  this.invalidateRuntimesCache();
78605
+ this.invalidateSkillsCache();
78606
+ this.invalidateWorkflowStepsCache();
78607
+ this.invalidatePromptContributionsCache();
78608
+ this.invalidateSetupCache();
78118
78609
  }
78119
78610
  /**
78120
78611
  * Handle plugin:unloaded event from loader - invalidate caches.
@@ -78124,6 +78615,10 @@ var init_plugin_runner = __esm({
78124
78615
  this.invalidateRoutesCache();
78125
78616
  this.invalidateUiSlotsCache();
78126
78617
  this.invalidateRuntimesCache();
78618
+ this.invalidateSkillsCache();
78619
+ this.invalidateWorkflowStepsCache();
78620
+ this.invalidatePromptContributionsCache();
78621
+ this.invalidateSetupCache();
78127
78622
  }
78128
78623
  /**
78129
78624
  * Handle plugin:reloaded event from loader - invalidate caches.
@@ -78133,6 +78628,10 @@ var init_plugin_runner = __esm({
78133
78628
  this.invalidateRoutesCache();
78134
78629
  this.invalidateUiSlotsCache();
78135
78630
  this.invalidateRuntimesCache();
78631
+ this.invalidateSkillsCache();
78632
+ this.invalidateWorkflowStepsCache();
78633
+ this.invalidatePromptContributionsCache();
78634
+ this.invalidateSetupCache();
78136
78635
  }
78137
78636
  // ── Tool Conversion ───────────────────────────────────────────────
78138
78637
  /**
@@ -78304,6 +78803,22 @@ var init_plugin_runner = __esm({
78304
78803
  this.runtimesCacheVersion++;
78305
78804
  this.log.log(`Runtimes cache invalidated (version: ${this.runtimesCacheVersion})`);
78306
78805
  }
78806
+ invalidateSkillsCache() {
78807
+ this.skillsCacheVersion++;
78808
+ this.log.log(`Skills cache invalidated (version: ${this.skillsCacheVersion})`);
78809
+ }
78810
+ invalidateWorkflowStepsCache() {
78811
+ this.workflowStepsCacheVersion++;
78812
+ this.log.log(`Workflow steps cache invalidated (version: ${this.workflowStepsCacheVersion})`);
78813
+ }
78814
+ invalidatePromptContributionsCache() {
78815
+ this.promptContributionsCacheVersion++;
78816
+ this.log.log(`Prompt contributions cache invalidated (version: ${this.promptContributionsCacheVersion})`);
78817
+ }
78818
+ invalidateSetupCache() {
78819
+ this.setupCacheVersion++;
78820
+ this.log.log(`Setup cache invalidated (version: ${this.setupCacheVersion})`);
78821
+ }
78307
78822
  // ── Store Event Subscriptions ────────────────────────────────────
78308
78823
  /**
78309
78824
  * Subscribe to TaskStore events for task lifecycle hooks.
@@ -78447,6 +78962,10 @@ var init_in_process_runtime = __esm({
78447
78962
  * before `start()` via `setMergeEnqueuer`.
78448
78963
  */
78449
78964
  mergeEnqueuer;
78965
+ /** Tracks whether startup recovery was intentionally deferred due to pause state. */
78966
+ startupRecoveryDeferred = false;
78967
+ /** Prevent duplicate unpause recovery dispatches from racing each other. */
78968
+ resumeAfterUnpauseRunning = false;
78450
78969
  /**
78451
78970
  * Start the runtime and initialize all subsystems.
78452
78971
  *
@@ -78480,7 +78999,7 @@ var init_in_process_runtime = __esm({
78480
78999
  runtimeLog.log(`TaskStore initialized for project ${this.config.projectId}`);
78481
79000
  }
78482
79001
  this.messageStore = new MessageStoreClass(this.taskStore.getDatabase());
78483
- this.pluginStore = new PluginStoreClass(this.taskStore.getFusionDir());
79002
+ this.pluginStore = new PluginStoreClass(this.config.workingDirectory);
78484
79003
  await this.pluginStore.init();
78485
79004
  this.pluginLoader = new PluginLoaderClass({
78486
79005
  pluginStore: this.pluginStore,
@@ -78872,11 +79391,16 @@ var init_in_process_runtime = __esm({
78872
79391
  this.selfHealingManager.start();
78873
79392
  this.stuckTaskDetector.start();
78874
79393
  this.setupEventForwarding();
78875
- await this.selfHealingManager.recoverNoProgressNoTaskDoneFailures();
78876
- await this.executor.resumeOrphaned();
78877
- void this.selfHealingManager.runStartupRecovery().catch((err) => {
78878
- runtimeLog.error("Self-healing startup recovery failed:", err);
78879
- });
79394
+ const startupSettings = await this.taskStore.getSettings();
79395
+ if (startupSettings.globalPause || startupSettings.enginePaused) {
79396
+ this.startupRecoveryDeferred = true;
79397
+ runtimeLog.log(
79398
+ `Startup recovery deferred \u2014 ${startupSettings.globalPause ? "global pause" : "engine pause"} is active`
79399
+ );
79400
+ } else {
79401
+ this.startupRecoveryDeferred = false;
79402
+ await this.resumeStartupRecoverySequence();
79403
+ }
78880
79404
  this.scheduler.start();
78881
79405
  this.triageProcessor?.start();
78882
79406
  this.missionExecutionLoop = missionExecutionLoop;
@@ -79042,6 +79566,45 @@ var init_in_process_runtime = __esm({
79042
79566
  setMergeEnqueuer(enqueueMerge) {
79043
79567
  this.mergeEnqueuer = enqueueMerge;
79044
79568
  }
79569
+ /**
79570
+ * Resume executor/self-healing activity after an unpause transition.
79571
+ *
79572
+ * When startup recovery had been deferred, this replays the original startup
79573
+ * ordering so orphan resume and self-healing cannot race each other.
79574
+ */
79575
+ async resumeAfterUnpause() {
79576
+ if (!this.taskStore || !this.executor || !this.selfHealingManager) {
79577
+ return;
79578
+ }
79579
+ if (this.resumeAfterUnpauseRunning) {
79580
+ return;
79581
+ }
79582
+ this.resumeAfterUnpauseRunning = true;
79583
+ try {
79584
+ const settings = await this.taskStore.getSettings();
79585
+ if (settings.globalPause || settings.enginePaused) {
79586
+ runtimeLog.log(
79587
+ `Unpause recovery still blocked \u2014 ${settings.globalPause ? "global pause" : "engine pause"} remains active`
79588
+ );
79589
+ return;
79590
+ }
79591
+ if (this.startupRecoveryDeferred) {
79592
+ await this.resumeStartupRecoverySequence();
79593
+ this.startupRecoveryDeferred = false;
79594
+ return;
79595
+ }
79596
+ await this.executor.resumeOrphaned();
79597
+ } finally {
79598
+ this.resumeAfterUnpauseRunning = false;
79599
+ }
79600
+ }
79601
+ async resumeStartupRecoverySequence() {
79602
+ await this.selfHealingManager.recoverNoProgressNoTaskDoneFailures();
79603
+ await this.executor.resumeOrphaned();
79604
+ void this.selfHealingManager.runStartupRecovery().catch((err) => {
79605
+ runtimeLog.error("Self-healing startup recovery failed:", err);
79606
+ });
79607
+ }
79045
79608
  /**
79046
79609
  * Get the project's TaskStore instance.
79047
79610
  * @throws Error if runtime has not been started
@@ -82881,13 +83444,13 @@ ${detail}`
82881
83444
  if (prev.globalPause && !s.globalPause) {
82882
83445
  runtimeLog.log("Global unpause \u2014 resuming agentic activity");
82883
83446
  try {
82884
- const executor = this.runtime.executor;
82885
- executor?.resumeOrphaned?.().catch(
82886
- (err) => runtimeLog.error("Failed to resume orphaned tasks on unpause:", err)
83447
+ const runtime = this.runtime;
83448
+ runtime.resumeAfterUnpause?.().catch(
83449
+ (err) => runtimeLog.error("Failed to resume agentic activity on unpause:", err)
82887
83450
  );
82888
83451
  } catch (err) {
82889
83452
  runtimeLog.warn(
82890
- `Global unpause: failed to dispatch resumeOrphaned: ${err instanceof Error ? err.message : String(err)}`
83453
+ `Global unpause: failed to dispatch resumeAfterUnpause: ${err instanceof Error ? err.message : String(err)}`
82891
83454
  );
82892
83455
  }
82893
83456
  if (s.autoMerge) {
@@ -82911,13 +83474,13 @@ ${detail}`
82911
83474
  if (prev.enginePaused && !s.enginePaused) {
82912
83475
  runtimeLog.log("Engine unpaused \u2014 resuming agentic activity");
82913
83476
  try {
82914
- const executor = this.runtime.executor;
82915
- executor?.resumeOrphaned?.().catch(
82916
- (err) => runtimeLog.error("Failed to resume orphaned tasks on engine unpause:", err)
83477
+ const runtime = this.runtime;
83478
+ runtime.resumeAfterUnpause?.().catch(
83479
+ (err) => runtimeLog.error("Failed to resume agentic activity on engine unpause:", err)
82917
83480
  );
82918
83481
  } catch (err) {
82919
83482
  runtimeLog.warn(
82920
- `Engine unpause: failed to dispatch resumeOrphaned: ${err instanceof Error ? err.message : String(err)}`
83483
+ `Engine unpause: failed to dispatch resumeAfterUnpause: ${err instanceof Error ? err.message : String(err)}`
82921
83484
  );
82922
83485
  }
82923
83486
  if (s.autoMerge) {
@@ -96746,7 +97309,7 @@ var require_websocket = __commonJS({
96746
97309
  var http = __require("http");
96747
97310
  var net = __require("net");
96748
97311
  var tls = __require("tls");
96749
- var { randomBytes: randomBytes3, createHash: createHash5 } = __require("crypto");
97312
+ var { randomBytes: randomBytes3, createHash: createHash6 } = __require("crypto");
96750
97313
  var { Duplex, Readable } = __require("stream");
96751
97314
  var { URL: URL2 } = __require("url");
96752
97315
  var PerMessageDeflate2 = require_permessage_deflate();
@@ -97406,7 +97969,7 @@ var require_websocket = __commonJS({
97406
97969
  abortHandshake(websocket, socket, "Invalid Upgrade header");
97407
97970
  return;
97408
97971
  }
97409
- const digest = createHash5("sha1").update(key + GUID).digest("base64");
97972
+ const digest = createHash6("sha1").update(key + GUID).digest("base64");
97410
97973
  if (res.headers["sec-websocket-accept"] !== digest) {
97411
97974
  abortHandshake(websocket, socket, "Invalid Sec-WebSocket-Accept header");
97412
97975
  return;
@@ -97773,7 +98336,7 @@ var require_websocket_server = __commonJS({
97773
98336
  var EventEmitter31 = __require("events");
97774
98337
  var http = __require("http");
97775
98338
  var { Duplex } = __require("stream");
97776
- var { createHash: createHash5 } = __require("crypto");
98339
+ var { createHash: createHash6 } = __require("crypto");
97777
98340
  var extension2 = require_extension();
97778
98341
  var PerMessageDeflate2 = require_permessage_deflate();
97779
98342
  var subprotocol2 = require_subprotocol();
@@ -98074,7 +98637,7 @@ var require_websocket_server = __commonJS({
98074
98637
  );
98075
98638
  }
98076
98639
  if (this._state > RUNNING) return abortHandshake(socket, 503);
98077
- const digest = createHash5("sha1").update(key + GUID).digest("base64");
98640
+ const digest = createHash6("sha1").update(key + GUID).digest("base64");
98078
98641
  const headers = [
98079
98642
  "HTTP/1.1 101 Switching Protocols",
98080
98643
  "Upgrade: websocket",