@runfusion/fusion 0.0.4 → 0.0.6

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.
@@ -78,11 +78,11 @@
78
78
  }
79
79
  })();
80
80
  </script>
81
- <script type="module" crossorigin src="/assets/index-wtVkS5Qz.js"></script>
81
+ <script type="module" crossorigin src="/assets/index-zTogbMzz.js"></script>
82
82
  <link rel="modulepreload" crossorigin href="/assets/vendor-react-K0fH_qHe.js">
83
83
  <link rel="modulepreload" crossorigin href="/assets/vendor-xterm-DzcZoU0P.js">
84
84
  <link rel="stylesheet" crossorigin href="/assets/vendor-xterm-LZoznX6r.css">
85
- <link rel="stylesheet" crossorigin href="/assets/index-9GdD09x7.css">
85
+ <link rel="stylesheet" crossorigin href="/assets/index-CYkQfLYV.css">
86
86
  </head>
87
87
  <body>
88
88
  <div id="root"></div>
package/dist/extension.js CHANGED
@@ -133,6 +133,8 @@ var init_settings_schema = __esm({
133
133
  defaultPresetBySize: {},
134
134
  autoResolveConflicts: true,
135
135
  smartConflictResolution: true,
136
+ worktreeRebaseBeforeMerge: true,
137
+ worktreeRebaseRemote: "",
136
138
  strictScopeEnforcement: false,
137
139
  buildRetryCount: 0,
138
140
  verificationFixRetries: 1,
@@ -3509,13 +3511,25 @@ import { mkdir, readFile, writeFile, readdir, unlink, rename } from "node:fs/pro
3509
3511
  import { basename, join as join2, resolve } from "node:path";
3510
3512
  import { randomUUID, randomBytes, createHash } from "node:crypto";
3511
3513
  import { EventEmitter } from "node:events";
3512
- var AgentStore;
3514
+ function resolveCreationRuntimeConfig(incoming, metadata) {
3515
+ const isEphemeral = isEphemeralAgent({ metadata });
3516
+ if (isEphemeral) {
3517
+ return incoming;
3518
+ }
3519
+ const rc = { ...incoming ?? {} };
3520
+ if (typeof rc.heartbeatIntervalMs !== "number" || !Number.isFinite(rc.heartbeatIntervalMs)) {
3521
+ rc.heartbeatIntervalMs = DEFAULT_AGENT_HEARTBEAT_INTERVAL_MS;
3522
+ }
3523
+ return rc;
3524
+ }
3525
+ var DEFAULT_AGENT_HEARTBEAT_INTERVAL_MS, AgentStore;
3513
3526
  var init_agent_store = __esm({
3514
3527
  "../core/src/agent-store.ts"() {
3515
3528
  "use strict";
3516
3529
  init_types();
3517
3530
  init_agent_permissions();
3518
3531
  init_db();
3532
+ DEFAULT_AGENT_HEARTBEAT_INTERVAL_MS = 36e5;
3519
3533
  AgentStore = class extends EventEmitter {
3520
3534
  rootDir;
3521
3535
  agentsDir;
@@ -3654,6 +3668,16 @@ var init_agent_store = __esm({
3654
3668
  }
3655
3669
  /**
3656
3670
  * Create a new agent with "idle" state.
3671
+ *
3672
+ * For non-ephemeral agents, ensures `runtimeConfig.heartbeatIntervalMs` is
3673
+ * persisted at creation time — previously it was only ever written when the
3674
+ * user interacted with the dashboard dropdown, so agents created and never
3675
+ * touched would end up with no interval on disk. That made the dashboard's
3676
+ * freshness check behave inconsistently between agents that had been
3677
+ * configured and agents that hadn't, even though the scheduler applied the
3678
+ * same default (1h) to both at runtime. Writing the default explicitly
3679
+ * removes that divergence and keeps the persisted config truthful.
3680
+ *
3657
3681
  * @param input - Creation parameters
3658
3682
  * @returns The created agent
3659
3683
  * @throws Error if input is invalid
@@ -3667,6 +3691,8 @@ var init_agent_store = __esm({
3667
3691
  }
3668
3692
  const now = (/* @__PURE__ */ new Date()).toISOString();
3669
3693
  const agentId = `agent-${randomUUID().slice(0, 8)}`;
3694
+ const metadata = input.metadata ?? {};
3695
+ const runtimeConfig = resolveCreationRuntimeConfig(input.runtimeConfig, metadata);
3670
3696
  const agent = {
3671
3697
  id: agentId,
3672
3698
  name: input.name.trim(),
@@ -3674,11 +3700,11 @@ var init_agent_store = __esm({
3674
3700
  state: "idle",
3675
3701
  createdAt: now,
3676
3702
  updatedAt: now,
3677
- metadata: input.metadata ?? {},
3703
+ metadata,
3678
3704
  ...input.title && { title: input.title },
3679
3705
  ...input.icon && { icon: input.icon },
3680
3706
  ...input.reportsTo && { reportsTo: input.reportsTo },
3681
- ...input.runtimeConfig && { runtimeConfig: input.runtimeConfig },
3707
+ ...runtimeConfig && { runtimeConfig },
3682
3708
  ...input.permissions && { permissions: input.permissions },
3683
3709
  ...input.instructionsPath && { instructionsPath: input.instructionsPath },
3684
3710
  ...input.instructionsText && { instructionsText: input.instructionsText },
@@ -30524,6 +30550,7 @@ ${newTask.description}
30524
30550
  }
30525
30551
  }
30526
30552
  if (updates.steps !== void 0) task.steps = updates.steps;
30553
+ if (updates.currentStep !== void 0) task.currentStep = updates.currentStep;
30527
30554
  if (updates.status === null) {
30528
30555
  task.status = void 0;
30529
30556
  } else if (updates.status !== void 0) {
@@ -47048,6 +47075,7 @@ __export(src_exports, {
47048
47075
  CheckoutConflictError: () => CheckoutConflictError,
47049
47076
  DAEMON_TOKEN_HEX_LENGTH: () => DAEMON_TOKEN_HEX_LENGTH,
47050
47077
  DAEMON_TOKEN_PREFIX: () => DAEMON_TOKEN_PREFIX,
47078
+ DEFAULT_AGENT_HEARTBEAT_INTERVAL_MS: () => DEFAULT_AGENT_HEARTBEAT_INTERVAL_MS,
47051
47079
  DEFAULT_AUTO_SUMMARIZE_SCHEDULE: () => DEFAULT_AUTO_SUMMARIZE_SCHEDULE,
47052
47080
  DEFAULT_EXECUTION_MODE: () => DEFAULT_EXECUTION_MODE,
47053
47081
  DEFAULT_GLOBAL_SETTINGS: () => DEFAULT_GLOBAL_SETTINGS,
@@ -53395,6 +53423,71 @@ async function aiMergeTask(store, rootDir, taskId, options = {}) {
53395
53423
  mergerLog.warn(`${taskId}: unable to verify/checkout main branch \u2014 proceeding on current HEAD`);
53396
53424
  }
53397
53425
  }
53426
+ if (settings.worktreeRebaseBeforeMerge !== false) {
53427
+ try {
53428
+ let remote = settings.worktreeRebaseRemote?.trim();
53429
+ if (!remote) {
53430
+ try {
53431
+ const { stdout: mainBranchOut } = await execAsync2(
53432
+ "git rev-parse --abbrev-ref HEAD",
53433
+ { cwd: rootDir, encoding: "utf-8" }
53434
+ );
53435
+ const mainBranch = mainBranchOut.trim();
53436
+ const { stdout: configuredRemote } = await execAsync2(
53437
+ `git config --get branch.${mainBranch}.remote`,
53438
+ { cwd: rootDir, encoding: "utf-8" }
53439
+ ).catch(() => ({ stdout: "" }));
53440
+ remote = configuredRemote.trim();
53441
+ } catch {
53442
+ }
53443
+ }
53444
+ if (!remote) {
53445
+ try {
53446
+ const { stdout: remotesOut } = await execAsync2("git remote", {
53447
+ cwd: rootDir,
53448
+ encoding: "utf-8"
53449
+ });
53450
+ const remotes = remotesOut.trim().split(/\s+/).filter(Boolean);
53451
+ if (remotes.length === 1) {
53452
+ remote = remotes[0];
53453
+ } else if (remotes.includes("origin")) {
53454
+ remote = "origin";
53455
+ }
53456
+ } catch {
53457
+ }
53458
+ }
53459
+ if (!remote) {
53460
+ mergerLog.log(`${taskId}: no remote resolvable \u2014 skipping pre-merge rebase`);
53461
+ } else {
53462
+ mergerLog.log(`${taskId}: fetching ${remote} before merge`);
53463
+ await execAsync2(`git fetch "${remote}"`, { cwd: rootDir });
53464
+ try {
53465
+ const { stdout: mainBranchOut } = await execAsync2(
53466
+ "git rev-parse --abbrev-ref HEAD",
53467
+ { cwd: rootDir, encoding: "utf-8" }
53468
+ );
53469
+ const mainBranch = mainBranchOut.trim();
53470
+ const remoteRef = `${remote}/${mainBranch}`;
53471
+ if (worktreePath) {
53472
+ await execAsync2(`git rebase "${remoteRef}"`, { cwd: worktreePath });
53473
+ mergerLog.log(`${taskId}: rebased ${branch} onto ${remoteRef}`);
53474
+ } else {
53475
+ mergerLog.warn(`${taskId}: no worktreePath \u2014 skipping task branch rebase`);
53476
+ }
53477
+ } catch (rebaseErr) {
53478
+ const msg = rebaseErr instanceof Error ? rebaseErr.message : String(rebaseErr);
53479
+ mergerLog.warn(`${taskId}: pre-merge rebase failed (${msg}) \u2014 aborting rebase and falling through to smart/AI merge`);
53480
+ if (worktreePath) {
53481
+ await execAsync2("git rebase --abort", { cwd: worktreePath }).catch(() => {
53482
+ });
53483
+ }
53484
+ }
53485
+ }
53486
+ } catch (err) {
53487
+ const msg = err instanceof Error ? err.message : String(err);
53488
+ mergerLog.warn(`${taskId}: pre-merge rebase pipeline failed (${msg}) \u2014 proceeding without rebase`);
53489
+ }
53490
+ }
53398
53491
  let commitLog = "";
53399
53492
  let diffStat = "";
53400
53493
  try {
@@ -55911,19 +56004,6 @@ import { existsSync as existsSync25 } from "node:fs";
55911
56004
  import { readFile as readFile14, writeFile as writeFile11 } from "node:fs/promises";
55912
56005
  import { Type as Type4 } from "@mariozechner/pi-ai";
55913
56006
  import { ModelRegistry as ModelRegistry2, SessionManager as SessionManager2 } from "@mariozechner/pi-coding-agent";
55914
- function determineRevisionResetStart(steps, feedback) {
55915
- const total = steps.length;
55916
- if (total === 0) return 0;
55917
- const skipPreflight = /preflight/i.test(steps[0].name);
55918
- const firstCandidate = skipPreflight ? 1 : 0;
55919
- if (firstCandidate >= total) return total;
55920
- const fb = feedback.toLowerCase();
55921
- for (let i = firstCandidate; i < total; i++) {
55922
- const tokens = steps[i].name.toLowerCase().match(/[a-z][a-z]{4,}/g) ?? [];
55923
- if (tokens.some((t) => fb.includes(t))) return i;
55924
- }
55925
- return firstCandidate;
55926
- }
55927
56007
  function truncateWorkflowScriptOutput2(output) {
55928
56008
  if (output.length <= WORKFLOW_SCRIPT_OUTPUT_MAX_CHARS2) return output;
55929
56009
  return `... output truncated to last ${WORKFLOW_SCRIPT_OUTPUT_MAX_CHARS2} characters ...
@@ -58240,35 +58320,23 @@ Take a different approach. Do NOT repeat the rejected strategy. Re-read the step
58240
58320
  }
58241
58321
  /**
58242
58322
  * Handle a workflow step revision request.
58243
- *
58244
- * This method:
58245
- * 1. Updates PROMPT.md with "Workflow Revision Instructions" section
58246
- * 2. Resets task execution state (all steps reset to pending)
58247
- * 3. Schedules fresh execution to run after current guard unwinds
58248
- *
58249
- * The task stays in "in-progress" and is scheduled for a fresh executor pass.
58323
+ *
58324
+ * Re-opens ONLY the last step so the executor has exactly one pending slot
58325
+ * to re-enter through. All earlier done steps stay done — the agent reads
58326
+ * the injected feedback from PROMPT.md and applies an in-place fix rather
58327
+ * than redoing any completed step.
58250
58328
  */
58251
58329
  async handleWorkflowRevisionRequest(task, worktreePath, feedback, stepName) {
58252
58330
  executorLog.log(`${task.id}: workflow revision requested by step "${stepName}"`);
58253
58331
  const updatedTask = await this.store.getTask(task.id);
58254
- const resetStart = determineRevisionResetStart(updatedTask.steps, feedback);
58255
- const targetStepName = resetStart < updatedTask.steps.length ? updatedTask.steps[resetStart].name : null;
58256
- const resetSummary = resetStart >= updatedTask.steps.length ? "no steps to reset" : `resetting steps ${resetStart + 1}\u2013${updatedTask.steps.length} (starting at "${targetStepName}")`;
58332
+ const reopen = await this.reopenLastStepForRevision(task.id, updatedTask);
58333
+ const reopenSummary = reopen ? `re-opening Step ${reopen.index + 1} ("${reopen.name}") for in-place fix` : "no step to re-open (none were completed)";
58257
58334
  await this.store.logEntry(
58258
58335
  task.id,
58259
- `Workflow step "${stepName}" requested revision \u2014 ${resetSummary}`,
58336
+ `Workflow step "${stepName}" requested revision \u2014 ${reopenSummary}`,
58260
58337
  feedback
58261
58338
  );
58262
- await this.injectWorkflowRevisionInstructions(task, feedback, {
58263
- resetStart,
58264
- targetStepName,
58265
- totalSteps: updatedTask.steps.length
58266
- });
58267
- for (let i = resetStart; i < updatedTask.steps.length; i++) {
58268
- if (updatedTask.steps[i].status !== "pending") {
58269
- await this.store.updateStep(task.id, i, "pending");
58270
- }
58271
- }
58339
+ await this.injectWorkflowRevisionInstructions(task, feedback);
58272
58340
  await this.store.updateTask(task.id, {
58273
58341
  status: null,
58274
58342
  sessionFile: null
@@ -58290,12 +58358,36 @@ Take a different approach. Do NOT repeat the rejected strategy. Re-read the step
58290
58358
  }
58291
58359
  }, 0);
58292
58360
  }
58361
+ /**
58362
+ * Re-open the last non-pending step so a revision/failure handler gives the
58363
+ * executor exactly one pending slot to re-enter through. Returns the index
58364
+ * and name of the step that was flipped to `pending`, or null when there
58365
+ * was nothing to re-open.
58366
+ */
58367
+ async reopenLastStepForRevision(taskId, task) {
58368
+ const steps = task.steps;
58369
+ if (steps.length === 0) return null;
58370
+ let targetIndex = -1;
58371
+ for (let i = steps.length - 1; i >= 0; i--) {
58372
+ if (steps[i].status !== "pending") {
58373
+ targetIndex = i;
58374
+ break;
58375
+ }
58376
+ }
58377
+ if (targetIndex === -1) {
58378
+ await this.store.updateTask(taskId, { currentStep: 0 });
58379
+ return null;
58380
+ }
58381
+ await this.store.updateStep(taskId, targetIndex, "pending");
58382
+ await this.store.updateTask(taskId, { currentStep: targetIndex });
58383
+ return { index: targetIndex, name: steps[targetIndex].name };
58384
+ }
58293
58385
  /**
58294
58386
  * Inject or update the "Workflow Revision Instructions" section in PROMPT.md.
58295
58387
  * This section contains feedback from workflow steps that requested revisions.
58296
58388
  * The section is replaced entirely to avoid accumulation of old feedback.
58297
58389
  */
58298
- async injectWorkflowRevisionInstructions(task, feedback, scope) {
58390
+ async injectWorkflowRevisionInstructions(task, feedback) {
58299
58391
  const promptPath = join32(this.store.getFusionDir(), "tasks", task.id, "PROMPT.md");
58300
58392
  let content;
58301
58393
  try {
@@ -58304,14 +58396,7 @@ Take a different approach. Do NOT repeat the rejected strategy. Re-read the step
58304
58396
  executorLog.warn(`${task.id}: PROMPT.md not found at ${promptPath}, skipping revision injection`);
58305
58397
  return;
58306
58398
  }
58307
- let scopeLine;
58308
- if (scope && scope.targetStepName && scope.resetStart < scope.totalSteps) {
58309
- scopeLine = `Re-execution starts at **Step ${scope.resetStart + 1} ("${scope.targetStepName}")**. Earlier steps remain done \u2014 do not re-run them unless the feedback explicitly calls them out.`;
58310
- } else if (scope && scope.resetStart >= scope.totalSteps) {
58311
- scopeLine = "No steps were reset; apply the feedback as an in-place fix and call task_done() when complete.";
58312
- } else {
58313
- scopeLine = "Address the feedback above by making the necessary code changes, then mark all affected steps as done and call task_done() when complete.";
58314
- }
58399
+ const scopeLine = "All prior steps remain **done**. Apply the feedback above as an in-place fix (make the necessary code changes, commit, and call `task_done()` when complete). Do **not** re-run or re-plan any earlier step unless the feedback explicitly calls it out.";
58315
58400
  const revisionSectionHeader = "## Workflow Revision Instructions";
58316
58401
  const revisionSectionContent = `${revisionSectionHeader}
58317
58402
 
@@ -58370,11 +58455,7 @@ ${feedback}
58370
58455
  });
58371
58456
  await this.injectWorkflowStepFailureInstructions(task, failureFeedback, stepName, retryCount);
58372
58457
  const updatedTask = await this.store.getTask(task.id);
58373
- for (let i = 0; i < updatedTask.steps.length; i++) {
58374
- if (updatedTask.steps[i].status !== "pending") {
58375
- await this.store.updateStep(task.id, i, "pending");
58376
- }
58377
- }
58458
+ await this.reopenLastStepForRevision(task.id, updatedTask);
58378
58459
  await this.store.updateTask(task.id, {
58379
58460
  status: null,
58380
58461
  sessionFile: null
@@ -58418,11 +58499,7 @@ Please fix the issues so the verification can pass on the next attempt.`,
58418
58499
  );
58419
58500
  await this.injectWorkflowStepFailureInstructions(task, failureFeedback, stepName, MAX_WORKFLOW_STEP_RETRIES);
58420
58501
  const updatedTask = await this.store.getTask(taskId);
58421
- for (let i = 0; i < updatedTask.steps.length; i++) {
58422
- if (updatedTask.steps[i].status !== "pending") {
58423
- await this.store.updateStep(taskId, i, "pending");
58424
- }
58425
- }
58502
+ await this.reopenLastStepForRevision(taskId, updatedTask);
58426
58503
  await this.store.updateTask(taskId, {
58427
58504
  status: null,
58428
58505
  error: null,
@@ -64221,6 +64298,12 @@ async function getHeartbeatMemorySettings(taskStore) {
64221
64298
  }
64222
64299
  return maybeGetSettings.call(taskStore);
64223
64300
  }
64301
+ function isTickableState(state) {
64302
+ return state === "active" || state === "running";
64303
+ }
64304
+ function isHeartbeatManaged(agent) {
64305
+ return !isEphemeralAgent(agent);
64306
+ }
64224
64307
  var HEARTBEAT_SYSTEM_PROMPT, HEARTBEAT_NO_TASK_SYSTEM_PROMPT, heartbeatDoneParams, HeartbeatMonitor, HeartbeatTriggerScheduler;
64225
64308
  var init_agent_heartbeat = __esm({
64226
64309
  "../engine/src/agent-heartbeat.ts"() {
@@ -65457,10 +65540,6 @@ ${taskDetail.prompt}` : "No PROMPT.md available.",
65457
65540
  * @param config - Per-agent heartbeat config
65458
65541
  */
65459
65542
  registerAgent(agentId, config) {
65460
- if (config.enabled === false) {
65461
- heartbeatLog.log(`Skipping timer registration for ${agentId} (disabled)`);
65462
- return;
65463
- }
65464
65543
  let rawIntervalMs = config.heartbeatIntervalMs;
65465
65544
  let usingDefaultInterval = false;
65466
65545
  if (!rawIntervalMs || typeof rawIntervalMs !== "number" || !Number.isFinite(rawIntervalMs) || rawIntervalMs <= 0) {
@@ -65550,8 +65629,8 @@ ${taskDetail.prompt}` : "No PROMPT.md available.",
65550
65629
  this.assignedListener = async (agent, taskId) => {
65551
65630
  if (!this.running) return;
65552
65631
  try {
65553
- if (agent.runtimeConfig?.enabled === false) {
65554
- heartbeatLog.log(`Assignment trigger skipped for ${agent.id} (heartbeat disabled)`);
65632
+ if (!isHeartbeatManaged(agent) || !isTickableState(agent.state)) {
65633
+ heartbeatLog.log(`Assignment trigger skipped for ${agent.id} (state=${agent.state})`);
65555
65634
  return;
65556
65635
  }
65557
65636
  const activeRun = await this.store.getActiveHeartbeatRun(agent.id);
@@ -65620,9 +65699,21 @@ ${taskDetail.prompt}` : "No PROMPT.md available.",
65620
65699
  watchAgentLifecycle() {
65621
65700
  if (this.updatedListener || this.deletedListener) return;
65622
65701
  this.updatedListener = (agent) => {
65623
- if (agent.state === "terminated" || agent.runtimeConfig?.enabled === false) {
65702
+ if (!isHeartbeatManaged(agent) || !isTickableState(agent.state)) {
65624
65703
  this.unregisterAgent(agent.id);
65704
+ return;
65625
65705
  }
65706
+ if (this.timers.has(agent.id)) {
65707
+ return;
65708
+ }
65709
+ const rc = agent.runtimeConfig ?? {};
65710
+ this.registerAgent(agent.id, {
65711
+ heartbeatIntervalMs: rc.heartbeatIntervalMs,
65712
+ maxConcurrentRuns: rc.maxConcurrentRuns
65713
+ });
65714
+ heartbeatLog.log(
65715
+ `State-driven registration: ${agent.id} is ${agent.state} \u2014 timer armed`
65716
+ );
65626
65717
  };
65627
65718
  this.deletedListener = (agentId) => {
65628
65719
  this.unregisterAgent(agentId);
@@ -65653,8 +65744,8 @@ ${taskDetail.prompt}` : "No PROMPT.md available.",
65653
65744
  this.unregisterAgent(agentId);
65654
65745
  return;
65655
65746
  }
65656
- if (agent.state === "terminated" || agent.runtimeConfig?.enabled === false) {
65657
- heartbeatLog.log(`Timer tick skipped for ${agentId} (disabled or terminated)`);
65747
+ if (!isHeartbeatManaged(agent) || !isTickableState(agent.state)) {
65748
+ heartbeatLog.log(`Timer tick skipped for ${agentId} (state=${agent.state})`);
65658
65749
  this.unregisterAgent(agentId);
65659
65750
  return;
65660
65751
  }
@@ -67859,13 +67950,13 @@ var init_in_process_runtime = __esm({
67859
67950
  this.taskStore
67860
67951
  );
67861
67952
  this.triggerScheduler.start();
67953
+ const isTickable = (agent) => !isEphemeralAgent(agent) && (agent.state === "active" || agent.state === "running");
67862
67954
  this.agentCreatedListener = (agent) => {
67863
67955
  if (!this.triggerScheduler) return;
67956
+ if (!isTickable(agent)) return;
67864
67957
  const rc = agent.runtimeConfig;
67865
- if (rc?.enabled === false) return;
67866
67958
  this.triggerScheduler.registerAgent(agent.id, {
67867
67959
  heartbeatIntervalMs: rc?.heartbeatIntervalMs,
67868
- enabled: rc?.enabled,
67869
67960
  maxConcurrentRuns: rc?.maxConcurrentRuns
67870
67961
  });
67871
67962
  runtimeLog.log(`Registered new agent ${agent.id} for heartbeat triggers`);
@@ -67873,18 +67964,17 @@ var init_in_process_runtime = __esm({
67873
67964
  this.agentStore.on("agent:created", this.agentCreatedListener);
67874
67965
  this.agentUpdatedListener = (agent) => {
67875
67966
  if (!this.triggerScheduler) return;
67876
- const rc = agent.runtimeConfig;
67877
- if (rc?.enabled === false) {
67967
+ if (!isTickable(agent)) {
67878
67968
  this.triggerScheduler.unregisterAgent(agent.id);
67879
- runtimeLog.log(`Unregistered agent ${agent.id} from heartbeat triggers (disabled)`);
67880
- } else {
67881
- this.triggerScheduler.registerAgent(agent.id, {
67882
- heartbeatIntervalMs: rc?.heartbeatIntervalMs,
67883
- enabled: rc?.enabled,
67884
- maxConcurrentRuns: rc?.maxConcurrentRuns
67885
- });
67886
- runtimeLog.log(`Re-registered agent ${agent.id} for heartbeat triggers`);
67969
+ runtimeLog.log(`Unregistered agent ${agent.id} from heartbeat triggers (state=${agent.state})`);
67970
+ return;
67887
67971
  }
67972
+ const rc = agent.runtimeConfig;
67973
+ this.triggerScheduler.registerAgent(agent.id, {
67974
+ heartbeatIntervalMs: rc?.heartbeatIntervalMs,
67975
+ maxConcurrentRuns: rc?.maxConcurrentRuns
67976
+ });
67977
+ runtimeLog.log(`Re-registered agent ${agent.id} for heartbeat triggers (state=${agent.state})`);
67888
67978
  };
67889
67979
  this.agentStore.on("agent:updated", this.agentUpdatedListener);
67890
67980
  this.ephemeralTerminationListener = (agentId, from, to) => {
@@ -67919,15 +68009,13 @@ var init_in_process_runtime = __esm({
67919
68009
  const agents = await this.agentStore.listAgents();
67920
68010
  let registeredCount = 0;
67921
68011
  for (const agent of agents) {
68012
+ if (!isTickable(agent)) continue;
67922
68013
  const rc = agent.runtimeConfig;
67923
- if (rc?.enabled !== false) {
67924
- this.triggerScheduler.registerAgent(agent.id, {
67925
- heartbeatIntervalMs: rc?.heartbeatIntervalMs,
67926
- enabled: rc?.enabled,
67927
- maxConcurrentRuns: rc?.maxConcurrentRuns
67928
- });
67929
- registeredCount++;
67930
- }
68014
+ this.triggerScheduler.registerAgent(agent.id, {
68015
+ heartbeatIntervalMs: rc?.heartbeatIntervalMs,
68016
+ maxConcurrentRuns: rc?.maxConcurrentRuns
68017
+ });
68018
+ registeredCount++;
67931
68019
  }
67932
68020
  if (agents.length > 0) {
67933
68021
  runtimeLog.log(`Registered ${registeredCount} of ${agents.length} agents for heartbeat triggers`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@runfusion/fusion",
3
- "version": "0.0.4",
3
+ "version": "0.0.6",
4
4
  "license": "MIT",
5
5
  "description": "Fusion CLI: HTTP API server, daemon, dashboard launcher, and task tooling for the Fusion AI coding agent.",
6
6
  "homepage": "https://github.com/Runfusion/Fusion#readme",
@@ -69,8 +69,8 @@
69
69
  "vitest": "^3.1.0",
70
70
  "yaml": "^2.8.3",
71
71
  "@fusion/core": "0.1.0",
72
- "@fusion/engine": "0.1.0",
73
- "@fusion/dashboard": "0.1.0"
72
+ "@fusion/dashboard": "0.1.0",
73
+ "@fusion/engine": "0.1.0"
74
74
  },
75
75
  "repository": {
76
76
  "type": "git",